From 6ffe7594ab760549fec7022d3b84aea4f699eb1e Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 20 Mar 2026 09:47:36 +0100 Subject: [PATCH] feat(gpu): afegir suport SPIRV (Vulkan) per Linux/Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nou: shaders/sprite.vert|frag, postfx.vert|frag, ball.vert (GLSL) - Nou: cmake/spv_to_header.cmake — converteix .spv → uint8_t C header - CMakeLists.txt: bloc non-Apple troba glslc, compila GLSL → SPIRV en build-time i genera headers embeguts a build/generated_shaders/ - gpu_context.cpp: MSL|METALLIB en Apple, SPIRV en la resta - gpu_pipeline.cpp: createShaderSPIRV() + branques #ifdef __APPLE__ per sprite/ball/postfx pipelines - Corregeix crash a engine.cpp:821 (Windows/Linux) causat per pipelines null quan init() fallava en no trobar suport MSL Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 47 +++++++++++++++++++++++++++ shaders/ball.vert | 23 ++++++++++++++ shaders/postfx.frag | 24 ++++++++++++++ shaders/postfx.vert | 10 ++++++ shaders/sprite.frag | 9 ++++++ shaders/sprite.vert | 11 +++++++ source/gpu/gpu_context.cpp | 10 +++--- source/gpu/gpu_pipeline.cpp | 63 ++++++++++++++++++++++++++++++++++++- source/gpu/gpu_pipeline.hpp | 9 ++++++ 9 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 shaders/ball.vert create mode 100644 shaders/postfx.frag create mode 100644 shaders/postfx.vert create mode 100644 shaders/sprite.frag create mode 100644 shaders/sprite.vert diff --git a/CMakeLists.txt b/CMakeLists.txt index fa27fb1..f0558ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,48 @@ if (NOT SDL3_ttf_FOUND) message(FATAL_ERROR "SDL3_ttf no encontrado. Por favor, verifica su instalación.") endif() +# ---- Shader compilation (non-Apple only: Vulkan/SPIRV) ---- +if(NOT APPLE) + find_program(GLSLC glslc HINTS "$ENV{VULKAN_SDK}/bin" "$ENV{VULKAN_SDK}/Bin") + if(NOT GLSLC) + message(FATAL_ERROR "glslc not found. Install the Vulkan SDK and ensure glslc is on PATH.") + endif() + + set(SHADER_SRC_DIR "${CMAKE_SOURCE_DIR}/shaders") + set(SHADER_GEN_DIR "${CMAKE_BINARY_DIR}/generated_shaders") + file(MAKE_DIRECTORY "${SHADER_GEN_DIR}") + + set(SPIRV_HEADERS) + foreach(SHADER sprite_vert sprite_frag postfx_vert postfx_frag ball_vert) + if(SHADER MATCHES "_vert$") + set(STAGE_FLAG "-fshader-stage=vertex") + else() + set(STAGE_FLAG "-fshader-stage=fragment") + endif() + string(REGEX REPLACE "_vert$" ".vert" GLSL_NAME "${SHADER}") + string(REGEX REPLACE "_frag$" ".frag" GLSL_NAME "${GLSL_NAME}") + + set(GLSL_FILE "${SHADER_SRC_DIR}/${GLSL_NAME}") + set(SPV_FILE "${SHADER_GEN_DIR}/${SHADER}.spv") + set(H_FILE "${SHADER_GEN_DIR}/${SHADER}_spv.h") + + add_custom_command( + OUTPUT "${H_FILE}" + COMMAND "${GLSLC}" ${STAGE_FLAG} -o "${SPV_FILE}" "${GLSL_FILE}" + COMMAND "${CMAKE_COMMAND}" + -DINPUT="${SPV_FILE}" + -DOUTPUT="${H_FILE}" + -DVAR_NAME="k${SHADER}_spv" + -P "${CMAKE_SOURCE_DIR}/cmake/spv_to_header.cmake" + DEPENDS "${GLSL_FILE}" + COMMENT "Compiling ${GLSL_NAME} to SPIRV" + ) + list(APPEND SPIRV_HEADERS "${H_FILE}") + endforeach() + + add_custom_target(shaders ALL DEPENDS ${SPIRV_HEADERS}) +endif() + # Archivos fuente (excluir main_old.cpp) file(GLOB SOURCE_FILES source/*.cpp source/external/*.cpp source/boids_mgr/*.cpp source/gpu/*.cpp source/input/*.cpp source/scene/*.cpp source/shapes/*.cpp source/shapes_mgr/*.cpp source/state/*.cpp source/themes/*.cpp source/text/*.cpp source/ui/*.cpp) list(REMOVE_ITEM SOURCE_FILES "${CMAKE_SOURCE_DIR}/source/main_old.cpp") @@ -60,6 +102,11 @@ set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAK # Enlazar las bibliotecas necesarias target_link_libraries(${PROJECT_NAME} ${LINK_LIBS}) +if(NOT APPLE) + add_dependencies(${PROJECT_NAME} shaders) + target_include_directories(${PROJECT_NAME} PRIVATE "${SHADER_GEN_DIR}") +endif() + # Tool: pack_resources add_executable(pack_resources tools/pack_resources.cpp source/resource_pack.cpp) target_include_directories(pack_resources PRIVATE ${CMAKE_SOURCE_DIR}/source) diff --git a/shaders/ball.vert b/shaders/ball.vert new file mode 100644 index 0000000..9304d88 --- /dev/null +++ b/shaders/ball.vert @@ -0,0 +1,23 @@ +#version 450 +// Per-instance data (input_rate = INSTANCE in the pipeline) +layout(location=0) in vec2 center; +layout(location=1) in vec2 halfsize; +layout(location=2) in vec4 col; +layout(location=0) out vec2 v_uv; +layout(location=1) out vec4 v_col; +void main() { + // gl_VertexIndex cycles 0..5 per instance (6 vertices = 2 triangles) + // Vertex order: TL TR BL | TR BR BL (CCW winding) + const vec2 offsets[6] = vec2[6]( + vec2(-1.0, 1.0), vec2(1.0, 1.0), vec2(-1.0,-1.0), + vec2( 1.0, 1.0), vec2(1.0,-1.0), vec2(-1.0,-1.0) + ); + const vec2 uvs[6] = vec2[6]( + vec2(0.0,0.0), vec2(1.0,0.0), vec2(0.0,1.0), + vec2(1.0,0.0), vec2(1.0,1.0), vec2(0.0,1.0) + ); + int vid = gl_VertexIndex; + gl_Position = vec4(center + offsets[vid] * halfsize, 0.0, 1.0); + v_uv = uvs[vid]; + v_col = col; +} diff --git a/shaders/postfx.frag b/shaders/postfx.frag new file mode 100644 index 0000000..57d301b --- /dev/null +++ b/shaders/postfx.frag @@ -0,0 +1,24 @@ +#version 450 +layout(location=0) in vec2 v_uv; +layout(location=0) out vec4 out_color; +layout(set=2, binding=0) uniform sampler2D scene; +layout(set=3, binding=0) uniform PostFXUniforms { + float vignette_strength; + float chroma_strength; + float scanline_strength; + float time; +} u; +void main() { + float ca = u.chroma_strength * 0.005; + vec4 color; + color.r = texture(scene, v_uv + vec2( ca, 0.0)).r; + color.g = texture(scene, v_uv).g; + color.b = texture(scene, v_uv - vec2( ca, 0.0)).b; + color.a = texture(scene, v_uv).a; + float scan = 0.85 + 0.15 * sin(v_uv.y * 3.14159265 * 720.0); + color.rgb *= mix(1.0, scan, u.scanline_strength); + vec2 d = v_uv - vec2(0.5, 0.5); + float vignette = 1.0 - dot(d, d) * u.vignette_strength; + color.rgb *= clamp(vignette, 0.0, 1.0); + out_color = color; +} diff --git a/shaders/postfx.vert b/shaders/postfx.vert new file mode 100644 index 0000000..eb46652 --- /dev/null +++ b/shaders/postfx.vert @@ -0,0 +1,10 @@ +#version 450 +layout(location=0) out vec2 v_uv; +void main() { + // Full-screen triangle from vertex index (no vertex buffer needed) + // NDC/UV mapping matches the MSL version (SDL3 GPU normalizes Y-up on all backends) + vec2 positions[3] = vec2[3](vec2(-1.0,-1.0), vec2(3.0,-1.0), vec2(-1.0,3.0)); + vec2 uvs[3] = vec2[3](vec2(0.0, 1.0), vec2(2.0, 1.0), vec2(0.0,-1.0)); + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + v_uv = uvs[gl_VertexIndex]; +} diff --git a/shaders/sprite.frag b/shaders/sprite.frag new file mode 100644 index 0000000..c6c10b6 --- /dev/null +++ b/shaders/sprite.frag @@ -0,0 +1,9 @@ +#version 450 +layout(location=0) in vec2 v_uv; +layout(location=1) in vec4 v_col; +layout(location=0) out vec4 out_color; +layout(set=2, binding=0) uniform sampler2D tex; +void main() { + vec4 t = texture(tex, v_uv); + out_color = vec4(t.rgb * v_col.rgb, t.a * v_col.a); +} diff --git a/shaders/sprite.vert b/shaders/sprite.vert new file mode 100644 index 0000000..3154e80 --- /dev/null +++ b/shaders/sprite.vert @@ -0,0 +1,11 @@ +#version 450 +layout(location=0) in vec2 pos; +layout(location=1) in vec2 uv; +layout(location=2) in vec4 col; +layout(location=0) out vec2 v_uv; +layout(location=1) out vec4 v_col; +void main() { + gl_Position = vec4(pos, 0.0, 1.0); + v_uv = uv; + v_col = col; +} diff --git a/source/gpu/gpu_context.cpp b/source/gpu/gpu_context.cpp index 7b676c0..1cab7f7 100644 --- a/source/gpu/gpu_context.cpp +++ b/source/gpu/gpu_context.cpp @@ -6,10 +6,12 @@ bool GpuContext::init(SDL_Window* window) { window_ = window; - // Create GPU device — prefer Metal on macOS, Vulkan elsewhere - SDL_GPUShaderFormat preferred = SDL_GPU_SHADERFORMAT_MSL - | SDL_GPU_SHADERFORMAT_METALLIB - | SDL_GPU_SHADERFORMAT_SPIRV; + // Create GPU device: Metal on Apple, Vulkan elsewhere +#ifdef __APPLE__ + SDL_GPUShaderFormat preferred = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB; +#else + SDL_GPUShaderFormat preferred = SDL_GPU_SHADERFORMAT_SPIRV; +#endif device_ = SDL_CreateGPUDevice(preferred, false, nullptr); if (!device_) { std::cerr << "GpuContext: SDL_CreateGPUDevice failed: " << SDL_GetError() << std::endl; diff --git a/source/gpu/gpu_pipeline.cpp b/source/gpu/gpu_pipeline.cpp index 71295f4..50f187a 100644 --- a/source/gpu/gpu_pipeline.cpp +++ b/source/gpu/gpu_pipeline.cpp @@ -6,6 +6,15 @@ #include // offsetof #include // strlen +#ifndef __APPLE__ +// Generated at build time by CMake + glslc (see cmake/spv_to_header.cmake) +#include "sprite_vert_spv.h" +#include "sprite_frag_spv.h" +#include "postfx_vert_spv.h" +#include "postfx_frag_spv.h" +#include "ball_vert_spv.h" +#endif + // ============================================================================ // MSL Shaders (Metal Shading Language, macOS) // ============================================================================ @@ -197,18 +206,32 @@ bool GpuPipeline::init(SDL_GPUDevice* device, SDL_GPUTextureFormat target_format, SDL_GPUTextureFormat offscreen_format) { SDL_GPUShaderFormat supported = SDL_GetGPUShaderFormats(device); +#ifdef __APPLE__ if (!(supported & SDL_GPU_SHADERFORMAT_MSL)) { - SDL_Log("GpuPipeline: device does not support MSL shaders (format mask=%u)", supported); + SDL_Log("GpuPipeline: MSL not supported (format mask=%u)", supported); return false; } +#else + if (!(supported & SDL_GPU_SHADERFORMAT_SPIRV)) { + SDL_Log("GpuPipeline: SPIRV not supported (format mask=%u)", supported); + return false; + } +#endif // ---------------------------------------------------------------- // Sprite pipeline // ---------------------------------------------------------------- +#ifdef __APPLE__ SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); +#else + SDL_GPUShader* sprite_vert = createShaderSPIRV(device, ksprite_vert_spv, ksprite_vert_spv_size, + "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* sprite_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, + "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); +#endif if (!sprite_vert || !sprite_frag) { SDL_Log("GpuPipeline: failed to create sprite shaders"); if (sprite_vert) SDL_ReleaseGPUShader(device, sprite_vert); @@ -284,10 +307,17 @@ bool GpuPipeline::init(SDL_GPUDevice* device, // Fragment: sprite_fs (same texture+color blend as sprite pipeline) // Targets: offscreen (same as sprite pipeline) // ---------------------------------------------------------------- +#ifdef __APPLE__ SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); +#else + SDL_GPUShader* ball_vert = createShaderSPIRV(device, kball_vert_spv, kball_vert_spv_size, + "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* ball_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, + "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); +#endif if (!ball_vert || !ball_frag) { SDL_Log("GpuPipeline: failed to create ball instanced shaders"); if (ball_vert) SDL_ReleaseGPUShader(device, ball_vert); @@ -356,10 +386,17 @@ bool GpuPipeline::init(SDL_GPUDevice* device, // ---------------------------------------------------------------- // PostFX pipeline // ---------------------------------------------------------------- +#ifdef __APPLE__ SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); +#else + SDL_GPUShader* postfx_vert = createShaderSPIRV(device, kpostfx_vert_spv, kpostfx_vert_spv_size, + "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); + SDL_GPUShader* postfx_frag = createShaderSPIRV(device, kpostfx_frag_spv, kpostfx_frag_spv_size, + "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); +#endif if (!postfx_vert || !postfx_frag) { SDL_Log("GpuPipeline: failed to create postfx shaders"); if (postfx_vert) SDL_ReleaseGPUShader(device, postfx_vert); @@ -406,6 +443,30 @@ void GpuPipeline::destroy(SDL_GPUDevice* device) { if (postfx_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, postfx_pipeline_); postfx_pipeline_ = nullptr; } } +SDL_GPUShader* GpuPipeline::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, + Uint32 num_storage_buffers) { + 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_storage_textures = 0; + info.num_storage_buffers = num_storage_buffers; + info.num_uniform_buffers = num_uniform_buffers; + SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info); + if (!shader) + SDL_Log("GpuPipeline: SPIRV shader '%s' failed: %s", entrypoint, SDL_GetError()); + return shader; +} + SDL_GPUShader* GpuPipeline::createShader(SDL_GPUDevice* device, const char* msl_source, const char* entrypoint, diff --git a/source/gpu/gpu_pipeline.hpp b/source/gpu/gpu_pipeline.hpp index fc5e05b..59048f1 100644 --- a/source/gpu/gpu_pipeline.hpp +++ b/source/gpu/gpu_pipeline.hpp @@ -48,6 +48,15 @@ private: Uint32 num_uniform_buffers, Uint32 num_storage_buffers = 0); + SDL_GPUShader* 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, + Uint32 num_storage_buffers = 0); + SDL_GPUGraphicsPipeline* sprite_pipeline_ = nullptr; SDL_GPUGraphicsPipeline* ball_pipeline_ = nullptr; SDL_GPUGraphicsPipeline* postfx_pipeline_ = nullptr;