primera implementacio de postfx
This commit is contained in:
@@ -8,12 +8,6 @@ set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Establece la política CMP0072 para indicar cómo se debe seleccionar la implementación de OpenGL.
|
||||
# En este caso, se elige la opción "GLVND", que utiliza bibliotecas modernas y modulares (libOpenGL, libGLX),
|
||||
# en lugar de la biblioteca OpenGL clásica (libGL). Esto mejora la compatibilidad con drivers recientes
|
||||
# y evita ambigüedades cuando se encuentran múltiples implementaciones de OpenGL en el sistema.
|
||||
cmake_policy(SET CMP0072 NEW)
|
||||
set(OpenGL_GL_PREFERENCE GLVND)
|
||||
|
||||
# --- GENERACIÓN DE VERSIÓN AUTOMÁTICA ---
|
||||
find_package(Git QUIET)
|
||||
@@ -118,21 +112,61 @@ set(EXTERNAL_SOURCES
|
||||
|
||||
# Fuentes del sistema de renderizado
|
||||
set(RENDERING_SOURCES
|
||||
source/rendering/opengl/opengl_shader.cpp
|
||||
source/rendering/sdl3gpu/sdl3gpu_shader.cpp
|
||||
)
|
||||
|
||||
# Configuración de SDL3
|
||||
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
||||
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
||||
|
||||
# --- SHADER COMPILATION (Linux/Windows only - macOS uses Metal) ---
|
||||
if(NOT APPLE)
|
||||
find_program(GLSLC_EXE NAMES glslc)
|
||||
|
||||
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
|
||||
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
|
||||
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_vert_spv.h")
|
||||
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_frag_spv.h")
|
||||
|
||||
if(GLSLC_EXE)
|
||||
add_custom_command(
|
||||
OUTPUT "${SHADER_VERT_H}" "${SHADER_FRAG_H}"
|
||||
COMMAND "${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.sh"
|
||||
DEPENDS "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}"
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Compilando shaders SPIR-V..."
|
||||
)
|
||||
add_custom_target(shaders DEPENDS "${SHADER_VERT_H}" "${SHADER_FRAG_H}")
|
||||
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
|
||||
else()
|
||||
if(NOT EXISTS "${SHADER_VERT_H}" OR NOT EXISTS "${SHADER_FRAG_H}")
|
||||
message(FATAL_ERROR
|
||||
"glslc no encontrado y headers SPIR-V no existen.\n"
|
||||
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
|
||||
" choco install vulkan-sdk (Windows)\n"
|
||||
" O genera los headers manualmente: tools/shaders/compile_spirv.sh"
|
||||
)
|
||||
else()
|
||||
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)")
|
||||
endif()
|
||||
|
||||
# --- 2. AÑADIR EJECUTABLE ---
|
||||
add_executable(${PROJECT_NAME} ${APP_SOURCES} ${EXTERNAL_SOURCES} ${RENDERING_SOURCES})
|
||||
|
||||
if(NOT APPLE AND GLSLC_EXE)
|
||||
add_dependencies(${PROJECT_NAME} shaders)
|
||||
endif()
|
||||
|
||||
# --- 3. DIRECTORIOS DE INCLUSIÓN ---
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
"${CMAKE_SOURCE_DIR}/source"
|
||||
"${CMAKE_SOURCE_DIR}/source/external"
|
||||
"${CMAKE_SOURCE_DIR}/source/rendering"
|
||||
"${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu"
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
)
|
||||
|
||||
@@ -155,26 +189,14 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<CONFIG:DEBUG>:_DEBUG>)
|
||||
# Configuración específica para cada plataforma
|
||||
if(WIN32)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE WINDOWS_BUILD)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 mingw32 opengl32)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 mingw32)
|
||||
elseif(APPLE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64")
|
||||
elseif(UNIX AND NOT APPLE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
||||
endif()
|
||||
|
||||
# Configuración común para OpenGL
|
||||
if(NOT WIN32)
|
||||
find_package(OpenGL REQUIRED)
|
||||
if(OPENGL_FOUND)
|
||||
message(STATUS "OpenGL encontrado: ${OPENGL_LIBRARIES}")
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${OPENGL_LIBRARIES})
|
||||
else()
|
||||
message(FATAL_ERROR "OpenGL no encontrado")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Especificar la ubicación del ejecutable
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
|
||||
|
||||
22
Makefile
22
Makefile
@@ -114,17 +114,17 @@ APP_SOURCES := \
|
||||
source/utils.cpp \
|
||||
source/external/jail_audio.cpp \
|
||||
source/external/gif.cpp \
|
||||
source/rendering/opengl/opengl_shader.cpp
|
||||
source/rendering/sdl3gpu/sdl3gpu_shader.cpp
|
||||
|
||||
# Includes
|
||||
INCLUDES := -Isource -Isource/external -Isource/rendering -I$(DIR_BUILD)
|
||||
INCLUDES := -Isource -Isource/external -Isource/rendering -Isource/rendering/sdl3gpu -I$(DIR_BUILD)
|
||||
|
||||
# Variables según el sistema operativo
|
||||
ifeq ($(OS),Windows_NT)
|
||||
FixPath = $(subst /,\\,$1)
|
||||
CXXFLAGS := -std=c++20 -Wall -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -static-libstdc++ -static-libgcc -Wl,-Bstatic -lpthread -Wl,-Bdynamic -Wl,-subsystem,windows -DWINDOWS_BUILD
|
||||
CXXFLAGS_DEBUG := -std=c++20 -Wall -g -D_DEBUG -DWINDOWS_BUILD
|
||||
LDFLAGS := -lmingw32 -lws2_32 -lSDL3 -lopengl32
|
||||
LDFLAGS := -lmingw32 -lws2_32 -lSDL3
|
||||
RM := del /Q
|
||||
MKDIR := mkdir
|
||||
else
|
||||
@@ -138,12 +138,10 @@ else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
CXXFLAGS += -DLINUX_BUILD
|
||||
LDFLAGS += -lGL
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
CXXFLAGS += -Wno-deprecated -DMACOS_BUILD
|
||||
CXXFLAGS_DEBUG += -Wno-deprecated -DMACOS_BUILD
|
||||
LDFLAGS += -framework OpenGL
|
||||
CXXFLAGS += -DMACOS_BUILD
|
||||
CXXFLAGS_DEBUG += -DMACOS_BUILD
|
||||
# Configurar arquitectura (por defecto arm64, como en CMake)
|
||||
CXXFLAGS += -arch arm64
|
||||
CXXFLAGS_DEBUG += -arch arm64
|
||||
@@ -254,7 +252,7 @@ macos_release:
|
||||
|
||||
# Compila la versión para procesadores Intel
|
||||
ifdef ENABLE_MACOS_X86_64
|
||||
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DMACOS_BUILD -DRELEASE_BUILD -std=c++20 -Wall -Os -Wno-deprecated -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -framework OpenGL -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos10.15
|
||||
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DMACOS_BUILD -DRELEASE_BUILD -std=c++20 -Wall -Os -Wno-deprecated -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos10.15
|
||||
|
||||
# Firma la aplicación
|
||||
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
|
||||
@@ -278,7 +276,7 @@ ifdef ENABLE_MACOS_X86_64
|
||||
endif
|
||||
|
||||
# Compila la versión para procesadores Apple Silicon
|
||||
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DMACOS_BUILD -DRELEASE_BUILD -DSDL_DISABLE_IMMINTRIN_H -std=c++20 -Wall -Os -Wno-deprecated -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -framework OpenGL -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11
|
||||
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DMACOS_BUILD -DRELEASE_BUILD -DSDL_DISABLE_IMMINTRIN_H -std=c++20 -Wall -Os -Wno-deprecated -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11
|
||||
|
||||
# Firma la aplicación
|
||||
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
|
||||
@@ -518,6 +516,10 @@ help:
|
||||
@echo " show_version - Mostrar version actual ($(VERSION))"
|
||||
@echo " help - Mostrar esta ayuda"
|
||||
|
||||
.PHONY: windows windows_rec windows_debug windows_release macos macos_debug macos_release linux linux_debug linux_release linux_release_desktop raspi raspi_debug raspi_release anbernic no_audio show_version help pack_tool resources.pack
|
||||
spirv:
|
||||
@echo "Compilando shaders SPIR-V..."
|
||||
tools/shaders/compile_spirv.sh
|
||||
|
||||
.PHONY: windows windows_rec windows_debug windows_release macos macos_debug macos_release linux linux_debug linux_release linux_release_desktop raspi raspi_debug raspi_release anbernic no_audio show_version help pack_tool resources.pack spirv
|
||||
|
||||
FORCE:
|
||||
@@ -6,6 +6,7 @@
|
||||
# Archivos de configuración del sistema (absolutos y opcionales)
|
||||
DATA|${SYSTEM_FOLDER}/config.yaml|optional,absolute
|
||||
DATA|${SYSTEM_FOLDER}/controllers.json|optional,absolute
|
||||
DATA|${SYSTEM_FOLDER}/postfx.yaml|optional,absolute
|
||||
DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute
|
||||
|
||||
# Archivos de configuración del juego
|
||||
|
||||
144
data/shaders/postfx.frag
Normal file
144
data/shaders/postfx.frag
Normal file
@@ -0,0 +1,144 @@
|
||||
#version 450
|
||||
|
||||
// Vulkan GLSL fragment shader — PostFX effects
|
||||
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
|
||||
// Compile: glslc postfx.frag -o postfx.frag.spv
|
||||
// xxd -i postfx.frag.spv > ../../source/core/rendering/sdl3gpu/postfx_frag_spv.h
|
||||
//
|
||||
// PostFXUniforms must match exactly the C++ struct in sdl3gpu_shader.hpp
|
||||
// (8 floats, 32 bytes, std140/scalar layout).
|
||||
|
||||
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 screen_height;
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||
float time; // seconds since SDL init
|
||||
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
|
||||
} u;
|
||||
|
||||
// YCbCr helpers for NTSC bleeding
|
||||
vec3 rgb_to_ycc(vec3 rgb) {
|
||||
return vec3(
|
||||
0.299*rgb.r + 0.587*rgb.g + 0.114*rgb.b,
|
||||
-0.169*rgb.r - 0.331*rgb.g + 0.500*rgb.b + 0.5,
|
||||
0.500*rgb.r - 0.419*rgb.g - 0.081*rgb.b + 0.5
|
||||
);
|
||||
}
|
||||
vec3 ycc_to_rgb(vec3 ycc) {
|
||||
float y = ycc.x;
|
||||
float cb = ycc.y - 0.5;
|
||||
float cr = ycc.z - 0.5;
|
||||
return clamp(vec3(
|
||||
y + 1.402*cr,
|
||||
y - 0.344*cb - 0.714*cr,
|
||||
y + 1.772*cb
|
||||
), 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = v_uv;
|
||||
|
||||
// Curvatura barrel CRT
|
||||
if (u.curvature > 0.0) {
|
||||
vec2 c = uv - 0.5;
|
||||
float rsq = dot(c, c);
|
||||
vec2 dist = vec2(0.05, 0.1) * u.curvature;
|
||||
vec2 barrelScale = vec2(1.0) - 0.23 * dist;
|
||||
c += c * (dist * rsq);
|
||||
c *= barrelScale;
|
||||
if (abs(c.x) >= 0.5 || abs(c.y) >= 0.5) {
|
||||
out_color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
return;
|
||||
}
|
||||
uv = c + 0.5;
|
||||
}
|
||||
|
||||
// Muestra base
|
||||
vec3 base = texture(scene, uv).rgb;
|
||||
|
||||
// Sangrado NTSC — difuminado horizontal de crominancia.
|
||||
// step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
|
||||
vec3 colour;
|
||||
if (u.bleeding > 0.0) {
|
||||
float tw = float(textureSize(scene, 0).x);
|
||||
float step = u.oversample / tw; // 1 pixel lógico en UV
|
||||
vec3 ycc = rgb_to_ycc(base);
|
||||
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
|
||||
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
|
||||
vec3 ycc_r1 = rgb_to_ycc(texture(scene, uv + vec2(1.0*step, 0.0)).rgb);
|
||||
vec3 ycc_r2 = rgb_to_ycc(texture(scene, uv + vec2(2.0*step, 0.0)).rgb);
|
||||
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0 + ycc.yz*2.0 + ycc_r1.yz*2.0 + ycc_r2.yz) / 8.0;
|
||||
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||
} else {
|
||||
colour = base;
|
||||
}
|
||||
|
||||
// Aberración cromática (drift animado con time para efecto NTSC real)
|
||||
float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
|
||||
colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
|
||||
colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
|
||||
|
||||
// Corrección gamma (linealizar antes de scanlines, codificar después)
|
||||
if (u.gamma_strength > 0.0) {
|
||||
vec3 lin = pow(colour, vec3(2.4));
|
||||
colour = mix(colour, lin, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Scanlines — 1 pixel físico oscuro por fila lógica.
|
||||
// Usa uv.y (independiente del offset de letterbox) con pixel_scale para
|
||||
// calcular la posición dentro de la fila en coordenadas físicas.
|
||||
// 3x: 1 dark + 2 bright. 4x: 1 dark + 3 bright.
|
||||
// bright=3.5×, dark floor=0.42 (mantiene aspecto CRT original).
|
||||
if (u.scanline_strength > 0.0) {
|
||||
float ps = max(1.0, round(u.pixel_scale));
|
||||
float frac_in_row = fract(uv.y * u.screen_height);
|
||||
float row_pos = floor(frac_in_row * ps);
|
||||
float is_dark = step(ps - 1.0, row_pos);
|
||||
float scan = mix(3.5, 0.42, is_dark);
|
||||
colour *= mix(1.0, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0) {
|
||||
vec3 enc = pow(colour, vec3(1.0 / 2.2));
|
||||
colour = mix(colour, enc, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Viñeta
|
||||
vec2 d = uv - 0.5;
|
||||
float vignette = 1.0 - dot(d, d) * u.vignette_strength;
|
||||
colour *= clamp(vignette, 0.0, 1.0);
|
||||
|
||||
// Máscara de fósforo RGB — después de scanlines (orden original):
|
||||
// filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
|
||||
if (u.mask_strength > 0.0) {
|
||||
float whichMask = fract(gl_FragCoord.x * 0.3333333);
|
||||
vec3 mask = vec3(0.80);
|
||||
if (whichMask < 0.3333333)
|
||||
mask.x = 1.0;
|
||||
else if (whichMask < 0.6666666)
|
||||
mask.y = 1.0;
|
||||
else
|
||||
mask.z = 1.0;
|
||||
colour = mix(colour, colour * mask, u.mask_strength);
|
||||
}
|
||||
|
||||
// Parpadeo de fósforo CRT (~50 Hz)
|
||||
if (u.flicker > 0.0) {
|
||||
float flicker_wave = sin(u.time * 100.0) * 0.5 + 0.5;
|
||||
colour *= 1.0 - u.flicker * 0.04 * flicker_wave;
|
||||
}
|
||||
|
||||
out_color = vec4(colour, 1.0);
|
||||
}
|
||||
24
data/shaders/postfx.vert
Normal file
24
data/shaders/postfx.vert
Normal file
@@ -0,0 +1,24 @@
|
||||
#version 450
|
||||
|
||||
// Vulkan GLSL vertex shader — postfx full-screen triangle
|
||||
// Used for SDL3 GPU API (SPIR-V path, Win/Linux).
|
||||
// Compile: glslc postfx.vert -o postfx.vert.spv
|
||||
// xxd -i postfx.vert.spv > ../../source/core/rendering/sdl3gpu/postfx_vert_spv.h
|
||||
|
||||
layout(location = 0) out vec2 v_uv;
|
||||
|
||||
void main() {
|
||||
// Full-screen triangle (no vertex buffer needed)
|
||||
const vec2 positions[3] = vec2[3](
|
||||
vec2(-1.0, -1.0),
|
||||
vec2( 3.0, -1.0),
|
||||
vec2(-1.0, 3.0)
|
||||
);
|
||||
const 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];
|
||||
}
|
||||
@@ -221,6 +221,8 @@ namespace GameDefaults {
|
||||
constexpr bool VIDEO_VSYNC = true;
|
||||
constexpr bool VIDEO_INTEGER_SCALE = true;
|
||||
constexpr bool VIDEO_SHADERS = false;
|
||||
constexpr bool VIDEO_POSTFX = false;
|
||||
constexpr bool VIDEO_SUPERSAMPLING = false;
|
||||
|
||||
// Music
|
||||
constexpr bool MUSIC_ENABLED = true;
|
||||
|
||||
@@ -105,7 +105,9 @@ void Director::init() {
|
||||
Logger::section("INIT CONFIG");
|
||||
Options::setConfigFile(Asset::get()->getPath("config.yaml")); // Establece el fichero de configuración
|
||||
Options::setControllersFile(Asset::get()->getPath("controllers.json")); // Establece el fichero de configuración de mandos
|
||||
Options::loadFromFile(); // Carga el archivo de configuración
|
||||
Options::setPostFXFile(Asset::get()->getPath("postfx.yaml")); // Establece el fichero de presets PostFX
|
||||
Options::loadFromFile(); // Carga el archivo de configuración
|
||||
Options::loadPostFXFromFile(); // Carga los presets PostFX
|
||||
loadParams(); // Carga los parámetros del programa
|
||||
loadScoreFile(); // Carga el archivo de puntuaciones
|
||||
|
||||
|
||||
@@ -62,10 +62,23 @@ namespace GlobalInputs {
|
||||
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 14") + " " + boolToOnOff(Options::video.vsync)});
|
||||
}
|
||||
|
||||
// Activa o desactiva los shaders
|
||||
void toggleShaders() {
|
||||
Screen::toggleShaders();
|
||||
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 13") + " " + boolToOnOff(Options::video.shaders)});
|
||||
// Activa o desactiva los efectos PostFX
|
||||
void togglePostFX() {
|
||||
Screen::togglePostFX();
|
||||
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 13") + " " + boolToOnOff(Options::video.postfx)});
|
||||
}
|
||||
|
||||
// Avanza al siguiente preset PostFX
|
||||
void nextPostFXPreset() {
|
||||
Screen::nextPostFXPreset();
|
||||
const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::current_postfx_preset)).name;
|
||||
Notifier::get()->show({"PostFX: " + name});
|
||||
}
|
||||
|
||||
// Activa o desactiva el supersampling 3x
|
||||
void toggleSupersampling() {
|
||||
Screen::toggleSupersampling();
|
||||
Notifier::get()->show({"3x SS: " + boolToOnOff(Options::video.supersampling)});
|
||||
}
|
||||
|
||||
// Cambia al siguiente idioma
|
||||
@@ -169,7 +182,6 @@ namespace GlobalInputs {
|
||||
{Action::TOGGLE_AUDIO, toggleAudio},
|
||||
{Action::TOGGLE_AUTO_FIRE, toggleFireMode},
|
||||
{Action::CHANGE_LANG, setNextLang},
|
||||
{Action::TOGGLE_VIDEO_SHADERS, toggleShaders},
|
||||
{Action::TOGGLE_VIDEO_INTEGER_SCALE, toggleIntegerScale},
|
||||
{Action::TOGGLE_VIDEO_VSYNC, toggleVSync},
|
||||
#ifdef _DEBUG
|
||||
@@ -177,13 +189,29 @@ namespace GlobalInputs {
|
||||
#endif
|
||||
};
|
||||
|
||||
return std::ranges::any_of(ACTIONS, [](const auto& pair) -> auto {
|
||||
if (Input::get()->checkAction(pair.first, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
||||
pair.second();
|
||||
return true;
|
||||
if (std::ranges::any_of(ACTIONS, [](const auto& pair) -> auto {
|
||||
if (Input::get()->checkAction(pair.first, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
||||
pair.second();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// F4 con modificadores: Ctrl+F4 = supersampling, Shift+F4 = siguiente preset, F4 = toggle PostFX
|
||||
if (Input::get()->checkAction(Input::Action::TOGGLE_VIDEO_POSTFX, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
||||
if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
|
||||
toggleSupersampling();
|
||||
} else if (Options::video.postfx && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) {
|
||||
nextPostFXPreset();
|
||||
} else {
|
||||
togglePostFX();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Comprueba el resto de entradas
|
||||
|
||||
@@ -82,6 +82,7 @@ class Input {
|
||||
{Action::WINDOW_INC_SIZE, KeyState(SDL_SCANCODE_F2)},
|
||||
{Action::WINDOW_FULLSCREEN, KeyState(SDL_SCANCODE_F3)},
|
||||
{Action::TOGGLE_VIDEO_SHADERS, KeyState(SDL_SCANCODE_F4)},
|
||||
{Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
|
||||
{Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)},
|
||||
{Action::TOGGLE_VIDEO_VSYNC, KeyState(SDL_SCANCODE_F6)},
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
|
||||
{InputAction::WINDOW_INC_SIZE, "WINDOW_INC_SIZE"},
|
||||
{InputAction::WINDOW_DEC_SIZE, "WINDOW_DEC_SIZE"},
|
||||
{InputAction::TOGGLE_VIDEO_SHADERS, "TOGGLE_VIDEO_SHADERS"},
|
||||
{InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"},
|
||||
{InputAction::TOGGLE_VIDEO_INTEGER_SCALE, "TOGGLE_VIDEO_INTEGER_SCALE"},
|
||||
{InputAction::TOGGLE_VIDEO_VSYNC, "TOGGLE_VIDEO_VSYNC"},
|
||||
{InputAction::RESET, "RESET"},
|
||||
@@ -52,6 +53,7 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
|
||||
{"WINDOW_INC_SIZE", InputAction::WINDOW_INC_SIZE},
|
||||
{"WINDOW_DEC_SIZE", InputAction::WINDOW_DEC_SIZE},
|
||||
{"TOGGLE_VIDEO_SHADERS", InputAction::TOGGLE_VIDEO_SHADERS},
|
||||
{"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX},
|
||||
{"TOGGLE_VIDEO_INTEGER_SCALE", InputAction::TOGGLE_VIDEO_INTEGER_SCALE},
|
||||
{"TOGGLE_VIDEO_VSYNC", InputAction::TOGGLE_VIDEO_VSYNC},
|
||||
{"RESET", InputAction::RESET},
|
||||
|
||||
@@ -31,7 +31,8 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
|
||||
WINDOW_FULLSCREEN,
|
||||
WINDOW_INC_SIZE,
|
||||
WINDOW_DEC_SIZE,
|
||||
TOGGLE_VIDEO_SHADERS,
|
||||
TOGGLE_VIDEO_SHADERS, // backward compat alias
|
||||
TOGGLE_VIDEO_POSTFX,
|
||||
TOGGLE_VIDEO_INTEGER_SCALE,
|
||||
TOGGLE_VIDEO_VSYNC,
|
||||
RESET,
|
||||
|
||||
@@ -24,6 +24,16 @@ namespace Options {
|
||||
GamepadManager gamepad_manager; // Opciones de mando para cada jugador
|
||||
Keyboard keyboard; // Opciones para el teclado
|
||||
PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
||||
std::vector<PostFXPreset> postfx_presets = { // Lista de presets de PostFX
|
||||
{"CRT", 0.6F, 0.7F, 0.15F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F},
|
||||
{"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F},
|
||||
{"CURVED", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F},
|
||||
{"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F},
|
||||
{"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F},
|
||||
{"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F},
|
||||
};
|
||||
int current_postfx_preset = 0; // Índice del preset PostFX activo
|
||||
std::string postfx_file_path; // Ruta al fichero de presets PostFX
|
||||
|
||||
// Establece el fichero de configuración
|
||||
void setConfigFile(const std::string& file_path) { settings.config_file = file_path; }
|
||||
@@ -31,6 +41,166 @@ namespace Options {
|
||||
// Establece el fichero de configuración de mandos
|
||||
void setControllersFile(const std::string& file_path) { settings.controllers_file = file_path; }
|
||||
|
||||
// Establece la ruta del fichero de PostFX
|
||||
void setPostFXFile(const std::string& path) { postfx_file_path = path; }
|
||||
|
||||
// Helper: extrae un campo float de un nodo YAML si existe, ignorando errores de conversión
|
||||
static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) {
|
||||
if (node.contains(key)) {
|
||||
try {
|
||||
target = node[key].get_value<float>();
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Carga los presets de PostFX desde el fichero
|
||||
auto loadPostFXFromFile() -> bool {
|
||||
postfx_presets.clear();
|
||||
|
||||
std::ifstream file(postfx_file_path);
|
||||
if (!file.good()) {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "PostFX file not found, creating default: %s", postfx_file_path.c_str());
|
||||
return savePostFXToFile();
|
||||
}
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
|
||||
try {
|
||||
auto yaml = fkyaml::node::deserialize(content);
|
||||
|
||||
if (yaml.contains("presets")) {
|
||||
const auto& presets = yaml["presets"];
|
||||
for (const auto& p : presets) {
|
||||
PostFXPreset preset;
|
||||
if (p.contains("name")) {
|
||||
preset.name = p["name"].get_value<std::string>();
|
||||
}
|
||||
parseFloatField(p, "vignette", preset.vignette);
|
||||
parseFloatField(p, "scanlines", preset.scanlines);
|
||||
parseFloatField(p, "chroma", preset.chroma);
|
||||
parseFloatField(p, "mask", preset.mask);
|
||||
parseFloatField(p, "gamma", preset.gamma);
|
||||
parseFloatField(p, "curvature", preset.curvature);
|
||||
parseFloatField(p, "bleeding", preset.bleeding);
|
||||
parseFloatField(p, "flicker", preset.flicker);
|
||||
postfx_presets.push_back(preset);
|
||||
}
|
||||
}
|
||||
|
||||
if (!postfx_presets.empty()) {
|
||||
current_postfx_preset = std::clamp(
|
||||
current_postfx_preset, 0,
|
||||
static_cast<int>(postfx_presets.size()) - 1);
|
||||
} else {
|
||||
current_postfx_preset = 0;
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "PostFX file loaded: %zu preset(s)", postfx_presets.size());
|
||||
return true;
|
||||
|
||||
} catch (const fkyaml::exception& e) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Error parsing PostFX YAML: %s. Recreating defaults.", e.what());
|
||||
return savePostFXToFile();
|
||||
}
|
||||
}
|
||||
|
||||
// Guarda los presets de PostFX por defecto al fichero
|
||||
auto savePostFXToFile() -> bool {
|
||||
if (postfx_file_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(postfx_file_path);
|
||||
if (!file.is_open()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: %s can't be opened for writing", postfx_file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
file << "# Coffee Crisis Arcade Edition - PostFX Presets\n";
|
||||
file << "# Each preset defines the intensity of post-processing effects (0.0 to 1.0).\n";
|
||||
file << "# vignette: screen darkening at the edges\n";
|
||||
file << "# scanlines: horizontal scanline effect\n";
|
||||
file << "# chroma: chromatic aberration (RGB color fringing)\n";
|
||||
file << "# mask: phosphor dot mask (RGB subpixel pattern)\n";
|
||||
file << "# gamma: gamma correction input 2.4 / output 2.2\n";
|
||||
file << "# curvature: CRT barrel distortion\n";
|
||||
file << "# bleeding: NTSC horizontal colour bleeding\n";
|
||||
file << "# flicker: phosphor CRT flicker ~50 Hz (0.0 = off, 1.0 = max)\n";
|
||||
file << "\n";
|
||||
file << "presets:\n";
|
||||
file << " - name: \"CRT\"\n";
|
||||
file << " vignette: 0.6\n";
|
||||
file << " scanlines: 0.7\n";
|
||||
file << " chroma: 0.15\n";
|
||||
file << " mask: 0.5\n";
|
||||
file << " gamma: 0.5\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.0\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"NTSC\"\n";
|
||||
file << " vignette: 0.4\n";
|
||||
file << " scanlines: 0.5\n";
|
||||
file << " chroma: 0.2\n";
|
||||
file << " mask: 0.3\n";
|
||||
file << " gamma: 0.3\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.6\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"CURVED\"\n";
|
||||
file << " vignette: 0.5\n";
|
||||
file << " scanlines: 0.6\n";
|
||||
file << " chroma: 0.1\n";
|
||||
file << " mask: 0.4\n";
|
||||
file << " gamma: 0.4\n";
|
||||
file << " curvature: 0.8\n";
|
||||
file << " bleeding: 0.0\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"SCANLINES\"\n";
|
||||
file << " vignette: 0.0\n";
|
||||
file << " scanlines: 0.8\n";
|
||||
file << " chroma: 0.0\n";
|
||||
file << " mask: 0.0\n";
|
||||
file << " gamma: 0.0\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.0\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"SUBTLE\"\n";
|
||||
file << " vignette: 0.3\n";
|
||||
file << " scanlines: 0.4\n";
|
||||
file << " chroma: 0.05\n";
|
||||
file << " mask: 0.0\n";
|
||||
file << " gamma: 0.2\n";
|
||||
file << " curvature: 0.0\n";
|
||||
file << " bleeding: 0.0\n";
|
||||
file << " flicker: 0.0\n";
|
||||
file << " - name: \"CRT LIVE\"\n";
|
||||
file << " vignette: 0.5\n";
|
||||
file << " scanlines: 0.6\n";
|
||||
file << " chroma: 0.3\n";
|
||||
file << " mask: 0.3\n";
|
||||
file << " gamma: 0.4\n";
|
||||
file << " curvature: 0.3\n";
|
||||
file << " bleeding: 0.4\n";
|
||||
file << " flicker: 0.8\n";
|
||||
|
||||
file.close();
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "PostFX file created with defaults: %s", postfx_file_path.c_str());
|
||||
|
||||
// Cargar los presets recién escritos
|
||||
postfx_presets.clear();
|
||||
postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.15F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F});
|
||||
postfx_presets.push_back({"CURVED", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F});
|
||||
current_postfx_preset = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inicializa las opciones del programa
|
||||
void init() {
|
||||
// Dificultades
|
||||
@@ -78,6 +248,20 @@ namespace Options {
|
||||
if (vid.contains("shaders")) {
|
||||
try { video.shaders = vid["shaders"].get_value<bool>(); } catch (...) {}
|
||||
}
|
||||
if (vid.contains("postfx")) {
|
||||
try { video.postfx = vid["postfx"].get_value<bool>(); } catch (...) {}
|
||||
}
|
||||
if (vid.contains("supersampling")) {
|
||||
try { video.supersampling = vid["supersampling"].get_value<bool>(); } catch (...) {}
|
||||
}
|
||||
if (vid.contains("postfx_preset")) {
|
||||
try {
|
||||
int preset = vid["postfx_preset"].get_value<int>();
|
||||
if (preset >= 0 && preset < static_cast<int>(postfx_presets.size())) {
|
||||
current_postfx_preset = preset;
|
||||
}
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
void loadAudioFromYaml(const fkyaml::node& yaml) {
|
||||
@@ -247,6 +431,9 @@ namespace Options {
|
||||
file << " vsync: " << boolToString(video.vsync) << "\n";
|
||||
file << " integer_scale: " << boolToString(video.integer_scale) << "\n";
|
||||
file << " shaders: " << boolToString(video.shaders) << "\n";
|
||||
file << " postfx: " << boolToString(video.postfx) << "\n";
|
||||
file << " supersampling: " << boolToString(video.supersampling) << "\n";
|
||||
file << " postfx_preset: " << current_postfx_preset << "\n";
|
||||
file << "\n";
|
||||
|
||||
// AUDIO
|
||||
|
||||
@@ -24,6 +24,18 @@
|
||||
namespace Options {
|
||||
|
||||
// --- Estructuras ---
|
||||
struct PostFXPreset {
|
||||
std::string name;
|
||||
float vignette{0.6F};
|
||||
float scanlines{0.7F};
|
||||
float chroma{0.15F};
|
||||
float mask{0.0F};
|
||||
float gamma{0.0F};
|
||||
float curvature{0.0F};
|
||||
float bleeding{0.0F};
|
||||
float flicker{0.0F};
|
||||
};
|
||||
|
||||
struct Window {
|
||||
std::string caption = GameDefaults::Options::WINDOW_CAPTION; // Texto que aparece en la barra de título de la ventana
|
||||
int zoom = GameDefaults::Options::WINDOW_ZOOM; // Valor por el que se multiplica el tamaño de la ventana
|
||||
@@ -35,7 +47,9 @@ namespace Options {
|
||||
bool fullscreen = GameDefaults::Options::VIDEO_FULLSCREEN; // Indica si se usa pantalla completa
|
||||
bool vsync = GameDefaults::Options::VIDEO_VSYNC; // Indica si se usa vsync
|
||||
bool integer_scale = GameDefaults::Options::VIDEO_INTEGER_SCALE; // Indica si se usa escalado entero
|
||||
bool shaders = GameDefaults::Options::VIDEO_SHADERS; // Indica si se usan shaders para los filtros de vídeo
|
||||
bool shaders = GameDefaults::Options::VIDEO_SHADERS; // Indica si se usan shaders para los filtros de vídeo (backward compat)
|
||||
bool postfx = GameDefaults::Options::VIDEO_POSTFX; // Indica si se usan efectos PostFX
|
||||
bool supersampling = GameDefaults::Options::VIDEO_SUPERSAMPLING; // Indica si se usa supersampling 3x
|
||||
std::string info; // Información sobre el modo de vídeo
|
||||
};
|
||||
|
||||
@@ -278,11 +292,17 @@ namespace Options {
|
||||
extern GamepadManager gamepad_manager; // Manager de mandos para cada jugador
|
||||
extern Keyboard keyboard; // Opciones para el teclado
|
||||
extern PendingChanges pending_changes; // Opciones que se aplican al cerrar
|
||||
extern std::vector<PostFXPreset> postfx_presets; // Lista de presets de PostFX
|
||||
extern int current_postfx_preset; // Índice del preset PostFX activo
|
||||
extern std::string postfx_file_path; // Ruta al fichero de presets PostFX
|
||||
|
||||
// --- Funciones ---
|
||||
void init(); // Inicializa las opciones del programa
|
||||
void setConfigFile(const std::string& file_path); // Establece el fichero de configuración
|
||||
void setControllersFile(const std::string& file_path); // Establece el fichero de configuración de mandos
|
||||
void setPostFXFile(const std::string& path); // Establece el fichero de presets PostFX
|
||||
auto loadPostFXFromFile() -> bool; // Carga los presets PostFX desde fichero
|
||||
auto savePostFXToFile() -> bool; // Guarda los presets PostFX por defecto al fichero
|
||||
auto loadFromFile() -> bool; // Carga el fichero de configuración
|
||||
auto saveToFile() -> bool; // Guarda el fichero de configuración
|
||||
void setKeyboardToPlayer(Player::Id player_id); // Asigna el teclado al jugador
|
||||
|
||||
@@ -1,522 +0,0 @@
|
||||
#include "opengl_shader.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "ui/logger.hpp" // Para Loger
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
OpenGLShader::~OpenGLShader() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
auto OpenGLShader::initGLExtensions() -> bool {
|
||||
glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader");
|
||||
glShaderSource = (PFNGLSHADERSOURCEPROC)SDL_GL_GetProcAddress("glShaderSource");
|
||||
glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader");
|
||||
glGetShaderiv = (PFNGLGETSHADERIVPROC)SDL_GL_GetProcAddress("glGetShaderiv");
|
||||
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)SDL_GL_GetProcAddress("glGetShaderInfoLog");
|
||||
glDeleteShader = (PFNGLDELETESHADERPROC)SDL_GL_GetProcAddress("glDeleteShader");
|
||||
glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader");
|
||||
glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram");
|
||||
glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram");
|
||||
glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)SDL_GL_GetProcAddress("glValidateProgram");
|
||||
glGetProgramiv = (PFNGLGETPROGRAMIVPROC)SDL_GL_GetProcAddress("glGetProgramiv");
|
||||
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)SDL_GL_GetProcAddress("glGetProgramInfoLog");
|
||||
glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram");
|
||||
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)SDL_GL_GetProcAddress("glDeleteProgram");
|
||||
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)SDL_GL_GetProcAddress("glGetUniformLocation");
|
||||
glUniform2f = (PFNGLUNIFORM2FPROC)SDL_GL_GetProcAddress("glUniform2f");
|
||||
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glGenVertexArrays");
|
||||
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)SDL_GL_GetProcAddress("glBindVertexArray");
|
||||
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glDeleteVertexArrays");
|
||||
glGenBuffers = (PFNGLGENBUFFERSPROC)SDL_GL_GetProcAddress("glGenBuffers");
|
||||
glBindBuffer = (PFNGLBINDBUFFERPROC)SDL_GL_GetProcAddress("glBindBuffer");
|
||||
glBufferData = (PFNGLBUFFERDATAPROC)SDL_GL_GetProcAddress("glBufferData");
|
||||
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteBuffers");
|
||||
glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)SDL_GL_GetProcAddress("glVertexAttribPointer");
|
||||
glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)SDL_GL_GetProcAddress("glEnableVertexAttribArray");
|
||||
|
||||
return (glCreateShader != nullptr) && (glShaderSource != nullptr) && (glCompileShader != nullptr) && (glGetShaderiv != nullptr) &&
|
||||
(glGetShaderInfoLog != nullptr) && (glDeleteShader != nullptr) && (glAttachShader != nullptr) && (glCreateProgram != nullptr) &&
|
||||
(glLinkProgram != nullptr) && (glValidateProgram != nullptr) && (glGetProgramiv != nullptr) && (glGetProgramInfoLog != nullptr) &&
|
||||
(glUseProgram != nullptr) && (glDeleteProgram != nullptr) && (glGetUniformLocation != nullptr) && (glUniform2f != nullptr) &&
|
||||
(glGenVertexArrays != nullptr) && (glBindVertexArray != nullptr) && (glDeleteVertexArrays != nullptr) &&
|
||||
(glGenBuffers != nullptr) && (glBindBuffer != nullptr) && (glBufferData != nullptr) && (glDeleteBuffers != nullptr) &&
|
||||
(glVertexAttribPointer != nullptr) && (glEnableVertexAttribArray != nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
void OpenGLShader::checkGLError(const char* operation) {
|
||||
GLenum error = glGetError();
|
||||
if (error != GL_NO_ERROR) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error OpenGL en %s: 0x%x",
|
||||
operation,
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
auto OpenGLShader::compileShader(const std::string& source, GLenum shader_type) -> GLuint {
|
||||
if (source.empty()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"ERROR: El código fuente del shader está vacío");
|
||||
return 0;
|
||||
}
|
||||
|
||||
GLuint shader_id = glCreateShader(shader_type);
|
||||
if (shader_id == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al crear shader");
|
||||
checkGLError("glCreateShader");
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::array<const char*, 1> sources = {source.c_str()};
|
||||
glShaderSource(shader_id, 1, sources.data(), nullptr);
|
||||
checkGLError("glShaderSource");
|
||||
|
||||
glCompileShader(shader_id);
|
||||
checkGLError("glCompileShader");
|
||||
|
||||
// Verificar compilación
|
||||
GLint compiled = GL_FALSE;
|
||||
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &compiled);
|
||||
if (compiled != GL_TRUE) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error en compilación del shader");
|
||||
GLint log_length;
|
||||
glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_length);
|
||||
if (log_length > 0) {
|
||||
std::vector<char> log(log_length);
|
||||
glGetShaderInfoLog(shader_id, log_length, &log_length, log.data());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Log de compilación: %s",
|
||||
log.data());
|
||||
}
|
||||
glDeleteShader(shader_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return shader_id;
|
||||
}
|
||||
|
||||
auto OpenGLShader::linkProgram(GLuint vertex_shader, GLuint fragment_shader) -> GLuint {
|
||||
GLuint program = glCreateProgram();
|
||||
if (program == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al crear programa de shaders");
|
||||
return 0;
|
||||
}
|
||||
|
||||
glAttachShader(program, vertex_shader);
|
||||
checkGLError("glAttachShader(vertex)");
|
||||
glAttachShader(program, fragment_shader);
|
||||
checkGLError("glAttachShader(fragment)");
|
||||
|
||||
glLinkProgram(program);
|
||||
checkGLError("glLinkProgram");
|
||||
|
||||
// Verificar enlace
|
||||
GLint linked = GL_FALSE;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &linked);
|
||||
if (linked != GL_TRUE) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al enlazar programa");
|
||||
GLint log_length;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
|
||||
if (log_length > 0) {
|
||||
std::vector<char> log(log_length);
|
||||
glGetProgramInfoLog(program, log_length, &log_length, log.data());
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Log de enlace: %s",
|
||||
log.data());
|
||||
}
|
||||
glDeleteProgram(program);
|
||||
return 0;
|
||||
}
|
||||
|
||||
glValidateProgram(program);
|
||||
checkGLError("glValidateProgram");
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
void OpenGLShader::createQuadGeometry() {
|
||||
// Datos del quad: posición (x, y) + coordenadas de textura (u, v)
|
||||
// Formato: x, y, u, v
|
||||
std::array<float, 16> vertices = {
|
||||
// Posición // TexCoords
|
||||
-1.0F,
|
||||
-1.0F,
|
||||
0.0F,
|
||||
0.0F, // Inferior izquierda
|
||||
1.0F,
|
||||
-1.0F,
|
||||
1.0F,
|
||||
0.0F, // Inferior derecha
|
||||
1.0F,
|
||||
1.0F,
|
||||
1.0F,
|
||||
1.0F, // Superior derecha
|
||||
-1.0F,
|
||||
1.0F,
|
||||
0.0F,
|
||||
1.0F // Superior izquierda
|
||||
};
|
||||
|
||||
// Índices para dibujar el quad con dos triángulos
|
||||
std::array<unsigned int, 6> indices = {
|
||||
0,
|
||||
1,
|
||||
2, // Primer triángulo
|
||||
2,
|
||||
3,
|
||||
0 // Segundo triángulo
|
||||
};
|
||||
|
||||
// Generar y configurar VAO
|
||||
glGenVertexArrays(1, &vao_);
|
||||
glBindVertexArray(vao_);
|
||||
checkGLError("glBindVertexArray");
|
||||
|
||||
// Generar y configurar VBO
|
||||
glGenBuffers(1, &vbo_);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices.data(), GL_STATIC_DRAW);
|
||||
checkGLError("glBufferData(VBO)");
|
||||
|
||||
// Generar y configurar EBO
|
||||
glGenBuffers(1, &ebo_);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices.data(), GL_STATIC_DRAW);
|
||||
checkGLError("glBufferData(EBO)");
|
||||
|
||||
// Atributo 0: Posición (2 floats)
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), nullptr);
|
||||
glEnableVertexAttribArray(0);
|
||||
checkGLError("glVertexAttribPointer(position)");
|
||||
|
||||
// Atributo 1: Coordenadas de textura (2 floats)
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), static_cast<const void*>(static_cast<const char*>(nullptr) + (2 * sizeof(float))));
|
||||
glEnableVertexAttribArray(1);
|
||||
checkGLError("glVertexAttribPointer(texcoord)");
|
||||
|
||||
// Desvincular
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
auto OpenGLShader::getTextureID(SDL_Texture* texture) -> GLuint {
|
||||
if (texture == nullptr) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
|
||||
GLuint texture_id = 0;
|
||||
|
||||
// Intentar obtener ID de textura OpenGL
|
||||
texture_id = static_cast<GLuint>(reinterpret_cast<uintptr_t>(SDL_GetPointerProperty(props, "SDL.texture.opengl.texture", nullptr)));
|
||||
|
||||
if (texture_id == 0) {
|
||||
texture_id = static_cast<GLuint>(reinterpret_cast<uintptr_t>(SDL_GetPointerProperty(props, "texture.opengl.texture", nullptr)));
|
||||
}
|
||||
|
||||
if (texture_id == 0) {
|
||||
texture_id = static_cast<GLuint>(SDL_GetNumberProperty(props, "SDL.texture.opengl.texture", 1));
|
||||
}
|
||||
|
||||
if (texture_id == 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"No se pudo obtener ID de textura OpenGL, usando 1 por defecto");
|
||||
texture_id = 1;
|
||||
}
|
||||
|
||||
return texture_id;
|
||||
}
|
||||
|
||||
auto OpenGLShader::init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool {
|
||||
window_ = window;
|
||||
back_buffer_ = texture;
|
||||
renderer_ = SDL_GetRenderer(window);
|
||||
|
||||
if (renderer_ == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error: No se pudo obtener el renderer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Obtener tamaños
|
||||
SDL_GetWindowSize(window_, &window_width_, &window_height_);
|
||||
SDL_GetTextureSize(back_buffer_, &texture_width_, &texture_height_);
|
||||
|
||||
/*
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Inicializando shaders: ventana=%dx%d, textura=%.0fx%.0f",
|
||||
window_width_,
|
||||
window_height_,
|
||||
texture_width_,
|
||||
texture_height_);
|
||||
*/
|
||||
|
||||
Logger::info(
|
||||
"Inicializando shaders: ventana=" +
|
||||
std::to_string(window_width_) +
|
||||
"x" +
|
||||
std::to_string(window_height_) +
|
||||
", textura=" +
|
||||
std::to_string(static_cast<int>(texture_width_)) +
|
||||
"x" +
|
||||
std::to_string(static_cast<int>(texture_height_)));
|
||||
|
||||
// Verificar que es OpenGL
|
||||
const char* renderer_name = SDL_GetRendererName(renderer_);
|
||||
if ((renderer_name == nullptr) || strncmp(renderer_name, "opengl", 6) != 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Renderer no es OpenGL: %s",
|
||||
(renderer_name != nullptr) ? renderer_name : "unknown");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
// Inicializar extensiones OpenGL en Windows/Linux
|
||||
if (!initGLExtensions()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al inicializar extensiones OpenGL");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Limpiar shader anterior si existe
|
||||
if (program_id_ != 0) {
|
||||
glDeleteProgram(program_id_);
|
||||
program_id_ = 0;
|
||||
}
|
||||
|
||||
// Compilar shaders
|
||||
GLuint vertex_shader = compileShader(vertex_source, GL_VERTEX_SHADER);
|
||||
GLuint fragment_shader = compileShader(fragment_source, GL_FRAGMENT_SHADER);
|
||||
|
||||
if (vertex_shader == 0 || fragment_shader == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al compilar shaders");
|
||||
if (vertex_shader != 0) {
|
||||
glDeleteShader(vertex_shader);
|
||||
}
|
||||
if (fragment_shader != 0) {
|
||||
glDeleteShader(fragment_shader);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enlazar programa
|
||||
program_id_ = linkProgram(vertex_shader, fragment_shader);
|
||||
|
||||
// Limpiar shaders (ya no necesarios tras el enlace)
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
|
||||
if (program_id_ == 0) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error al crear programa de shaders");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Crear geometría del quad
|
||||
createQuadGeometry();
|
||||
|
||||
// Obtener ubicación del uniform TextureSize
|
||||
glUseProgram(program_id_);
|
||||
texture_size_location_ = glGetUniformLocation(program_id_, "TextureSize");
|
||||
if (texture_size_location_ != -1) {
|
||||
/*
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Configurando TextureSize uniform: %.0fx%.0f",
|
||||
texture_width_,
|
||||
texture_height_);
|
||||
*/
|
||||
Logger::info(
|
||||
"Configurando TextureSize uniform: " +
|
||||
std::to_string(static_cast<int>(texture_width_)) +
|
||||
"x" +
|
||||
std::to_string(static_cast<int>(texture_height_)));
|
||||
glUniform2f(texture_size_location_, texture_width_, texture_height_);
|
||||
checkGLError("glUniform2f(TextureSize)");
|
||||
} else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Uniform 'TextureSize' no encontrado en shader");
|
||||
}
|
||||
glUseProgram(0);
|
||||
|
||||
is_initialized_ = true;
|
||||
/*
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"** OpenGL 3.3 Shader Backend inicializado correctamente");
|
||||
*/
|
||||
Logger::info("OpenGL 3.3 Shader Backend inicializado correctamente");
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLShader::render() {
|
||||
if (!is_initialized_ || program_id_ == 0) {
|
||||
// Fallback: renderizado SDL normal
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_RenderTexture(renderer_, back_buffer_, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtener tamaño actual de ventana (puede haber cambiado)
|
||||
int current_width;
|
||||
int current_height;
|
||||
SDL_GetWindowSize(window_, ¤t_width, ¤t_height);
|
||||
|
||||
// Guardar estados OpenGL
|
||||
GLint old_program;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, &old_program);
|
||||
|
||||
std::array<GLint, 4> old_viewport{};
|
||||
glGetIntegerv(GL_VIEWPORT, old_viewport.data());
|
||||
|
||||
GLboolean was_texture_enabled = glIsEnabled(GL_TEXTURE_2D);
|
||||
GLint old_texture;
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture);
|
||||
|
||||
GLint old_vao;
|
||||
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao);
|
||||
|
||||
// Preparar renderizado
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
SDL_RenderClear(renderer_);
|
||||
|
||||
// Obtener y bindear textura
|
||||
GLuint texture_id = getTextureID(back_buffer_);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, texture_id);
|
||||
checkGLError("glBindTexture");
|
||||
|
||||
// Usar nuestro programa
|
||||
glUseProgram(program_id_);
|
||||
checkGLError("glUseProgram");
|
||||
|
||||
// Configurar viewport (obtener tamaño lógico de SDL)
|
||||
int logical_w;
|
||||
int logical_h;
|
||||
SDL_RendererLogicalPresentation mode;
|
||||
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &mode);
|
||||
|
||||
if (logical_w == 0 || logical_h == 0) {
|
||||
logical_w = current_width;
|
||||
logical_h = current_height;
|
||||
}
|
||||
|
||||
// Calcular viewport considerando aspect ratio
|
||||
int viewport_x = 0;
|
||||
int viewport_y = 0;
|
||||
int viewport_w = current_width;
|
||||
int viewport_h = current_height;
|
||||
|
||||
if (mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) {
|
||||
int scale_x = current_width / logical_w;
|
||||
int scale_y = current_height / logical_h;
|
||||
int scale = (scale_x < scale_y) ? scale_x : scale_y;
|
||||
scale = std::max(scale, 1);
|
||||
|
||||
viewport_w = logical_w * scale;
|
||||
viewport_h = logical_h * scale;
|
||||
viewport_x = (current_width - viewport_w) / 2;
|
||||
viewport_y = (current_height - viewport_h) / 2;
|
||||
} else {
|
||||
float window_aspect = static_cast<float>(current_width) / current_height;
|
||||
float logical_aspect = static_cast<float>(logical_w) / logical_h;
|
||||
|
||||
if (window_aspect > logical_aspect) {
|
||||
viewport_w = static_cast<int>(logical_aspect * current_height);
|
||||
viewport_x = (current_width - viewport_w) / 2;
|
||||
} else {
|
||||
viewport_h = static_cast<int>(current_width / logical_aspect);
|
||||
viewport_y = (current_height - viewport_h) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
glViewport(viewport_x, viewport_y, viewport_w, viewport_h);
|
||||
checkGLError("glViewport");
|
||||
|
||||
// Dibujar quad usando VAO
|
||||
glBindVertexArray(vao_);
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
|
||||
checkGLError("glDrawElements");
|
||||
|
||||
// Presentar
|
||||
SDL_GL_SwapWindow(window_);
|
||||
|
||||
// Restaurar estados OpenGL
|
||||
glUseProgram(old_program);
|
||||
glBindTexture(GL_TEXTURE_2D, old_texture);
|
||||
if (was_texture_enabled == 0U) {
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
}
|
||||
glBindVertexArray(old_vao);
|
||||
glViewport(old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3]);
|
||||
}
|
||||
|
||||
void OpenGLShader::setTextureSize(float width, float height) {
|
||||
if (!is_initialized_ || program_id_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
texture_width_ = width;
|
||||
texture_height_ = height;
|
||||
|
||||
GLint old_program;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, &old_program);
|
||||
|
||||
glUseProgram(program_id_);
|
||||
|
||||
if (texture_size_location_ != -1) {
|
||||
glUniform2f(texture_size_location_, width, height);
|
||||
checkGLError("glUniform2f(TextureSize)");
|
||||
}
|
||||
|
||||
glUseProgram(old_program);
|
||||
}
|
||||
|
||||
void OpenGLShader::cleanup() {
|
||||
if (vao_ != 0) {
|
||||
glDeleteVertexArrays(1, &vao_);
|
||||
vao_ = 0;
|
||||
}
|
||||
|
||||
if (vbo_ != 0) {
|
||||
glDeleteBuffers(1, &vbo_);
|
||||
vbo_ = 0;
|
||||
}
|
||||
|
||||
if (ebo_ != 0) {
|
||||
glDeleteBuffers(1, &ebo_);
|
||||
ebo_ = 0;
|
||||
}
|
||||
|
||||
if (program_id_ != 0) {
|
||||
glDeleteProgram(program_id_);
|
||||
program_id_ = 0;
|
||||
}
|
||||
|
||||
is_initialized_ = false;
|
||||
window_ = nullptr;
|
||||
renderer_ = nullptr;
|
||||
back_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
@@ -1,98 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../shader_backend.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <OpenGL/gl3.h>
|
||||
#else
|
||||
#include <SDL3/SDL_opengl.h>
|
||||
#endif
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/**
|
||||
* @brief Backend de shaders usando OpenGL 3.3 Core Profile
|
||||
*
|
||||
* Implementa el renderizado de shaders usando APIs modernas de OpenGL:
|
||||
* - VAO (Vertex Array Objects)
|
||||
* - VBO (Vertex Buffer Objects)
|
||||
* - Shaders GLSL #version 330 core
|
||||
*/
|
||||
class OpenGLShader : public ShaderBackend {
|
||||
public:
|
||||
OpenGLShader() = default;
|
||||
~OpenGLShader() override;
|
||||
|
||||
auto init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool override;
|
||||
|
||||
void render() override;
|
||||
void setTextureSize(float width, float height) override;
|
||||
void cleanup() final;
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||
|
||||
private:
|
||||
// Funciones auxiliares
|
||||
auto initGLExtensions() -> bool;
|
||||
static auto compileShader(const std::string& source, GLenum shader_type) -> GLuint;
|
||||
static auto linkProgram(GLuint vertex_shader, GLuint fragment_shader) -> GLuint;
|
||||
void createQuadGeometry();
|
||||
static auto getTextureID(SDL_Texture* texture) -> GLuint;
|
||||
static void checkGLError(const char* operation);
|
||||
|
||||
// Estado SDL
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_Renderer* renderer_ = nullptr;
|
||||
SDL_Texture* back_buffer_ = nullptr;
|
||||
|
||||
// Estado OpenGL
|
||||
GLuint program_id_ = 0;
|
||||
GLuint vao_ = 0; // Vertex Array Object
|
||||
GLuint vbo_ = 0; // Vertex Buffer Object
|
||||
GLuint ebo_ = 0; // Element Buffer Object
|
||||
|
||||
// Ubicaciones de uniforms
|
||||
GLint texture_size_location_ = -1;
|
||||
|
||||
// Tamaños
|
||||
int window_width_ = 0;
|
||||
int window_height_ = 0;
|
||||
float texture_width_ = 0.0F;
|
||||
float texture_height_ = 0.0F;
|
||||
|
||||
// Estado
|
||||
bool is_initialized_ = false;
|
||||
|
||||
#ifndef __APPLE__
|
||||
// Punteros a funciones OpenGL en Windows/Linux
|
||||
PFNGLCREATESHADERPROC glCreateShader = nullptr;
|
||||
PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
|
||||
PFNGLCOMPILESHADERPROC glCompileShader = nullptr;
|
||||
PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
|
||||
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
|
||||
PFNGLDELETESHADERPROC glDeleteShader = nullptr;
|
||||
PFNGLATTACHSHADERPROC glAttachShader = nullptr;
|
||||
PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr;
|
||||
PFNGLLINKPROGRAMPROC glLinkProgram = nullptr;
|
||||
PFNGLVALIDATEPROGRAMPROC glValidateProgram = nullptr;
|
||||
PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
|
||||
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr;
|
||||
PFNGLUSEPROGRAMPROC glUseProgram = nullptr;
|
||||
PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr;
|
||||
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
|
||||
PFNGLUNIFORM2FPROC glUniform2f = nullptr;
|
||||
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr;
|
||||
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr;
|
||||
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = nullptr;
|
||||
PFNGLGENBUFFERSPROC glGenBuffers = nullptr;
|
||||
PFNGLBINDBUFFERPROC glBindBuffer = nullptr;
|
||||
PFNGLBUFFERDATAPROC glBufferData = nullptr;
|
||||
PFNGLDELETEBUFFERSPROC glDeleteBuffers = nullptr;
|
||||
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
|
||||
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
10341
source/rendering/sdl3gpu/postfx_frag_spv.h
Normal file
10341
source/rendering/sdl3gpu/postfx_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
1449
source/rendering/sdl3gpu/postfx_vert_spv.h
Normal file
1449
source/rendering/sdl3gpu/postfx_vert_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
720
source/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
720
source/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
@@ -0,0 +1,720 @@
|
||||
#include "rendering/sdl3gpu/sdl3gpu_shader.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <algorithm> // std::min, std::max, std::floor
|
||||
#include <cmath> // std::floor
|
||||
#include <cstring> // memcpy, strlen
|
||||
|
||||
#ifndef __APPLE__
|
||||
#include "rendering/sdl3gpu/postfx_frag_spv.h"
|
||||
#include "rendering/sdl3gpu/postfx_vert_spv.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
// ============================================================================
|
||||
// MSL shaders (Metal Shading Language) — macOS
|
||||
// ============================================================================
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
static const char* POSTFX_VERT_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
||||
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
||||
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
||||
PostVOut out;
|
||||
out.pos = float4(positions[vid], 0.0, 1.0);
|
||||
out.uv = uvs[vid];
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* POSTFX_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float oversample; // 1.0 = sin SS, 3.0 = 3× supersampling
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz
|
||||
};
|
||||
|
||||
// YCbCr helpers for NTSC bleeding
|
||||
static float3 rgb_to_ycc(float3 rgb) {
|
||||
return float3(
|
||||
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
|
||||
-0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f,
|
||||
0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f
|
||||
);
|
||||
}
|
||||
static float3 ycc_to_rgb(float3 ycc) {
|
||||
float y = ycc.x;
|
||||
float cb = ycc.y - 0.5f;
|
||||
float cr = ycc.z - 0.5f;
|
||||
return clamp(float3(
|
||||
y + 1.402f*cr,
|
||||
y - 0.344f*cb - 0.714f*cr,
|
||||
y + 1.772f*cb
|
||||
), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant PostFXUniforms& u [[buffer(0)]]) {
|
||||
float2 uv = in.uv;
|
||||
|
||||
// Curvatura barrel CRT
|
||||
if (u.curvature > 0.0f) {
|
||||
float2 c = uv - 0.5f;
|
||||
float rsq = dot(c, c);
|
||||
float2 dist = float2(0.05f, 0.1f) * u.curvature;
|
||||
float2 barrelScale = 1.0f - 0.23f * dist;
|
||||
c += c * (dist * rsq);
|
||||
c *= barrelScale;
|
||||
if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) {
|
||||
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
uv = c + 0.5f;
|
||||
}
|
||||
|
||||
// Muestra base
|
||||
float3 base = scene.sample(samp, uv).rgb;
|
||||
|
||||
// Sangrado NTSC — difuminado horizontal de crominancia.
|
||||
// step = 1 pixel de juego en espacio UV (corrige SS: scene.get_width() = game_w * oversample).
|
||||
float3 colour;
|
||||
if (u.bleeding > 0.0f) {
|
||||
float tw = float(scene.get_width());
|
||||
float step = u.oversample / tw; // 1 pixel lógico en UV
|
||||
float3 ycc = rgb_to_ycc(base);
|
||||
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
|
||||
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
|
||||
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||
} else {
|
||||
colour = base;
|
||||
}
|
||||
|
||||
// Aberración cromática (drift animado con time para efecto NTSC real)
|
||||
float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f));
|
||||
colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r;
|
||||
colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b;
|
||||
|
||||
// Corrección gamma (linealizar antes de scanlines, codificar después)
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 lin = pow(colour, float3(2.4f));
|
||||
colour = mix(colour, lin, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Scanlines — 1 pixel físico oscuro por fila lógica.
|
||||
// Usa uv.y (independiente del offset de letterbox) con pixel_scale para
|
||||
// calcular la posición dentro de la fila en coordenadas físicas.
|
||||
// 3x: 1 dark + 2 bright. 4x: 1 dark + 3 bright.
|
||||
// bright=3.5×, dark floor=0.42 (mantiene aspecto CRT original).
|
||||
if (u.scanline_strength > 0.0f) {
|
||||
float ps = max(1.0f, round(u.pixel_scale));
|
||||
float frac_in_row = fract(uv.y * u.screen_height);
|
||||
float row_pos = floor(frac_in_row * ps);
|
||||
float is_dark = step(ps - 1.0f, row_pos);
|
||||
float scan = mix(3.5f, 0.42f, is_dark);
|
||||
colour *= mix(1.0f, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 enc = pow(colour, float3(1.0f/2.2f));
|
||||
colour = mix(colour, enc, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Viñeta
|
||||
float2 d = uv - 0.5f;
|
||||
float vignette = 1.0f - dot(d, d) * u.vignette_strength;
|
||||
colour *= clamp(vignette, 0.0f, 1.0f);
|
||||
|
||||
// Máscara de fósforo RGB — después de scanlines (orden original):
|
||||
// filas brillantes saturadas → máscara invisible, filas oscuras → RGB visible.
|
||||
if (u.mask_strength > 0.0f) {
|
||||
float whichMask = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(0.80f);
|
||||
if (whichMask < 0.3333333f) mask.x = 1.0f;
|
||||
else if (whichMask < 0.6666667f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour = mix(colour, colour * mask, u.mask_strength);
|
||||
}
|
||||
|
||||
// Parpadeo de fósforo CRT (~50 Hz)
|
||||
if (u.flicker > 0.0f) {
|
||||
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
|
||||
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
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<int>(fw);
|
||||
game_height_ = static_cast<int>(fh);
|
||||
tex_width_ = game_width_ * oversample_;
|
||||
tex_height_ = game_height_ * oversample_;
|
||||
uniforms_.screen_height = static_cast<float>(tex_height_); // Altura de la textura GPU
|
||||
uniforms_.oversample = static_cast<float>(oversample_);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 1. 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
|
||||
device_ = SDL_CreateGPUDevice(PREFERRED, false, nullptr);
|
||||
if (device_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: SDL_CreateGPUDevice failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
SDL_Log("SDL3GPUShader: driver = %s", SDL_GetGPUDeviceDriver(device_));
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 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, vsync_ ? SDL_GPU_PRESENTMODE_VSYNC : SDL_GPU_PRESENTMODE_IMMEDIATE);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 3. Create scene texture (upload target + sampler source)
|
||||
// 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<Uint32>(tex_width_);
|
||||
tex_info.height = static_cast<Uint32>(tex_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, size = w*h*4 bytes)
|
||||
// ----------------------------------------------------------------
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tb_info.size = static_cast<Uint32>(tex_width_ * tex_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 samplers: NEAREST (pixel art) + LINEAR (supersampling)
|
||||
// ----------------------------------------------------------------
|
||||
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;
|
||||
}
|
||||
|
||||
SDL_GPUSamplerCreateInfo lsamp_info = {};
|
||||
lsamp_info.min_filter = SDL_GPU_FILTER_LINEAR;
|
||||
lsamp_info.mag_filter = SDL_GPU_FILTER_LINEAR;
|
||||
lsamp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
lsamp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
lsamp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
lsamp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
linear_sampler_ = SDL_CreateGPUSampler(device_, &lsamp_info);
|
||||
if (linear_sampler_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: failed to create linear sampler: %s", SDL_GetError());
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 6. Create PostFX graphics pipeline
|
||||
// ----------------------------------------------------------------
|
||||
if (!createPipeline()) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
is_initialized_ = true;
|
||||
SDL_Log("SDL3GPUShader: initialized OK (%dx%d)", tex_width_, tex_height_);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createPipeline
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::createPipeline() -> 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_, POSTFX_FRAG_MSL, "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)) {
|
||||
SDL_Log("SDL3GPUShader: failed to compile PostFX 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;
|
||||
|
||||
pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &pipe_info);
|
||||
|
||||
SDL_ReleaseGPUShader(device_, vert);
|
||||
SDL_ReleaseGPUShader(device_, frag);
|
||||
|
||||
if (pipeline_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: pipeline creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// uploadPixels — copies ARGB8888 CPU pixels into the GPU transfer buffer.
|
||||
// Con supersampling (oversample_ > 1) expande cada pixel del juego a un bloque
|
||||
// oversample × oversample y hornea la scanline oscura en la última fila del bloque.
|
||||
// ---------------------------------------------------------------------------
|
||||
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;
|
||||
}
|
||||
|
||||
if (oversample_ <= 1) {
|
||||
// Path sin supersampling: copia directa
|
||||
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
|
||||
} else {
|
||||
// Path con supersampling: expande cada pixel a OS×OS, oscurece última fila.
|
||||
// Replica la fórmula del shader: mix(3.5, 0.42, scanline_strength).
|
||||
auto* out = static_cast<Uint32*>(mapped);
|
||||
const int OS = oversample_;
|
||||
const float BRIGHT_MUL = 1.0F + (baked_scanline_strength_ * 2.5F); // rows 0..OS-2
|
||||
const float DARK_MUL = 1.0F - (baked_scanline_strength_ * 0.58F); // row OS-1
|
||||
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const Uint32 SRC = pixels[(y * width) + x];
|
||||
const Uint32 ALPHA = (SRC >> 24) & 0xFFU;
|
||||
const auto FR = static_cast<float>((SRC >> 16) & 0xFFU);
|
||||
const auto FG = static_cast<float>((SRC >> 8) & 0xFFU);
|
||||
const auto FB = static_cast<float>(SRC & 0xFFU);
|
||||
|
||||
auto make_px = [ALPHA](float rv, float gv, float bv) -> Uint32 {
|
||||
auto cl = [](float v) -> Uint32 { return static_cast<Uint32>(std::min(255.0F, v)); };
|
||||
return (ALPHA << 24) | (cl(rv) << 16) | (cl(gv) << 8) | cl(bv);
|
||||
};
|
||||
|
||||
const Uint32 BRIGHT = make_px(FR * BRIGHT_MUL, FG * BRIGHT_MUL, FB * BRIGHT_MUL);
|
||||
const Uint32 DARK = make_px(FR * DARK_MUL, FG * DARK_MUL, FB * DARK_MUL);
|
||||
|
||||
for (int dy = 0; dy < OS; ++dy) {
|
||||
const Uint32 OUT_PX = (dy == OS - 1) ? DARK : BRIGHT;
|
||||
const int DST_Y = (y * OS) + dy;
|
||||
for (int dx = 0; dx < OS; ++dx) {
|
||||
out[(DST_Y * (width * OS)) + ((x * OS) + dx)] = OUT_PX;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// render — upload scene texture + PostFX pass → swapchain
|
||||
// ---------------------------------------------------------------------------
|
||||
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;
|
||||
}
|
||||
|
||||
// ---- 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<Uint32>(tex_width_);
|
||||
src.rows_per_layer = static_cast<Uint32>(tex_height_);
|
||||
|
||||
SDL_GPUTextureRegion dst = {};
|
||||
dst.texture = scene_texture_;
|
||||
dst.w = static_cast<Uint32>(tex_width_);
|
||||
dst.h = static_cast<Uint32>(tex_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)) {
|
||||
SDL_Log("SDL3GPUShader: SDL_AcquireGPUSwapchainTexture failed: %s", SDL_GetError());
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
return;
|
||||
}
|
||||
if (swapchain == nullptr) {
|
||||
// Window minimized — skip frame
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Render pass: PostFX → swapchain ----
|
||||
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_);
|
||||
|
||||
// Calcular viewport usando las dimensiones lógicas del canvas (game_width_/height_),
|
||||
// no las de la textura GPU (que pueden ser game×3 con supersampling).
|
||||
// El GPU escala la textura para cubrir el viewport independientemente de su resolución.
|
||||
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<int>(sw) / game_width_, static_cast<int>(sh) / game_height_));
|
||||
vw = static_cast<float>(game_width_ * SCALE);
|
||||
vh = static_cast<float>(game_height_ * SCALE);
|
||||
} else {
|
||||
const float SCALE = std::min(
|
||||
static_cast<float>(sw) / static_cast<float>(game_width_),
|
||||
static_cast<float>(sh) / static_cast<float>(game_height_));
|
||||
vw = static_cast<float>(game_width_) * SCALE;
|
||||
vh = static_cast<float>(game_height_) * SCALE;
|
||||
}
|
||||
vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
|
||||
vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);
|
||||
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(pass, &vp);
|
||||
|
||||
// pixel_scale: pixels físicos por pixel lógico de juego (para scanlines sin SS).
|
||||
// Con SS las scanlines están horneadas en CPU → scanline_strength=0 → no se usa.
|
||||
uniforms_.pixel_scale = (game_height_ > 0)
|
||||
? (vh / static_cast<float>(game_height_))
|
||||
: 1.0F;
|
||||
uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||
uniforms_.oversample = static_cast<float>(oversample_);
|
||||
|
||||
// Con supersampling usamos LINEAR para que el escalado a zooms no-múltiplo-de-3
|
||||
// promedia correctamente las filas de scanline horneadas en CPU.
|
||||
SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr)
|
||||
? linear_sampler_
|
||||
: sampler_;
|
||||
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = scene_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);
|
||||
}
|
||||
|
||||
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 (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;
|
||||
}
|
||||
if (linear_sampler_ != nullptr) {
|
||||
SDL_ReleaseGPUSampler(device_, linear_sampler_);
|
||||
linear_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<const Uint8*>(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, // 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) {
|
||||
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_strength = p.chroma;
|
||||
uniforms_.mask_strength = p.mask;
|
||||
uniforms_.gamma_strength = p.gamma;
|
||||
uniforms_.curvature = p.curvature;
|
||||
uniforms_.bleeding = p.bleeding;
|
||||
uniforms_.flicker = p.flicker;
|
||||
|
||||
// Con supersampling las scanlines se hornean en CPU (uploadPixels).
|
||||
// El shader recibe strength=0 para no aplicarlas de nuevo en GPU.
|
||||
baked_scanline_strength_ = p.scanlines;
|
||||
uniforms_.scanline_strength = (oversample_ > 1) ? 0.0F : p.scanlines;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setVSync(bool vsync) {
|
||||
vsync_ = vsync;
|
||||
if (device_ != nullptr && window_ != nullptr) {
|
||||
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, vsync_ ? SDL_GPU_PRESENTMODE_VSYNC : SDL_GPU_PRESENTMODE_IMMEDIATE);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setScaleMode(bool integer_scale) {
|
||||
integer_scale_ = integer_scale;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// setOversample — cambia el factor SS; recrea texturas si ya está inicializado
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::setOversample(int factor) {
|
||||
const int NEW_FACTOR = std::max(1, factor);
|
||||
if (NEW_FACTOR == oversample_) { return; }
|
||||
oversample_ = NEW_FACTOR;
|
||||
if (is_initialized_) {
|
||||
reinitTexturesAndBuffer();
|
||||
// scanline_strength se actualizará en el próximo setPostFXParams
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// reinitTexturesAndBuffer — recrea scene_texture_ y upload_buffer_ con el
|
||||
// tamaño actual (game × oversample_). No toca pipeline 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;
|
||||
}
|
||||
|
||||
tex_width_ = game_width_ * oversample_;
|
||||
tex_height_ = game_height_ * oversample_;
|
||||
uniforms_.screen_height = static_cast<float>(tex_height_);
|
||||
uniforms_.oversample = static_cast<float>(oversample_);
|
||||
|
||||
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<Uint32>(tex_width_);
|
||||
tex_info.height = static_cast<Uint32>(tex_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<Uint32>(tex_width_ * tex_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;
|
||||
}
|
||||
|
||||
SDL_Log("SDL3GPUShader: oversample %d → texture %dx%d", oversample_, tex_width_, tex_height_);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
106
source/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
106
source/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include "rendering/shader_backend.hpp"
|
||||
|
||||
// PostFX uniforms pushed to fragment stage each frame.
|
||||
// Must match the MSL struct and GLSL uniform block layout.
|
||||
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength; // 0 = none, ~0.8 = subtle
|
||||
float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration
|
||||
float scanline_strength; // 0 = off, 1 = full
|
||||
float screen_height; // logical height in pixels (used by bleeding effect)
|
||||
float mask_strength; // 0 = off, 1 = full phosphor dot mask
|
||||
float gamma_strength; // 0 = off, 1 = full gamma 2.4/2.2 correction
|
||||
float curvature; // 0 = flat, 1 = max barrel distortion
|
||||
float bleeding; // 0 = off, 1 = max NTSC chrominance bleeding
|
||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
|
||||
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16)
|
||||
};
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/**
|
||||
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
|
||||
*
|
||||
* Reemplaza el backend OpenGL para que los shaders PostFX funcionen en macOS.
|
||||
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||
* → PostFX render pass → swapchain → present
|
||||
*/
|
||||
class SDL3GPUShader : public ShaderBackend {
|
||||
public:
|
||||
SDL3GPUShader() = default;
|
||||
~SDL3GPUShader() override;
|
||||
|
||||
auto init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool override;
|
||||
|
||||
void render() override;
|
||||
void setTextureSize(float width, float height) override {}
|
||||
void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo
|
||||
void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||
|
||||
// Sube píxeles ARGB8888 desde CPU; llamado antes de render()
|
||||
void uploadPixels(const Uint32* pixels, int width, int height) override;
|
||||
|
||||
// Actualiza los parámetros de intensidad de los efectos PostFX
|
||||
void setPostFXParams(const PostFXParams& p) override;
|
||||
|
||||
// Activa/desactiva VSync en el swapchain
|
||||
void setVSync(bool vsync) override;
|
||||
|
||||
// Activa/desactiva escalado entero (integer scale)
|
||||
void setScaleMode(bool integer_scale) override;
|
||||
|
||||
// Establece factor de supersampling (1 = off, 3 = 3×SS)
|
||||
void setOversample(int factor) override;
|
||||
|
||||
private:
|
||||
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
static auto 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*;
|
||||
|
||||
auto createPipeline() -> bool;
|
||||
auto reinitTexturesAndBuffer() -> bool; // Recrea textura y buffer con oversample actual
|
||||
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||
SDL_GPUTexture* scene_texture_ = nullptr;
|
||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr; // NEAREST — para path sin supersampling
|
||||
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR — para path con supersampling
|
||||
|
||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
|
||||
|
||||
int game_width_ = 0; // Dimensiones originales del canvas (sin SS)
|
||||
int game_height_ = 0;
|
||||
int tex_width_ = 0; // Dimensiones de la textura GPU (game × oversample_)
|
||||
int tex_height_ = 0;
|
||||
int oversample_ = 1; // Factor SS actual (1 o 3)
|
||||
float baked_scanline_strength_ = 0.0F; // Guardado para hornear en CPU
|
||||
bool is_initialized_ = false;
|
||||
bool vsync_ = true;
|
||||
bool integer_scale_ = false;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
@@ -6,6 +6,20 @@
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/**
|
||||
* @brief Parámetros de intensidad de los efectos PostFX
|
||||
*/
|
||||
struct PostFXParams {
|
||||
float vignette = 0.0F;
|
||||
float scanlines = 0.0F;
|
||||
float chroma = 0.0F;
|
||||
float mask = 0.0F;
|
||||
float gamma = 0.0F;
|
||||
float curvature = 0.0F;
|
||||
float bleeding = 0.0F;
|
||||
float flicker = 0.0F;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Interfaz abstracta para backends de renderizado con shaders
|
||||
*
|
||||
@@ -51,6 +65,12 @@ namespace Rendering {
|
||||
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
||||
*/
|
||||
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||
|
||||
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
||||
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||
virtual void setVSync(bool /*vsync*/) {}
|
||||
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||
virtual void setOversample(int /*factor*/) {}
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "screen.hpp"
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WINDOW_OPENGL, SDL_WindowFlags
|
||||
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WindowFlags
|
||||
|
||||
#include <algorithm> // Para min, max
|
||||
#include <cstring> // Para memcpy
|
||||
#include <memory> // Para allocator, shared_ptr, unique_ptr, __shared_ptr_access, make_shared, make_unique
|
||||
#include <string> // Para basic_string, operator+, char_traits, to_string, string
|
||||
#include <vector> // Para vector
|
||||
@@ -11,8 +12,7 @@
|
||||
#include "mouse.hpp" // Para updateCursorVisibility
|
||||
#include "options.hpp" // Para Video, video, Window, window
|
||||
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug
|
||||
#include "rendering/opengl/opengl_shader.hpp" // Para OpenGLShader
|
||||
#include "shader_backend.hpp" // Para ShaderBackend
|
||||
#include "rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
|
||||
#include "text.hpp" // Para Text
|
||||
#include "texture.hpp" // Para Texture
|
||||
#include "ui/logger.hpp" // Para info
|
||||
@@ -23,7 +23,10 @@
|
||||
Screen* Screen::instance = nullptr;
|
||||
|
||||
// Inicializa la instancia única del singleton
|
||||
void Screen::init() { Screen::instance = new Screen(); }
|
||||
void Screen::init() {
|
||||
Screen::instance = new Screen();
|
||||
Screen::initShaders(); // Llamar aquí para que Screen::get() ya devuelva la instancia
|
||||
}
|
||||
|
||||
// Libera la instancia
|
||||
void Screen::destroy() { delete Screen::instance; }
|
||||
@@ -44,9 +47,12 @@ Screen::Screen()
|
||||
initSDLVideo();
|
||||
|
||||
// Crea la textura de destino
|
||||
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
||||
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
|
||||
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
// Inicializar buffer de píxeles para SDL3GPU
|
||||
pixel_buffer_.resize(static_cast<size_t>(param.game.width) * static_cast<size_t>(param.game.height));
|
||||
|
||||
// Crea el objeto de texto
|
||||
createText();
|
||||
|
||||
@@ -63,8 +69,6 @@ Screen::Screen()
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_RenderPresent(renderer_);
|
||||
|
||||
// Ahora sí inicializar los shaders
|
||||
initShaders();
|
||||
}
|
||||
|
||||
// Destructor
|
||||
@@ -101,15 +105,37 @@ void Screen::coreRender() {
|
||||
|
||||
// Renderiza el contenido del game_canvas_
|
||||
void Screen::renderPresent() {
|
||||
#ifndef NO_SHADERS
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
// Leer píxeles de game_canvas_ con la API SDL3 (devuelve SDL_Surface*)
|
||||
SDL_SetRenderTarget(renderer_, game_canvas_);
|
||||
SDL_Surface* surface = SDL_RenderReadPixels(renderer_, nullptr);
|
||||
if (surface != nullptr) {
|
||||
if (surface->format == SDL_PIXELFORMAT_ARGB8888) {
|
||||
std::memcpy(pixel_buffer_.data(), surface->pixels,
|
||||
pixel_buffer_.size() * sizeof(Uint32));
|
||||
} else {
|
||||
SDL_Surface* converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
|
||||
if (converted != nullptr) {
|
||||
std::memcpy(pixel_buffer_.data(), converted->pixels,
|
||||
pixel_buffer_.size() * sizeof(Uint32));
|
||||
SDL_DestroySurface(converted);
|
||||
}
|
||||
}
|
||||
SDL_DestroySurface(surface);
|
||||
}
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
// Subir a GPU y presentar con PostFX
|
||||
shader_backend_->uploadPixels(pixel_buffer_.data(), param.game.width, param.game.height);
|
||||
shader_backend_->render();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// Fallback: SDL_Renderer
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
clean();
|
||||
|
||||
if (Options::video.shaders && shader_backend_) {
|
||||
shader_backend_->render();
|
||||
} else {
|
||||
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
// Establece el modo de video
|
||||
@@ -230,61 +256,32 @@ void Screen::renderInfo() const {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Carga el contenido de los archivos GLSL
|
||||
void Screen::loadShaders() {
|
||||
if (vertex_shader_source_.empty()) {
|
||||
// Detectar si necesitamos OpenGL ES (Raspberry Pi)
|
||||
// Intentar cargar versión ES primero si existe
|
||||
std::string vertex_file = "crtpi_vertex_es.glsl";
|
||||
auto data = Asset::get()->loadData(vertex_file);
|
||||
|
||||
if (data.empty()) {
|
||||
// Si no existe versión ES, usar versión Desktop
|
||||
vertex_file = "crtpi_vertex.glsl";
|
||||
data = Asset::get()->loadData(vertex_file);
|
||||
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Usando shaders OpenGL Desktop 3.3");
|
||||
Logger::info("Usando shaders OpenGL Desktop 3.3");
|
||||
} else {
|
||||
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Usando shaders OpenGL ES 3.0 (Raspberry Pi)");
|
||||
Logger::info("Usando shaders OpenGL ES 3.0 (Raspberry Pi)");
|
||||
}
|
||||
|
||||
if (!data.empty()) {
|
||||
vertex_shader_source_ = std::string(data.begin(), data.end());
|
||||
}
|
||||
// Inicializa los shaders (SDL3GPU)
|
||||
void Screen::initShaders() {
|
||||
#ifndef NO_SHADERS
|
||||
auto* self = Screen::get();
|
||||
if (self == nullptr) {
|
||||
SDL_Log("Screen::initShaders: instance is null, skipping");
|
||||
return;
|
||||
}
|
||||
if (fragment_shader_source_.empty()) {
|
||||
// Intentar cargar versión ES primero si existe
|
||||
std::string fragment_file = "crtpi_fragment_es.glsl";
|
||||
auto data = Asset::get()->loadData(fragment_file);
|
||||
|
||||
if (data.empty()) {
|
||||
// Si no existe versión ES, usar versión Desktop
|
||||
fragment_file = "crtpi_fragment.glsl";
|
||||
data = Asset::get()->loadData(fragment_file);
|
||||
}
|
||||
|
||||
if (!data.empty()) {
|
||||
fragment_shader_source_ = std::string(data.begin(), data.end());
|
||||
}
|
||||
if (!self->shader_backend_) {
|
||||
self->shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||
}
|
||||
if (!self->shader_backend_->isHardwareAccelerated()) {
|
||||
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
|
||||
SDL_Log("Screen::initShaders: SDL3GPUShader::init() = %s", ok ? "OK" : "FAILED");
|
||||
}
|
||||
SDL_Log("Screen::initShaders: presets=%d current=%d postfx=%s",
|
||||
static_cast<int>(Options::postfx_presets.size()),
|
||||
Options::current_postfx_preset,
|
||||
Options::video.postfx ? "ON" : "OFF");
|
||||
self->applyCurrentPostFXPreset();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Inicializa los shaders
|
||||
void Screen::initShaders() {
|
||||
#ifndef __APPLE__
|
||||
if (Options::video.shaders) {
|
||||
loadShaders();
|
||||
if (!shader_backend_) {
|
||||
shader_backend_ = std::make_unique<Rendering::OpenGLShader>();
|
||||
}
|
||||
shader_backend_->init(window_, game_canvas_, vertex_shader_source_, fragment_shader_source_);
|
||||
}
|
||||
#else
|
||||
// En macOS, OpenGL está deprecated y rinde mal
|
||||
// TODO: Implementar backend de Metal para shaders en macOS
|
||||
Logger::info("WARNING: Shaders no disponibles en macOS (OpenGL deprecated). Usa Metal backend.", Logger::YELLOW);
|
||||
#endif
|
||||
// Inicializa PostFX (alias de initShaders)
|
||||
void Screen::initPostFX() {
|
||||
initShaders();
|
||||
}
|
||||
|
||||
// Calcula el tamaño de la ventana
|
||||
@@ -350,32 +347,13 @@ auto Screen::initSDLVideo() -> bool {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Failed to set Metal hint!");
|
||||
}
|
||||
#else
|
||||
// Configurar hint de render driver
|
||||
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Failed to set OpenGL hint!");
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows: Pedir explícitamente OpenGL 3.3 Core Profile
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Solicitando OpenGL 3.3 Core Profile");
|
||||
Logger::info("Solicitando OpenGL 3.3 Core Profile");
|
||||
#else
|
||||
// Linux: Dejar que SDL elija (Desktop 3.3 en PC, ES 3.0 en RPi automáticamente)
|
||||
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Usando OpenGL por defecto del sistema");
|
||||
Logger::info("Usando OpenGL por defecto del sistema");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Crear ventana
|
||||
#ifdef __APPLE__
|
||||
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
|
||||
#else
|
||||
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL;
|
||||
SDL_WindowFlags window_flags = 0;
|
||||
#endif
|
||||
if (Options::video.fullscreen) {
|
||||
window_flags |= SDL_WINDOW_FULLSCREEN;
|
||||
@@ -465,22 +443,79 @@ void Screen::getDisplayInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar los shaders
|
||||
// Alterna entre activar y desactivar los shaders (backward compat)
|
||||
void Screen::toggleShaders() {
|
||||
Options::video.shaders = !Options::video.shaders;
|
||||
initShaders();
|
||||
Screen::togglePostFX();
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar los efectos PostFX
|
||||
void Screen::togglePostFX() {
|
||||
Options::video.postfx = !Options::video.postfx;
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr) {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Avanza al siguiente preset PostFX
|
||||
void Screen::nextPostFXPreset() {
|
||||
if (Options::postfx_presets.empty()) { return; }
|
||||
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr) {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar el supersampling 3x
|
||||
void Screen::toggleSupersampling() {
|
||||
Options::video.supersampling = !Options::video.supersampling;
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr && self->shader_backend_) {
|
||||
self->shader_backend_->setOversample(Options::video.supersampling ? 3 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica el preset PostFX activo al backend
|
||||
void Screen::applyCurrentPostFXPreset() {
|
||||
if (!shader_backend_) { return; }
|
||||
Rendering::PostFXParams p{};
|
||||
if (Options::video.postfx && !Options::postfx_presets.empty()) {
|
||||
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::current_postfx_preset));
|
||||
p.vignette = preset.vignette;
|
||||
p.scanlines = preset.scanlines;
|
||||
p.chroma = preset.chroma;
|
||||
p.mask = preset.mask;
|
||||
p.gamma = preset.gamma;
|
||||
p.curvature = preset.curvature;
|
||||
p.bleeding = preset.bleeding;
|
||||
p.flicker = preset.flicker;
|
||||
SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f",
|
||||
preset.name.c_str(), p.scanlines, p.vignette, p.chroma);
|
||||
} else {
|
||||
SDL_Log("Screen::applyCurrentPostFXPreset: PostFX=%s presets=%d → passthrough",
|
||||
Options::video.postfx ? "ON" : "OFF",
|
||||
static_cast<int>(Options::postfx_presets.size()));
|
||||
}
|
||||
shader_backend_->setPostFXParams(p);
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar el escalado entero
|
||||
void Screen::toggleIntegerScale() {
|
||||
Options::video.integer_scale = !Options::video.integer_scale;
|
||||
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setScaleMode(Options::video.integer_scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar el V-Sync
|
||||
void Screen::toggleVSync() {
|
||||
Options::video.vsync = !Options::video.vsync;
|
||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setVSync(Options::video.vsync);
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el estado del V-Sync
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "color.hpp" // Para Color
|
||||
#include "options.hpp" // Para VideoOptions, video
|
||||
@@ -40,12 +41,16 @@ class Screen {
|
||||
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
|
||||
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
|
||||
void applySettings(); // Aplica los valores de las opciones
|
||||
static void initShaders(); // Inicializa los shaders
|
||||
static void initShaders(); // Inicializa los shaders (SDL3GPU)
|
||||
static void initPostFX(); // Inicializa PostFX (alias de initShaders)
|
||||
|
||||
// --- Efectos visuales ---
|
||||
void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); } // Agita la pantalla (tiempo en segundos)
|
||||
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); } // Pone la pantalla de color (tiempo en segundos)
|
||||
static void toggleShaders(); // Alterna entre activar y desactivar los shaders
|
||||
static void toggleShaders(); // Alterna entre activar y desactivar los shaders (backward compat)
|
||||
static void togglePostFX(); // Alterna entre activar y desactivar los efectos PostFX
|
||||
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
||||
static void toggleSupersampling(); // Alterna entre activar y desactivar el supersampling 3x
|
||||
void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero
|
||||
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
||||
void setVSync(bool enabled); // Establece el estado del V-Sync
|
||||
@@ -214,14 +219,13 @@ class Screen {
|
||||
ServiceMenu* service_menu_; // Objeto para mostrar el menú de servicio
|
||||
Notifier* notifier_; // Objeto para mostrar las notificaciones por pantalla
|
||||
std::shared_ptr<Text> text_; // Objeto para escribir texto en pantalla
|
||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (OpenGL/Metal/Vulkan)
|
||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (SDL3GPU)
|
||||
|
||||
// --- Variables de estado ---
|
||||
SDL_FRect src_rect_; // Coordenadas de origen para dibujar la textura del juego
|
||||
SDL_FRect dst_rect_; // Coordenadas destino para dibujar la textura del juego
|
||||
std::string vertex_shader_source_; // Almacena el vertex shader
|
||||
std::string fragment_shader_source_; // Almacena el fragment shader
|
||||
FPS fps_; // Gestión de frames por segundo
|
||||
SDL_FRect src_rect_; // Coordenadas de origen para dibujar la textura del juego
|
||||
SDL_FRect dst_rect_; // Coordenadas destino para dibujar la textura del juego
|
||||
std::vector<Uint32> pixel_buffer_; // Buffer de píxeles para SDL_RenderReadPixels
|
||||
FPS fps_; // Gestión de frames por segundo
|
||||
FlashEffect flash_effect_; // Efecto de flash en pantalla
|
||||
ShakeEffect shake_effect_; // Efecto de agitar la pantalla
|
||||
bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada
|
||||
@@ -231,17 +235,17 @@ class Screen {
|
||||
#endif
|
||||
|
||||
// --- Métodos internos ---
|
||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
||||
void renderShake(); // Aplica el efecto de agitar la pantalla
|
||||
void renderInfo() const; // Muestra información por pantalla
|
||||
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
||||
void loadShaders(); // Carga el contenido del archivo GLSL
|
||||
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||
void renderOverlays(); // Renderiza todos los overlays y efectos
|
||||
void renderAttenuate(); // Atenúa la pantalla
|
||||
void createText(); // Crea el objeto de texto
|
||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
||||
void renderShake(); // Aplica el efecto de agitar la pantalla
|
||||
void renderInfo() const; // Muestra información por pantalla
|
||||
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
||||
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
||||
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||
void renderOverlays(); // Renderiza todos los overlays y efectos
|
||||
void renderAttenuate(); // Atenúa la pantalla
|
||||
void createText(); // Crea el objeto de texto
|
||||
|
||||
// --- Constructores y destructor privados (singleton) ---
|
||||
Screen(); // Constructor privado
|
||||
|
||||
44
tools/shaders/compile_spirv.sh
Executable file
44
tools/shaders/compile_spirv.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# Compile Vulkan GLSL shaders to SPIR-V and update the C++ headers used by SDL3GPUShader.
|
||||
# Required: glslc (from Vulkan SDK or: brew install glslang / apt install glslang-tools)
|
||||
#
|
||||
# Run from the project root: tools/shaders/compile_spirv.sh
|
||||
|
||||
set -e
|
||||
|
||||
SHADERS_DIR="data/shaders"
|
||||
HEADERS_DIR="source/rendering/sdl3gpu"
|
||||
|
||||
if ! command -v glslc &> /dev/null; then
|
||||
echo "ERROR: glslc not found. Install Vulkan SDK or run:"
|
||||
echo " macOS: brew install glslang"
|
||||
echo " Linux: sudo apt install glslang-tools"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Compiling SPIR-V shaders..."
|
||||
|
||||
glslc "${SHADERS_DIR}/postfx.vert" -o /tmp/postfx.vert.spv
|
||||
glslc "${SHADERS_DIR}/postfx.frag" -o /tmp/postfx.frag.spv
|
||||
|
||||
echo "Generating C++ headers..."
|
||||
|
||||
xxd -i /tmp/postfx.vert.spv | \
|
||||
sed 's/unsigned char .*postfx_vert_spv\[\]/static const uint8_t kpostfx_vert_spv[]/' | \
|
||||
sed 's/unsigned int .*postfx_vert_spv_len/static const size_t kpostfx_vert_spv_size/' \
|
||||
> "${HEADERS_DIR}/postfx_vert_spv.h"
|
||||
|
||||
xxd -i /tmp/postfx.frag.spv | \
|
||||
sed 's/unsigned char .*postfx_frag_spv\[\]/static const uint8_t kpostfx_frag_spv[]/' | \
|
||||
sed 's/unsigned int .*postfx_frag_spv_len/static const size_t kpostfx_frag_spv_size/' \
|
||||
> "${HEADERS_DIR}/postfx_frag_spv.h"
|
||||
|
||||
# Prepend required includes to the headers
|
||||
for f in "${HEADERS_DIR}/postfx_vert_spv.h" "${HEADERS_DIR}/postfx_frag_spv.h"; do
|
||||
echo -e "#pragma once\n#include <cstdint>\n#include <cstddef>\n$(cat "$f")" > "$f"
|
||||
done
|
||||
|
||||
echo "Done. Headers updated in ${HEADERS_DIR}/"
|
||||
echo " postfx_vert_spv.h"
|
||||
echo " postfx_frag_spv.h"
|
||||
echo "Rebuild the project to use the new shaders."
|
||||
Reference in New Issue
Block a user