Compare commits
26 Commits
0e49f35d3b
...
2026.05.17
| Author | SHA1 | Date | |
|---|---|---|---|
| f3371c33b0 | |||
| 441a2122bc | |||
| 502127283b | |||
| 0fb9be931f | |||
| 1d46c4f3bd | |||
| dbef9c558d | |||
| 64586f4a86 | |||
| ab20e98663 | |||
| ae6e72e0d9 | |||
| bd5683d498 | |||
| 11eec8f222 | |||
| 2c1673d2dd | |||
| e58b7d36fb | |||
| 0d14e10de5 | |||
| a39cd45bf1 | |||
| ab858aacb8 | |||
| 0647eceab7 | |||
| a903343385 | |||
| 17341f923d | |||
| fcd2718794 | |||
| c9d16959d0 | |||
| ce662609f3 | |||
| 583d901162 | |||
| 61d35374e0 | |||
| 194c8187f9 | |||
| 490d5c923b |
+71
-43
@@ -8,72 +8,100 @@ Checks:
|
||||
- -bugprone-integer-division
|
||||
- -bugprone-easily-swappable-parameters
|
||||
- -bugprone-narrowing-conversions
|
||||
- -modernize-avoid-c-arrays,-warnings-as-errors
|
||||
- -modernize-avoid-c-arrays
|
||||
|
||||
WarningsAsErrors: '*'
|
||||
# Solo headers del propio código fuente (external/ y spv/ tienen su propio .clang-tidy dummy)
|
||||
HeaderFilterRegex: 'source/.*'
|
||||
# Headers nostres (excloem source/external/ que conté dependències de tercers no editables)
|
||||
HeaderFilterRegex: 'source/(core|game|utils)/'
|
||||
FormatStyle: file
|
||||
|
||||
CheckOptions:
|
||||
# bugprone-empty-catch: aceptar catches vacíos marcados con @INTENTIONAL en un comentario
|
||||
- { key: bugprone-empty-catch.IgnoreCatchWithKeywords, value: '@INTENTIONAL' }
|
||||
|
||||
# Variables locales en snake_case
|
||||
# =====================================================================
|
||||
# CONSTANTES → UPPER_CASE (compile-time y runtime, en cualquier scope)
|
||||
# =====================================================================
|
||||
# Todo lo que sea const o constexpr se identifica visualmente en UPPER_CASE,
|
||||
# sin importar si es global, local, miembro o static.
|
||||
|
||||
# constexpr en cualquier scope (globales y locales)
|
||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE }
|
||||
|
||||
# Constantes globales (const no-constexpr)
|
||||
- { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE }
|
||||
|
||||
# Constantes locales (const en función)
|
||||
- { key: readability-identifier-naming.LocalConstantCase, value: UPPER_CASE }
|
||||
|
||||
# Static const a nivel de archivo/namespace
|
||||
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
|
||||
|
||||
# Miembros static const/constexpr de clase (p.ej. static constexpr int MAX = 100;)
|
||||
- { key: readability-identifier-naming.ClassConstantCase, value: UPPER_CASE }
|
||||
|
||||
# Miembros const no-static de clase (p.ej. const int limit;)
|
||||
- { key: readability-identifier-naming.ConstantMemberCase, value: UPPER_CASE }
|
||||
|
||||
# Valores de enums
|
||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||
|
||||
# NOTA: Los parámetros const NO se tratan como constantes aquí.
|
||||
# Un parámetro sigue siendo un parámetro aunque sea const → hereda ParameterCase.
|
||||
|
||||
# =====================================================================
|
||||
# VARIABLES NO-CONST
|
||||
# =====================================================================
|
||||
|
||||
# Variables locales
|
||||
- { key: readability-identifier-naming.VariableCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.LocalVariableCase, value: lower_case }
|
||||
|
||||
# Miembros privados en snake_case con sufijo _
|
||||
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.PrivateMemberSuffix, value: _ }
|
||||
# Parámetros de función
|
||||
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
|
||||
|
||||
# Miembros protegidos en snake_case con sufijo _
|
||||
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
|
||||
|
||||
# Miembros públicos en snake_case (sin sufijo)
|
||||
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
|
||||
|
||||
# Namespaces en CamelCase
|
||||
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
|
||||
|
||||
# Variables estáticas privadas como miembros privados
|
||||
# Variables estáticas no-const (static locales, static file-scope,
|
||||
# y static members no-const de clase como el instance_ de un Singleton).
|
||||
# Sufijo _ para marcar que tienen storage estático.
|
||||
- { key: readability-identifier-naming.StaticVariableCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.StaticVariableSuffix, value: _ }
|
||||
|
||||
# Constantes estáticas sin sufijo
|
||||
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
|
||||
# =====================================================================
|
||||
# MIEMBROS DE CLASE NO-CONST
|
||||
# =====================================================================
|
||||
# Privados: snake_case con sufijo _
|
||||
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.PrivateMemberSuffix, value: _ }
|
||||
|
||||
# Constantes globales en UPPER_CASE
|
||||
- { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE }
|
||||
# Protegidos: snake_case con sufijo _
|
||||
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
|
||||
|
||||
# Variables constexpr globales en UPPER_CASE
|
||||
- { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE }
|
||||
# Públicos: snake_case sin sufijo
|
||||
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
|
||||
|
||||
# Constantes locales en UPPER_CASE
|
||||
- { key: readability-identifier-naming.LocalConstantCase, value: UPPER_CASE }
|
||||
|
||||
# Constexpr miembros en UPPER_CASE (sin sufijo)
|
||||
- { key: readability-identifier-naming.ConstexprMemberCase, value: UPPER_CASE }
|
||||
|
||||
# Constexpr miembros privados/protegidos con sufijo _
|
||||
- { key: readability-identifier-naming.ConstexprMethodCase, value: UPPER_CASE }
|
||||
|
||||
# Clases, structs y enums en CamelCase
|
||||
# =====================================================================
|
||||
# TIPOS
|
||||
# =====================================================================
|
||||
- { key: readability-identifier-naming.ClassCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.StructCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.EnumCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.UnionCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.TypeAliasCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.TypedefCase, value: CamelCase }
|
||||
- { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase }
|
||||
|
||||
# Valores de enums en UPPER_CASE
|
||||
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
|
||||
# Namespaces
|
||||
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
|
||||
|
||||
# Métodos en camelBack (sin sufijos)
|
||||
# =====================================================================
|
||||
# FUNCIONES Y MÉTODOS (incluyendo constexpr)
|
||||
# =====================================================================
|
||||
# Un método/función constexpr es un invocable, no una constante → camelBack.
|
||||
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
|
||||
- { key: readability-identifier-naming.ConstexprFunctionCase, value: camelBack }
|
||||
- { key: readability-identifier-naming.MethodCase, value: camelBack }
|
||||
- { key: readability-identifier-naming.PrivateMethodCase, value: camelBack }
|
||||
- { key: readability-identifier-naming.ProtectedMethodCase, value: camelBack }
|
||||
- { key: readability-identifier-naming.PublicMethodCase, value: camelBack }
|
||||
|
||||
# Funciones en camelBack
|
||||
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
|
||||
|
||||
# Parámetros en lower_case
|
||||
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
|
||||
- { key: readability-identifier-naming.ConstexprMethodCase, value: camelBack }
|
||||
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
# Pre-commit hook: aplica clang-format als fitxers C++ staged abans del commit.
|
||||
# - Només toca fitxers staged dins source/ (exclou source/external/).
|
||||
# - Avorta el commit si hi ha canvis NO staged en aquests fitxers (per no incloure'ls sense voler).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if ! command -v clang-format >/dev/null 2>&1; then
|
||||
echo "pre-commit: clang-format no trobat — saltant format check" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mapfile -t STAGED < <(git diff --cached --name-only --diff-filter=ACMR \
|
||||
| grep -E '^source/.*\.(cpp|hpp|h)$' \
|
||||
| grep -vE '^source/external/' || true)
|
||||
|
||||
if [ ${#STAGED[@]} -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
UNSTAGED_DIRTY=()
|
||||
for f in "${STAGED[@]}"; do
|
||||
if ! git diff --quiet -- "$f"; then
|
||||
UNSTAGED_DIRTY+=("$f")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#UNSTAGED_DIRTY[@]} -gt 0 ]; then
|
||||
echo "pre-commit: aquests fitxers tenen canvis NO staged i estan al commit." >&2
|
||||
echo " Fes 'git add' o 'git stash' abans de continuar:" >&2
|
||||
printf ' %s\n' "${UNSTAGED_DIRTY[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
clang-format -i "${STAGED[@]}"
|
||||
git add -- "${STAGED[@]}"
|
||||
|
||||
# --- clang-tidy només sobre els fitxers staged ---
|
||||
if ! command -v clang-tidy >/dev/null 2>&1; then
|
||||
echo "pre-commit: clang-tidy no trobat — saltant tidy" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||
BUILD_DIR="$REPO_ROOT/build"
|
||||
|
||||
if [ ! -f "$BUILD_DIR/compile_commands.json" ]; then
|
||||
echo "pre-commit: generant compile_commands.json (build dir buit)..." >&2
|
||||
cmake -S "$REPO_ROOT" -B "$BUILD_DIR" >/dev/null
|
||||
fi
|
||||
|
||||
echo "pre-commit: clang-tidy sobre ${#STAGED[@]} fitxer(s)..." >&2
|
||||
if ! clang-tidy -p "$BUILD_DIR" --quiet "${STAGED[@]}"; then
|
||||
echo "pre-commit: clang-tidy ha trobat errors — commit avortat" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- cppcheck només sobre els .cpp staged ---
|
||||
if ! command -v cppcheck >/dev/null 2>&1; then
|
||||
echo "pre-commit: cppcheck no trobat — saltant cppcheck" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CPP_STAGED=()
|
||||
for f in "${STAGED[@]}"; do
|
||||
[[ "$f" == *.cpp ]] && CPP_STAGED+=("$f")
|
||||
done
|
||||
|
||||
if [ ${#CPP_STAGED[@]} -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "pre-commit: cppcheck sobre ${#CPP_STAGED[@]} fitxer(s)..." >&2
|
||||
if ! cppcheck \
|
||||
--enable=warning,style,performance,portability \
|
||||
--std=c++20 \
|
||||
--language=c++ \
|
||||
--inline-suppr \
|
||||
--suppress=missingIncludeSystem \
|
||||
--suppress=toomanyconfigs \
|
||||
--suppress='*:*source/external/*' \
|
||||
--suppress='*:*source/core/rendering/sdl3gpu/spv/*' \
|
||||
--suppress=normalCheckLevelMaxBranches \
|
||||
-D_DEBUG \
|
||||
-DLINUX_BUILD \
|
||||
--quiet \
|
||||
--error-exitcode=1 \
|
||||
-I "$REPO_ROOT/source" \
|
||||
"${CPP_STAGED[@]}"; then
|
||||
echo "pre-commit: cppcheck ha trobat errors — commit avortat" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -4,6 +4,22 @@ Historial de canvis i novetats de Coffee Crisis Arcade Edition.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-17
|
||||
|
||||
- **Migració del shader PostFX a la versió d'aee_arcade**: filtratge analític de scanlines amb `smoothstep` (paràmetres `scan_dark_ratio`, `scan_dark_floor`, `scan_edge_soft` exposats per preset). Eliminat tot el pipeline de supersampling 3× + Lanczos downscale (shaders, render targets, opcions de menu, claus de YAML, keybinding F0, llengües). 1 sol render pass vs 3 anteriors.
|
||||
- **Chroma `min`/`max` amb mostreig bilinear subpíxel**: substitueix l'antic `chroma_strength`. Amb min==max queda estàtic; amb min!=max pulsa sinusoidalment entre els dos valors. Adéu al *tic-tac* del NEAREST amb offsets fraccionals.
|
||||
- **MSL extret a headers separats** sota `source/core/rendering/sdl3gpu/msl/` (postfx_vert, postfx_frag, crtpi_frag).
|
||||
- **Service Menu: VSync ara funciona de veritat**. `Screen::applySettings()` propaga al backend GPU (abans només tocava `SDL_SetRenderVSync` i amb backend GPU el present real el fa el swapchain — el canvi era invisible). Separat el setter aplicat al hardware del setter de preferència persistent: `Resource::beginLoad` ja no clobera `Options::video.vsync` durant el preload, així la preferència de l'usuari es preserva entre llançaments.
|
||||
- **Demo player anti-crash**: guarda amb modul al lector (`demo_.data.at(index).at(demo_.index % size)`) i trigger del fade `>=` (era `==`, fragil a frame skips per canvis de preset des del service menu) — evita `vector::_M_range_check` quan el frame de trigger 1800 saltava i `demo_.index` arribava a 2000.
|
||||
- **Reinici real des del service menu** via `execv`.
|
||||
- **Opció de preset de paràmetres al service menu** (`params_file` → `params_preset`: classic / arcade / red). Presets `red` i `classic` arreglats.
|
||||
- **Pack inclou ara la carpeta `config/`** (assets, params, stages, pools, formations).
|
||||
- **Bullet "fire up" reposicionada** (-1 px x, -2 px y) i pintada sobre el jugador en lloc de sota.
|
||||
- **Defaults**: zoom de finestra a 3 (era 2).
|
||||
- Neteja de codi: passades exhaustives de clang-tidy i cppcheck (de 105 warnings a 0).
|
||||
|
||||
---
|
||||
|
||||
## 2026-04-03
|
||||
|
||||
- **Nova intro cinematogràfica**: les tarjetes s'llancen des dels costats de la pantalla amb zoom, rotació i rebot, simulant tirar cartes sobre una mesa. Les anteriors ixen despedides girant quan arriba la següent. Sombra amb efecte de perspectiva 2D→3D.
|
||||
|
||||
+50
-50
@@ -158,56 +158,41 @@ endif()
|
||||
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(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
|
||||
set(HEADERS_DIR "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv")
|
||||
|
||||
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_SOURCES "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}" "${SHADER_CRTPI_SRC}" "${SHADER_UPSCALE_SRC}" "${SHADER_DOWNSCALE_SRC}")
|
||||
set(ALL_SHADER_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
|
||||
set(ALL_SHADER_HEADERS
|
||||
"${HEADERS_DIR}/postfx_vert_spv.h"
|
||||
"${HEADERS_DIR}/postfx_frag_spv.h"
|
||||
"${HEADERS_DIR}/crtpi_frag_spv.h"
|
||||
)
|
||||
set(ALL_SHADER_SOURCES
|
||||
"${SHADERS_DIR}/postfx.vert"
|
||||
"${SHADERS_DIR}/postfx.frag"
|
||||
"${SHADERS_DIR}/crtpi_frag.glsl"
|
||||
)
|
||||
|
||||
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_command(
|
||||
OUTPUT ${ALL_SHADER_HEADERS}
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-D GLSLC=${GLSLC_EXE}
|
||||
-D SHADERS_DIR=${SHADERS_DIR}
|
||||
-D HEADERS_DIR=${HEADERS_DIR}
|
||||
-P ${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.cmake
|
||||
DEPENDS ${ALL_SHADER_SOURCES}
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Compilando shaders SPIR-V..."
|
||||
)
|
||||
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}")
|
||||
foreach(HDR ${ALL_SHADER_HEADERS})
|
||||
if(NOT EXISTS "${HDR}")
|
||||
message(FATAL_ERROR
|
||||
"glslc no encontrado y header SPIR-V no existe: ${_h}\n"
|
||||
"glslc no encontrado y header SPIR-V no existe: ${HDR}\n"
|
||||
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
|
||||
" choco install vulkan-sdk (Windows)\n"
|
||||
" O genera los headers manualmente: tools/shaders/compile_spirv.sh"
|
||||
" choco install vulkan-sdk (Windows)"
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
@@ -295,13 +280,6 @@ elseif(UNIX AND NOT APPLE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
||||
endif()
|
||||
|
||||
# Especificar la ubicación del ejecutable
|
||||
if(EMSCRIPTEN)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
else()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
# --- 5. STATIC ANALYSIS TARGETS ---
|
||||
|
||||
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
|
||||
@@ -390,6 +368,8 @@ if(CPPCHECK_EXE)
|
||||
--inline-suppr
|
||||
--suppress=missingIncludeSystem
|
||||
--suppress=toomanyconfigs
|
||||
--suppress=*:*/source/external/*
|
||||
--suppress=*:*/source/core/rendering/sdl3gpu/spv/*
|
||||
-D_DEBUG
|
||||
-DLINUX_BUILD
|
||||
--quiet
|
||||
@@ -417,7 +397,7 @@ if(NOT EMSCRIPTEN)
|
||||
|
||||
# Regeneració automàtica de resources.pack en cada build si canvia data/.
|
||||
file(GLOB_RECURSE DATA_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/data/*")
|
||||
set(RESOURCE_PACK "${CMAKE_SOURCE_DIR}/resources.pack")
|
||||
set(RESOURCE_PACK "${CMAKE_BINARY_DIR}/resources.pack")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${RESOURCE_PACK}
|
||||
@@ -432,4 +412,24 @@ if(NOT EMSCRIPTEN)
|
||||
|
||||
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
|
||||
add_dependencies(${PROJECT_NAME} resource_pack)
|
||||
|
||||
# --- CÒPIA DE gamecontrollerdb.txt AL COSTAT DEL BINARI ---
|
||||
# SDL_AddGamepadMappingsFromFile només llegeix del filesystem real (no del
|
||||
# pack), així que el fitxer ha de viure al directori del binari. Es copia
|
||||
# només si existeix per no fallar la build d'algú que encara no ha fet
|
||||
# `make controllerdb`.
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt")
|
||||
set(CONTROLLER_DB "${CMAKE_BINARY_DIR}/gamecontrollerdb.txt")
|
||||
add_custom_command(
|
||||
OUTPUT ${CONTROLLER_DB}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt"
|
||||
"${CONTROLLER_DB}"
|
||||
DEPENDS "${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt"
|
||||
COMMENT "Copiant gamecontrollerdb.txt → build/"
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(controller_db ALL DEPENDS ${CONTROLLER_DB})
|
||||
add_dependencies(${PROJECT_NAME} controller_db)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -2,16 +2,13 @@
|
||||
# DIRECTORIES
|
||||
# ==============================================================================
|
||||
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
|
||||
BUILDDIR := build
|
||||
|
||||
# ==============================================================================
|
||||
# TARGET NAMES
|
||||
# ==============================================================================
|
||||
TARGET_NAME := coffee_crisis_arcade_edition
|
||||
ifeq ($(OS),Windows_NT)
|
||||
TARGET_FILE := $(DIR_ROOT)$(TARGET_NAME).exe
|
||||
else
|
||||
TARGET_FILE := $(DIR_ROOT)$(TARGET_NAME)
|
||||
endif
|
||||
TARGET_FILE := $(BUILDDIR)/$(TARGET_NAME)
|
||||
APP_NAME := Coffee Crisis Arcade Edition
|
||||
DIST_DIR := dist
|
||||
RELEASE_FOLDER := dist/_tmp
|
||||
@@ -23,7 +20,7 @@ RESOURCE_FILE := release/windows/coffee.res
|
||||
# ==============================================================================
|
||||
SHADER_CMAKE := $(DIR_ROOT)tools/shaders/compile_spirv.cmake
|
||||
SHADERS_DIR := $(DIR_ROOT)data/shaders
|
||||
HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu
|
||||
HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu/spv
|
||||
ifeq ($(OS),Windows_NT)
|
||||
GLSLC := $(shell where glslc 2>NUL)
|
||||
else
|
||||
@@ -61,7 +58,7 @@ endif
|
||||
# WINDOWS-SPECIFIC VARIABLES
|
||||
# ==============================================================================
|
||||
ifeq ($(OS),Windows_NT)
|
||||
WIN_TARGET_FILE := $(DIR_ROOT)$(APP_NAME)
|
||||
WIN_TARGET_FILE := $(BUILDDIR)/$(APP_NAME)
|
||||
WIN_RELEASE_FILE := $(RELEASE_FOLDER)/$(APP_NAME)
|
||||
# Escapa apòstrofs per a PowerShell (duplica ' → ''). Sense això, APP_NAMEs
|
||||
# com "JailDoctor's Dilemma" trencarien el parsing de -Destination '...'.
|
||||
@@ -96,36 +93,66 @@ else
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# CMAKE GENERATOR (Windows needs explicit MinGW Makefiles generator)
|
||||
# CMAKE GENERATOR (usa Ninja si está disponible; si no, MinGW Makefiles en
|
||||
# Windows / generador por defecto en Linux/macOS). Ninja paraleliza mejor.
|
||||
# ==============================================================================
|
||||
ifeq ($(OS),Windows_NT)
|
||||
CMAKE_GEN := -G "MinGW Makefiles"
|
||||
# Dins MSYS2/Git Bash/MinGW, $(shell ...) usa sh.exe i "NUL" NO és
|
||||
# dispositiu — un redirect "2>NUL" crearia un fitxer literal anomenat
|
||||
# NUL al cwd. Detectem MSYSTEM per usar /dev/null en aquests entorns.
|
||||
ifneq ($(MSYSTEM),)
|
||||
NULDEV := /dev/null
|
||||
else
|
||||
NULDEV := NUL
|
||||
endif
|
||||
HAS_NINJA := $(shell ninja --version 2>$(NULDEV))
|
||||
ifneq ($(HAS_NINJA),)
|
||||
CMAKE_GEN := -G "Ninja"
|
||||
else
|
||||
CMAKE_GEN := -G "MinGW Makefiles"
|
||||
endif
|
||||
else
|
||||
CMAKE_GEN :=
|
||||
HAS_NINJA := $(shell ninja --version 2>/dev/null)
|
||||
ifneq ($(HAS_NINJA),)
|
||||
CMAKE_GEN := -G "Ninja"
|
||||
else
|
||||
CMAKE_GEN :=
|
||||
endif
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# COMPILACIÓN CON CMAKE
|
||||
# ==============================================================================
|
||||
all:
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build
|
||||
|
||||
debug:
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Debug
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Debug -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build
|
||||
|
||||
run: all
|
||||
@./$(TARGET_FILE)
|
||||
|
||||
run-debug: debug
|
||||
@./$(TARGET_FILE)
|
||||
|
||||
clean:
|
||||
@rm -rf $(BUILDDIR)
|
||||
|
||||
rebuild: clean all
|
||||
|
||||
# ==============================================================================
|
||||
# RELEASE AUTOMÁTICO (detecta SO)
|
||||
# ==============================================================================
|
||||
release:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@"$(MAKE)" _windows_release
|
||||
@"$(MAKE)" _windows-release
|
||||
else
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
@$(MAKE) _macos_release
|
||||
@$(MAKE) _macos-release
|
||||
else
|
||||
@$(MAKE) _linux_release
|
||||
@$(MAKE) _linux-release
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -135,12 +162,12 @@ endif
|
||||
pack:
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build --target pack_resources
|
||||
@./build/pack_resources data resources.pack
|
||||
@./build/pack_resources data build/resources.pack
|
||||
|
||||
# ==============================================================================
|
||||
# REGLAS PARA COMPILACIÓN DE SHADERS (multiplataforma via cmake)
|
||||
# ==============================================================================
|
||||
compile_shaders:
|
||||
compile-shaders:
|
||||
ifdef GLSLC
|
||||
@cmake -D GLSLC=$(GLSLC) -D SHADERS_DIR=$(SHADERS_DIR) -D HEADERS_DIR=$(HEADERS_DIR) -P $(SHADER_CMAKE)
|
||||
else
|
||||
@@ -150,13 +177,13 @@ endif
|
||||
# ==============================================================================
|
||||
# COMPILACIÓN PARA WINDOWS (RELEASE)
|
||||
# ==============================================================================
|
||||
_windows_release:
|
||||
_windows-release:
|
||||
@$(MAKE) pack
|
||||
@echo off
|
||||
@echo Creando release para Windows - Version: $(VERSION)
|
||||
|
||||
# Compila con cmake
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build
|
||||
|
||||
# Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER'
|
||||
@@ -166,13 +193,13 @@ _windows_release:
|
||||
|
||||
# Copia la carpeta 'config' y el archivo 'resources.pack'
|
||||
@powershell -Command "Copy-Item -Path 'config' -Destination '$(RELEASE_FOLDER)' -recurse -Force"
|
||||
@powershell -Command "Copy-Item -Path 'resources.pack' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item -Path 'build/resources.pack' -Destination '$(RELEASE_FOLDER)'"
|
||||
|
||||
# Copia los ficheros que estan en la raíz del proyecto
|
||||
@powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item -Path '$(TARGET_FILE)' -Destination '$(WIN_RELEASE_FILE_PS).exe'"
|
||||
@powershell -Command "Copy-Item -Path '$(TARGET_FILE).exe' -Destination '$(WIN_RELEASE_FILE_PS).exe'"
|
||||
strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
|
||||
|
||||
# Crea el fichero .zip
|
||||
@@ -186,7 +213,7 @@ _windows_release:
|
||||
# ==============================================================================
|
||||
# COMPILACIÓN PARA MACOS (RELEASE)
|
||||
# ==============================================================================
|
||||
_macos_release:
|
||||
_macos-release:
|
||||
@$(MAKE) pack
|
||||
@echo "Creando release para macOS - Version: $(VERSION)"
|
||||
|
||||
@@ -227,8 +254,7 @@ _macos_release:
|
||||
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||
|
||||
# Copia carpetas y ficheros
|
||||
cp -R config "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||
cp build/resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
|
||||
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
|
||||
@@ -251,6 +277,7 @@ _macos_release:
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \
|
||||
-DMACOS_BUNDLE=ON \
|
||||
-DGIT_HASH=$(GIT_HASH) \
|
||||
&& cmake --build build/intel; then \
|
||||
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"; \
|
||||
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"; \
|
||||
@@ -284,7 +311,7 @@ _macos_release:
|
||||
@echo "============================================"
|
||||
@echo " Compilando version Apple Silicon (arm64)"
|
||||
@echo "============================================"
|
||||
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON
|
||||
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build/arm
|
||||
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
|
||||
|
||||
@@ -317,12 +344,12 @@ _macos_release:
|
||||
# ==============================================================================
|
||||
# COMPILACIÓN PARA LINUX (RELEASE)
|
||||
# ==============================================================================
|
||||
_linux_release:
|
||||
_linux-release:
|
||||
@$(MAKE) pack
|
||||
@echo "Creando release para Linux - Version: $(VERSION)"
|
||||
|
||||
# Compila con cmake
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build
|
||||
|
||||
# Elimina carpeta temporal previa y la recrea (crea dist/ si no existe)
|
||||
@@ -330,8 +357,7 @@ _linux_release:
|
||||
$(MKDIR) "$(RELEASE_FOLDER)"
|
||||
|
||||
# Copia ficheros
|
||||
cp -R config "$(RELEASE_FOLDER)"
|
||||
cp resources.pack "$(RELEASE_FOLDER)"
|
||||
cp build/resources.pack "$(RELEASE_FOLDER)"
|
||||
cp LICENSE "$(RELEASE_FOLDER)"
|
||||
cp README.md "$(RELEASE_FOLDER)"
|
||||
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
|
||||
@@ -367,45 +393,52 @@ wasm:
|
||||
ssh maverick 'cd /home/sergio/gitea/web_jailgames && ./deploy.sh'
|
||||
@echo "Deployed to maverick"
|
||||
|
||||
# Versió Debug del build wasm: build local sense deploy. Sortida a dist/wasm_debug/.
|
||||
wasm_debug:
|
||||
# Versió Debug del build wasm: build local sense deploy. Sortida a dist/wasm-debug/.
|
||||
wasm-debug:
|
||||
@echo "Compilando WebAssembly Debug - Version: $(VERSION) ($(GIT_HASH))"
|
||||
docker run --rm \
|
||||
--user $(shell id -u):$(shell id -g) \
|
||||
-v $(DIR_ROOT):/src \
|
||||
-w /src \
|
||||
emscripten/emsdk:latest \
|
||||
bash -c "emcmake cmake -S . -B build/wasm_debug -DCMAKE_BUILD_TYPE=Debug -DGIT_HASH=$(GIT_HASH) && cmake --build build/wasm_debug"
|
||||
$(MKDIR) "$(DIST_DIR)/wasm_debug"
|
||||
cp build/wasm_debug/$(TARGET_NAME).html $(DIST_DIR)/wasm_debug/
|
||||
cp build/wasm_debug/$(TARGET_NAME).js $(DIST_DIR)/wasm_debug/
|
||||
cp build/wasm_debug/$(TARGET_NAME).wasm $(DIST_DIR)/wasm_debug/
|
||||
cp build/wasm_debug/$(TARGET_NAME).data $(DIST_DIR)/wasm_debug/
|
||||
@echo "Output: $(DIST_DIR)/wasm_debug/"
|
||||
bash -c "emcmake cmake -S . -B build/wasm-debug -DCMAKE_BUILD_TYPE=Debug -DGIT_HASH=$(GIT_HASH) && cmake --build build/wasm-debug"
|
||||
$(MKDIR) "$(DIST_DIR)/wasm-debug"
|
||||
cp build/wasm-debug/$(TARGET_NAME).html $(DIST_DIR)/wasm-debug/
|
||||
cp build/wasm-debug/$(TARGET_NAME).js $(DIST_DIR)/wasm-debug/
|
||||
cp build/wasm-debug/$(TARGET_NAME).wasm $(DIST_DIR)/wasm-debug/
|
||||
cp build/wasm-debug/$(TARGET_NAME).data $(DIST_DIR)/wasm-debug/
|
||||
@echo "Output: $(DIST_DIR)/wasm-debug/"
|
||||
|
||||
# ==============================================================================
|
||||
# CODE QUALITY (delegados a cmake)
|
||||
# ==============================================================================
|
||||
format:
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build --target format
|
||||
|
||||
format-check:
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build --target format-check
|
||||
|
||||
tidy:
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build --target tidy
|
||||
|
||||
tidy-fix:
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build --target tidy-fix
|
||||
|
||||
cppcheck:
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
|
||||
@cmake --build build --target cppcheck
|
||||
|
||||
# ==============================================================================
|
||||
# GIT HOOKS
|
||||
# ==============================================================================
|
||||
hooks-install:
|
||||
@git config core.hooksPath .githooks
|
||||
@echo "Git hooks activats: $(shell pwd)/.githooks"
|
||||
|
||||
# ==============================================================================
|
||||
# DESCARGA DE GAMECONTROLLERDB
|
||||
# ==============================================================================
|
||||
@@ -418,7 +451,7 @@ controllerdb:
|
||||
# ==============================================================================
|
||||
# REGLAS ESPECIALES
|
||||
# ==============================================================================
|
||||
show_version:
|
||||
show-version:
|
||||
@echo "Version actual: $(VERSION)"
|
||||
|
||||
help:
|
||||
@@ -429,14 +462,18 @@ help:
|
||||
@echo " make - Compilar con cmake (Release)"
|
||||
@echo " make debug - Compilar con cmake (Debug)"
|
||||
@echo ""
|
||||
@echo " Ejecucion:"
|
||||
@echo " make run - Compilar (Release) y ejecutar"
|
||||
@echo " make run-debug - Compilar (Debug) y ejecutar"
|
||||
@echo ""
|
||||
@echo " Release:"
|
||||
@echo " make release - Crear release (detecta SO automaticamente)"
|
||||
@echo " make wasm - Crear build WebAssembly (requiere Docker) en dist/wasm"
|
||||
@echo " make wasm_debug - Build WebAssembly Debug local (sin deploy)"
|
||||
@echo " make wasm-debug - Build WebAssembly Debug local (sin deploy)"
|
||||
@echo ""
|
||||
@echo " Herramientas:"
|
||||
@echo " make compile_shaders - Compilar shaders SPIR-V"
|
||||
@echo " make pack - Empaquetar recursos a resources.pack"
|
||||
@echo " make compile-shaders - Compilar shaders SPIR-V"
|
||||
@echo " make pack - Empaquetar recursos a $(BUILDDIR)/resources.pack"
|
||||
@echo ""
|
||||
@echo " Calidad de codigo:"
|
||||
@echo " make format - Formatear codigo con clang-format"
|
||||
@@ -446,7 +483,10 @@ help:
|
||||
@echo " make cppcheck - Analisis estatico con cppcheck"
|
||||
@echo ""
|
||||
@echo " Otros:"
|
||||
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
||||
@echo " make clean - Borrar carpeta $(BUILDDIR)/"
|
||||
@echo " make rebuild - clean + all"
|
||||
@echo " make show-version - Mostrar version actual ($(VERSION))"
|
||||
@echo " make hooks-install - Activar git hooks del proyecto"
|
||||
@echo " make help - Mostrar esta ayuda"
|
||||
|
||||
.PHONY: all debug release _windows_release _macos_release _linux_release wasm wasm_debug pack compile_shaders format format-check tidy tidy-fix cppcheck controllerdb show_version help
|
||||
.PHONY: all debug run run-debug clean rebuild release _windows-release _macos-release _linux-release wasm wasm-debug pack compile-shaders format format-check tidy tidy-fix cppcheck controllerdb hooks-install show-version help
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,14 +10,15 @@ DATA|${SYSTEM_FOLDER}/postfx.yaml|optional,absolute
|
||||
DATA|${SYSTEM_FOLDER}/crtpi.yaml|optional,absolute
|
||||
DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute
|
||||
|
||||
# Archivos de configuración del juego
|
||||
DATA|${PREFIX}/config/formations.txt
|
||||
DATA|${PREFIX}/config/gamecontrollerdb.txt
|
||||
DATA|${PREFIX}/config/param_320x240.txt
|
||||
DATA|${PREFIX}/config/param_320x256.txt
|
||||
DATA|${PREFIX}/config/param_red.txt
|
||||
DATA|${PREFIX}/config/pools.txt
|
||||
DATA|${PREFIX}/config/stages.txt
|
||||
# Dades de gameplay (viatgen dins el pack)
|
||||
DATA|/data/config/gameplay/formations.txt
|
||||
DATA|/data/config/gameplay/pools.txt
|
||||
DATA|/data/config/gameplay/stages.txt
|
||||
|
||||
# Presets de paràmetres (viatgen dins el pack)
|
||||
DATA|/data/config/presets/classic.txt
|
||||
DATA|/data/config/presets/arcade.txt
|
||||
DATA|/data/config/presets/red.txt
|
||||
|
||||
# Archivos con los datos de la demo
|
||||
DEMODATA|/data/demo/demo1.bin
|
||||
@@ -22,7 +22,7 @@ fade.venetian_size 12 # Tamaño de las bandas para el efecto v
|
||||
|
||||
# --- SCOREBOARD ---
|
||||
scoreboard.rect.x 0 # Posición X del marcador
|
||||
scoreboard.rect.y 216 # Posición Y del marcador
|
||||
scoreboard.rect.y 200 # Posición Y del marcador
|
||||
scoreboard.rect.w 320 # Ancho del marcador
|
||||
scoreboard.rect.h 40 # Alto del marcador
|
||||
scoreboard.separator_autocolor true # ¿El separador usa color automático?
|
||||
@@ -99,8 +99,7 @@ intro.text_distance_from_bottom 48 # Posicion del texto
|
||||
debug.color 00FFFF # Color para elementos de depuración
|
||||
|
||||
# --- RESOURCE ---
|
||||
resource.color FFFFFF # Color de recurso 1
|
||||
resource.color FFFFFF # Color de recurso 2
|
||||
resource.color FFFFFF # Color de recursos
|
||||
|
||||
# --- TABE ---
|
||||
tabe.min_spawn_time 2.0f # Tiempo mínimo en minutos para que aparezca el Tabe
|
||||
@@ -2,7 +2,6 @@
|
||||
# Formato: PARAMETRO VALOR
|
||||
|
||||
# --- GAME ---
|
||||
game.item_size 20 # Tamaño de los items del juego (en píxeles)
|
||||
game.item_text_outline_color FFB8B8F0 # Color del outline del texto de los items (RGBA hex) - Rojo claro
|
||||
game.width 320 # Ancho de la resolución nativa del juego (en píxeles)
|
||||
game.height 256 # Alto de la resolución nativa del juego (en píxeles)
|
||||
@@ -12,8 +11,6 @@ game.play_area.rect.w 320 # Ancho de la zona jugable
|
||||
game.play_area.rect.h 216 # Alto de la zona jugable
|
||||
game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
|
||||
game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
|
||||
game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo
|
||||
game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop
|
||||
|
||||
# --- FADE ---
|
||||
fade.color 5C1F1F # Color hexadecimal para el efecto de fundido - Rojo oscuro
|
||||
@@ -39,24 +36,24 @@ scoreboard.text_color2 FFE6E6 # Color secundario del texto del marca
|
||||
scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos)
|
||||
|
||||
# --- TITLE ---
|
||||
title.press_start_position 180 # Posición Y del texto "Press Start"
|
||||
title.title_duration 800 # Duración de la pantalla de título (frames)
|
||||
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
|
||||
title.title_c_c_position 80 # Posición Y del título principal
|
||||
title.bg_color 8B4A3A # Color de fondo en la sección titulo - Marrón rojizo
|
||||
title.press_start_position 180 # Posición Y del texto "Press Start"
|
||||
title.title_duration 14 # Duración de la pantalla de título (segundos)
|
||||
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
|
||||
title.title_c_c_position 80 # Posición Y del título principal
|
||||
title.bg_color 8B4A3A # Color de fondo en la sección titulo - Marrón rojizo
|
||||
|
||||
# --- BACKGROUND ---
|
||||
background.attenuate_color FF4A3A40 # Color de atenuación del fondo (RGBA hexadecimal) - Blanco rosado
|
||||
|
||||
# --- BALLOONS ---
|
||||
balloon.settings[0].vel 2.75f # Velocidad inicial del globo 1
|
||||
balloon.settings[0].grav 0.09f # Gravedad aplicada al globo 1
|
||||
balloon.settings[1].vel 3.70f # Velocidad inicial del globo 2
|
||||
balloon.settings[1].grav 0.10f # Gravedad aplicada al globo 2
|
||||
balloon.settings[2].vel 4.70f # Velocidad inicial del globo 3
|
||||
balloon.settings[2].grav 0.10f # Gravedad aplicada al globo 3
|
||||
balloon.settings[3].vel 5.45f # Velocidad inicial del globo 4
|
||||
balloon.settings[3].grav 0.10f # Gravedad aplicada al globo 4
|
||||
# --- BALLOONS --- (deltaTime en segundos: vel en pixels/s, grav en pixels/s²)
|
||||
balloon.settings[0].vel 165.0f # Velocidad inicial del globo 1 (pixels/s)
|
||||
balloon.settings[0].grav 320.0f # Gravedad aplicada al globo 1 (pixels/s²)
|
||||
balloon.settings[1].vel 222.0f # Velocidad inicial del globo 2 (pixels/s)
|
||||
balloon.settings[1].grav 360.0f # Gravedad aplicada al globo 2 (pixels/s²)
|
||||
balloon.settings[2].vel 282.0f # Velocidad inicial del globo 3 (pixels/s)
|
||||
balloon.settings[2].grav 360.0f # Gravedad aplicada al globo 3 (pixels/s²)
|
||||
balloon.settings[3].vel 327.0f # Velocidad inicial del globo 4 (pixels/s)
|
||||
balloon.settings[3].grav 360.0f # Gravedad aplicada al globo 4 (pixels/s²)
|
||||
|
||||
balloon.color[0] orange # Color de creación del globo normal
|
||||
balloon.color[1] red # Color del globo normal
|
||||
@@ -83,7 +83,6 @@
|
||||
"[SERVICE_MENU] SHADER": "Shader",
|
||||
"[SERVICE_MENU] SHADER_DISABLED": "Desactivat",
|
||||
"[SERVICE_MENU] SHADER_PRESET": "Preset",
|
||||
"[SERVICE_MENU] SUPERSAMPLING": "Supermostreig",
|
||||
"[SERVICE_MENU] VSYNC": "Sincronisme vertical",
|
||||
"[SERVICE_MENU] INTEGER_SCALE": "Escalat sencer",
|
||||
"[SERVICE_MENU] FILTER": "Filtre",
|
||||
@@ -105,6 +104,10 @@
|
||||
"[SERVICE_MENU] EASY": "Facil",
|
||||
"[SERVICE_MENU] NORMAL": "Normal",
|
||||
"[SERVICE_MENU] HARD": "Dificil",
|
||||
"[SERVICE_MENU] GAME_PRESET": "Perfil",
|
||||
"[SERVICE_MENU] PRESET_CLASSIC": "Classic",
|
||||
"[SERVICE_MENU] PRESET_ARCADE": "Arcade",
|
||||
"[SERVICE_MENU] PRESET_RED": "Roig",
|
||||
"[SERVICE_MENU] NEED_RESTART_MESSAGE": "Reiniciar per aplicar canvis",
|
||||
"[SERVICE_MENU] ENABLE_SHUTDOWN": "Permetre apagar el sistema",
|
||||
"[SERVICE_MENU] CONTROLS": "Controls",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"[SERVICE_MENU] SHADER": "Shader",
|
||||
"[SERVICE_MENU] SHADER_DISABLED": "Disabled",
|
||||
"[SERVICE_MENU] SHADER_PRESET": "Preset",
|
||||
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
|
||||
"[SERVICE_MENU] VSYNC": "V-Sync",
|
||||
"[SERVICE_MENU] INTEGER_SCALE": "Integer Scale",
|
||||
"[SERVICE_MENU] FILTER": "Filter",
|
||||
@@ -104,6 +103,10 @@
|
||||
"[SERVICE_MENU] EASY": "Easy",
|
||||
"[SERVICE_MENU] NORMAL": "Normal",
|
||||
"[SERVICE_MENU] HARD": "Hard",
|
||||
"[SERVICE_MENU] GAME_PRESET": "Preset",
|
||||
"[SERVICE_MENU] PRESET_CLASSIC": "Classic",
|
||||
"[SERVICE_MENU] PRESET_ARCADE": "Arcade",
|
||||
"[SERVICE_MENU] PRESET_RED": "Red",
|
||||
"[SERVICE_MENU] NEED_RESTART_MESSAGE": "Restart to apply changes",
|
||||
"[SERVICE_MENU] ENABLE_SHUTDOWN": "Allow system shutdown",
|
||||
"[SERVICE_MENU] CONTROLS": "Controls",
|
||||
|
||||
@@ -82,7 +82,6 @@
|
||||
"[SERVICE_MENU] SHADER": "Shader",
|
||||
"[SERVICE_MENU] SHADER_DISABLED": "Desactivado",
|
||||
"[SERVICE_MENU] SHADER_PRESET": "Preset",
|
||||
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
|
||||
"[SERVICE_MENU] VSYNC": "Sincronismo vertical",
|
||||
"[SERVICE_MENU] INTEGER_SCALE": "Escalado proporcional",
|
||||
"[SERVICE_MENU] FILTER": "Filtro",
|
||||
@@ -104,6 +103,10 @@
|
||||
"[SERVICE_MENU] EASY": "Facil",
|
||||
"[SERVICE_MENU] NORMAL": "Normal",
|
||||
"[SERVICE_MENU] HARD": "Dificil",
|
||||
"[SERVICE_MENU] GAME_PRESET": "Perfil",
|
||||
"[SERVICE_MENU] PRESET_CLASSIC": "Clasico",
|
||||
"[SERVICE_MENU] PRESET_ARCADE": "Arcade",
|
||||
"[SERVICE_MENU] PRESET_RED": "Rojo",
|
||||
"[SERVICE_MENU] NEED_RESTART_MESSAGE": "Reiniciar para aplicar cambios",
|
||||
"[SERVICE_MENU] ENABLE_SHUTDOWN": "Permitir apagar el sistema",
|
||||
"[SERVICE_MENU] CONTROLS": "Controles",
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
+47
-18
@@ -6,7 +6,9 @@
|
||||
// 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).
|
||||
// (16 floats = 4 × vec4 = 64 bytes, std140/scalar layout).
|
||||
// IMPORTANT: Qualsevol canvi ací cal replicar-lo a mà a
|
||||
// source/core/rendering/sdl3gpu/msl/postfx_frag.msl.h (no hi ha generador).
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
@@ -15,7 +17,7 @@ layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||
|
||||
layout(set = 3, binding = 0) uniform PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float chroma_min; // intensitat mínima de l'aberració cromàtica
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
@@ -24,10 +26,28 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
|
||||
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)
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz
|
||||
float chroma_max; // intensitat màxima; si == chroma_min → chroma estàtic
|
||||
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
|
||||
float scan_dark_ratio; // fracció de subfila fosca per fila lògica (1/3 ≈ 0.333)
|
||||
float scan_dark_floor; // multiplicador de brillantor de la subfila fosca
|
||||
float scan_edge_soft; // 0 = step dur; 1 = suavitzat d'1 píxel físic (estil crtpi)
|
||||
float pad3; // padding per tancar a 64 bytes (4 × vec4)
|
||||
} u;
|
||||
|
||||
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
|
||||
// NEAREST quan l'offset de chroma és subpíxel: sense interpolar, l'offset
|
||||
// arrodonia entre 1 i 2 píxels i el drift temporal feia un parpelleig discret.
|
||||
float sampleBilinearX(vec2 uv_target, int channel) {
|
||||
vec2 tex_size = vec2(textureSize(scene, 0));
|
||||
float px = uv_target.x * tex_size.x - 0.5;
|
||||
float p_floor = floor(px);
|
||||
float f = px - p_floor;
|
||||
vec4 c0 = texture(scene, vec2((p_floor + 0.5) / tex_size.x, uv_target.y));
|
||||
vec4 c1 = texture(scene, vec2((p_floor + 1.5) / tex_size.x, uv_target.y));
|
||||
return mix(c0[channel], c1[channel], f);
|
||||
}
|
||||
|
||||
// YCbCr helpers for NTSC bleeding
|
||||
vec3 rgb_to_ycc(vec3 rgb) {
|
||||
return vec3(
|
||||
@@ -69,11 +89,11 @@ void main() {
|
||||
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).
|
||||
// step = 1 pixel lógico de juego en UV.
|
||||
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
|
||||
float step = 1.0 / 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);
|
||||
@@ -85,10 +105,14 @@ void main() {
|
||||
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;
|
||||
// Aberración cromática — intensitat varia entre chroma_min i chroma_max amb
|
||||
// una sinusoidal (si min == max, queda estàtica). Mostreig bilinear horitzontal
|
||||
// per evitar el "tic-tac" del NEAREST sampler quan l'offset és subpíxel.
|
||||
if (u.chroma_min > 0.0 || u.chroma_max > 0.0) {
|
||||
float ca = mix(u.chroma_min, u.chroma_max, 0.5 + 0.5 * sin(u.time * 7.3)) * 0.005;
|
||||
colour.r = sampleBilinearX(uv + vec2(ca, 0.0), 0);
|
||||
colour.b = sampleBilinearX(uv - vec2(ca, 0.0), 2);
|
||||
}
|
||||
|
||||
// Corrección gamma (linealizar antes de scanlines, codificar después)
|
||||
if (u.gamma_strength > 0.0) {
|
||||
@@ -96,15 +120,20 @@ void main() {
|
||||
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.
|
||||
// Scanlines — tècnica dels 3 subpíxels verticals per píxel lògic (aee/projecte_2026):
|
||||
// franja fosca ocupant `scan_dark_ratio` al final de cada fila lògica. La transició es
|
||||
// suavitza amb smoothstep d'ample ≈ 1 píxel físic (estil crtpi: filtratge analític
|
||||
// continu), controlat per `scan_edge_soft`. A 0 és equivalent al step dur antic.
|
||||
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);
|
||||
float ps = max(u.pixel_scale, 1.0);
|
||||
float sub = fract(uv.y * u.screen_height); // [0,1) dins la fila lògica
|
||||
float dark_center = 1.0 - u.scan_dark_ratio * 0.5; // centre de la franja fosca
|
||||
float d = abs(sub - dark_center);
|
||||
d = min(d, 1.0 - d); // wrap a la fila següent
|
||||
float half_width = u.scan_dark_ratio * 0.5;
|
||||
float softness = u.scan_edge_soft * 0.5 / ps; // mig píxel físic a cada costat
|
||||
float band = 1.0 - smoothstep(half_width - softness, half_width + softness, d);
|
||||
float scan = mix(1.0, u.scan_dark_floor, band);
|
||||
colour *= mix(1.0, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
+31
-30
@@ -8,6 +8,7 @@
|
||||
// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp).
|
||||
// clang-format off
|
||||
#undef STB_VORBIS_HEADER_ONLY
|
||||
// NOLINTNEXTLINE(bugprone-suspicious-include) — stb_vorbis és single-file: el TU principal inclou el .c per portar la implementació.
|
||||
#include "external/stb_vorbis.c"
|
||||
// stb_vorbis.c filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
|
||||
// perquè xocarien amb noms de paràmetres de plantilla en altres headers.
|
||||
@@ -43,15 +44,15 @@ Audio::Audio() { initSDLAudio(); }
|
||||
|
||||
// Destructor
|
||||
Audio::~Audio() {
|
||||
JA_Quit();
|
||||
Ja::quit();
|
||||
}
|
||||
|
||||
// Método principal
|
||||
void Audio::update() {
|
||||
JA_Update();
|
||||
Ja::update();
|
||||
|
||||
// Sincronizar estado: detectar cuando la música se para (ej. fade-out completado)
|
||||
if (instance && instance->music_.state == MusicState::PLAYING && JA_GetMusicState() != JA_MUSIC_PLAYING) {
|
||||
if (instance != nullptr && instance->music_.state == MusicState::PLAYING && Ja::getMusicState() != Ja::MusicState::PLAYING) {
|
||||
instance->music_.state = MusicState::STOPPED;
|
||||
}
|
||||
}
|
||||
@@ -65,18 +66,18 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa
|
||||
return;
|
||||
}
|
||||
|
||||
if (!music_enabled_) return;
|
||||
if (!music_enabled_) { return; }
|
||||
|
||||
auto* resource = AudioResource::getMusic(name);
|
||||
if (resource == nullptr) return;
|
||||
if (resource == nullptr) { return; }
|
||||
|
||||
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
|
||||
JA_CrossfadeMusic(resource, crossfade_ms, loop);
|
||||
Ja::crossfadeMusic(resource, crossfade_ms, loop);
|
||||
} else {
|
||||
if (music_.state == MusicState::PLAYING) {
|
||||
JA_StopMusic();
|
||||
Ja::stopMusic();
|
||||
}
|
||||
JA_PlayMusic(resource, loop);
|
||||
Ja::playMusic(resource, loop);
|
||||
}
|
||||
|
||||
music_.name = name;
|
||||
@@ -85,16 +86,16 @@ void Audio::playMusic(const std::string& name, const int loop, const int crossfa
|
||||
}
|
||||
|
||||
// Reproduce la música por puntero (con crossfade opcional)
|
||||
void Audio::playMusic(JA_Music_t* music, const int loop, const int crossfade_ms) {
|
||||
if (!music_enabled_ || music == nullptr) return;
|
||||
void Audio::playMusic(Ja::Music* music, const int loop, const int crossfade_ms) {
|
||||
if (!music_enabled_ || music == nullptr) { return; }
|
||||
|
||||
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
|
||||
JA_CrossfadeMusic(music, crossfade_ms, loop);
|
||||
Ja::crossfadeMusic(music, crossfade_ms, loop);
|
||||
} else {
|
||||
if (music_.state == MusicState::PLAYING) {
|
||||
JA_StopMusic();
|
||||
Ja::stopMusic();
|
||||
}
|
||||
JA_PlayMusic(music, loop);
|
||||
Ja::playMusic(music, loop);
|
||||
}
|
||||
|
||||
music_.name.clear(); // nom desconegut quan es passa per punter
|
||||
@@ -105,7 +106,7 @@ void Audio::playMusic(JA_Music_t* music, const int loop, const int crossfade_ms)
|
||||
// Pausa la música
|
||||
void Audio::pauseMusic() {
|
||||
if (music_enabled_ && music_.state == MusicState::PLAYING) {
|
||||
JA_PauseMusic();
|
||||
Ja::pauseMusic();
|
||||
music_.state = MusicState::PAUSED;
|
||||
}
|
||||
}
|
||||
@@ -113,7 +114,7 @@ void Audio::pauseMusic() {
|
||||
// Continua la música pausada
|
||||
void Audio::resumeMusic() {
|
||||
if (music_enabled_ && music_.state == MusicState::PAUSED) {
|
||||
JA_ResumeMusic();
|
||||
Ja::resumeMusic();
|
||||
music_.state = MusicState::PLAYING;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +122,7 @@ void Audio::resumeMusic() {
|
||||
// Detiene la música
|
||||
void Audio::stopMusic() {
|
||||
if (music_enabled_) {
|
||||
JA_StopMusic();
|
||||
Ja::stopMusic();
|
||||
music_.state = MusicState::STOPPED;
|
||||
}
|
||||
}
|
||||
@@ -129,42 +130,42 @@ void Audio::stopMusic() {
|
||||
// Reproduce un sonido por nombre
|
||||
void Audio::playSound(const std::string& name, Group group) const {
|
||||
if (sound_enabled_) {
|
||||
JA_PlaySound(AudioResource::getSound(name), 0, static_cast<int>(group));
|
||||
Ja::playSound(AudioResource::getSound(name), 0, static_cast<int>(group));
|
||||
}
|
||||
}
|
||||
|
||||
// Reproduce un sonido por puntero directo
|
||||
void Audio::playSound(JA_Sound_t* sound, Group group) const {
|
||||
void Audio::playSound(Ja::Sound* sound, Group group) const {
|
||||
if (sound_enabled_ && sound != nullptr) {
|
||||
JA_PlaySound(sound, 0, static_cast<int>(group));
|
||||
Ja::playSound(sound, 0, static_cast<int>(group));
|
||||
}
|
||||
}
|
||||
|
||||
// Detiene todos los sonidos
|
||||
void Audio::stopAllSounds() const {
|
||||
if (sound_enabled_) {
|
||||
JA_StopChannel(-1);
|
||||
Ja::stopChannel(-1);
|
||||
}
|
||||
}
|
||||
|
||||
// Realiza un fundido de salida de la música
|
||||
void Audio::fadeOutMusic(int milliseconds) const {
|
||||
if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) {
|
||||
JA_FadeOutMusic(milliseconds);
|
||||
Ja::fadeOutMusic(milliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
// Consulta directamente el estado real de la música en jailaudio
|
||||
auto Audio::getRealMusicState() -> MusicState {
|
||||
JA_Music_state ja_state = JA_GetMusicState();
|
||||
Ja::MusicState ja_state = Ja::getMusicState();
|
||||
switch (ja_state) {
|
||||
case JA_MUSIC_PLAYING:
|
||||
case Ja::MusicState::PLAYING:
|
||||
return MusicState::PLAYING;
|
||||
case JA_MUSIC_PAUSED:
|
||||
case Ja::MusicState::PAUSED:
|
||||
return MusicState::PAUSED;
|
||||
case JA_MUSIC_STOPPED:
|
||||
case JA_MUSIC_INVALID:
|
||||
case JA_MUSIC_DISABLED:
|
||||
case Ja::MusicState::STOPPED:
|
||||
case Ja::MusicState::INVALID:
|
||||
case Ja::MusicState::DISABLED:
|
||||
default:
|
||||
return MusicState::STOPPED;
|
||||
}
|
||||
@@ -175,7 +176,7 @@ void Audio::setSoundVolume(float sound_volume, Group group) const {
|
||||
if (sound_enabled_) {
|
||||
sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
|
||||
const float CONVERTED_VOLUME = sound_volume * Options::audio.volume;
|
||||
JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
|
||||
Ja::setSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +185,7 @@ void Audio::setMusicVolume(float music_volume) const {
|
||||
if (music_enabled_) {
|
||||
music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
|
||||
const float CONVERTED_VOLUME = music_volume * Options::audio.volume;
|
||||
JA_SetMusicVolume(CONVERTED_VOLUME);
|
||||
Ja::setMusicVolume(CONVERTED_VOLUME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +207,7 @@ void Audio::initSDLAudio() {
|
||||
if (!SDL_Init(SDL_INIT_AUDIO)) {
|
||||
std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n';
|
||||
} else {
|
||||
JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2);
|
||||
Ja::init(FREQUENCY, SDL_AUDIO_S16LE, 2);
|
||||
enable(Options::audio.enabled);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-12
@@ -1,8 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath> // Para std::lround
|
||||
#include <cstdint> // Para int8_t, uint8_t
|
||||
#include <string> // Para string
|
||||
#include <utility> // Para move
|
||||
|
||||
namespace Ja {
|
||||
struct Music;
|
||||
struct Sound;
|
||||
} // namespace Ja
|
||||
|
||||
// --- Clase Audio: gestor de audio (singleton) ---
|
||||
// Implementació canònica, byte-idèntica entre projectes.
|
||||
@@ -41,17 +46,17 @@ class Audio {
|
||||
static void update(); // Actualización del sistema de audio
|
||||
|
||||
// --- Control de música ---
|
||||
void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional)
|
||||
void playMusic(struct JA_Music_t* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional)
|
||||
void pauseMusic(); // Pausar reproducción de música
|
||||
void resumeMusic(); // Continua la música pausada
|
||||
void stopMusic(); // Detener completamente la música
|
||||
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
|
||||
void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional)
|
||||
void playMusic(Ja::Music* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional)
|
||||
void pauseMusic(); // Pausar reproducción de música
|
||||
void resumeMusic(); // Continua la música pausada
|
||||
void stopMusic(); // Detener completamente la música
|
||||
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
|
||||
|
||||
// --- Control de sonidos ---
|
||||
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre
|
||||
void playSound(struct JA_Sound_t* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
|
||||
void stopAllSounds() const; // Detener todos los sonidos
|
||||
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre
|
||||
void playSound(Ja::Sound* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
|
||||
void stopAllSounds() const; // Detener todos los sonidos
|
||||
|
||||
// --- Control de volumen (API interna: float 0.0..1.0) ---
|
||||
void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
|
||||
@@ -59,8 +64,8 @@ class Audio {
|
||||
|
||||
// --- Helpers de conversió per a la capa de presentació ---
|
||||
// UI (menús, notificacions) manega enters 0..100; internament viu float 0..1.
|
||||
static constexpr auto toPercent(float volume) -> int {
|
||||
return static_cast<int>(volume * 100.0F + 0.5F);
|
||||
static auto toPercent(float volume) -> int {
|
||||
return static_cast<int>(std::lround(volume * 100.0F));
|
||||
}
|
||||
static constexpr auto fromPercent(int percent) -> float {
|
||||
return static_cast<float>(percent) / 100.0F;
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#include "core/resources/resource.hpp"
|
||||
|
||||
namespace AudioResource {
|
||||
JA_Music_t* getMusic(const std::string& name) {
|
||||
auto getMusic(const std::string& name) -> Ja::Music* {
|
||||
return Resource::get()->getMusic(name);
|
||||
}
|
||||
|
||||
JA_Sound_t* getSound(const std::string& name) {
|
||||
auto getSound(const std::string& name) -> Ja::Sound* {
|
||||
return Resource::get()->getSound(name);
|
||||
}
|
||||
} // namespace AudioResource
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// --- Audio Resource Adapter ---
|
||||
// Aquest fitxer exposa una interfície comuna a Audio per obtenir JA_Music_t* /
|
||||
// JA_Sound_t* per nom. Cada projecte la implementa en audio_adapter.cpp
|
||||
// Aquest fitxer exposa una interfície comuna a Audio per obtenir Ja::Music* /
|
||||
// Ja::Sound* per nom. Cada projecte la implementa en audio_adapter.cpp
|
||||
// delegant al seu singleton de recursos (Resource::get(), Resource::Cache::get(),
|
||||
// etc.). Això permet que audio.hpp/audio.cpp siguin idèntics entre projectes.
|
||||
|
||||
#include <string> // Para string
|
||||
|
||||
struct JA_Music_t;
|
||||
struct JA_Sound_t;
|
||||
namespace Ja {
|
||||
struct Music;
|
||||
struct Sound;
|
||||
} // namespace Ja
|
||||
|
||||
namespace AudioResource {
|
||||
JA_Music_t* getMusic(const std::string& name);
|
||||
JA_Sound_t* getSound(const std::string& name);
|
||||
auto getMusic(const std::string& name) -> Ja::Music*;
|
||||
auto getSound(const std::string& name) -> Ja::Sound*;
|
||||
} // namespace AudioResource
|
||||
|
||||
+595
-575
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
#include "core/input/define_buttons.hpp"
|
||||
|
||||
#include <algorithm> // Para __all_of_fn, all_of
|
||||
#include <algorithm> // Para __all_of_fn, all_of, ranges::transform
|
||||
#include <iterator> // Para back_inserter
|
||||
#include <memory> // Para unique_ptr, allocator, shared_ptr, operator==, make_unique
|
||||
|
||||
#include "core/input/input.hpp" // Para Input
|
||||
#include "core/input/input_types.hpp" // Para InputAction
|
||||
#include "core/locale/lang.hpp" // Para getText
|
||||
#include "core/resources/resource.hpp" // Para Resource
|
||||
#include "game/options.hpp" // Para Gamepad
|
||||
@@ -16,10 +16,9 @@ DefineButtons::DefineButtons()
|
||||
: input_(Input::get()) {
|
||||
clearButtons();
|
||||
|
||||
auto gamepads = input_->getGamepads();
|
||||
for (const auto& gamepad : gamepads) {
|
||||
controller_names_.emplace_back(Input::getControllerName(gamepad));
|
||||
}
|
||||
const auto GAMEPADS = input_->getGamepads();
|
||||
controller_names_.reserve(GAMEPADS.size());
|
||||
std::ranges::transform(GAMEPADS, std::back_inserter(controller_names_), Input::getControllerName);
|
||||
|
||||
// Crear la ventana de mensaje
|
||||
WindowMessage::Config config(param.service_menu.window_message);
|
||||
|
||||
@@ -2,21 +2,19 @@
|
||||
|
||||
#include <algorithm> // Para __any_of_fn, any_of
|
||||
#include <functional> // Para function
|
||||
#include <iterator> // Para pair
|
||||
#include <string> // Para basic_string, operator+, allocator, char_traits, string, to_string
|
||||
#include <utility> // Para pair
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/input/input.hpp" // Para Input
|
||||
#include "core/input/input_types.hpp" // Para InputAction
|
||||
#include "core/locale/lang.hpp" // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/system/section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
|
||||
#include "game/options.hpp" // Para Video, video, Settings, settings, Audio, audio, Window, window
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
||||
#include "utils/utils.hpp" // Para boolToOnOff
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/input/input.hpp" // Para Input
|
||||
#include "core/locale/lang.hpp" // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/system/section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
|
||||
#include "game/options.hpp" // Para Video, video, Settings, settings, Audio, audio, Window, window
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
||||
#include "utils/utils.hpp" // Para boolToOnOff
|
||||
|
||||
namespace GlobalInputs {
|
||||
// Termina
|
||||
@@ -84,21 +82,15 @@ namespace GlobalInputs {
|
||||
void nextPreset() {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
Screen::nextCrtPiPreset();
|
||||
const std::string name = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
||||
Notifier::get()->show({"CrtPi: " + name});
|
||||
const std::string NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
||||
Notifier::get()->show({"CrtPi: " + NAME});
|
||||
} else {
|
||||
Screen::nextPostFXPreset();
|
||||
const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||
Notifier::get()->show({"PostFX: " + name});
|
||||
const std::string NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||
Notifier::get()->show({"PostFX: " + NAME});
|
||||
}
|
||||
}
|
||||
|
||||
// Activa o desactiva el supersampling
|
||||
void toggleSupersampling() {
|
||||
Screen::toggleSupersampling();
|
||||
Notifier::get()->show({"SS: " + std::string(Options::video.supersampling.enabled ? "ON" : "OFF")});
|
||||
}
|
||||
|
||||
// Cambia al siguiente idioma
|
||||
void setNextLang() {
|
||||
const std::string CODE = "LANG";
|
||||
@@ -229,10 +221,6 @@ namespace GlobalInputs {
|
||||
nextPreset();
|
||||
return true;
|
||||
}
|
||||
if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
|
||||
toggleSupersampling();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
+26
-71
@@ -118,53 +118,22 @@ auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const s
|
||||
|
||||
// Comprueba si hay almenos una acción activa
|
||||
auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool {
|
||||
// Obtenemos el número total de acciones posibles para iterar sobre ellas.
|
||||
const auto JUST_PRESSED = [](const auto& pair) { return pair.second.just_pressed; };
|
||||
|
||||
// --- Comprobación del Teclado ---
|
||||
if (check_keyboard) {
|
||||
for (const auto& pair : keyboard_.bindings) {
|
||||
// Simplemente leemos el estado pre-calculado por Input::update().
|
||||
// Ya no se llama a SDL_GetKeyboardState ni se modifica el estado '.active'.
|
||||
if (pair.second.just_pressed) {
|
||||
return true; // Se encontró una acción recién pulsada.
|
||||
}
|
||||
}
|
||||
if (check_keyboard && std::ranges::any_of(keyboard_.bindings, JUST_PRESSED)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Comprobación del Mando ---
|
||||
// Comprobamos si hay mandos y si el índice solicitado es válido.
|
||||
if (gamepad != nullptr) {
|
||||
// Iteramos sobre todas las acciones, no sobre el número de mandos.
|
||||
for (const auto& pair : gamepad->bindings) {
|
||||
// Leemos el estado pre-calculado para el mando y la acción específicos.
|
||||
if (pair.second.just_pressed) {
|
||||
return true; // Se encontró una acción recién pulsada en el mando.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si llegamos hasta aquí, no se detectó ninguna nueva pulsación.
|
||||
return false;
|
||||
return gamepad != nullptr && std::ranges::any_of(gamepad->bindings, JUST_PRESSED);
|
||||
}
|
||||
|
||||
// Comprueba si hay algún botón pulsado
|
||||
auto Input::checkAnyButton(bool repeat) -> bool {
|
||||
// Solo comprueba los botones definidos previamente
|
||||
for (auto bi : BUTTON_INPUTS) {
|
||||
// Comprueba el teclado
|
||||
if (checkAction(bi, repeat, CHECK_KEYBOARD)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Comprueba los mandos
|
||||
for (const auto& gamepad : gamepads_) {
|
||||
if (checkAction(bi, repeat, DO_NOT_CHECK_KEYBOARD, gamepad)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return std::ranges::any_of(BUTTON_INPUTS, [this, repeat](auto bi) {
|
||||
if (checkAction(bi, repeat, CHECK_KEYBOARD)) { return true; }
|
||||
return std::ranges::any_of(gamepads_, [this, bi, repeat](const auto& gamepad) {
|
||||
return checkAction(bi, repeat, DO_NOT_CHECK_KEYBOARD, gamepad);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Comprueba si hay algun mando conectado
|
||||
@@ -178,9 +147,8 @@ auto Input::getControllerName(const std::shared_ptr<Gamepad>& gamepad) -> std::s
|
||||
// Obtiene la lista de nombres de mandos
|
||||
auto Input::getControllerNames() const -> std::vector<std::string> {
|
||||
std::vector<std::string> names;
|
||||
for (const auto& gamepad : gamepads_) {
|
||||
names.push_back(gamepad->name);
|
||||
}
|
||||
names.reserve(gamepads_.size());
|
||||
std::ranges::transform(gamepads_, std::back_inserter(names), [](const auto& gamepad) { return gamepad->name; });
|
||||
return names;
|
||||
}
|
||||
|
||||
@@ -189,21 +157,15 @@ auto Input::getNumGamepads() const -> int { return gamepads_.size(); }
|
||||
|
||||
// Obtiene el gamepad a partir de un event.id
|
||||
auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepad> {
|
||||
for (const auto& gamepad : gamepads_) {
|
||||
if (gamepad->instance_id == id) {
|
||||
return gamepad;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
const auto IT = std::ranges::find_if(gamepads_,
|
||||
[id](const auto& gamepad) { return gamepad->instance_id == id; });
|
||||
return IT != gamepads_.end() ? *IT : nullptr;
|
||||
}
|
||||
|
||||
auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad> {
|
||||
for (const auto& gamepad : gamepads_) {
|
||||
if (gamepad && gamepad->name == name) {
|
||||
return gamepad;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
const auto IT = std::ranges::find_if(gamepads_,
|
||||
[&name](const auto& gamepad) { return gamepad && gamepad->name == name; });
|
||||
return IT != gamepads_.end() ? *IT : nullptr;
|
||||
}
|
||||
|
||||
// Obtiene el SDL_GamepadButton asignado a un action
|
||||
@@ -359,7 +321,7 @@ void Input::resetInputStates() {
|
||||
key.second.just_pressed = false;
|
||||
}
|
||||
// Resetear todos los ControllerBindings.active a false
|
||||
for (auto& gamepad : gamepads_) {
|
||||
for (const auto& gamepad : gamepads_) {
|
||||
for (auto& binding : gamepad->bindings) {
|
||||
binding.second.is_held = false;
|
||||
binding.second.just_pressed = false;
|
||||
@@ -398,8 +360,9 @@ auto Input::handleEvent(const SDL_Event& event) -> std::string {
|
||||
return addGamepad(event.gdevice.which);
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
return removeGamepad(event.gdevice.which);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Input::addGamepad(int device_index) -> std::string {
|
||||
@@ -568,19 +531,11 @@ auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std::
|
||||
}
|
||||
|
||||
// Buscar por nombre
|
||||
for (const auto& gamepad : gamepads_) {
|
||||
if (gamepad && gamepad->name == gamepad_name) {
|
||||
return gamepad;
|
||||
}
|
||||
}
|
||||
auto by_name = std::ranges::find_if(gamepads_,
|
||||
[&gamepad_name](const auto& gamepad) { return gamepad && gamepad->name == gamepad_name; });
|
||||
if (by_name != gamepads_.end()) { return *by_name; }
|
||||
|
||||
// Si no se encuentra por nombre, devolver el primer gamepad válido
|
||||
for (const auto& gamepad : gamepads_) {
|
||||
if (gamepad) {
|
||||
return gamepad;
|
||||
}
|
||||
}
|
||||
|
||||
// Si llegamos aquí, no hay gamepads válidos
|
||||
return nullptr;
|
||||
auto first_valid = std::ranges::find_if(gamepads_, [](const auto& gamepad) { return gamepad != nullptr; });
|
||||
return first_valid != gamepads_.end() ? *first_valid : nullptr;
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string, basic_string
|
||||
#include <unordered_map> // Para unordered_map
|
||||
#include <utility> // Para pair
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/input/gamepad_config_manager.hpp" // for GamepadConfig (ptr only), GamepadConfigs
|
||||
@@ -32,7 +31,7 @@ class Input {
|
||||
bool is_held; // Está pulsada ahora mismo
|
||||
bool just_pressed; // Se acaba de pulsar en este fotograma
|
||||
|
||||
KeyState(Uint8 scancode = 0, bool is_held = false, bool just_pressed = false)
|
||||
explicit KeyState(Uint8 scancode = 0, bool is_held = false, bool just_pressed = false)
|
||||
: scancode(scancode),
|
||||
is_held(is_held),
|
||||
just_pressed(just_pressed) {}
|
||||
@@ -45,7 +44,7 @@ class Input {
|
||||
bool axis_active; // Estado del eje
|
||||
bool trigger_active{false}; // Estado del trigger como botón digital
|
||||
|
||||
ButtonState(int btn = static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID), bool is_held = false, bool just_pressed = false, bool axis_act = false)
|
||||
explicit ButtonState(int btn = static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID), bool is_held = false, bool just_pressed = false, bool axis_act = false)
|
||||
: button(btn),
|
||||
is_held(is_held),
|
||||
just_pressed(just_pressed),
|
||||
@@ -84,7 +83,6 @@ class Input {
|
||||
{Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
|
||||
{Action::NEXT_SHADER, KeyState(SDL_SCANCODE_8)},
|
||||
{Action::NEXT_POSTFX_PRESET, KeyState(SDL_SCANCODE_9)},
|
||||
{Action::TOGGLE_SUPERSAMPLING, KeyState(SDL_SCANCODE_0)},
|
||||
{Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)},
|
||||
{Action::TOGGLE_VIDEO_VSYNC, KeyState(SDL_SCANCODE_F6)},
|
||||
|
||||
@@ -109,13 +107,13 @@ class Input {
|
||||
// Evita nombres como "Retroid Controller (vendor: 1001) ..." en las notificaciones.
|
||||
static auto trimName(const char* raw) -> std::string {
|
||||
std::string s(raw != nullptr ? raw : "");
|
||||
const auto pos = s.find_first_of("([");
|
||||
if (pos != std::string::npos) { s.erase(pos); }
|
||||
const auto POS = s.find_first_of("([");
|
||||
if (POS != std::string::npos) { s.erase(POS); }
|
||||
while (!s.empty() && s.back() == ' ') { s.pop_back(); }
|
||||
return s;
|
||||
}
|
||||
|
||||
Gamepad(SDL_Gamepad* gamepad)
|
||||
explicit Gamepad(SDL_Gamepad* gamepad)
|
||||
: pad(gamepad),
|
||||
instance_id(SDL_GetJoystickID(SDL_GetGamepadJoystick(gamepad))),
|
||||
name(trimName(SDL_GetGamepadName(gamepad))),
|
||||
@@ -148,7 +146,7 @@ class Input {
|
||||
|
||||
// Reasigna un botón a una acción
|
||||
void rebindAction(Action action, SDL_GamepadButton new_button) {
|
||||
bindings[action] = static_cast<int>(new_button);
|
||||
bindings[action] = ButtonState{static_cast<int>(new_button)};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
|
||||
{InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"},
|
||||
{InputAction::NEXT_SHADER, "NEXT_SHADER"},
|
||||
{InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"},
|
||||
{InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"},
|
||||
{InputAction::TOGGLE_VIDEO_INTEGER_SCALE, "TOGGLE_VIDEO_INTEGER_SCALE"},
|
||||
{InputAction::TOGGLE_VIDEO_VSYNC, "TOGGLE_VIDEO_VSYNC"},
|
||||
{InputAction::RESET, "RESET"},
|
||||
@@ -57,7 +56,6 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
|
||||
{"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX},
|
||||
{"NEXT_SHADER", InputAction::NEXT_SHADER},
|
||||
{"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET},
|
||||
{"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING},
|
||||
{"TOGGLE_VIDEO_INTEGER_SCALE", InputAction::TOGGLE_VIDEO_INTEGER_SCALE},
|
||||
{"TOGGLE_VIDEO_VSYNC", InputAction::TOGGLE_VIDEO_VSYNC},
|
||||
{"RESET", InputAction::RESET},
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
// --- Enums ---
|
||||
enum class InputAction : int { // Acciones de entrada posibles en el juego
|
||||
enum class InputAction : std::uint8_t { // Acciones de entrada posibles en el juego
|
||||
// Inputs de movimiento
|
||||
UP,
|
||||
DOWN,
|
||||
@@ -34,7 +35,6 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
|
||||
TOGGLE_VIDEO_POSTFX,
|
||||
NEXT_SHADER,
|
||||
NEXT_POSTFX_PRESET,
|
||||
TOGGLE_SUPERSAMPLING,
|
||||
TOGGLE_VIDEO_INTEGER_SCALE,
|
||||
TOGGLE_VIDEO_VSYNC,
|
||||
RESET,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// --- Clase PauseManager: maneja el sistema de pausa del juego ---
|
||||
class PauseManager {
|
||||
@@ -48,7 +50,7 @@ class PauseManager {
|
||||
|
||||
// --- Métodos principales ---
|
||||
void setFlag(Source source, bool enable) { // Establece/quita una fuente de pausa específica
|
||||
bool was_paused = isPaused();
|
||||
const bool WAS_PAUSED = isPaused();
|
||||
|
||||
if (enable) {
|
||||
flags_ |= source;
|
||||
@@ -56,7 +58,8 @@ class PauseManager {
|
||||
flags_ &= ~source; // Ahora funciona: Source &= uint8_t
|
||||
}
|
||||
|
||||
if (was_paused != isPaused()) {
|
||||
// cppcheck-suppress knownConditionTrueFalse // false-positive: flags_ ha estat modificat entre les dues crides a isPaused()
|
||||
if (WAS_PAUSED != isPaused()) {
|
||||
notifyPauseChanged();
|
||||
}
|
||||
}
|
||||
@@ -88,30 +91,16 @@ class PauseManager {
|
||||
return "Active";
|
||||
}
|
||||
|
||||
std::vector<std::string> parts;
|
||||
if (hasFlag(Source::PLAYER)) { parts.emplace_back("Player"); }
|
||||
if (hasFlag(Source::SERVICE_MENU)) { parts.emplace_back("ServiceMenu"); }
|
||||
if (hasFlag(Source::FOCUS_LOST)) { parts.emplace_back("FocusLoss"); }
|
||||
|
||||
std::string result = "Paused by: ";
|
||||
bool first = true;
|
||||
|
||||
if (hasFlag(Source::PLAYER)) {
|
||||
if (!first) {
|
||||
result += ", ";
|
||||
}
|
||||
result += "Player";
|
||||
first = false;
|
||||
for (size_t i = 0; i < parts.size(); ++i) {
|
||||
if (i > 0) { result += ", "; }
|
||||
result += parts[i];
|
||||
}
|
||||
if (hasFlag(Source::SERVICE_MENU)) {
|
||||
if (!first) {
|
||||
result += ", ";
|
||||
}
|
||||
result += "ServiceMenu";
|
||||
first = false;
|
||||
}
|
||||
if (hasFlag(Source::FOCUS_LOST)) {
|
||||
if (!first) {
|
||||
result += ", ";
|
||||
}
|
||||
result += "FocusLoss";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
void setCallback(std::function<void(bool)> callback) { // Permite cambiar el callback en runtime
|
||||
|
||||
+17
-31
@@ -1,5 +1,6 @@
|
||||
#include "core/locale/lang.hpp"
|
||||
|
||||
#include <algorithm> // Para ranges::find_if
|
||||
#include <cstddef> // Para size_t
|
||||
#include <exception> // Para exception
|
||||
#include <fstream> // Para basic_ifstream, basic_istream, ifstream
|
||||
@@ -13,7 +14,7 @@
|
||||
#include "game/gameplay/difficulty.hpp" // Para Difficulty
|
||||
#include "game/options.hpp" // Para SettingsOpt...
|
||||
|
||||
using json = nlohmann::json;
|
||||
using Json = nlohmann::json;
|
||||
|
||||
namespace Lang {
|
||||
std::unordered_map<std::string, std::string> texts;
|
||||
@@ -32,12 +33,12 @@ namespace Lang {
|
||||
auto resource_data = ResourceHelper::loadFile(file_path);
|
||||
|
||||
try {
|
||||
json j;
|
||||
Json j;
|
||||
|
||||
if (!resource_data.empty()) {
|
||||
// Cargar desde datos del pack
|
||||
std::string content(resource_data.begin(), resource_data.end());
|
||||
j = json::parse(content);
|
||||
j = Json::parse(content);
|
||||
} else {
|
||||
// Fallback a filesystem directo
|
||||
std::ifstream rfile(file_path);
|
||||
@@ -80,35 +81,23 @@ namespace Lang {
|
||||
|
||||
// Obtiene un idioma del vector de idiomas a partir de un código
|
||||
auto getLanguage(Code code) -> Language {
|
||||
for (const auto& lang : languages) {
|
||||
if (lang.code == code) {
|
||||
return lang;
|
||||
}
|
||||
}
|
||||
// Si no se encuentra, devuelve el primero por defecto
|
||||
return languages[0];
|
||||
const auto IT = std::ranges::find_if(languages,
|
||||
[code](const auto& lang) { return lang.code == code; });
|
||||
return IT != languages.end() ? *IT : languages[0];
|
||||
}
|
||||
|
||||
// Devuelve el código de un idioma a partir de un nombre
|
||||
auto getCodeFromName(const std::string& name) -> Code {
|
||||
for (const auto& lang : languages) {
|
||||
if (lang.name == name) {
|
||||
return lang.code;
|
||||
}
|
||||
}
|
||||
// Si no se encuentra, devuelve el primero por defecto
|
||||
return languages[0].code;
|
||||
const auto IT = std::ranges::find_if(languages,
|
||||
[&name](const auto& lang) { return lang.name == name; });
|
||||
return IT != languages.end() ? IT->code : languages[0].code;
|
||||
}
|
||||
|
||||
// Devuelve el nombre de un idioma a partir de un código
|
||||
auto getNameFromCode(Code code) -> std::string {
|
||||
for (const auto& lang : languages) {
|
||||
if (lang.code == code) {
|
||||
return lang.name;
|
||||
}
|
||||
}
|
||||
// Si no se encuentra, devuelve el nombre del primer idioma por defecto
|
||||
return languages[0].name;
|
||||
const auto IT = std::ranges::find_if(languages,
|
||||
[code](const auto& lang) { return lang.code == code; });
|
||||
return IT != languages.end() ? IT->name : languages[0].name;
|
||||
}
|
||||
|
||||
// Actualiza los nombres de los idiomas
|
||||
@@ -155,13 +144,10 @@ namespace Lang {
|
||||
|
||||
// Obtiene una fichero a partir de un lang::Code
|
||||
auto getLanguageFileName(Lang::Code code) -> std::string {
|
||||
for (const auto& lang : languages) {
|
||||
if (lang.code == code) {
|
||||
return Asset::get()->getPath(lang.file_name);
|
||||
}
|
||||
}
|
||||
// Si no se encuentra, devuelve el fichero del primer idioma por defecto
|
||||
return Asset::get()->getPath(languages[0].file_name);
|
||||
const auto IT = std::ranges::find_if(languages,
|
||||
[code](const auto& lang) { return lang.code == code; });
|
||||
const auto& file = (IT != languages.end()) ? IT->file_name : languages[0].file_name;
|
||||
return Asset::get()->getPath(file);
|
||||
}
|
||||
|
||||
// Establece el idioma
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <string> // Para string, basic_string
|
||||
#include <utility> // Para move
|
||||
|
||||
// --- Namespace Lang: gestión de idiomas y textos ---
|
||||
namespace Lang {
|
||||
// --- Enums ---
|
||||
enum class Code : int {
|
||||
enum class Code : std::uint8_t {
|
||||
SPANISH = 0, // Español
|
||||
VALENCIAN = 1, // Valenciano
|
||||
ENGLISH = 2 // Inglés
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// NOLINTNEXTLINE(bugprone-reserved-identifier) -- requerido por <cmath> para exponer M_PI en MSVC
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "core/rendering/background.hpp"
|
||||
|
||||
@@ -29,10 +30,10 @@ Background::Background(float total_progress_to_complete)
|
||||
moon_texture_(Resource::get()->getTexture("game_moon.png")),
|
||||
grass_sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("game_grass.png"), Resource::get()->getAnimation("game_grass.ani"))),
|
||||
|
||||
total_progress_to_complete_(total_progress_to_complete),
|
||||
progress_per_stage_(total_progress_to_complete_ / STAGES),
|
||||
sun_completion_progress_(total_progress_to_complete_ * SUN_COMPLETION_FACTOR),
|
||||
minimum_completed_progress_(total_progress_to_complete_ * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE),
|
||||
TOTAL_PROGRESS_TO_COMPLETE(total_progress_to_complete),
|
||||
PROGRESS_PER_STAGE(TOTAL_PROGRESS_TO_COMPLETE / STAGES),
|
||||
SUM_COMPLETION_PROGRESS(TOTAL_PROGRESS_TO_COMPLETE * SUN_COMPLETION_FACTOR),
|
||||
MINIMUM_COMPLETED_PROGRESS(TOTAL_PROGRESS_TO_COMPLETE * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE),
|
||||
|
||||
rect_(SDL_FRect{.x = 0, .y = 0, .w = static_cast<float>(gradients_texture_->getWidth() / 2), .h = static_cast<float>(gradients_texture_->getHeight() / 2)}),
|
||||
src_rect_({.x = 0, .y = 0, .w = 320, .h = 240}),
|
||||
@@ -167,7 +168,7 @@ void Background::incrementProgress(float amount) {
|
||||
if (state_ == State::NORMAL) {
|
||||
float old_progress = progress_;
|
||||
progress_ += amount;
|
||||
progress_ = std::min(progress_, total_progress_to_complete_);
|
||||
progress_ = std::min(progress_, TOTAL_PROGRESS_TO_COMPLETE);
|
||||
|
||||
// Notifica el cambio si hay callback y el progreso cambió
|
||||
if (progress_callback_ && progress_ != old_progress) {
|
||||
@@ -179,7 +180,7 @@ void Background::incrementProgress(float amount) {
|
||||
// Establece la progresión absoluta
|
||||
void Background::setProgress(float absolute_progress) {
|
||||
float old_progress = progress_;
|
||||
progress_ = std::clamp(absolute_progress, 0.0F, total_progress_to_complete_);
|
||||
progress_ = std::clamp(absolute_progress, 0.0F, TOTAL_PROGRESS_TO_COMPLETE);
|
||||
|
||||
// Notifica el cambio si hay callback y el progreso cambió
|
||||
if (progress_callback_ && progress_ != old_progress) {
|
||||
@@ -282,27 +283,27 @@ void Background::updateProgression(float delta_time) {
|
||||
float eased_t = easeOutCubic(static_cast<double>(t));
|
||||
|
||||
// Interpolación desde progreso inicial hasta mínimo
|
||||
float progress_range = completion_initial_progress_ - minimum_completed_progress_;
|
||||
float progress_range = completion_initial_progress_ - MINIMUM_COMPLETED_PROGRESS;
|
||||
progress_ = completion_initial_progress_ - (progress_range * eased_t);
|
||||
} else {
|
||||
// Transición completada, fijar al valor mínimo
|
||||
progress_ = minimum_completed_progress_;
|
||||
progress_ = MINIMUM_COMPLETED_PROGRESS;
|
||||
}
|
||||
}
|
||||
|
||||
// Calcula la transición de los diferentes fondos
|
||||
const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / progress_per_stage_, 3.0F);
|
||||
const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / PROGRESS_PER_STAGE, 3.0F);
|
||||
const float PERCENT = GRADIENT_NUMBER_FLOAT - static_cast<int>(GRADIENT_NUMBER_FLOAT);
|
||||
|
||||
gradient_number_ = static_cast<size_t>(GRADIENT_NUMBER_FLOAT);
|
||||
transition_ = PERCENT;
|
||||
|
||||
// Calcula la posición del sol
|
||||
const float SUN_PROGRESSION = std::min(progress_ / sun_completion_progress_, 1.0F);
|
||||
const float SUN_PROGRESSION = std::min(progress_ / SUM_COMPLETION_PROGRESS, 1.0F);
|
||||
sun_index_ = static_cast<size_t>(SUN_PROGRESSION * (sun_path_.size() - 1));
|
||||
|
||||
// Calcula la posición de la luna
|
||||
const float MOON_PROGRESSION = std::min(progress_ / total_progress_to_complete_, 1.0F);
|
||||
const float MOON_PROGRESSION = std::min(progress_ / TOTAL_PROGRESS_TO_COMPLETE, 1.0F);
|
||||
moon_index_ = static_cast<size_t>(MOON_PROGRESSION * (moon_path_.size() - 1));
|
||||
|
||||
// Actualiza la velocidad de las nubes
|
||||
@@ -318,12 +319,12 @@ void Background::updateCloudsSpeed() {
|
||||
|
||||
// Velocidad base según progreso (de -3.0 a -120.0 píxeles/segundo, igual que la versión original)
|
||||
float base_clouds_speed = (-CLOUDS_INITIAL_SPEED_PX_PER_S) +
|
||||
(-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / total_progress_to_complete_));
|
||||
(-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / TOTAL_PROGRESS_TO_COMPLETE));
|
||||
|
||||
// En estado completado, las nubes se ralentizan gradualmente
|
||||
if (state_ == State::COMPLETED) {
|
||||
float completion_factor = (progress_ - minimum_completed_progress_) /
|
||||
(total_progress_to_complete_ - minimum_completed_progress_);
|
||||
float completion_factor = (progress_ - MINIMUM_COMPLETED_PROGRESS) /
|
||||
(TOTAL_PROGRESS_TO_COMPLETE - MINIMUM_COMPLETED_PROGRESS);
|
||||
completion_factor = std::max(0.1F, completion_factor);
|
||||
base_clouds_speed *= completion_factor;
|
||||
}
|
||||
@@ -562,14 +563,13 @@ void Background::createMoonPath() {
|
||||
const int FREEZE_START_INDEX = static_cast<int>(NUM_STEPS * (1.0F - FREEZE_PERCENTAGE));
|
||||
|
||||
for (int i = 0; i < NUM_STEPS; ++i) {
|
||||
double theta = i * STEP;
|
||||
float x = CENTER_X + (RADIUS * cos(theta));
|
||||
float y = CENTER_Y - (RADIUS * sin(theta));
|
||||
|
||||
if (i >= FREEZE_START_INDEX && !moon_path_.empty()) {
|
||||
moon_path_.push_back(moon_path_.back()); // Repite el último punto válido
|
||||
} else {
|
||||
moon_path_.push_back({.x = x, .y = y});
|
||||
const double THETA = i * STEP;
|
||||
const float X = CENTER_X + (RADIUS * cos(THETA));
|
||||
const float Y = CENTER_Y - (RADIUS * sin(THETA));
|
||||
moon_path_.push_back({.x = X, .y = Y});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <array> // Para array
|
||||
#include <cstddef> // Para size_t
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para unique_ptr, shared_ptr
|
||||
#include <vector> // Para vector
|
||||
@@ -19,7 +20,7 @@ class AnimatedSprite;
|
||||
class Background {
|
||||
public:
|
||||
// --- Enums ---
|
||||
enum class State {
|
||||
enum class State : std::uint8_t {
|
||||
NORMAL, // Progresión normal del día
|
||||
COMPLETED // Reducción gradual de la actividad
|
||||
};
|
||||
@@ -28,8 +29,8 @@ class Background {
|
||||
using ProgressCallback = std::function<void(float)>; // Callback para sincronización
|
||||
|
||||
// --- Constructor y destructor ---
|
||||
Background(float total_progress_to_complete = 6100.0F); // Constructor principal
|
||||
~Background(); // Destructor
|
||||
explicit Background(float total_progress_to_complete = 6100.0F); // Constructor principal
|
||||
~Background(); // Destructor
|
||||
|
||||
// --- Métodos principales ---
|
||||
void update(float delta_time); // Actualiza la lógica del objeto
|
||||
@@ -87,10 +88,10 @@ class Background {
|
||||
std::unique_ptr<AnimatedSprite> grass_sprite_; // Sprite con la hierba
|
||||
|
||||
// --- Variables de configuración ---
|
||||
const float total_progress_to_complete_; // Progreso total para completar
|
||||
const float progress_per_stage_; // Progreso por etapa
|
||||
const float sun_completion_progress_; // Progreso de completado del sol
|
||||
const float minimum_completed_progress_; // Progreso mínimo calculado dinámicamente
|
||||
const float TOTAL_PROGRESS_TO_COMPLETE; // Progreso total para completar
|
||||
const float PROGRESS_PER_STAGE; // Progreso por etapa
|
||||
const float SUM_COMPLETION_PROGRESS; // Progreso de completado del sol
|
||||
const float MINIMUM_COMPLETED_PROGRESS; // Progreso mínimo calculado dinámicamente
|
||||
ProgressCallback progress_callback_; // Callback para notificar cambios de progreso
|
||||
|
||||
// --- Variables de estado ---
|
||||
|
||||
@@ -481,7 +481,7 @@ void Fade::activate() {
|
||||
case Type::DIAGONAL: {
|
||||
rect1_ = {.x = 0, .y = 0, .w = static_cast<float>(param.game.width / num_squares_width_), .h = static_cast<float>(param.game.height / num_squares_height_)};
|
||||
square_.clear();
|
||||
square_age_.assign(num_squares_width_ * num_squares_height_, -1);
|
||||
square_age_.assign(static_cast<size_t>(num_squares_width_) * num_squares_height_, -1);
|
||||
for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) {
|
||||
rect1_.x = (i % num_squares_width_) * rect1_.w;
|
||||
rect1_.y = (i / num_squares_width_) * rect1_.h;
|
||||
|
||||
+197
-185
@@ -8,145 +8,233 @@
|
||||
#include <string> // Para char_traits, operator==, basic_string, string
|
||||
|
||||
namespace GIF {
|
||||
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) {
|
||||
std::memcpy(dst, buffer, size);
|
||||
buffer += size;
|
||||
}
|
||||
|
||||
void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) {
|
||||
if (code_length < 2 || code_length > 12) {
|
||||
std::cout << "Invalid LZW code length: " << code_length << '\n';
|
||||
throw std::runtime_error("Invalid LZW code length");
|
||||
namespace {
|
||||
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) {
|
||||
std::memcpy(dst, buffer, size);
|
||||
buffer += size;
|
||||
}
|
||||
|
||||
int i, bit;
|
||||
int prev = -1;
|
||||
std::vector<DictionaryEntry> dictionary;
|
||||
int dictionary_ind;
|
||||
unsigned int mask = 0x01;
|
||||
int reset_code_length = code_length;
|
||||
int clear_code = 1 << code_length;
|
||||
int stop_code = clear_code + 1;
|
||||
int match_len = 0;
|
||||
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||
dictionary[dictionary_ind].prev = -1;
|
||||
dictionary[dictionary_ind].len = 1;
|
||||
// Llavor del diccionari LZW: 0..N-1 com a entrades base, i salta 2 (clear_code + stop_code).
|
||||
void resetDictionary(std::vector<DictionaryEntry> &dict, int code_length, int &dictionary_ind) {
|
||||
dict.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
||||
dict[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||
dict[dictionary_ind].prev = -1;
|
||||
dict[dictionary_ind].len = 1;
|
||||
}
|
||||
dictionary_ind += 2;
|
||||
}
|
||||
dictionary_ind += 2;
|
||||
|
||||
while (input_length > 0) {
|
||||
// Llig `code_length + 1` bits LSB-first del flux d'entrada. Llança si s'acaba el buffer.
|
||||
auto readNextCode(const uint8_t *&input, int &input_length, int code_length, unsigned int &mask) -> int {
|
||||
int code = 0;
|
||||
for (i = 0; i < (code_length + 1); i++) {
|
||||
for (int i = 0; i < code_length + 1; i++) {
|
||||
if (input_length <= 0) {
|
||||
std::cout << "Unexpected end of input in decompress" << '\n';
|
||||
throw std::runtime_error("Unexpected end of input in decompress");
|
||||
}
|
||||
bit = ((*input & mask) != 0) ? 1 : 0;
|
||||
const int BIT = ((*input & mask) != 0) ? 1 : 0;
|
||||
mask <<= 1;
|
||||
if (mask == 0x100) {
|
||||
mask = 0x01;
|
||||
input++;
|
||||
input_length--;
|
||||
}
|
||||
code |= (bit << i);
|
||||
code |= (BIT << i);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
if (code == clear_code) {
|
||||
code_length = reset_code_length;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
|
||||
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
|
||||
dictionary[dictionary_ind].prev = -1;
|
||||
dictionary[dictionary_ind].len = 1;
|
||||
}
|
||||
dictionary_ind += 2;
|
||||
prev = -1;
|
||||
continue;
|
||||
} else if (code == stop_code) {
|
||||
break;
|
||||
// Afig una nova entrada al diccionari. Resol el cas especial KwKwK (code == dictionary_ind)
|
||||
// començant la cadena des de `prev` en lloc de des de `code`.
|
||||
void addDictionaryEntry(std::vector<DictionaryEntry> &dict, int dictionary_ind, int code, int prev) {
|
||||
int ptr = (code == dictionary_ind) ? prev : code;
|
||||
while (dict[ptr].prev != -1) {
|
||||
ptr = dict[ptr].prev;
|
||||
}
|
||||
dict[dictionary_ind].byte = dict[ptr].byte;
|
||||
dict[dictionary_ind].prev = prev;
|
||||
dict[dictionary_ind].len = dict[prev].len + 1;
|
||||
}
|
||||
|
||||
if (prev > -1 && code_length < 12) {
|
||||
if (code > dictionary_ind) {
|
||||
std::cout << "LZW error: code (" << code << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n';
|
||||
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
|
||||
}
|
||||
|
||||
int ptr;
|
||||
if (code == dictionary_ind) {
|
||||
ptr = prev;
|
||||
while (dictionary[ptr].prev != -1)
|
||||
ptr = dictionary[ptr].prev;
|
||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
||||
} else {
|
||||
ptr = code;
|
||||
while (dictionary[ptr].prev != -1)
|
||||
ptr = dictionary[ptr].prev;
|
||||
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
|
||||
}
|
||||
dictionary[dictionary_ind].prev = prev;
|
||||
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
|
||||
dictionary_ind++;
|
||||
|
||||
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
|
||||
code_length++;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
}
|
||||
}
|
||||
|
||||
prev = code;
|
||||
|
||||
if (code < 0 || static_cast<size_t>(code) >= dictionary.size()) {
|
||||
std::cout << "Invalid LZW code " << code << ", dictionary size " << static_cast<unsigned long>(dictionary.size()) << '\n';
|
||||
throw std::runtime_error("LZW error: invalid code encountered");
|
||||
}
|
||||
|
||||
int curCode = code;
|
||||
match_len = dictionary[curCode].len;
|
||||
while (curCode != -1) {
|
||||
out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
|
||||
if (dictionary[curCode].prev == curCode) {
|
||||
// Escriu la cadena de bytes associada a `code` en `out` (en ordre invers seguint .prev).
|
||||
// Retorna la longitud del match per avançar el cursor de l'eixida.
|
||||
auto emitMatch(const std::vector<DictionaryEntry> &dict, int code, uint8_t *out) -> int {
|
||||
const int MATCH_LEN = dict[code].len;
|
||||
int cur_code = code;
|
||||
while (cur_code != -1) {
|
||||
out[dict[cur_code].len - 1] = dict[cur_code].byte;
|
||||
if (dict[cur_code].prev == cur_code) {
|
||||
std::cout << "Internal error; self-reference detected." << '\n';
|
||||
throw std::runtime_error("Internal error in decompress: self-reference");
|
||||
}
|
||||
curCode = dictionary[curCode].prev;
|
||||
cur_code = dict[cur_code].prev;
|
||||
}
|
||||
out += match_len;
|
||||
return MATCH_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer) {
|
||||
std::vector<uint8_t> data;
|
||||
uint8_t block_size = *buffer;
|
||||
buffer++;
|
||||
while (block_size != 0) {
|
||||
data.insert(data.end(), buffer, buffer + block_size);
|
||||
buffer += block_size;
|
||||
block_size = *buffer;
|
||||
// Descompone (uncompress) el bloque comprimido usando LZW.
|
||||
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) {
|
||||
if (code_length < 2 || code_length > 12) {
|
||||
std::cout << "Invalid LZW code length: " << code_length << '\n';
|
||||
throw std::runtime_error("Invalid LZW code length");
|
||||
}
|
||||
|
||||
int prev = -1;
|
||||
std::vector<DictionaryEntry> dictionary;
|
||||
int dictionary_ind = 0;
|
||||
unsigned int mask = 0x01;
|
||||
const int RESET_CODE_LENGTH = code_length;
|
||||
const int CLEAR_CODE = 1 << code_length;
|
||||
const int STOP_CODE = CLEAR_CODE + 1;
|
||||
|
||||
resetDictionary(dictionary, code_length, dictionary_ind);
|
||||
|
||||
while (input_length > 0) {
|
||||
const int CODE = readNextCode(input, input_length, code_length, mask);
|
||||
|
||||
if (CODE == CLEAR_CODE) {
|
||||
code_length = RESET_CODE_LENGTH;
|
||||
resetDictionary(dictionary, code_length, dictionary_ind);
|
||||
prev = -1;
|
||||
continue;
|
||||
}
|
||||
if (CODE == STOP_CODE) { break; }
|
||||
|
||||
if (prev > -1 && code_length < 12) {
|
||||
if (CODE > dictionary_ind) {
|
||||
std::cout << "LZW error: code (" << CODE << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n';
|
||||
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
|
||||
}
|
||||
addDictionaryEntry(dictionary, dictionary_ind, CODE, prev);
|
||||
dictionary_ind++;
|
||||
|
||||
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
|
||||
code_length++;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
}
|
||||
}
|
||||
|
||||
prev = CODE;
|
||||
|
||||
if (CODE < 0 || static_cast<size_t>(CODE) >= dictionary.size()) {
|
||||
std::cout << "Invalid LZW code " << CODE << ", dictionary size " << static_cast<unsigned long>(dictionary.size()) << '\n';
|
||||
throw std::runtime_error("LZW error: invalid code encountered");
|
||||
}
|
||||
|
||||
out += emitMatch(dictionary, CODE, out);
|
||||
}
|
||||
}
|
||||
|
||||
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
|
||||
auto readSubBlocks(const uint8_t *&buffer) -> std::vector<uint8_t> {
|
||||
std::vector<uint8_t> data;
|
||||
uint8_t block_size = *buffer;
|
||||
buffer++;
|
||||
while (block_size != 0) {
|
||||
data.insert(data.end(), buffer, buffer + block_size);
|
||||
buffer += block_size;
|
||||
block_size = *buffer;
|
||||
buffer++;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits) {
|
||||
ImageDescriptor image_descriptor;
|
||||
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
||||
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
||||
auto processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits) -> std::vector<uint8_t> {
|
||||
ImageDescriptor image_descriptor;
|
||||
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
||||
|
||||
uint8_t lzw_code_size;
|
||||
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
|
||||
uint8_t lzw_code_size;
|
||||
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
|
||||
|
||||
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
|
||||
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
|
||||
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
|
||||
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
|
||||
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
|
||||
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
|
||||
|
||||
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
|
||||
return uncompressed_data;
|
||||
}
|
||||
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
|
||||
return uncompressed_data;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer) {
|
||||
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
||||
auto processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t> {
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
|
||||
std::string header_str(reinterpret_cast<char *>(header), 6);
|
||||
if (header_str != "GIF87a" && header_str != "GIF89a") {
|
||||
std::cout << "Formato de archivo GIF inválido: " << header_str << '\n';
|
||||
throw std::runtime_error("Formato de archivo GIF inválido.");
|
||||
}
|
||||
|
||||
ScreenDescriptor screen_descriptor;
|
||||
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
||||
|
||||
w = screen_descriptor.width;
|
||||
h = screen_descriptor.height;
|
||||
|
||||
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
||||
std::vector<RGB> global_color_table;
|
||||
if ((screen_descriptor.fields & 0x80) != 0) {
|
||||
const size_t GLOBAL_COLOR_TABLE_SIZE = 1U << (((screen_descriptor.fields & 0x07) + 1));
|
||||
global_color_table.resize(GLOBAL_COLOR_TABLE_SIZE);
|
||||
std::memcpy(global_color_table.data(), buffer, 3 * GLOBAL_COLOR_TABLE_SIZE);
|
||||
buffer += 3 * GLOBAL_COLOR_TABLE_SIZE;
|
||||
}
|
||||
|
||||
uint8_t block_type = *buffer++;
|
||||
while (block_type != TRAILER) {
|
||||
if (block_type == EXTENSION_INTRODUCER) {
|
||||
uint8_t extension_label = *buffer++;
|
||||
switch (extension_label) {
|
||||
case GRAPHIC_CONTROL: {
|
||||
uint8_t block_size = *buffer++;
|
||||
buffer += block_size;
|
||||
uint8_t sub_block_size = *buffer++;
|
||||
while (sub_block_size != 0) {
|
||||
buffer += sub_block_size;
|
||||
sub_block_size = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APPLICATION_EXTENSION:
|
||||
case COMMENT_EXTENSION:
|
||||
case PLAINTEXT_EXTENSION: {
|
||||
uint8_t block_size = *buffer++;
|
||||
buffer += block_size;
|
||||
uint8_t sub_block_size = *buffer++;
|
||||
while (sub_block_size != 0) {
|
||||
buffer += sub_block_size;
|
||||
sub_block_size = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
uint8_t block_size = *buffer++;
|
||||
buffer += block_size;
|
||||
uint8_t sub_block_size = *buffer++;
|
||||
while (sub_block_size != 0) {
|
||||
buffer += sub_block_size;
|
||||
sub_block_size = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (block_type == IMAGE_DESCRIPTOR) {
|
||||
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
||||
} else {
|
||||
std::cout << "Unrecognized block type: 0x" << std::hex << static_cast<int>(block_type) << std::dec << '\n';
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
block_type = *buffer++;
|
||||
}
|
||||
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
auto loadPalette(const uint8_t *buffer) -> std::vector<uint32_t> {
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
@@ -156,7 +244,7 @@ namespace GIF {
|
||||
buffer += sizeof(ScreenDescriptor);
|
||||
|
||||
std::vector<uint32_t> global_color_table;
|
||||
if (screen_descriptor.fields & 0x80) {
|
||||
if ((screen_descriptor.fields & 0x80) != 0) {
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
global_color_table.resize(global_color_table_size);
|
||||
for (int i = 0; i < global_color_table_size; ++i) {
|
||||
@@ -170,83 +258,7 @@ namespace GIF {
|
||||
return global_color_table;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) {
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
|
||||
std::string headerStr(reinterpret_cast<char *>(header), 6);
|
||||
if (headerStr != "GIF87a" && headerStr != "GIF89a") {
|
||||
std::cout << "Formato de archivo GIF inválido: " << headerStr << '\n';
|
||||
throw std::runtime_error("Formato de archivo GIF inválido.");
|
||||
}
|
||||
|
||||
ScreenDescriptor screen_descriptor;
|
||||
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
|
||||
|
||||
w = screen_descriptor.width;
|
||||
h = screen_descriptor.height;
|
||||
|
||||
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
||||
std::vector<RGB> global_color_table;
|
||||
if (screen_descriptor.fields & 0x80) {
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
global_color_table.resize(global_color_table_size);
|
||||
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
|
||||
buffer += 3 * global_color_table_size;
|
||||
}
|
||||
|
||||
uint8_t block_type = *buffer++;
|
||||
while (block_type != TRAILER) {
|
||||
if (block_type == EXTENSION_INTRODUCER) {
|
||||
uint8_t extension_label = *buffer++;
|
||||
switch (extension_label) {
|
||||
case GRAPHIC_CONTROL: {
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0) {
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case APPLICATION_EXTENSION:
|
||||
case COMMENT_EXTENSION:
|
||||
case PLAINTEXT_EXTENSION: {
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0) {
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
uint8_t blockSize = *buffer++;
|
||||
buffer += blockSize;
|
||||
uint8_t subBlockSize = *buffer++;
|
||||
while (subBlockSize != 0) {
|
||||
buffer += subBlockSize;
|
||||
subBlockSize = *buffer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (block_type == IMAGE_DESCRIPTOR) {
|
||||
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
|
||||
} else {
|
||||
std::cout << "Unrecognized block type: 0x" << std::hex << static_cast<int>(block_type) << std::dec << '\n';
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
block_type = *buffer++;
|
||||
}
|
||||
|
||||
return std::vector<uint8_t>{};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) {
|
||||
auto loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t> {
|
||||
return processGifStream(buffer, w, h);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,29 +64,12 @@ namespace GIF {
|
||||
uint8_t foreground_color, background_color;
|
||||
};
|
||||
|
||||
class Gif {
|
||||
public:
|
||||
// Descompone (uncompress) el bloque comprimido usando LZW.
|
||||
// Este método puede lanzar std::runtime_error en caso de error.
|
||||
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out);
|
||||
// Carga la paleta (global color table) a partir de un buffer,
|
||||
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
|
||||
auto loadPalette(const uint8_t *buffer) -> std::vector<uint32_t>;
|
||||
|
||||
// Carga la paleta (global color table) a partir de un buffer,
|
||||
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
|
||||
std::vector<uint32_t> loadPalette(const uint8_t *buffer);
|
||||
|
||||
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
|
||||
// asigna el ancho y alto mediante referencias.
|
||||
std::vector<uint8_t> loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
||||
|
||||
private:
|
||||
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
|
||||
std::vector<uint8_t> readSubBlocks(const uint8_t *&buffer);
|
||||
|
||||
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
|
||||
std::vector<uint8_t> processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits);
|
||||
|
||||
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
|
||||
std::vector<uint8_t> processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h);
|
||||
};
|
||||
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
|
||||
// asigna el ancho y alto mediante referencias.
|
||||
auto loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t>;
|
||||
|
||||
} // namespace GIF
|
||||
|
||||
@@ -17,16 +17,17 @@
|
||||
#ifndef NO_SHADERS
|
||||
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
|
||||
#endif
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/rendering/texture.hpp" // Para Texture
|
||||
#include "core/resources/asset.hpp" // Para Asset
|
||||
#include "core/resources/resource.hpp" // Para Resource
|
||||
#include "core/system/director.hpp" // Para Director::debug_config
|
||||
#include "game/options.hpp" // Para Video, video, Window, window
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
||||
#include "utils/param.hpp" // Para Param, param, ParamGame, ParamDebug
|
||||
#include "utils/utils.hpp" // Para toLower
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/rendering/texture.hpp" // Para Texture
|
||||
#include "core/resources/asset.hpp" // Para Asset
|
||||
#ifdef _DEBUG
|
||||
#include "core/resources/resource.hpp" // Para Resource (només a debug, font 8bithud)
|
||||
#include "core/system/director.hpp" // Para Director::debug_config (només a debug)
|
||||
#endif
|
||||
#include "game/options.hpp" // Para Video, video, Window, window
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
||||
#include "utils/param.hpp" // Para Param, param, ParamGame, ParamDebug
|
||||
|
||||
// Singleton
|
||||
Screen* Screen::instance = nullptr;
|
||||
@@ -82,12 +83,7 @@ auto Screen::get() -> Screen* { return Screen::instance; }
|
||||
|
||||
// Constructor
|
||||
Screen::Screen()
|
||||
: window_(nullptr),
|
||||
renderer_(nullptr),
|
||||
game_canvas_(nullptr),
|
||||
service_menu_(nullptr),
|
||||
notifier_(nullptr),
|
||||
src_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}),
|
||||
: src_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}),
|
||||
dst_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}) {
|
||||
// Arranca SDL VIDEO, crea la ventana y el renderizador
|
||||
initSDLVideo();
|
||||
@@ -320,49 +316,49 @@ void Screen::renderShake() {
|
||||
}
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
// Compone la línia d'informació de debug: "fps - driver - shader preset"
|
||||
auto Screen::buildDebugInfoText() const -> std::string {
|
||||
std::string info_text = std::to_string(fps_.last_value) + " fps";
|
||||
|
||||
// Driver GPU
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
const std::string DRIVER = shader_backend_->getDriverName();
|
||||
info_text += DRIVER.empty() ? "" : " - " + toLower(DRIVER);
|
||||
} else {
|
||||
info_text += " - sdl";
|
||||
}
|
||||
|
||||
// Shader + preset (només si està activat)
|
||||
if (!Options::video.shader.enabled) { return info_text; }
|
||||
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
||||
info_text += " - crtpi " + toLower(PRESET_NAME);
|
||||
} else {
|
||||
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||
info_text += " - postfx " + toLower(PRESET_NAME);
|
||||
}
|
||||
return info_text;
|
||||
}
|
||||
|
||||
// Muestra información por pantalla
|
||||
void Screen::renderInfo() const {
|
||||
if (debug_info_.show) {
|
||||
const Color GOLD(0xFF, 0xD7, 0x00);
|
||||
const Color GOLD_SHADOW = GOLD.DARKEN(150);
|
||||
if (!debug_info_.show) { return; }
|
||||
|
||||
// Construir texto: fps - driver - preset
|
||||
std::string info_text = std::to_string(fps_.last_value) + " fps";
|
||||
const Color GOLD(0xFF, 0xD7, 0x00);
|
||||
const Color GOLD_SHADOW = GOLD.darken(150);
|
||||
|
||||
// Driver GPU
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
const std::string DRIVER = shader_backend_->getDriverName();
|
||||
if (!DRIVER.empty()) {
|
||||
info_text += " - " + toLower(DRIVER);
|
||||
}
|
||||
} else {
|
||||
info_text += " - sdl";
|
||||
}
|
||||
|
||||
// Shader + preset
|
||||
if (Options::video.shader.enabled) {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
|
||||
info_text += " - crtpi " + toLower(PRESET_NAME);
|
||||
} else {
|
||||
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
|
||||
info_text += " - postfx " + toLower(PRESET_NAME);
|
||||
if (Options::video.supersampling.enabled) { info_text += " (ss)"; }
|
||||
}
|
||||
}
|
||||
|
||||
// Centrado arriba
|
||||
const int TEXT_WIDTH = debug_info_.text->length(info_text);
|
||||
const int X_POS = (static_cast<int>(param.game.width) - TEXT_WIDTH) / 2;
|
||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, info_text, 1, GOLD, 1, GOLD_SHADOW);
|
||||
const std::string INFO_TEXT = buildDebugInfoText();
|
||||
const int TEXT_WIDTH = debug_info_.text->length(INFO_TEXT);
|
||||
const int X_POS = (static_cast<int>(param.game.width) - TEXT_WIDTH) / 2;
|
||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, INFO_TEXT, 1, GOLD, 1, GOLD_SHADOW);
|
||||
|
||||
#ifdef RECORDING
|
||||
const std::string REC_TEXT = "recording";
|
||||
const int REC_WIDTH = debug_info_.text->length(REC_TEXT);
|
||||
const int REC_X = (static_cast<int>(param.game.width) - REC_WIDTH) / 2;
|
||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW);
|
||||
const std::string REC_TEXT = "recording";
|
||||
const int REC_WIDTH = debug_info_.text->length(REC_TEXT);
|
||||
const int REC_X = (static_cast<int>(param.game.width) - REC_WIDTH) / 2;
|
||||
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Inicializa shaders (SDL3GPU)
|
||||
@@ -380,14 +376,10 @@ void Screen::initShaders() {
|
||||
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
|
||||
}
|
||||
if (!self->shader_backend_->isHardwareAccelerated()) {
|
||||
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
|
||||
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (ok ? "OK" : "FAILED") << '\n';
|
||||
const bool OK = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
|
||||
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (OK ? "OK" : "FAILED") << '\n';
|
||||
}
|
||||
if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||
self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
|
||||
self->shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
|
||||
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
|
||||
if (!Options::video.shader.enabled) {
|
||||
// Passthrough: POSTFX con parámetros a cero
|
||||
self->shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
@@ -592,36 +584,25 @@ void Screen::nextCrtPiPreset() {
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna supersampling
|
||||
void Screen::toggleSupersampling() {
|
||||
Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
|
||||
auto* self = Screen::get();
|
||||
if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
|
||||
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
self->applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
self->applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica el preset PostFX activo al backend
|
||||
void Screen::applyCurrentPostFXPreset() {
|
||||
if (!shader_backend_) { return; }
|
||||
shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
Rendering::PostFXParams p{};
|
||||
if (Options::video.shader.enabled && !Options::postfx_presets.empty()) {
|
||||
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset));
|
||||
p.vignette = preset.vignette;
|
||||
p.scanlines = preset.scanlines;
|
||||
p.chroma = preset.chroma;
|
||||
p.chroma_min = preset.chroma_min;
|
||||
p.chroma_max = preset.chroma_max;
|
||||
p.mask = preset.mask;
|
||||
p.gamma = preset.gamma;
|
||||
p.curvature = preset.curvature;
|
||||
p.bleeding = preset.bleeding;
|
||||
p.flicker = preset.flicker;
|
||||
std::cout << "Screen::applyCurrentPostFXPreset: preset='" << preset.name << "' scan=" << p.scanlines << " vign=" << p.vignette << " chroma=" << p.chroma << '\n';
|
||||
p.scan_dark_ratio = preset.scan_dark_ratio;
|
||||
p.scan_dark_floor = preset.scan_dark_floor;
|
||||
p.scan_edge_soft = preset.scan_edge_soft;
|
||||
std::cout << "Screen::applyCurrentPostFXPreset: preset='" << preset.name << "' scan=" << p.scanlines << " vign=" << p.vignette << " chroma=[" << p.chroma_min << ".." << p.chroma_max << "]\n";
|
||||
}
|
||||
shader_backend_->setPostFXParams(p);
|
||||
}
|
||||
@@ -661,13 +642,12 @@ void Screen::toggleIntegerScale() {
|
||||
}
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar el V-Sync
|
||||
// Alterna entre activar y desactivar el V-Sync. ÚNICA via "user-facing" que
|
||||
// modifica la preferencia persistent (Options::video.vsync); la resta de
|
||||
// crides a setVSync apliquen l'estat al hardware sense escriure preferència.
|
||||
void Screen::toggleVSync() {
|
||||
Options::video.vsync = !Options::video.vsync;
|
||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setVSync(Options::video.vsync);
|
||||
}
|
||||
setVSync(Options::video.vsync);
|
||||
}
|
||||
|
||||
// Aplica Options::video.scale_mode a la textura del canvas de juego
|
||||
@@ -689,10 +669,16 @@ auto Screen::isHardwareAccelerated() -> bool {
|
||||
return self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated();
|
||||
}
|
||||
|
||||
// Establece el estado del V-Sync
|
||||
// Aplica V-Sync al renderer SDL i al backend GPU. NO toca la preferència
|
||||
// persistent (Options::video.vsync) — és responsabilitat del caller (menú
|
||||
// servei, toggleVSync). Així Resource::beginLoad/finishBoot poden canviar
|
||||
// el vsync efectiu durant el preload sense clobberar la preferència de
|
||||
// l'usuari (bug històric: el seu vsync=true es perdia entre llançaments).
|
||||
void Screen::setVSync(bool enabled) {
|
||||
Options::video.vsync = enabled;
|
||||
SDL_SetRenderVSync(renderer_, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setVSync(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene los punteros a los singletones
|
||||
@@ -717,7 +703,7 @@ void Screen::getSingletons() {
|
||||
// el viewport queda cacheado al tamaño de la ventana pequeña previa y el juego
|
||||
// se ve pequeño y centrado con barras negras alrededor.
|
||||
void Screen::applySettings() {
|
||||
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
|
||||
setVSync(Options::video.vsync);
|
||||
setFullscreenMode();
|
||||
adjustWindowSize();
|
||||
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
|
||||
@@ -44,13 +44,12 @@ class Screen {
|
||||
// --- Efectos visuales ---
|
||||
void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); }
|
||||
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); }
|
||||
static void toggleShaders(); // Alterna activar/desactivar shaders
|
||||
static void nextShader(); // Cambia entre PostFX y CrtPi
|
||||
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
||||
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
|
||||
static void toggleSupersampling(); // Alterna supersampling
|
||||
void toggleFilter(); // Alterna SDL_SCALEMODE_NEAREST ↔ SDL_SCALEMODE_LINEAR
|
||||
void applyFilter(); // Aplica Options::video.scale_mode a game_canvas_
|
||||
static void toggleShaders(); // Alterna activar/desactivar shaders
|
||||
static void nextShader(); // Cambia entre PostFX y CrtPi
|
||||
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
|
||||
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
|
||||
void toggleFilter(); // Alterna SDL_SCALEMODE_NEAREST ↔ SDL_SCALEMODE_LINEAR
|
||||
void applyFilter(); // Aplica Options::video.scale_mode a game_canvas_
|
||||
void toggleIntegerScale();
|
||||
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
|
||||
void setVSync(bool enabled); // Establece el estado del V-Sync
|
||||
@@ -66,7 +65,7 @@ class Screen {
|
||||
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; } // Obtiene el puntero al texto de Screen
|
||||
|
||||
// --- Display Monitor getters ---
|
||||
[[nodiscard]] auto getDisplayMonitorName() const -> std::string { return display_monitor_.name; }
|
||||
[[nodiscard]] auto getDisplayMonitorName() const -> const std::string& { return display_monitor_.name; }
|
||||
[[nodiscard]] auto getDisplayMonitorWidth() const -> int { return display_monitor_.width; }
|
||||
[[nodiscard]] auto getDisplayMonitorHeight() const -> int { return display_monitor_.height; }
|
||||
[[nodiscard]] auto getDisplayMonitorRefreshRate() const -> int { return display_monitor_.refresh_rate; }
|
||||
@@ -214,11 +213,11 @@ class Screen {
|
||||
#endif
|
||||
|
||||
// --- Objetos y punteros ---
|
||||
SDL_Window* window_; // Ventana de la aplicación
|
||||
SDL_Renderer* renderer_; // El renderizador de la ventana
|
||||
SDL_Texture* game_canvas_; // Textura donde se dibuja todo antes de volcarse al renderizador
|
||||
ServiceMenu* service_menu_; // Objeto para mostrar el menú de servicio
|
||||
Notifier* notifier_; // Objeto para mostrar las notificaciones por pantalla
|
||||
SDL_Window* window_ = nullptr; // Ventana de la aplicación
|
||||
SDL_Renderer* renderer_ = nullptr; // El renderizador de la ventana
|
||||
SDL_Texture* game_canvas_ = nullptr; // Textura donde se dibuja todo antes de volcarse al renderizador
|
||||
ServiceMenu* service_menu_ = nullptr; // Objeto para mostrar el menú de servicio
|
||||
Notifier* notifier_ = nullptr; // Objeto para mostrar las notificaciones por pantalla
|
||||
std::shared_ptr<Text> text_; // Objeto para escribir texto en pantalla
|
||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (SDL3GPU)
|
||||
|
||||
@@ -236,19 +235,20 @@ class Screen {
|
||||
#endif
|
||||
|
||||
// --- Métodos internos ---
|
||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||
void registerEmscriptenEventCallbacks(); // Registra callbacks nativos para restaurar el canvas en wasm (no-op fuera de emscripten)
|
||||
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
||||
void renderShake(); // Aplica el efecto de agitar la pantalla
|
||||
void renderInfo() const; // Muestra información por pantalla
|
||||
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
||||
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
||||
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
|
||||
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||
void renderOverlays(); // Renderiza todos los overlays y efectos
|
||||
void renderAttenuate(); // Atenúa la pantalla
|
||||
void createText(); // Crea el objeto de texto
|
||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||
void registerEmscriptenEventCallbacks(); // Registra callbacks nativos para restaurar el canvas en wasm (no-op fuera de emscripten)
|
||||
void renderFlash(); // Dibuja el efecto de flash en la pantalla
|
||||
void renderShake(); // Aplica el efecto de agitar la pantalla
|
||||
void renderInfo() const; // Muestra información por pantalla
|
||||
[[nodiscard]] auto buildDebugInfoText() const -> std::string; // Compone fps + driver + shader/preset para renderInfo
|
||||
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
|
||||
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
|
||||
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
|
||||
void adjustWindowSize(); // Calcula el tamaño de la ventana
|
||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||
void renderOverlays(); // Renderiza todos los overlays y efectos
|
||||
void renderAttenuate(); // Atenúa la pantalla
|
||||
void createText(); // Crea el objeto de texto
|
||||
|
||||
// --- Constructores y destructor privados (singleton) ---
|
||||
Screen(); // Constructor privado
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
// Fragment shader del shader "crtpi" (algoritme CRT-Pi): scanlines amb
|
||||
// pesos gaussians, multisample opcional, gamma i màscara de subpíxels.
|
||||
namespace Rendering::Msl {
|
||||
|
||||
inline constexpr const char* kCrtpiFrag = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct CrtPiUniforms {
|
||||
float scanline_weight;
|
||||
float scanline_gap_brightness;
|
||||
float bloom_factor;
|
||||
float input_gamma;
|
||||
float output_gamma;
|
||||
float mask_brightness;
|
||||
float curvature_x;
|
||||
float curvature_y;
|
||||
int mask_type;
|
||||
int enable_scanlines;
|
||||
int enable_multisample;
|
||||
int enable_gamma;
|
||||
int enable_curvature;
|
||||
int enable_sharper;
|
||||
float texture_width;
|
||||
float texture_height;
|
||||
};
|
||||
|
||||
static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) {
|
||||
float2 curvature = float2(cx, cy);
|
||||
float2 barrel_scale = 1.0f - (0.23f * curvature);
|
||||
coord *= screen_scale;
|
||||
coord -= 0.5f;
|
||||
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||
coord += coord * (curvature * rsq);
|
||||
coord *= barrel_scale;
|
||||
if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); }
|
||||
coord += 0.5f;
|
||||
coord /= screen_scale;
|
||||
return coord;
|
||||
}
|
||||
|
||||
static float crtpi_scan_weight(float dist, float sw, float gap) {
|
||||
return max(1.0f - dist * dist * sw, gap);
|
||||
}
|
||||
|
||||
static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) {
|
||||
float w = crtpi_scan_weight(dy, sw, gap);
|
||||
if (ms) {
|
||||
w += crtpi_scan_weight(dy - filter_w, sw, gap);
|
||||
w += crtpi_scan_weight(dy + filter_w, sw, gap);
|
||||
w *= 0.3333333f;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
fragment float4 crtpi_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> tex [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant CrtPiUniforms& u [[buffer(0)]]) {
|
||||
float2 tex_size = float2(u.texture_width, u.texture_height);
|
||||
// Amplada del filtre de scanline analític. 768 = alçada de referència
|
||||
// CRT a la qual es va tarar l'algoritme original; 3 = divisió per
|
||||
// subpíxel (R/G/B) del multisample. El resultat escala amb la textura
|
||||
// d'entrada, de manera que més alçada → filtre més fi.
|
||||
const float CRT_REFERENCE_HEIGHT = 768.0f;
|
||||
const float SUBPIXEL_DIV = 3.0f;
|
||||
float filter_width = (CRT_REFERENCE_HEIGHT / u.texture_height) / SUBPIXEL_DIV;
|
||||
float2 texcoord = in.uv;
|
||||
|
||||
if (u.enable_curvature != 0) {
|
||||
texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y);
|
||||
if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
float2 coord_in_pixels = texcoord * tex_size;
|
||||
float2 tc;
|
||||
float scan_weight;
|
||||
|
||||
if (u.enable_sharper != 0) {
|
||||
float2 temp = floor(coord_in_pixels) + 0.5f;
|
||||
tc = temp / tex_size;
|
||||
float2 deltas = coord_in_pixels - temp;
|
||||
scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float2 signs = sign(deltas);
|
||||
deltas.x *= 2.0f;
|
||||
deltas = deltas * deltas;
|
||||
deltas.y = deltas.y * deltas.y;
|
||||
deltas.x *= 0.5f;
|
||||
deltas.y *= 8.0f;
|
||||
deltas /= tex_size;
|
||||
deltas *= signs;
|
||||
tc = tc + deltas;
|
||||
} else {
|
||||
float temp_y = floor(coord_in_pixels.y) + 0.5f;
|
||||
float y_coord = temp_y / tex_size.y;
|
||||
float dy = coord_in_pixels.y - temp_y;
|
||||
scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float sign_y = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
dy *= 8.0f;
|
||||
dy /= tex_size.y;
|
||||
dy *= sign_y;
|
||||
tc = float2(texcoord.x, y_coord + dy);
|
||||
}
|
||||
|
||||
float3 colour = tex.sample(samp, tc).rgb;
|
||||
|
||||
if (u.enable_scanlines != 0) {
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); }
|
||||
colour *= scan_weight * u.bloom_factor;
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); }
|
||||
}
|
||||
|
||||
if (u.mask_type == 1) {
|
||||
float wm = fract(in.pos.x * 0.5f);
|
||||
float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness)
|
||||
: float3(1.0f, u.mask_brightness, 1.0f);
|
||||
colour *= mask;
|
||||
} else if (u.mask_type == 2) {
|
||||
float wm = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(u.mask_brightness);
|
||||
if (wm < 0.3333333f) mask.x = 1.0f;
|
||||
else if (wm < 0.6666666f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour *= mask;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
|
||||
} // namespace Rendering::Msl
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
// Fragment shader del shader "postfx": vignette, chroma, scanlines, mask,
|
||||
// gamma, curvature, bleeding i flicker. Els paràmetres venen via uniforms.
|
||||
//
|
||||
// IMPORTANT: mantenir sincronitzat a mà amb data/shaders/postfx.frag. SDL3 GPU
|
||||
// compila aquest string MSL en runtime; no hi ha generador automàtic. Qualsevol
|
||||
// canvi a la struct d'uniforms o a la lògica del GLSL cal replicar-lo ací al
|
||||
// mateix commit. Mida total = 64 bytes (4 × vec4).
|
||||
namespace Rendering::Msl {
|
||||
|
||||
inline constexpr const char* kPostfxFrag = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_min;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float flicker;
|
||||
float chroma_max;
|
||||
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
|
||||
float scan_dark_ratio;
|
||||
float scan_dark_floor;
|
||||
float scan_edge_soft;
|
||||
float pad3;
|
||||
};
|
||||
|
||||
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
|
||||
// NEAREST quan l'offset de chroma és subpíxel.
|
||||
static float sampleBilinearX(float2 uv_target, int channel, texture2d<float> scene, sampler samp) {
|
||||
float2 tex_size = float2(scene.get_width(), scene.get_height());
|
||||
float px = uv_target.x * tex_size.x - 0.5f;
|
||||
float p_floor = floor(px);
|
||||
float f = px - p_floor;
|
||||
float4 c0 = scene.sample(samp, float2((p_floor + 0.5f) / tex_size.x, uv_target.y));
|
||||
float4 c1 = scene.sample(samp, float2((p_floor + 1.5f) / tex_size.x, uv_target.y));
|
||||
return mix(c0[channel], c1[channel], f);
|
||||
}
|
||||
|
||||
static float3 rgb_to_ycc(float3 rgb) {
|
||||
return float3(
|
||||
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
|
||||
-0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f,
|
||||
0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f
|
||||
);
|
||||
}
|
||||
static float3 ycc_to_rgb(float3 ycc) {
|
||||
float y = ycc.x;
|
||||
float cb = ycc.y - 0.5f;
|
||||
float cr = ycc.z - 0.5f;
|
||||
return clamp(float3(
|
||||
y + 1.402f*cr,
|
||||
y - 0.344f*cb - 0.714f*cr,
|
||||
y + 1.772f*cb
|
||||
), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant PostFXUniforms& u [[buffer(0)]]) {
|
||||
float2 uv = in.uv;
|
||||
|
||||
if (u.curvature > 0.0f) {
|
||||
float2 c = uv - 0.5f;
|
||||
float rsq = dot(c, c);
|
||||
float2 dist = float2(0.05f, 0.1f) * u.curvature;
|
||||
float2 barrelScale = 1.0f - 0.23f * dist;
|
||||
c += c * (dist * rsq);
|
||||
c *= barrelScale;
|
||||
if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) {
|
||||
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
uv = c + 0.5f;
|
||||
}
|
||||
|
||||
float3 base = scene.sample(samp, uv).rgb;
|
||||
|
||||
float3 colour;
|
||||
if (u.bleeding > 0.0f) {
|
||||
float tw = float(scene.get_width());
|
||||
float step = 1.0f / tw;
|
||||
float3 ycc = rgb_to_ycc(base);
|
||||
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
|
||||
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
|
||||
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||
} else {
|
||||
colour = base;
|
||||
}
|
||||
|
||||
// Chroma — varia entre chroma_min i chroma_max via sinusoidal; si min == max
|
||||
// queda estàtic. Mostreig bilinear horitzontal per evitar el "tic-tac" del
|
||||
// NEAREST sampler amb offsets subpíxel.
|
||||
if (u.chroma_min > 0.0f || u.chroma_max > 0.0f) {
|
||||
float ca = mix(u.chroma_min, u.chroma_max, 0.5f + 0.5f * sin(u.time * 7.3f)) * 0.005f;
|
||||
colour.r = sampleBilinearX(uv + float2(ca, 0.0f), 0, scene, samp);
|
||||
colour.b = sampleBilinearX(uv - float2(ca, 0.0f), 2, scene, samp);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 lin = pow(colour, float3(2.4f));
|
||||
colour = mix(colour, lin, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Scanlines — 3 subpíxels per fila lògica (2 brillants + 1 fosca). Transició
|
||||
// suavitzada amb smoothstep d'ample ≈ 1 píxel físic (estil crtpi: filtratge
|
||||
// analític continu). scan_edge_soft = 0 recupera el step dur de l'original.
|
||||
if (u.scanline_strength > 0.0f) {
|
||||
float ps = max(u.pixel_scale, 1.0f);
|
||||
float sub = fract(uv.y * u.screen_height);
|
||||
float dark_center = 1.0f - u.scan_dark_ratio * 0.5f;
|
||||
float d = abs(sub - dark_center);
|
||||
d = min(d, 1.0f - d);
|
||||
float half_width = u.scan_dark_ratio * 0.5f;
|
||||
float softness = u.scan_edge_soft * 0.5f / ps;
|
||||
float band = 1.0f - smoothstep(half_width - softness, half_width + softness, d);
|
||||
float scan = mix(1.0f, u.scan_dark_floor, band);
|
||||
colour *= mix(1.0f, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 enc = pow(colour, float3(1.0f/2.2f));
|
||||
colour = mix(colour, enc, u.gamma_strength);
|
||||
}
|
||||
|
||||
float2 d = uv - 0.5f;
|
||||
float vignette = 1.0f - dot(d, d) * u.vignette_strength;
|
||||
colour *= clamp(vignette, 0.0f, 1.0f);
|
||||
|
||||
if (u.mask_strength > 0.0f) {
|
||||
float whichMask = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(0.80f);
|
||||
if (whichMask < 0.3333333f) mask.x = 1.0f;
|
||||
else if (whichMask < 0.6666667f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour = mix(colour, colour * mask, u.mask_strength);
|
||||
}
|
||||
|
||||
if (u.flicker > 0.0f) {
|
||||
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
|
||||
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
|
||||
} // namespace Rendering::Msl
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
// Vertex shader compartit per tots els pipelines de post-procés:
|
||||
// fullscreen-triangle que cobreix tota l'àrea del swapchain amb UVs a [0,1].
|
||||
namespace Rendering::Msl {
|
||||
|
||||
inline constexpr const char* kPostfxVert = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
||||
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
||||
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
||||
PostVOut out;
|
||||
out.pos = float4(positions[vid], 0.0, 1.0);
|
||||
out.uv = uvs[vid];
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
} // namespace Rendering::Msl
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -2,349 +2,22 @@
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <algorithm> // std::min, std::max, std::floor
|
||||
#include <cmath> // std::floor, std::ceil
|
||||
#include <algorithm> // std::min, std::max
|
||||
#include <cmath> // std::floor
|
||||
#include <cstring> // memcpy, strlen
|
||||
#include <iostream> // Para std::cout
|
||||
|
||||
#ifndef __APPLE__
|
||||
#include "core/rendering/sdl3gpu/spv/crtpi_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/downscale_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/postfx_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
|
||||
#include "core/rendering/sdl3gpu/spv/upscale_frag_spv.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
// ============================================================================
|
||||
// MSL shaders (Metal Shading Language) — macOS
|
||||
// ============================================================================
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
static const char* POSTFX_VERT_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
||||
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
||||
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
||||
PostVOut out;
|
||||
out.pos = float4(positions[vid], 0.0, 1.0);
|
||||
out.uv = uvs[vid];
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* POSTFX_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float oversample;
|
||||
float flicker;
|
||||
};
|
||||
|
||||
static float3 rgb_to_ycc(float3 rgb) {
|
||||
return float3(
|
||||
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
|
||||
-0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f,
|
||||
0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f
|
||||
);
|
||||
}
|
||||
static float3 ycc_to_rgb(float3 ycc) {
|
||||
float y = ycc.x;
|
||||
float cb = ycc.y - 0.5f;
|
||||
float cr = ycc.z - 0.5f;
|
||||
return clamp(float3(
|
||||
y + 1.402f*cr,
|
||||
y - 0.344f*cb - 0.714f*cr,
|
||||
y + 1.772f*cb
|
||||
), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant PostFXUniforms& u [[buffer(0)]]) {
|
||||
float2 uv = in.uv;
|
||||
|
||||
if (u.curvature > 0.0f) {
|
||||
float2 c = uv - 0.5f;
|
||||
float rsq = dot(c, c);
|
||||
float2 dist = float2(0.05f, 0.1f) * u.curvature;
|
||||
float2 barrelScale = 1.0f - 0.23f * dist;
|
||||
c += c * (dist * rsq);
|
||||
c *= barrelScale;
|
||||
if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) {
|
||||
return float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
uv = c + 0.5f;
|
||||
}
|
||||
|
||||
float3 base = scene.sample(samp, uv).rgb;
|
||||
|
||||
float3 colour;
|
||||
if (u.bleeding > 0.0f) {
|
||||
float tw = float(scene.get_width());
|
||||
float step = u.oversample / tw;
|
||||
float3 ycc = rgb_to_ycc(base);
|
||||
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
|
||||
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
|
||||
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
|
||||
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
|
||||
} else {
|
||||
colour = base;
|
||||
}
|
||||
|
||||
float ca = u.chroma_strength * 0.005f * (1.0f + 0.15f * sin(u.time * 7.3f));
|
||||
colour.r = scene.sample(samp, uv + float2(ca, 0.0f)).r;
|
||||
colour.b = scene.sample(samp, uv - float2(ca, 0.0f)).b;
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 lin = pow(colour, float3(2.4f));
|
||||
colour = mix(colour, lin, u.gamma_strength);
|
||||
}
|
||||
|
||||
const float SCAN_DARK_RATIO = 0.333f;
|
||||
const float SCAN_DARK_FLOOR = 0.42f;
|
||||
if (u.scanline_strength > 0.0f) {
|
||||
float ps = max(1.0f, round(u.pixel_scale));
|
||||
float frac_in_row = fract(uv.y * u.screen_height);
|
||||
float row_pos = floor(frac_in_row * ps);
|
||||
float bright_rows = (ps < 2.0f) ? ps : ((ps < 3.0f) ? 1.0f : floor(ps * (1.0f - SCAN_DARK_RATIO)));
|
||||
float is_dark = step(bright_rows, row_pos);
|
||||
float scan = mix(1.0f, SCAN_DARK_FLOOR, is_dark);
|
||||
colour *= mix(1.0f, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
if (u.gamma_strength > 0.0f) {
|
||||
float3 enc = pow(colour, float3(1.0f/2.2f));
|
||||
colour = mix(colour, enc, u.gamma_strength);
|
||||
}
|
||||
|
||||
float2 d = uv - 0.5f;
|
||||
float vignette = 1.0f - dot(d, d) * u.vignette_strength;
|
||||
colour *= clamp(vignette, 0.0f, 1.0f);
|
||||
|
||||
if (u.mask_strength > 0.0f) {
|
||||
float whichMask = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(0.80f);
|
||||
if (whichMask < 0.3333333f) mask.x = 1.0f;
|
||||
else if (whichMask < 0.6666667f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour = mix(colour, colour * mask, u.mask_strength);
|
||||
}
|
||||
|
||||
if (u.flicker > 0.0f) {
|
||||
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
|
||||
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
static const char* UPSCALE_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
struct VertOut { float4 pos [[position]]; float2 uv; };
|
||||
fragment float4 upscale_fs(VertOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler smp [[sampler(0)]])
|
||||
{
|
||||
return scene.sample(smp, in.uv);
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* DOWNSCALE_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
struct VertOut { float4 pos [[position]]; float2 uv; };
|
||||
struct DownscaleUniforms { int algorithm; float pad0; float pad1; float pad2; };
|
||||
|
||||
static float lanczos_w(float t, float a) {
|
||||
t = abs(t);
|
||||
if (t < 0.0001f) { return 1.0f; }
|
||||
if (t >= a) { return 0.0f; }
|
||||
const float PI = 3.14159265358979f;
|
||||
float pt = PI * t;
|
||||
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
|
||||
}
|
||||
|
||||
fragment float4 downscale_fs(VertOut in [[stage_in]],
|
||||
texture2d<float> source [[texture(0)]],
|
||||
sampler smp [[sampler(0)]],
|
||||
constant DownscaleUniforms& u [[buffer(0)]])
|
||||
{
|
||||
float2 src_size = float2(source.get_width(), source.get_height());
|
||||
float2 p = in.uv * src_size;
|
||||
float2 p_floor = floor(p);
|
||||
float a = (u.algorithm == 0) ? 2.0f : 3.0f;
|
||||
int win = int(a);
|
||||
float4 color = float4(0.0f);
|
||||
float weight_sum = 0.0f;
|
||||
for (int j = -win; j <= win; j++) {
|
||||
for (int i = -win; i <= win; i++) {
|
||||
float2 tap_center = p_floor + float2(float(i), float(j)) + 0.5f;
|
||||
float2 offset = tap_center - p;
|
||||
float w = lanczos_w(offset.x, a) * lanczos_w(offset.y, a);
|
||||
color += source.sample(smp, tap_center / src_size) * w;
|
||||
weight_sum += w;
|
||||
}
|
||||
}
|
||||
return (weight_sum > 0.0f) ? (color / weight_sum) : float4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
)";
|
||||
static const char* CRTPI_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct CrtPiUniforms {
|
||||
float scanline_weight;
|
||||
float scanline_gap_brightness;
|
||||
float bloom_factor;
|
||||
float input_gamma;
|
||||
float output_gamma;
|
||||
float mask_brightness;
|
||||
float curvature_x;
|
||||
float curvature_y;
|
||||
int mask_type;
|
||||
int enable_scanlines;
|
||||
int enable_multisample;
|
||||
int enable_gamma;
|
||||
int enable_curvature;
|
||||
int enable_sharper;
|
||||
float texture_width;
|
||||
float texture_height;
|
||||
};
|
||||
|
||||
static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) {
|
||||
float2 curvature = float2(cx, cy);
|
||||
float2 barrel_scale = 1.0f - (0.23f * curvature);
|
||||
coord *= screen_scale;
|
||||
coord -= 0.5f;
|
||||
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||
coord += coord * (curvature * rsq);
|
||||
coord *= barrel_scale;
|
||||
if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); }
|
||||
coord += 0.5f;
|
||||
coord /= screen_scale;
|
||||
return coord;
|
||||
}
|
||||
|
||||
static float crtpi_scan_weight(float dist, float sw, float gap) {
|
||||
return max(1.0f - dist * dist * sw, gap);
|
||||
}
|
||||
|
||||
static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) {
|
||||
float w = crtpi_scan_weight(dy, sw, gap);
|
||||
if (ms) {
|
||||
w += crtpi_scan_weight(dy - filter_w, sw, gap);
|
||||
w += crtpi_scan_weight(dy + filter_w, sw, gap);
|
||||
w *= 0.3333333f;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
fragment float4 crtpi_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> tex [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant CrtPiUniforms& u [[buffer(0)]]) {
|
||||
float2 tex_size = float2(u.texture_width, u.texture_height);
|
||||
float filter_width = (768.0f / u.texture_height) / 3.0f;
|
||||
float2 texcoord = in.uv;
|
||||
|
||||
if (u.enable_curvature != 0) {
|
||||
texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y);
|
||||
if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
float2 coord_in_pixels = texcoord * tex_size;
|
||||
float2 tc;
|
||||
float scan_weight;
|
||||
|
||||
if (u.enable_sharper != 0) {
|
||||
float2 temp = floor(coord_in_pixels) + 0.5f;
|
||||
tc = temp / tex_size;
|
||||
float2 deltas = coord_in_pixels - temp;
|
||||
scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float2 signs = sign(deltas);
|
||||
deltas.x *= 2.0f;
|
||||
deltas = deltas * deltas;
|
||||
deltas.y = deltas.y * deltas.y;
|
||||
deltas.x *= 0.5f;
|
||||
deltas.y *= 8.0f;
|
||||
deltas /= tex_size;
|
||||
deltas *= signs;
|
||||
tc = tc + deltas;
|
||||
} else {
|
||||
float temp_y = floor(coord_in_pixels.y) + 0.5f;
|
||||
float y_coord = temp_y / tex_size.y;
|
||||
float dy = coord_in_pixels.y - temp_y;
|
||||
scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
|
||||
float sign_y = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
dy *= 8.0f;
|
||||
dy /= tex_size.y;
|
||||
dy *= sign_y;
|
||||
tc = float2(texcoord.x, y_coord + dy);
|
||||
}
|
||||
|
||||
float3 colour = tex.sample(samp, tc).rgb;
|
||||
|
||||
if (u.enable_scanlines != 0) {
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); }
|
||||
colour *= scan_weight * u.bloom_factor;
|
||||
if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); }
|
||||
}
|
||||
|
||||
if (u.mask_type == 1) {
|
||||
float wm = fract(in.pos.x * 0.5f);
|
||||
float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness)
|
||||
: float3(1.0f, u.mask_brightness, 1.0f);
|
||||
colour *= mask;
|
||||
} else if (u.mask_type == 2) {
|
||||
float wm = fract(in.pos.x * 0.3333333f);
|
||||
float3 mask = float3(u.mask_brightness);
|
||||
if (wm < 0.3333333f) mask.x = 1.0f;
|
||||
else if (wm < 0.6666666f) mask.y = 1.0f;
|
||||
else mask.z = 1.0f;
|
||||
colour *= mask;
|
||||
}
|
||||
|
||||
return float4(colour, 1.0f);
|
||||
}
|
||||
)";
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
#endif // __APPLE__
|
||||
#include "core/rendering/sdl3gpu/msl/crtpi_frag.msl.h"
|
||||
#include "core/rendering/sdl3gpu/msl/postfx_frag.msl.h"
|
||||
#include "core/rendering/sdl3gpu/msl/postfx_vert.msl.h"
|
||||
#endif
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
@@ -374,7 +47,6 @@ namespace Rendering {
|
||||
game_width_ = static_cast<int>(fw);
|
||||
game_height_ = static_cast<int>(fh);
|
||||
uniforms_.screen_height = static_cast<float>(game_height_);
|
||||
uniforms_.oversample = static_cast<float>(oversample_);
|
||||
|
||||
// 1. GPU disabled by config
|
||||
if (preferred_driver_ == "none") {
|
||||
@@ -412,7 +84,7 @@ namespace Rendering {
|
||||
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode(vsync_));
|
||||
}
|
||||
|
||||
// 3. Create scene texture (always game resolution)
|
||||
// 3. Create scene texture (game resolution)
|
||||
SDL_GPUTextureCreateInfo tex_info = {};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
@@ -428,9 +100,7 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
ss_factor_ = 0;
|
||||
|
||||
// 4. Create upload transfer buffer (always game resolution)
|
||||
// 4. Create upload transfer buffer (game resolution)
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
|
||||
@@ -441,7 +111,7 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. Create samplers
|
||||
// 5. Create nearest sampler
|
||||
SDL_GPUSamplerCreateInfo samp_info = {};
|
||||
samp_info.min_filter = SDL_GPU_FILTER_NEAREST;
|
||||
samp_info.mag_filter = SDL_GPU_FILTER_NEAREST;
|
||||
@@ -456,20 +126,6 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUSamplerCreateInfo lsamp_info = {};
|
||||
lsamp_info.min_filter = SDL_GPU_FILTER_LINEAR;
|
||||
lsamp_info.mag_filter = SDL_GPU_FILTER_LINEAR;
|
||||
lsamp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
lsamp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
lsamp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
lsamp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
linear_sampler_ = SDL_CreateGPUSampler(device_, &lsamp_info);
|
||||
if (linear_sampler_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: failed to create linear sampler: " << SDL_GetError() << '\n';
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 6. Create pipelines
|
||||
if (!createPipeline()) {
|
||||
cleanup();
|
||||
@@ -486,15 +142,14 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createPipeline — PostFX + Upscale + PostFX offscreen + Downscale pipelines
|
||||
// createPipeline — PostFX → swapchain
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::createPipeline() -> bool { // NOLINT(readability-function-cognitive-complexity)
|
||||
auto SDL3GPUShader::createPipeline() -> bool {
|
||||
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||
|
||||
// ---- PostFX pipeline (→ swapchain) ----
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, Msl::kPostfxFrag, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
@@ -534,114 +189,6 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Upscale pipeline (scene → scaled_texture_) ----
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* uvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ufrag = createShaderMSL(device_, UPSCALE_FRAG_MSL, "upscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#else
|
||||
SDL_GPUShader* uvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ufrag = createShaderSPIRV(device_, kupscale_frag_spv, kupscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#endif
|
||||
|
||||
if ((uvert == nullptr) || (ufrag == nullptr)) {
|
||||
if (uvert != nullptr) { SDL_ReleaseGPUShader(device_, uvert); }
|
||||
if (ufrag != nullptr) { SDL_ReleaseGPUShader(device_, ufrag); }
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUColorTargetDescription upscale_ct = {};
|
||||
upscale_ct.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
upscale_ct.blend_state = no_blend;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo upscale_pi = {};
|
||||
upscale_pi.vertex_shader = uvert;
|
||||
upscale_pi.fragment_shader = ufrag;
|
||||
upscale_pi.vertex_input_state = no_input;
|
||||
upscale_pi.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
upscale_pi.target_info.num_color_targets = 1;
|
||||
upscale_pi.target_info.color_target_descriptions = &upscale_ct;
|
||||
|
||||
upscale_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &upscale_pi);
|
||||
SDL_ReleaseGPUShader(device_, uvert);
|
||||
SDL_ReleaseGPUShader(device_, ufrag);
|
||||
|
||||
if (upscale_pipeline_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: upscale pipeline creation failed: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- PostFX offscreen pipeline (→ B8G8R8A8 texture, for Lanczos path) ----
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* ofvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* offrag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* ofvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* offrag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#endif
|
||||
|
||||
if ((ofvert == nullptr) || (offrag == nullptr)) {
|
||||
if (ofvert != nullptr) { SDL_ReleaseGPUShader(device_, ofvert); }
|
||||
if (offrag != nullptr) { SDL_ReleaseGPUShader(device_, offrag); }
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUColorTargetDescription offscreen_ct = {};
|
||||
offscreen_ct.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
offscreen_ct.blend_state = no_blend;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo offscreen_pi = {};
|
||||
offscreen_pi.vertex_shader = ofvert;
|
||||
offscreen_pi.fragment_shader = offrag;
|
||||
offscreen_pi.vertex_input_state = no_input;
|
||||
offscreen_pi.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
offscreen_pi.target_info.num_color_targets = 1;
|
||||
offscreen_pi.target_info.color_target_descriptions = &offscreen_ct;
|
||||
|
||||
postfx_offscreen_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &offscreen_pi);
|
||||
SDL_ReleaseGPUShader(device_, ofvert);
|
||||
SDL_ReleaseGPUShader(device_, offrag);
|
||||
|
||||
if (postfx_offscreen_pipeline_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: PostFX offscreen pipeline creation failed: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Downscale pipeline (Lanczos → swapchain) ----
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* dvert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* dfrag = createShaderMSL(device_, DOWNSCALE_FRAG_MSL, "downscale_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* dvert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* dfrag = createShaderSPIRV(device_, kdownscale_frag_spv, kdownscale_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#endif
|
||||
|
||||
if ((dvert == nullptr) || (dfrag == nullptr)) {
|
||||
if (dvert != nullptr) { SDL_ReleaseGPUShader(device_, dvert); }
|
||||
if (dfrag != nullptr) { SDL_ReleaseGPUShader(device_, dfrag); }
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUColorTargetDescription downscale_ct = {};
|
||||
downscale_ct.format = SWAPCHAIN_FMT;
|
||||
downscale_ct.blend_state = no_blend;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo downscale_pi = {};
|
||||
downscale_pi.vertex_shader = dvert;
|
||||
downscale_pi.fragment_shader = dfrag;
|
||||
downscale_pi.vertex_input_state = no_input;
|
||||
downscale_pi.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
downscale_pi.target_info.num_color_targets = 1;
|
||||
downscale_pi.target_info.color_target_descriptions = &downscale_ct;
|
||||
|
||||
downscale_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &downscale_pi);
|
||||
SDL_ReleaseGPUShader(device_, dvert);
|
||||
SDL_ReleaseGPUShader(device_, dfrag);
|
||||
|
||||
if (downscale_pipeline_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: downscale pipeline creation failed: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -652,8 +199,8 @@ namespace Rendering {
|
||||
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, CRTPI_FRAG_MSL, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, Msl::kCrtpiFrag, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
@@ -691,7 +238,7 @@ namespace Rendering {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// uploadPixels — direct memcpy, GPU handles upscale
|
||||
// uploadPixels — direct memcpy
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) {
|
||||
if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; }
|
||||
@@ -702,29 +249,16 @@ namespace Rendering {
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
|
||||
std::memcpy(mapped, pixels, static_cast<size_t>(width) * height * 4);
|
||||
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// render — 3-path: CrtPi direct, PostFX+Lanczos, PostFX direct
|
||||
// render — CrtPi direct OR PostFX direct → swapchain
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::render() { // NOLINT(readability-function-cognitive-complexity)
|
||||
void SDL3GPUShader::render() {
|
||||
if (!is_initialized_) { return; }
|
||||
|
||||
// SS factor calculation
|
||||
if (oversample_ > 1 && game_height_ > 0) {
|
||||
int win_w = 0;
|
||||
int win_h = 0;
|
||||
SDL_GetWindowSizeInPixels(window_, &win_w, &win_h);
|
||||
const float ZOOM = static_cast<float>(win_h) / static_cast<float>(game_height_);
|
||||
const int NEED_FACTOR = calcSsFactor(ZOOM);
|
||||
if (NEED_FACTOR != ss_factor_) {
|
||||
SDL_WaitForGPUIdle(device_);
|
||||
recreateScaledTexture(NEED_FACTOR);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
|
||||
if (cmd == nullptr) {
|
||||
std::cout << "SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: " << SDL_GetError() << '\n';
|
||||
@@ -750,25 +284,6 @@ namespace Rendering {
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
}
|
||||
|
||||
// ---- Upscale pass: scene → scaled_texture_ ----
|
||||
if (oversample_ > 1 && scaled_texture_ != nullptr && upscale_pipeline_ != nullptr) {
|
||||
SDL_GPUColorTargetInfo upscale_target = {};
|
||||
upscale_target.texture = scaled_texture_;
|
||||
upscale_target.load_op = SDL_GPU_LOADOP_DONT_CARE;
|
||||
upscale_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
|
||||
SDL_GPURenderPass* upass = SDL_BeginGPURenderPass(cmd, &upscale_target, 1, nullptr);
|
||||
if (upass != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(upass, upscale_pipeline_);
|
||||
SDL_GPUTextureSamplerBinding ubinding = {};
|
||||
ubinding.texture = scene_texture_;
|
||||
ubinding.sampler = (linear_upscale_ && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_;
|
||||
SDL_BindGPUFragmentSamplers(upass, 0, &ubinding, 1);
|
||||
SDL_DrawGPUPrimitives(upass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(upass);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Acquire swapchain texture ----
|
||||
SDL_GPUTexture* swapchain = nullptr;
|
||||
Uint32 sw = 0;
|
||||
@@ -802,117 +317,37 @@ namespace Rendering {
|
||||
vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
|
||||
vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);
|
||||
|
||||
if (oversample_ > 1 && ss_factor_ > 0) {
|
||||
uniforms_.pixel_scale = static_cast<float>(ss_factor_);
|
||||
} else {
|
||||
uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.0F;
|
||||
}
|
||||
uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.0F;
|
||||
uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||
uniforms_.oversample = (oversample_ > 1 && ss_factor_ > 0)
|
||||
? static_cast<float>(ss_factor_)
|
||||
: 1.0F;
|
||||
|
||||
// ---- Path A: CrtPi direct ----
|
||||
if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) {
|
||||
SDL_GPUColorTargetInfo ct = {};
|
||||
ct.texture = swapchain;
|
||||
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
SDL_GPUColorTargetInfo ct = {};
|
||||
ct.texture = swapchain;
|
||||
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
||||
if (pass != nullptr) {
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
||||
if (pass != nullptr) {
|
||||
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(pass, &vp);
|
||||
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = scene_texture_;
|
||||
binding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||
|
||||
if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(pass, crtpi_pipeline_);
|
||||
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(pass, &vp);
|
||||
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = scene_texture_;
|
||||
binding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||
|
||||
crtpi_uniforms_.texture_width = static_cast<float>(game_width_);
|
||||
crtpi_uniforms_.texture_height = static_cast<float>(game_height_);
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &crtpi_uniforms_, sizeof(CrtPiUniforms));
|
||||
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Path B: PostFX + Lanczos (SS active + algo selected) ----
|
||||
const bool USE_LANCZOS = (oversample_ > 1 && downscale_algo_ > 0 && scaled_texture_ != nullptr && postfx_texture_ != nullptr && postfx_offscreen_pipeline_ != nullptr && downscale_pipeline_ != nullptr);
|
||||
|
||||
if (USE_LANCZOS) {
|
||||
// Pass B1: PostFX → postfx_texture_
|
||||
SDL_GPUColorTargetInfo postfx_ct = {};
|
||||
postfx_ct.texture = postfx_texture_;
|
||||
postfx_ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
postfx_ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
postfx_ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* ppass = SDL_BeginGPURenderPass(cmd, &postfx_ct, 1, nullptr);
|
||||
if (ppass != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(ppass, postfx_offscreen_pipeline_);
|
||||
SDL_GPUTextureSamplerBinding pbinding = {};
|
||||
pbinding.texture = scaled_texture_;
|
||||
pbinding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(ppass, 0, &pbinding, 1);
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
|
||||
SDL_DrawGPUPrimitives(ppass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(ppass);
|
||||
}
|
||||
|
||||
// Pass B2: Lanczos downscale → swapchain
|
||||
SDL_GPUColorTargetInfo ds_ct = {};
|
||||
ds_ct.texture = swapchain;
|
||||
ds_ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
ds_ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
ds_ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* dpass = SDL_BeginGPURenderPass(cmd, &ds_ct, 1, nullptr);
|
||||
if (dpass != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(dpass, downscale_pipeline_);
|
||||
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(dpass, &vp);
|
||||
SDL_GPUTextureSamplerBinding dbinding = {};
|
||||
dbinding.texture = postfx_texture_;
|
||||
dbinding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(dpass, 0, &dbinding, 1);
|
||||
DownscaleUniforms du = {.algorithm = downscale_algo_ - 1, .pad0 = 0.0F, .pad1 = 0.0F, .pad2 = 0.0F};
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &du, sizeof(DownscaleUniforms));
|
||||
SDL_DrawGPUPrimitives(dpass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(dpass);
|
||||
}
|
||||
} else {
|
||||
// ---- Path C: PostFX direct → swapchain ----
|
||||
SDL_GPUColorTargetInfo ct = {};
|
||||
ct.texture = swapchain;
|
||||
ct.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
||||
if (pass != nullptr) {
|
||||
} else {
|
||||
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
|
||||
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
|
||||
SDL_SetGPUViewport(pass, &vp);
|
||||
|
||||
SDL_GPUTexture* input_texture = (oversample_ > 1 && scaled_texture_ != nullptr) ? scaled_texture_ : scene_texture_;
|
||||
SDL_GPUSampler* active_sampler = (oversample_ > 1 && linear_sampler_ != nullptr) ? linear_sampler_ : sampler_;
|
||||
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = input_texture;
|
||||
binding.sampler = active_sampler;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
@@ -942,13 +377,7 @@ namespace Rendering {
|
||||
|
||||
release_pipeline(pipeline_);
|
||||
release_pipeline(crtpi_pipeline_);
|
||||
release_pipeline(postfx_offscreen_pipeline_);
|
||||
release_pipeline(upscale_pipeline_);
|
||||
release_pipeline(downscale_pipeline_);
|
||||
release_texture(scene_texture_);
|
||||
release_texture(scaled_texture_);
|
||||
release_texture(postfx_texture_);
|
||||
ss_factor_ = 0;
|
||||
if (upload_buffer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
|
||||
upload_buffer_ = nullptr;
|
||||
@@ -957,10 +386,6 @@ namespace Rendering {
|
||||
SDL_ReleaseGPUSampler(device_, sampler_);
|
||||
sampler_ = nullptr;
|
||||
}
|
||||
if (linear_sampler_ != nullptr) {
|
||||
SDL_ReleaseGPUSampler(device_, linear_sampler_);
|
||||
linear_sampler_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,13 +453,17 @@ namespace Rendering {
|
||||
|
||||
void SDL3GPUShader::setPostFXParams(const PostFXParams& p) {
|
||||
uniforms_.vignette_strength = p.vignette;
|
||||
uniforms_.chroma_strength = p.chroma;
|
||||
uniforms_.chroma_min = p.chroma_min;
|
||||
uniforms_.chroma_max = p.chroma_max;
|
||||
uniforms_.scanline_strength = p.scanlines;
|
||||
uniforms_.mask_strength = p.mask;
|
||||
uniforms_.gamma_strength = p.gamma;
|
||||
uniforms_.curvature = p.curvature;
|
||||
uniforms_.bleeding = p.bleeding;
|
||||
uniforms_.flicker = p.flicker;
|
||||
uniforms_.scan_dark_ratio = p.scan_dark_ratio;
|
||||
uniforms_.scan_dark_floor = p.scan_dark_floor;
|
||||
uniforms_.scan_edge_soft = p.scan_edge_soft;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setCrtPiParams(const CrtPiParams& p) {
|
||||
@@ -1080,28 +509,6 @@ namespace Rendering {
|
||||
integer_scale_ = integer_scale;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setOversample(int factor) {
|
||||
const int NEW_FACTOR = std::max(1, factor);
|
||||
if (NEW_FACTOR == oversample_) { return; }
|
||||
oversample_ = NEW_FACTOR;
|
||||
if (is_initialized_) {
|
||||
reinitTexturesAndBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setLinearUpscale(bool linear) {
|
||||
linear_upscale_ = linear;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setDownscaleAlgo(int algo) {
|
||||
downscale_algo_ = std::max(0, std::min(algo, 2));
|
||||
}
|
||||
|
||||
auto SDL3GPUShader::getSsTextureSize() const -> std::pair<int, int> {
|
||||
if (ss_factor_ <= 1) { return {0, 0}; }
|
||||
return {game_width_ * ss_factor_, game_height_ * ss_factor_};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// reinitTexturesAndBuffer
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1113,18 +520,12 @@ namespace Rendering {
|
||||
SDL_ReleaseGPUTexture(device_, scene_texture_);
|
||||
scene_texture_ = nullptr;
|
||||
}
|
||||
if (scaled_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
}
|
||||
ss_factor_ = 0;
|
||||
if (upload_buffer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
|
||||
upload_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
uniforms_.screen_height = static_cast<float>(game_height_);
|
||||
uniforms_.oversample = static_cast<float>(oversample_);
|
||||
|
||||
SDL_GPUTextureCreateInfo tex_info = {};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
@@ -1151,55 +552,7 @@ namespace Rendering {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "SDL3GPUShader: reinit — scene " << game_width_ << "x" << game_height_ << ", SS " << (oversample_ > 1 ? "on" : "off") << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
auto SDL3GPUShader::calcSsFactor(float zoom) -> int {
|
||||
const int MULTIPLE = 3;
|
||||
const int N = static_cast<int>(std::ceil(zoom / static_cast<float>(MULTIPLE)));
|
||||
return std::max(1, N) * MULTIPLE;
|
||||
}
|
||||
|
||||
auto SDL3GPUShader::recreateScaledTexture(int factor) -> bool {
|
||||
if (scaled_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
}
|
||||
if (postfx_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, postfx_texture_);
|
||||
postfx_texture_ = nullptr;
|
||||
}
|
||||
ss_factor_ = 0;
|
||||
|
||||
const int W = game_width_ * factor;
|
||||
const int H = game_height_ * factor;
|
||||
|
||||
SDL_GPUTextureCreateInfo info = {};
|
||||
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
|
||||
info.width = static_cast<Uint32>(W);
|
||||
info.height = static_cast<Uint32>(H);
|
||||
info.layer_count_or_depth = 1;
|
||||
info.num_levels = 1;
|
||||
|
||||
scaled_texture_ = SDL_CreateGPUTexture(device_, &info);
|
||||
if (scaled_texture_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: failed to create scaled texture " << W << "x" << H << ": " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
postfx_texture_ = SDL_CreateGPUTexture(device_, &info);
|
||||
if (postfx_texture_ == nullptr) {
|
||||
std::cout << "SDL3GPUShader: failed to create postfx texture " << W << "x" << H << ": " << SDL_GetError() << '\n';
|
||||
SDL_ReleaseGPUTexture(device_, scaled_texture_);
|
||||
scaled_texture_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
ss_factor_ = factor;
|
||||
std::cout << "SDL3GPUShader: scaled+postfx textures " << W << "x" << H << " (factor " << factor << "x)" << '\n';
|
||||
std::cout << "SDL3GPUShader: reinit — scene " << game_width_ << "x" << game_height_ << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,25 +4,32 @@
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/shader_backend.hpp"
|
||||
|
||||
// PostFX uniforms pushed to fragment stage each frame.
|
||||
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
|
||||
// 16 floats = 64 bytes (4 × vec4) — std140/scalar layout for Vulkan + Metal.
|
||||
struct PostFXUniforms {
|
||||
// vec4 #0
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float chroma_min;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
// vec4 #1
|
||||
float mask_strength;
|
||||
float gamma_strength;
|
||||
float curvature;
|
||||
float bleeding;
|
||||
// vec4 #2
|
||||
float pixel_scale;
|
||||
float time;
|
||||
float oversample;
|
||||
float flicker;
|
||||
float chroma_max;
|
||||
// vec4 #3 — paràmetres de scanlines exposats al preset YAML
|
||||
float scan_dark_ratio;
|
||||
float scan_dark_floor;
|
||||
float scan_edge_soft;
|
||||
float pad3;
|
||||
};
|
||||
|
||||
// CrtPi uniforms pushed to fragment stage each frame.
|
||||
@@ -46,22 +53,13 @@ struct CrtPiUniforms {
|
||||
float texture_height;
|
||||
};
|
||||
|
||||
// Downscale uniforms for Lanczos downscale fragment stage.
|
||||
// 1 int + 3 floats = 16 bytes.
|
||||
struct DownscaleUniforms {
|
||||
int algorithm;
|
||||
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)
|
||||
*
|
||||
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||
* → [Upscale →] PostFX/CrtPi render pass → [Lanczos downscale →] swapchain → present
|
||||
* → PostFX/CrtPi render pass → swapchain → present
|
||||
*/
|
||||
class SDL3GPUShader : public ShaderBackend {
|
||||
public:
|
||||
@@ -85,13 +83,6 @@ namespace Rendering {
|
||||
void setPostFXParams(const PostFXParams& p) override;
|
||||
void setVSync(bool vsync) override;
|
||||
void setScaleMode(bool integer_scale) override;
|
||||
void setOversample(int factor) override;
|
||||
|
||||
void setLinearUpscale(bool linear) override;
|
||||
[[nodiscard]] auto isLinearUpscale() const -> bool override { return linear_upscale_; }
|
||||
void setDownscaleAlgo(int algo) override;
|
||||
[[nodiscard]] auto getDownscaleAlgo() const -> int override { return downscale_algo_; }
|
||||
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||
|
||||
void setActiveShader(ShaderType type) override;
|
||||
void setCrtPiParams(const CrtPiParams& p) override;
|
||||
@@ -116,39 +107,27 @@ namespace Rendering {
|
||||
auto createPipeline() -> bool;
|
||||
auto createCrtPiPipeline() -> bool;
|
||||
auto reinitTexturesAndBuffer() -> bool;
|
||||
auto recreateScaledTexture(int factor) -> bool;
|
||||
static auto calcSsFactor(float zoom) -> int;
|
||||
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr;
|
||||
SDL_GPUTexture* scene_texture_ = nullptr;
|
||||
SDL_GPUTexture* scaled_texture_ = nullptr;
|
||||
SDL_GPUTexture* postfx_texture_ = nullptr;
|
||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
SDL_GPUSampler* linear_sampler_ = nullptr;
|
||||
|
||||
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};
|
||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_min = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .chroma_max = 0.15F, .scan_dark_ratio = 0.333F, .scan_dark_floor = 0.42F, .scan_edge_soft = 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;
|
||||
|
||||
int game_width_ = 0;
|
||||
int game_height_ = 0;
|
||||
int ss_factor_ = 0;
|
||||
int oversample_ = 1;
|
||||
int downscale_algo_ = 1;
|
||||
std::string driver_name_;
|
||||
std::string preferred_driver_;
|
||||
bool is_initialized_ = false;
|
||||
bool vsync_ = true;
|
||||
bool integer_scale_ = false;
|
||||
bool linear_upscale_ = false;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -10357,5 +10357,6 @@ static const uint8_t kcrtpi_frag_spv[] = {
|
||||
0x38,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00};
|
||||
0x00,
|
||||
};
|
||||
static const size_t kcrtpi_frag_spv_size = 10356;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1445,5 +1445,6 @@ static const uint8_t kpostfx_vert_spv[] = {
|
||||
0x38,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00};
|
||||
0x00,
|
||||
};
|
||||
static const size_t kpostfx_vert_spv_size = 1444;
|
||||
|
||||
@@ -1,633 +0,0 @@
|
||||
#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;
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/** @brief Identificador del shader de post-procesado activo */
|
||||
enum class ShaderType { POSTFX,
|
||||
enum class ShaderType : std::uint8_t { POSTFX,
|
||||
CRTPI };
|
||||
|
||||
/**
|
||||
@@ -17,12 +17,19 @@ namespace Rendering {
|
||||
struct PostFXParams {
|
||||
float vignette = 0.0F;
|
||||
float scanlines = 0.0F;
|
||||
float chroma = 0.0F;
|
||||
// Aberració cromàtica — varia entre min i max via sinusoidal; si coincideixen
|
||||
// queda estàtica. Permet un "floor" perquè la imatge mai sigui lliure de chroma.
|
||||
float chroma_min = 0.0F;
|
||||
float chroma_max = 0.0F;
|
||||
float mask = 0.0F;
|
||||
float gamma = 0.0F;
|
||||
float curvature = 0.0F;
|
||||
float bleeding = 0.0F;
|
||||
float flicker = 0.0F;
|
||||
// Forma de les scanlines — 3 subpíxels per fila lògica per defecte.
|
||||
float scan_dark_ratio{0.333F};
|
||||
float scan_dark_floor{0.42F};
|
||||
float scan_edge_soft{1.0F};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -65,15 +72,6 @@ namespace Rendering {
|
||||
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
|
||||
virtual void setVSync(bool /*vsync*/) {}
|
||||
virtual void setScaleMode(bool /*integer_scale*/) {}
|
||||
virtual void setOversample(int /*factor*/) {}
|
||||
|
||||
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||
|
||||
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||
|
||||
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||
|
||||
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ auto CardSprite::enable() -> bool {
|
||||
|
||||
// Ángulo inicial
|
||||
rotate_.angle = start_angle_;
|
||||
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
|
||||
rotate_.center = {.x = pos_.w / 2.0F, .y = pos_.h / 2.0F};
|
||||
|
||||
shadow_visible_ = true;
|
||||
return true;
|
||||
@@ -55,7 +55,7 @@ void CardSprite::startExit() {
|
||||
// Rotación continua
|
||||
rotate_.enabled = true;
|
||||
rotate_.amount = exit_rotate_amount_;
|
||||
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
|
||||
rotate_.center = {.x = pos_.w / 2.0F, .y = pos_.h / 2.0F};
|
||||
}
|
||||
|
||||
// Actualiza según el estado
|
||||
@@ -80,7 +80,7 @@ void CardSprite::updateEntering(float delta_time) {
|
||||
double eased = entry_easing_(static_cast<double>(progress));
|
||||
|
||||
// Zoom: de start_zoom_ a 1.0 con rebote
|
||||
auto current_zoom = static_cast<float>(start_zoom_ + (1.0 - start_zoom_) * eased);
|
||||
auto current_zoom = static_cast<float>(start_zoom_ + ((1.0 - start_zoom_) * eased));
|
||||
horizontal_zoom_ = current_zoom;
|
||||
vertical_zoom_ = current_zoom;
|
||||
|
||||
@@ -90,8 +90,8 @@ void CardSprite::updateEntering(float delta_time) {
|
||||
// Posición: de entry_start a landing con easing suave (sin rebote)
|
||||
// Usamos easeOutCubic para que el desplazamiento sea fluido
|
||||
double pos_eased = easeOutCubic(static_cast<double>(progress));
|
||||
auto current_x = static_cast<float>(entry_start_x_ + (landing_x_ - entry_start_x_) * pos_eased);
|
||||
auto current_y = static_cast<float>(entry_start_y_ + (landing_y_ - entry_start_y_) * pos_eased);
|
||||
auto current_x = static_cast<float>(entry_start_x_ + ((landing_x_ - entry_start_x_) * pos_eased));
|
||||
auto current_y = static_cast<float>(entry_start_y_ + ((landing_y_ - entry_start_y_) * pos_eased));
|
||||
setPos(current_x, current_y);
|
||||
|
||||
// Detecta el primer toque (cuando el easing alcanza ~1.0 por primera vez)
|
||||
@@ -117,12 +117,9 @@ void CardSprite::updateExiting(float delta_time) {
|
||||
|
||||
// Ganar altura gradualmente (zoom hacia el objetivo)
|
||||
if (exit_zoom_speed_ > 0.0F && horizontal_zoom_ < exit_target_zoom_) {
|
||||
float new_zoom = horizontal_zoom_ + exit_zoom_speed_ * delta_time;
|
||||
if (new_zoom > exit_target_zoom_) {
|
||||
new_zoom = exit_target_zoom_;
|
||||
}
|
||||
horizontal_zoom_ = new_zoom;
|
||||
vertical_zoom_ = new_zoom;
|
||||
const float NEW_ZOOM = std::min(horizontal_zoom_ + (exit_zoom_speed_ * delta_time), exit_target_zoom_);
|
||||
horizontal_zoom_ = NEW_ZOOM;
|
||||
vertical_zoom_ = NEW_ZOOM;
|
||||
}
|
||||
|
||||
if (isOffScreen()) {
|
||||
@@ -164,8 +161,8 @@ void CardSprite::renderShadow() {
|
||||
|
||||
// Offset respecto a la tarjeta: base + extra proporcional a la altura
|
||||
// La sombra se aleja en diagonal abajo-derecha (opuesta a la luz en 0,0)
|
||||
float offset_x = shadow_offset_x_ + height * SHADOW_HEIGHT_MULTIPLIER;
|
||||
float offset_y = shadow_offset_y_ + height * SHADOW_HEIGHT_MULTIPLIER;
|
||||
float offset_x = shadow_offset_x_ + (height * SHADOW_HEIGHT_MULTIPLIER);
|
||||
float offset_y = shadow_offset_y_ + (height * SHADOW_HEIGHT_MULTIPLIER);
|
||||
|
||||
shadow_texture_->render(
|
||||
pos_.x + offset_x,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
|
||||
@@ -10,7 +11,7 @@
|
||||
class Texture;
|
||||
|
||||
// --- Estados de la tarjeta ---
|
||||
enum class CardState {
|
||||
enum class CardState : std::uint8_t {
|
||||
IDLE, // No activada todavía
|
||||
ENTERING, // Animación de entrada (zoom + rotación + desplazamiento con rebote)
|
||||
LANDED, // En reposo sobre la mesa
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FPoint
|
||||
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <functional> // Para std::function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <utility>
|
||||
@@ -12,12 +13,12 @@
|
||||
class Texture;
|
||||
|
||||
// --- Enums ---
|
||||
enum class PathType { // Tipos de recorrido
|
||||
enum class PathType : std::uint8_t { // Tipos de recorrido
|
||||
VERTICAL,
|
||||
HORIZONTAL,
|
||||
};
|
||||
|
||||
enum class PathCentered { // Centrado del recorrido
|
||||
enum class PathCentered : std::uint8_t { // Centrado del recorrido
|
||||
ON_X,
|
||||
ON_Y,
|
||||
NONE,
|
||||
@@ -25,12 +26,12 @@ enum class PathCentered { // Centrado del recorrido
|
||||
|
||||
// --- Estructuras ---
|
||||
struct Path { // Define un recorrido para el sprite
|
||||
float start_pos; // Posición inicial
|
||||
float end_pos; // Posición final
|
||||
PathType type; // Tipo de movimiento (horizontal/vertical)
|
||||
float fixed_pos; // Posición fija en el eje contrario
|
||||
float duration_s; // Duración de la animación en segundos
|
||||
float waiting_time_s; // Tiempo de espera una vez en el destino
|
||||
float start_pos = 0.0F; // Posición inicial
|
||||
float end_pos = 0.0F; // Posición final
|
||||
PathType type = PathType::HORIZONTAL; // Tipo de movimiento (horizontal/vertical)
|
||||
float fixed_pos = 0.0F; // Posición fija en el eje contrario
|
||||
float duration_s = 0.0F; // Duración de la animación en segundos
|
||||
float waiting_time_s = 0.0F; // Tiempo de espera una vez en el destino
|
||||
std::function<double(double)> easing_function; // Función de easing
|
||||
float elapsed_time = 0.0F; // Tiempo transcurrido
|
||||
float waiting_elapsed = 0.0F; // Tiempo de espera transcurrido
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "core/rendering/sprite/smart_sprite.hpp"
|
||||
|
||||
#include "core/rendering/sprite/moving_sprite.hpp" // Para MovingSprite
|
||||
|
||||
// Actualiza la posición y comprueba si ha llegado a su destino (time-based)
|
||||
void SmartSprite::update(float delta_time) {
|
||||
if (enabled_) {
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "core/rendering/sprite/moving_sprite.hpp" // Para MovingSprite
|
||||
|
||||
class Texture;
|
||||
|
||||
// --- Clase SmartSprite: sprite animado que se mueve hacia un destino y puede deshabilitarse automáticamente ---
|
||||
class SmartSprite : public AnimatedSprite {
|
||||
// --- Clase SmartSprite: sprite que se mueve hacia un destino y puede deshabilitarse automáticamente ---
|
||||
class SmartSprite : public MovingSprite {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
explicit SmartSprite(std::shared_ptr<Texture> texture)
|
||||
: AnimatedSprite(std::move(texture)) {}
|
||||
: MovingSprite(std::move(texture)) {}
|
||||
~SmartSprite() override = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
|
||||
@@ -403,9 +403,8 @@ auto Text::loadFile(const std::string& file_path) -> std::shared_ptr<Text::File>
|
||||
file.open(file_path);
|
||||
}
|
||||
|
||||
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
||||
|
||||
if ((using_resource_data && stream.good()) || (!using_resource_data && file.is_open() && file.good())) {
|
||||
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
||||
std::string buffer;
|
||||
|
||||
// Lee los dos primeros valores del fichero
|
||||
|
||||
@@ -39,7 +39,7 @@ class Text {
|
||||
int kerning;
|
||||
|
||||
// Constructor con argumentos por defecto
|
||||
Style(Uint8 flags = 0,
|
||||
explicit Style(Uint8 flags = 0,
|
||||
Color text = Color(),
|
||||
Color shadow = Color(),
|
||||
Uint8 distance = 1,
|
||||
|
||||
@@ -251,11 +251,9 @@ auto Texture::loadSurface(const std::string& file_path) -> std::shared_ptr<Surfa
|
||||
}
|
||||
}
|
||||
|
||||
// Crear un objeto Gif y llamar a la función loadGif
|
||||
GIF::Gif gif;
|
||||
Uint16 w = 0;
|
||||
Uint16 h = 0;
|
||||
std::vector<Uint8> raw_pixels = gif.loadGif(buffer.data(), w, h);
|
||||
std::vector<Uint8> raw_pixels = GIF::loadGif(buffer.data(), w, h);
|
||||
if (raw_pixels.empty()) {
|
||||
std::cout << "Error: No se pudo cargar el GIF " << file_path << '\n';
|
||||
return nullptr;
|
||||
@@ -329,8 +327,7 @@ auto Texture::loadPaletteFromFile(const std::string& file_path) -> Palette {
|
||||
}
|
||||
|
||||
// Usar la nueva función loadPalette, que devuelve un vector<uint32_t>
|
||||
GIF::Gif gif;
|
||||
std::vector<uint32_t> pal = gif.loadPalette(buffer.data());
|
||||
std::vector<uint32_t> pal = GIF::loadPalette(buffer.data());
|
||||
if (pal.empty()) {
|
||||
std::cout << "Advertencia: No se encontró paleta en el archivo " << file_path << '\n';
|
||||
return palette; // Devuelve un vector vacío si no hay paleta
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_SetTextureColorMod, SDL_Renderer, SDL_Texture
|
||||
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
|
||||
#include "utils/color.hpp" // Para Color
|
||||
|
||||
// --- Enums ---
|
||||
enum class TiledBGMode : int { // Modos de funcionamiento para el tileado de fondo
|
||||
enum class TiledBGMode : std::uint8_t { // Modos de funcionamiento para el tileado de fondo
|
||||
CIRCLE = 0,
|
||||
DIAGONAL = 1,
|
||||
RANDOM = 2,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "core/rendering/writer.hpp"
|
||||
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
// Text es completat ací per `text_->write/length` via shared_ptr; include-cleaner no detecta l'ús indirecte.
|
||||
#include "core/rendering/text.hpp" // IWYU pragma: keep
|
||||
|
||||
// Actualiza el objeto (delta_time en ms)
|
||||
void Writer::update(float delta_time) {
|
||||
|
||||
@@ -47,27 +47,39 @@ void Asset::add(const std::string& file_path, Type type, bool required, bool abs
|
||||
addToMap(file_path, type, required, absolute);
|
||||
}
|
||||
|
||||
// Carga recursos desde un archivo de configuración con soporte para variables
|
||||
// Carga recursos desde un archivo de configuración en el filesystem
|
||||
void Asset::loadFromFile(const std::string& config_file_path, const std::string& prefix, const std::string& system_folder) {
|
||||
std::ifstream file(config_file_path);
|
||||
if (!file.is_open()) {
|
||||
std::cout << "Error: Cannot open config file: " << config_file_path << '\n';
|
||||
return;
|
||||
}
|
||||
parseConfigStream(file, prefix, system_folder);
|
||||
}
|
||||
|
||||
// Carga recursos desde un buffer en memoria (p.ex. obtenido del pack)
|
||||
void Asset::loadFromBuffer(const std::vector<uint8_t>& buffer, const std::string& prefix, const std::string& system_folder) {
|
||||
if (buffer.empty()) {
|
||||
std::cout << "Error: Asset index buffer is empty" << '\n';
|
||||
return;
|
||||
}
|
||||
std::string content(buffer.begin(), buffer.end());
|
||||
std::istringstream stream(content);
|
||||
parseConfigStream(stream, prefix, system_folder);
|
||||
}
|
||||
|
||||
// Parsea el contenido del índice (formato común a loadFromFile y loadFromBuffer)
|
||||
void Asset::parseConfigStream(std::istream& stream, const std::string& prefix, const std::string& system_folder) {
|
||||
std::string line;
|
||||
int line_number = 0;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
while (std::getline(stream, line)) {
|
||||
++line_number;
|
||||
|
||||
// Limpiar espacios en blanco al principio y final
|
||||
line.erase(0, line.find_first_not_of(" \t\r"));
|
||||
line.erase(line.find_last_not_of(" \t\r") + 1);
|
||||
|
||||
// DEBUG: mostrar línea leída (opcional, puedes comentar esta línea)
|
||||
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Line %d: '%s'", line_number, line.c_str());
|
||||
|
||||
// Ignorar líneas vacías y comentarios
|
||||
if (line.empty() || line[0] == '#' || line[0] == ';') {
|
||||
continue;
|
||||
@@ -114,8 +126,6 @@ void Asset::loadFromFile(const std::string& config_file_path, const std::string&
|
||||
std::cout << "Error parsing line " << line_number << " in config file: " << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Devuelve la ruta completa a un fichero (búsqueda O(1))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // Para uint8_t
|
||||
#include <iosfwd> // Para istream (forward decl)
|
||||
#include <string> // Para string
|
||||
#include <unordered_map> // Para unordered_map
|
||||
#include <utility> // Para move
|
||||
@@ -10,7 +11,7 @@
|
||||
class Asset {
|
||||
public:
|
||||
// --- Enums ---
|
||||
enum class Type : int {
|
||||
enum class Type : std::uint8_t {
|
||||
BITMAP, // Imágenes
|
||||
MUSIC, // Música
|
||||
SOUND, // Sonidos
|
||||
@@ -32,7 +33,8 @@ class Asset {
|
||||
|
||||
// --- Métodos para la gestión de recursos ---
|
||||
void add(const std::string& file_path, Type type, bool required = true, bool absolute = false);
|
||||
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables
|
||||
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Carga el índice desde el filesystem
|
||||
void loadFromBuffer(const std::vector<uint8_t>& buffer, const std::string& prefix = "", const std::string& system_folder = ""); // Carga el índice desde memoria (p.ex. desde el pack)
|
||||
[[nodiscard]] auto getPath(const std::string& filename) const -> std::string;
|
||||
[[nodiscard]] auto loadData(const std::string& filename) const -> std::vector<uint8_t>; // Carga datos del archivo
|
||||
[[nodiscard]] auto check() const -> bool;
|
||||
@@ -63,6 +65,7 @@ class Asset {
|
||||
void addToMap(const std::string& file_path, Type type, bool required, bool absolute); // Añade archivo al mapa
|
||||
[[nodiscard]] static auto replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string; // Reemplaza variables en la ruta
|
||||
static auto parseOptions(const std::string& options, bool& required, bool& absolute) -> void; // Parsea opciones
|
||||
void parseConfigStream(std::istream& stream, const std::string& prefix, const std::string& system_folder); // Lógica común de parseo del índice
|
||||
|
||||
// --- Constructores y destructor privados (singleton) ---
|
||||
explicit Asset(std::string executable_path) // Constructor privado
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "core/resources/resource_loader.hpp"
|
||||
|
||||
bool AssetIntegrated::resource_pack_enabled = false;
|
||||
|
||||
void AssetIntegrated::initWithResourcePack(const std::string& executable_path,
|
||||
@@ -86,20 +88,11 @@ auto AssetIntegrated::getSystemPath(const std::string& filename) -> std::string
|
||||
}
|
||||
|
||||
auto AssetIntegrated::shouldUseResourcePack(const std::string& filepath) -> bool {
|
||||
// Los archivos que NO van al pack:
|
||||
// - Archivos de config/ (ahora están fuera de data/)
|
||||
// - Archivos con absolute=true en assets.txt
|
||||
// - Archivos de sistema (${SYSTEM_FOLDER})
|
||||
// Els fitxers que NO van al pack:
|
||||
// - Fitxers amb absolute=true a assets.txt
|
||||
// - Fitxers de sistema (${SYSTEM_FOLDER})
|
||||
// - Fitxers al costat del binari (com gamecontrollerdb.txt)
|
||||
|
||||
if (filepath.find("/config/") != std::string::npos ||
|
||||
filepath.starts_with("config/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filepath.find("/data/") != std::string::npos ||
|
||||
filepath.starts_with("data/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return filepath.find("/data/") != std::string::npos ||
|
||||
filepath.starts_with("data/");
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core/resources/asset.hpp"
|
||||
#include "core/resources/resource_loader.hpp"
|
||||
|
||||
// Extensión de Asset que integra ResourceLoader
|
||||
class AssetIntegrated : public Asset {
|
||||
|
||||
+107
-159
@@ -2,17 +2,15 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_LogError, SDL_SetRenderDrawColor, SDL_EventType, SDL_PollEvent, SDL_RenderFillRect, SDL_RenderRect, SDLK_ESCAPE, SDL_Event
|
||||
|
||||
#include <algorithm> // Para ranges::transform, ranges::find_if
|
||||
#include <array> // Para array
|
||||
#include <cstdlib> // Para exit
|
||||
#include <exception> // Para exception
|
||||
#include <filesystem> // Para exists, path, remove
|
||||
#include <fstream> // Para basic_ofstream, basic_ios, basic_ostream::write, ios, ofstream
|
||||
#include <iostream> // Para std::cout
|
||||
#include <ranges> // Para __find_if_fn, find_if, __find_fn, find
|
||||
#include <iterator> // Para back_inserter
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <utility> // Para move
|
||||
|
||||
#include "core/audio/jail_audio.hpp" // Para JA_LoadMusic, JA_LoadSound, JA_DeleteMusic, JA_DeleteSound
|
||||
#include "core/audio/jail_audio.hpp" // Para Ja::loadMusic, Ja::loadSound, Ja::deleteMusic, Ja::deleteSound
|
||||
#include "core/locale/lang.hpp" // Para getText
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
@@ -25,9 +23,6 @@
|
||||
#include "utils/utils.hpp" // Para getFileName
|
||||
#include "version.h" // Para APP_NAME, GIT_HASH
|
||||
|
||||
struct JA_Music_t; // lines 11-11
|
||||
struct JA_Sound_t; // lines 12-12
|
||||
|
||||
// Helper para cargar archivos de audio desde pack o filesystem en memoria
|
||||
namespace {
|
||||
struct AudioData {
|
||||
@@ -138,40 +133,37 @@ void Resource::loadEssentialTextures() {
|
||||
|
||||
// Inicializa las listas de recursos sin cargar el contenido (modo lazy)
|
||||
void Resource::initResourceLists() {
|
||||
const auto FILE_TO_NAME = [](const auto& file) { return getFileName(file); };
|
||||
|
||||
// Inicializa lista de sonidos
|
||||
auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
|
||||
const auto SOUND_LIST = Asset::get()->getListByType(Asset::Type::SOUND);
|
||||
sounds_.clear();
|
||||
for (const auto& file : sound_list) {
|
||||
sounds_.emplace_back(getFileName(file));
|
||||
}
|
||||
sounds_.reserve(SOUND_LIST.size());
|
||||
std::ranges::transform(SOUND_LIST, std::back_inserter(sounds_), [&](const auto& file) { return ResourceSound(FILE_TO_NAME(file)); });
|
||||
|
||||
// Inicializa lista de músicas
|
||||
auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
|
||||
const auto MUSIC_LIST = Asset::get()->getListByType(Asset::Type::MUSIC);
|
||||
musics_.clear();
|
||||
for (const auto& file : music_list) {
|
||||
musics_.emplace_back(getFileName(file));
|
||||
}
|
||||
musics_.reserve(MUSIC_LIST.size());
|
||||
std::ranges::transform(MUSIC_LIST, std::back_inserter(musics_), [&](const auto& file) { return ResourceMusic(FILE_TO_NAME(file)); });
|
||||
|
||||
// Inicializa lista de texturas
|
||||
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||
const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||
textures_.clear();
|
||||
for (const auto& file : texture_list) {
|
||||
textures_.emplace_back(getFileName(file));
|
||||
}
|
||||
textures_.reserve(TEXTURE_LIST.size());
|
||||
std::ranges::transform(TEXTURE_LIST, std::back_inserter(textures_), [&](const auto& file) { return ResourceTexture(FILE_TO_NAME(file)); });
|
||||
|
||||
// Inicializa lista de ficheros de texto
|
||||
auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
|
||||
const auto TEXT_FILE_LIST = Asset::get()->getListByType(Asset::Type::FONT);
|
||||
text_files_.clear();
|
||||
for (const auto& file : text_file_list) {
|
||||
text_files_.emplace_back(getFileName(file));
|
||||
}
|
||||
text_files_.reserve(TEXT_FILE_LIST.size());
|
||||
std::ranges::transform(TEXT_FILE_LIST, std::back_inserter(text_files_), [&](const auto& file) { return ResourceTextFile(FILE_TO_NAME(file)); });
|
||||
|
||||
// Inicializa lista de animaciones
|
||||
auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
||||
const auto ANIMATION_LIST = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
||||
animations_.clear();
|
||||
for (const auto& file : animation_list) {
|
||||
animations_.emplace_back(getFileName(file));
|
||||
}
|
||||
animations_.reserve(ANIMATION_LIST.size());
|
||||
std::ranges::transform(ANIMATION_LIST, std::back_inserter(animations_), [&](const auto& file) { return ResourceAnimation(FILE_TO_NAME(file)); });
|
||||
|
||||
// Los demos se cargan directamente sin mostrar progreso (son pocos y pequeños)
|
||||
loadDemoDataQuiet();
|
||||
@@ -192,13 +184,12 @@ void Resource::initResourceLists() {
|
||||
"smb2_grad"};
|
||||
|
||||
texts_.clear();
|
||||
for (const auto& text_name : TEXT_OBJECTS) {
|
||||
texts_.emplace_back(text_name); // Constructor con nullptr por defecto
|
||||
}
|
||||
texts_.reserve(TEXT_OBJECTS.size());
|
||||
std::ranges::transform(TEXT_OBJECTS, std::back_inserter(texts_), [](const auto& text_name) { return ResourceText(text_name); });
|
||||
}
|
||||
|
||||
// Obtiene el sonido a partir de un nombre (con carga perezosa)
|
||||
auto Resource::getSound(const std::string& name) -> JA_Sound_t* {
|
||||
auto Resource::getSound(const std::string& name) -> Ja::Sound* {
|
||||
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> auto { return s.name == name; });
|
||||
|
||||
if (it != sounds_.end()) {
|
||||
@@ -214,7 +205,7 @@ auto Resource::getSound(const std::string& name) -> JA_Sound_t* {
|
||||
}
|
||||
|
||||
// Obtiene la música a partir de un nombre (con carga perezosa)
|
||||
auto Resource::getMusic(const std::string& name) -> JA_Music_t* {
|
||||
auto Resource::getMusic(const std::string& name) -> Ja::Music* {
|
||||
auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> auto { return m.name == name; });
|
||||
|
||||
if (it != musics_.end()) {
|
||||
@@ -305,54 +296,48 @@ auto Resource::getDemoData(int index) -> DemoData& {
|
||||
|
||||
// --- Métodos de carga perezosa ---
|
||||
|
||||
auto Resource::loadSoundLazy(const std::string& name) -> JA_Sound_t* {
|
||||
auto Resource::loadSoundLazy(const std::string& name) -> Ja::Sound* {
|
||||
auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
|
||||
for (const auto& file : sound_list) {
|
||||
if (getFileName(file) == name) {
|
||||
auto audio_data = loadAudioData(file);
|
||||
if (!audio_data.data.empty()) {
|
||||
return JA_LoadSound(audio_data.data.data(), audio_data.data.size());
|
||||
return Ja::loadSound(audio_data.data.data(), audio_data.data.size());
|
||||
}
|
||||
// Fallback a cargar desde disco si no está en pack
|
||||
return JA_LoadSound(file.c_str());
|
||||
return Ja::loadSound(file.c_str());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto Resource::loadMusicLazy(const std::string& name) -> JA_Music_t* {
|
||||
auto Resource::loadMusicLazy(const std::string& name) -> Ja::Music* {
|
||||
auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
|
||||
for (const auto& file : music_list) {
|
||||
if (getFileName(file) == name) {
|
||||
auto audio_data = loadAudioData(file);
|
||||
if (!audio_data.data.empty()) {
|
||||
return JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
|
||||
return Ja::loadMusic(audio_data.data.data(), audio_data.data.size());
|
||||
}
|
||||
// Fallback a cargar desde disco si no está en pack
|
||||
return JA_LoadMusic(file.c_str());
|
||||
return Ja::loadMusic(file.c_str());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto Resource::loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture> {
|
||||
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||
for (const auto& file : texture_list) {
|
||||
if (getFileName(file) == name) {
|
||||
return std::make_shared<Texture>(Screen::get()->getRenderer(), file);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||
const auto IT = std::ranges::find_if(TEXTURE_LIST,
|
||||
[&name](const auto& file) { return getFileName(file) == name; });
|
||||
return IT != TEXTURE_LIST.end() ? std::make_shared<Texture>(Screen::get()->getRenderer(), *IT) : nullptr;
|
||||
}
|
||||
|
||||
auto Resource::loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File> {
|
||||
auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
|
||||
for (const auto& file : text_file_list) {
|
||||
if (getFileName(file) == name) {
|
||||
return Text::loadFile(file);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
const auto TEXT_FILE_LIST = Asset::get()->getListByType(Asset::Type::FONT);
|
||||
const auto IT = std::ranges::find_if(TEXT_FILE_LIST,
|
||||
[&name](const auto& file) { return getFileName(file) == name; });
|
||||
return IT != TEXT_FILE_LIST.end() ? Text::loadFile(*IT) : nullptr;
|
||||
}
|
||||
|
||||
auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr<Text> {
|
||||
@@ -377,27 +362,22 @@ auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr<Text> {
|
||||
{.key = "smb2", .texture_file = "smb2.png", .text_file = "smb2.txt"},
|
||||
{.key = "smb2_grad", .texture_file = "smb2_grad.png", .text_file = "smb2.txt"}};
|
||||
|
||||
for (const auto& mapping : TEXT_MAPPINGS) {
|
||||
if (mapping.key == name) {
|
||||
// Cargar las dependencias automáticamente
|
||||
auto texture = getTexture(mapping.texture_file); // Esto cargará la textura si no está cargada
|
||||
auto text_file = getTextFile(mapping.text_file); // Esto cargará el archivo de texto si no está cargado
|
||||
|
||||
if (texture && text_file) {
|
||||
return std::make_shared<Text>(texture, text_file);
|
||||
}
|
||||
}
|
||||
const auto IT = std::ranges::find_if(TEXT_MAPPINGS,
|
||||
[&name](const auto& mapping) { return mapping.key == name; });
|
||||
if (IT == TEXT_MAPPINGS.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
auto texture = getTexture(IT->texture_file); // Esto cargará la textura si no está cargada
|
||||
auto text_file = getTextFile(IT->text_file); // Esto cargará el archivo de texto si no está cargado
|
||||
return (texture && text_file) ? std::make_shared<Text>(texture, text_file) : nullptr;
|
||||
}
|
||||
|
||||
auto Resource::loadAnimationLazy(const std::string& name) -> AnimationsFileBuffer {
|
||||
auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
||||
for (const auto& file : animation_list) {
|
||||
if (getFileName(file) == name) {
|
||||
return loadAnimationsFromFile(file);
|
||||
}
|
||||
const auto ANIMATION_LIST = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
||||
const auto IT = std::ranges::find_if(ANIMATION_LIST,
|
||||
[&name](const auto& file) { return getFileName(file) == name; });
|
||||
if (IT != ANIMATION_LIST.end()) {
|
||||
return loadAnimationsFromFile(*IT);
|
||||
}
|
||||
// Si no se encuentra, retorna vector vacío
|
||||
return AnimationsFileBuffer{};
|
||||
@@ -440,80 +420,54 @@ auto Resource::isLoadDone() const -> bool {
|
||||
return stage_ == LoadStage::DONE;
|
||||
}
|
||||
|
||||
// Avança una etapa que descarrega una llista d'assets.
|
||||
void Resource::advanceListLoadStage(const std::vector<std::string>& list, void (Resource::*load_one)(size_t), LoadStage next_stage) {
|
||||
if (stage_index_ >= list.size()) {
|
||||
stage_ = next_stage;
|
||||
stage_index_ = 0;
|
||||
return;
|
||||
}
|
||||
(this->*load_one)(stage_index_++);
|
||||
}
|
||||
|
||||
// Bombea la máquina de etapas hasta agotar el presupuesto de tiempo o completar la carga.
|
||||
// Devuelve true cuando ya no queda nada por cargar.
|
||||
auto Resource::loadStep(int budget_ms) -> bool {
|
||||
if (stage_ == LoadStage::DONE) { return true; }
|
||||
|
||||
const Uint64 start_ns = SDL_GetTicksNS();
|
||||
const Uint64 budget_ns = static_cast<Uint64>(budget_ms) * 1'000'000ULL;
|
||||
const Uint64 START_NS = SDL_GetTicksNS();
|
||||
const Uint64 BUDGET_NS = static_cast<Uint64>(budget_ms) * 1'000'000ULL;
|
||||
|
||||
while (stage_ != LoadStage::DONE) {
|
||||
switch (stage_) {
|
||||
case LoadStage::SOUNDS: {
|
||||
auto list = Asset::get()->getListByType(Asset::Type::SOUND);
|
||||
if (stage_index_ == 0) { sounds_.clear(); }
|
||||
if (stage_index_ >= list.size()) {
|
||||
stage_ = LoadStage::MUSICS;
|
||||
stage_index_ = 0;
|
||||
break;
|
||||
}
|
||||
loadOneSound(stage_index_++);
|
||||
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::SOUND), &Resource::loadOneSound, LoadStage::MUSICS);
|
||||
break;
|
||||
}
|
||||
case LoadStage::MUSICS: {
|
||||
auto list = Asset::get()->getListByType(Asset::Type::MUSIC);
|
||||
if (stage_index_ == 0) { musics_.clear(); }
|
||||
if (stage_index_ >= list.size()) {
|
||||
stage_ = LoadStage::TEXTURES;
|
||||
stage_index_ = 0;
|
||||
break;
|
||||
}
|
||||
loadOneMusic(stage_index_++);
|
||||
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::MUSIC), &Resource::loadOneMusic, LoadStage::TEXTURES);
|
||||
break;
|
||||
}
|
||||
case LoadStage::TEXTURES: {
|
||||
auto list = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||
if (stage_index_ == 0) { textures_.clear(); }
|
||||
if (stage_index_ >= list.size()) {
|
||||
stage_ = LoadStage::TEXT_FILES;
|
||||
stage_index_ = 0;
|
||||
break;
|
||||
}
|
||||
loadOneTexture(stage_index_++);
|
||||
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::BITMAP), &Resource::loadOneTexture, LoadStage::TEXT_FILES);
|
||||
break;
|
||||
}
|
||||
case LoadStage::TEXT_FILES: {
|
||||
auto list = Asset::get()->getListByType(Asset::Type::FONT);
|
||||
if (stage_index_ == 0) { text_files_.clear(); }
|
||||
if (stage_index_ >= list.size()) {
|
||||
stage_ = LoadStage::ANIMATIONS;
|
||||
stage_index_ = 0;
|
||||
break;
|
||||
}
|
||||
loadOneTextFile(stage_index_++);
|
||||
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::FONT), &Resource::loadOneTextFile, LoadStage::ANIMATIONS);
|
||||
break;
|
||||
}
|
||||
case LoadStage::ANIMATIONS: {
|
||||
auto list = Asset::get()->getListByType(Asset::Type::ANIMATION);
|
||||
if (stage_index_ == 0) { animations_.clear(); }
|
||||
if (stage_index_ >= list.size()) {
|
||||
stage_ = LoadStage::DEMO_DATA;
|
||||
stage_index_ = 0;
|
||||
break;
|
||||
}
|
||||
loadOneAnimation(stage_index_++);
|
||||
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::ANIMATION), &Resource::loadOneAnimation, LoadStage::DEMO_DATA);
|
||||
break;
|
||||
}
|
||||
case LoadStage::DEMO_DATA: {
|
||||
auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
|
||||
if (stage_index_ == 0) { demos_.clear(); }
|
||||
if (stage_index_ >= list.size()) {
|
||||
stage_ = LoadStage::CREATE_TEXT;
|
||||
stage_index_ = 0;
|
||||
break;
|
||||
}
|
||||
loadOneDemoData(stage_index_++);
|
||||
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::DEMODATA), &Resource::loadOneDemoData, LoadStage::CREATE_TEXT);
|
||||
break;
|
||||
}
|
||||
case LoadStage::CREATE_TEXT:
|
||||
@@ -532,7 +486,7 @@ auto Resource::loadStep(int budget_ms) -> bool {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((SDL_GetTicksNS() - start_ns) >= budget_ns) { break; }
|
||||
if ((SDL_GetTicksNS() - START_NS) >= BUDGET_NS) { break; }
|
||||
}
|
||||
|
||||
return stage_ == LoadStage::DONE;
|
||||
@@ -555,11 +509,11 @@ void Resource::loadOneSound(size_t idx) {
|
||||
auto name = getFileName(path);
|
||||
updateLoadingProgress(name);
|
||||
auto audio_data = loadAudioData(path);
|
||||
JA_Sound_t* sound = nullptr;
|
||||
Ja::Sound* sound = nullptr;
|
||||
if (!audio_data.data.empty()) {
|
||||
sound = JA_LoadSound(audio_data.data.data(), audio_data.data.size());
|
||||
sound = Ja::loadSound(audio_data.data.data(), audio_data.data.size());
|
||||
} else {
|
||||
sound = JA_LoadSound(path.c_str());
|
||||
sound = Ja::loadSound(path.c_str());
|
||||
}
|
||||
if (sound == nullptr) {
|
||||
std::cout << "Sound load failed: " << name << '\n';
|
||||
@@ -574,11 +528,11 @@ void Resource::loadOneMusic(size_t idx) {
|
||||
auto name = getFileName(path);
|
||||
updateLoadingProgress(name);
|
||||
auto audio_data = loadAudioData(path);
|
||||
JA_Music_t* music = nullptr;
|
||||
Ja::Music* music = nullptr;
|
||||
if (!audio_data.data.empty()) {
|
||||
music = JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
|
||||
music = Ja::loadMusic(audio_data.data.data(), audio_data.data.size());
|
||||
} else {
|
||||
music = JA_LoadMusic(path.c_str());
|
||||
music = Ja::loadMusic(path.c_str());
|
||||
}
|
||||
if (music == nullptr) {
|
||||
std::cout << "Music load failed: " << name << '\n';
|
||||
@@ -640,14 +594,10 @@ void Resource::createPlayerTextures() {
|
||||
const auto& player = players[player_idx]; // Obtenemos el jugador actual
|
||||
|
||||
// Encontrar el archivo original de la textura
|
||||
std::string texture_file_path;
|
||||
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||
for (const auto& file : texture_list) {
|
||||
if (getFileName(file) == player.base_texture) {
|
||||
texture_file_path = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP);
|
||||
const auto IT = std::ranges::find_if(TEXTURE_LIST,
|
||||
[&player](const auto& file) { return getFileName(file) == player.base_texture; });
|
||||
const std::string TEXTURE_FILE_PATH = (IT != TEXTURE_LIST.end()) ? *IT : std::string{};
|
||||
|
||||
// Crear las 4 texturas con sus respectivas paletas
|
||||
for (int palette_idx = 0; palette_idx < 4; ++palette_idx) {
|
||||
@@ -656,14 +606,14 @@ void Resource::createPlayerTextures() {
|
||||
if (palette_idx == 0) {
|
||||
// Textura 0 - usar la ya cargada y modificar solo paleta 0 (default_shirt)
|
||||
texture = getTexture(player.base_texture);
|
||||
texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.TO_UINT32());
|
||||
texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.TO_UINT32());
|
||||
texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.TO_UINT32());
|
||||
texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.TO_UINT32());
|
||||
texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].TO_UINT32());
|
||||
texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.toUint32());
|
||||
texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.toUint32());
|
||||
texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.toUint32());
|
||||
texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.toUint32());
|
||||
texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].toUint32());
|
||||
} else {
|
||||
// Crear textura nueva desde archivo usando ResourceHelper
|
||||
texture = std::make_shared<Texture>(Screen::get()->getRenderer(), texture_file_path);
|
||||
texture = std::make_shared<Texture>(Screen::get()->getRenderer(), TEXTURE_FILE_PATH);
|
||||
|
||||
// Añadir todas las paletas
|
||||
texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[0]));
|
||||
@@ -672,18 +622,18 @@ void Resource::createPlayerTextures() {
|
||||
|
||||
if (palette_idx == 1) {
|
||||
// Textura 1 - modificar solo paleta 1 (one_coffee_shirt)
|
||||
texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.TO_UINT32());
|
||||
texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.TO_UINT32());
|
||||
texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.TO_UINT32());
|
||||
texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.TO_UINT32());
|
||||
texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].TO_UINT32());
|
||||
texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.toUint32());
|
||||
texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.toUint32());
|
||||
texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.toUint32());
|
||||
texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.toUint32());
|
||||
texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].toUint32());
|
||||
} else if (palette_idx == 2) {
|
||||
// Textura 2 - modificar solo paleta 2 (two_coffee_shirt)
|
||||
texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.TO_UINT32());
|
||||
texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.TO_UINT32());
|
||||
texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.TO_UINT32());
|
||||
texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.TO_UINT32());
|
||||
texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].TO_UINT32());
|
||||
texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.toUint32());
|
||||
texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.toUint32());
|
||||
texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.toUint32());
|
||||
texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.toUint32());
|
||||
texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].toUint32());
|
||||
}
|
||||
// Textura 3 (palette_idx == 3) - no modificar nada, usar colores originales
|
||||
}
|
||||
@@ -720,9 +670,9 @@ void Resource::createTextTextures() {
|
||||
{"game_text_1000000_points", Lang::getText("[GAME_TEXT] 8")}};
|
||||
|
||||
auto text1 = getText("04b_25_enhanced");
|
||||
for (const auto& s : strings1) {
|
||||
textures_.emplace_back(s.name, text1->writeDXToTexture(Text::STROKE, s.text, -2, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
|
||||
}
|
||||
std::ranges::transform(strings1, std::back_inserter(textures_), [&](const auto& s) {
|
||||
return ResourceTexture(s.name, text1->writeDXToTexture(Text::STROKE, s.text, -2, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
|
||||
});
|
||||
|
||||
// Texturas de tamaño doble
|
||||
std::vector<NameAndText> strings2 = {
|
||||
@@ -734,9 +684,9 @@ void Resource::createTextTextures() {
|
||||
{"game_text_game_over", "Game Over"}};
|
||||
|
||||
auto text2 = getText("04b_25_2x_enhanced");
|
||||
for (const auto& s : strings2) {
|
||||
textures_.emplace_back(s.name, text2->writeDXToTexture(Text::STROKE, s.text, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
|
||||
}
|
||||
std::ranges::transform(strings2, std::back_inserter(textures_), [&](const auto& s) {
|
||||
return ResourceTexture(s.name, text2->writeDXToTexture(Text::STROKE, s.text, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
|
||||
});
|
||||
}
|
||||
|
||||
// Crea los objetos de texto a partir de los archivos de textura y texto
|
||||
@@ -786,7 +736,7 @@ void Resource::createText() {
|
||||
void Resource::clearSounds() {
|
||||
for (auto& sound : sounds_) {
|
||||
if (sound.sound != nullptr) {
|
||||
JA_DeleteSound(sound.sound);
|
||||
Ja::deleteSound(sound.sound);
|
||||
sound.sound = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -797,7 +747,7 @@ void Resource::clearSounds() {
|
||||
void Resource::clearMusics() {
|
||||
for (auto& music : musics_) {
|
||||
if (music.music != nullptr) {
|
||||
JA_DeleteMusic(music.music);
|
||||
Ja::deleteMusic(music.music);
|
||||
music.music = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -847,7 +797,7 @@ void Resource::renderProgress() {
|
||||
const bool WAITING_FOR_INPUT = isLoadDone() && Options::loading.wait_for_input;
|
||||
|
||||
auto text_color = param.resource.color;
|
||||
auto bar_color = param.resource.color.DARKEN(100);
|
||||
auto bar_color = param.resource.color.darken(100);
|
||||
const auto TEXT_HEIGHT = loading_text_->getCharacterSize();
|
||||
|
||||
// Dibuja el interior de la barra de progreso
|
||||
@@ -897,12 +847,10 @@ void Resource::renderProgress() {
|
||||
|
||||
// Carga los datos para el modo demostración (sin mostrar progreso)
|
||||
void Resource::loadDemoDataQuiet() {
|
||||
auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
|
||||
const auto LIST = Asset::get()->getListByType(Asset::Type::DEMODATA);
|
||||
demos_.clear();
|
||||
|
||||
for (const auto& l : list) {
|
||||
demos_.emplace_back(loadDemoDataFromFile(l));
|
||||
}
|
||||
demos_.reserve(LIST.size());
|
||||
std::ranges::transform(LIST, std::back_inserter(demos_), [](const auto& l) { return loadDemoDataFromFile(l); });
|
||||
}
|
||||
|
||||
// Inicializa los rectangulos que definen la barra de progreso
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <utility> // Para move
|
||||
@@ -13,14 +14,16 @@
|
||||
#include "core/rendering/texture.hpp" // Para Texture
|
||||
#include "core/system/demo.hpp" // Para DemoData
|
||||
|
||||
struct JA_Music_t;
|
||||
struct JA_Sound_t;
|
||||
namespace Ja {
|
||||
struct Music;
|
||||
struct Sound;
|
||||
} // namespace Ja
|
||||
|
||||
// --- Clase Resource: gestiona todos los recursos del juego (singleton) ---
|
||||
class Resource {
|
||||
public:
|
||||
// --- Enum para el modo de carga ---
|
||||
enum class LoadingMode {
|
||||
enum class LoadingMode : std::uint8_t {
|
||||
PRELOAD, // Carga todos los recursos al inicio
|
||||
LAZY_LOAD // Carga los recursos bajo demanda
|
||||
};
|
||||
@@ -31,8 +34,8 @@ class Resource {
|
||||
static auto get() -> Resource*; // Obtiene el puntero al objeto Resource
|
||||
|
||||
// --- Métodos de acceso a recursos ---
|
||||
auto getSound(const std::string& name) -> JA_Sound_t*; // Obtiene el sonido por nombre
|
||||
auto getMusic(const std::string& name) -> JA_Music_t*; // Obtiene la música por nombre
|
||||
auto getSound(const std::string& name) -> Ja::Sound*; // Obtiene el sonido por nombre
|
||||
auto getMusic(const std::string& name) -> Ja::Music*; // Obtiene la música por nombre
|
||||
auto getTexture(const std::string& name) -> std::shared_ptr<Texture>; // Obtiene la textura por nombre
|
||||
auto getTextFile(const std::string& name) -> std::shared_ptr<Text::File>; // Obtiene el fichero de texto por nombre
|
||||
auto getText(const std::string& name) -> std::shared_ptr<Text>; // Obtiene el objeto de texto por nombre
|
||||
@@ -58,18 +61,18 @@ class Resource {
|
||||
// --- Estructuras para recursos individuales ---
|
||||
struct ResourceSound {
|
||||
std::string name; // Nombre del sonido
|
||||
JA_Sound_t* sound; // Objeto con el sonido
|
||||
Ja::Sound* sound; // Objeto con el sonido
|
||||
|
||||
ResourceSound(std::string name, JA_Sound_t* sound = nullptr)
|
||||
explicit ResourceSound(std::string name, Ja::Sound* sound = nullptr)
|
||||
: name(std::move(name)),
|
||||
sound(sound) {}
|
||||
};
|
||||
|
||||
struct ResourceMusic {
|
||||
std::string name; // Nombre de la música
|
||||
JA_Music_t* music; // Objeto con la música
|
||||
Ja::Music* music; // Objeto con la música
|
||||
|
||||
ResourceMusic(std::string name, JA_Music_t* music = nullptr)
|
||||
explicit ResourceMusic(std::string name, Ja::Music* music = nullptr)
|
||||
: name(std::move(name)),
|
||||
music(music) {}
|
||||
};
|
||||
@@ -78,7 +81,7 @@ class Resource {
|
||||
std::string name; // Nombre de la textura
|
||||
std::shared_ptr<Texture> texture; // Objeto con la textura
|
||||
|
||||
ResourceTexture(std::string name, std::shared_ptr<Texture> texture = nullptr)
|
||||
explicit ResourceTexture(std::string name, std::shared_ptr<Texture> texture = nullptr)
|
||||
: name(std::move(name)),
|
||||
texture(std::move(texture)) {}
|
||||
};
|
||||
@@ -87,7 +90,7 @@ class Resource {
|
||||
std::string name; // Nombre del fichero
|
||||
std::shared_ptr<Text::File> text_file; // Objeto con los descriptores de la fuente de texto
|
||||
|
||||
ResourceTextFile(std::string name, std::shared_ptr<Text::File> text_file = nullptr)
|
||||
explicit ResourceTextFile(std::string name, std::shared_ptr<Text::File> text_file = nullptr)
|
||||
: name(std::move(name)),
|
||||
text_file(std::move(text_file)) {}
|
||||
};
|
||||
@@ -96,7 +99,7 @@ class Resource {
|
||||
std::string name; // Nombre del objeto
|
||||
std::shared_ptr<Text> text; // Objeto de texto
|
||||
|
||||
ResourceText(std::string name, std::shared_ptr<Text> text = nullptr)
|
||||
explicit ResourceText(std::string name, std::shared_ptr<Text> text = nullptr)
|
||||
: name(std::move(name)),
|
||||
text(std::move(text)) {}
|
||||
};
|
||||
@@ -105,7 +108,7 @@ class Resource {
|
||||
std::string name; // Nombre de la animación
|
||||
AnimationsFileBuffer animation; // Objeto con las animaciones
|
||||
|
||||
ResourceAnimation(std::string name, AnimationsFileBuffer animation = {})
|
||||
explicit ResourceAnimation(std::string name, AnimationsFileBuffer animation = {})
|
||||
: name(std::move(name)),
|
||||
animation(std::move(animation)) {}
|
||||
};
|
||||
@@ -116,7 +119,7 @@ class Resource {
|
||||
size_t loaded{0}; // Número de recursos cargados
|
||||
|
||||
ResourceCount() = default;
|
||||
ResourceCount(size_t total)
|
||||
explicit ResourceCount(size_t total)
|
||||
: total(total) {}
|
||||
|
||||
void add(size_t amount) { loaded += amount; }
|
||||
@@ -156,7 +159,7 @@ class Resource {
|
||||
SDL_FRect loading_full_rect_;
|
||||
|
||||
// --- Estado del cargador incremental ---
|
||||
enum class LoadStage {
|
||||
enum class LoadStage : std::uint8_t {
|
||||
SOUNDS,
|
||||
MUSICS,
|
||||
TEXTURES,
|
||||
@@ -187,8 +190,8 @@ class Resource {
|
||||
|
||||
// --- Métodos para carga perezosa ---
|
||||
void initResourceLists(); // Inicializa las listas de recursos sin cargar el contenido
|
||||
static auto loadSoundLazy(const std::string& name) -> JA_Sound_t*; // Carga un sonido específico bajo demanda
|
||||
static auto loadMusicLazy(const std::string& name) -> JA_Music_t*; // Carga una música específica bajo demanda
|
||||
static auto loadSoundLazy(const std::string& name) -> Ja::Sound*; // Carga un sonido específico bajo demanda
|
||||
static auto loadMusicLazy(const std::string& name) -> Ja::Music*; // Carga una música específica bajo demanda
|
||||
static auto loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture>; // Carga una textura específica bajo demanda
|
||||
static auto loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File>; // Carga un fichero de texto específico bajo demanda
|
||||
auto loadTextLazy(const std::string& name) -> std::shared_ptr<Text>; // Carga un objeto de texto específico bajo demanda
|
||||
@@ -200,6 +203,10 @@ class Resource {
|
||||
void initProgressBar(); // Inicializa los rectangulos que definen la barra de progreso
|
||||
void updateProgressBar(); // Actualiza la barra de estado
|
||||
|
||||
// Avança una etapa que descarrega una llista d'assets: si `stage_index_` desborda la mida,
|
||||
// salta a `next_stage`; si no, crida `load_one` per a l'element actual i incrementa.
|
||||
void advanceListLoadStage(const std::vector<std::string>& list, void (Resource::*load_one)(size_t), LoadStage next_stage);
|
||||
|
||||
// --- Helpers del cargador incremental (cargan un único recurso) ---
|
||||
void loadOneSound(size_t idx);
|
||||
void loadOneMusic(size_t idx);
|
||||
|
||||
@@ -59,20 +59,10 @@ namespace ResourceHelper {
|
||||
}
|
||||
|
||||
auto shouldUseResourcePack(const std::string& filepath) -> bool {
|
||||
// Archivos que NO van al pack:
|
||||
// - config/ (ahora está fuera de data/)
|
||||
// - archivos absolutos del sistema
|
||||
|
||||
if (filepath.find("config/") != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si contiene "data/" es candidato para el pack
|
||||
if (filepath.find("data/") != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Si la ruta conté "data/" és candidat al pack. La resta (paths absoluts del
|
||||
// SYSTEM_FOLDER o relatives al binari com gamecontrollerdb.txt) van sempre al
|
||||
// filesystem.
|
||||
return filepath.find("data/") != std::string::npos;
|
||||
}
|
||||
|
||||
auto getPackPath(const std::string& asset_path) -> std::string {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // Para uint8_t
|
||||
#include <filesystem> // Para remove, path
|
||||
#include <fstream> // Para basic_ofstream, basic_ios, basic_ostream::write, ios, ofstream
|
||||
#include <string> // Para string, basic_string, hash, operator+, to_string, __str_hash_base
|
||||
#include <vector> // Para vector
|
||||
#include <cstdint> // Para uint8_t
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
// Helper functions para integrar ResourceLoader con el sistema existente
|
||||
namespace ResourceHelper {
|
||||
@@ -22,24 +20,4 @@ namespace ResourceHelper {
|
||||
|
||||
// Convierte ruta Asset a ruta relativa para ResourceLoader
|
||||
auto getPackPath(const std::string& asset_path) -> std::string;
|
||||
|
||||
// Wrappea la carga de archivos para mantener compatibilidad
|
||||
template <typename T>
|
||||
auto loadResourceFile(const std::string& asset_path, T* (*loader_func)(const char*)) -> T* {
|
||||
auto data = loadFile(asset_path);
|
||||
if (data.empty()) {
|
||||
return loader_func(asset_path.c_str());
|
||||
}
|
||||
|
||||
// Crear archivo temporal para funciones que esperan path
|
||||
std::string temp_path = "/tmp/ccae_" + std::to_string(std::hash<std::string>{}(asset_path));
|
||||
std::ofstream temp_file(temp_path, std::ios::binary);
|
||||
temp_file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
temp_file.close();
|
||||
|
||||
T* result = loader_func(temp_path.c_str());
|
||||
std::filesystem::remove(temp_path);
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace ResourceHelper
|
||||
@@ -9,9 +9,7 @@
|
||||
|
||||
std::unique_ptr<ResourceLoader> ResourceLoader::instance = nullptr;
|
||||
|
||||
ResourceLoader::ResourceLoader()
|
||||
: resource_pack_(nullptr),
|
||||
fallback_to_files_(true) {}
|
||||
ResourceLoader::ResourceLoader() = default;
|
||||
|
||||
auto ResourceLoader::getInstance() -> ResourceLoader& {
|
||||
if (!instance) {
|
||||
|
||||
@@ -11,9 +11,9 @@ class ResourcePack;
|
||||
class ResourceLoader {
|
||||
private:
|
||||
static std::unique_ptr<ResourceLoader> instance;
|
||||
ResourcePack* resource_pack_;
|
||||
ResourcePack* resource_pack_ = nullptr;
|
||||
std::string pack_path_;
|
||||
bool fallback_to_files_;
|
||||
bool fallback_to_files_ = true;
|
||||
|
||||
ResourceLoader();
|
||||
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
#include "core/resources/resource_pack.hpp"
|
||||
|
||||
#include <algorithm> // Para replace
|
||||
#include <algorithm> // Para replace, ranges::all_of
|
||||
#include <array> // Para array
|
||||
#include <filesystem> // Para path, recursive_directory_iterator, directory_entry, exists, relative
|
||||
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ofstream, operator<<, basic_ios, basic_istream::read, basic_ostream::write, endl, ios, basic_istream, ifstream, operator|, basic_istream::seekg, basic_istream::tellg, ofstream, streamsize
|
||||
#include <iostream> // Para cerr
|
||||
#include <numeric> // Para accumulate
|
||||
#include <utility> // Para pair
|
||||
|
||||
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCAE_RESOURCES__2024";
|
||||
|
||||
ResourcePack::ResourcePack()
|
||||
: loaded_(false) {}
|
||||
ResourcePack::ResourcePack() = default;
|
||||
|
||||
ResourcePack::~ResourcePack() {
|
||||
clear();
|
||||
}
|
||||
|
||||
auto ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t {
|
||||
uint32_t checksum = 0x12345678;
|
||||
for (unsigned char i : data) {
|
||||
checksum = ((checksum << 5) + checksum) + i;
|
||||
}
|
||||
return checksum;
|
||||
return std::accumulate(data.begin(), data.end(), static_cast<uint32_t>(0x12345678), [](uint32_t checksum, unsigned char i) { return ((checksum << 5) + checksum) + i; });
|
||||
}
|
||||
|
||||
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
|
||||
@@ -162,20 +158,16 @@ auto ResourcePack::addDirectory(const std::string& directory) -> bool {
|
||||
return false; // NOLINT(readability-simplify-boolean-expr)
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
|
||||
if (entry.is_regular_file()) {
|
||||
return std::ranges::all_of(std::filesystem::recursive_directory_iterator(directory),
|
||||
[this, &directory](const auto& entry) {
|
||||
if (!entry.is_regular_file()) {
|
||||
return true;
|
||||
}
|
||||
std::string filepath = entry.path().string();
|
||||
std::string filename = std::filesystem::relative(entry.path(), directory).string();
|
||||
|
||||
std::ranges::replace(filename, '\\', '/');
|
||||
|
||||
if (!addFile(filename, filepath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return addFile(filename, filepath);
|
||||
});
|
||||
}
|
||||
|
||||
auto ResourcePack::getResource(const std::string& filename) -> std::vector<uint8_t> {
|
||||
|
||||
@@ -17,7 +17,7 @@ class ResourcePack {
|
||||
private:
|
||||
std::unordered_map<std::string, ResourceEntry> resources_;
|
||||
std::vector<uint8_t> data_;
|
||||
bool loaded_;
|
||||
bool loaded_ = false;
|
||||
|
||||
static auto calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t;
|
||||
static void encryptData(std::vector<uint8_t>& data, const std::string& key);
|
||||
|
||||
@@ -177,8 +177,8 @@ namespace Defaults::Player::OutlineColor {
|
||||
|
||||
namespace Defaults::Window {
|
||||
constexpr const char* CAPTION = "© 2025 Coffee Crisis Arcade Edition — JailDesigner";
|
||||
constexpr int ZOOM = 2;
|
||||
constexpr int MAX_ZOOM = 2;
|
||||
constexpr int ZOOM = 3;
|
||||
constexpr int MAX_ZOOM = 3;
|
||||
} // namespace Defaults::Window
|
||||
|
||||
namespace Defaults::Video {
|
||||
@@ -188,9 +188,6 @@ namespace Defaults::Video {
|
||||
constexpr bool INTEGER_SCALE = true;
|
||||
constexpr bool GPU_ACCELERATION = true;
|
||||
constexpr bool SHADER_ENABLED = false;
|
||||
constexpr bool SUPERSAMPLING = false;
|
||||
constexpr bool LINEAR_UPSCALE = false;
|
||||
constexpr int DOWNSCALE_ALGO = 1;
|
||||
} // namespace Defaults::Video
|
||||
|
||||
namespace Defaults::Music {
|
||||
@@ -211,7 +208,7 @@ namespace Defaults::Audio {
|
||||
namespace Defaults::Settings {
|
||||
constexpr bool AUTOFIRE = true;
|
||||
constexpr bool SHUTDOWN_ENABLED = false;
|
||||
constexpr const char* PARAMS_FILE = "param_320x256.txt";
|
||||
constexpr const char* PARAMS_PRESET = "arcade"; // Identificador intern del preset (correspon a data/config/<preset>.txt)
|
||||
} // namespace Defaults::Settings
|
||||
|
||||
namespace Defaults::Loading {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <stdexcept> // Para runtime_error
|
||||
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
#include "utils/utils.hpp" // Para getFileName
|
||||
|
||||
// Carga el fichero de datos para la demo
|
||||
auto loadDemoDataFromFile(const std::string& file_path) -> DemoData {
|
||||
|
||||
+100
-84
@@ -3,7 +3,9 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_SetLogPriority, SDL_LogCategory, SDL_LogPriority, SDL_Quit
|
||||
|
||||
#include <cerrno> // Para errno
|
||||
#include <cstdlib> // Para srand, exit, rand, EXIT_FAILURE
|
||||
#include <cstring> // Para std::strerror
|
||||
#include <ctime> // Para time
|
||||
#include <fstream> // Para ifstream, ofstream
|
||||
#include <iostream> // Para basic_ostream, operator<<, cerr
|
||||
@@ -11,6 +13,12 @@
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string> // Para allocator, basic_string, char_traits, operator+, string, operator==
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h> // Per _execv
|
||||
#else
|
||||
#include <unistd.h> // Per execv
|
||||
#endif
|
||||
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/input/input.hpp" // Para Input
|
||||
#include "core/locale/lang.hpp" // Para setLanguage
|
||||
@@ -38,8 +46,41 @@
|
||||
#include "game/ui/service_menu.hpp" // Para ServiceMenu
|
||||
#include "utils/param.hpp" // Para loadParamsFromFile
|
||||
|
||||
namespace {
|
||||
// Llig un camp opcional d'un YAML cap a `dest`. Si no existeix, no toca `dest`.
|
||||
// Si existeix però el tipus no encaixa, deixa el valor per defecte i avisa per stderr
|
||||
// (un debug.yaml mal escrit no ha de tombar l'arrencada, però l'usuari ha de saber-ho).
|
||||
template <typename T>
|
||||
void loadYamlField(const fkyaml::node& yaml, const std::string& key, T& dest) {
|
||||
if (!yaml.contains(key)) { return; }
|
||||
try {
|
||||
dest = yaml[key].get_value<T>();
|
||||
} catch (...) {
|
||||
std::cerr << "debug.yaml: valor invàlid per a '" << key << "', es manté el valor per defecte\n";
|
||||
}
|
||||
}
|
||||
|
||||
auto parseInitialSection(const std::string& value) -> Section::Name {
|
||||
if (value == "logo") { return Section::Name::LOGO; }
|
||||
if (value == "intro") { return Section::Name::INTRO; }
|
||||
if (value == "title") { return Section::Name::TITLE; }
|
||||
if (value == "credits") { return Section::Name::CREDITS; }
|
||||
if (value == "instructions") { return Section::Name::INSTRUCTIONS; }
|
||||
if (value == "hiscore") { return Section::Name::HI_SCORE_TABLE; }
|
||||
return Section::Name::GAME; // "game" i qualsevol valor desconegut
|
||||
}
|
||||
|
||||
auto parseInitialOptions(const std::string& value) -> Section::Options {
|
||||
if (value == "none") { return Section::Options::NONE; }
|
||||
if (value == "2p") { return Section::Options::GAME_PLAY_2P; }
|
||||
if (value == "both") { return Section::Options::GAME_PLAY_BOTH; }
|
||||
return Section::Options::GAME_PLAY_1P; // "1p" i qualsevol valor desconegut
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Constructor
|
||||
Director::Director() {
|
||||
Director::Director(int /*argc*/, char** argv)
|
||||
: argv_(argv) {
|
||||
Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;
|
||||
|
||||
// Establece el nivel de prioridad de la categoría de registro
|
||||
@@ -109,7 +150,9 @@ void Director::init() {
|
||||
|
||||
loadAssets(); // Crea el índice de archivos
|
||||
|
||||
Input::init(Asset::get()->getPath("gamecontrollerdb.txt"), Asset::get()->getPath("controllers.json")); // Carga configuración de controles
|
||||
// gamecontrollerdb.txt no pot anar al pack (SDL_AddGamepadMappingsFromFile només llegeix del FS).
|
||||
// Sempre viu al costat del binari, fora del índex d'assets.
|
||||
Input::init(executable_path_ + "gamecontrollerdb.txt", Asset::get()->getPath("controllers.json")); // Carga configuración de controles
|
||||
|
||||
Options::setConfigFile(Asset::get()->getPath("config.yaml")); // Establece el fichero de configuración
|
||||
Options::setControllersFile(Asset::get()->getPath("controllers.json")); // Establece el fichero de configuración de mandos
|
||||
@@ -180,8 +223,8 @@ void Director::finishBoot() {
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra todo y libera recursos del sistema y de los singletons
|
||||
void Director::close() {
|
||||
// Allibera tots els singletons i SDL (compartit entre close() i relaunch())
|
||||
void Director::shutdownSubsystems() {
|
||||
// Guarda las opciones actuales en el archivo de configuración
|
||||
Options::saveToFile();
|
||||
|
||||
@@ -194,22 +237,47 @@ void Director::close() {
|
||||
Screen::destroy(); // Libera el sistema de pantalla y renderizado
|
||||
Asset::destroy(); // Libera el gestor de archivos
|
||||
|
||||
std::cout << "\nBye!\n";
|
||||
|
||||
// Libera todos los recursos de SDL
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
// Cierra todo y libera recursos del sistema y de los singletons
|
||||
void Director::close() {
|
||||
shutdownSubsystems();
|
||||
std::cout << "\nBye!\n";
|
||||
// Apaga el sistema
|
||||
shutdownSystem(Section::options == Section::Options::SHUTDOWN);
|
||||
}
|
||||
|
||||
// Reemplaça el procés actual per ell mateix (reinici en calent). En cas d'èxit no torna.
|
||||
// Si no es pot reiniciar (Emscripten, argv invàlid), retorna i el caller fa el reset clàssic.
|
||||
void Director::relaunch() const {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Al navegador el reinici real seria location.reload(); aquí caiem al reset intern.
|
||||
return;
|
||||
#else
|
||||
if (argv_ == nullptr || argv_[0] == nullptr) { return; }
|
||||
std::cout << "Relaunching " << argv_[0] << "...\n";
|
||||
shutdownSubsystems();
|
||||
#ifdef _WIN32
|
||||
_execv(argv_[0], argv_);
|
||||
#else
|
||||
execv(argv_[0], argv_);
|
||||
#endif
|
||||
// Si arribem aquí, execv ha fallat. Tots els subsistemes ja estan destruïts: no
|
||||
// podem reprendre el bucle. Sortim amb error.
|
||||
std::cerr << "Relaunch failed: " << std::strerror(errno) << "\n";
|
||||
std::exit(EXIT_FAILURE);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Carga los parametros
|
||||
void Director::loadParams() {
|
||||
// Carga los parametros para configurar el juego
|
||||
#ifdef ANBERNIC
|
||||
const std::string PARAM_FILE_PATH = Asset::get()->getPath("param_320x240.txt");
|
||||
const std::string PARAM_FILE_PATH = Asset::get()->getPath("classic.txt");
|
||||
#else
|
||||
const std::string PARAM_FILE_PATH = Asset::get()->getPath(Options::settings.params_file);
|
||||
const std::string PARAM_FILE_PATH = Asset::get()->getPath(Options::settings.params_preset + ".txt");
|
||||
#endif
|
||||
loadParamsFromFile(PARAM_FILE_PATH);
|
||||
}
|
||||
@@ -224,7 +292,7 @@ void Director::loadScoreFile() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Carga el indice de ficheros desde un fichero
|
||||
// Carga el indice de ficheros desde el pack (o filesystem como fallback)
|
||||
void Director::loadAssets() {
|
||||
#ifdef MACOS_BUNDLE
|
||||
const std::string PREFIX = "/../Resources";
|
||||
@@ -232,9 +300,13 @@ void Director::loadAssets() {
|
||||
const std::string PREFIX;
|
||||
#endif
|
||||
|
||||
// Cargar la configuración de assets (también aplicar el prefijo al archivo de configuración)
|
||||
std::string config_path = executable_path_ + PREFIX + "/config/assets.txt";
|
||||
Asset::get()->loadFromFile(config_path, PREFIX, system_folder_);
|
||||
// El índice ahora vive dins el pack a "config/assets.txt" (ResourceHelper li trau el "data/" prefix).
|
||||
// ResourceHelper::loadFile fa fallback automàtic al filesystem si el pack no està o no conté el fitxer.
|
||||
auto buffer = ResourceHelper::loadFile("/data/config/assets.txt");
|
||||
if (buffer.empty()) {
|
||||
throw std::runtime_error("No s'ha pogut carregar l'índex d'assets (data/config/assets.txt)");
|
||||
}
|
||||
Asset::get()->loadFromBuffer(buffer, PREFIX, system_folder_);
|
||||
|
||||
// Si falta algun fichero, sale del programa
|
||||
if (!Asset::get()->check()) {
|
||||
@@ -275,78 +347,20 @@ void Director::loadDebugConfig() {
|
||||
file.close();
|
||||
try {
|
||||
auto yaml = fkyaml::node::deserialize(content);
|
||||
if (yaml.contains("initial_section")) {
|
||||
try {
|
||||
debug_config.initial_section = yaml["initial_section"].get_value<std::string>();
|
||||
} catch (...) {}
|
||||
}
|
||||
if (yaml.contains("initial_options")) {
|
||||
try {
|
||||
debug_config.initial_options = yaml["initial_options"].get_value<std::string>();
|
||||
} catch (...) {}
|
||||
}
|
||||
if (yaml.contains("initial_stage")) {
|
||||
try {
|
||||
debug_config.initial_stage = yaml["initial_stage"].get_value<int>();
|
||||
} catch (...) {}
|
||||
}
|
||||
if (yaml.contains("show_render_info")) {
|
||||
try {
|
||||
debug_config.show_render_info = yaml["show_render_info"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
}
|
||||
if (yaml.contains("resource_loading")) {
|
||||
try {
|
||||
debug_config.resource_loading = yaml["resource_loading"].get_value<std::string>();
|
||||
} catch (...) {}
|
||||
}
|
||||
if (yaml.contains("autoplay")) {
|
||||
try {
|
||||
debug_config.autoplay = yaml["autoplay"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
}
|
||||
if (yaml.contains("invincibility")) {
|
||||
try {
|
||||
debug_config.invincibility = yaml["invincibility"].get_value<bool>();
|
||||
} catch (...) {}
|
||||
}
|
||||
loadYamlField(yaml, "initial_section", debug_config.initial_section);
|
||||
loadYamlField(yaml, "initial_options", debug_config.initial_options);
|
||||
loadYamlField(yaml, "initial_stage", debug_config.initial_stage);
|
||||
loadYamlField(yaml, "show_render_info", debug_config.show_render_info);
|
||||
loadYamlField(yaml, "resource_loading", debug_config.resource_loading);
|
||||
loadYamlField(yaml, "autoplay", debug_config.autoplay);
|
||||
loadYamlField(yaml, "invincibility", debug_config.invincibility);
|
||||
} catch (...) {
|
||||
std::cout << "Error parsing debug.yaml, using defaults" << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Mapear strings a enums
|
||||
const auto& sec = debug_config.initial_section;
|
||||
if (sec == "logo") {
|
||||
Section::name = Section::Name::LOGO;
|
||||
} else if (sec == "intro") {
|
||||
Section::name = Section::Name::INTRO;
|
||||
} else if (sec == "title") {
|
||||
Section::name = Section::Name::TITLE;
|
||||
} else if (sec == "game") {
|
||||
Section::name = Section::Name::GAME;
|
||||
} else if (sec == "credits") {
|
||||
Section::name = Section::Name::CREDITS;
|
||||
} else if (sec == "instructions") {
|
||||
Section::name = Section::Name::INSTRUCTIONS;
|
||||
} else if (sec == "hiscore") {
|
||||
Section::name = Section::Name::HI_SCORE_TABLE;
|
||||
} else {
|
||||
Section::name = Section::Name::GAME;
|
||||
}
|
||||
|
||||
const auto& opt = debug_config.initial_options;
|
||||
if (opt == "none") {
|
||||
Section::options = Section::Options::NONE;
|
||||
} else if (opt == "1p") {
|
||||
Section::options = Section::Options::GAME_PLAY_1P;
|
||||
} else if (opt == "2p") {
|
||||
Section::options = Section::Options::GAME_PLAY_2P;
|
||||
} else if (opt == "both") {
|
||||
Section::options = Section::Options::GAME_PLAY_BOTH;
|
||||
} else {
|
||||
Section::options = Section::Options::GAME_PLAY_1P;
|
||||
}
|
||||
Section::name = parseInitialSection(debug_config.initial_section);
|
||||
Section::options = parseInitialOptions(debug_config.initial_options);
|
||||
}
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
@@ -374,9 +388,11 @@ void Director::resetActiveSection() {
|
||||
|
||||
// Destruye la sección anterior y construye la nueva cuando Section::name cambia
|
||||
void Director::handleSectionTransition() {
|
||||
// RESET: recarga recursos y vuelve a LOGO (el propio reset() cambia Section::name)
|
||||
// RESET: intenta reinici real via execv; si no es pot (Emscripten o argv invàlid),
|
||||
// cau al reset intern (recarrega recursos i torna a LOGO, sense recrear Screen/Params).
|
||||
if (Section::name == Section::Name::RESET) {
|
||||
resetActiveSection(); // libera recursos actuales antes del reload
|
||||
relaunch(); // En èxit no torna; el binari es reemplaça
|
||||
resetActiveSection(); // Fallback: libera recursos actuales antes del reload
|
||||
reset();
|
||||
}
|
||||
|
||||
@@ -499,7 +515,7 @@ auto Director::iterate() -> SDL_AppResult {
|
||||
|
||||
// Ejecuta un frame de la sección activa
|
||||
if (preload_) {
|
||||
preload_->iterate();
|
||||
Preload::iterate();
|
||||
} else if (logo_) {
|
||||
logo_->iterate();
|
||||
} else if (intro_) {
|
||||
@@ -520,13 +536,13 @@ auto Director::iterate() -> SDL_AppResult {
|
||||
}
|
||||
|
||||
// Procesa un evento SDL (llamado desde SDL_AppEvent)
|
||||
auto Director::handleEvent(SDL_Event& event) -> SDL_AppResult {
|
||||
auto Director::handleEvent(const SDL_Event& event) -> SDL_AppResult {
|
||||
// Eventos globales (SDL_EVENT_QUIT, resize, render target reset, hotplug, service menu, ratón)
|
||||
GlobalEvents::handle(event);
|
||||
|
||||
// Reenvía a la sección activa
|
||||
if (preload_) {
|
||||
preload_->handleEvent(event);
|
||||
Preload::handleEvent(event);
|
||||
} else if (logo_) {
|
||||
logo_->handleEvent(event);
|
||||
} else if (intro_) {
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_AppResult, SDL_Event
|
||||
|
||||
#include <memory> // Para unique_ptr
|
||||
#include <string> // Para string
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <memory> // Para unique_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
#include "core/system/section.hpp" // Para Section::Name
|
||||
|
||||
namespace Lang {
|
||||
enum class Code : int;
|
||||
enum class Code : std::uint8_t;
|
||||
}
|
||||
|
||||
// Declaraciones adelantadas de las secciones
|
||||
@@ -25,12 +26,12 @@ class Credits;
|
||||
class Director {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
Director();
|
||||
Director(int argc, char** argv);
|
||||
~Director();
|
||||
|
||||
// --- Callbacks para SDL_MAIN_USE_CALLBACKS ---
|
||||
auto iterate() -> SDL_AppResult; // Avanza un frame de la sección activa
|
||||
auto handleEvent(SDL_Event& event) -> SDL_AppResult; // Procesa un evento SDL
|
||||
auto iterate() -> SDL_AppResult; // Avanza un frame de la sección activa
|
||||
auto handleEvent(const SDL_Event& event) -> SDL_AppResult; // Procesa un evento SDL
|
||||
|
||||
// --- Debug config (accesible desde otras clases) ---
|
||||
struct DebugConfig {
|
||||
@@ -53,6 +54,7 @@ class Director {
|
||||
// --- Variables internas ---
|
||||
std::string executable_path_; // Ruta del ejecutable
|
||||
std::string system_folder_; // Carpeta del sistema para almacenar datos
|
||||
char** argv_ = nullptr; // argv original; usat per relaunch() (execv)
|
||||
|
||||
// --- Sección activa (una y sólo una viva en cada momento) ---
|
||||
std::unique_ptr<Preload> preload_;
|
||||
@@ -69,9 +71,11 @@ class Director {
|
||||
bool boot_loading_ = true; // True mientras Resource::loadStep está cargando incremental
|
||||
|
||||
// --- Inicialización y cierre del sistema ---
|
||||
void init(); // Inicializa la aplicación (pre-boot)
|
||||
void finishBoot(); // Post-boot: inicializa lo que depende de recursos cargados
|
||||
static void close(); // Cierra y libera recursos
|
||||
void init(); // Inicializa la aplicación (pre-boot)
|
||||
static void finishBoot(); // Post-boot: inicializa lo que depende de recursos cargados
|
||||
static void shutdownSubsystems(); // Allibera singletons i SDL (sense apagar el sistema)
|
||||
static void close(); // Cierra y libera recursos
|
||||
void relaunch() const; // Reemplaça el procés via execv (fallback silenciós si no es pot)
|
||||
|
||||
// --- Configuración inicial ---
|
||||
static void loadParams(); // Carga los parámetros del programa
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/*
|
||||
Namespace section: define los estados/secciones principales del programa,
|
||||
así como las opciones y modos especiales (como el Attract Mode).
|
||||
@@ -8,7 +10,7 @@
|
||||
|
||||
namespace Section {
|
||||
// --- Enumeraciones de secciones del programa ---
|
||||
enum class Name {
|
||||
enum class Name : std::uint8_t {
|
||||
RESET, // Inicialización
|
||||
PRELOAD, // Carga incremental de recursos
|
||||
LOGO, // Pantalla de logo
|
||||
@@ -23,7 +25,7 @@ namespace Section {
|
||||
};
|
||||
|
||||
// --- Opciones para la sección actual ---
|
||||
enum class Options {
|
||||
enum class Options : std::uint8_t {
|
||||
GAME_PLAY_1P, // Iniciar el juego con el jugador 1
|
||||
GAME_PLAY_2P, // Iniciar el juego con el jugador 2
|
||||
GAME_PLAY_BOTH, // Iniciar el juego con los dos jugadores
|
||||
@@ -37,7 +39,7 @@ namespace Section {
|
||||
};
|
||||
|
||||
// --- Modos para el Attract Mode ---
|
||||
enum class AttractMode {
|
||||
enum class AttractMode : std::uint8_t {
|
||||
TITLE_TO_DEMO, // Pasar de título a demo
|
||||
TITLE_TO_LOGO, // Pasar de título a logo
|
||||
};
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// --- Namespace SystemShutdown: utilidad multiplataforma para apagar el sistema de forma segura ---
|
||||
namespace SystemShutdown {
|
||||
|
||||
// --- Enums ---
|
||||
enum class ShutdownResult {
|
||||
enum class ShutdownResult : std::uint8_t {
|
||||
SUCCESS = 0, // Éxito
|
||||
ERROR_PERMISSION, // Error de permisos insuficientes
|
||||
ERROR_SYSTEM_CALL, // Error en la llamada al sistema
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
// --- Namespace SystemUtils: utilidades multiplataforma para operaciones del sistema ---
|
||||
namespace SystemUtils {
|
||||
// --- Enums ---
|
||||
enum class Result { // Códigos de resultado para operaciones del sistema
|
||||
enum class Result : std::uint8_t { // Códigos de resultado para operaciones del sistema
|
||||
SUCCESS = 0,
|
||||
PERMISSION_DENIED, // Sin permisos para crear la carpeta
|
||||
PATH_TOO_LONG, // Ruta demasiado larga
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
|
||||
#include "core/rendering/texture.hpp" // Para Texture
|
||||
#include "core/rendering/texture.hpp" // IWYU pragma: keep
|
||||
#include "utils/param.hpp" // Para Param, ParamBalloon, param
|
||||
|
||||
// Constructor
|
||||
|
||||
@@ -85,7 +85,7 @@ class Balloon {
|
||||
};
|
||||
|
||||
// --- Constructores y destructor ---
|
||||
Balloon(const Config& config);
|
||||
explicit Balloon(const Config& config);
|
||||
~Balloon() = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, Uint16
|
||||
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "utils/utils.hpp" // Para Circle
|
||||
@@ -12,7 +13,7 @@
|
||||
class Texture;
|
||||
|
||||
// --- Enums ---
|
||||
enum class ItemType : int {
|
||||
enum class ItemType : std::uint8_t {
|
||||
DISK = 1, // Disco
|
||||
GAVINA = 2, // Gavina
|
||||
PACMAR = 3, // Pacman
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/input/input.hpp" // Para Input
|
||||
#include "core/input/input_types.hpp" // Para InputAction
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "core/rendering/texture.hpp" // Para Texture
|
||||
#include "core/resources/asset.hpp" // Para Asset
|
||||
@@ -977,7 +976,7 @@ void Player::playSound(const std::string& name) const {
|
||||
return;
|
||||
}
|
||||
|
||||
static auto* audio_ = Audio::get();
|
||||
static const auto* audio_ = Audio::get();
|
||||
audio_->playSound(name);
|
||||
}
|
||||
|
||||
@@ -1135,8 +1134,6 @@ void Player::updateFiringStateFromVisual() {
|
||||
firing_state_ = State::RECOILING_RIGHT;
|
||||
break;
|
||||
case State::FIRING_UP:
|
||||
firing_state_ = State::RECOILING_UP;
|
||||
break;
|
||||
default:
|
||||
firing_state_ = State::RECOILING_UP;
|
||||
break;
|
||||
@@ -1152,8 +1149,6 @@ void Player::updateFiringStateFromVisual() {
|
||||
firing_state_ = State::COOLING_RIGHT;
|
||||
break;
|
||||
case State::FIRING_UP:
|
||||
firing_state_ = State::COOLING_UP;
|
||||
break;
|
||||
default:
|
||||
firing_state_ = State::COOLING_UP;
|
||||
break;
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_FlipMode
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <iterator> // Para pair
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para basic_string, string
|
||||
#include <utility> // Para move, pair
|
||||
#include <vector> // Para vector
|
||||
#include <cstddef> // Para size_t
|
||||
#include <cstdint> // Para std::uint8_t, std::int8_t
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para basic_string, string
|
||||
#include <utility> // Para move, pair
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/input/input.hpp" // for Input
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // for AnimatedSprite
|
||||
@@ -49,14 +49,14 @@ class Player {
|
||||
};
|
||||
|
||||
// --- Enums ---
|
||||
enum class Id : int {
|
||||
enum class Id : std::int8_t {
|
||||
NO_PLAYER = -1, // Sin jugador
|
||||
BOTH_PLAYERS = 0, // Ambos jugadores
|
||||
PLAYER1 = 1, // Jugador 1
|
||||
PLAYER2 = 2 // Jugador 2
|
||||
};
|
||||
|
||||
enum class State {
|
||||
enum class State : std::uint8_t {
|
||||
// Estados de movimiento
|
||||
WALKING_LEFT, // Caminando hacia la izquierda
|
||||
WALKING_RIGHT, // Caminando hacia la derecha
|
||||
@@ -114,7 +114,7 @@ class Player {
|
||||
};
|
||||
|
||||
// --- Constructor y destructor ---
|
||||
Player(const Config& config);
|
||||
explicit Player(const Config& config);
|
||||
~Player() = default;
|
||||
|
||||
// --- Inicialización y ciclo de vida ---
|
||||
@@ -201,16 +201,16 @@ class Player {
|
||||
[[nodiscard]] auto getCoffees() const -> int { return coffees_; }
|
||||
[[nodiscard]] auto getPowerUpCounter() const -> int { return power_up_counter_; }
|
||||
[[nodiscard]] auto getInvulnerableCounter() const -> int { return invulnerable_counter_; }
|
||||
[[nodiscard]] auto getBulletColor() const -> Bullet::Color; // Devuelve el color actual de bala según el estado
|
||||
auto getNextBulletColor() -> Bullet::Color; // Devuelve el color para la próxima bala (alterna si está en modo toggle)
|
||||
void setBulletColors(Bullet::Color normal, Bullet::Color powered); // Establece los colores de bala para este jugador
|
||||
[[nodiscard]] auto getBulletSoundFile() const -> std::string { return bullet_sound_file_; } // Devuelve el archivo de sonido de bala
|
||||
void setBulletSoundFile(const std::string& filename); // Establece el archivo de sonido de bala para este jugador
|
||||
[[nodiscard]] auto getBulletColor() const -> Bullet::Color; // Devuelve el color actual de bala según el estado
|
||||
auto getNextBulletColor() -> Bullet::Color; // Devuelve el color para la próxima bala (alterna si está en modo toggle)
|
||||
void setBulletColors(Bullet::Color normal, Bullet::Color powered); // Establece los colores de bala para este jugador
|
||||
[[nodiscard]] auto getBulletSoundFile() const -> const std::string& { return bullet_sound_file_; } // Devuelve el archivo de sonido de bala
|
||||
void setBulletSoundFile(const std::string& filename); // Establece el archivo de sonido de bala para este jugador
|
||||
|
||||
// Contadores y timers
|
||||
[[nodiscard]] auto getContinueCounter() const -> int { return continue_counter_; }
|
||||
[[nodiscard]] auto getRecordName() const -> std::string { return enter_name_ ? enter_name_->getFinalName() : "xxx"; }
|
||||
[[nodiscard]] auto getLastEnterName() const -> std::string { return last_enter_name_; }
|
||||
[[nodiscard]] auto getLastEnterName() const -> const std::string& { return last_enter_name_; }
|
||||
|
||||
// --- Configuración e interfaz externa ---
|
||||
void setName(const std::string& name) { name_ = name; }
|
||||
@@ -293,7 +293,7 @@ class Player {
|
||||
bool can_fire_new_system_ = true; // true si puede disparar ahora mismo
|
||||
|
||||
// LÍNEA 2: SISTEMA VISUAL (Animaciones)
|
||||
enum class VisualFireState {
|
||||
enum class VisualFireState : std::uint8_t {
|
||||
NORMAL, // Brazo en posición neutral
|
||||
AIMING, // Brazo alzado (disparando)
|
||||
RECOILING, // Brazo en retroceso
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para Uint32, SDL_GetTicks, SDL_FRect
|
||||
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <cstdlib> // Para rand
|
||||
#include <memory> // Para unique_ptr
|
||||
|
||||
@@ -11,12 +12,12 @@
|
||||
class Tabe {
|
||||
public:
|
||||
// --- Enumeraciones para dirección y estado ---
|
||||
enum class Direction : int {
|
||||
enum class Direction : std::uint8_t {
|
||||
TO_THE_LEFT = 0,
|
||||
TO_THE_RIGHT = 1,
|
||||
};
|
||||
|
||||
enum class State : int {
|
||||
enum class State : std::uint8_t {
|
||||
FLY = 0,
|
||||
HIT = 1,
|
||||
};
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
#include <cctype> // Para isdigit
|
||||
#include <cstddef> // Para size_t
|
||||
#include <exception> // Para exception
|
||||
#include <fstream> // Para basic_istream, basic_ifstream, ifstream, istringstream
|
||||
#include <iterator> // Para reverse_iterator
|
||||
#include <map> // Para map, operator==, _Rb_tree_iterator
|
||||
#include <sstream> // Para basic_istringstream
|
||||
#include <string> // Para string, char_traits, allocator, operator==, stoi, getline, operator<=>, basic_string
|
||||
#include <utility> // Para std::cmp_less
|
||||
|
||||
#include "core/resources/asset.hpp" // Para Asset
|
||||
#include "game/entities/balloon.hpp" // Para Balloon
|
||||
#include "utils/param.hpp" // Para Param, ParamGame, param
|
||||
#include "utils/utils.hpp" // Para Zone, BLOCK
|
||||
#include "core/resources/asset.hpp" // Para Asset
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper::loadFile
|
||||
#include "game/entities/balloon.hpp" // Para Balloon
|
||||
#include "utils/param.hpp" // Para Param, ParamGame, param
|
||||
#include "utils/utils.hpp" // Para Zone, BLOCK
|
||||
|
||||
void BalloonFormations::initFormations() {
|
||||
// Calcular posiciones base
|
||||
@@ -60,16 +60,19 @@ void BalloonFormations::initFormations() {
|
||||
}
|
||||
|
||||
auto BalloonFormations::loadFormationsFromFile(const std::string& filename, const std::map<std::string, float>& variables) -> bool {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
auto buffer = ResourceHelper::loadFile(filename);
|
||||
if (buffer.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string content(buffer.begin(), buffer.end());
|
||||
std::istringstream stream(content);
|
||||
|
||||
std::string line;
|
||||
int current_formation = -1;
|
||||
std::vector<SpawnParams> current_params;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
while (std::getline(stream, line)) {
|
||||
// Eliminar espacios en blanco al inicio y final
|
||||
line = trim(line);
|
||||
|
||||
@@ -113,7 +116,6 @@ auto BalloonFormations::loadFormationsFromFile(const std::string& filename, cons
|
||||
addTestFormation();
|
||||
#endif
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -201,6 +203,8 @@ auto BalloonFormations::evaluateSimpleExpression(const std::string& expr, const
|
||||
return left_val * right_val;
|
||||
case '/':
|
||||
return right_val != 0 ? left_val / right_val : 0;
|
||||
default:
|
||||
break; // Inalcanzable: el if exterior solo deja pasar '+', '-', '*', '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,13 +226,13 @@ void BalloonFormations::createFloaterVariants() {
|
||||
formations_.resize(100);
|
||||
|
||||
// Crear variantes flotantes de las primeras 50 formaciones
|
||||
for (size_t k = 0; k < 50 && k < formations_.size(); k++) {
|
||||
for (size_t k = 0; k < 50; k++) {
|
||||
const auto& source = formations_.at(k).balloons;
|
||||
std::vector<SpawnParams> floater_params;
|
||||
floater_params.reserve(formations_.at(k).balloons.size());
|
||||
|
||||
for (const auto& original : formations_.at(k).balloons) {
|
||||
floater_params.emplace_back(original.x, original.y, original.vel_x, Balloon::Type::FLOATER, original.size, original.creation_counter);
|
||||
}
|
||||
floater_params.reserve(source.size());
|
||||
std::ranges::transform(source, std::back_inserter(floater_params), [](const auto& original) {
|
||||
return SpawnParams{original.x, original.y, original.vel_x, Balloon::Type::FLOATER, original.size, original.creation_counter};
|
||||
});
|
||||
|
||||
formations_.at(k + 50) = Formation(floater_params);
|
||||
}
|
||||
@@ -270,18 +274,21 @@ void BalloonFormations::initFormationPools() {
|
||||
}
|
||||
|
||||
auto BalloonFormations::loadPoolsFromFile(const std::string& filename) -> bool {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
auto buffer = ResourceHelper::loadFile(filename);
|
||||
if (buffer.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string content(buffer.begin(), buffer.end());
|
||||
std::istringstream stream(content);
|
||||
|
||||
std::string line;
|
||||
pools_.clear(); // Limpiar pools existentes
|
||||
|
||||
// Map temporal para ordenar los pools por ID
|
||||
std::map<int, std::vector<int>> temp_pools;
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
while (std::getline(stream, line)) {
|
||||
// Eliminar espacios en blanco al inicio y final
|
||||
line = trim(line);
|
||||
|
||||
@@ -297,8 +304,6 @@ auto BalloonFormations::loadPoolsFromFile(const std::string& filename) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// Convertir el map ordenado a vector
|
||||
// Redimensionar el vector para el pool con ID más alto
|
||||
if (!temp_pools.empty()) {
|
||||
@@ -395,24 +400,21 @@ void BalloonFormations::loadDefaultPools() {
|
||||
}
|
||||
}
|
||||
|
||||
// Pool 2: Mix de formaciones normales y floaters (50+)
|
||||
// Pools 2 i 3: requereixen formacions de floaters (>50)
|
||||
if (total_formations > 50) {
|
||||
// Pool 2: Mix de formacions normals i floaters
|
||||
Pool pool2;
|
||||
// Agregar algunas formaciones básicas
|
||||
for (size_t i = 0; i < std::min(static_cast<size_t>(5), total_formations); ++i) {
|
||||
pool2.push_back(static_cast<int>(i));
|
||||
}
|
||||
// Agregar algunas floaters si existen
|
||||
for (size_t i = 50; i < std::min(static_cast<size_t>(55), total_formations); ++i) {
|
||||
pool2.push_back(static_cast<int>(i));
|
||||
}
|
||||
if (!pool2.empty()) {
|
||||
pools_.push_back(pool2);
|
||||
}
|
||||
}
|
||||
|
||||
// Pool 3: Solo floaters (si existen formaciones 50+)
|
||||
if (total_formations > 50) {
|
||||
// Pool 3: Només floaters
|
||||
Pool pool3;
|
||||
for (size_t i = 50; i < std::min(static_cast<size_t>(70), total_formations); ++i) {
|
||||
pool3.push_back(static_cast<int>(i));
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <iterator> // Para pair
|
||||
#include <map> // Para map
|
||||
#include <optional> // Para optional
|
||||
#include <string> // Para string
|
||||
@@ -39,7 +38,7 @@ class BalloonFormations {
|
||||
std::vector<SpawnParams> balloons; // Vector con todas las inicializaciones de los globos de la formación
|
||||
|
||||
// Constructor con parámetros
|
||||
Formation(const std::vector<SpawnParams>& spawn_params)
|
||||
explicit Formation(const std::vector<SpawnParams>& spawn_params)
|
||||
: balloons(spawn_params) {}
|
||||
|
||||
// Constructor por defecto
|
||||
|
||||
@@ -106,7 +106,7 @@ void BalloonManager::deployRandomFormation(int stage) {
|
||||
|
||||
// Crea los globos de la formación
|
||||
const auto BALLOONS = balloon_formations_->getFormationFromPool(stage, formation_id).balloons;
|
||||
for (auto balloon : BALLOONS) {
|
||||
for (const auto& balloon : BALLOONS) {
|
||||
Balloon::Config config = {
|
||||
.x = balloon.x,
|
||||
.y = balloon.y,
|
||||
@@ -127,7 +127,7 @@ void BalloonManager::deployRandomFormation(int stage) {
|
||||
// Crea una formación de globos específica
|
||||
void BalloonManager::deployFormation(int formation_id) {
|
||||
const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons;
|
||||
for (auto balloon : BALLOONS) {
|
||||
for (const auto& balloon : BALLOONS) {
|
||||
Balloon::Config config = {
|
||||
.x = balloon.x,
|
||||
.y = balloon.y,
|
||||
@@ -143,7 +143,7 @@ void BalloonManager::deployFormation(int formation_id) {
|
||||
// Crea una formación de globos específica a una altura determinada
|
||||
void BalloonManager::deployFormation(int formation_id, float y) {
|
||||
const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons;
|
||||
for (auto balloon : BALLOONS) {
|
||||
for (const auto& balloon : BALLOONS) {
|
||||
Balloon::Config config = {
|
||||
.x = balloon.x,
|
||||
.y = y,
|
||||
|
||||
@@ -24,7 +24,7 @@ using Balloons = std::list<std::shared_ptr<Balloon>>;
|
||||
class BalloonManager {
|
||||
public:
|
||||
// --- Constructor y destructor ---
|
||||
BalloonManager(IStageInfo* stage_info);
|
||||
explicit BalloonManager(IStageInfo* stage_info);
|
||||
~BalloonManager() = default;
|
||||
|
||||
// --- Métodos principales ---
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "game/gameplay/bullet_manager.hpp"
|
||||
|
||||
#include <algorithm> // Para remove_if
|
||||
#include <utility>
|
||||
|
||||
#include "game/entities/bullet.hpp" // Para Bullet
|
||||
@@ -14,7 +13,7 @@ BulletManager::BulletManager()
|
||||
|
||||
// Actualiza el estado de todas las balas
|
||||
void BulletManager::update(float delta_time) {
|
||||
for (auto& bullet : bullets_) {
|
||||
for (const auto& bullet : bullets_) {
|
||||
if (bullet->isEnabled()) {
|
||||
processBulletUpdate(bullet, delta_time);
|
||||
}
|
||||
@@ -23,7 +22,7 @@ void BulletManager::update(float delta_time) {
|
||||
|
||||
// Renderiza todas las balas activas
|
||||
void BulletManager::render() {
|
||||
for (auto& bullet : bullets_) {
|
||||
for (const auto& bullet : bullets_) {
|
||||
if (bullet->isEnabled()) {
|
||||
bullet->render();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <functional> // Para function
|
||||
#include <list> // Para list
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "game/entities/bullet.hpp" // for Bullet
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
class Cooldown {
|
||||
public:
|
||||
Cooldown(float first_delay_s = 0.0F, float repeat_delay_s = 0.0F)
|
||||
explicit Cooldown(float first_delay_s = 0.0F, float repeat_delay_s = 0.0F)
|
||||
: first_delay_s_(first_delay_s),
|
||||
repeat_delay_s_(repeat_delay_s) {}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "game/gameplay/difficulty.hpp"
|
||||
|
||||
#include <vector> // Para vector
|
||||
#include <algorithm> // Para ranges::find_if
|
||||
#include <vector> // Para vector
|
||||
|
||||
namespace Difficulty {
|
||||
|
||||
@@ -18,19 +19,19 @@ namespace Difficulty {
|
||||
}
|
||||
|
||||
auto getNameFromCode(Code code) -> std::string {
|
||||
for (const auto& difficulty : difficulties_list) {
|
||||
if (difficulty.code == code) {
|
||||
return difficulty.name;
|
||||
}
|
||||
const auto IT = std::ranges::find_if(difficulties_list,
|
||||
[code](const auto& difficulty) { return difficulty.code == code; });
|
||||
if (IT != difficulties_list.end()) {
|
||||
return IT->name;
|
||||
}
|
||||
return !difficulties_list.empty() ? difficulties_list.front().name : "Unknown";
|
||||
}
|
||||
|
||||
auto getCodeFromName(const std::string& name) -> Code {
|
||||
for (const auto& difficulty : difficulties_list) {
|
||||
if (difficulty.name == name) {
|
||||
return difficulty.code;
|
||||
}
|
||||
const auto IT = std::ranges::find_if(difficulties_list,
|
||||
[&name](const auto& difficulty) { return difficulty.name == name; });
|
||||
if (IT != difficulties_list.end()) {
|
||||
return IT->code;
|
||||
}
|
||||
return !difficulties_list.empty() ? difficulties_list.front().code : Code::NORMAL;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
namespace Difficulty {
|
||||
|
||||
// --- Enums ---
|
||||
enum class Code {
|
||||
enum class Code : std::uint8_t {
|
||||
EASY = 0, // Dificultad fácil
|
||||
NORMAL = 1, // Dificultad normal
|
||||
HARD = 2, // Dificultad difícil
|
||||
|
||||
@@ -21,7 +21,7 @@ class EnterName {
|
||||
void removeLastCharacter(); // Elimina el último carácter del nombre
|
||||
|
||||
auto getFinalName() -> std::string; // Obtiene el nombre final (o aleatorio si vacío)
|
||||
[[nodiscard]] auto getCurrentName() const -> std::string { return name_; } // Obtiene el nombre actual en proceso
|
||||
[[nodiscard]] auto getCurrentName() const -> const std::string& { return name_; } // Obtiene el nombre actual en proceso
|
||||
[[nodiscard]] auto getSelectedCharacter(int offset = 0) const -> std::string; // Devuelve el carácter seleccionado con offset relativo
|
||||
[[nodiscard]] auto getCarousel(int size) const -> std::string; // Devuelve el carrusel de caracteres (size debe ser impar)
|
||||
[[nodiscard]] auto getSelectedIndex() const -> int { return selected_index_; } // Obtiene el índice del carácter seleccionado
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory> // Para unique_ptr, shared_ptr
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <memory> // Para unique_ptr, shared_ptr
|
||||
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
|
||||
#include "core/rendering/sprite/smart_sprite.hpp" // Para SmartSprite
|
||||
@@ -36,7 +37,7 @@ class GameLogo {
|
||||
|
||||
private:
|
||||
// --- Enums ---
|
||||
enum class Status {
|
||||
enum class Status : std::uint8_t {
|
||||
DISABLED, // Deshabilitado
|
||||
MOVING, // En movimiento
|
||||
SHAKING, // Temblando
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
#include <SDL3/SDL.h> // Para SDL_ReadIO, SDL_WriteIO, SDL_CloseIO, SDL_GetError, SDL_IOFromFile, SDL_LogError, SDL_LogCategory, SDL_LogInfo
|
||||
|
||||
#include <algorithm> // Para __sort_fn, sort
|
||||
#include <array> // Para array
|
||||
#include <functional> // Para identity
|
||||
#include <iomanip> // Para std::setw, std::setfill
|
||||
#include <iostream> // Para std::cout
|
||||
#include <iterator> // Para distance
|
||||
#include <ranges> // Para __find_if_fn, find_if
|
||||
#include <utility> // Para move
|
||||
#include <algorithm> // Para sort, ranges::find_if, ranges::sort
|
||||
#include <array> // Para array
|
||||
#include <iomanip> // Para std::setw, std::setfill
|
||||
#include <iostream> // Para std::cout
|
||||
#include <iterator> // Para distance
|
||||
#include <numeric> // Para accumulate
|
||||
#include <utility> // Para move
|
||||
|
||||
#include "utils/utils.hpp" // Para getFileName
|
||||
|
||||
@@ -260,22 +259,11 @@ auto ManageHiScoreTable::verifyChecksum(SDL_IOStream* file, const std::string& f
|
||||
|
||||
// Calcula checksum de la tabla
|
||||
auto ManageHiScoreTable::calculateChecksum(const Table& table) -> unsigned int {
|
||||
unsigned int checksum = 0x12345678; // Magic seed
|
||||
|
||||
for (const auto& entry : table) {
|
||||
// Checksum del score
|
||||
return std::accumulate(table.begin(), table.end(), 0x12345678U, [](unsigned int checksum, const auto& entry) {
|
||||
checksum = ((checksum << 5) + checksum) + static_cast<unsigned int>(entry.score);
|
||||
|
||||
// Checksum del nombre
|
||||
for (char c : entry.name) {
|
||||
checksum = ((checksum << 5) + checksum) + static_cast<unsigned int>(c);
|
||||
}
|
||||
|
||||
// Checksum de one_credit_complete
|
||||
checksum = ((checksum << 5) + checksum) + (entry.one_credit_complete ? 1U : 0U);
|
||||
}
|
||||
|
||||
return checksum;
|
||||
checksum = std::accumulate(entry.name.begin(), entry.name.end(), checksum, [](unsigned int acc, char c) { return ((acc << 5) + acc) + static_cast<unsigned int>(c); });
|
||||
return ((checksum << 5) + checksum) + (entry.one_credit_complete ? 1U : 0U);
|
||||
});
|
||||
}
|
||||
|
||||
// Guarda la tabla en un fichero
|
||||
|
||||
@@ -79,7 +79,7 @@ Scoreboard::Scoreboard()
|
||||
fillBackgroundTexture();
|
||||
|
||||
// Inicializa el ciclo de colores para el nombre
|
||||
name_color_cycle_ = Colors::generateMirroredCycle(color_.INVERSE(), ColorCycleStyle::VIBRANT);
|
||||
name_color_cycle_ = Colors::generateMirroredCycle(color_.inverse(), ColorCycleStyle::VIBRANT);
|
||||
animated_color_ = name_color_cycle_.at(0);
|
||||
}
|
||||
|
||||
@@ -337,13 +337,13 @@ void Scoreboard::render() {
|
||||
void Scoreboard::setColor(Color color) {
|
||||
// Actualiza las variables de colores
|
||||
color_ = color;
|
||||
text_color1_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(100) : param.scoreboard.text_color1;
|
||||
text_color2_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(150) : param.scoreboard.text_color2;
|
||||
text_color1_ = param.scoreboard.text_autocolor ? color_.lighten(100) : param.scoreboard.text_color1;
|
||||
text_color2_ = param.scoreboard.text_autocolor ? color_.lighten(150) : param.scoreboard.text_color2;
|
||||
|
||||
// Aplica los colores
|
||||
power_meter_sprite_->getTexture()->setColor(text_color2_);
|
||||
fillBackgroundTexture();
|
||||
name_color_cycle_ = Colors::generateMirroredCycle(color_.INVERSE(), ColorCycleStyle::VIBRANT);
|
||||
name_color_cycle_ = Colors::generateMirroredCycle(color_.inverse(), ColorCycleStyle::VIBRANT);
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
@@ -383,9 +383,9 @@ void Scoreboard::fillPanelTextures() {
|
||||
}
|
||||
|
||||
// Interpolar entre color base y color aclarado
|
||||
Color target_color = color_.LIGHTEN(PANEL_PULSE_LIGHTEN_AMOUNT);
|
||||
Color target_color = color_.lighten(PANEL_PULSE_LIGHTEN_AMOUNT);
|
||||
// Color target_color = color_.INVERSE();
|
||||
background_color = color_.LERP(target_color, pulse_intensity);
|
||||
background_color = color_.lerp(target_color, pulse_intensity);
|
||||
background_color.a = 255; // Opaco durante el pulso
|
||||
}
|
||||
|
||||
@@ -716,7 +716,7 @@ void Scoreboard::createPanelTextures() {
|
||||
// Dibuja la linea que separa la zona de juego del marcador
|
||||
void Scoreboard::renderSeparator() {
|
||||
// Dibuja la linea que separa el marcador de la zona de juego
|
||||
auto color = param.scoreboard.separator_autocolor ? color_.DARKEN() : param.scoreboard.separator_color;
|
||||
auto color = param.scoreboard.separator_autocolor ? color_.darken() : param.scoreboard.separator_color;
|
||||
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 255);
|
||||
SDL_RenderLine(renderer_, 0, 0, rect_.w, 0);
|
||||
}
|
||||
@@ -724,7 +724,7 @@ void Scoreboard::renderSeparator() {
|
||||
// Pinta el carrusel de caracteres con efecto de color LERP y animación suave
|
||||
void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) {
|
||||
// Obtener referencia a EnterName
|
||||
EnterName* enter_name = enter_name_ref_.at(panel_index);
|
||||
const EnterName* enter_name = enter_name_ref_.at(panel_index);
|
||||
if (enter_name == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -755,7 +755,7 @@ void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) {
|
||||
}
|
||||
|
||||
const float FRACTIONAL_OFFSET = frac;
|
||||
const int PIXEL_OFFSET = static_cast<int>((FRACTIONAL_OFFSET * CHAR_STEP) + 0.5F);
|
||||
const int PIXEL_OFFSET = static_cast<int>(std::lround(FRACTIONAL_OFFSET * CHAR_STEP));
|
||||
|
||||
// Índice base en la lista de caracteres (posición central)
|
||||
const int BASE_INDEX = static_cast<int>(std::floor(carousel_pos));
|
||||
@@ -790,13 +790,13 @@ void Scoreboard::renderCarousel(size_t panel_index, int center_x, int y) {
|
||||
if (DISTANCE_FROM_CENTER < 0.5F) {
|
||||
// Letra central → transiciona hacia animated_color_
|
||||
float lerp_to_animated = DISTANCE_FROM_CENTER / 0.5F; // 0.0 a 1.0
|
||||
letter_color = animated_color_.LERP(text_color1_, lerp_to_animated);
|
||||
letter_color = animated_color_.lerp(text_color1_, lerp_to_animated);
|
||||
} else {
|
||||
// Letras alejadas → degradan hacia color_ base
|
||||
float base_lerp = (DISTANCE_FROM_CENTER - 0.5F) / (HALF_VISIBLE - 0.5F);
|
||||
base_lerp = std::min(base_lerp, 1.0F);
|
||||
const float LERP_FACTOR = base_lerp * 0.85F;
|
||||
letter_color = text_color1_.LERP(color_, LERP_FACTOR);
|
||||
letter_color = text_color1_.lerp(color_, LERP_FACTOR);
|
||||
}
|
||||
|
||||
// Calcular posición X de la letra
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <array> // Para array
|
||||
#include <cstddef> // Para size_t
|
||||
#include <cstdint> // Para std::uint8_t
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string, basic_string
|
||||
#include <vector> // Para vector
|
||||
@@ -21,14 +22,14 @@ class Texture;
|
||||
class Scoreboard {
|
||||
public:
|
||||
// --- Enums ---
|
||||
enum class Id : size_t {
|
||||
enum class Id : std::uint8_t {
|
||||
LEFT = 0,
|
||||
CENTER = 1,
|
||||
RIGHT = 2,
|
||||
SIZE = 3
|
||||
};
|
||||
|
||||
enum class Mode : int {
|
||||
enum class Mode : std::uint8_t {
|
||||
SCORE,
|
||||
STAGE_INFO,
|
||||
CONTINUE,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user