sdl3gpu
This commit is contained in:
24
CLAUDE.md
24
CLAUDE.md
@@ -15,6 +15,7 @@ make # Release build
|
|||||||
make debug # Debug build (defines DEBUG and PAUSE)
|
make debug # Debug build (defines DEBUG and PAUSE)
|
||||||
make release # Empaqueta .tar.gz / .dmg / .zip segons SO
|
make release # Empaqueta .tar.gz / .dmg / .zip segons SO
|
||||||
make pack # Regenera resources.pack
|
make pack # Regenera resources.pack
|
||||||
|
make compile_shaders # Compila shaders GLSL → headers SPIR-V (requereix glslc)
|
||||||
make controllerdb # Descarga gamecontrollerdb.txt
|
make controllerdb # Descarga gamecontrollerdb.txt
|
||||||
make format # clang-format -i
|
make format # clang-format -i
|
||||||
make tidy # clang-tidy
|
make tidy # clang-tidy
|
||||||
@@ -33,6 +34,10 @@ source/
|
|||||||
│ ├── input/ input.*, mouse.*
|
│ ├── input/ input.*, mouse.*
|
||||||
│ ├── locale/ lang.*
|
│ ├── locale/ lang.*
|
||||||
│ ├── rendering/ screen, fade, text, writer, texture, sprite + animated/moving/smart
|
│ ├── rendering/ screen, fade, text, writer, texture, sprite + animated/moving/smart
|
||||||
|
│ │ ├── shader_backend.hpp (interfície abstracta de post-procesado)
|
||||||
|
│ │ └── sdl3gpu/ (pipeline SDL3 GPU)
|
||||||
|
│ │ ├── sdl3gpu_shader.* (implementació del backend GPU)
|
||||||
|
│ │ └── spv/ (headers SPIR-V generats — protegits amb dummies `.clang-*`)
|
||||||
│ ├── resources/ asset, resource, resource_pack, resource_loader, resource_helper
|
│ ├── resources/ asset, resource, resource_pack, resource_loader, resource_helper
|
||||||
│ └── system/ director
|
│ └── system/ director
|
||||||
├── game/
|
├── game/
|
||||||
@@ -66,6 +71,24 @@ Flux general controlat per la classe **Director** (`core/system/director.h`): in
|
|||||||
|
|
||||||
**jail_audio** (`core/audio/jail_audio.hpp`): wrapper audio SDL3 first-party. Usa stb_vorbis per OGG. API `JA_*` per música i efectes amb mesclat per canals.
|
**jail_audio** (`core/audio/jail_audio.hpp`): wrapper audio SDL3 first-party. Usa stb_vorbis per OGG. API `JA_*` per música i efectes amb mesclat per canals.
|
||||||
|
|
||||||
|
### GPU / shaders (post-procesado)
|
||||||
|
|
||||||
|
Pipeline SDL3 GPU portat de `coffee_crisis_arcade_edition`. El canvas 256×192 es pot passar per un backend GPU que aplica PostFX (vinyeta, scanlines, chroma, gamma, mask, curvatura, bleeding, flicker) o CrtPi (scanlines continues amb bloom). Fallback transparent al `SDL_Renderer` clàssic si la GPU falla o si es desactiva.
|
||||||
|
|
||||||
|
- **Interfície**: `core/rendering/shader_backend.hpp` (`Rendering::ShaderBackend`).
|
||||||
|
- **Implementació**: `core/rendering/sdl3gpu/sdl3gpu_shader.*` + shaders GLSL a `data/shaders/` compilats a `spv/*_spv.h` via `glslc` (o precompilats si no hi ha `glslc`).
|
||||||
|
- **Emscripten**: compile-time `NO_SHADERS` → sempre ruta clàssica.
|
||||||
|
- **macOS**: shaders Metal (MSL) inline dins `sdl3gpu_shader.cpp`; no SPIR-V.
|
||||||
|
- **Opcions persistents** a `config.txt` (migració a YAML pendent):
|
||||||
|
- `videoGpuAcceleration` (bool)
|
||||||
|
- `videoGpuPreferredDriver` (string, buit = auto)
|
||||||
|
- `videoShaderEnabled` (bool)
|
||||||
|
- `videoShaderType` (0=POSTFX, 1=CRTPI)
|
||||||
|
- **Hotkeys** (provisionals fins que hi hagi menú d'opcions): `F9` toggle GPU · `F10` toggle shader · `F11` alterna POSTFX ↔ CRTPI.
|
||||||
|
- **API** a `Screen`: `setGpuAcceleration`/`toggleGpuAcceleration`/`isGpuAccelerated`, `setShaderEnabled`/`toggleShaderEnabled`/`isShaderEnabled`, `setActiveShader`/`toggleActiveShader`/`getActiveShader`.
|
||||||
|
|
||||||
|
Presets PostFX/CrtPi i cicle de presets encara **no** estan implementats — arribaran amb la migració a YAML. Per ara, valors per defecte hardcoded.
|
||||||
|
|
||||||
## Data Directory
|
## Data Directory
|
||||||
|
|
||||||
- `data/gfx/` — PNG spritesheets and `.ani` animation definition files
|
- `data/gfx/` — PNG spritesheets and `.ani` animation definition files
|
||||||
@@ -74,6 +97,7 @@ Flux general controlat per la classe **Director** (`core/system/director.h`): in
|
|||||||
- `data/lang/` — language files (es_ES, ba_BA, en_UK)
|
- `data/lang/` — language files (es_ES, ba_BA, en_UK)
|
||||||
- `data/demo/` — demo recording data (gamecontrollerdb.txt vive en la raíz del proyecto, fuera del pack)
|
- `data/demo/` — demo recording data (gamecontrollerdb.txt vive en la raíz del proyecto, fuera del pack)
|
||||||
- `data/menu/` — menu definition files
|
- `data/menu/` — menu definition files
|
||||||
|
- `data/shaders/` — fonts GLSL per al post-procesado SDL3 GPU (no van al pack; s'empotren al binari via headers SPIR-V)
|
||||||
|
|
||||||
## Language
|
## Language
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ set(DIR_SOURCES "${CMAKE_SOURCE_DIR}/source")
|
|||||||
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "${DIR_SOURCES}/*.cpp")
|
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "${DIR_SOURCES}/*.cpp")
|
||||||
list(FILTER SOURCES EXCLUDE REGEX "${DIR_SOURCES}/external/.*")
|
list(FILTER SOURCES EXCLUDE REGEX "${DIR_SOURCES}/external/.*")
|
||||||
|
|
||||||
|
# En Emscripten no compilamos sdl3gpu_shader (SDL3 GPU no está soportado en WebGL2).
|
||||||
|
# Define NO_SHADERS más abajo y filtra el fuente aquí.
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
list(REMOVE_ITEM SOURCES "${DIR_SOURCES}/core/rendering/sdl3gpu/sdl3gpu_shader.cpp")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Verificar si se encontraron archivos fuente
|
# Verificar si se encontraron archivos fuente
|
||||||
if(NOT SOURCES)
|
if(NOT SOURCES)
|
||||||
message(FATAL_ERROR "No se encontraron archivos fuente en ${DIR_SOURCES}.")
|
message(FATAL_ERROR "No se encontraron archivos fuente en ${DIR_SOURCES}.")
|
||||||
@@ -55,9 +61,78 @@ else()
|
|||||||
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# --- SHADER COMPILATION (Linux/Windows only - macOS uses Metal, Emscripten no soporta SDL3 GPU) ---
|
||||||
|
if(NOT APPLE AND NOT EMSCRIPTEN)
|
||||||
|
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_CRTPI_SRC "${CMAKE_SOURCE_DIR}/data/shaders/crtpi_frag.glsl")
|
||||||
|
set(SHADER_UPSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/upscale.frag")
|
||||||
|
set(SHADER_DOWNSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/downscale.frag")
|
||||||
|
|
||||||
|
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/postfx_vert_spv.h")
|
||||||
|
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h")
|
||||||
|
set(SHADER_CRTPI_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h")
|
||||||
|
set(SHADER_UPSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h")
|
||||||
|
set(SHADER_DOWNSCALE_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h")
|
||||||
|
|
||||||
|
set(ALL_SHADER_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
|
||||||
|
|
||||||
|
if(GLSLC_EXE)
|
||||||
|
set(COMPILE_SHADER_SCRIPT "${CMAKE_SOURCE_DIR}/tools/shaders/compile_shader.cmake")
|
||||||
|
|
||||||
|
macro(add_shader SRC_FILE OUT_H VAR_NAME)
|
||||||
|
cmake_parse_arguments(S "" "STAGE" "" ${ARGN})
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${OUT_H}"
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
"-DGLSLC=${GLSLC_EXE}"
|
||||||
|
"-DSRC=${SRC_FILE}"
|
||||||
|
"-DOUT_H=${OUT_H}"
|
||||||
|
"-DVAR=${VAR_NAME}"
|
||||||
|
"-DSTAGE=${S_STAGE}"
|
||||||
|
-P "${COMPILE_SHADER_SCRIPT}"
|
||||||
|
DEPENDS "${SRC_FILE}" "${COMPILE_SHADER_SCRIPT}"
|
||||||
|
COMMENT "Compilando shader: ${VAR_NAME}"
|
||||||
|
)
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
add_shader("${SHADER_VERT_SRC}" "${SHADER_VERT_H}" "postfx_vert_spv")
|
||||||
|
add_shader("${SHADER_FRAG_SRC}" "${SHADER_FRAG_H}" "postfx_frag_spv")
|
||||||
|
add_shader("${SHADER_CRTPI_SRC}" "${SHADER_CRTPI_H}" "crtpi_frag_spv" STAGE fragment)
|
||||||
|
add_shader("${SHADER_UPSCALE_SRC}" "${SHADER_UPSCALE_H}" "upscale_frag_spv")
|
||||||
|
add_shader("${SHADER_DOWNSCALE_SRC}" "${SHADER_DOWNSCALE_H}" "downscale_frag_spv")
|
||||||
|
|
||||||
|
add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS})
|
||||||
|
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
|
||||||
|
else()
|
||||||
|
foreach(_h IN LISTS ALL_SHADER_HEADERS)
|
||||||
|
if(NOT EXISTS "${_h}")
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"glslc no encontrado y header SPIR-V no existe: ${_h}\n"
|
||||||
|
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
|
||||||
|
" choco install vulkan-sdk (Windows)"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
message(STATUS "Emscripten: shaders SPIR-V omitidos (SDL3 GPU no soportado en WebGL2)")
|
||||||
|
else()
|
||||||
|
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal inline)")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Añadir ejecutable principal
|
# Añadir ejecutable principal
|
||||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||||
|
|
||||||
|
if(NOT APPLE AND NOT EMSCRIPTEN AND GLSLC_EXE)
|
||||||
|
add_dependencies(${PROJECT_NAME} shaders)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Includes relatius a source/ (p.e. `#include "core/rendering/texture.h"`)
|
# Includes relatius a source/ (p.e. `#include "core/rendering/texture.h"`)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${DIR_SOURCES})
|
target_include_directories(${PROJECT_NAME} PRIVATE ${DIR_SOURCES})
|
||||||
|
|
||||||
@@ -96,7 +171,7 @@ elseif(APPLE)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
elseif(EMSCRIPTEN)
|
elseif(EMSCRIPTEN)
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD)
|
target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD NO_SHADERS)
|
||||||
# En wasm NO empaquetamos un resources.pack: el propio --preload-file de
|
# En wasm NO empaquetamos un resources.pack: el propio --preload-file de
|
||||||
# emscripten ya hace el mismo trabajo (bundle del directorio en un .data),
|
# emscripten ya hace el mismo trabajo (bundle del directorio en un .data),
|
||||||
# así que metemos directamente 'data' y dejamos que el Resource lea por
|
# así que metemos directamente 'data' y dejamos que el Resource lea por
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -349,6 +349,11 @@ cppcheck:
|
|||||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
@cmake --build build --target cppcheck
|
@cmake --build build --target cppcheck
|
||||||
|
|
||||||
|
# SHADERS (SPIR-V) — sólo Linux/Windows. Requiere glslc en el PATH.
|
||||||
|
compile_shaders:
|
||||||
|
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||||
|
@cmake --build build --target shaders
|
||||||
|
|
||||||
# DESCARGA DE GAMECONTROLLERDB
|
# DESCARGA DE GAMECONTROLLERDB
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
controllerdb:
|
controllerdb:
|
||||||
@@ -378,6 +383,7 @@ help:
|
|||||||
@echo ""
|
@echo ""
|
||||||
@echo " Herramientas:"
|
@echo " Herramientas:"
|
||||||
@echo " make pack - Empaquetar recursos a resources.pack"
|
@echo " make pack - Empaquetar recursos a resources.pack"
|
||||||
|
@echo " make compile_shaders - Compilar shaders GLSL a headers SPIR-V (requiere glslc)"
|
||||||
@echo " make controllerdb - Descargar gamecontrollerdb.txt actualizado"
|
@echo " make controllerdb - Descargar gamecontrollerdb.txt actualizado"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo " Calidad de codigo:"
|
@echo " Calidad de codigo:"
|
||||||
@@ -391,4 +397,4 @@ help:
|
|||||||
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
||||||
@echo " make help - Mostrar esta ayuda"
|
@echo " make help - Mostrar esta ayuda"
|
||||||
|
|
||||||
.PHONY: all debug release _windows_release _macos_release _linux_release wasm wasm_debug controllerdb pack format format-check tidy tidy-fix cppcheck show_version help
|
.PHONY: all debug release _windows_release _macos_release _linux_release wasm wasm_debug controllerdb pack format format-check tidy tidy-fix cppcheck compile_shaders show_version help
|
||||||
|
|||||||
152
data/shaders/crtpi_frag.glsl
Normal file
152
data/shaders/crtpi_frag.glsl
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL fragment shader — CRT-Pi PostFX
|
||||||
|
// Algoritmo de scanlines continuas con pesos gaussianos, bloom y máscara de fósforo.
|
||||||
|
// Basado en el shader CRT-Pi original (GLSL 3.3), portado a GLSL 4.50 con parámetros uniformes.
|
||||||
|
//
|
||||||
|
// Compile: glslc -fshader-stage=frag --target-env=vulkan1.0 crtpi_frag.glsl -o crtpi_frag.spv
|
||||||
|
// xxd -i crtpi_frag.spv > ../../source/core/rendering/sdl3gpu/crtpi_frag_spv.h
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D Texture;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform CrtPiBlock {
|
||||||
|
// vec4 #0
|
||||||
|
float scanline_weight; // Ajuste gaussiano de scanlines (default 6.0)
|
||||||
|
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
|
||||||
|
float bloom_factor; // Factor de brillo en zonas iluminadas (default 3.5)
|
||||||
|
float input_gamma; // Gamma de entrada — linealización (default 2.4)
|
||||||
|
// vec4 #1
|
||||||
|
float output_gamma; // Gamma de salida — codificación (default 2.2)
|
||||||
|
float mask_brightness; // Brillo sub-píxeles de la máscara (default 0.80)
|
||||||
|
float curvature_x; // Distorsión barrel eje X (default 0.05)
|
||||||
|
float curvature_y; // Distorsión barrel eje Y (default 0.10)
|
||||||
|
// vec4 #2
|
||||||
|
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||||
|
int enable_scanlines; // 0 = off, 1 = on
|
||||||
|
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico de scanlines)
|
||||||
|
int enable_gamma; // 0 = off, 1 = on
|
||||||
|
// vec4 #3
|
||||||
|
int enable_curvature; // 0 = off, 1 = on
|
||||||
|
int enable_sharper; // 0 = off, 1 = on
|
||||||
|
float texture_width; // Ancho del canvas lógico en píxeles
|
||||||
|
float texture_height; // Alto del canvas lógico en píxeles
|
||||||
|
} u;
|
||||||
|
|
||||||
|
// Distorsión barrel CRT
|
||||||
|
vec2 distort(vec2 coord, vec2 screen_scale) {
|
||||||
|
vec2 curvature = vec2(u.curvature_x, u.curvature_y);
|
||||||
|
vec2 barrel_scale = 1.0 - (0.23 * curvature);
|
||||||
|
coord *= screen_scale;
|
||||||
|
coord -= vec2(0.5);
|
||||||
|
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||||
|
coord += coord * (curvature * rsq);
|
||||||
|
coord *= barrel_scale;
|
||||||
|
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5) {
|
||||||
|
return vec2(-1.0); // fuera de pantalla
|
||||||
|
}
|
||||||
|
coord += vec2(0.5);
|
||||||
|
coord /= screen_scale;
|
||||||
|
return coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
float calcScanLineWeight(float dist) {
|
||||||
|
return max(1.0 - dist * dist * u.scanline_weight, u.scanline_gap_brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
float calcScanLine(float dy, float filter_width) {
|
||||||
|
float weight = calcScanLineWeight(dy);
|
||||||
|
if (u.enable_multisample != 0) {
|
||||||
|
weight += calcScanLineWeight(dy - filter_width);
|
||||||
|
weight += calcScanLineWeight(dy + filter_width);
|
||||||
|
weight *= 0.3333333;
|
||||||
|
}
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 tex_size = vec2(u.texture_width, u.texture_height);
|
||||||
|
|
||||||
|
// filterWidth: equivalente al original (768.0 / TextureSize.y) / 3.0
|
||||||
|
float filter_width = (768.0 / u.texture_height) / 3.0;
|
||||||
|
|
||||||
|
vec2 texcoord = v_uv;
|
||||||
|
|
||||||
|
// Curvatura barrel opcional
|
||||||
|
if (u.enable_curvature != 0) {
|
||||||
|
texcoord = distort(texcoord, vec2(1.0, 1.0));
|
||||||
|
if (texcoord.x < 0.0) {
|
||||||
|
out_color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 texcoord_in_pixels = texcoord * tex_size;
|
||||||
|
vec2 tc;
|
||||||
|
float scan_line_weight;
|
||||||
|
|
||||||
|
if (u.enable_sharper != 0) {
|
||||||
|
// Modo SHARPER: filtrado bicúbico-like con subpixel sharpen
|
||||||
|
vec2 temp_coord = floor(texcoord_in_pixels) + 0.5;
|
||||||
|
tc = temp_coord / tex_size;
|
||||||
|
vec2 deltas = texcoord_in_pixels - temp_coord;
|
||||||
|
scan_line_weight = calcScanLine(deltas.y, filter_width);
|
||||||
|
vec2 signs = sign(deltas);
|
||||||
|
deltas.x *= 2.0;
|
||||||
|
deltas = deltas * deltas;
|
||||||
|
deltas.y = deltas.y * deltas.y;
|
||||||
|
deltas.x *= 0.5;
|
||||||
|
deltas.y *= 8.0;
|
||||||
|
deltas /= tex_size;
|
||||||
|
deltas *= signs;
|
||||||
|
tc = tc + deltas;
|
||||||
|
} else {
|
||||||
|
// Modo estándar
|
||||||
|
float temp_y = floor(texcoord_in_pixels.y) + 0.5;
|
||||||
|
float y_coord = temp_y / tex_size.y;
|
||||||
|
float dy = texcoord_in_pixels.y - temp_y;
|
||||||
|
scan_line_weight = calcScanLine(dy, filter_width);
|
||||||
|
float sign_y = sign(dy);
|
||||||
|
dy = dy * dy;
|
||||||
|
dy = dy * dy;
|
||||||
|
dy *= 8.0;
|
||||||
|
dy /= tex_size.y;
|
||||||
|
dy *= sign_y;
|
||||||
|
tc = vec2(texcoord.x, y_coord + dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 colour = texture(Texture, tc).rgb;
|
||||||
|
|
||||||
|
if (u.enable_scanlines != 0) {
|
||||||
|
if (u.enable_gamma != 0) {
|
||||||
|
colour = pow(colour, vec3(u.input_gamma));
|
||||||
|
}
|
||||||
|
colour *= scan_line_weight * u.bloom_factor;
|
||||||
|
if (u.enable_gamma != 0) {
|
||||||
|
colour = pow(colour, vec3(1.0 / u.output_gamma));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Máscara de fósforo
|
||||||
|
if (u.mask_type == 1) {
|
||||||
|
float which_mask = fract(gl_FragCoord.x * 0.5);
|
||||||
|
vec3 mask = (which_mask < 0.5)
|
||||||
|
? vec3(u.mask_brightness, 1.0, u.mask_brightness)
|
||||||
|
: vec3(1.0, u.mask_brightness, 1.0);
|
||||||
|
colour *= mask;
|
||||||
|
} else if (u.mask_type == 2) {
|
||||||
|
float which_mask = fract(gl_FragCoord.x * 0.3333333);
|
||||||
|
vec3 mask = vec3(u.mask_brightness);
|
||||||
|
if (which_mask < 0.3333333)
|
||||||
|
mask.x = 1.0;
|
||||||
|
else if (which_mask < 0.6666666)
|
||||||
|
mask.y = 1.0;
|
||||||
|
else
|
||||||
|
mask.z = 1.0;
|
||||||
|
colour *= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_color = vec4(colour, 1.0);
|
||||||
|
}
|
||||||
48
data/shaders/downscale.frag
Normal file
48
data/shaders/downscale.frag
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#version 450
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D source;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform DownscaleUniforms {
|
||||||
|
int algorithm; // 0 = Lanczos2 (ventana 2, ±2 taps), 1 = Lanczos3 (ventana 3, ±3 taps)
|
||||||
|
float pad0;
|
||||||
|
float pad1;
|
||||||
|
float pad2;
|
||||||
|
} u;
|
||||||
|
|
||||||
|
// Kernel Lanczos normalizado: sinc(t) * sinc(t/a) para |t| < a, 0 fuera.
|
||||||
|
float lanczos(float t, float a) {
|
||||||
|
t = abs(t);
|
||||||
|
if (t < 0.0001) { return 1.0; }
|
||||||
|
if (t >= a) { return 0.0; }
|
||||||
|
const float PI = 3.14159265358979;
|
||||||
|
float pt = PI * t;
|
||||||
|
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 src_size = vec2(textureSize(source, 0));
|
||||||
|
// Posición en coordenadas de texel (centros de texel en N+0.5)
|
||||||
|
vec2 p = v_uv * src_size;
|
||||||
|
vec2 p_floor = floor(p);
|
||||||
|
|
||||||
|
float a = (u.algorithm == 0) ? 2.0 : 3.0;
|
||||||
|
int win = int(a);
|
||||||
|
|
||||||
|
vec4 color = vec4(0.0);
|
||||||
|
float weight_sum = 0.0;
|
||||||
|
|
||||||
|
for (int j = -win; j <= win; j++) {
|
||||||
|
for (int i = -win; i <= win; i++) {
|
||||||
|
// Centro del texel (i,j) relativo a p_floor
|
||||||
|
vec2 tap_center = p_floor + vec2(float(i), float(j)) + 0.5;
|
||||||
|
vec2 offset = tap_center - p;
|
||||||
|
float w = lanczos(offset.x, a) * lanczos(offset.y, a);
|
||||||
|
color += texture(source, tap_center / src_size) * w;
|
||||||
|
weight_sum += w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out_color = (weight_sum > 0.0) ? (color / weight_sum) : vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
142
data/shaders/postfx.frag
Normal file
142
data/shaders/postfx.frag
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
#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.
|
||||||
|
// Modelo sustractivo: las filas de scanline se oscurecen, las demás no cambian.
|
||||||
|
// Esto evita el efecto de sobrebrillo en contenido con colores vivos.
|
||||||
|
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(1.0, 0.0, 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];
|
||||||
|
}
|
||||||
15
data/shaders/upscale.frag
Normal file
15
data/shaders/upscale.frag
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Vulkan GLSL fragment shader — Nearest-neighbour upscale pass
|
||||||
|
// Used as the first render pass when supersampling is active.
|
||||||
|
// Compile: glslc upscale.frag -o upscale.frag.spv
|
||||||
|
// xxd -i upscale.frag.spv > ../../source/core/rendering/sdl3gpu/upscale_frag_spv.h
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_uv;
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_color = texture(scene, v_uv);
|
||||||
|
}
|
||||||
@@ -34,6 +34,11 @@ enum inputs_e {
|
|||||||
input_window_inc_size,
|
input_window_inc_size,
|
||||||
input_window_dec_size,
|
input_window_dec_size,
|
||||||
|
|
||||||
|
// GPU / shaders (hotkeys provisionales hasta que haya menú de opciones)
|
||||||
|
input_toggle_gpu,
|
||||||
|
input_toggle_shader,
|
||||||
|
input_toggle_shader_type,
|
||||||
|
|
||||||
// Input obligatorio
|
// Input obligatorio
|
||||||
input_number_of_inputs
|
input_number_of_inputs
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include <algorithm> // for max, min
|
#include <algorithm> // for max, min
|
||||||
|
#include <cstring> // for memcpy
|
||||||
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
#include <iostream> // for basic_ostream, operator<<, cout, endl
|
||||||
#include <string> // for basic_string, char_traits, string
|
#include <string> // for basic_string, char_traits, string
|
||||||
|
|
||||||
@@ -11,6 +12,10 @@
|
|||||||
#include "core/resources/asset.h" // for Asset
|
#include "core/resources/asset.h" // for Asset
|
||||||
#include "core/resources/resource.h"
|
#include "core/resources/resource.h"
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // for Rendering::SDL3GPUShader
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#include <emscripten/html5.h>
|
#include <emscripten/html5.h>
|
||||||
@@ -69,8 +74,23 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
|||||||
// Define el color del borde para el modo de pantalla completa
|
// Define el color del borde para el modo de pantalla completa
|
||||||
borderColor = {0x00, 0x00, 0x00};
|
borderColor = {0x00, 0x00, 0x00};
|
||||||
|
|
||||||
// Crea la textura donde se dibujan los graficos del juego
|
// Establece el modo de video (fullscreen/ventana + logical presentation)
|
||||||
gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight);
|
// ANTES de crear la textura — SDL3 GPU necesita la logical presentation
|
||||||
|
// del renderer ya aplicada al swapchain quan es reclama la ventana per a GPU.
|
||||||
|
// Mirror del pattern de jaildoctors_dilemma (que usa exactament 256×192 i
|
||||||
|
// funciona) on `initSDLVideo` configura la presentation abans de crear cap
|
||||||
|
// textura.
|
||||||
|
setVideoMode(options->videoMode != 0);
|
||||||
|
|
||||||
|
// Força al window manager a completar el resize/posicionat abans de passar
|
||||||
|
// la ventana al dispositiu GPU. Sense açò en Linux/X11 hi ha un race
|
||||||
|
// condition que deixa el swapchain en estat inestable i fa crashear el
|
||||||
|
// driver Vulkan en `SDL_CreateGPUGraphicsPipeline`.
|
||||||
|
SDL_SyncWindow(window);
|
||||||
|
|
||||||
|
// Crea la textura donde se dibujan los graficos del juego.
|
||||||
|
// ARGB8888 per simplificar el readback cap al pipeline SDL3 GPU.
|
||||||
|
gameCanvas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, gameCanvasWidth, gameCanvasHeight);
|
||||||
if (gameCanvas != nullptr) {
|
if (gameCanvas != nullptr) {
|
||||||
SDL_SetTextureScaleMode(gameCanvas, options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
|
SDL_SetTextureScaleMode(gameCanvas, options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);
|
||||||
}
|
}
|
||||||
@@ -80,11 +100,24 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establece el modo de video
|
#ifndef NO_SHADERS
|
||||||
setVideoMode(options->videoMode != 0);
|
// Buffer de readback del gameCanvas (lo dimensionamos una vez)
|
||||||
|
pixel_buffer_.resize(static_cast<size_t>(gameCanvasWidth) * static_cast<size_t>(gameCanvasHeight));
|
||||||
|
#endif
|
||||||
|
|
||||||
// Inicializa el sistema de notificaciones (Text compartido de Resource)
|
// Renderiza una vez la textura vacía al renderer abans d'inicialitzar els
|
||||||
notificationText = Resource::get()->getText("8bithud");
|
// shaders: jaildoctors_dilemma ho fa així i evita que el driver Vulkan
|
||||||
|
// crashegi en la creació del pipeline gràfic. `initShaders()` es crida
|
||||||
|
// després des de `Director` amb el swapchain ja estable.
|
||||||
|
SDL_RenderTexture(renderer, gameCanvas, nullptr, nullptr);
|
||||||
|
|
||||||
|
// Estado inicial de las notificaciones. El Text real se enlaza después vía
|
||||||
|
// `initNotifications()` quan `Resource` ja estigui inicialitzat. Dividim
|
||||||
|
// això del constructor perquè `initShaders()` (GPU) ha de cridar-se ABANS
|
||||||
|
// de carregar recursos: si el SDL_Renderer ha fet abans moltes
|
||||||
|
// allocacions (carrega de textures), el driver Vulkan crasheja quan
|
||||||
|
// després es reclama la ventana per al dispositiu GPU.
|
||||||
|
notificationText = nullptr;
|
||||||
notificationMessage = "";
|
notificationMessage = "";
|
||||||
notificationTextColor = {0xFF, 0xFF, 0xFF};
|
notificationTextColor = {0xFF, 0xFF, 0xFF};
|
||||||
notificationOutlineColor = {0x00, 0x00, 0x00};
|
notificationOutlineColor = {0x00, 0x00, 0x00};
|
||||||
@@ -95,9 +128,18 @@ Screen::Screen(SDL_Window *window, SDL_Renderer *renderer, Asset *asset, options
|
|||||||
registerEmscriptenEventCallbacks();
|
registerEmscriptenEventCallbacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enllaça el Text de les notificacions amb el recurs compartit de `Resource`.
|
||||||
|
// S'ha de cridar després de `Resource::init(...)`.
|
||||||
|
void Screen::initNotifications() {
|
||||||
|
notificationText = Resource::get()->getText("8bithud");
|
||||||
|
}
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
Screen::~Screen() {
|
Screen::~Screen() {
|
||||||
// notificationText es propiedad de Resource — no liberar.
|
// notificationText es propiedad de Resource — no liberar.
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
shutdownShaders();
|
||||||
|
#endif
|
||||||
SDL_DestroyTexture(gameCanvas);
|
SDL_DestroyTexture(gameCanvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +160,51 @@ void Screen::blit() {
|
|||||||
SDL_SetRenderTarget(renderer, gameCanvas);
|
SDL_SetRenderTarget(renderer, gameCanvas);
|
||||||
renderNotification();
|
renderNotification();
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// Si el backend GPU està viu i accelerat, passem sempre per ell (tant amb
|
||||||
|
// shaders com sense). Seguim el mateix pattern que aee_plus: quan shader
|
||||||
|
// està desactivat, forcem POSTFX + params a zero només per a aquest frame
|
||||||
|
// i restaurem el shader actiu, així CRTPI no aplica les seues scanlines
|
||||||
|
// quan l'usuari ho ha desactivat.
|
||||||
|
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (options->videoShaderEnabled) {
|
||||||
|
// Ruta normal: shader amb els seus params.
|
||||||
|
shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight);
|
||||||
|
shader_backend_->render();
|
||||||
|
} else {
|
||||||
|
// Shader off: POSTFX amb params zero (passa-per-aquí). CRTPI no
|
||||||
|
// val perque sempre aplica els seus efectes interns; salvem i
|
||||||
|
// restaurem el shader actiu.
|
||||||
|
const auto PREV_SHADER = shader_backend_->getActiveShader();
|
||||||
|
if (PREV_SHADER != Rendering::ShaderType::POSTFX) {
|
||||||
|
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||||
|
}
|
||||||
|
shader_backend_->setPostFXParams(Rendering::PostFXParams{});
|
||||||
|
shader_backend_->uploadPixels(pixel_buffer_.data(), gameCanvasWidth, gameCanvasHeight);
|
||||||
|
shader_backend_->render();
|
||||||
|
if (PREV_SHADER != Rendering::ShaderType::POSTFX) {
|
||||||
|
shader_backend_->setActiveShader(PREV_SHADER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Vuelve a dejar el renderizador en modo normal
|
// Vuelve a dejar el renderizador en modo normal
|
||||||
SDL_SetRenderTarget(renderer, nullptr);
|
SDL_SetRenderTarget(renderer, nullptr);
|
||||||
|
|
||||||
@@ -199,6 +286,11 @@ void Screen::toggleIntegerScale() {
|
|||||||
void Screen::setVSync(bool enabled) {
|
void Screen::setVSync(bool enabled) {
|
||||||
options->vSync = enabled;
|
options->vSync = enabled;
|
||||||
SDL_SetRenderVSync(renderer, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
SDL_SetRenderVSync(renderer, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (shader_backend_) {
|
||||||
|
shader_backend_->setVSync(enabled);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alterna el V-Sync
|
// Alterna el V-Sync
|
||||||
@@ -321,7 +413,7 @@ void Screen::clearNotification() {
|
|||||||
|
|
||||||
// Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
// Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
||||||
void Screen::renderNotification() {
|
void Screen::renderNotification() {
|
||||||
if (SDL_GetTicks() >= notificationEndTime) {
|
if (notificationText == nullptr || SDL_GetTicks() >= notificationEndTime) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
notificationText->writeDX(TXT_CENTER | TXT_COLOR | TXT_STROKE,
|
notificationText->writeDX(TXT_CENTER | TXT_COLOR | TXT_STROKE,
|
||||||
@@ -367,3 +459,144 @@ void Screen::registerEmscriptenEventCallbacks() {
|
|||||||
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
|
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GPU / shaders (SDL3 GPU post-procesado). En builds con NO_SHADERS (Emscripten)
|
||||||
|
// las operaciones son no-op; la ruta clásica sigue siendo la única disponible.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// Aplica al backend el preset del shader actiu segons options.
|
||||||
|
// Només s'ha de cridar quan `videoShaderEnabled=true` (en cas contrari el
|
||||||
|
// blit() ja força POSTFX+zero params per a desactivar els efectes sense
|
||||||
|
// tocar els paràmetres emmagatzemats).
|
||||||
|
void Screen::applyShaderParams() {
|
||||||
|
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Rendering::ShaderType ACTIVE = options->videoShaderType == 1
|
||||||
|
? Rendering::ShaderType::CRTPI
|
||||||
|
: Rendering::ShaderType::POSTFX;
|
||||||
|
shader_backend_->setActiveShader(ACTIVE);
|
||||||
|
|
||||||
|
// Preset per defecte (carregador YAML pendent). Valors estil "CRT" de CCAE.
|
||||||
|
Rendering::PostFXParams POSTFX;
|
||||||
|
POSTFX.vignette = 0.15F;
|
||||||
|
POSTFX.scanlines = 0.7F;
|
||||||
|
POSTFX.chroma = 0.2F;
|
||||||
|
shader_backend_->setPostFXParams(POSTFX);
|
||||||
|
|
||||||
|
// CrtPi: defaults del struct ja raonables (scanline_weight=6.0, bloom=3.5…).
|
||||||
|
shader_backend_->setCrtPiParams(Rendering::CrtPiParams{});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Screen::initShaders() {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (!shader_backend_) {
|
||||||
|
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||||
|
const std::string FALLBACK_DRIVER = "none";
|
||||||
|
shader_backend_->setPreferredDriver(
|
||||||
|
options->videoGpuAcceleration ? options->videoGpuPreferredDriver : FALLBACK_DRIVER);
|
||||||
|
}
|
||||||
|
if (!shader_backend_->isHardwareAccelerated()) {
|
||||||
|
const bool ok = shader_backend_->init(window, gameCanvas, "", "");
|
||||||
|
if (options->console) {
|
||||||
|
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shader_backend_->isHardwareAccelerated()) {
|
||||||
|
shader_backend_->setScaleMode(options->integerScale);
|
||||||
|
shader_backend_->setVSync(options->vSync);
|
||||||
|
applyShaderParams(); // aplica preset del shader actiu
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::shutdownShaders() {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// Només es crida des del destructor de Screen. Els toggles runtime NO la
|
||||||
|
// poden cridar: destruir + recrear el dispositiu SDL3 GPU amb la ventana
|
||||||
|
// ja reclamada és inestable (Vulkan/Radeon crasheja en el següent claim).
|
||||||
|
if (shader_backend_) {
|
||||||
|
shader_backend_->cleanup();
|
||||||
|
shader_backend_.reset();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::setGpuAcceleration(bool enabled) {
|
||||||
|
if (options->videoGpuAcceleration == enabled) { return; }
|
||||||
|
options->videoGpuAcceleration = enabled;
|
||||||
|
// Soft toggle: el backend es manté viu (vegeu shutdownShaders). El canvi
|
||||||
|
// s'aplica al proper arrencada. S'emet una notificació perquè l'usuari
|
||||||
|
// sap que ha tocat la tecla però el canvi no és immediat.
|
||||||
|
const color_t YELLOW = {0xFF, 0xFF, 0x00};
|
||||||
|
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||||
|
const Uint32 DUR_MS = 2500;
|
||||||
|
notify(enabled ? "GPU: ON (restart)" : "GPU: OFF (restart)", YELLOW, BLACK, DUR_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::toggleGpuAcceleration() {
|
||||||
|
setGpuAcceleration(!options->videoGpuAcceleration);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::isGpuAccelerated() const -> bool {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
return shader_backend_ && shader_backend_->isHardwareAccelerated();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::setShaderEnabled(bool enabled) {
|
||||||
|
if (options->videoShaderEnabled == enabled) { return; }
|
||||||
|
options->videoShaderEnabled = enabled;
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (enabled) {
|
||||||
|
applyShaderParams(); // restaura preset del shader actiu
|
||||||
|
}
|
||||||
|
// Si enabled=false, blit() forçarà POSTFX+zero per frame — no cal tocar
|
||||||
|
// res ara.
|
||||||
|
#endif
|
||||||
|
const color_t CYAN = {0x00, 0xFF, 0xFF};
|
||||||
|
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||||
|
const Uint32 DUR_MS = 1500;
|
||||||
|
notify(enabled ? "Shader: ON" : "Shader: OFF", CYAN, BLACK, DUR_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::toggleShaderEnabled() {
|
||||||
|
setShaderEnabled(!options->videoShaderEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::isShaderEnabled() const -> bool {
|
||||||
|
return options->videoShaderEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
void Screen::setActiveShader(Rendering::ShaderType type) {
|
||||||
|
options->videoShaderType = type == Rendering::ShaderType::CRTPI ? 1 : 0;
|
||||||
|
if (options->videoShaderEnabled) {
|
||||||
|
applyShaderParams();
|
||||||
|
}
|
||||||
|
const color_t MAGENTA = {0xFF, 0x00, 0xFF};
|
||||||
|
const color_t BLACK = {0x00, 0x00, 0x00};
|
||||||
|
const Uint32 DUR_MS = 1500;
|
||||||
|
notify(type == Rendering::ShaderType::CRTPI ? "Shader: CRTPI" : "Shader: POSTFX", MAGENTA, BLACK, DUR_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Screen::getActiveShader() const -> Rendering::ShaderType {
|
||||||
|
return options->videoShaderType == 1 ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Screen::toggleActiveShader() {
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
const Rendering::ShaderType NEXT = getActiveShader() == Rendering::ShaderType::POSTFX
|
||||||
|
? Rendering::ShaderType::CRTPI
|
||||||
|
: Rendering::ShaderType::POSTFX;
|
||||||
|
setActiveShader(NEXT);
|
||||||
|
#else
|
||||||
|
options->videoShaderType = options->videoShaderType == 1 ? 0 : 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,19 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <memory> // for unique_ptr
|
||||||
#include <string> // for string
|
#include <string> // for string
|
||||||
|
#include <vector> // for vector
|
||||||
|
|
||||||
#include "utils/utils.h" // for color_t
|
#include "utils/utils.h" // for color_t
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
#include "core/rendering/shader_backend.hpp" // for Rendering::ShaderType
|
||||||
|
namespace Rendering {
|
||||||
|
class ShaderBackend;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
class Asset;
|
class Asset;
|
||||||
class Text;
|
class Text;
|
||||||
|
|
||||||
@@ -50,9 +60,25 @@ class Screen {
|
|||||||
void setBorderColor(color_t color); // Cambia el color del borde
|
void setBorderColor(color_t color); // Cambia el color del borde
|
||||||
|
|
||||||
// Notificaciones
|
// Notificaciones
|
||||||
|
void initNotifications(); // Enllaça el Text de notificacions amb `Resource`. A cridar després de `Resource::init(...)`.
|
||||||
void notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs); // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación).
|
void notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs); // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación).
|
||||||
void clearNotification(); // Limpia la notificación actual
|
void clearNotification(); // Limpia la notificación actual
|
||||||
|
|
||||||
|
// GPU / shaders (post-procesado). En builds con NO_SHADERS (Emscripten) son no-op.
|
||||||
|
void initShaders(); // Crea el backend GPU si no existe y lo inicializa
|
||||||
|
void shutdownShaders(); // Libera el backend GPU
|
||||||
|
void setGpuAcceleration(bool enabled); // Crea/destruye el backend según valor, persiste options
|
||||||
|
void toggleGpuAcceleration(); // Alterna aceleración GPU
|
||||||
|
auto isGpuAccelerated() const -> bool; // true si el backend existe y reporta hardware OK
|
||||||
|
void setShaderEnabled(bool enabled); // Activa o desactiva el post-procesado (persiste)
|
||||||
|
void toggleShaderEnabled(); // Alterna post-procesado
|
||||||
|
auto isShaderEnabled() const -> bool; // Estado actual (lee options)
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
void setActiveShader(Rendering::ShaderType type); // POSTFX o CRTPI
|
||||||
|
auto getActiveShader() const -> Rendering::ShaderType;
|
||||||
|
#endif
|
||||||
|
void toggleActiveShader(); // Alterna POSTFX ↔ CRTPI
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helpers internos de setVideoMode
|
// Helpers internos de setVideoMode
|
||||||
void applyFullscreen(bool fullscreen); // SDL_SetWindowFullscreen + visibilidad del cursor
|
void applyFullscreen(bool fullscreen); // SDL_SetWindowFullscreen + visibilidad del cursor
|
||||||
@@ -67,6 +93,12 @@ class Screen {
|
|||||||
// Notificaciones
|
// Notificaciones
|
||||||
void renderNotification(); // Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
void renderNotification(); // Dibuja la notificación activa (si la hay) sobre el gameCanvas
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// Aplica els paràmetres actuals del shader al backend segons options
|
||||||
|
// (pass-through si `videoShaderEnabled==false`, preset per defecte si true).
|
||||||
|
void applyShaderParams();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Objetos y punteros
|
// Objetos y punteros
|
||||||
SDL_Window *window; // Ventana de la aplicación
|
SDL_Window *window; // Ventana de la aplicación
|
||||||
SDL_Renderer *renderer; // El renderizador de la ventana
|
SDL_Renderer *renderer; // El renderizador de la ventana
|
||||||
@@ -91,4 +123,10 @@ class Screen {
|
|||||||
color_t notificationOutlineColor; // Color del outline
|
color_t notificationOutlineColor; // Color del outline
|
||||||
Uint32 notificationEndTime; // SDL_GetTicks() hasta el cual se muestra
|
Uint32 notificationEndTime; // SDL_GetTicks() hasta el cual se muestra
|
||||||
int notificationY; // Fila vertical en el canvas virtual
|
int notificationY; // Fila vertical en el canvas virtual
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
// GPU / shaders
|
||||||
|
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend GPU (nullptr si inactivo)
|
||||||
|
std::vector<Uint32> pixel_buffer_; // Buffer de readback del gameCanvas (ARGB8888)
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|||||||
1329
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
1329
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
File diff suppressed because it is too large
Load Diff
178
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
178
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
|
||||||
|
#include "core/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)
|
||||||
|
};
|
||||||
|
|
||||||
|
// CrtPi uniforms pushed to fragment stage each frame.
|
||||||
|
// Must match the MSL struct and GLSL uniform block layout.
|
||||||
|
// 14 fields (8 floats + 6 ints) + 2 floats (texture size) = 16 fields = 64 bytes — 4 × 16-byte alignment.
|
||||||
|
struct CrtPiUniforms {
|
||||||
|
// vec4 #0
|
||||||
|
float scanline_weight; // Ajuste gaussiano (default 6.0)
|
||||||
|
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
|
||||||
|
float bloom_factor; // Factor brillo zonas iluminadas (default 3.5)
|
||||||
|
float input_gamma; // Gamma de entrada (default 2.4)
|
||||||
|
// vec4 #1
|
||||||
|
float output_gamma; // Gamma de salida (default 2.2)
|
||||||
|
float mask_brightness; // Brillo sub-píxeles máscara (default 0.80)
|
||||||
|
float curvature_x; // Distorsión barrel X (default 0.05)
|
||||||
|
float curvature_y; // Distorsión barrel Y (default 0.10)
|
||||||
|
// vec4 #2
|
||||||
|
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||||
|
int enable_scanlines; // 0 = off, 1 = on
|
||||||
|
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico)
|
||||||
|
int enable_gamma; // 0 = off, 1 = on
|
||||||
|
// vec4 #3
|
||||||
|
int enable_curvature; // 0 = off, 1 = on
|
||||||
|
int enable_sharper; // 0 = off, 1 = on
|
||||||
|
float texture_width; // Ancho del canvas en píxeles (inyectado en render)
|
||||||
|
float texture_height; // Alto del canvas en píxeles (inyectado en render)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Downscale uniforms pushed to the Lanczos downscale fragment stage.
|
||||||
|
// 1 int + 3 floats = 16 bytes — meets Metal/Vulkan alignment.
|
||||||
|
struct DownscaleUniforms {
|
||||||
|
int algorithm; // 0 = Lanczos2 (ventana 2), 1 = Lanczos3 (ventana 3)
|
||||||
|
float pad0;
|
||||||
|
float pad1;
|
||||||
|
float pad2;
|
||||||
|
};
|
||||||
|
|
||||||
|
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_; }
|
||||||
|
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
||||||
|
|
||||||
|
// Establece el driver GPU preferido (vacío = auto). Debe llamarse antes de init().
|
||||||
|
void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Activa/desactiva interpolación LINEAR en el upscale (false = NEAREST)
|
||||||
|
void setLinearUpscale(bool linear) override;
|
||||||
|
|
||||||
|
// Selecciona algoritmo de downscale: 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
|
||||||
|
void setDownscaleAlgo(int algo) override;
|
||||||
|
|
||||||
|
// Devuelve las dimensiones de la textura de supersampling (0,0 si SS desactivado)
|
||||||
|
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||||
|
|
||||||
|
// Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
|
||||||
|
void setActiveShader(ShaderType type) override;
|
||||||
|
|
||||||
|
// Actualiza los parámetros del shader CRT-Pi
|
||||||
|
void setCrtPiParams(const CrtPiParams& p) override;
|
||||||
|
|
||||||
|
// Devuelve el shader activo
|
||||||
|
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
|
||||||
|
|
||||||
|
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 createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi
|
||||||
|
auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_
|
||||||
|
auto recreateScaledTexture(int factor) -> bool; // Recrea scaled_texture_ para factor dado
|
||||||
|
static auto calcSsFactor(float zoom) -> int; // Primer múltiplo de 3 >= zoom (mín 3)
|
||||||
|
// Devuelve el mejor present mode disponible: IMMEDIATE > MAILBOX > VSYNC
|
||||||
|
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||||
|
|
||||||
|
SDL_Window* window_ = nullptr;
|
||||||
|
SDL_GPUDevice* device_ = nullptr;
|
||||||
|
SDL_GPUGraphicsPipeline* pipeline_ = nullptr; // PostFX pass (→ swapchain o → postfx_texture_)
|
||||||
|
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr; // CrtPi pass (→ swapchain directo, sin SS)
|
||||||
|
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr; // PostFX → postfx_texture_ (B8G8R8A8, solo con Lanczos)
|
||||||
|
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr; // Upscale pass (solo con SS)
|
||||||
|
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr; // Lanczos downscale (solo con SS + algo > 0)
|
||||||
|
SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del juego (game_width_ × game_height_)
|
||||||
|
SDL_GPUTexture* scaled_texture_ = nullptr; // Upscale target (game×factor), solo con SS
|
||||||
|
SDL_GPUTexture* postfx_texture_ = nullptr; // PostFX output a resolución escalada, solo con Lanczos
|
||||||
|
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||||
|
SDL_GPUSampler* sampler_ = nullptr; // NEAREST
|
||||||
|
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR
|
||||||
|
|
||||||
|
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};
|
||||||
|
CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
|
||||||
|
ShaderType active_shader_ = ShaderType::POSTFX; // Shader de post-procesado activo
|
||||||
|
|
||||||
|
int game_width_ = 0; // Dimensiones originales del canvas
|
||||||
|
int game_height_ = 0;
|
||||||
|
int ss_factor_ = 0; // Factor SS activo (3, 6, 9...) o 0 si SS desactivado
|
||||||
|
int oversample_ = 1; // SS on/off (1 = off, >1 = on)
|
||||||
|
int downscale_algo_ = 1; // 0 = bilinear legacy, 1 = Lanczos2, 2 = Lanczos3
|
||||||
|
std::string driver_name_;
|
||||||
|
std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige)
|
||||||
|
bool is_initialized_ = false;
|
||||||
|
bool vsync_ = true;
|
||||||
|
bool integer_scale_ = false;
|
||||||
|
bool linear_upscale_ = false; // Upscale NEAREST (false) o LINEAR (true)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
2
source/core/rendering/sdl3gpu/spv/.clang-format
Normal file
2
source/core/rendering/sdl3gpu/spv/.clang-format
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DisableFormat: true
|
||||||
|
SortIncludes: Never
|
||||||
4
source/core/rendering/sdl3gpu/spv/.clang-tidy
Normal file
4
source/core/rendering/sdl3gpu/spv/.clang-tidy
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# source/core/rendering/sdl3gpu/spv/.clang-tidy
|
||||||
|
Checks: '-*'
|
||||||
|
WarningsAsErrors: ''
|
||||||
|
HeaderFilterRegex: ''
|
||||||
10362
source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h
Normal file
10362
source/core/rendering/sdl3gpu/spv/crtpi_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
4253
source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h
Normal file
4253
source/core/rendering/sdl3gpu/spv/downscale_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
11717
source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h
Normal file
11717
source/core/rendering/sdl3gpu/spv/postfx_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
1449
source/core/rendering/sdl3gpu/spv/postfx_vert_spv.h
Normal file
1449
source/core/rendering/sdl3gpu/spv/postfx_vert_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
633
source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h
Normal file
633
source/core/rendering/sdl3gpu/spv/upscale_frag_spv.h
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
static const uint8_t kupscale_frag_spv[] = {
|
||||||
|
0x03,
|
||||||
|
0x02,
|
||||||
|
0x23,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x14,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x53,
|
||||||
|
0x4c,
|
||||||
|
0x2e,
|
||||||
|
0x73,
|
||||||
|
0x74,
|
||||||
|
0x64,
|
||||||
|
0x2e,
|
||||||
|
0x34,
|
||||||
|
0x35,
|
||||||
|
0x30,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0e,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x6d,
|
||||||
|
0x61,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xc2,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0a,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x5f,
|
||||||
|
0x47,
|
||||||
|
0x4f,
|
||||||
|
0x4f,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x45,
|
||||||
|
0x5f,
|
||||||
|
0x63,
|
||||||
|
0x70,
|
||||||
|
0x70,
|
||||||
|
0x5f,
|
||||||
|
0x73,
|
||||||
|
0x74,
|
||||||
|
0x79,
|
||||||
|
0x6c,
|
||||||
|
0x65,
|
||||||
|
0x5f,
|
||||||
|
0x6c,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x65,
|
||||||
|
0x5f,
|
||||||
|
0x64,
|
||||||
|
0x69,
|
||||||
|
0x72,
|
||||||
|
0x65,
|
||||||
|
0x63,
|
||||||
|
0x74,
|
||||||
|
0x69,
|
||||||
|
0x76,
|
||||||
|
0x65,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x08,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x5f,
|
||||||
|
0x47,
|
||||||
|
0x4f,
|
||||||
|
0x4f,
|
||||||
|
0x47,
|
||||||
|
0x4c,
|
||||||
|
0x45,
|
||||||
|
0x5f,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x63,
|
||||||
|
0x6c,
|
||||||
|
0x75,
|
||||||
|
0x64,
|
||||||
|
0x65,
|
||||||
|
0x5f,
|
||||||
|
0x64,
|
||||||
|
0x69,
|
||||||
|
0x72,
|
||||||
|
0x65,
|
||||||
|
0x63,
|
||||||
|
0x74,
|
||||||
|
0x69,
|
||||||
|
0x76,
|
||||||
|
0x65,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x6d,
|
||||||
|
0x61,
|
||||||
|
0x69,
|
||||||
|
0x6e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x6f,
|
||||||
|
0x75,
|
||||||
|
0x74,
|
||||||
|
0x5f,
|
||||||
|
0x63,
|
||||||
|
0x6f,
|
||||||
|
0x6c,
|
||||||
|
0x6f,
|
||||||
|
0x72,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x73,
|
||||||
|
0x63,
|
||||||
|
0x65,
|
||||||
|
0x6e,
|
||||||
|
0x65,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x76,
|
||||||
|
0x5f,
|
||||||
|
0x75,
|
||||||
|
0x76,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x21,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x22,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x47,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x13,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x21,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x16,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x17,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x08,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3b,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x08,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x19,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x0a,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x1b,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0a,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0c,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3b,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0c,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x17,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x20,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3b,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x10,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x36,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xf8,
|
||||||
|
0x00,
|
||||||
|
0x02,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3d,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0b,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0d,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3d,
|
||||||
|
0x00,
|
||||||
|
0x04,
|
||||||
|
0x00,
|
||||||
|
0x0f,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x12,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x11,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x57,
|
||||||
|
0x00,
|
||||||
|
0x05,
|
||||||
|
0x00,
|
||||||
|
0x07,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x13,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x0e,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x12,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x3e,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x13,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xfd,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x38,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00};
|
||||||
|
static const size_t kupscale_frag_spv_size = 628;
|
||||||
175
source/core/rendering/shader_backend.hpp
Normal file
175
source/core/rendering/shader_backend.hpp
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Rendering {
|
||||||
|
|
||||||
|
/** @brief Identificador del shader de post-procesado activo */
|
||||||
|
enum class ShaderType { POSTFX,
|
||||||
|
CRTPI };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parámetros de intensidad de los efectos PostFX
|
||||||
|
* Definido a nivel de namespace para facilitar el uso desde subclases y screen.cpp
|
||||||
|
*/
|
||||||
|
struct PostFXParams {
|
||||||
|
float vignette = 0.0F; // Intensidad de la viñeta
|
||||||
|
float scanlines = 0.0F; // Intensidad de las scanlines
|
||||||
|
float chroma = 0.0F; // Aberración cromática
|
||||||
|
float mask = 0.0F; // Máscara de fósforo RGB
|
||||||
|
float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full)
|
||||||
|
float curvature = 0.0F; // Curvatura barrel CRT
|
||||||
|
float bleeding = 0.0F; // Sangrado de color NTSC
|
||||||
|
float flicker = 0.0F; // Parpadeo de fósforo CRT ~50 Hz
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
|
||||||
|
* Diferente al PostFX: usa pesos gaussianos por distancia subpixel y bloom.
|
||||||
|
*/
|
||||||
|
struct CrtPiParams {
|
||||||
|
float scanline_weight{6.0F}; // Ajuste gaussiano (mayor = scanlines más estrechas)
|
||||||
|
float scanline_gap_brightness{0.12F}; // Brillo mínimo en las ranuras entre scanlines
|
||||||
|
float bloom_factor{3.5F}; // Factor de brillo para zonas iluminadas
|
||||||
|
float input_gamma{2.4F}; // Gamma de entrada (linealización)
|
||||||
|
float output_gamma{2.2F}; // Gamma de salida (codificación)
|
||||||
|
float mask_brightness{0.80F}; // Sub-píxeles tenues en la máscara de fósforo
|
||||||
|
float curvature_x{0.05F}; // Distorsión barrel eje X
|
||||||
|
float curvature_y{0.10F}; // Distorsión barrel eje Y
|
||||||
|
int mask_type{2}; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||||
|
bool enable_scanlines{true}; // Activar efecto de scanlines
|
||||||
|
bool enable_multisample{true}; // Antialiasing analítico de scanlines
|
||||||
|
bool enable_gamma{true}; // Corrección gamma
|
||||||
|
bool enable_curvature{false}; // Distorsión barrel CRT
|
||||||
|
bool enable_sharper{false}; // Submuestreo más nítido (modo SHARPER)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Interfaz abstracta para backends de renderizado con shaders
|
||||||
|
*
|
||||||
|
* Esta interfaz define el contrato que todos los backends de shaders
|
||||||
|
* deben cumplir (OpenGL, Metal, Vulkan, etc.)
|
||||||
|
*/
|
||||||
|
class ShaderBackend {
|
||||||
|
public:
|
||||||
|
virtual ~ShaderBackend() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Inicializa el backend de shaders
|
||||||
|
* @param window Ventana SDL
|
||||||
|
* @param texture Textura de backbuffer a la que aplicar shaders
|
||||||
|
* @param vertex_source Código fuente del vertex shader
|
||||||
|
* @param fragment_source Código fuente del fragment shader
|
||||||
|
* @return true si la inicialización fue exitosa
|
||||||
|
*/
|
||||||
|
virtual auto init(SDL_Window* window,
|
||||||
|
SDL_Texture* texture,
|
||||||
|
const std::string& vertex_source,
|
||||||
|
const std::string& fragment_source) -> bool = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renderiza la textura con los shaders aplicados
|
||||||
|
*/
|
||||||
|
virtual void render() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el tamaño de la textura como parámetro del shader
|
||||||
|
* @param width Ancho de la textura
|
||||||
|
* @param height Alto de la textura
|
||||||
|
*/
|
||||||
|
virtual void setTextureSize(float width, float height) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Limpia y libera recursos del backend
|
||||||
|
*/
|
||||||
|
virtual void cleanup() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sube píxeles ARGB8888 desde la CPU al backend de shaders
|
||||||
|
* Usado por SDL3GPUShader para evitar pasar por SDL_Texture
|
||||||
|
*/
|
||||||
|
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece los parámetros de intensidad de los efectos PostFX
|
||||||
|
* @param p Struct con todos los parámetros PostFX
|
||||||
|
*/
|
||||||
|
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa o desactiva VSync en el swapchain del GPU device
|
||||||
|
*/
|
||||||
|
virtual void setVSync(bool /*vsync*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa o desactiva el escalado entero (integer scale)
|
||||||
|
*/
|
||||||
|
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el factor de supersampling (1 = off, 3 = 3× SS)
|
||||||
|
* Con factor > 1, la textura GPU se crea a game×factor resolución y
|
||||||
|
* las scanlines se hornean en CPU (uploadPixels). El sampler usa LINEAR.
|
||||||
|
*/
|
||||||
|
virtual void setOversample(int /*factor*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Activa/desactiva interpolación LINEAR en el paso de upscale (SS).
|
||||||
|
* Por defecto NEAREST (false). Solo tiene efecto con supersampling activo.
|
||||||
|
*/
|
||||||
|
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||||
|
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Selecciona el algoritmo de downscale tras el PostFX (SS activo).
|
||||||
|
* 0 = bilinear legacy (comportamiento actual, sin textura intermedia),
|
||||||
|
* 1 = Lanczos2 (ventana 2, ~25 muestras), 2 = Lanczos3 (ventana 3, ~49 muestras).
|
||||||
|
*/
|
||||||
|
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||||
|
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Devuelve las dimensiones de la textura de supersampling.
|
||||||
|
* @return Par (ancho, alto) en píxeles; (0, 0) si SS está desactivado.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Verifica si el backend está usando aceleración por hardware
|
||||||
|
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Nombre del driver GPU activo (p.ej. "vulkan", "metal", "direct3d12")
|
||||||
|
* @return Cadena vacía si no disponible
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece el driver GPU preferido antes de init().
|
||||||
|
* Vacío = selección automática de SDL. Implementado en SDL3GPUShader.
|
||||||
|
*/
|
||||||
|
virtual void setPreferredDriver(const std::string& /*driver*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Selecciona el shader de post-procesado activo (POSTFX o CRTPI).
|
||||||
|
* Debe llamarse antes de render(). No recrea pipelines.
|
||||||
|
*/
|
||||||
|
virtual void setActiveShader(ShaderType /*type*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Establece los parámetros del shader CRT-Pi.
|
||||||
|
*/
|
||||||
|
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Devuelve el shader de post-procesado activo.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Rendering
|
||||||
@@ -114,12 +114,29 @@ Director::Director(int argc, const char *argv[]) {
|
|||||||
#endif
|
#endif
|
||||||
initInput();
|
initInput();
|
||||||
|
|
||||||
// Precarga todos los recursos en memoria (texturas, sonidos, música, ...).
|
// Orden importante: Screen + initShaders ANTES de Resource::init.
|
||||||
// Vivirán durante toda la vida de la app; las escenas leen handles compartidos.
|
// Si `Resource::init` se ejecuta primero, carga ~100 texturas vía
|
||||||
// Debe ir antes de crear Screen porque Screen usa Text (propiedad de Resource).
|
// `SDL_CreateTexture` que dejan el SDL_Renderer con el swapchain en un
|
||||||
|
// estado que hace crashear al driver Vulkan cuando después `initShaders`
|
||||||
|
// intenta reclamar la ventana para el dispositivo SDL3 GPU.
|
||||||
|
//
|
||||||
|
// Por eso el constructor de Screen NO carga notificationText desde
|
||||||
|
// Resource; se enlaza después vía `screen->initNotifications()`.
|
||||||
|
screen = new Screen(window, renderer, asset, options);
|
||||||
|
|
||||||
|
#ifndef NO_SHADERS
|
||||||
|
if (options->videoGpuAcceleration) {
|
||||||
|
screen->initShaders();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Ahora sí, precarga todos los recursos en memoria (texturas, sonidos,
|
||||||
|
// música, ...). Vivirán durante toda la vida de la app.
|
||||||
Resource::init(renderer, asset, input);
|
Resource::init(renderer, asset, input);
|
||||||
|
|
||||||
screen = new Screen(window, renderer, asset, options);
|
// Completa el enlazado de Screen con recursos que necesitan Resource
|
||||||
|
// inicializado (actualmente sólo el Text de las notificaciones).
|
||||||
|
screen->initNotifications();
|
||||||
|
|
||||||
activeSection = ActiveSection::None;
|
activeSection = ActiveSection::None;
|
||||||
}
|
}
|
||||||
@@ -183,6 +200,9 @@ void Director::initInput() {
|
|||||||
input->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
|
input->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
|
||||||
input->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
|
input->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
|
||||||
input->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
|
input->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
|
||||||
|
input->bindKey(input_toggle_gpu, SDL_SCANCODE_F9);
|
||||||
|
input->bindKey(input_toggle_shader, SDL_SCANCODE_F10);
|
||||||
|
input->bindKey(input_toggle_shader_type, SDL_SCANCODE_F11);
|
||||||
|
|
||||||
// Mando - Movimiento del jugador
|
// Mando - Movimiento del jugador
|
||||||
input->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
input->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
||||||
@@ -250,6 +270,10 @@ bool Director::initSDL() {
|
|||||||
}
|
}
|
||||||
success = false;
|
success = false;
|
||||||
} else {
|
} else {
|
||||||
|
// Modo de blending por defecto (consistente con CCAE):
|
||||||
|
// permite alpha blending para fades y notificaciones.
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
|
|
||||||
// Activa vsync si es necesario
|
// Activa vsync si es necesario
|
||||||
if (options->vSync) {
|
if (options->vSync) {
|
||||||
SDL_SetRenderVSync(renderer, 1);
|
SDL_SetRenderVSync(renderer, 1);
|
||||||
@@ -626,6 +650,12 @@ bool Director::saveConfigFile() {
|
|||||||
file << "borderWidth=" + std::to_string(options->borderWidth) + "\n";
|
file << "borderWidth=" + std::to_string(options->borderWidth) + "\n";
|
||||||
file << "borderHeight=" + std::to_string(options->borderHeight) + "\n";
|
file << "borderHeight=" + std::to_string(options->borderHeight) + "\n";
|
||||||
|
|
||||||
|
// Opciones de GPU / shaders (post-procesado)
|
||||||
|
file << "videoGpuAcceleration=" + boolToString(options->videoGpuAcceleration) + "\n";
|
||||||
|
file << "videoGpuPreferredDriver=" + options->videoGpuPreferredDriver + "\n";
|
||||||
|
file << "videoShaderEnabled=" + boolToString(options->videoShaderEnabled) + "\n";
|
||||||
|
file << "videoShaderType=" + std::to_string(options->videoShaderType) + "\n";
|
||||||
|
|
||||||
// Otras opciones del programa
|
// Otras opciones del programa
|
||||||
file << "\n## OTHER OPTIONS\n";
|
file << "\n## OTHER OPTIONS\n";
|
||||||
file << "language=" + std::to_string(options->language) + "\n";
|
file << "language=" + std::to_string(options->language) + "\n";
|
||||||
@@ -835,6 +865,26 @@ bool Director::setOptions(options_t *options, std::string var, std::string value
|
|||||||
options->borderHeight = std::stoi(value);
|
options->borderHeight = std::stoi(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Opciones de GPU / shaders
|
||||||
|
else if (var == "videoGpuAcceleration") {
|
||||||
|
options->videoGpuAcceleration = stringToBool(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (var == "videoGpuPreferredDriver") {
|
||||||
|
options->videoGpuPreferredDriver = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (var == "videoShaderEnabled") {
|
||||||
|
options->videoShaderEnabled = stringToBool(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (var == "videoShaderType") {
|
||||||
|
options->videoShaderType = std::stoi(value);
|
||||||
|
if (options->videoShaderType < 0 || options->videoShaderType > 1) {
|
||||||
|
options->videoShaderType = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Opciones varias
|
// Opciones varias
|
||||||
else if (var == "language") {
|
else if (var == "language") {
|
||||||
options->language = std::stoi(value);
|
options->language = std::stoi(value);
|
||||||
|
|||||||
14726
source/external/fkyaml_node.hpp
vendored
Normal file
14726
source/external/fkyaml_node.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2530,6 +2530,18 @@ void Game::checkGameInput() {
|
|||||||
screen->incWindowZoom();
|
screen->incWindowZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_gpu, REPEAT_FALSE)) {
|
||||||
|
screen->toggleGpuAcceleration();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader, REPEAT_FALSE)) {
|
||||||
|
screen->toggleShaderEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader_type, REPEAT_FALSE)) {
|
||||||
|
screen->toggleActiveShader();
|
||||||
|
}
|
||||||
|
|
||||||
// Modo Demo activo
|
// Modo Demo activo
|
||||||
if (demo.enabled) {
|
if (demo.enabled) {
|
||||||
const int index = 0;
|
const int index = 0;
|
||||||
|
|||||||
@@ -232,6 +232,18 @@ void Instructions::checkInput() {
|
|||||||
screen->incWindowZoom();
|
screen->incWindowZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_gpu, REPEAT_FALSE)) {
|
||||||
|
screen->toggleGpuAcceleration();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader, REPEAT_FALSE)) {
|
||||||
|
screen->toggleShaderEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader_type, REPEAT_FALSE)) {
|
||||||
|
screen->toggleActiveShader();
|
||||||
|
}
|
||||||
|
|
||||||
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
||||||
if (mode == m_auto) {
|
if (mode == m_auto) {
|
||||||
finished = true;
|
finished = true;
|
||||||
|
|||||||
@@ -208,6 +208,18 @@ void Intro::checkInput() {
|
|||||||
screen->incWindowZoom();
|
screen->incWindowZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_gpu, REPEAT_FALSE)) {
|
||||||
|
screen->toggleGpuAcceleration();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader, REPEAT_FALSE)) {
|
||||||
|
screen->toggleShaderEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader_type, REPEAT_FALSE)) {
|
||||||
|
screen->toggleActiveShader();
|
||||||
|
}
|
||||||
|
|
||||||
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
||||||
JA_StopMusic();
|
JA_StopMusic();
|
||||||
section->name = SECTION_PROG_TITLE;
|
section->name = SECTION_PROG_TITLE;
|
||||||
|
|||||||
@@ -91,6 +91,18 @@ void Logo::checkInput() {
|
|||||||
screen->incWindowZoom();
|
screen->incWindowZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_gpu, REPEAT_FALSE)) {
|
||||||
|
screen->toggleGpuAcceleration();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader, REPEAT_FALSE)) {
|
||||||
|
screen->toggleShaderEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader_type, REPEAT_FALSE)) {
|
||||||
|
screen->toggleActiveShader();
|
||||||
|
}
|
||||||
|
|
||||||
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
else if (input->checkInput(input_pause, REPEAT_FALSE) || input->checkInput(input_accept, REPEAT_FALSE) || input->checkInput(input_fire_left, REPEAT_FALSE) || input->checkInput(input_fire_center, REPEAT_FALSE) || input->checkInput(input_fire_right, REPEAT_FALSE)) {
|
||||||
section->name = SECTION_PROG_TITLE;
|
section->name = SECTION_PROG_TITLE;
|
||||||
section->subsection = SUBSECTION_TITLE_1;
|
section->subsection = SUBSECTION_TITLE_1;
|
||||||
|
|||||||
@@ -236,10 +236,14 @@ void Title::update() {
|
|||||||
if (coffeeBitmap->hasFinished() && crisisBitmap->hasFinished()) {
|
if (coffeeBitmap->hasFinished() && crisisBitmap->hasFinished()) {
|
||||||
section->subsection = SUBSECTION_TITLE_2;
|
section->subsection = SUBSECTION_TITLE_2;
|
||||||
|
|
||||||
// Pantallazo blanco
|
// Pantallazo blanco: pintar sobre el gameCanvas y dejar
|
||||||
|
// que Screen::blit() presente por la ruta activa (GPU o
|
||||||
|
// SDL_Renderer). Un `SDL_RenderPresent(renderer)` directe
|
||||||
|
// crasheja quan el SDL3 GPU ha reclamat la ventana.
|
||||||
|
screen->start();
|
||||||
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
|
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
|
||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
SDL_RenderPresent(renderer);
|
screen->blit();
|
||||||
|
|
||||||
// Reproduce el efecto sonoro
|
// Reproduce el efecto sonoro
|
||||||
JA_PlaySound(crashSound);
|
JA_PlaySound(crashSound);
|
||||||
@@ -650,6 +654,18 @@ void Title::checkInput() {
|
|||||||
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
|
else if (input->checkInput(input_window_inc_size, REPEAT_FALSE)) {
|
||||||
screen->incWindowZoom();
|
screen->incWindowZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_gpu, REPEAT_FALSE)) {
|
||||||
|
screen->toggleGpuAcceleration();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader, REPEAT_FALSE)) {
|
||||||
|
screen->toggleShaderEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (input->checkInput(input_toggle_shader_type, REPEAT_FALSE)) {
|
||||||
|
screen->toggleActiveShader();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualiza el tileado de fondo
|
// Actualiza el tileado de fondo
|
||||||
|
|||||||
@@ -109,6 +109,13 @@ struct options_t {
|
|||||||
palette_e palette; // Paleta de colores a usar en el juego
|
palette_e palette; // Paleta de colores a usar en el juego
|
||||||
bool console; // Indica si ha de mostrar información por la consola de texto
|
bool console; // Indica si ha de mostrar información por la consola de texto
|
||||||
|
|
||||||
|
// GPU / shaders (persistent; also toggled via F9/F10/F11 hotkeys).
|
||||||
|
// YAML migration pending — for now they live as flat keys in config.txt.
|
||||||
|
bool videoGpuAcceleration = true; // Intenta usar SDL3 GPU si está disponible
|
||||||
|
std::string videoGpuPreferredDriver; // Driver preferido para SDL3 GPU (vacío = auto)
|
||||||
|
bool videoShaderEnabled = false; // Activa el post-procesado (PostFX / CrtPi)
|
||||||
|
int videoShaderType = 0; // 0 = POSTFX, 1 = CRTPI
|
||||||
|
|
||||||
op_screen_t screen; // Opciones relativas a la clase screen
|
op_screen_t screen; // Opciones relativas a la clase screen
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
57
tools/shaders/compile_shader.cmake
Normal file
57
tools/shaders/compile_shader.cmake
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# compile_shader.cmake
|
||||||
|
# Compila un shader GLSL a header C++ embebible con datos SPIR-V.
|
||||||
|
# Funciona en Windows, Linux y macOS sin bash ni herramientas Unix.
|
||||||
|
#
|
||||||
|
# Variables requeridas (pasar con -D al invocar cmake -P):
|
||||||
|
# GLSLC - ruta al ejecutable glslc
|
||||||
|
# SRC - archivo fuente GLSL
|
||||||
|
# OUT_H - archivo header de salida
|
||||||
|
# VAR - nombre base de la variable C++ (e.g. "postfx_vert_spv")
|
||||||
|
# STAGE - (opcional) stage explícito para glslc (e.g. "fragment")
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
get_filename_component(SRC_NAME "${SRC}" NAME_WE)
|
||||||
|
get_filename_component(OUT_DIR "${OUT_H}" DIRECTORY)
|
||||||
|
set(OUT_SPV "${OUT_DIR}/${SRC_NAME}.spv")
|
||||||
|
|
||||||
|
# Compilar GLSL → SPIR-V
|
||||||
|
if(DEFINED STAGE AND NOT STAGE STREQUAL "")
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${GLSLC}" "-fshader-stage=${STAGE}" "${SRC}" -o "${OUT_SPV}"
|
||||||
|
RESULT_VARIABLE RESULT
|
||||||
|
ERROR_VARIABLE ERROR_MSG
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${GLSLC}" "${SRC}" -o "${OUT_SPV}"
|
||||||
|
RESULT_VARIABLE RESULT
|
||||||
|
ERROR_VARIABLE ERROR_MSG
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT RESULT EQUAL 0)
|
||||||
|
message(FATAL_ERROR "glslc falló compilando ${SRC}:\n${ERROR_MSG}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Leer binario SPIR-V como cadena hexadecimal
|
||||||
|
file(READ "${OUT_SPV}" SPV_HEX HEX)
|
||||||
|
file(REMOVE "${OUT_SPV}")
|
||||||
|
|
||||||
|
string(LENGTH "${SPV_HEX}" HEX_LEN)
|
||||||
|
math(EXPR BYTE_COUNT "${HEX_LEN} / 2")
|
||||||
|
|
||||||
|
# Convertir a array C++ con formato " 0xXX,\n" por byte
|
||||||
|
string(REGEX REPLACE "([0-9a-f][0-9a-f])" " 0x\\1,\n" SPV_BYTES "${SPV_HEX}")
|
||||||
|
# Eliminar la última coma y sustituir por el "}" de cierre
|
||||||
|
string(REGEX REPLACE ",\n$" "}" SPV_BYTES "${SPV_BYTES}")
|
||||||
|
|
||||||
|
# Escribir el header
|
||||||
|
file(WRITE "${OUT_H}"
|
||||||
|
"#pragma once\n"
|
||||||
|
"#include <cstddef>\n"
|
||||||
|
"#include <cstdint>\n"
|
||||||
|
"static const uint8_t k${VAR}[] = {\n"
|
||||||
|
"${SPV_BYTES};\n"
|
||||||
|
"static const size_t k${VAR}_size = ${BYTE_COUNT};\n"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user