This commit is contained in:
Sergio Valor Martinez
2026-03-20 09:54:08 +01:00
9 changed files with 201 additions and 5 deletions

View File

@@ -24,6 +24,48 @@ if (NOT SDL3_ttf_FOUND)
message(FATAL_ERROR "SDL3_ttf no encontrado. Por favor, verifica su instalación.") message(FATAL_ERROR "SDL3_ttf no encontrado. Por favor, verifica su instalación.")
endif() 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) # 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) 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") 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 # Enlazar las bibliotecas necesarias
target_link_libraries(${PROJECT_NAME} ${LINK_LIBS}) 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 # Tool: pack_resources
add_executable(pack_resources tools/pack_resources.cpp source/resource_pack.cpp) add_executable(pack_resources tools/pack_resources.cpp source/resource_pack.cpp)
target_include_directories(pack_resources PRIVATE ${CMAKE_SOURCE_DIR}/source) target_include_directories(pack_resources PRIVATE ${CMAKE_SOURCE_DIR}/source)

23
shaders/ball.vert Normal file
View File

@@ -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;
}

24
shaders/postfx.frag Normal file
View File

@@ -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;
}

10
shaders/postfx.vert Normal file
View File

@@ -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];
}

9
shaders/sprite.frag Normal file
View File

@@ -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);
}

11
shaders/sprite.vert Normal file
View File

@@ -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;
}

View File

@@ -6,10 +6,12 @@
bool GpuContext::init(SDL_Window* window) { bool GpuContext::init(SDL_Window* window) {
window_ = window; window_ = window;
// Create GPU device — prefer Metal on macOS, Vulkan elsewhere // Create GPU device: Metal on Apple, Vulkan elsewhere
SDL_GPUShaderFormat preferred = SDL_GPU_SHADERFORMAT_MSL #ifdef __APPLE__
| SDL_GPU_SHADERFORMAT_METALLIB SDL_GPUShaderFormat preferred = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB;
| SDL_GPU_SHADERFORMAT_SPIRV; #else
SDL_GPUShaderFormat preferred = SDL_GPU_SHADERFORMAT_SPIRV;
#endif
device_ = SDL_CreateGPUDevice(preferred, false, nullptr); device_ = SDL_CreateGPUDevice(preferred, false, nullptr);
if (!device_) { if (!device_) {
std::cerr << "GpuContext: SDL_CreateGPUDevice failed: " << SDL_GetError() << std::endl; std::cerr << "GpuContext: SDL_CreateGPUDevice failed: " << SDL_GetError() << std::endl;

View File

@@ -6,6 +6,15 @@
#include <cstddef> // offsetof #include <cstddef> // offsetof
#include <cstring> // strlen #include <cstring> // 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) // MSL Shaders (Metal Shading Language, macOS)
// ============================================================================ // ============================================================================
@@ -197,18 +206,32 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
SDL_GPUTextureFormat target_format, SDL_GPUTextureFormat target_format,
SDL_GPUTextureFormat offscreen_format) { SDL_GPUTextureFormat offscreen_format) {
SDL_GPUShaderFormat supported = SDL_GetGPUShaderFormats(device); SDL_GPUShaderFormat supported = SDL_GetGPUShaderFormats(device);
#ifdef __APPLE__
if (!(supported & SDL_GPU_SHADERFORMAT_MSL)) { 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; return false;
} }
#else
if (!(supported & SDL_GPU_SHADERFORMAT_SPIRV)) {
SDL_Log("GpuPipeline: SPIRV not supported (format mask=%u)", supported);
return false;
}
#endif
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// Sprite pipeline // Sprite pipeline
// ---------------------------------------------------------------- // ----------------------------------------------------------------
#ifdef __APPLE__
SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs", SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs",
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs",
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); 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) { if (!sprite_vert || !sprite_frag) {
SDL_Log("GpuPipeline: failed to create sprite shaders"); SDL_Log("GpuPipeline: failed to create sprite shaders");
if (sprite_vert) SDL_ReleaseGPUShader(device, sprite_vert); 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) // Fragment: sprite_fs (same texture+color blend as sprite pipeline)
// Targets: offscreen (same as sprite pipeline) // Targets: offscreen (same as sprite pipeline)
// ---------------------------------------------------------------- // ----------------------------------------------------------------
#ifdef __APPLE__
SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs", SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs",
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs",
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0); 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) { if (!ball_vert || !ball_frag) {
SDL_Log("GpuPipeline: failed to create ball instanced shaders"); SDL_Log("GpuPipeline: failed to create ball instanced shaders");
if (ball_vert) SDL_ReleaseGPUShader(device, ball_vert); if (ball_vert) SDL_ReleaseGPUShader(device, ball_vert);
@@ -356,10 +386,17 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// PostFX pipeline // PostFX pipeline
// ---------------------------------------------------------------- // ----------------------------------------------------------------
#ifdef __APPLE__
SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs", SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs",
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0); SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs", SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs",
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1); 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) { if (!postfx_vert || !postfx_frag) {
SDL_Log("GpuPipeline: failed to create postfx shaders"); SDL_Log("GpuPipeline: failed to create postfx shaders");
if (postfx_vert) SDL_ReleaseGPUShader(device, postfx_vert); 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; } 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, SDL_GPUShader* GpuPipeline::createShader(SDL_GPUDevice* device,
const char* msl_source, const char* msl_source,
const char* entrypoint, const char* entrypoint,

View File

@@ -48,6 +48,15 @@ private:
Uint32 num_uniform_buffers, Uint32 num_uniform_buffers,
Uint32 num_storage_buffers = 0); 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* sprite_pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* ball_pipeline_ = nullptr; SDL_GPUGraphicsPipeline* ball_pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* postfx_pipeline_ = nullptr; SDL_GPUGraphicsPipeline* postfx_pipeline_ = nullptr;