Compare commits
6 Commits
4e2393f4d5
...
06457654f4
| Author | SHA1 | Date | |
|---|---|---|---|
| 06457654f4 | |||
| e9fc2e8fa0 | |||
| 23863c02a6 | |||
| 6996b3a82a | |||
| 2b2eb31c67 | |||
| 8aad52f33f |
@@ -111,9 +111,17 @@ set(APP_SOURCES
|
||||
)
|
||||
|
||||
# Fuentes del sistema de renderizado
|
||||
set(RENDERING_SOURCES
|
||||
source/core/rendering/opengl/opengl_shader.cpp
|
||||
)
|
||||
# En macOS usamos SDL3 GPU (Metal), no OpenGL
|
||||
if(APPLE)
|
||||
set(RENDERING_SOURCES
|
||||
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
|
||||
)
|
||||
else()
|
||||
set(RENDERING_SOURCES
|
||||
source/core/rendering/opengl/opengl_shader.cpp
|
||||
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
# Fuentes de debug (solo en modo Debug)
|
||||
set(DEBUG_SOURCES
|
||||
@@ -161,8 +169,10 @@ elseif(UNIX AND NOT APPLE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
||||
endif()
|
||||
|
||||
# Configuración común para OpenGL
|
||||
if(NOT WIN32)
|
||||
# Configuración común para OpenGL (no requerido en macOS: usamos SDL3 GPU API / Metal)
|
||||
if(WIN32)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE opengl32)
|
||||
elseif(NOT APPLE)
|
||||
find_package(OpenGL REQUIRED)
|
||||
if(OPENGL_FOUND)
|
||||
message(STATUS "OpenGL encontrado: ${OPENGL_LIBRARIES}")
|
||||
|
||||
43
Makefile
43
Makefile
@@ -13,7 +13,7 @@ TARGET_NAME := jaildoctors_dilemma
|
||||
TARGET_FILE := $(DIR_BIN)$(TARGET_NAME)
|
||||
APP_NAME := JailDoctor's Dilemma
|
||||
DIST_DIR := dist
|
||||
RELEASE_FOLDER := jdd_release
|
||||
RELEASE_FOLDER := dist/_tmp
|
||||
RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME)
|
||||
RESOURCE_FILE := release/windows/jdd.res
|
||||
|
||||
@@ -76,6 +76,8 @@ APP_SOURCES := \
|
||||
source/core/rendering/text.cpp \
|
||||
source/core/rendering/texture.cpp \
|
||||
source/core/rendering/gif.cpp \
|
||||
source/core/rendering/pixel_reveal.cpp \
|
||||
source/core/rendering/surface_dissolve_sprite.cpp \
|
||||
source/core/rendering/opengl/opengl_shader.cpp \
|
||||
source/core/resources/resource_list.cpp \
|
||||
source/core/resources/resource_cache.cpp \
|
||||
@@ -184,13 +186,6 @@ windows:
|
||||
g++ $(ALL_SOURCES) $(RESOURCE_FILE) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_TARGET_FILE).exe"
|
||||
strip -s -R .comment -R .gnu.version "$(WIN_TARGET_FILE).exe" --strip-unneeded
|
||||
|
||||
windows_debug:
|
||||
@echo off
|
||||
@echo Generando version.h...
|
||||
@powershell -Command "$$GIT_HASH = (git rev-parse --short=7 HEAD 2>$$null); if (-not $$GIT_HASH) { $$GIT_HASH = 'unknown' }; (Get-Content source/version.h.in) -replace '@GIT_HASH@', $$GIT_HASH | Set-Content source/version.h"
|
||||
@echo Compilando version debug para Windows: "$(WIN_TARGET_FILE)_debug.exe"
|
||||
g++ $(ALL_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(WIN_TARGET_FILE)_debug.exe"
|
||||
|
||||
windows_release:
|
||||
@$(MAKE) pack_tool
|
||||
@$(MAKE) resources.pack
|
||||
@@ -201,7 +196,8 @@ windows_release:
|
||||
@echo "Generando version.h..."
|
||||
@powershell -Command "$$GIT_HASH = (git rev-parse --short=7 HEAD 2>$$null); if (-not $$GIT_HASH) { $$GIT_HASH = 'unknown' }; (Get-Content source/version.h.in) -replace '@GIT_HASH@', $$GIT_HASH | Set-Content source/version.h"
|
||||
|
||||
# Crea carpeta temporal 'RELEASE_FOLDER'
|
||||
# Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER'
|
||||
powershell if (-not (Test-Path "$(DIST_DIR)")) {New-Item "$(DIST_DIR)" -ItemType Directory}
|
||||
powershell if (Test-Path "$(RELEASE_FOLDER)") {Remove-Item "$(RELEASE_FOLDER)" -Recurse -Force}
|
||||
powershell if (-not (Test-Path "$(RELEASE_FOLDER)")) {New-Item "$(RELEASE_FOLDER)" -ItemType Directory}
|
||||
|
||||
@@ -220,7 +216,6 @@ windows_release:
|
||||
strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
|
||||
|
||||
# Crea el fichero .zip
|
||||
powershell if (-not (Test-Path "$(DIST_DIR)")) {New-Item "$(DIST_DIR)" -ItemType Directory}
|
||||
powershell if (Test-Path "$(WINDOWS_RELEASE)") {Remove-Item "$(WINDOWS_RELEASE)"}
|
||||
powershell Compress-Archive -Path "$(RELEASE_FOLDER)"/* -DestinationPath "$(WINDOWS_RELEASE)"
|
||||
@echo Release creado: $(WINDOWS_RELEASE)
|
||||
@@ -237,12 +232,6 @@ macos:
|
||||
@echo "Compilando para macOS: $(TARGET_NAME)"
|
||||
clang++ $(ALL_SOURCES) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)"
|
||||
|
||||
macos_debug:
|
||||
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \
|
||||
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
|
||||
@echo "Compilando version debug para macOS: $(TARGET_NAME)_debug"
|
||||
clang++ $(ALL_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
|
||||
|
||||
macos_release:
|
||||
@$(MAKE) pack_tool
|
||||
@$(MAKE) resources.pack
|
||||
@@ -258,21 +247,18 @@ macos_release:
|
||||
|
||||
# Elimina datos de compilaciones anteriores
|
||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
||||
$(RMDIR) Frameworks
|
||||
$(RMFILE) tmp.dmg
|
||||
|
||||
$(RMFILE) "$(DIST_DIR)"/rw.*
|
||||
|
||||
# Crea la carpeta temporal para hacer el trabajo y las carpetas obligatorias para crear una app de macOS
|
||||
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
|
||||
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS"
|
||||
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||
$(MKDIR) Frameworks
|
||||
|
||||
# Copia carpetas y ficheros
|
||||
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||
cp gamecontrollerdb.txt "$(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 -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework Frameworks
|
||||
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
|
||||
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
|
||||
cp LICENSE "$(RELEASE_FOLDER)"
|
||||
@@ -285,7 +271,7 @@ macos_release:
|
||||
sed -i '' '/<key>CFBundleVersion<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"
|
||||
|
||||
# Compila la versión para procesadores Intel
|
||||
clang++ $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F ./Frameworks -framework OpenGL -Wno-deprecated -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos10.15
|
||||
clang++ $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -framework OpenGL -Wno-deprecated -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos10.15
|
||||
|
||||
# Firma la aplicación
|
||||
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
|
||||
@@ -309,7 +295,7 @@ macos_release:
|
||||
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
|
||||
|
||||
# Compila la versión para procesadores Apple Silicon
|
||||
clang++ $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F ./Frameworks -framework OpenGL -Wno-deprecated -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11
|
||||
clang++ $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -framework OpenGL -Wno-deprecated -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11
|
||||
|
||||
# Firma la aplicación
|
||||
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
|
||||
@@ -332,8 +318,8 @@ macos_release:
|
||||
@echo "Release Apple Silicon creado: $(MACOS_APPLE_SILICON_RELEASE)"
|
||||
|
||||
# Elimina las carpetas temporales
|
||||
$(RMDIR) Frameworks
|
||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
||||
$(RMFILE) "$(DIST_DIR)"/rw.*
|
||||
|
||||
# ==============================================================================
|
||||
# COMPILACIÓN PARA LINUX
|
||||
@@ -345,12 +331,6 @@ linux:
|
||||
g++ $(ALL_SOURCES) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)"
|
||||
strip -s -R .comment -R .gnu.version "$(TARGET_FILE)" --strip-unneeded
|
||||
|
||||
linux_debug:
|
||||
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \
|
||||
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
|
||||
@echo "Compilando version debug para Linux: $(TARGET_NAME)_debug"
|
||||
g++ $(ALL_SOURCES) $(INCLUDES) -DDEBUG -DVERBOSE $(CXXFLAGS_DEBUG) $(LDFLAGS) -o "$(TARGET_FILE)_debug"
|
||||
|
||||
linux_release:
|
||||
@$(MAKE) pack_tool
|
||||
@$(MAKE) resources.pack
|
||||
@@ -398,13 +378,10 @@ help:
|
||||
@echo "Makefile para JailDoctor's Dilemma"
|
||||
@echo "Comandos disponibles:"
|
||||
@echo " windows - Compilar para Windows"
|
||||
@echo " windows_debug - Compilar debug para Windows"
|
||||
@echo " windows_release - Crear release completo para Windows"
|
||||
@echo " linux - Compilar para Linux"
|
||||
@echo " linux_debug - Compilar debug para Linux"
|
||||
@echo " linux_release - Crear release completo para Linux"
|
||||
@echo " macos - Compilar para macOS"
|
||||
@echo " macos_debug - Compilar debug para macOS"
|
||||
@echo " macos_release - Crear release completo para macOS"
|
||||
@echo " pack_tool - Compilar herramienta de empaquetado"
|
||||
@echo " resources.pack - Generar pack de recursos desde data/"
|
||||
@@ -413,4 +390,4 @@ help:
|
||||
|
||||
FORCE:
|
||||
|
||||
.PHONY: windows windows_debug windows_release macos macos_debug macos_release linux linux_debug linux_release pack_tool resources.pack show_version help
|
||||
.PHONY: windows windows_release macos macos_release linux linux_release pack_tool resources.pack show_version help
|
||||
|
||||
@@ -90,6 +90,10 @@ assets:
|
||||
path: ${SYSTEM_FOLDER}/cheevos.bin
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/postfx.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
|
||||
# ROOMS
|
||||
rooms:
|
||||
|
||||
@@ -31,6 +31,10 @@ out vec4 FragColor;
|
||||
// Uniforms
|
||||
uniform sampler2D Texture;
|
||||
uniform vec2 TextureSize;
|
||||
uniform float uVignette; // 0 = sin viñeta, 1 = máxima
|
||||
uniform float uScanlines; // 0 = desactivadas, 1 = plenas
|
||||
uniform float uChroma; // 0 = sin aberración, 1 = máxima
|
||||
uniform float uOutputHeight; // altura del viewport en pixels de pantalla
|
||||
|
||||
#if defined(CURVATURE)
|
||||
vec2 Distort(vec2 coord)
|
||||
@@ -100,8 +104,20 @@ void main()
|
||||
#else
|
||||
float tempY = floor(texcoordInPixels.y) + 0.5;
|
||||
float yCoord = tempY / TextureSize.y;
|
||||
|
||||
// Scanline en espacio de pantalla (subpíxel)
|
||||
float scaleY = uOutputHeight / TextureSize.y;
|
||||
float screenY = vTexCoord.y * uOutputHeight;
|
||||
float posInRow = mod(screenY, scaleY);
|
||||
float scanLineDY = posInRow / scaleY - 0.5;
|
||||
float localFilterWidth = 1.0 / scaleY;
|
||||
float scanLineWeight = CalcScanLineWeight(scanLineDY);
|
||||
scanLineWeight += CalcScanLineWeight(scanLineDY - localFilterWidth);
|
||||
scanLineWeight += CalcScanLineWeight(scanLineDY + localFilterWidth);
|
||||
scanLineWeight *= 0.3333333;
|
||||
|
||||
// Phosphor blur en espacio textura (sin cambios)
|
||||
float dy = texcoordInPixels.y - tempY;
|
||||
float scanLineWeight = CalcScanLine(dy);
|
||||
float signY = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
@@ -111,7 +127,11 @@ void main()
|
||||
vec2 tc = vec2(texcoord.x, yCoord + dy);
|
||||
#endif
|
||||
|
||||
vec3 colour = texture(Texture, tc).rgb;
|
||||
float ca = uChroma * 0.005;
|
||||
vec3 colour;
|
||||
colour.r = texture(Texture, tc + vec2(ca, 0.0)).r;
|
||||
colour.g = texture(Texture, tc).g;
|
||||
colour.b = texture(Texture, tc - vec2(ca, 0.0)).b;
|
||||
|
||||
#if defined(SCANLINES)
|
||||
#if defined(GAMMA)
|
||||
@@ -122,7 +142,7 @@ void main()
|
||||
#endif
|
||||
#endif
|
||||
scanLineWeight *= BLOOM_FACTOR;
|
||||
colour *= scanLineWeight;
|
||||
colour *= mix(1.0, scanLineWeight, uScanlines);
|
||||
|
||||
#if defined(GAMMA)
|
||||
#if defined(FAKE_GAMMA)
|
||||
@@ -133,6 +153,12 @@ void main()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (uVignette > 0.0) {
|
||||
vec2 uv = texcoord - vec2(0.5);
|
||||
float vig = 1.0 - dot(uv, uv) * uVignette * 4.0;
|
||||
colour *= clamp(vig, 0.0, 1.0);
|
||||
}
|
||||
|
||||
#if MASK_TYPE == 0
|
||||
FragColor = vec4(colour, 1.0);
|
||||
#elif MASK_TYPE == 1
|
||||
|
||||
@@ -34,6 +34,10 @@ out vec4 FragColor;
|
||||
// Uniforms
|
||||
uniform sampler2D Texture;
|
||||
uniform vec2 TextureSize;
|
||||
uniform float uVignette; // 0 = sin viñeta, 1 = máxima
|
||||
uniform float uScanlines; // 0 = desactivadas, 1 = plenas
|
||||
uniform float uChroma; // 0 = sin aberración, 1 = máxima
|
||||
uniform float uOutputHeight; // altura del viewport en pixels de pantalla
|
||||
|
||||
#if defined(CURVATURE)
|
||||
vec2 Distort(vec2 coord)
|
||||
@@ -103,8 +107,20 @@ void main()
|
||||
#else
|
||||
float tempY = floor(texcoordInPixels.y) + 0.5;
|
||||
float yCoord = tempY / TextureSize.y;
|
||||
|
||||
// Scanline en espacio de pantalla (subpíxel)
|
||||
float scaleY = uOutputHeight / TextureSize.y;
|
||||
float screenY = vTexCoord.y * uOutputHeight;
|
||||
float posInRow = mod(screenY, scaleY);
|
||||
float scanLineDY = posInRow / scaleY - 0.5;
|
||||
float localFilterWidth = 1.0 / scaleY;
|
||||
float scanLineWeight = CalcScanLineWeight(scanLineDY);
|
||||
scanLineWeight += CalcScanLineWeight(scanLineDY - localFilterWidth);
|
||||
scanLineWeight += CalcScanLineWeight(scanLineDY + localFilterWidth);
|
||||
scanLineWeight *= 0.3333333;
|
||||
|
||||
// Phosphor blur en espacio textura (sin cambios)
|
||||
float dy = texcoordInPixels.y - tempY;
|
||||
float scanLineWeight = CalcScanLine(dy);
|
||||
float signY = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
@@ -114,7 +130,11 @@ void main()
|
||||
vec2 tc = vec2(texcoord.x, yCoord + dy);
|
||||
#endif
|
||||
|
||||
vec3 colour = texture(Texture, tc).rgb;
|
||||
float ca = uChroma * 0.005;
|
||||
vec3 colour;
|
||||
colour.r = texture(Texture, tc + vec2(ca, 0.0)).r;
|
||||
colour.g = texture(Texture, tc).g;
|
||||
colour.b = texture(Texture, tc - vec2(ca, 0.0)).b;
|
||||
|
||||
#if defined(SCANLINES)
|
||||
#if defined(GAMMA)
|
||||
@@ -125,7 +145,7 @@ void main()
|
||||
#endif
|
||||
#endif
|
||||
scanLineWeight *= BLOOM_FACTOR;
|
||||
colour *= scanLineWeight;
|
||||
colour *= mix(1.0, scanLineWeight, uScanlines);
|
||||
|
||||
#if defined(GAMMA)
|
||||
#if defined(FAKE_GAMMA)
|
||||
@@ -136,6 +156,12 @@ void main()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (uVignette > 0.0) {
|
||||
vec2 uv = texcoord - vec2(0.5);
|
||||
float vig = 1.0 - dot(uv, uv) * uVignette * 4.0;
|
||||
colour *= clamp(vig, 0.0, 1.0);
|
||||
}
|
||||
|
||||
#if MASK_TYPE == 0
|
||||
FragColor = vec4(colour, 1.0);
|
||||
#elif MASK_TYPE == 1
|
||||
|
||||
@@ -91,9 +91,17 @@ void handleIncWindowZoom() {
|
||||
}
|
||||
}
|
||||
|
||||
void handleToggleShaders() {
|
||||
Screen::get()->toggleShaders();
|
||||
Notifier::get()->show({"SHADERS " + std::string(Options::video.shaders ? "ENABLED" : "DISABLED")});
|
||||
void handleTogglePostFX() {
|
||||
Screen::get()->togglePostFX();
|
||||
Notifier::get()->show({"POSTFX " + std::string(Options::video.postfx ? "ENABLED" : "DISABLED")});
|
||||
}
|
||||
|
||||
void handleNextPostFXPreset() {
|
||||
if (!Options::postfx_presets.empty()) {
|
||||
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
|
||||
Screen::get()->reloadPostFX();
|
||||
Notifier::get()->show({"POSTFX " + Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name});
|
||||
}
|
||||
}
|
||||
|
||||
void handleNextPalette() {
|
||||
@@ -152,8 +160,11 @@ auto getPressedAction() -> InputAction {
|
||||
return InputAction::WINDOW_INC_ZOOM;
|
||||
}
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_SHADERS, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::TOGGLE_SHADERS;
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_POSTFX, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
if (Options::video.postfx && (SDL_GetModState() & SDL_KMOD_SHIFT)) {
|
||||
return InputAction::NEXT_POSTFX_PRESET;
|
||||
}
|
||||
return InputAction::TOGGLE_POSTFX;
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::NEXT_PALETTE;
|
||||
@@ -221,8 +232,12 @@ void handle() {
|
||||
handleIncWindowZoom();
|
||||
break;
|
||||
|
||||
case InputAction::TOGGLE_SHADERS:
|
||||
handleToggleShaders();
|
||||
case InputAction::TOGGLE_POSTFX:
|
||||
handleTogglePostFX();
|
||||
break;
|
||||
|
||||
case InputAction::NEXT_POSTFX_PRESET:
|
||||
handleNextPostFXPreset();
|
||||
break;
|
||||
|
||||
case InputAction::NEXT_PALETTE:
|
||||
|
||||
@@ -43,7 +43,7 @@ Input::Input(std::string game_controller_db_path)
|
||||
{Action::WINDOW_DEC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F1}},
|
||||
{Action::WINDOW_INC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F2}},
|
||||
{Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}},
|
||||
{Action::TOGGLE_SHADERS, KeyState{.scancode = SDL_SCANCODE_F4}},
|
||||
{Action::TOGGLE_POSTFX, KeyState{.scancode = SDL_SCANCODE_F4}},
|
||||
{Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}},
|
||||
{Action::PREVIOUS_PALETTE, KeyState{.scancode = SDL_SCANCODE_F6}},
|
||||
{Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}},
|
||||
|
||||
@@ -20,7 +20,8 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
|
||||
{InputAction::TOGGLE_MUSIC, "TOGGLE_MUSIC"},
|
||||
{InputAction::NEXT_PALETTE, "NEXT_PALETTE"},
|
||||
{InputAction::PREVIOUS_PALETTE, "PREVIOUS_PALETTE"},
|
||||
{InputAction::TOGGLE_SHADERS, "TOGGLE_SHADERS"},
|
||||
{InputAction::TOGGLE_POSTFX, "TOGGLE_POSTFX"},
|
||||
{InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"},
|
||||
{InputAction::SHOW_DEBUG_INFO, "SHOW_DEBUG_INFO"},
|
||||
{InputAction::TOGGLE_DEBUG, "TOGGLE_DEBUG"},
|
||||
{InputAction::NONE, "NONE"}};
|
||||
@@ -42,7 +43,8 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
|
||||
{"TOGGLE_MUSIC", InputAction::TOGGLE_MUSIC},
|
||||
{"NEXT_PALETTE", InputAction::NEXT_PALETTE},
|
||||
{"PREVIOUS_PALETTE", InputAction::PREVIOUS_PALETTE},
|
||||
{"TOGGLE_SHADERS", InputAction::TOGGLE_SHADERS},
|
||||
{"TOGGLE_POSTFX", InputAction::TOGGLE_POSTFX},
|
||||
{"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET},
|
||||
{"SHOW_DEBUG_INFO", InputAction::SHOW_DEBUG_INFO},
|
||||
{"TOGGLE_DEBUG", InputAction::TOGGLE_DEBUG},
|
||||
{"NONE", InputAction::NONE}};
|
||||
|
||||
@@ -24,7 +24,8 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
|
||||
TOGGLE_FULLSCREEN,
|
||||
TOGGLE_VSYNC,
|
||||
TOGGLE_INTEGER_SCALE,
|
||||
TOGGLE_SHADERS,
|
||||
TOGGLE_POSTFX,
|
||||
NEXT_POSTFX_PRESET,
|
||||
TOGGLE_BORDER,
|
||||
TOGGLE_MUSIC,
|
||||
NEXT_PALETTE,
|
||||
|
||||
@@ -30,6 +30,7 @@ auto OpenGLShader::initGLExtensions() -> bool {
|
||||
glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram");
|
||||
glDeleteProgram = (PFNGLDELETEPROGRAMPROC)SDL_GL_GetProcAddress("glDeleteProgram");
|
||||
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)SDL_GL_GetProcAddress("glGetUniformLocation");
|
||||
glUniform1f = (PFNGLUNIFORM1FPROC)SDL_GL_GetProcAddress("glUniform1f");
|
||||
glUniform2f = (PFNGLUNIFORM2FPROC)SDL_GL_GetProcAddress("glUniform2f");
|
||||
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glGenVertexArrays");
|
||||
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)SDL_GL_GetProcAddress("glBindVertexArray");
|
||||
@@ -44,7 +45,8 @@ auto OpenGLShader::initGLExtensions() -> bool {
|
||||
return (glCreateShader != nullptr) && (glShaderSource != nullptr) && (glCompileShader != nullptr) && (glGetShaderiv != nullptr) &&
|
||||
(glGetShaderInfoLog != nullptr) && (glDeleteShader != nullptr) && (glAttachShader != nullptr) && (glCreateProgram != nullptr) &&
|
||||
(glLinkProgram != nullptr) && (glValidateProgram != nullptr) && (glGetProgramiv != nullptr) && (glGetProgramInfoLog != nullptr) &&
|
||||
(glUseProgram != nullptr) && (glDeleteProgram != nullptr) && (glGetUniformLocation != nullptr) && (glUniform2f != nullptr) &&
|
||||
(glUseProgram != nullptr) && (glDeleteProgram != nullptr) && (glGetUniformLocation != nullptr) &&
|
||||
(glUniform1f != nullptr) && (glUniform2f != nullptr) &&
|
||||
(glGenVertexArrays != nullptr) && (glBindVertexArray != nullptr) && (glDeleteVertexArrays != nullptr) &&
|
||||
(glGenBuffers != nullptr) && (glBindBuffer != nullptr) && (glBufferData != nullptr) && (glDeleteBuffers != nullptr) &&
|
||||
(glVertexAttribPointer != nullptr) && (glEnableVertexAttribArray != nullptr);
|
||||
@@ -320,7 +322,7 @@ auto OpenGLShader::init(SDL_Window* window,
|
||||
// Crear geometría del quad
|
||||
createQuadGeometry();
|
||||
|
||||
// Obtener ubicación del uniform TextureSize
|
||||
// Obtener ubicaciones de uniforms y configurar valores iniciales
|
||||
glUseProgram(program_id_);
|
||||
texture_size_location_ = glGetUniformLocation(program_id_, "TextureSize");
|
||||
if (texture_size_location_ != -1) {
|
||||
@@ -334,6 +336,15 @@ auto OpenGLShader::init(SDL_Window* window,
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Uniform 'TextureSize' not found in shader");
|
||||
}
|
||||
|
||||
// Uniforms PostFX
|
||||
vignette_location_ = glGetUniformLocation(program_id_, "uVignette");
|
||||
scanlines_location_ = glGetUniformLocation(program_id_, "uScanlines");
|
||||
chroma_location_ = glGetUniformLocation(program_id_, "uChroma");
|
||||
output_height_location_ = glGetUniformLocation(program_id_, "uOutputHeight");
|
||||
if (vignette_location_ != -1) { glUniform1f(vignette_location_, postfx_vignette_); }
|
||||
if (scanlines_location_ != -1) { glUniform1f(scanlines_location_, postfx_scanlines_); }
|
||||
if (chroma_location_ != -1) { glUniform1f(chroma_location_, postfx_chroma_); }
|
||||
glUseProgram(0);
|
||||
|
||||
is_initialized_ = true;
|
||||
@@ -388,6 +399,11 @@ void OpenGLShader::render() {
|
||||
glUseProgram(program_id_);
|
||||
checkGLError("glUseProgram");
|
||||
|
||||
// Pasar uniforms PostFX
|
||||
if (vignette_location_ != -1) { glUniform1f(vignette_location_, postfx_vignette_); }
|
||||
if (scanlines_location_ != -1) { glUniform1f(scanlines_location_, postfx_scanlines_); }
|
||||
if (chroma_location_ != -1) { glUniform1f(chroma_location_, postfx_chroma_); }
|
||||
|
||||
// Configurar viewport (obtener tamaño lógico de SDL)
|
||||
int logical_w;
|
||||
int logical_h;
|
||||
@@ -431,6 +447,10 @@ void OpenGLShader::render() {
|
||||
glViewport(viewport_x, viewport_y, viewport_w, viewport_h);
|
||||
checkGLError("glViewport");
|
||||
|
||||
if (output_height_location_ != -1) {
|
||||
glUniform1f(output_height_location_, static_cast<float>(viewport_h));
|
||||
}
|
||||
|
||||
// Dibujar quad usando VAO
|
||||
glBindVertexArray(vao_);
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
|
||||
@@ -449,6 +469,12 @@ void OpenGLShader::render() {
|
||||
glViewport(old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3]);
|
||||
}
|
||||
|
||||
void OpenGLShader::setPostFXParams(float vignette, float scanlines, float chroma) {
|
||||
postfx_vignette_ = vignette;
|
||||
postfx_scanlines_ = scanlines;
|
||||
postfx_chroma_ = chroma;
|
||||
}
|
||||
|
||||
void OpenGLShader::setTextureSize(float width, float height) {
|
||||
if (!is_initialized_ || program_id_ == 0) {
|
||||
return;
|
||||
|
||||
@@ -30,6 +30,7 @@ class OpenGLShader : public ShaderBackend {
|
||||
|
||||
void render() override;
|
||||
void setTextureSize(float width, float height) override;
|
||||
void setPostFXParams(float vignette, float scanlines, float chroma) override;
|
||||
void cleanup() final;
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||
|
||||
@@ -55,6 +56,15 @@ class OpenGLShader : public ShaderBackend {
|
||||
|
||||
// Ubicaciones de uniforms
|
||||
GLint texture_size_location_ = -1;
|
||||
GLint vignette_location_ = -1;
|
||||
GLint scanlines_location_ = -1;
|
||||
GLint chroma_location_ = -1;
|
||||
GLint output_height_location_ = -1;
|
||||
|
||||
// Valores cacheados de PostFX
|
||||
float postfx_vignette_ = 0.6F;
|
||||
float postfx_scanlines_ = 0.7F;
|
||||
float postfx_chroma_ = 0.15F;
|
||||
|
||||
// Tamaños
|
||||
int window_width_ = 0;
|
||||
@@ -83,6 +93,7 @@ class OpenGLShader : public ShaderBackend {
|
||||
PFNGLUSEPROGRAMPROC glUseProgram = nullptr;
|
||||
PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr;
|
||||
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
|
||||
PFNGLUNIFORM1FPROC glUniform1f = nullptr;
|
||||
PFNGLUNIFORM2FPROC glUniform2f = nullptr;
|
||||
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr;
|
||||
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr;
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
#include <string> // Para char_traits, string, operator+, operator==
|
||||
|
||||
#include "core/input/mouse.hpp" // Para updateCursorVisibility
|
||||
#ifndef __APPLE__
|
||||
#include "core/rendering/opengl/opengl_shader.hpp" // Para OpenGLShader
|
||||
#endif
|
||||
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
|
||||
#include "core/rendering/surface.hpp" // Para Surface, readPalFile
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource
|
||||
@@ -124,13 +127,16 @@ void Screen::start() { setRendererSurface(nullptr); }
|
||||
void Screen::render() {
|
||||
fps_.increment();
|
||||
|
||||
// Renderiza todos los overlays
|
||||
// Renderiza todos los overlays (escribe en game_surface_ CPU-side)
|
||||
renderOverlays();
|
||||
|
||||
// Copia la surface a la textura
|
||||
surfaceToTexture();
|
||||
// En el path SDL3GPU, los píxeles se suben directamente desde la Surface.
|
||||
// En el path SDL_Renderer, primero copiamos la surface a la SDL_Texture.
|
||||
if (!(Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated())) {
|
||||
surfaceToTexture();
|
||||
}
|
||||
|
||||
// Copia la textura al renderizador
|
||||
// Copia la textura al renderizador (o hace el present GPU)
|
||||
textureToRenderer();
|
||||
}
|
||||
|
||||
@@ -203,10 +209,24 @@ void Screen::renderNotifications() const {
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia el estado de los shaders
|
||||
void Screen::toggleShaders() {
|
||||
Options::video.shaders = !Options::video.shaders;
|
||||
initShaders();
|
||||
// Cambia el estado del PostFX
|
||||
void Screen::togglePostFX() {
|
||||
Options::video.postfx = !Options::video.postfx;
|
||||
if (!Options::video.postfx && shader_backend_) {
|
||||
// Al desactivar PostFX, limpiar el backend para liberar el swapchain de GPU
|
||||
shader_backend_->cleanup();
|
||||
} else {
|
||||
initShaders();
|
||||
}
|
||||
}
|
||||
|
||||
// Recarga el shader del preset actual sin toggle
|
||||
void Screen::reloadPostFX() {
|
||||
if (Options::video.postfx) {
|
||||
vertex_shader_source_.clear();
|
||||
fragment_shader_source_.clear();
|
||||
initShaders();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza la lógica de la clase (versión nueva con delta_time para escenas migradas)
|
||||
@@ -304,13 +324,42 @@ void Screen::surfaceToTexture() {
|
||||
}
|
||||
}
|
||||
|
||||
// Copia la textura al renderizador
|
||||
// Copia la textura al renderizador (o hace el present GPU)
|
||||
void Screen::textureToRenderer() {
|
||||
SDL_Texture* texture_to_render = Options::video.border.enabled ? border_texture_ : game_texture_;
|
||||
|
||||
if (Options::video.shaders && shader_backend_) {
|
||||
if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
// ---- SDL3 GPU path: convertir Surface → ARGB → upload → PostFX → present ----
|
||||
if (Options::video.border.enabled) {
|
||||
// El border_surface_ solo tiene el color de borde; hay que componer encima el game_surface_
|
||||
const int BORDER_W = static_cast<int>(border_surface_->getWidth());
|
||||
const int BORDER_H = static_cast<int>(border_surface_->getHeight());
|
||||
pixel_buffer_.resize(static_cast<size_t>(BORDER_W * BORDER_H));
|
||||
border_surface_->toARGBBuffer(pixel_buffer_.data());
|
||||
|
||||
// Compositar game_surface_ en la posición correcta dentro del buffer
|
||||
const int GAME_W = static_cast<int>(game_surface_->getWidth());
|
||||
const int GAME_H = static_cast<int>(game_surface_->getHeight());
|
||||
const int OFF_X = static_cast<int>(game_surface_dstrect_.x);
|
||||
const int OFF_Y = static_cast<int>(game_surface_dstrect_.y);
|
||||
std::vector<Uint32> game_pixels(static_cast<size_t>(GAME_W * GAME_H));
|
||||
game_surface_->toARGBBuffer(game_pixels.data());
|
||||
for (int y = 0; y < GAME_H; ++y) {
|
||||
for (int x = 0; x < GAME_W; ++x) {
|
||||
pixel_buffer_[static_cast<size_t>(((OFF_Y + y) * BORDER_W) + (OFF_X + x))] = game_pixels[static_cast<size_t>((y * GAME_W) + x)];
|
||||
}
|
||||
}
|
||||
shader_backend_->uploadPixels(pixel_buffer_.data(), BORDER_W, BORDER_H);
|
||||
} else {
|
||||
const int GAME_W = static_cast<int>(game_surface_->getWidth());
|
||||
const int GAME_H = static_cast<int>(game_surface_->getHeight());
|
||||
pixel_buffer_.resize(static_cast<size_t>(GAME_W * GAME_H));
|
||||
game_surface_->toARGBBuffer(pixel_buffer_.data());
|
||||
shader_backend_->uploadPixels(pixel_buffer_.data(), GAME_W, GAME_H);
|
||||
}
|
||||
shader_backend_->render();
|
||||
} else {
|
||||
// ---- SDL_Renderer path (fallback / no-shader) ----
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
|
||||
SDL_RenderClear(renderer_);
|
||||
@@ -402,13 +451,10 @@ void Screen::loadShaders() {
|
||||
if (vertex_shader_source_.empty()) {
|
||||
// Detectar si necesitamos OpenGL ES (Raspberry Pi)
|
||||
// Intentar cargar versión ES primero si existe
|
||||
std::string vertex_file = "crtpi_vertex_es.glsl";
|
||||
auto data = loadData(Resource::List::get()->get(vertex_file));
|
||||
auto data = loadData(Resource::List::get()->get("crtpi_vertex_es.glsl"));
|
||||
|
||||
if (data.empty()) {
|
||||
// Si no existe versión ES, usar versión Desktop
|
||||
vertex_file = "crtpi_vertex.glsl";
|
||||
data = loadData(Resource::List::get()->get(vertex_file));
|
||||
data = loadData(Resource::List::get()->get("crtpi_vertex.glsl"));
|
||||
std::cout << "Usando shaders OpenGL Desktop 3.3\n";
|
||||
} else {
|
||||
std::cout << "Usando shaders OpenGL ES 3.0 (Raspberry Pi)\n";
|
||||
@@ -420,13 +466,10 @@ void Screen::loadShaders() {
|
||||
}
|
||||
if (fragment_shader_source_.empty()) {
|
||||
// Intentar cargar versión ES primero si existe
|
||||
std::string fragment_file = "crtpi_fragment_es.glsl";
|
||||
auto data = loadData(Resource::List::get()->get(fragment_file));
|
||||
auto data = loadData(Resource::List::get()->get("crtpi_fragment_es.glsl"));
|
||||
|
||||
if (data.empty()) {
|
||||
// Si no existe versión ES, usar versión Desktop
|
||||
fragment_file = "crtpi_fragment.glsl";
|
||||
data = loadData(Resource::List::get()->get(fragment_file));
|
||||
data = loadData(Resource::List::get()->get("crtpi_fragment.glsl"));
|
||||
}
|
||||
|
||||
if (!data.empty()) {
|
||||
@@ -435,22 +478,38 @@ void Screen::loadShaders() {
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica los parámetros del preset actual al backend de shaders
|
||||
void Screen::applyCurrentPostFXPreset() {
|
||||
if (shader_backend_ && !Options::postfx_presets.empty()) {
|
||||
const auto& p = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)];
|
||||
shader_backend_->setPostFXParams(p.vignette, p.scanlines, p.chroma);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa los shaders
|
||||
void Screen::initShaders() {
|
||||
#ifndef __APPLE__
|
||||
if (Options::video.shaders) {
|
||||
loadShaders();
|
||||
if (!shader_backend_) {
|
||||
shader_backend_ = std::make_unique<Rendering::OpenGLShader>();
|
||||
}
|
||||
shader_backend_->init(window_, Options::video.border.enabled ? border_texture_ : game_texture_, vertex_shader_source_, fragment_shader_source_);
|
||||
// shader_backend_->init(window_, shaders_texture_, vertex_shader_source_, fragment_shader_source_);
|
||||
if (!Options::video.postfx) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Texture* tex = Options::video.border.enabled ? border_texture_ : game_texture_;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// macOS: usar SDL3 GPU API (Metal) via SDL3GPUShader
|
||||
if (!shader_backend_) {
|
||||
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||
}
|
||||
shader_backend_->init(window_, tex, "", "");
|
||||
#else
|
||||
// En macOS, OpenGL está deprecated y rinde mal
|
||||
// TODO: Implementar backend de Metal para shaders en macOS
|
||||
std::cout << "WARNING: Shaders no disponibles en macOS (OpenGL deprecated). Usa Metal backend.\n";
|
||||
// Win/Linux: usar OpenGL + GLSL
|
||||
loadShaders();
|
||||
if (!shader_backend_) {
|
||||
shader_backend_ = std::make_unique<Rendering::OpenGLShader>();
|
||||
}
|
||||
shader_backend_->init(window_, tex, vertex_shader_source_, fragment_shader_source_);
|
||||
#endif
|
||||
|
||||
applyCurrentPostFXPreset();
|
||||
}
|
||||
|
||||
// Obtiene información sobre la pantalla
|
||||
@@ -537,7 +596,9 @@ auto Screen::initSDLVideo() -> bool {
|
||||
const auto WINDOW_WIDTH = Options::video.border.enabled ? Options::game.width + (Options::video.border.width * 2) : Options::game.width;
|
||||
const auto WINDOW_HEIGHT = Options::video.border.enabled ? Options::game.height + (Options::video.border.height * 2) : Options::game.height;
|
||||
#ifdef __APPLE__
|
||||
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
|
||||
// SDL_WINDOW_METAL no es necesario: SDL3GPU autodetecta Metal via SDL_CreateGPUDevice.
|
||||
// SDL_Renderer también usará Metal si está disponible (via hint o autoselección).
|
||||
SDL_WindowFlags window_flags = 0;
|
||||
#elif defined(LINUX_BUILD)
|
||||
// En Linux, SDL_WINDOW_OPENGL puede entrar en conflicto con el backend del renderer;
|
||||
// el hint SDL_HINT_RENDER_DRIVER="opengl" es suficiente para seleccionar OpenGL.
|
||||
@@ -571,8 +632,8 @@ auto Screen::initSDLVideo() -> bool {
|
||||
return false;
|
||||
}
|
||||
// Sin OpenGL garantizado, deshabilitar shaders
|
||||
Options::video.shaders = false;
|
||||
std::cout << "WARNING: Shaders disabled (OpenGL not available)\n";
|
||||
Options::video.postfx = false;
|
||||
std::cout << "WARNING: PostFX disabled (OpenGL not available)\n";
|
||||
}
|
||||
|
||||
// Configurar renderer
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include <SDL3/SDL_pixels.h> // Para Uint32
|
||||
|
||||
#include "utils/utils.hpp" // Para Color
|
||||
class Surface;
|
||||
class Text;
|
||||
@@ -51,11 +53,12 @@ class Screen {
|
||||
static void setBorderEnabled(bool value); // Establece si se ha de ver el borde
|
||||
void toggleBorder(); // Cambia entre borde visible y no visible
|
||||
|
||||
// Paletas y shaders
|
||||
// Paletas y PostFX
|
||||
void nextPalette(); // Cambia a la siguiente paleta
|
||||
void previousPalette(); // Cambia a la paleta anterior
|
||||
void setPalete(); // Establece la paleta actual
|
||||
void toggleShaders(); // Cambia el estado de los shaders
|
||||
void togglePostFX(); // Cambia el estado del PostFX
|
||||
void reloadPostFX(); // Recarga el shader del preset actual sin toggle
|
||||
|
||||
// Surfaces y notificaciones
|
||||
void setRendererSurface(const std::shared_ptr<Surface>& surface = nullptr); // Establece el renderizador para las surfaces
|
||||
@@ -114,6 +117,7 @@ class Screen {
|
||||
auto findPalette(const std::string& name) -> size_t; // Localiza la paleta dentro del vector de paletas
|
||||
void initShaders(); // Inicializa los shaders
|
||||
void loadShaders(); // Carga el contenido del archivo GLSL
|
||||
void applyCurrentPostFXPreset(); // Aplica los parámetros del preset actual al backend
|
||||
void renderInfo(); // Muestra información por pantalla
|
||||
void getDisplayInfo(); // Obtiene información sobre la pantalla
|
||||
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
|
||||
@@ -155,6 +159,7 @@ class Screen {
|
||||
std::string info_resolution_; // Texto con la información de la pantalla
|
||||
std::string vertex_shader_source_; // Almacena el vertex shader
|
||||
std::string fragment_shader_source_; // Almacena el fragment shader
|
||||
std::vector<Uint32> pixel_buffer_; // Buffer intermedio para SDL3GPU path (surface → ARGB)
|
||||
|
||||
#ifdef _DEBUG
|
||||
bool show_debug_info_{true}; // Indica si ha de mostrar la información de debug
|
||||
|
||||
5
source/core/rendering/sdl3gpu/postfx_frag_spv.h
Normal file
5
source/core/rendering/sdl3gpu/postfx_frag_spv.h
Normal file
File diff suppressed because one or more lines are too long
5
source/core/rendering/sdl3gpu/postfx_vert_spv.h
Normal file
5
source/core/rendering/sdl3gpu/postfx_vert_spv.h
Normal file
File diff suppressed because one or more lines are too long
432
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
432
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <cstring> // memcpy, strlen
|
||||
|
||||
#ifndef __APPLE__
|
||||
#include "core/rendering/sdl3gpu/postfx_frag_spv.h"
|
||||
#include "core/rendering/sdl3gpu/postfx_vert_spv.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
// ============================================================================
|
||||
// MSL shaders (Metal Shading Language) — macOS
|
||||
// ============================================================================
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
static const char* POSTFX_VERT_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
|
||||
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
||||
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
|
||||
PostVOut out;
|
||||
out.pos = float4(positions[vid], 0.0, 1.0);
|
||||
out.uv = uvs[vid];
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* POSTFX_FRAG_MSL = R"(
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct PostVOut {
|
||||
float4 pos [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength;
|
||||
float chroma_strength;
|
||||
float scanline_strength;
|
||||
float screen_height;
|
||||
};
|
||||
|
||||
fragment float4 postfx_fs(PostVOut in [[stage_in]],
|
||||
texture2d<float> scene [[texture(0)]],
|
||||
sampler samp [[sampler(0)]],
|
||||
constant PostFXUniforms& u [[buffer(0)]]) {
|
||||
float ca = u.chroma_strength * 0.005;
|
||||
float4 color;
|
||||
color.r = scene.sample(samp, in.uv + float2( ca, 0.0)).r;
|
||||
color.g = scene.sample(samp, in.uv).g;
|
||||
color.b = scene.sample(samp, in.uv - float2( ca, 0.0)).b;
|
||||
color.a = scene.sample(samp, in.uv).a;
|
||||
|
||||
float texHeight = float(scene.get_height());
|
||||
float scaleY = u.screen_height / texHeight;
|
||||
float screenY = in.uv.y * u.screen_height;
|
||||
float posInRow = fmod(screenY, scaleY);
|
||||
float scanLineDY = posInRow / scaleY - 0.5;
|
||||
float scan = max(1.0 - scanLineDY * scanLineDY * 6.0, 0.12) * 3.5;
|
||||
color.rgb *= mix(1.0, scan, u.scanline_strength);
|
||||
|
||||
float2 d = in.uv - float2(0.5, 0.5);
|
||||
float vignette = 1.0 - dot(d, d) * u.vignette_strength;
|
||||
color.rgb *= clamp(vignette, 0.0, 1.0);
|
||||
|
||||
return color;
|
||||
}
|
||||
)";
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
#endif // __APPLE__
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Destructor
|
||||
// ---------------------------------------------------------------------------
|
||||
SDL3GPUShader::~SDL3GPUShader() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// init
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& /*vertex_source*/,
|
||||
const std::string& /*fragment_source*/) -> bool {
|
||||
// Si ya estaba inicializado, limpiar antes de reinicializar (p.ej. al cambiar borde)
|
||||
if (is_initialized_) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
window_ = window;
|
||||
|
||||
// Dimensions from the SDL_Texture placeholder
|
||||
float fw = 0.0F;
|
||||
float fh = 0.0F;
|
||||
SDL_GetTextureSize(texture, &fw, &fh);
|
||||
tex_width_ = static_cast<int>(fw);
|
||||
tex_height_ = static_cast<int>(fh);
|
||||
uniforms_.screen_height = fh;
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 1. Create GPU device
|
||||
// ----------------------------------------------------------------
|
||||
#ifdef __APPLE__
|
||||
const SDL_GPUShaderFormat PREFERRED = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB;
|
||||
#else
|
||||
const SDL_GPUShaderFormat PREFERRED = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||
#endif
|
||||
device_ = SDL_CreateGPUDevice(PREFERRED, false, nullptr);
|
||||
if (device_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: SDL_CreateGPUDevice failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
SDL_Log("SDL3GPUShader: driver = %s", SDL_GetGPUDeviceDriver(device_));
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 2. Claim window (GPU device owns the swapchain from now on)
|
||||
// ----------------------------------------------------------------
|
||||
if (!SDL_ClaimWindowForGPUDevice(device_, window_)) {
|
||||
SDL_Log("SDL3GPUShader: SDL_ClaimWindowForGPUDevice failed: %s", SDL_GetError());
|
||||
SDL_DestroyGPUDevice(device_);
|
||||
device_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
SDL_SetGPUSwapchainParameters(device_, window_,
|
||||
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
|
||||
SDL_GPU_PRESENTMODE_VSYNC);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 3. Create scene texture (upload target + sampler source)
|
||||
// Format: B8G8R8A8_UNORM matches SDL ARGB8888 byte layout on LE
|
||||
// ----------------------------------------------------------------
|
||||
SDL_GPUTextureCreateInfo tex_info = {};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
tex_info.width = static_cast<Uint32>(tex_width_);
|
||||
tex_info.height = static_cast<Uint32>(tex_height_);
|
||||
tex_info.layer_count_or_depth = 1;
|
||||
tex_info.num_levels = 1;
|
||||
scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
|
||||
if (scene_texture_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: failed to create scene texture: %s", SDL_GetError());
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 4. Create upload transfer buffer (CPU → GPU, size = w*h*4 bytes)
|
||||
// ----------------------------------------------------------------
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tb_info.size = static_cast<Uint32>(tex_width_ * tex_height_ * 4);
|
||||
upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info);
|
||||
if (upload_buffer_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: failed to create upload buffer: %s", SDL_GetError());
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 5. Create nearest-neighbour sampler (retro pixel art)
|
||||
// ----------------------------------------------------------------
|
||||
SDL_GPUSamplerCreateInfo samp_info = {};
|
||||
samp_info.min_filter = SDL_GPU_FILTER_NEAREST;
|
||||
samp_info.mag_filter = SDL_GPU_FILTER_NEAREST;
|
||||
samp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
samp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
samp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
samp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
sampler_ = SDL_CreateGPUSampler(device_, &samp_info);
|
||||
if (sampler_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: failed to create sampler: %s", SDL_GetError());
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// 6. Create PostFX graphics pipeline
|
||||
// ----------------------------------------------------------------
|
||||
if (!createPipeline()) {
|
||||
cleanup();
|
||||
return false;
|
||||
}
|
||||
|
||||
is_initialized_ = true;
|
||||
SDL_Log("SDL3GPUShader: initialized OK (%dx%d)", tex_width_, tex_height_);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// createPipeline
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::createPipeline() -> bool {
|
||||
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* vert = createShaderMSL(device_, POSTFX_VERT_MSL, "postfx_vs",
|
||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderMSL(device_, POSTFX_FRAG_MSL, "postfx_fs",
|
||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#endif
|
||||
|
||||
if ((vert == nullptr) || (frag == nullptr)) {
|
||||
SDL_Log("SDL3GPUShader: failed to compile PostFX shaders");
|
||||
if (vert != nullptr) { SDL_ReleaseGPUShader(device_, vert); }
|
||||
if (frag != nullptr) { SDL_ReleaseGPUShader(device_, frag); }
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GPUColorTargetBlendState no_blend = {};
|
||||
no_blend.enable_blend = false;
|
||||
no_blend.enable_color_write_mask = false;
|
||||
|
||||
SDL_GPUColorTargetDescription color_target = {};
|
||||
color_target.format = SWAPCHAIN_FMT;
|
||||
color_target.blend_state = no_blend;
|
||||
|
||||
SDL_GPUVertexInputState no_input = {};
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo pipe_info = {};
|
||||
pipe_info.vertex_shader = vert;
|
||||
pipe_info.fragment_shader = frag;
|
||||
pipe_info.vertex_input_state = no_input;
|
||||
pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
pipe_info.target_info.num_color_targets = 1;
|
||||
pipe_info.target_info.color_target_descriptions = &color_target;
|
||||
|
||||
pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &pipe_info);
|
||||
|
||||
SDL_ReleaseGPUShader(device_, vert);
|
||||
SDL_ReleaseGPUShader(device_, frag);
|
||||
|
||||
if (pipeline_ == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: pipeline creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// uploadPixels — copies ARGB8888 CPU pixels into the GPU transfer buffer
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) {
|
||||
if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; }
|
||||
|
||||
void* mapped = SDL_MapGPUTransferBuffer(device_, upload_buffer_, false);
|
||||
if (mapped == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: SDL_MapGPUTransferBuffer failed: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
std::memcpy(mapped, pixels, static_cast<size_t>(width * height * 4));
|
||||
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// render — upload scene texture + PostFX pass → swapchain
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::render() {
|
||||
if (!is_initialized_) { return; }
|
||||
|
||||
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
|
||||
if (cmd == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Copy pass: transfer buffer → scene texture ----
|
||||
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
|
||||
if (copy != nullptr) {
|
||||
SDL_GPUTextureTransferInfo src = {};
|
||||
src.transfer_buffer = upload_buffer_;
|
||||
src.offset = 0;
|
||||
src.pixels_per_row = static_cast<Uint32>(tex_width_);
|
||||
src.rows_per_layer = static_cast<Uint32>(tex_height_);
|
||||
|
||||
SDL_GPUTextureRegion dst = {};
|
||||
dst.texture = scene_texture_;
|
||||
dst.w = static_cast<Uint32>(tex_width_);
|
||||
dst.h = static_cast<Uint32>(tex_height_);
|
||||
dst.d = 1;
|
||||
|
||||
SDL_UploadToGPUTexture(copy, &src, &dst, false);
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
}
|
||||
|
||||
// ---- Acquire swapchain texture ----
|
||||
SDL_GPUTexture* swapchain = nullptr;
|
||||
Uint32 sw = 0;
|
||||
Uint32 sh = 0;
|
||||
if (!SDL_AcquireGPUSwapchainTexture(cmd, window_, &swapchain, &sw, &sh)) {
|
||||
SDL_Log("SDL3GPUShader: SDL_AcquireGPUSwapchainTexture failed: %s", SDL_GetError());
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
return;
|
||||
}
|
||||
if (swapchain == nullptr) {
|
||||
// Window minimized — skip frame
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
uniforms_.screen_height = static_cast<float>(sh);
|
||||
|
||||
// ---- Render pass: PostFX → swapchain ----
|
||||
SDL_GPUColorTargetInfo color_target = {};
|
||||
color_target.texture = swapchain;
|
||||
color_target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
color_target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
color_target.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &color_target, 1, nullptr);
|
||||
if (pass != nullptr) {
|
||||
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
|
||||
|
||||
SDL_GPUTextureSamplerBinding binding = {};
|
||||
binding.texture = scene_texture_;
|
||||
binding.sampler = sampler_;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
|
||||
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
|
||||
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// cleanup
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::cleanup() {
|
||||
is_initialized_ = false;
|
||||
|
||||
if (device_ != nullptr) {
|
||||
SDL_WaitForGPUIdle(device_);
|
||||
|
||||
if (pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_);
|
||||
pipeline_ = nullptr;
|
||||
}
|
||||
if (scene_texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device_, scene_texture_);
|
||||
scene_texture_ = nullptr;
|
||||
}
|
||||
if (upload_buffer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
|
||||
upload_buffer_ = nullptr;
|
||||
}
|
||||
if (sampler_ != nullptr) {
|
||||
SDL_ReleaseGPUSampler(device_, sampler_);
|
||||
sampler_ = nullptr;
|
||||
}
|
||||
|
||||
SDL_ReleaseWindowFromGPUDevice(device_, window_);
|
||||
SDL_DestroyGPUDevice(device_);
|
||||
device_ = nullptr;
|
||||
}
|
||||
window_ = nullptr;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shader creation helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::createShaderMSL(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader* {
|
||||
SDL_GPUShaderCreateInfo info = {};
|
||||
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
||||
info.code_size = std::strlen(msl_source) + 1;
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_MSL;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
||||
if (shader == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: MSL shader '%s' failed: %s", entrypoint, SDL_GetError());
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
auto SDL3GPUShader::createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader* {
|
||||
SDL_GPUShaderCreateInfo info = {};
|
||||
info.code = spv_code;
|
||||
info.code_size = spv_size;
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
||||
if (shader == nullptr) {
|
||||
SDL_Log("SDL3GPUShader: SPIRV shader '%s' failed: %s", entrypoint, SDL_GetError());
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
void SDL3GPUShader::setPostFXParams(float vignette, float scanlines, float chroma) {
|
||||
uniforms_.vignette_strength = vignette;
|
||||
uniforms_.scanline_strength = scanlines;
|
||||
uniforms_.chroma_strength = chroma;
|
||||
}
|
||||
|
||||
} // namespace Rendering
|
||||
79
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
79
source/core/rendering/sdl3gpu/sdl3gpu_shader.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include "core/rendering/shader_backend.hpp"
|
||||
|
||||
// PostFX uniforms pushed to fragment stage each frame.
|
||||
// Must match the MSL struct and GLSL uniform block layout.
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength; // 0 = none, ~0.8 = subtle
|
||||
float chroma_strength; // 0 = off, ~0.2 = subtle chromatic aberration
|
||||
float scanline_strength; // 0 = off, 1 = full
|
||||
float screen_height; // logical height in pixels (for resolution-independent scanlines)
|
||||
};
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/**
|
||||
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
|
||||
*
|
||||
* Reemplaza el backend OpenGL para que los shaders PostFX funcionen en macOS.
|
||||
* Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
|
||||
* → PostFX render pass → swapchain → present
|
||||
*/
|
||||
class SDL3GPUShader : public ShaderBackend {
|
||||
public:
|
||||
SDL3GPUShader() = default;
|
||||
~SDL3GPUShader() override;
|
||||
|
||||
auto init(SDL_Window* window,
|
||||
SDL_Texture* texture,
|
||||
const std::string& vertex_source,
|
||||
const std::string& fragment_source) -> bool override;
|
||||
|
||||
void render() override;
|
||||
void setTextureSize(float width, float height) override {}
|
||||
void cleanup() override;
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||
|
||||
// Sube píxeles ARGB8888 desde CPU; llamado antes de render()
|
||||
void uploadPixels(const Uint32* pixels, int width, int height) override;
|
||||
|
||||
// Actualiza los parámetros de intensidad de los efectos PostFX
|
||||
void setPostFXParams(float vignette, float scanlines, float chroma) override;
|
||||
|
||||
private:
|
||||
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
static auto createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
auto createPipeline() -> bool;
|
||||
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||
SDL_GPUTexture* scene_texture_ = nullptr;
|
||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
|
||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F};
|
||||
|
||||
int tex_width_ = 0;
|
||||
int tex_height_ = 0;
|
||||
bool is_initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
@@ -46,6 +46,20 @@ class ShaderBackend {
|
||||
*/
|
||||
virtual void cleanup() = 0;
|
||||
|
||||
/**
|
||||
* @brief Sube píxeles ARGB8888 desde la CPU al backend de shaders
|
||||
* Usado por SDL3GPUShader para evitar pasar por SDL_Texture
|
||||
*/
|
||||
virtual void uploadPixels(const Uint32* /*pixels*/, int /*width*/, int /*height*/) {}
|
||||
|
||||
/**
|
||||
* @brief Establece los parámetros de intensidad de los efectos PostFX
|
||||
* @param vignette Intensidad de la viñeta (0.0 = ninguna, 1.0 = máxima)
|
||||
* @param scanlines Intensidad de las scanlines (0.0 = desactivadas, 1.0 = máximas)
|
||||
* @param chroma Intensidad de la aberración cromática (0.0 = ninguna, 1.0 = máxima)
|
||||
*/
|
||||
virtual void setPostFXParams(float /*vignette*/, float /*scanlines*/, float /*chroma*/) {}
|
||||
|
||||
/**
|
||||
* @brief Verifica si el backend está usando aceleración por hardware
|
||||
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
||||
|
||||
@@ -524,6 +524,19 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
|
||||
}
|
||||
}
|
||||
|
||||
// Vuelca los píxeles como ARGB8888 a un buffer externo (sin SDL_Texture ni SDL_Renderer)
|
||||
void Surface::toARGBBuffer(Uint32* buffer) const {
|
||||
if (!surface_data_ || (surface_data_->data == nullptr)) { return; }
|
||||
const int WIDTH = static_cast<int>(surface_data_->width);
|
||||
const int HEIGHT = static_cast<int>(surface_data_->height);
|
||||
const Uint8* src = surface_data_->data.get();
|
||||
for (int y = 0; y < HEIGHT; ++y) {
|
||||
for (int x = 0; x < WIDTH; ++x) {
|
||||
buffer[(y * WIDTH) + x] = palette_[src[(y * WIDTH) + x]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vuelca la superficie a una textura
|
||||
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) {
|
||||
if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) {
|
||||
|
||||
@@ -106,6 +106,9 @@ class Surface {
|
||||
auto fadePalette() -> bool;
|
||||
auto fadeSubPalette(Uint32 delay = 0) -> bool;
|
||||
|
||||
// Vuelca los píxeles como ARGB8888 a un buffer externo (sin SDL_Texture)
|
||||
void toARGBBuffer(Uint32* buffer) const;
|
||||
|
||||
// Pone un pixel en la SurfaceData
|
||||
void putPixel(int x, int y, Uint8 color);
|
||||
|
||||
|
||||
@@ -122,6 +122,10 @@ Director::Director(std::vector<std::string> const& args) {
|
||||
Options::setConfigFile(Resource::List::get()->get("config.yaml"));
|
||||
Options::loadFromFile();
|
||||
|
||||
// Configura la ruta y carga los presets de PostFX
|
||||
Options::setPostFXFile(Resource::List::get()->get("postfx.yaml"));
|
||||
Options::loadPostFXFromFile();
|
||||
|
||||
// En mode quiosc, forçar pantalla completa independentment de la configuració
|
||||
if (Options::kiosk.enabled) {
|
||||
Options::video.fullscreen = true;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Video {
|
||||
constexpr bool FULLSCREEN = false; // Modo de pantalla completa por defecto (false = ventana)
|
||||
constexpr Screen::Filter FILTER = Screen::Filter::NEAREST; // Filtro por defecto
|
||||
constexpr bool VERTICAL_SYNC = true; // Vsync activado por defecto
|
||||
constexpr bool SHADERS = false; // Shaders desactivados por defecto
|
||||
constexpr bool POSTFX = false; // PostFX desactivado por defecto
|
||||
constexpr bool INTEGER_SCALE = true; // Escalado entero activado por defecto
|
||||
constexpr bool KEEP_ASPECT = true; // Mantener aspecto activado por defecto
|
||||
constexpr const char* PALETTE_NAME = "zx-spectrum"; // Paleta por defecto
|
||||
|
||||
@@ -329,11 +329,11 @@ void loadBasicVideoFieldsFromYaml(const fkyaml::node& vid) {
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("shaders")) {
|
||||
if (vid.contains("postfx")) {
|
||||
try {
|
||||
video.shaders = vid["shaders"].get_value<bool>();
|
||||
video.postfx = vid["postfx"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.shaders = Defaults::Video::SHADERS;
|
||||
video.postfx = Defaults::Video::POSTFX;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,7 +606,7 @@ auto saveToFile() -> bool {
|
||||
file << "video:\n";
|
||||
file << " fullscreen: " << (video.fullscreen ? "true" : "false") << "\n";
|
||||
file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n";
|
||||
file << " shaders: " << (video.shaders ? "true" : "false") << "\n";
|
||||
file << " postfx: " << (video.postfx ? "true" : "false") << "\n";
|
||||
file << " vertical_sync: " << (video.vertical_sync ? "true" : "false") << "\n";
|
||||
file << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n";
|
||||
file << " keep_aspect: " << (video.keep_aspect ? "true" : "false") << "\n";
|
||||
@@ -649,4 +649,113 @@ auto saveToFile() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Establece la ruta del fichero de PostFX
|
||||
void setPostFXFile(const std::string& path) {
|
||||
postfx_file_path = path;
|
||||
}
|
||||
|
||||
// Carga los presets de PostFX desde el fichero
|
||||
auto loadPostFXFromFile() -> bool {
|
||||
postfx_presets.clear();
|
||||
current_postfx_preset = 0;
|
||||
|
||||
std::ifstream file(postfx_file_path);
|
||||
if (!file.good()) {
|
||||
if (console) {
|
||||
std::cout << "PostFX file not found, creating default: " << postfx_file_path << '\n';
|
||||
}
|
||||
return savePostFXToFile();
|
||||
}
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
|
||||
try {
|
||||
auto yaml = fkyaml::node::deserialize(content);
|
||||
|
||||
if (yaml.contains("presets")) {
|
||||
const auto& presets = yaml["presets"];
|
||||
for (size_t i = 0; i < presets.size(); ++i) {
|
||||
const auto& p = presets[i];
|
||||
PostFXPreset preset;
|
||||
if (p.contains("name")) {
|
||||
preset.name = p["name"].get_value<std::string>();
|
||||
}
|
||||
if (p.contains("vignette")) {
|
||||
try { preset.vignette = p["vignette"].get_value<float>(); } catch (...) {}
|
||||
}
|
||||
if (p.contains("scanlines")) {
|
||||
try { preset.scanlines = p["scanlines"].get_value<float>(); } catch (...) {}
|
||||
}
|
||||
if (p.contains("chroma")) {
|
||||
try { preset.chroma = p["chroma"].get_value<float>(); } catch (...) {}
|
||||
}
|
||||
postfx_presets.push_back(preset);
|
||||
}
|
||||
}
|
||||
|
||||
if (console) {
|
||||
std::cout << "PostFX file loaded: " << postfx_presets.size() << " preset(s)\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (const fkyaml::exception& e) {
|
||||
if (console) {
|
||||
std::cerr << "Error parsing PostFX YAML: " << e.what() << '\n';
|
||||
}
|
||||
return savePostFXToFile();
|
||||
}
|
||||
}
|
||||
|
||||
// Guarda los presets de PostFX por defecto
|
||||
auto savePostFXToFile() -> bool {
|
||||
if (postfx_file_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(postfx_file_path);
|
||||
if (!file.is_open()) {
|
||||
if (console) {
|
||||
std::cerr << "Error: Unable to open file " << postfx_file_path << " for writing\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
file << "# JailDoctor's Dilemma - PostFX Presets\n";
|
||||
file << "# Each preset defines the intensity of post-processing effects (0.0 to 1.0).\n";
|
||||
file << "# vignette: screen darkening at the edges\n";
|
||||
file << "# scanlines: horizontal scanline effect\n";
|
||||
file << "# chroma: chromatic aberration (RGB color fringing)\n";
|
||||
file << "\n";
|
||||
file << "presets:\n";
|
||||
file << " - name: \"CRT\"\n";
|
||||
file << " vignette: 0.6\n";
|
||||
file << " scanlines: 0.7\n";
|
||||
file << " chroma: 0.15\n";
|
||||
file << " - name: \"SCANLINES\"\n";
|
||||
file << " vignette: 0.0\n";
|
||||
file << " scanlines: 0.8\n";
|
||||
file << " chroma: 0.0\n";
|
||||
file << " - name: \"SUBTLE\"\n";
|
||||
file << " vignette: 0.3\n";
|
||||
file << " scanlines: 0.4\n";
|
||||
file << " chroma: 0.05\n";
|
||||
|
||||
file.close();
|
||||
|
||||
if (console) {
|
||||
std::cout << "PostFX file created with defaults: " << postfx_file_path << '\n';
|
||||
}
|
||||
|
||||
// Cargar los presets recién creados
|
||||
postfx_presets.clear();
|
||||
postfx_presets.push_back({"CRT", 0.6F, 0.7F, 0.15F});
|
||||
postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F, 0.0F});
|
||||
postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F});
|
||||
current_postfx_preset = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Options
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <string> // Para string, basic_string
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen::Filter
|
||||
#include "game/defaults.hpp"
|
||||
@@ -79,7 +80,7 @@ struct Video {
|
||||
bool fullscreen{Defaults::Video::FULLSCREEN}; // Contiene el valor del modo de pantalla completa
|
||||
Screen::Filter filter{Defaults::Video::FILTER}; // Filtro usado para el escalado de la imagen
|
||||
bool vertical_sync{Defaults::Video::VERTICAL_SYNC}; // Indica si se quiere usar vsync o no
|
||||
bool shaders{Defaults::Video::SHADERS}; // Indica si se van a usar shaders o no
|
||||
bool postfx{Defaults::Video::POSTFX}; // Indica si se van a usar efectos PostFX o no
|
||||
bool integer_scale{Defaults::Video::INTEGER_SCALE}; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa
|
||||
bool keep_aspect{Defaults::Video::KEEP_ASPECT}; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa
|
||||
Border border{}; // Borde de la pantalla
|
||||
@@ -113,6 +114,14 @@ struct Game {
|
||||
float height{Defaults::Canvas::HEIGHT}; // Alto de la resolucion del juego
|
||||
};
|
||||
|
||||
// Estructura para un preset de PostFX
|
||||
struct PostFXPreset {
|
||||
std::string name; // Nombre del preset
|
||||
float vignette{0.6F}; // Intensidad de la viñeta (0.0 = ninguna, 1.0 = máxima)
|
||||
float scanlines{0.7F}; // Intensidad de las scanlines (0.0 = desactivadas, 1.0 = máximas)
|
||||
float chroma{0.15F}; // Intensidad de la aberración cromática (0.0 = ninguna, 1.0 = máxima)
|
||||
};
|
||||
|
||||
// --- Variables globales ---
|
||||
inline std::string version{}; // Versión del fichero de configuración. Sirve para saber si las opciones son compatibles
|
||||
inline bool console{false}; // Indica si ha de mostrar información por la consola de texto
|
||||
@@ -129,10 +138,18 @@ inline Kiosk kiosk{}; // Opciones del modo kiosko
|
||||
// Ruta completa del fichero de configuración (establecida mediante setConfigFile)
|
||||
inline std::string config_file_path{};
|
||||
|
||||
// --- Variables PostFX ---
|
||||
inline std::vector<PostFXPreset> postfx_presets{}; // Lista de presets de PostFX
|
||||
inline int current_postfx_preset{0}; // Índice del preset de PostFX actual
|
||||
inline std::string postfx_file_path{}; // Ruta del fichero postfx.yaml
|
||||
|
||||
// --- Funciones públicas ---
|
||||
void init(); // Crea e inicializa las opciones del programa
|
||||
void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración
|
||||
auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado
|
||||
auto saveToFile() -> bool; // Guarda las opciones al fichero configurado
|
||||
void init(); // Crea e inicializa las opciones del programa
|
||||
void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración
|
||||
auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado
|
||||
auto saveToFile() -> bool; // Guarda las opciones al fichero configurado
|
||||
void setPostFXFile(const std::string& path); // Establece la ruta del fichero de PostFX
|
||||
auto loadPostFXFromFile() -> bool; // Carga los presets de PostFX desde el fichero
|
||||
auto savePostFXToFile() -> bool; // Guarda los presets de PostFX por defecto
|
||||
|
||||
} // namespace Options
|
||||
@@ -34,7 +34,7 @@ enum class Options {
|
||||
|
||||
// --- Variables de estado globales ---
|
||||
#ifdef _DEBUG
|
||||
inline Scene current = Scene::ENDING2; // Escena actual
|
||||
inline Scene current = Scene::GAME; // Escena actual
|
||||
inline Options options = Options::LOGO_TO_LOADING_SCREEN; // Opciones de la escena actual
|
||||
#else
|
||||
inline Scene current = Scene::LOGO; // Escena actual
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// Textos
|
||||
namespace Texts {
|
||||
constexpr const char* WINDOW_CAPTION = "JailDoctor's Dilemma";
|
||||
constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — JailDesigner";
|
||||
constexpr const char* COPYRIGHT = "@2022 JailDesigner";
|
||||
constexpr const char* VERSION = "1.10"; // Versión por defecto
|
||||
} // namespace Texts
|
||||
|
||||
Reference in New Issue
Block a user