diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp index 4ad3452..b89b96f 100644 --- a/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp @@ -20,7 +20,6 @@ // MSL shaders (Metal Shading Language) — macOS // ============================================================================ -// NOLINTBEGIN(readability-identifier-naming) static const char* POSTFX_VERT_MSL = R"( #include using namespace metal; @@ -360,7 +359,6 @@ fragment float4 crtpi_fs(PostVOut in [[stage_in]], return float4(colour, 1.0f); } )"; -// NOLINTEND(readability-identifier-naming) #endif // __APPLE__ @@ -525,25 +523,30 @@ namespace Rendering { } // --------------------------------------------------------------------------- - // createPipeline + // createPostfxVertexShader — fullscreen-triangle vertex compartit per tots els pipelines // --------------------------------------------------------------------------- - auto SDL3GPUShader::createPipeline() -> bool { // NOLINT(readability-function-cognitive-complexity) - const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_); - - // ---- PostFX pipeline (scene/scaled → swapchain) ---- + auto SDL3GPUShader::createPostfxVertexShader() -> SDL_GPUShader* { #ifdef __APPLE__ - SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); + return createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); #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); + return createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); #endif + } - if ((vert == nullptr) || (frag == nullptr)) { - SDL_Log("SDL3GPUShader: failed to compile PostFX shaders"); - if (vert != nullptr) { SDL_ReleaseGPUShader(device_, vert); } - if (frag != nullptr) { SDL_ReleaseGPUShader(device_, frag); } - return false; + // --------------------------------------------------------------------------- + // 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 = {}; @@ -551,145 +554,55 @@ namespace Rendering { no_blend.enable_color_write_mask = false; SDL_GPUColorTargetDescription color_target = {}; - color_target.format = SWAPCHAIN_FMT; + color_target.format = format; 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; + 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; - pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &pipe_info); + SDL_GPUGraphicsPipeline* pipeline = SDL_CreateGPUGraphicsPipeline(device_, &info); SDL_ReleaseGPUShader(device_, vert); SDL_ReleaseGPUShader(device_, frag); - if (pipeline_ == nullptr) { - SDL_Log("SDL3GPUShader: PostFX pipeline creation failed: %s", SDL_GetError()); - return false; + if (pipeline == nullptr) { + SDL_Log("SDL3GPUShader: %s pipeline creation failed: %s", debug_name, SDL_GetError()); } + return pipeline; + } + + // --------------------------------------------------------------------------- + // createPipeline — crea els 4 pipelines del flux PostFX + // --------------------------------------------------------------------------- + auto SDL3GPUShader::createPipeline() -> bool { + const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_); + const SDL_GPUTextureFormat OFFSCREEN_FMT = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; - // ---- Upscale pipeline (scene → scaled_texture_, nearest) ---- #ifdef __APPLE__ - SDL_GPUShader* uvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* ufrag = createShaderMSL(device_, UPSCALE_FRAG_MSL, "upscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); + SDL_GPUShader* postfx_frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); + SDL_GPUShader* upscale_frag = createShaderMSL(device_, UPSCALE_FRAG_MSL, "upscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); + SDL_GPUShader* offscreen_frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); + SDL_GPUShader* downscale_frag = createShaderMSL(device_, DOWNSCALE_FRAG_MSL, "downscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #else - SDL_GPUShader* uvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* ufrag = createShaderSPIRV(device_, kupscale_frag_spv, kupscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); + SDL_GPUShader* postfx_frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); + SDL_GPUShader* upscale_frag = createShaderSPIRV(device_, kupscale_frag_spv, kupscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); + SDL_GPUShader* offscreen_frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); + SDL_GPUShader* downscale_frag = createShaderSPIRV(device_, kdownscale_frag_spv, kdownscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); #endif - if ((uvert == nullptr) || (ufrag == nullptr)) { - SDL_Log("SDL3GPUShader: failed to compile upscale shaders"); - if (uvert != nullptr) { SDL_ReleaseGPUShader(device_, uvert); } - if (ufrag != nullptr) { SDL_ReleaseGPUShader(device_, ufrag); } - return false; - } + pipeline_ = createPostfxLikePipeline(postfx_frag, SWAPCHAIN_FMT, "PostFX"); + upscale_pipeline_ = createPostfxLikePipeline(upscale_frag, OFFSCREEN_FMT, "upscale"); + postfx_offscreen_pipeline_ = createPostfxLikePipeline(offscreen_frag, OFFSCREEN_FMT, "PostFX offscreen"); + downscale_pipeline_ = createPostfxLikePipeline(downscale_frag, SWAPCHAIN_FMT, "downscale"); - SDL_GPUColorTargetDescription upscale_color_target = {}; - upscale_color_target.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; - upscale_color_target.blend_state = no_blend; - - SDL_GPUGraphicsPipelineCreateInfo upscale_pipe_info = {}; - upscale_pipe_info.vertex_shader = uvert; - upscale_pipe_info.fragment_shader = ufrag; - upscale_pipe_info.vertex_input_state = no_input; - upscale_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - upscale_pipe_info.target_info.num_color_targets = 1; - upscale_pipe_info.target_info.color_target_descriptions = &upscale_color_target; - - upscale_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &upscale_pipe_info); - - SDL_ReleaseGPUShader(device_, uvert); - SDL_ReleaseGPUShader(device_, ufrag); - - if (upscale_pipeline_ == nullptr) { - SDL_Log("SDL3GPUShader: upscale pipeline creation failed: %s", SDL_GetError()); - return false; - } - - // ---- PostFX offscreen pipeline (scaled_texture_ → postfx_texture_, B8G8R8A8) ---- - // Mismos shaders que pipeline_ pero con formato de salida B8G8R8A8_UNORM para textura intermedia. -#ifdef __APPLE__ - SDL_GPUShader* ofvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* offrag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); -#else - SDL_GPUShader* ofvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* offrag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); -#endif - - if ((ofvert == nullptr) || (offrag == nullptr)) { - SDL_Log("SDL3GPUShader: failed to compile PostFX offscreen shaders"); - if (ofvert != nullptr) { SDL_ReleaseGPUShader(device_, ofvert); } - if (offrag != nullptr) { SDL_ReleaseGPUShader(device_, offrag); } - return false; - } - - SDL_GPUColorTargetDescription offscreen_color_target = {}; - offscreen_color_target.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; - offscreen_color_target.blend_state = no_blend; - - SDL_GPUGraphicsPipelineCreateInfo offscreen_pipe_info = {}; - offscreen_pipe_info.vertex_shader = ofvert; - offscreen_pipe_info.fragment_shader = offrag; - offscreen_pipe_info.vertex_input_state = no_input; - offscreen_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - offscreen_pipe_info.target_info.num_color_targets = 1; - offscreen_pipe_info.target_info.color_target_descriptions = &offscreen_color_target; - - postfx_offscreen_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &offscreen_pipe_info); - - SDL_ReleaseGPUShader(device_, ofvert); - SDL_ReleaseGPUShader(device_, offrag); - - if (postfx_offscreen_pipeline_ == nullptr) { - SDL_Log("SDL3GPUShader: PostFX offscreen pipeline creation failed: %s", SDL_GetError()); - return false; - } - - // ---- Downscale pipeline (postfx_texture_ → swapchain, Lanczos) ---- -#ifdef __APPLE__ - SDL_GPUShader* dvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* dfrag = createShaderMSL(device_, DOWNSCALE_FRAG_MSL, "downscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); -#else - SDL_GPUShader* dvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); - SDL_GPUShader* dfrag = createShaderSPIRV(device_, kdownscale_frag_spv, kdownscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); -#endif - - if ((dvert == nullptr) || (dfrag == nullptr)) { - SDL_Log("SDL3GPUShader: failed to compile downscale shaders"); - if (dvert != nullptr) { SDL_ReleaseGPUShader(device_, dvert); } - if (dfrag != nullptr) { SDL_ReleaseGPUShader(device_, dfrag); } - return false; - } - - SDL_GPUColorTargetDescription downscale_color_target = {}; - downscale_color_target.format = SWAPCHAIN_FMT; - downscale_color_target.blend_state = no_blend; - - SDL_GPUGraphicsPipelineCreateInfo downscale_pipe_info = {}; - downscale_pipe_info.vertex_shader = dvert; - downscale_pipe_info.fragment_shader = dfrag; - downscale_pipe_info.vertex_input_state = no_input; - downscale_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - downscale_pipe_info.target_info.num_color_targets = 1; - downscale_pipe_info.target_info.color_target_descriptions = &downscale_color_target; - - downscale_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &downscale_pipe_info); - - SDL_ReleaseGPUShader(device_, dvert); - SDL_ReleaseGPUShader(device_, dfrag); - - if (downscale_pipeline_ == nullptr) { - SDL_Log("SDL3GPUShader: downscale pipeline creation failed: %s", SDL_GetError()); - return false; - } - - return true; + return (pipeline_ != nullptr) && (upscale_pipeline_ != nullptr) && (postfx_offscreen_pipeline_ != nullptr) && (downscale_pipeline_ != nullptr); } // --------------------------------------------------------------------------- @@ -700,51 +613,13 @@ namespace Rendering { // --------------------------------------------------------------------------- auto SDL3GPUShader::createCrtPiPipeline() -> bool { const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_); - #ifdef __APPLE__ - SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* frag = createShaderMSL(device_, CRTPI_FRAG_MSL, "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)) { - SDL_Log("SDL3GPUShader: failed to compile CrtPi shaders"); - 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; - - crtpi_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &pipe_info); - - SDL_ReleaseGPUShader(device_, vert); - SDL_ReleaseGPUShader(device_, frag); - - if (crtpi_pipeline_ == nullptr) { - SDL_Log("SDL3GPUShader: CrtPi pipeline creation failed: %s", SDL_GetError()); - return false; - } - - return true; + crtpi_pipeline_ = createPostfxLikePipeline(frag, SWAPCHAIN_FMT, "CrtPi"); + return crtpi_pipeline_ != nullptr; } // --------------------------------------------------------------------------- @@ -762,93 +637,76 @@ namespace Rendering { } // Copia directa — el upscale lo hace la GPU en el primer render pass - std::memcpy(mapped, pixels, static_cast(width) * static_cast(height) * 4); + std::memcpy(mapped, pixels, static_cast(width) * height * 4); SDL_UnmapGPUTransferBuffer(device_, upload_buffer_); } // --------------------------------------------------------------------------- - // render — upload scene texture + PostFX pass → swapchain + // maybeRescaleSsTexture — recalcula factor SS i recrea scaled_texture_ si cal // --------------------------------------------------------------------------- - void SDL3GPUShader::render() { // NOLINT(readability-function-cognitive-complexity) - if (!is_initialized_) { return; } - - // Paso 0: si SS activo, calcular el factor necesario según el zoom actual y recrear si cambió. - // Factor = primer múltiplo de 3 >= zoom (mín 3). Se recrea solo en saltos de factor. - if (oversample_ > 1 && game_height_ > 0) { - int win_w = 0; - int win_h = 0; - SDL_GetWindowSizeInPixels(window_, &win_w, &win_h); - const float ZOOM = static_cast(win_h) / static_cast(game_height_); - const int NEED_FACTOR = calcSsFactor(ZOOM); - if (NEED_FACTOR != ss_factor_) { - SDL_WaitForGPUIdle(device_); - recreateScaledTexture(NEED_FACTOR); - } + void SDL3GPUShader::maybeRescaleSsTexture() { + if (oversample_ <= 1 || game_height_ <= 0) { return; } + int win_w = 0; + int win_h = 0; + SDL_GetWindowSizeInPixels(window_, &win_w, &win_h); + const float ZOOM = static_cast(win_h) / static_cast(game_height_); + const int NEED_FACTOR = calcSsFactor(ZOOM); + if (NEED_FACTOR != ss_factor_) { + SDL_WaitForGPUIdle(device_); + recreateScaledTexture(NEED_FACTOR); } + } - 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 (siempre a resolución del juego) ---- + // --------------------------------------------------------------------------- + // uploadSceneTexture — copy pass: transfer buffer → scene texture + // --------------------------------------------------------------------------- + void SDL3GPUShader::uploadSceneTexture(SDL_GPUCommandBuffer* cmd) { 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_); + if (copy == nullptr) { return; } - SDL_GPUTextureRegion dst = {}; - dst.texture = scene_texture_; - dst.w = static_cast(game_width_); - dst.h = static_cast(game_height_); - dst.d = 1; + 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_UploadToGPUTexture(copy, &src, &dst, false); - SDL_EndGPUCopyPass(copy); - } + SDL_GPUTextureRegion dst = {}; + dst.texture = scene_texture_; + dst.w = static_cast(game_width_); + dst.h = static_cast(game_height_); + dst.d = 1; - // ---- Upscale pass: scene_texture_ → scaled_texture_ (NEAREST o LINEAR según linear_upscale_) ---- - if (oversample_ > 1 && scaled_texture_ != nullptr && upscale_pipeline_ != nullptr) { - SDL_GPUColorTargetInfo upscale_target = {}; - upscale_target.texture = scaled_texture_; - upscale_target.load_op = SDL_GPU_LOADOP_DONT_CARE; - upscale_target.store_op = SDL_GPU_STOREOP_STORE; + SDL_UploadToGPUTexture(copy, &src, &dst, false); + SDL_EndGPUCopyPass(copy); + } - SDL_GPURenderPass* upass = SDL_BeginGPURenderPass(cmd, &upscale_target, 1, nullptr); - if (upass != nullptr) { - SDL_BindGPUGraphicsPipeline(upass, upscale_pipeline_); - SDL_GPUTextureSamplerBinding ubinding = {}; - ubinding.texture = scene_texture_; - ubinding.sampler = (linear_upscale_ && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_; - SDL_BindGPUFragmentSamplers(upass, 0, &ubinding, 1); - SDL_DrawGPUPrimitives(upass, 3, 1, 0, 0); - SDL_EndGPURenderPass(upass); - } - } + // --------------------------------------------------------------------------- + // runUpscalePass — scene_texture_ → scaled_texture_ (NEAREST o LINEAR segons linear_upscale_) + // --------------------------------------------------------------------------- + void SDL3GPUShader::runUpscalePass(SDL_GPUCommandBuffer* cmd) { + if (oversample_ <= 1 || scaled_texture_ == nullptr || upscale_pipeline_ == nullptr) { return; } - // ---- 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; - } + SDL_GPUColorTargetInfo target = {}; + target.texture = scaled_texture_; + target.load_op = SDL_GPU_LOADOP_DONT_CARE; + target.store_op = SDL_GPU_STOREOP_STORE; - // ---- Calcular viewport (dimensiones lógicas del canvas, no de textura GPU) ---- - float vx = 0.0F; - float vy = 0.0F; + SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &target, 1, nullptr); + if (pass == nullptr) { return; } + SDL_BindGPUGraphicsPipeline(pass, upscale_pipeline_); + SDL_GPUTextureSamplerBinding binding = {}; + binding.texture = scene_texture_; + binding.sampler = (linear_upscale_ && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_; + SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1); + SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0); + SDL_EndGPURenderPass(pass); + } + + // --------------------------------------------------------------------------- + // computeViewport — dimensions lògiques del canvas dins del swapchain (letterbox) + // --------------------------------------------------------------------------- + auto SDL3GPUShader::computeViewport(Uint32 sw, Uint32 sh) const -> Viewport { float vw = 0.0F; float vh = 0.0F; if (integer_scale_) { @@ -862,131 +720,172 @@ namespace Rendering { 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); + const float VX = std::floor((static_cast(sw) - vw) * 0.5F); + const float VY = std::floor((static_cast(sh) - vh) * 0.5F); + return {.x = VX, .y = VY, .w = vw, .h = vh}; + } - // pixel_scale: subpíxeles por pixel lógico. - // Sin SS: vh/game_height (zoom de ventana). - // Con SS: ss_factor_ exacto (3, 6, 9...). + // --------------------------------------------------------------------------- + // updateDynamicUniforms — actualitza pixel_scale, time, oversample per a aquest frame + // --------------------------------------------------------------------------- + void SDL3GPUShader::updateDynamicUniforms(float viewport_h) { + // pixel_scale: subpíxels per pixel lògic. Amb SS: ss_factor_ exacte; sense SS: zoom de finestra. if (oversample_ > 1 && ss_factor_ > 0) { uniforms_.pixel_scale = static_cast(ss_factor_); } else { - uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast(game_height_)) : 1.0F; + uniforms_.pixel_scale = (game_height_ > 0) ? (viewport_h / static_cast(game_height_)) : 1.0F; } uniforms_.time = static_cast(SDL_GetTicks()) / 1000.0F; - uniforms_.oversample = (oversample_ > 1 && ss_factor_ > 0) - ? static_cast(ss_factor_) - : 1.0F; + uniforms_.oversample = (oversample_ > 1 && ss_factor_ > 0) ? static_cast(ss_factor_) : 1.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}; + // --------------------------------------------------------------------------- + // 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) { - 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_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 hace su propio filtrado analítico - SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1); + 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); - // Inyectar texture_width/height antes del push - crtpi_uniforms_.texture_width = static_cast(game_width_); - crtpi_uniforms_.texture_height = static_cast(game_height_); - SDL_PushGPUFragmentUniformData(cmd, 0, &crtpi_uniforms_, sizeof(CrtPiUniforms)); + 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); - } + SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0); + SDL_EndGPURenderPass(pass); + } + // --------------------------------------------------------------------------- + // runLanczosPasses — scaled_texture_ → postfx_texture_ (PostFX) → swapchain (Lanczos) + // --------------------------------------------------------------------------- + void SDL3GPUShader::runLanczosPasses(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp) { + // Pass A: PostFX → postfx_texture_ (full scaled size, sense viewport) + SDL_GPUColorTargetInfo postfx_target = {}; + postfx_target.texture = postfx_texture_; + postfx_target.load_op = SDL_GPU_LOADOP_CLEAR; + postfx_target.store_op = SDL_GPU_STOREOP_STORE; + postfx_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F}; + + SDL_GPURenderPass* ppass = SDL_BeginGPURenderPass(cmd, &postfx_target, 1, nullptr); + if (ppass != nullptr) { + SDL_BindGPUGraphicsPipeline(ppass, postfx_offscreen_pipeline_); + SDL_GPUTextureSamplerBinding pbinding = {}; + pbinding.texture = scaled_texture_; + pbinding.sampler = sampler_; // NEAREST: 1:1 pass, efectes calculats analíticament + SDL_BindGPUFragmentSamplers(ppass, 0, &pbinding, 1); + SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms)); + SDL_DrawGPUPrimitives(ppass, 3, 1, 0, 0); + SDL_EndGPURenderPass(ppass); + } + + // Pass B: Downscale Lanczos → swapchain (amb viewport/letterbox) + SDL_GPUColorTargetInfo ds_target = {}; + ds_target.texture = swapchain; + ds_target.load_op = SDL_GPU_LOADOP_CLEAR; + ds_target.store_op = SDL_GPU_STOREOP_STORE; + ds_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F}; + + SDL_GPURenderPass* dpass = SDL_BeginGPURenderPass(cmd, &ds_target, 1, nullptr); + if (dpass == nullptr) { return; } + SDL_BindGPUGraphicsPipeline(dpass, downscale_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(dpass, &sdlvp); + SDL_GPUTextureSamplerBinding dbinding = {}; + dbinding.texture = postfx_texture_; + dbinding.sampler = sampler_; // NEAREST: el shader Lanczos fa la seua pròpia interpolació + SDL_BindGPUFragmentSamplers(dpass, 0, &dbinding, 1); + // algorithm: 0=Lanczos2, 1=Lanczos3 (downscale_algo_ és 1-based) + DownscaleUniforms downscale_u = {.algorithm = downscale_algo_ - 1, .pad0 = 0.0F, .pad1 = 0.0F, .pad2 = 0.0F}; + SDL_PushGPUFragmentUniformData(cmd, 0, &downscale_u, sizeof(DownscaleUniforms)); + SDL_DrawGPUPrimitives(dpass, 3, 1, 0, 0); + SDL_EndGPURenderPass(dpass); + } + + // --------------------------------------------------------------------------- + // runDirectPostfxPass — PostFX → swapchain directament (sense Lanczos) + // --------------------------------------------------------------------------- + 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); + + // Amb SS: llegir de scaled_texture_ amb LINEAR; sense SS: scene_texture_ amb NEAREST. + SDL_GPUTexture* input_texture = (oversample_ > 1 && scaled_texture_ != nullptr) ? scaled_texture_ : scene_texture_; + SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_; + + SDL_GPUTextureSamplerBinding binding = {}; + binding.texture = input_texture; + binding.sampler = active_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 + upscale + path PostFX (CrtPi / Lanczos / direct) + // --------------------------------------------------------------------------- + void SDL3GPUShader::render() { + if (!is_initialized_) { return; } + + maybeRescaleSsTexture(); + + SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_); + if (cmd == nullptr) { + SDL_Log("SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError()); + return; + } + + uploadSceneTexture(cmd); + runUpscalePass(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; } - // ---- Determinar si usar el path Lanczos (SS activo + algo seleccionado) ---- + const Viewport VP = computeViewport(sw, sh); + updateDynamicUniforms(VP.h); + const bool USE_LANCZOS = (oversample_ > 1 && downscale_algo_ > 0 && scaled_texture_ != nullptr && postfx_texture_ != nullptr && postfx_offscreen_pipeline_ != nullptr && downscale_pipeline_ != nullptr); - if (USE_LANCZOS) { - // ---- Pass A: PostFX → postfx_texture_ (full scaled size, sin viewport) ---- - SDL_GPUColorTargetInfo postfx_target = {}; - postfx_target.texture = postfx_texture_; - postfx_target.load_op = SDL_GPU_LOADOP_CLEAR; - postfx_target.store_op = SDL_GPU_STOREOP_STORE; - postfx_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F}; - - SDL_GPURenderPass* ppass = SDL_BeginGPURenderPass(cmd, &postfx_target, 1, nullptr); - if (ppass != nullptr) { - SDL_BindGPUGraphicsPipeline(ppass, postfx_offscreen_pipeline_); - SDL_GPUTextureSamplerBinding pbinding = {}; - pbinding.texture = scaled_texture_; - pbinding.sampler = sampler_; // NEAREST: 1:1 pass, efectos calculados analíticamente - SDL_BindGPUFragmentSamplers(ppass, 0, &pbinding, 1); - SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms)); - SDL_DrawGPUPrimitives(ppass, 3, 1, 0, 0); - SDL_EndGPURenderPass(ppass); - } - - // ---- Pass B: Downscale Lanczos → swapchain (con viewport/letterbox) ---- - SDL_GPUColorTargetInfo ds_target = {}; - ds_target.texture = swapchain; - ds_target.load_op = SDL_GPU_LOADOP_CLEAR; - ds_target.store_op = SDL_GPU_STOREOP_STORE; - ds_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F}; - - SDL_GPURenderPass* dpass = SDL_BeginGPURenderPass(cmd, &ds_target, 1, nullptr); - if (dpass != nullptr) { - SDL_BindGPUGraphicsPipeline(dpass, downscale_pipeline_); - SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F}; - SDL_SetGPUViewport(dpass, &vp); - SDL_GPUTextureSamplerBinding dbinding = {}; - dbinding.texture = postfx_texture_; - dbinding.sampler = sampler_; // NEAREST: el shader Lanczos hace su propia interpolación - SDL_BindGPUFragmentSamplers(dpass, 0, &dbinding, 1); - // algorithm: 0=Lanczos2, 1=Lanczos3 (downscale_algo_ es 1-based) - DownscaleUniforms downscale_u = {.algorithm = downscale_algo_ - 1, .pad0 = 0.0F, .pad1 = 0.0F, .pad2 = 0.0F}; - SDL_PushGPUFragmentUniformData(cmd, 0, &downscale_u, sizeof(DownscaleUniforms)); - SDL_DrawGPUPrimitives(dpass, 3, 1, 0, 0); - SDL_EndGPURenderPass(dpass); - } + if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) { + runCrtPiPass(cmd, swapchain, VP); + } else if (USE_LANCZOS) { + runLanczosPasses(cmd, swapchain, VP); } else { - // ---- Render pass: PostFX → swapchain directamente (bilinear, comportamiento original) ---- - 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); - - // Con SS: leer de scaled_texture_ con LINEAR; sin SS: scene_texture_ con NEAREST. - SDL_GPUTexture* input_texture = (oversample_ > 1 && scaled_texture_ != nullptr) - ? scaled_texture_ - : scene_texture_; - SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr) - ? linear_sampler_ - : sampler_; - - SDL_GPUTextureSamplerBinding binding = {}; - binding.texture = input_texture; - binding.sampler = active_sampler; - SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1); - - SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms)); - - SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0); - SDL_EndGPURenderPass(pass); - } + runDirectPostfxPass(cmd, swapchain, VP); } SDL_SubmitGPUCommandBuffer(cmd); diff --git a/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp index 6298898..57393c6 100644 --- a/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp +++ b/source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp @@ -137,7 +137,24 @@ namespace Rendering { Uint32 num_uniform_buffers) -> SDL_GPUShader*; auto createPipeline() -> bool; - auto createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi + auto createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi + auto createPostfxVertexShader() -> SDL_GPUShader*; // Vertex shader fullscreen-triangle compartido (MSL/SPIRV) + // Empaqueta el patrón vert(postfx) + frag dado + target format en un pipeline gráfico. + // Toma ownership de `frag`: lo libera tras crear el pipeline (o si vert falla). + auto createPostfxLikePipeline(SDL_GPUShader* frag, SDL_GPUTextureFormat format, const char* debug_name) -> SDL_GPUGraphicsPipeline*; + + // Sub-passos de render() (extrets per reduir complexitat ciclomàtica) + struct Viewport { + float x, y, w, h; + }; + void maybeRescaleSsTexture(); + void uploadSceneTexture(SDL_GPUCommandBuffer* cmd); + void runUpscalePass(SDL_GPUCommandBuffer* cmd); + [[nodiscard]] auto computeViewport(Uint32 sw, Uint32 sh) const -> Viewport; + void updateDynamicUniforms(float viewport_h); + void runCrtPiPass(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp); + void runLanczosPasses(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp); + void runDirectPostfxPass(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchain, const Viewport& vp); auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_ auto recreateScaledTexture(int factor) -> bool; // Recrea scaled_texture_ para factor dado static auto calcSsFactor(float zoom) -> int; // Primer múltiplo de 3 >= zoom (mín 3)