Compare commits
8 Commits
8dde13409b
...
f71f7cd5ed
| Author | SHA1 | Date | |
|---|---|---|---|
| f71f7cd5ed | |||
| dcea4ebbab | |||
| b9b5f0b29f | |||
| 200672756c | |||
| f3b029c5b6 | |||
| e46c3eb4ba | |||
| ea05e1eb2e | |||
| c052b45a60 |
3
.vscode/c_cpp_properties.json
vendored
3
.vscode/c_cpp_properties.json
vendored
@@ -5,6 +5,7 @@
|
||||
"includePath": [
|
||||
"${workspaceFolder}/source",
|
||||
"${workspaceFolder}/source/external",
|
||||
"${workspaceFolder}/build/generated_shaders",
|
||||
"${env:HOMEBREW_PREFIX}/include",
|
||||
"/opt/homebrew/include"
|
||||
],
|
||||
@@ -25,6 +26,7 @@
|
||||
"includePath": [
|
||||
"${workspaceFolder}/source",
|
||||
"${workspaceFolder}/source/external",
|
||||
"${workspaceFolder}/build/generated_shaders",
|
||||
"/usr/include",
|
||||
"/usr/local/include"
|
||||
],
|
||||
@@ -41,6 +43,7 @@
|
||||
"includePath": [
|
||||
"${workspaceFolder}/source",
|
||||
"${workspaceFolder}/source/external",
|
||||
"${workspaceFolder}/build/generated_shaders",
|
||||
"C:/mingw/include"
|
||||
],
|
||||
"defines": [
|
||||
|
||||
@@ -64,61 +64,6 @@ if(NOT APPLE)
|
||||
endforeach()
|
||||
|
||||
add_custom_target(shaders ALL DEPENDS ${SPIRV_HEADERS})
|
||||
|
||||
# External runtime shaders: auto-discover and compile data/shaders/**/*.vert/*.frag
|
||||
# Output: <source>.spv alongside each source file (loaded at runtime by GpuShaderPreset)
|
||||
file(GLOB_RECURSE DATA_SHADERS
|
||||
"${CMAKE_SOURCE_DIR}/data/shaders/**/*.vert"
|
||||
"${CMAKE_SOURCE_DIR}/data/shaders/**/*.frag")
|
||||
|
||||
set(DATA_SHADER_SPVS)
|
||||
foreach(SHADER_FILE ${DATA_SHADERS})
|
||||
get_filename_component(SHADER_EXT "${SHADER_FILE}" EXT)
|
||||
if(SHADER_EXT STREQUAL ".vert")
|
||||
set(STAGE_FLAG "-fshader-stage=vertex")
|
||||
else()
|
||||
set(STAGE_FLAG "-fshader-stage=fragment")
|
||||
endif()
|
||||
set(SPV_FILE "${SHADER_FILE}.spv")
|
||||
add_custom_command(
|
||||
OUTPUT "${SPV_FILE}"
|
||||
COMMAND "${GLSLC}" ${STAGE_FLAG} -o "${SPV_FILE}" "${SHADER_FILE}"
|
||||
DEPENDS "${SHADER_FILE}"
|
||||
COMMENT "Compiling ${SHADER_FILE}"
|
||||
)
|
||||
list(APPEND DATA_SHADER_SPVS "${SPV_FILE}")
|
||||
endforeach()
|
||||
|
||||
if(DATA_SHADER_SPVS)
|
||||
add_custom_target(data_shaders ALL DEPENDS ${DATA_SHADER_SPVS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# macOS: transpile .spv → .msl for GpuShaderPreset
|
||||
if(APPLE)
|
||||
find_program(SPIRV_CROSS spirv-cross)
|
||||
if(SPIRV_CROSS)
|
||||
file(GLOB_RECURSE DATA_SHADER_SPVS
|
||||
"${CMAKE_SOURCE_DIR}/data/shaders/*.vert.spv"
|
||||
"${CMAKE_SOURCE_DIR}/data/shaders/*.frag.spv"
|
||||
)
|
||||
set(DATA_SHADER_MSLS)
|
||||
foreach(SPV_FILE ${DATA_SHADER_SPVS})
|
||||
set(MSL_FILE "${SPV_FILE}.msl")
|
||||
add_custom_command(
|
||||
OUTPUT "${MSL_FILE}"
|
||||
COMMAND "${SPIRV_CROSS}" --msl --output "${MSL_FILE}" "${SPV_FILE}"
|
||||
DEPENDS "${SPV_FILE}"
|
||||
COMMENT "Transpiling ${SPV_FILE} to MSL"
|
||||
)
|
||||
list(APPEND DATA_SHADER_MSLS "${MSL_FILE}")
|
||||
endforeach()
|
||||
if(DATA_SHADER_MSLS)
|
||||
add_custom_target(data_shader_msls ALL DEPENDS ${DATA_SHADER_MSLS})
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "spirv-cross not found — GpuShaderPreset will not work on macOS (run: brew install spirv-cross)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Archivos fuente (excluir main_old.cpp)
|
||||
@@ -160,13 +105,6 @@ target_link_libraries(${PROJECT_NAME} ${LINK_LIBS})
|
||||
if(NOT APPLE)
|
||||
add_dependencies(${PROJECT_NAME} shaders)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE "${SHADER_GEN_DIR}")
|
||||
if(TARGET data_shaders)
|
||||
add_dependencies(${PROJECT_NAME} data_shaders)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(APPLE AND TARGET data_shader_msls)
|
||||
add_dependencies(${PROJECT_NAME} data_shader_msls)
|
||||
endif()
|
||||
|
||||
# Tool: pack_resources
|
||||
|
||||
17
Makefile
17
Makefile
@@ -9,9 +9,9 @@ DIR_TOOLS := $(addsuffix /, $(DIR_ROOT)tools)
|
||||
TARGET_NAME := vibe3_physics
|
||||
TARGET_FILE := $(DIR_BIN)$(TARGET_NAME)
|
||||
APP_NAME := ViBe3 Physics
|
||||
RELEASE_FOLDER := vibe3_release
|
||||
RELEASE_FOLDER := dist/_tmp
|
||||
RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME)
|
||||
RESOURCE_FILE := release/windows/vibe3.res
|
||||
RESOURCE_FILE := build/vibe3.res
|
||||
DIST_DIR := dist
|
||||
|
||||
# Variables para herramienta de empaquetado
|
||||
@@ -66,7 +66,7 @@ APP_SOURCES := $(wildcard source/*.cpp) \
|
||||
APP_SOURCES := $(filter-out source/main_old.cpp, $(APP_SOURCES))
|
||||
|
||||
# Includes
|
||||
INCLUDES := -Isource -Isource/external
|
||||
INCLUDES := -Isource -Isource/external -Ibuild/generated_shaders
|
||||
|
||||
# Variables según el sistema operativo
|
||||
CXXFLAGS_BASE := -std=c++20 -Wall
|
||||
@@ -107,7 +107,11 @@ $(PACK_TOOL): $(PACK_SOURCES)
|
||||
pack_tool: $(PACK_TOOL)
|
||||
|
||||
# Detectar todos los archivos en data/ como dependencias (regenera si cualquiera cambia)
|
||||
DATA_FILES := $(shell find data -type f 2>/dev/null)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
DATA_FILES :=
|
||||
else
|
||||
DATA_FILES := $(shell find data -type f 2>/dev/null)
|
||||
endif
|
||||
|
||||
resources.pack: $(PACK_TOOL) $(DATA_FILES)
|
||||
@echo "Generando resources.pack desde directorio data/..."
|
||||
@@ -123,8 +127,7 @@ force_resource_pack: $(PACK_TOOL)
|
||||
|
||||
# Reglas para compilación
|
||||
windows:
|
||||
@echo off
|
||||
@echo Compilando para Windows con nombre: "$(APP_NAME).exe"
|
||||
@echo Compilando para Windows con nombre: $(APP_NAME).exe
|
||||
windres release/windows/vibe3.rc -O coff -o $(RESOURCE_FILE)
|
||||
$(CXX) $(APP_SOURCES) $(RESOURCE_FILE) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_TARGET_FILE).exe"
|
||||
strip -s -R .comment -R .gnu.version "$(WIN_TARGET_FILE).exe" --strip-unneeded
|
||||
@@ -134,6 +137,7 @@ windows_release: force_resource_pack
|
||||
|
||||
# Crea carpeta temporal 'RELEASE_FOLDER'
|
||||
@if exist "$(RELEASE_FOLDER)" rmdir /S /Q "$(RELEASE_FOLDER)"
|
||||
@if not exist "$(DIST_DIR)" mkdir "$(DIST_DIR)"
|
||||
@mkdir "$(RELEASE_FOLDER)"
|
||||
|
||||
# Copia el archivo 'resources.pack'
|
||||
@@ -145,7 +149,6 @@ windows_release: force_resource_pack
|
||||
@copy /Y release\windows\dll\*.dll "$(RELEASE_FOLDER)\" >nul 2>&1 || echo DLLs copied successfully
|
||||
|
||||
# Compila
|
||||
@if not exist "$(DIST_DIR)" mkdir "$(DIST_DIR)"
|
||||
@windres release/windows/vibe3.rc -O coff -o $(RESOURCE_FILE)
|
||||
@$(CXX) $(APP_SOURCES) $(RESOURCE_FILE) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_RELEASE_FILE).exe"
|
||||
@strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
|
||||
|
||||
@@ -538,7 +538,7 @@ vibe3_physics/
|
||||
|
||||
### Dependencias de shaders
|
||||
|
||||
El proyecto compila shaders GLSL a SPIR-V en todas las plataformas usando `glslc` (incluido en el Vulkan SDK). En macOS, adicionalmente transpila los `.spv` a MSL (Metal) con `spirv-cross`.
|
||||
El proyecto compila shaders GLSL a SPIR-V en todas las plataformas usando `glslc` (incluido en el Vulkan SDK). En macOS, adicionalmente transpila los `.spv` a MSL (Metal) con `spirv-cross` (shaders internos: sprite, ball, postfx).
|
||||
|
||||
| Plataforma | Backend GPU | Herramienta de shaders | Resultado |
|
||||
|------------|-------------|------------------------|-----------|
|
||||
@@ -624,7 +624,7 @@ cmake ..
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
En macOS, CMake detecta automáticamente `spirv-cross` y genera los `.spv.msl` necesarios. Si no está instalado, los shaders de preset externos no funcionarán (el resto de la app sí).
|
||||
En macOS, CMake detecta automáticamente `spirv-cross` y genera los `.spv.msl` necesarios para los shaders internos (sprite, ball, postfx).
|
||||
|
||||
### Make directo (Linux/macOS)
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Based on dannyld's rainbow settings
|
||||
|
||||
shaders = 2
|
||||
|
||||
shader0 = "../crt/shaders/mame_hlsl/shaders/mame_ntsc_encode.slang"
|
||||
filter_linear0 = "true"
|
||||
scale_type0 = "source"
|
||||
scale0 = "1.000000"
|
||||
|
||||
shader1 = "../crt/shaders/mame_hlsl/shaders/mame_ntsc_decode.slang"
|
||||
filter_linear1 = "true"
|
||||
scale_type1 = "source"
|
||||
scale_1 = "1.000000"
|
||||
|
||||
# ntsc parameters
|
||||
ntscsignal = "1.000000"
|
||||
avalue = "0.000000"
|
||||
bvalue = "0.000000"
|
||||
scantime = "47.900070"
|
||||
|
||||
# optional blur
|
||||
shadowalpha = "0.100000"
|
||||
notch_width = "3.450001"
|
||||
ifreqresponse = "1.750000"
|
||||
qfreqresponse = "1.450000"
|
||||
|
||||
# uncomment for jailbars in blue
|
||||
#pvalue = "1.100000"
|
||||
@@ -1,69 +0,0 @@
|
||||
#version 450
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:Ryan Holtz,ImJezze
|
||||
// Adapted from mame_ntsc_encode.slang for SDL3 GPU / Vulkan SPIRV
|
||||
|
||||
layout(location=0) in vec2 v_uv;
|
||||
layout(location=0) out vec4 FragColor;
|
||||
|
||||
layout(set=2, binding=0) uniform sampler2D Source;
|
||||
|
||||
layout(set=3, binding=0) uniform NTSCParams {
|
||||
float source_width;
|
||||
float source_height;
|
||||
float a_value;
|
||||
float b_value;
|
||||
float cc_value;
|
||||
float scan_time;
|
||||
float notch_width;
|
||||
float y_freq;
|
||||
float i_freq;
|
||||
float q_freq;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
} u;
|
||||
|
||||
const float PI = 3.1415927;
|
||||
const float PI2 = PI * 2.0;
|
||||
|
||||
void main() {
|
||||
vec2 source_dims = vec2(u.source_width, u.source_height);
|
||||
|
||||
// p_value=1: one texel step per sub-sample (no horizontal stretch)
|
||||
vec2 PValueSourceTexel = vec2(1.0, 0.0) / source_dims;
|
||||
|
||||
vec2 C0 = v_uv + PValueSourceTexel * vec2(0.00, 0.0);
|
||||
vec2 C1 = v_uv + PValueSourceTexel * vec2(0.25, 0.0);
|
||||
vec2 C2 = v_uv + PValueSourceTexel * vec2(0.50, 0.0);
|
||||
vec2 C3 = v_uv + PValueSourceTexel * vec2(0.75, 0.0);
|
||||
|
||||
vec4 Cx = vec4(C0.x, C1.x, C2.x, C3.x);
|
||||
vec4 Cy = vec4(C0.y, C1.y, C2.y, C3.y);
|
||||
|
||||
vec4 Texel0 = texture(Source, C0);
|
||||
vec4 Texel1 = texture(Source, C1);
|
||||
vec4 Texel2 = texture(Source, C2);
|
||||
vec4 Texel3 = texture(Source, C3);
|
||||
|
||||
vec4 HPosition = Cx;
|
||||
vec4 VPosition = Cy;
|
||||
|
||||
const vec4 YDot = vec4(0.299, 0.587, 0.114, 0.0);
|
||||
const vec4 IDot = vec4(0.595716, -0.274453, -0.321263, 0.0);
|
||||
const vec4 QDot = vec4(0.211456, -0.522591, 0.311135, 0.0);
|
||||
|
||||
vec4 Y = vec4(dot(Texel0, YDot), dot(Texel1, YDot), dot(Texel2, YDot), dot(Texel3, YDot));
|
||||
vec4 I = vec4(dot(Texel0, IDot), dot(Texel1, IDot), dot(Texel2, IDot), dot(Texel3, IDot));
|
||||
vec4 Q = vec4(dot(Texel0, QDot), dot(Texel1, QDot), dot(Texel2, QDot), dot(Texel3, QDot));
|
||||
|
||||
float W = PI2 * u.cc_value * u.scan_time;
|
||||
float WoPI = W / PI;
|
||||
|
||||
float HOffset = u.a_value / WoPI;
|
||||
float VScale = u.b_value * source_dims.y / WoPI;
|
||||
|
||||
vec4 T = HPosition + vec4(HOffset) + VPosition * vec4(VScale);
|
||||
vec4 TW = T * W;
|
||||
|
||||
FragColor = Y + I * cos(TW) + Q * sin(TW);
|
||||
}
|
||||
Binary file not shown.
@@ -1,61 +0,0 @@
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
struct NTSCParams
|
||||
{
|
||||
float source_width;
|
||||
float source_height;
|
||||
float a_value;
|
||||
float b_value;
|
||||
float cc_value;
|
||||
float scan_time;
|
||||
float notch_width;
|
||||
float y_freq;
|
||||
float i_freq;
|
||||
float q_freq;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
};
|
||||
|
||||
struct main0_out
|
||||
{
|
||||
float4 FragColor [[color(0)]];
|
||||
};
|
||||
|
||||
struct main0_in
|
||||
{
|
||||
float2 v_uv [[user(locn0)]];
|
||||
};
|
||||
|
||||
fragment main0_out main0(main0_in in [[stage_in]], constant NTSCParams& u [[buffer(0)]], texture2d<float> Source [[texture(0)]], sampler SourceSmplr [[sampler(0)]])
|
||||
{
|
||||
main0_out out = {};
|
||||
float2 source_dims = float2(u.source_width, u.source_height);
|
||||
float2 PValueSourceTexel = float2(1.0, 0.0) / source_dims;
|
||||
float2 C0 = in.v_uv + (PValueSourceTexel * float2(0.0));
|
||||
float2 C1 = in.v_uv + (PValueSourceTexel * float2(0.25, 0.0));
|
||||
float2 C2 = in.v_uv + (PValueSourceTexel * float2(0.5, 0.0));
|
||||
float2 C3 = in.v_uv + (PValueSourceTexel * float2(0.75, 0.0));
|
||||
float4 Cx = float4(C0.x, C1.x, C2.x, C3.x);
|
||||
float4 Cy = float4(C0.y, C1.y, C2.y, C3.y);
|
||||
float4 Texel0 = Source.sample(SourceSmplr, C0);
|
||||
float4 Texel1 = Source.sample(SourceSmplr, C1);
|
||||
float4 Texel2 = Source.sample(SourceSmplr, C2);
|
||||
float4 Texel3 = Source.sample(SourceSmplr, C3);
|
||||
float4 HPosition = Cx;
|
||||
float4 VPosition = Cy;
|
||||
float4 Y = float4(dot(Texel0, float4(0.2989999949932098388671875, 0.58700001239776611328125, 0.114000000059604644775390625, 0.0)), dot(Texel1, float4(0.2989999949932098388671875, 0.58700001239776611328125, 0.114000000059604644775390625, 0.0)), dot(Texel2, float4(0.2989999949932098388671875, 0.58700001239776611328125, 0.114000000059604644775390625, 0.0)), dot(Texel3, float4(0.2989999949932098388671875, 0.58700001239776611328125, 0.114000000059604644775390625, 0.0)));
|
||||
float4 I = float4(dot(Texel0, float4(0.595715999603271484375, -0.2744530141353607177734375, -0.3212629854679107666015625, 0.0)), dot(Texel1, float4(0.595715999603271484375, -0.2744530141353607177734375, -0.3212629854679107666015625, 0.0)), dot(Texel2, float4(0.595715999603271484375, -0.2744530141353607177734375, -0.3212629854679107666015625, 0.0)), dot(Texel3, float4(0.595715999603271484375, -0.2744530141353607177734375, -0.3212629854679107666015625, 0.0)));
|
||||
float4 Q = float4(dot(Texel0, float4(0.211456000804901123046875, -0.52259099483489990234375, 0.311134994029998779296875, 0.0)), dot(Texel1, float4(0.211456000804901123046875, -0.52259099483489990234375, 0.311134994029998779296875, 0.0)), dot(Texel2, float4(0.211456000804901123046875, -0.52259099483489990234375, 0.311134994029998779296875, 0.0)), dot(Texel3, float4(0.211456000804901123046875, -0.52259099483489990234375, 0.311134994029998779296875, 0.0)));
|
||||
float W = (6.283185482025146484375 * u.cc_value) * u.scan_time;
|
||||
float WoPI = W / 3.1415927410125732421875;
|
||||
float HOffset = u.a_value / WoPI;
|
||||
float VScale = (u.b_value * source_dims.y) / WoPI;
|
||||
float4 T = (HPosition + float4(HOffset)) + (VPosition * float4(VScale));
|
||||
float4 TW = T * W;
|
||||
out.FragColor = (Y + (I * cos(TW))) + (Q * sin(TW));
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#version 450
|
||||
layout(location=0) out vec2 v_uv;
|
||||
void main() {
|
||||
vec2 positions[3] = vec2[3](vec2(-1.0,-1.0), vec2(3.0,-1.0), vec2(-1.0,3.0));
|
||||
vec2 uvs[3] = vec2[3](vec2(0.0, 1.0), vec2(2.0, 1.0), vec2(0.0,-1.0));
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
v_uv = uvs[gl_VertexIndex];
|
||||
}
|
||||
Binary file not shown.
@@ -1,63 +0,0 @@
|
||||
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||
#pragma clang diagnostic ignored "-Wmissing-braces"
|
||||
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
template<typename T, size_t Num>
|
||||
struct spvUnsafeArray
|
||||
{
|
||||
T elements[Num ? Num : 1];
|
||||
|
||||
thread T& operator [] (size_t pos) thread
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
constexpr const thread T& operator [] (size_t pos) const thread
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
|
||||
device T& operator [] (size_t pos) device
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
constexpr const device T& operator [] (size_t pos) const device
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
|
||||
constexpr const constant T& operator [] (size_t pos) const constant
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
|
||||
threadgroup T& operator [] (size_t pos) threadgroup
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
constexpr const threadgroup T& operator [] (size_t pos) const threadgroup
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
};
|
||||
|
||||
constant spvUnsafeArray<float2, 3> _18 = spvUnsafeArray<float2, 3>({ float2(-1.0), float2(3.0, -1.0), float2(-1.0, 3.0) });
|
||||
constant spvUnsafeArray<float2, 3> _26 = spvUnsafeArray<float2, 3>({ float2(0.0, 1.0), float2(2.0, 1.0), float2(0.0, -1.0) });
|
||||
|
||||
struct main0_out
|
||||
{
|
||||
float2 v_uv [[user(locn0)]];
|
||||
float4 gl_Position [[position]];
|
||||
};
|
||||
|
||||
vertex main0_out main0(uint gl_VertexIndex [[vertex_id]])
|
||||
{
|
||||
main0_out out = {};
|
||||
out.gl_Position = float4(_18[int(gl_VertexIndex)], 0.0, 1.0);
|
||||
out.v_uv = _26[int(gl_VertexIndex)];
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
#version 450
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:Ryan Holtz,ImJezze
|
||||
// Adapted from mame_ntsc_decode.slang for SDL3 GPU / Vulkan SPIRV
|
||||
|
||||
layout(location=0) in vec2 v_uv;
|
||||
layout(location=0) out vec4 FragColor;
|
||||
|
||||
layout(set=2, binding=0) uniform sampler2D Source;
|
||||
|
||||
layout(set=3, binding=0) uniform NTSCParams {
|
||||
float source_width;
|
||||
float source_height;
|
||||
float a_value;
|
||||
float b_value;
|
||||
float cc_value;
|
||||
float scan_time;
|
||||
float notch_width;
|
||||
float y_freq;
|
||||
float i_freq;
|
||||
float q_freq;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
} u;
|
||||
|
||||
const float PI = 3.1415927;
|
||||
const float PI2 = PI * 2.0;
|
||||
|
||||
const vec3 RDot = vec3(1.0, 0.956, 0.621);
|
||||
const vec3 GDot = vec3(1.0, -0.272, -0.647);
|
||||
const vec3 BDot = vec3(1.0, -1.106, 1.703);
|
||||
|
||||
const vec4 NotchOffset = vec4(0.0, 1.0, 2.0, 3.0);
|
||||
const int SampleCount = 64;
|
||||
const int HalfSampleCount = 32;
|
||||
|
||||
void main() {
|
||||
vec2 source_dims = vec2(u.source_width, u.source_height);
|
||||
vec4 BaseTexel = texture(Source, v_uv);
|
||||
|
||||
float CCValue = u.cc_value;
|
||||
float ScanTime = u.scan_time;
|
||||
float NotchHalfWidth = u.notch_width / 2.0;
|
||||
float YFreqResponse = u.y_freq;
|
||||
float IFreqResponse = u.i_freq;
|
||||
float QFreqResponse = u.q_freq;
|
||||
float AValue = u.a_value;
|
||||
float BValue = u.b_value;
|
||||
|
||||
float TimePerSample = ScanTime / (source_dims.x * 4.0);
|
||||
|
||||
float Fc_y1 = (CCValue - NotchHalfWidth) * TimePerSample;
|
||||
float Fc_y2 = (CCValue + NotchHalfWidth) * TimePerSample;
|
||||
float Fc_y3 = YFreqResponse * TimePerSample;
|
||||
float Fc_i = IFreqResponse * TimePerSample;
|
||||
float Fc_q = QFreqResponse * TimePerSample;
|
||||
|
||||
float Fc_i_2 = Fc_i * 2.0;
|
||||
float Fc_q_2 = Fc_q * 2.0;
|
||||
float Fc_y1_2 = Fc_y1 * 2.0;
|
||||
float Fc_y2_2 = Fc_y2 * 2.0;
|
||||
float Fc_y3_2 = Fc_y3 * 2.0;
|
||||
float Fc_i_pi2 = Fc_i * PI2;
|
||||
float Fc_q_pi2 = Fc_q * PI2;
|
||||
float Fc_y1_pi2 = Fc_y1 * PI2;
|
||||
float Fc_y2_pi2 = Fc_y2 * PI2;
|
||||
float Fc_y3_pi2 = Fc_y3 * PI2;
|
||||
float PI2Length = PI2 / float(SampleCount);
|
||||
|
||||
float W = PI2 * CCValue * ScanTime;
|
||||
float WoPI = W / PI;
|
||||
|
||||
float HOffset = BValue / WoPI;
|
||||
float VScale = AValue * source_dims.y / WoPI;
|
||||
|
||||
vec4 YAccum = vec4(0.0);
|
||||
vec4 IAccum = vec4(0.0);
|
||||
vec4 QAccum = vec4(0.0);
|
||||
|
||||
vec4 Cy = vec4(v_uv.y);
|
||||
vec4 VPosition = Cy;
|
||||
|
||||
for (float i = 0.0; i < float(SampleCount); i += 4.0) {
|
||||
float n = i - float(HalfSampleCount);
|
||||
vec4 n4 = n + NotchOffset;
|
||||
|
||||
vec4 Cx = vec4(v_uv.x) + (n4 * 0.25) / source_dims.x;
|
||||
vec4 HPosition = Cx;
|
||||
|
||||
vec4 C = texture(Source, vec2(Cx.r, Cy.r));
|
||||
vec4 T = HPosition + vec4(HOffset) + VPosition * vec4(VScale);
|
||||
vec4 WT = W * T;
|
||||
|
||||
vec4 SincKernel = 0.54 + 0.46 * cos(PI2Length * n4);
|
||||
|
||||
vec4 SincYIn1 = Fc_y1_pi2 * n4;
|
||||
vec4 SincYIn2 = Fc_y2_pi2 * n4;
|
||||
vec4 SincYIn3 = Fc_y3_pi2 * n4;
|
||||
vec4 SincIIn = Fc_i_pi2 * n4;
|
||||
vec4 SincQIn = Fc_q_pi2 * n4;
|
||||
|
||||
vec4 SincY1, SincY2, SincY3;
|
||||
SincY1.x = (SincYIn1.x != 0.0) ? sin(SincYIn1.x) / SincYIn1.x : 1.0;
|
||||
SincY1.y = (SincYIn1.y != 0.0) ? sin(SincYIn1.y) / SincYIn1.y : 1.0;
|
||||
SincY1.z = (SincYIn1.z != 0.0) ? sin(SincYIn1.z) / SincYIn1.z : 1.0;
|
||||
SincY1.w = (SincYIn1.w != 0.0) ? sin(SincYIn1.w) / SincYIn1.w : 1.0;
|
||||
SincY2.x = (SincYIn2.x != 0.0) ? sin(SincYIn2.x) / SincYIn2.x : 1.0;
|
||||
SincY2.y = (SincYIn2.y != 0.0) ? sin(SincYIn2.y) / SincYIn2.y : 1.0;
|
||||
SincY2.z = (SincYIn2.z != 0.0) ? sin(SincYIn2.z) / SincYIn2.z : 1.0;
|
||||
SincY2.w = (SincYIn2.w != 0.0) ? sin(SincYIn2.w) / SincYIn2.w : 1.0;
|
||||
SincY3.x = (SincYIn3.x != 0.0) ? sin(SincYIn3.x) / SincYIn3.x : 1.0;
|
||||
SincY3.y = (SincYIn3.y != 0.0) ? sin(SincYIn3.y) / SincYIn3.y : 1.0;
|
||||
SincY3.z = (SincYIn3.z != 0.0) ? sin(SincYIn3.z) / SincYIn3.z : 1.0;
|
||||
SincY3.w = (SincYIn3.w != 0.0) ? sin(SincYIn3.w) / SincYIn3.w : 1.0;
|
||||
|
||||
vec4 IdealY = Fc_y1_2 * SincY1 - Fc_y2_2 * SincY2 + Fc_y3_2 * SincY3;
|
||||
|
||||
vec4 IdealI, IdealQ;
|
||||
IdealI.x = Fc_i_2 * ((SincIIn.x != 0.0) ? sin(SincIIn.x) / SincIIn.x : 1.0);
|
||||
IdealI.y = Fc_i_2 * ((SincIIn.y != 0.0) ? sin(SincIIn.y) / SincIIn.y : 1.0);
|
||||
IdealI.z = Fc_i_2 * ((SincIIn.z != 0.0) ? sin(SincIIn.z) / SincIIn.z : 1.0);
|
||||
IdealI.w = Fc_i_2 * ((SincIIn.w != 0.0) ? sin(SincIIn.w) / SincIIn.w : 1.0);
|
||||
IdealQ.x = Fc_q_2 * ((SincQIn.x != 0.0) ? sin(SincQIn.x) / SincQIn.x : 1.0);
|
||||
IdealQ.y = Fc_q_2 * ((SincQIn.y != 0.0) ? sin(SincQIn.y) / SincQIn.y : 1.0);
|
||||
IdealQ.z = Fc_q_2 * ((SincQIn.z != 0.0) ? sin(SincQIn.z) / SincQIn.z : 1.0);
|
||||
IdealQ.w = Fc_q_2 * ((SincQIn.w != 0.0) ? sin(SincQIn.w) / SincQIn.w : 1.0);
|
||||
|
||||
vec4 FilterY = SincKernel * IdealY;
|
||||
vec4 FilterI = SincKernel * IdealI;
|
||||
vec4 FilterQ = SincKernel * IdealQ;
|
||||
|
||||
YAccum += C * FilterY;
|
||||
IAccum += C * cos(WT) * FilterI;
|
||||
QAccum += C * sin(WT) * FilterQ;
|
||||
}
|
||||
|
||||
vec3 YIQ = vec3(
|
||||
(YAccum.r + YAccum.g + YAccum.b + YAccum.a),
|
||||
(IAccum.r + IAccum.g + IAccum.b + IAccum.a) * 2.0,
|
||||
(QAccum.r + QAccum.g + QAccum.b + QAccum.a) * 2.0);
|
||||
|
||||
vec3 RGB = vec3(
|
||||
dot(YIQ, RDot),
|
||||
dot(YIQ, GDot),
|
||||
dot(YIQ, BDot));
|
||||
|
||||
FragColor = vec4(RGB, BaseTexel.a);
|
||||
}
|
||||
Binary file not shown.
@@ -1,304 +0,0 @@
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
struct NTSCParams
|
||||
{
|
||||
float source_width;
|
||||
float source_height;
|
||||
float a_value;
|
||||
float b_value;
|
||||
float cc_value;
|
||||
float scan_time;
|
||||
float notch_width;
|
||||
float y_freq;
|
||||
float i_freq;
|
||||
float q_freq;
|
||||
float _pad0;
|
||||
float _pad1;
|
||||
};
|
||||
|
||||
struct main0_out
|
||||
{
|
||||
float4 FragColor [[color(0)]];
|
||||
};
|
||||
|
||||
struct main0_in
|
||||
{
|
||||
float2 v_uv [[user(locn0)]];
|
||||
};
|
||||
|
||||
fragment main0_out main0(main0_in in [[stage_in]], constant NTSCParams& u [[buffer(0)]], texture2d<float> Source [[texture(0)]], sampler SourceSmplr [[sampler(0)]])
|
||||
{
|
||||
main0_out out = {};
|
||||
float2 source_dims = float2(u.source_width, u.source_height);
|
||||
float4 BaseTexel = Source.sample(SourceSmplr, in.v_uv);
|
||||
float CCValue = u.cc_value;
|
||||
float ScanTime = u.scan_time;
|
||||
float NotchHalfWidth = u.notch_width / 2.0;
|
||||
float YFreqResponse = u.y_freq;
|
||||
float IFreqResponse = u.i_freq;
|
||||
float QFreqResponse = u.q_freq;
|
||||
float AValue = u.a_value;
|
||||
float BValue = u.b_value;
|
||||
float TimePerSample = ScanTime / (source_dims.x * 4.0);
|
||||
float Fc_y1 = (CCValue - NotchHalfWidth) * TimePerSample;
|
||||
float Fc_y2 = (CCValue + NotchHalfWidth) * TimePerSample;
|
||||
float Fc_y3 = YFreqResponse * TimePerSample;
|
||||
float Fc_i = IFreqResponse * TimePerSample;
|
||||
float Fc_q = QFreqResponse * TimePerSample;
|
||||
float Fc_i_2 = Fc_i * 2.0;
|
||||
float Fc_q_2 = Fc_q * 2.0;
|
||||
float Fc_y1_2 = Fc_y1 * 2.0;
|
||||
float Fc_y2_2 = Fc_y2 * 2.0;
|
||||
float Fc_y3_2 = Fc_y3 * 2.0;
|
||||
float Fc_i_pi2 = Fc_i * 6.283185482025146484375;
|
||||
float Fc_q_pi2 = Fc_q * 6.283185482025146484375;
|
||||
float Fc_y1_pi2 = Fc_y1 * 6.283185482025146484375;
|
||||
float Fc_y2_pi2 = Fc_y2 * 6.283185482025146484375;
|
||||
float Fc_y3_pi2 = Fc_y3 * 6.283185482025146484375;
|
||||
float PI2Length = 0.098174773156642913818359375;
|
||||
float W = (6.283185482025146484375 * CCValue) * ScanTime;
|
||||
float WoPI = W / 3.1415927410125732421875;
|
||||
float HOffset = BValue / WoPI;
|
||||
float VScale = (AValue * source_dims.y) / WoPI;
|
||||
float4 YAccum = float4(0.0);
|
||||
float4 IAccum = float4(0.0);
|
||||
float4 QAccum = float4(0.0);
|
||||
float4 Cy = float4(in.v_uv.y);
|
||||
float4 VPosition = Cy;
|
||||
float4 SincY1;
|
||||
float _259;
|
||||
float _274;
|
||||
float _290;
|
||||
float _306;
|
||||
float4 SincY2;
|
||||
float _322;
|
||||
float _337;
|
||||
float _352;
|
||||
float _367;
|
||||
float4 SincY3;
|
||||
float _383;
|
||||
float _398;
|
||||
float _413;
|
||||
float _428;
|
||||
float4 IdealI;
|
||||
float _457;
|
||||
float _474;
|
||||
float _491;
|
||||
float _508;
|
||||
float4 IdealQ;
|
||||
float _526;
|
||||
float _543;
|
||||
float _560;
|
||||
float _577;
|
||||
for (float i = 0.0; i < 64.0; i += 4.0)
|
||||
{
|
||||
float n = i - 32.0;
|
||||
float4 n4 = float4(n) + float4(0.0, 1.0, 2.0, 3.0);
|
||||
float4 Cx = float4(in.v_uv.x) + ((n4 * 0.25) / float4(source_dims.x));
|
||||
float4 HPosition = Cx;
|
||||
float4 C = Source.sample(SourceSmplr, float2(Cx.x, Cy.x));
|
||||
float4 T = (HPosition + float4(HOffset)) + (VPosition * float4(VScale));
|
||||
float4 WT = T * W;
|
||||
float4 SincKernel = float4(0.540000021457672119140625) + (cos(n4 * PI2Length) * 0.4600000083446502685546875);
|
||||
float4 SincYIn1 = n4 * Fc_y1_pi2;
|
||||
float4 SincYIn2 = n4 * Fc_y2_pi2;
|
||||
float4 SincYIn3 = n4 * Fc_y3_pi2;
|
||||
float4 SincIIn = n4 * Fc_i_pi2;
|
||||
float4 SincQIn = n4 * Fc_q_pi2;
|
||||
if (SincYIn1.x != 0.0)
|
||||
{
|
||||
_259 = sin(SincYIn1.x) / SincYIn1.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
_259 = 1.0;
|
||||
}
|
||||
SincY1.x = _259;
|
||||
if (SincYIn1.y != 0.0)
|
||||
{
|
||||
_274 = sin(SincYIn1.y) / SincYIn1.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
_274 = 1.0;
|
||||
}
|
||||
SincY1.y = _274;
|
||||
if (SincYIn1.z != 0.0)
|
||||
{
|
||||
_290 = sin(SincYIn1.z) / SincYIn1.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
_290 = 1.0;
|
||||
}
|
||||
SincY1.z = _290;
|
||||
if (SincYIn1.w != 0.0)
|
||||
{
|
||||
_306 = sin(SincYIn1.w) / SincYIn1.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
_306 = 1.0;
|
||||
}
|
||||
SincY1.w = _306;
|
||||
if (SincYIn2.x != 0.0)
|
||||
{
|
||||
_322 = sin(SincYIn2.x) / SincYIn2.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
_322 = 1.0;
|
||||
}
|
||||
SincY2.x = _322;
|
||||
if (SincYIn2.y != 0.0)
|
||||
{
|
||||
_337 = sin(SincYIn2.y) / SincYIn2.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
_337 = 1.0;
|
||||
}
|
||||
SincY2.y = _337;
|
||||
if (SincYIn2.z != 0.0)
|
||||
{
|
||||
_352 = sin(SincYIn2.z) / SincYIn2.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
_352 = 1.0;
|
||||
}
|
||||
SincY2.z = _352;
|
||||
if (SincYIn2.w != 0.0)
|
||||
{
|
||||
_367 = sin(SincYIn2.w) / SincYIn2.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
_367 = 1.0;
|
||||
}
|
||||
SincY2.w = _367;
|
||||
if (SincYIn3.x != 0.0)
|
||||
{
|
||||
_383 = sin(SincYIn3.x) / SincYIn3.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
_383 = 1.0;
|
||||
}
|
||||
SincY3.x = _383;
|
||||
if (SincYIn3.y != 0.0)
|
||||
{
|
||||
_398 = sin(SincYIn3.y) / SincYIn3.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
_398 = 1.0;
|
||||
}
|
||||
SincY3.y = _398;
|
||||
if (SincYIn3.z != 0.0)
|
||||
{
|
||||
_413 = sin(SincYIn3.z) / SincYIn3.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
_413 = 1.0;
|
||||
}
|
||||
SincY3.z = _413;
|
||||
if (SincYIn3.w != 0.0)
|
||||
{
|
||||
_428 = sin(SincYIn3.w) / SincYIn3.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
_428 = 1.0;
|
||||
}
|
||||
SincY3.w = _428;
|
||||
float4 IdealY = ((SincY1 * Fc_y1_2) - (SincY2 * Fc_y2_2)) + (SincY3 * Fc_y3_2);
|
||||
if (SincIIn.x != 0.0)
|
||||
{
|
||||
_457 = sin(SincIIn.x) / SincIIn.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
_457 = 1.0;
|
||||
}
|
||||
IdealI.x = Fc_i_2 * _457;
|
||||
if (SincIIn.y != 0.0)
|
||||
{
|
||||
_474 = sin(SincIIn.y) / SincIIn.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
_474 = 1.0;
|
||||
}
|
||||
IdealI.y = Fc_i_2 * _474;
|
||||
if (SincIIn.z != 0.0)
|
||||
{
|
||||
_491 = sin(SincIIn.z) / SincIIn.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
_491 = 1.0;
|
||||
}
|
||||
IdealI.z = Fc_i_2 * _491;
|
||||
if (SincIIn.w != 0.0)
|
||||
{
|
||||
_508 = sin(SincIIn.w) / SincIIn.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
_508 = 1.0;
|
||||
}
|
||||
IdealI.w = Fc_i_2 * _508;
|
||||
if (SincQIn.x != 0.0)
|
||||
{
|
||||
_526 = sin(SincQIn.x) / SincQIn.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
_526 = 1.0;
|
||||
}
|
||||
IdealQ.x = Fc_q_2 * _526;
|
||||
if (SincQIn.y != 0.0)
|
||||
{
|
||||
_543 = sin(SincQIn.y) / SincQIn.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
_543 = 1.0;
|
||||
}
|
||||
IdealQ.y = Fc_q_2 * _543;
|
||||
if (SincQIn.z != 0.0)
|
||||
{
|
||||
_560 = sin(SincQIn.z) / SincQIn.z;
|
||||
}
|
||||
else
|
||||
{
|
||||
_560 = 1.0;
|
||||
}
|
||||
IdealQ.z = Fc_q_2 * _560;
|
||||
if (SincQIn.w != 0.0)
|
||||
{
|
||||
_577 = sin(SincQIn.w) / SincQIn.w;
|
||||
}
|
||||
else
|
||||
{
|
||||
_577 = 1.0;
|
||||
}
|
||||
IdealQ.w = Fc_q_2 * _577;
|
||||
float4 FilterY = SincKernel * IdealY;
|
||||
float4 FilterI = SincKernel * IdealI;
|
||||
float4 FilterQ = SincKernel * IdealQ;
|
||||
YAccum += (C * FilterY);
|
||||
IAccum += ((C * cos(WT)) * FilterI);
|
||||
QAccum += ((C * sin(WT)) * FilterQ);
|
||||
}
|
||||
float3 YIQ = float3(((YAccum.x + YAccum.y) + YAccum.z) + YAccum.w, (((IAccum.x + IAccum.y) + IAccum.z) + IAccum.w) * 2.0, (((QAccum.x + QAccum.y) + QAccum.z) + QAccum.w) * 2.0);
|
||||
float3 RGB = float3(dot(YIQ, float3(1.0, 0.95599997043609619140625, 0.620999991893768310546875)), dot(YIQ, float3(1.0, -0.272000014781951904296875, -0.647000014781951904296875)), dot(YIQ, float3(1.0, -1.10599994659423828125, 1.70299994945526123046875)));
|
||||
out.FragColor = float4(RGB, BaseTexel.w);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#version 450
|
||||
layout(location=0) out vec2 v_uv;
|
||||
void main() {
|
||||
vec2 positions[3] = vec2[3](vec2(-1.0,-1.0), vec2(3.0,-1.0), vec2(-1.0,3.0));
|
||||
vec2 uvs[3] = vec2[3](vec2(0.0, 1.0), vec2(2.0, 1.0), vec2(0.0,-1.0));
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
v_uv = uvs[gl_VertexIndex];
|
||||
}
|
||||
Binary file not shown.
@@ -1,63 +0,0 @@
|
||||
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||
#pragma clang diagnostic ignored "-Wmissing-braces"
|
||||
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
template<typename T, size_t Num>
|
||||
struct spvUnsafeArray
|
||||
{
|
||||
T elements[Num ? Num : 1];
|
||||
|
||||
thread T& operator [] (size_t pos) thread
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
constexpr const thread T& operator [] (size_t pos) const thread
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
|
||||
device T& operator [] (size_t pos) device
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
constexpr const device T& operator [] (size_t pos) const device
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
|
||||
constexpr const constant T& operator [] (size_t pos) const constant
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
|
||||
threadgroup T& operator [] (size_t pos) threadgroup
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
constexpr const threadgroup T& operator [] (size_t pos) const threadgroup
|
||||
{
|
||||
return elements[pos];
|
||||
}
|
||||
};
|
||||
|
||||
constant spvUnsafeArray<float2, 3> _18 = spvUnsafeArray<float2, 3>({ float2(-1.0), float2(3.0, -1.0), float2(-1.0, 3.0) });
|
||||
constant spvUnsafeArray<float2, 3> _26 = spvUnsafeArray<float2, 3>({ float2(0.0, 1.0), float2(2.0, 1.0), float2(0.0, -1.0) });
|
||||
|
||||
struct main0_out
|
||||
{
|
||||
float2 v_uv [[user(locn0)]];
|
||||
float4 gl_Position [[position]];
|
||||
};
|
||||
|
||||
vertex main0_out main0(uint gl_VertexIndex [[vertex_id]])
|
||||
{
|
||||
main0_out out = {};
|
||||
out.gl_Position = float4(_18[int(gl_VertexIndex)], 0.0, 1.0);
|
||||
out.v_uv = _26[int(gl_VertexIndex)];
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
name = ntsc-md-rainbows
|
||||
passes = 2
|
||||
|
||||
pass0_vert = pass0_encode.vert
|
||||
pass0_frag = pass0_encode.frag
|
||||
|
||||
pass1_vert = pass1_decode.vert
|
||||
pass1_frag = pass1_decode.frag
|
||||
|
||||
; NTSC parameters (mapped to NTSCParams uniform buffer)
|
||||
scantime = 47.9
|
||||
avalue = 0.0
|
||||
bvalue = 0.0
|
||||
ccvalue = 3.5795455
|
||||
notch_width = 3.45
|
||||
yfreqresponse = 1.75
|
||||
ifreqresponse = 1.75
|
||||
qfreqresponse = 1.45
|
||||
@@ -1,2 +1,2 @@
|
||||
// coffee.rc
|
||||
IDI_ICON1 ICON "icon.ico"
|
||||
IDI_ICON1 ICON "release/icons/icon.ico"
|
||||
|
||||
@@ -77,7 +77,7 @@ void BoidManager::activateBoids() {
|
||||
|
||||
// Mostrar notificación (solo si NO estamos en modo demo o logo)
|
||||
if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
ui_mgr_->showNotification("Modo Boids");
|
||||
ui_mgr_->showNotification("Modo boids");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ void BoidManager::deactivateBoids(bool force_gravity_on) {
|
||||
|
||||
// Mostrar notificación (solo si NO estamos en modo demo o logo)
|
||||
if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
ui_mgr_->showNotification("Modo Física");
|
||||
ui_mgr_->showNotification("Modo física");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ constexpr Uint64 NOTIFICATION_FADE_TIME = 200; // Duración animación salida
|
||||
constexpr float NOTIFICATION_BG_ALPHA = 0.7f; // Opacidad fondo semitransparente (0.0-1.0)
|
||||
constexpr int NOTIFICATION_PADDING = 10; // Padding interno del fondo (píxeles físicos)
|
||||
constexpr int NOTIFICATION_TOP_MARGIN = 20; // Margen superior desde borde pantalla (píxeles físicos)
|
||||
constexpr char KIOSK_NOTIFICATION_TEXT[] = "MODO KIOSKO";
|
||||
constexpr char KIOSK_NOTIFICATION_TEXT[] = "Modo kiosko";
|
||||
|
||||
// Configuración de pérdida aleatoria en rebotes
|
||||
constexpr float BASE_BOUNCE_COEFFICIENT = 0.75f; // Coeficiente base IGUAL para todas las pelotas
|
||||
|
||||
@@ -354,26 +354,6 @@ bool Engine::initialize(int width, int height, int zoom, bool fullscreen, AppMod
|
||||
delete[] tmp;
|
||||
}
|
||||
|
||||
// Inicializar ShaderManager (shaders externos desde data/shaders/)
|
||||
if (gpu_ctx_ && success) {
|
||||
shader_manager_ = std::make_unique<ShaderManager>();
|
||||
std::string shaders_dir = getResourcesDirectory() + "/data/shaders";
|
||||
shader_manager_->scan(shaders_dir);
|
||||
|
||||
// Si se especificó --shader, activar el preset inicial
|
||||
if (!initial_shader_name_.empty()) {
|
||||
active_shader_ = shader_manager_->load(
|
||||
gpu_ctx_->device(),
|
||||
initial_shader_name_,
|
||||
gpu_ctx_->swapchainFormat(),
|
||||
current_screen_width_, current_screen_height_);
|
||||
if (active_shader_) {
|
||||
const auto& names = shader_manager_->names();
|
||||
auto it = std::find(names.begin(), names.end(), initial_shader_name_);
|
||||
active_shader_idx_ = (it != names.end()) ? (int)(it - names.begin()) : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -408,7 +388,6 @@ void Engine::shutdown() {
|
||||
if (sprite_batch_) { sprite_batch_->destroy(gpu_ctx_->device()); sprite_batch_.reset(); }
|
||||
if (gpu_ball_buffer_) { gpu_ball_buffer_->destroy(gpu_ctx_->device()); gpu_ball_buffer_.reset(); }
|
||||
if (gpu_pipeline_) { gpu_pipeline_->destroy(gpu_ctx_->device()); gpu_pipeline_.reset(); }
|
||||
if (shader_manager_) { shader_manager_->destroyAll(gpu_ctx_->device()); shader_manager_.reset(); }
|
||||
}
|
||||
|
||||
// Destroy software UI renderer and surface
|
||||
@@ -493,20 +472,20 @@ void Engine::handleGravityToggle() {
|
||||
toggleBoidsMode(false); // Cambiar a PHYSICS sin activar gravedad (preserva inercia)
|
||||
// NO llamar a forceBallsGravityOff() porque aplica impulsos que destruyen la inercia de BOIDS
|
||||
// La gravedad ya está desactivada por BoidManager::activateBoids() y se mantiene al salir
|
||||
showNotificationForAction("Modo Física - Gravedad Off");
|
||||
showNotificationForAction("Modo física, gravedad off");
|
||||
return;
|
||||
}
|
||||
|
||||
// Si estamos en modo figura, salir a modo física SIN GRAVEDAD
|
||||
if (current_mode_ == SimulationMode::SHAPE) {
|
||||
toggleShapeModeInternal(false); // Desactivar figura sin forzar gravedad ON
|
||||
showNotificationForAction("Gravedad Off");
|
||||
showNotificationForAction("Gravedad off");
|
||||
} else {
|
||||
scene_manager_->switchBallsGravity(); // Toggle normal en modo física
|
||||
// Determinar estado actual de gravedad (gravity_force_ != 0.0f significa ON)
|
||||
const Ball* first_ball = scene_manager_->getFirstBall();
|
||||
bool gravity_on = (first_ball == nullptr) ? true : (first_ball->getGravityForce() != 0.0f);
|
||||
showNotificationForAction(gravity_on ? "Gravedad On" : "Gravedad Off");
|
||||
showNotificationForAction(gravity_on ? "Gravedad on" : "Gravedad off");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,9 +679,9 @@ void Engine::toggleDemoMode() {
|
||||
|
||||
// Mostrar notificación según el modo resultante
|
||||
if (new_mode == AppMode::SANDBOX && prev_mode != AppMode::SANDBOX) {
|
||||
showNotificationForAction("MODO SANDBOX");
|
||||
showNotificationForAction("Modo sandbox");
|
||||
} else if (new_mode == AppMode::DEMO && prev_mode != AppMode::DEMO) {
|
||||
showNotificationForAction("MODO DEMO");
|
||||
showNotificationForAction("Modo demo");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,9 +692,9 @@ void Engine::toggleDemoLiteMode() {
|
||||
|
||||
// Mostrar notificación según el modo resultante
|
||||
if (new_mode == AppMode::SANDBOX && prev_mode != AppMode::SANDBOX) {
|
||||
showNotificationForAction("MODO SANDBOX");
|
||||
showNotificationForAction("Modo sandbox");
|
||||
} else if (new_mode == AppMode::DEMO_LITE && prev_mode != AppMode::DEMO_LITE) {
|
||||
showNotificationForAction("MODO DEMO LITE");
|
||||
showNotificationForAction("Modo demo lite");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,9 +705,9 @@ void Engine::toggleLogoMode() {
|
||||
|
||||
// Mostrar notificación según el modo resultante
|
||||
if (new_mode == AppMode::SANDBOX && prev_mode != AppMode::SANDBOX) {
|
||||
showNotificationForAction("MODO SANDBOX");
|
||||
showNotificationForAction("Modo sandbox");
|
||||
} else if (new_mode == AppMode::LOGO && prev_mode != AppMode::LOGO) {
|
||||
showNotificationForAction("MODO LOGO");
|
||||
showNotificationForAction("Modo logo");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -903,75 +882,7 @@ void Engine::render() {
|
||||
SDL_SetGPUScissor(rp, &scissor);
|
||||
};
|
||||
|
||||
if (active_shader_ != nullptr) {
|
||||
// --- External multi-pass shader ---
|
||||
NTSCParams ntsc = {};
|
||||
ntsc.source_width = static_cast<float>(current_screen_width_);
|
||||
ntsc.source_height = static_cast<float>(current_screen_height_);
|
||||
ntsc.a_value = active_shader_->param("avalue", 0.0f);
|
||||
ntsc.b_value = active_shader_->param("bvalue", 0.0f);
|
||||
ntsc.cc_value = active_shader_->param("ccvalue", 3.5795455f);
|
||||
ntsc.scan_time = active_shader_->param("scantime", 47.9f);
|
||||
ntsc.notch_width = active_shader_->param("notch_width", 3.45f);
|
||||
ntsc.y_freq = active_shader_->param("yfreqresponse", 1.75f);
|
||||
ntsc.i_freq = active_shader_->param("ifreqresponse", 1.75f);
|
||||
ntsc.q_freq = active_shader_->param("qfreqresponse", 1.45f);
|
||||
|
||||
SDL_GPUTexture* current_input = offscreen_tex_->texture();
|
||||
SDL_GPUSampler* current_samp = offscreen_tex_->sampler();
|
||||
|
||||
for (int pi = 0; pi < active_shader_->passCount(); ++pi) {
|
||||
ShaderPass& sp = active_shader_->pass(pi);
|
||||
bool is_last = (pi == active_shader_->passCount() - 1);
|
||||
|
||||
SDL_GPUTexture* target_tex = is_last ? swapchain : sp.target->texture();
|
||||
SDL_GPULoadOp load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
|
||||
SDL_GPUColorTargetInfo ct = {};
|
||||
ct.texture = target_tex;
|
||||
ct.load_op = load_op;
|
||||
ct.clear_color = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
ct.store_op = SDL_GPU_STOREOP_STORE;
|
||||
|
||||
SDL_GPURenderPass* ext_pass = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
|
||||
if (is_last) applyViewport(ext_pass);
|
||||
|
||||
SDL_BindGPUGraphicsPipeline(ext_pass, sp.pipeline);
|
||||
SDL_GPUTextureSamplerBinding src_tsb = {current_input, current_samp};
|
||||
SDL_BindGPUFragmentSamplers(ext_pass, 0, &src_tsb, 1);
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &ntsc, sizeof(NTSCParams));
|
||||
SDL_DrawGPUPrimitives(ext_pass, 3, 1, 0, 0);
|
||||
|
||||
SDL_EndGPURenderPass(ext_pass);
|
||||
|
||||
if (!is_last) {
|
||||
current_input = sp.target->texture();
|
||||
current_samp = sp.target->sampler();
|
||||
}
|
||||
}
|
||||
|
||||
// Re-open swapchain pass for UI overlay
|
||||
SDL_GPUColorTargetInfo ct_ui = {};
|
||||
ct_ui.texture = swapchain;
|
||||
ct_ui.load_op = SDL_GPU_LOADOP_LOAD; // preserve shader output
|
||||
ct_ui.store_op = SDL_GPU_STOREOP_STORE;
|
||||
SDL_GPURenderPass* pass2 = SDL_BeginGPURenderPass(cmd, &ct_ui, 1, nullptr);
|
||||
applyViewport(pass2);
|
||||
|
||||
if (ui_tex_ && ui_tex_->isValid() && sprite_batch_->overlayIndexCount() > 0) {
|
||||
SDL_BindGPUGraphicsPipeline(pass2, gpu_pipeline_->spritePipeline());
|
||||
SDL_GPUBufferBinding vb = {sprite_batch_->vertexBuffer(), 0};
|
||||
SDL_GPUBufferBinding ib = {sprite_batch_->indexBuffer(), 0};
|
||||
SDL_BindGPUVertexBuffers(pass2, 0, &vb, 1);
|
||||
SDL_BindGPUIndexBuffer(pass2, &ib, SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
||||
SDL_GPUTextureSamplerBinding ui_tsb = {ui_tex_->texture(), ui_tex_->sampler()};
|
||||
SDL_BindGPUFragmentSamplers(pass2, 0, &ui_tsb, 1);
|
||||
SDL_DrawGPUIndexedPrimitives(pass2, sprite_batch_->overlayIndexCount(), 1,
|
||||
sprite_batch_->overlayIndexOffset(), 0, 0);
|
||||
}
|
||||
SDL_EndGPURenderPass(pass2);
|
||||
|
||||
} else {
|
||||
{
|
||||
// --- Native PostFX path ---
|
||||
SDL_GPUColorTargetInfo ct = {};
|
||||
ct.texture = swapchain;
|
||||
@@ -1003,7 +914,7 @@ void Engine::render() {
|
||||
}
|
||||
|
||||
SDL_EndGPURenderPass(pass2);
|
||||
} // end else (native PostFX)
|
||||
} // end native PostFX
|
||||
} // end if (swapchain && ...)
|
||||
|
||||
gpu_ctx_->submit(cmd);
|
||||
@@ -1141,10 +1052,10 @@ void Engine::toggleRealFullscreen() {
|
||||
|
||||
void Engine::applyPostFXPreset(int mode) {
|
||||
static constexpr float presets[4][3] = {
|
||||
{1.5f, 0.0f, 0.0f}, // 0: Vinyeta
|
||||
{1.5f, 0.0f, 0.8f}, // 1: Scanlines
|
||||
{1.5f, 1.0f, 0.0f}, // 2: Cromàtica
|
||||
{1.5f, 1.0f, 0.8f}, // 3: Complet
|
||||
{0.8f, 0.0f, 0.0f}, // 0: Vinyeta
|
||||
{0.8f, 0.0f, 0.8f}, // 1: Scanlines
|
||||
{0.8f, 0.2f, 0.0f}, // 2: Cromàtica
|
||||
{0.8f, 0.2f, 0.8f}, // 3: Complet
|
||||
};
|
||||
postfx_uniforms_.vignette_strength = presets[mode][0];
|
||||
postfx_uniforms_.chroma_strength = presets[mode][1];
|
||||
@@ -1155,25 +1066,14 @@ void Engine::applyPostFXPreset(int mode) {
|
||||
}
|
||||
|
||||
void Engine::handlePostFXCycle() {
|
||||
// Delegate to cycleShader() which handles both native PostFX and external shaders
|
||||
cycleShader();
|
||||
}
|
||||
|
||||
void Engine::handlePostFXToggle() {
|
||||
static constexpr const char* names[4] = {
|
||||
"PostFX: Vinyeta", "PostFX: Scanlines",
|
||||
"PostFX: Cromàtica", "PostFX: Complet"
|
||||
"PostFX viñeta", "PostFX scanlines",
|
||||
"PostFX cromática", "PostFX completo"
|
||||
};
|
||||
// If external shader is active, toggle it off
|
||||
if (active_shader_) {
|
||||
active_shader_ = nullptr;
|
||||
active_shader_idx_ = 0; // reset to OFF
|
||||
postfx_uniforms_.vignette_strength = 0.0f;
|
||||
postfx_uniforms_.chroma_strength = 0.0f;
|
||||
postfx_uniforms_.scanline_strength = 0.0f;
|
||||
showNotificationForAction("PostFX: Desactivat");
|
||||
return;
|
||||
}
|
||||
postfx_enabled_ = !postfx_enabled_;
|
||||
if (postfx_enabled_) {
|
||||
applyPostFXPreset(postfx_effect_mode_);
|
||||
@@ -1182,7 +1082,7 @@ void Engine::handlePostFXToggle() {
|
||||
postfx_uniforms_.vignette_strength = 0.0f;
|
||||
postfx_uniforms_.chroma_strength = 0.0f;
|
||||
postfx_uniforms_.scanline_strength = 0.0f;
|
||||
showNotificationForAction("PostFX: Desactivat");
|
||||
showNotificationForAction("PostFX desactivado");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1201,81 +1101,19 @@ void Engine::setPostFXParamOverrides(float vignette, float chroma) {
|
||||
if (chroma >= 0.f) postfx_uniforms_.chroma_strength = chroma;
|
||||
}
|
||||
|
||||
void Engine::setInitialShader(const std::string& name) {
|
||||
initial_shader_name_ = name;
|
||||
}
|
||||
|
||||
void Engine::cycleShader() {
|
||||
// Cycle order:
|
||||
// native OFF → native Vinyeta → Scanlines → Cromàtica → Complet →
|
||||
// ext shader 0 → ext shader 1 → ... → native OFF → ...
|
||||
if (!shader_manager_) {
|
||||
// No shader manager: fall back to native PostFX cycle
|
||||
handlePostFXCycle();
|
||||
return;
|
||||
}
|
||||
// X no hace nada si PostFX está desactivado
|
||||
if (!postfx_enabled_) return;
|
||||
|
||||
// active_shader_idx_ is a 0-based cycle counter:
|
||||
// -1 = uninitialized (first press → index 0 = OFF)
|
||||
// 0 = OFF
|
||||
// 1 = PostFX Vinyeta, 2 = Scanlines, 3 = Cromàtica, 4 = Complet
|
||||
// 5..4+num_ext = external shaders (0-based into names())
|
||||
const int num_native = 5; // 0=OFF, 1..4=PostFX modes
|
||||
const int num_ext = static_cast<int>(shader_manager_->names().size());
|
||||
const int total = num_native + num_ext;
|
||||
// Cicla solo entre los 4 modos (sin OFF)
|
||||
postfx_effect_mode_ = (postfx_effect_mode_ + 1) % 4;
|
||||
applyPostFXPreset(postfx_effect_mode_);
|
||||
|
||||
static const char* native_names[5] = {
|
||||
"PostFX: Desactivat", "PostFX: Vinyeta", "PostFX: Scanlines",
|
||||
"PostFX: Cromàtica", "PostFX: Complet"
|
||||
static constexpr const char* names[4] = {
|
||||
"PostFX viñeta", "PostFX scanlines",
|
||||
"PostFX cromática", "PostFX completo"
|
||||
};
|
||||
|
||||
// Advance and wrap
|
||||
int cycle = active_shader_idx_ + 1;
|
||||
if (cycle < 0 || cycle >= total) cycle = 0;
|
||||
active_shader_idx_ = cycle;
|
||||
|
||||
if (cycle < num_native) {
|
||||
// Native PostFX
|
||||
active_shader_ = nullptr;
|
||||
if (cycle == 0) {
|
||||
postfx_enabled_ = false;
|
||||
postfx_uniforms_.vignette_strength = 0.0f;
|
||||
postfx_uniforms_.chroma_strength = 0.0f;
|
||||
postfx_uniforms_.scanline_strength = 0.0f;
|
||||
} else {
|
||||
postfx_enabled_ = true;
|
||||
postfx_effect_mode_ = cycle - 1; // 0..3
|
||||
applyPostFXPreset(postfx_effect_mode_);
|
||||
}
|
||||
showNotificationForAction(native_names[cycle]);
|
||||
} else {
|
||||
// External shader
|
||||
int ext_idx = cycle - num_native;
|
||||
const std::string& shader_name = shader_manager_->names()[ext_idx];
|
||||
GpuShaderPreset* preset = shader_manager_->load(
|
||||
gpu_ctx_->device(),
|
||||
shader_name,
|
||||
gpu_ctx_->swapchainFormat(),
|
||||
current_screen_width_, current_screen_height_);
|
||||
if (preset) {
|
||||
active_shader_ = preset;
|
||||
postfx_enabled_ = false;
|
||||
postfx_uniforms_.vignette_strength = 0.0f;
|
||||
postfx_uniforms_.chroma_strength = 0.0f;
|
||||
postfx_uniforms_.scanline_strength = 0.0f;
|
||||
showNotificationForAction("Shader: " + shader_name);
|
||||
} else {
|
||||
// Failed to load: skip to next
|
||||
SDL_Log("Engine::cycleShader: failed to load '%s', skipping", shader_name.c_str());
|
||||
active_shader_ = nullptr;
|
||||
showNotificationForAction("Shader: ERROR " + shader_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Engine::getActiveShaderName() const {
|
||||
if (active_shader_) return active_shader_->name();
|
||||
return {};
|
||||
showNotificationForAction(names[postfx_effect_mode_]);
|
||||
}
|
||||
|
||||
void Engine::toggleIntegerScaling() {
|
||||
@@ -1292,13 +1130,13 @@ void Engine::toggleIntegerScaling() {
|
||||
break;
|
||||
}
|
||||
|
||||
const char* mode_name = "INTEGER";
|
||||
const char* mode_name = "entero";
|
||||
switch (current_scaling_mode_) {
|
||||
case ScalingMode::INTEGER: mode_name = "INTEGER"; break;
|
||||
case ScalingMode::LETTERBOX: mode_name = "LETTERBOX"; break;
|
||||
case ScalingMode::STRETCH: mode_name = "STRETCH"; break;
|
||||
case ScalingMode::INTEGER: mode_name = "entero"; break;
|
||||
case ScalingMode::LETTERBOX: mode_name = "letterbox"; break;
|
||||
case ScalingMode::STRETCH: mode_name = "stretch"; break;
|
||||
}
|
||||
showNotificationForAction(std::string("Escalado: ") + mode_name);
|
||||
showNotificationForAction(std::string("Escalado ") + mode_name);
|
||||
}
|
||||
|
||||
void Engine::addSpriteToBatch(float x, float y, float w, float h, int r, int g, int b, float scale) {
|
||||
@@ -1462,8 +1300,8 @@ void Engine::switchTextureInternal(bool show_notification) {
|
||||
// Mostrar notificación con el nombre de la textura (solo si se solicita)
|
||||
if (show_notification) {
|
||||
std::string texture_name = texture_names_[current_texture_index_];
|
||||
std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::toupper);
|
||||
showNotificationForAction("Sprite: " + texture_name);
|
||||
std::transform(texture_name.begin(), texture_name.end(), texture_name.begin(), ::tolower);
|
||||
showNotificationForAction("Textura " + texture_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1621,11 +1459,6 @@ void Engine::recreateOffscreenTexture() {
|
||||
base_screen_width_, base_screen_height_); // logical (font size based on base)
|
||||
}
|
||||
|
||||
// Recreate external shader intermediate targets
|
||||
if (shader_manager_) {
|
||||
shader_manager_->onResize(gpu_ctx_->device(), gpu_ctx_->swapchainFormat(),
|
||||
current_screen_width_, current_screen_height_);
|
||||
}
|
||||
if (ui_renderer_ && app_logo_) {
|
||||
app_logo_->initialize(ui_renderer_, current_screen_width_, current_screen_height_);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,8 @@
|
||||
#include "gpu/gpu_ball_buffer.hpp" // for GpuBallBuffer, BallGPUData
|
||||
#include "gpu/gpu_context.hpp" // for GpuContext
|
||||
#include "gpu/gpu_pipeline.hpp" // for GpuPipeline
|
||||
#include "gpu/gpu_shader_preset.hpp" // for NTSCParams, GpuShaderPreset
|
||||
#include "gpu/gpu_sprite_batch.hpp" // for GpuSpriteBatch
|
||||
#include "gpu/gpu_texture.hpp" // for GpuTexture
|
||||
#include "gpu/shader_manager.hpp" // for ShaderManager
|
||||
#include "input/input_handler.hpp" // for InputHandler
|
||||
#include "scene/scene_manager.hpp" // for SceneManager
|
||||
#include "shapes_mgr/shape_manager.hpp" // for ShapeManager
|
||||
@@ -84,10 +82,8 @@ class Engine {
|
||||
void setInitialPostFX(int mode);
|
||||
void setPostFXParamOverrides(float vignette, float chroma);
|
||||
|
||||
// External shader presets (loaded from data/shaders/)
|
||||
// Cicle PostFX nadiu (OFF → Vinyeta → Scanlines → Cromàtica → Complet)
|
||||
void cycleShader();
|
||||
void setInitialShader(const std::string& name);
|
||||
std::string getActiveShaderName() const;
|
||||
|
||||
// Modo kiosko
|
||||
void setKioskMode(bool enabled) { kiosk_mode_ = enabled; }
|
||||
@@ -137,7 +133,6 @@ class Engine {
|
||||
float getPostFXVignette() const { return postfx_uniforms_.vignette_strength; }
|
||||
float getPostFXChroma() const { return postfx_uniforms_.chroma_strength; }
|
||||
float getPostFXScanline() const { return postfx_uniforms_.scanline_strength; }
|
||||
bool isExternalShaderActive() const { return active_shader_ != nullptr; }
|
||||
|
||||
private:
|
||||
// === Componentes del sistema (Composición) ===
|
||||
@@ -187,17 +182,11 @@ class Engine {
|
||||
|
||||
// PostFX uniforms (passed to GPU each frame)
|
||||
PostFXUniforms postfx_uniforms_ = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
int postfx_effect_mode_ = 0;
|
||||
int postfx_effect_mode_ = 3;
|
||||
bool postfx_enabled_ = false;
|
||||
float postfx_override_vignette_ = -1.f; // -1 = sin override
|
||||
float postfx_override_chroma_ = -1.f;
|
||||
|
||||
// External shader system
|
||||
std::unique_ptr<ShaderManager> shader_manager_;
|
||||
GpuShaderPreset* active_shader_ = nullptr; // null = native PostFX
|
||||
int active_shader_idx_ = -1; // index into shader_manager_->names()
|
||||
std::string initial_shader_name_; // set before initialize()
|
||||
|
||||
// Sistema de zoom dinámico
|
||||
int current_window_zoom_ = DEFAULT_WINDOW_ZOOM;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "ball_vert_spv.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
// ============================================================================
|
||||
// MSL Shaders (Metal Shading Language, macOS)
|
||||
// ============================================================================
|
||||
@@ -197,6 +198,7 @@ vertex BallVOut ball_instanced_vs(BallInstance inst [[stage_in]],
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
#endif // __APPLE__
|
||||
|
||||
// ============================================================================
|
||||
// GpuPipeline implementation
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
// MSL binding: constant PostFXUniforms& u [[buffer(0)]]
|
||||
// ============================================================================
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength; // 0 = none, 1.5 = default subtle
|
||||
float chroma_strength; // 0 = off, 1 = full chromatic aberration
|
||||
float vignette_strength; // 0 = none, 0.8 = default subtle
|
||||
float chroma_strength; // 0 = off, 0.2 = default chromatic aberration
|
||||
float scanline_strength; // 0 = off, 1 = full scanlines
|
||||
float time; // accumulated seconds (for future animations)
|
||||
};
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
#include "gpu_shader_preset.hpp"
|
||||
#include "gpu_texture.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
// ============================================================================
|
||||
// Helpers
|
||||
// ============================================================================
|
||||
|
||||
static std::vector<uint8_t> readFile(const std::string& path) {
|
||||
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
||||
if (!f) return {};
|
||||
std::streamsize sz = f.tellg();
|
||||
f.seekg(0, std::ios::beg);
|
||||
std::vector<uint8_t> buf(static_cast<size_t>(sz));
|
||||
if (!f.read(reinterpret_cast<char*>(buf.data()), sz)) return {};
|
||||
return buf;
|
||||
}
|
||||
|
||||
static std::string trim(const std::string& s) {
|
||||
size_t a = s.find_first_not_of(" \t\r\n");
|
||||
if (a == std::string::npos) return {};
|
||||
size_t b = s.find_last_not_of(" \t\r\n");
|
||||
return s.substr(a, b - a + 1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GpuShaderPreset
|
||||
// ============================================================================
|
||||
|
||||
bool GpuShaderPreset::parseIni(const std::string& ini_path) {
|
||||
std::ifstream f(ini_path);
|
||||
if (!f) {
|
||||
SDL_Log("GpuShaderPreset: cannot open %s", ini_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
int num_passes = 0;
|
||||
std::string line;
|
||||
while (std::getline(f, line)) {
|
||||
// Strip comments
|
||||
auto comment = line.find(';');
|
||||
if (comment != std::string::npos) line = line.substr(0, comment);
|
||||
line = trim(line);
|
||||
if (line.empty()) continue;
|
||||
|
||||
auto eq = line.find('=');
|
||||
if (eq == std::string::npos) continue;
|
||||
|
||||
std::string key = trim(line.substr(0, eq));
|
||||
std::string value = trim(line.substr(eq + 1));
|
||||
if (key.empty() || value.empty()) continue;
|
||||
|
||||
if (key == "name") {
|
||||
name_ = value;
|
||||
} else if (key == "passes") {
|
||||
num_passes = std::stoi(value);
|
||||
} else {
|
||||
// Try to parse as float parameter
|
||||
try {
|
||||
params_[key] = std::stof(value);
|
||||
} catch (...) {
|
||||
// Non-float values stored separately (pass0_vert etc.)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (num_passes <= 0) {
|
||||
SDL_Log("GpuShaderPreset: no passes defined in %s", ini_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Second pass: read per-pass file names
|
||||
f.clear();
|
||||
f.seekg(0, std::ios::beg);
|
||||
descs_.resize(num_passes);
|
||||
while (std::getline(f, line)) {
|
||||
auto comment = line.find(';');
|
||||
if (comment != std::string::npos) line = line.substr(0, comment);
|
||||
line = trim(line);
|
||||
if (line.empty()) continue;
|
||||
auto eq = line.find('=');
|
||||
if (eq == std::string::npos) continue;
|
||||
std::string key = trim(line.substr(0, eq));
|
||||
std::string value = trim(line.substr(eq + 1));
|
||||
|
||||
for (int i = 0; i < num_passes; ++i) {
|
||||
std::string vi = "pass" + std::to_string(i) + "_vert";
|
||||
std::string fi = "pass" + std::to_string(i) + "_frag";
|
||||
if (key == vi) descs_[i].vert_name = value;
|
||||
if (key == fi) descs_[i].frag_name = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate
|
||||
for (int i = 0; i < num_passes; ++i) {
|
||||
if (descs_[i].vert_name.empty() || descs_[i].frag_name.empty()) {
|
||||
SDL_Log("GpuShaderPreset: pass %d missing vert or frag in %s", i, ini_path.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_GPUGraphicsPipeline* GpuShaderPreset::buildPassPipeline(SDL_GPUDevice* device,
|
||||
const std::string& vert_spv_path,
|
||||
const std::string& frag_spv_path,
|
||||
SDL_GPUTextureFormat target_fmt) {
|
||||
#ifdef __APPLE__
|
||||
constexpr SDL_GPUShaderFormat kFmt = SDL_GPU_SHADERFORMAT_MSL;
|
||||
constexpr const char* kEntry = "main0";
|
||||
#else
|
||||
constexpr SDL_GPUShaderFormat kFmt = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||
constexpr const char* kEntry = "main";
|
||||
#endif
|
||||
|
||||
auto vert_spv = readFile(vert_spv_path);
|
||||
auto frag_spv = readFile(frag_spv_path);
|
||||
if (vert_spv.empty()) {
|
||||
SDL_Log("GpuShaderPreset: cannot read %s", vert_spv_path.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
if (frag_spv.empty()) {
|
||||
SDL_Log("GpuShaderPreset: cannot read %s", frag_spv_path.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
vert_spv.push_back(0);
|
||||
frag_spv.push_back(0);
|
||||
#endif
|
||||
|
||||
SDL_GPUShaderCreateInfo vert_info = {};
|
||||
vert_info.code = vert_spv.data();
|
||||
vert_info.code_size = vert_spv.size();
|
||||
vert_info.entrypoint = kEntry;
|
||||
vert_info.format = kFmt;
|
||||
vert_info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
|
||||
vert_info.num_samplers = 0;
|
||||
vert_info.num_uniform_buffers = 0;
|
||||
|
||||
SDL_GPUShaderCreateInfo frag_info = {};
|
||||
frag_info.code = frag_spv.data();
|
||||
frag_info.code_size = frag_spv.size();
|
||||
frag_info.entrypoint = kEntry;
|
||||
frag_info.format = kFmt;
|
||||
frag_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
|
||||
frag_info.num_samplers = 1;
|
||||
frag_info.num_uniform_buffers = 1;
|
||||
|
||||
SDL_GPUShader* vert_shader = SDL_CreateGPUShader(device, &vert_info);
|
||||
SDL_GPUShader* frag_shader = SDL_CreateGPUShader(device, &frag_info);
|
||||
|
||||
if (!vert_shader || !frag_shader) {
|
||||
SDL_Log("GpuShaderPreset: shader creation failed for %s / %s: %s",
|
||||
vert_spv_path.c_str(), frag_spv_path.c_str(), SDL_GetError());
|
||||
if (vert_shader) SDL_ReleaseGPUShader(device, vert_shader);
|
||||
if (frag_shader) SDL_ReleaseGPUShader(device, frag_shader);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Full-screen triangle: no vertex input, no blend
|
||||
SDL_GPUColorTargetBlendState no_blend = {};
|
||||
no_blend.enable_blend = false;
|
||||
no_blend.enable_color_write_mask = false;
|
||||
|
||||
SDL_GPUColorTargetDescription ct_desc = {};
|
||||
ct_desc.format = target_fmt;
|
||||
ct_desc.blend_state = no_blend;
|
||||
|
||||
SDL_GPUVertexInputState no_input = {};
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo pipe_info = {};
|
||||
pipe_info.vertex_shader = vert_shader;
|
||||
pipe_info.fragment_shader = frag_shader;
|
||||
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 = &ct_desc;
|
||||
|
||||
SDL_GPUGraphicsPipeline* pipeline = SDL_CreateGPUGraphicsPipeline(device, &pipe_info);
|
||||
|
||||
SDL_ReleaseGPUShader(device, vert_shader);
|
||||
SDL_ReleaseGPUShader(device, frag_shader);
|
||||
|
||||
if (!pipeline)
|
||||
SDL_Log("GpuShaderPreset: pipeline creation failed: %s", SDL_GetError());
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
bool GpuShaderPreset::load(SDL_GPUDevice* device,
|
||||
const std::string& dir,
|
||||
SDL_GPUTextureFormat swapchain_fmt,
|
||||
int w, int h) {
|
||||
dir_ = dir;
|
||||
swapchain_fmt_ = swapchain_fmt;
|
||||
|
||||
// Parse ini
|
||||
if (!parseIni(dir + "/preset.ini"))
|
||||
return false;
|
||||
|
||||
int n = static_cast<int>(descs_.size());
|
||||
passes_.resize(n);
|
||||
|
||||
// Intermediate render target format (signed float to handle NTSC signal range)
|
||||
SDL_GPUTextureFormat inter_fmt = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT;
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
bool is_last = (i == n - 1);
|
||||
SDL_GPUTextureFormat target_fmt = is_last ? swapchain_fmt : inter_fmt;
|
||||
|
||||
#ifdef __APPLE__
|
||||
const char* ext = ".spv.msl";
|
||||
#else
|
||||
const char* ext = ".spv";
|
||||
#endif
|
||||
std::string vert_spv = dir + "/" + descs_[i].vert_name + ext;
|
||||
std::string frag_spv = dir + "/" + descs_[i].frag_name + ext;
|
||||
|
||||
passes_[i].pipeline = buildPassPipeline(device, vert_spv, frag_spv, target_fmt);
|
||||
if (!passes_[i].pipeline) {
|
||||
SDL_Log("GpuShaderPreset: failed to build pipeline for pass %d", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_last) {
|
||||
// Create intermediate render target
|
||||
auto tex = std::make_unique<GpuTexture>();
|
||||
if (!tex->createRenderTarget(device, w, h, inter_fmt)) {
|
||||
SDL_Log("GpuShaderPreset: failed to create intermediate target for pass %d", i);
|
||||
return false;
|
||||
}
|
||||
passes_[i].target = tex.get();
|
||||
targets_.push_back(std::move(tex));
|
||||
}
|
||||
// Last pass: target = null (caller binds swapchain)
|
||||
}
|
||||
|
||||
SDL_Log("GpuShaderPreset: loaded '%s' (%d passes)", name_.c_str(), n);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpuShaderPreset::recreateTargets(SDL_GPUDevice* device, int w, int h) {
|
||||
SDL_GPUTextureFormat inter_fmt = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT;
|
||||
for (auto& tex : targets_) {
|
||||
tex->destroy(device);
|
||||
tex->createRenderTarget(device, w, h, inter_fmt);
|
||||
}
|
||||
}
|
||||
|
||||
void GpuShaderPreset::destroy(SDL_GPUDevice* device) {
|
||||
for (auto& pass : passes_) {
|
||||
if (pass.pipeline) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device, pass.pipeline);
|
||||
pass.pipeline = nullptr;
|
||||
}
|
||||
}
|
||||
for (auto& tex : targets_) {
|
||||
if (tex) tex->destroy(device);
|
||||
}
|
||||
targets_.clear();
|
||||
passes_.clear();
|
||||
descs_.clear();
|
||||
params_.clear();
|
||||
}
|
||||
|
||||
float GpuShaderPreset::param(const std::string& key, float default_val) const {
|
||||
auto it = params_.find(key);
|
||||
return (it != params_.end()) ? it->second : default_val;
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "gpu_texture.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// NTSCParams — uniform buffer for NTSC shader passes (set=3, binding=0)
|
||||
// Matches the layout in pass0_encode.frag and pass1_decode.frag.
|
||||
// Pushed via SDL_PushGPUFragmentUniformData(cmd, 0, &ntsc, sizeof(NTSCParams)).
|
||||
// ============================================================================
|
||||
struct NTSCParams {
|
||||
float source_width;
|
||||
float source_height;
|
||||
float a_value;
|
||||
float b_value;
|
||||
float cc_value;
|
||||
float scan_time;
|
||||
float notch_width;
|
||||
float y_freq;
|
||||
float i_freq;
|
||||
float q_freq;
|
||||
float _pad[2];
|
||||
};
|
||||
static_assert(sizeof(NTSCParams) == 48, "NTSCParams must be 48 bytes");
|
||||
|
||||
// ============================================================================
|
||||
// ShaderPass — one render pass in a multi-pass shader preset
|
||||
// ============================================================================
|
||||
struct ShaderPass {
|
||||
SDL_GPUGraphicsPipeline* pipeline = nullptr;
|
||||
GpuTexture* target = nullptr; // null = swapchain (last pass)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// GpuShaderPreset — loads and owns a multi-pass shader preset from disk.
|
||||
//
|
||||
// Directory layout:
|
||||
// <dir>/preset.ini — descriptor
|
||||
// <dir>/pass0_xxx.vert — GLSL 4.50 vertex shader source
|
||||
// <dir>/pass0_xxx.frag — GLSL 4.50 fragment shader source
|
||||
// <dir>/pass0_xxx.vert.spv — compiled SPIRV (by CMake/glslc at build time)
|
||||
// <dir>/pass0_xxx.frag.spv — compiled SPIRV
|
||||
// ...
|
||||
// ============================================================================
|
||||
class GpuShaderPreset {
|
||||
public:
|
||||
// Load preset from directory. swapchain_fmt is the target format for the
|
||||
// last pass; intermediate passes use R16G16B16A16_FLOAT.
|
||||
bool load(SDL_GPUDevice* device,
|
||||
const std::string& dir,
|
||||
SDL_GPUTextureFormat swapchain_fmt,
|
||||
int w, int h);
|
||||
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
// Recreate intermediate render targets on resolution change.
|
||||
void recreateTargets(SDL_GPUDevice* device, int w, int h);
|
||||
|
||||
int passCount() const { return static_cast<int>(passes_.size()); }
|
||||
ShaderPass& pass(int i) { return passes_[i]; }
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
|
||||
// Read a float parameter parsed from preset.ini (returns default_val if absent).
|
||||
float param(const std::string& key, float default_val) const;
|
||||
|
||||
private:
|
||||
std::vector<ShaderPass> passes_;
|
||||
std::vector<std::unique_ptr<GpuTexture>> targets_; // intermediate render targets
|
||||
std::string name_;
|
||||
std::string dir_;
|
||||
std::unordered_map<std::string, float> params_;
|
||||
SDL_GPUTextureFormat swapchain_fmt_ = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
// Entries read from preset.ini for each pass
|
||||
struct PassDesc {
|
||||
std::string vert_name; // e.g. "pass0_encode.vert"
|
||||
std::string frag_name; // e.g. "pass0_encode.frag"
|
||||
};
|
||||
std::vector<PassDesc> descs_;
|
||||
|
||||
bool parseIni(const std::string& ini_path);
|
||||
|
||||
// Build a full-screen-triangle pipeline from two on-disk SPV files.
|
||||
SDL_GPUGraphicsPipeline* buildPassPipeline(SDL_GPUDevice* device,
|
||||
const std::string& vert_spv_path,
|
||||
const std::string& frag_spv_path,
|
||||
SDL_GPUTextureFormat target_fmt);
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
#include "shader_manager.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
void ShaderManager::scan(const std::string& root_dir) {
|
||||
root_dir_ = root_dir;
|
||||
names_.clear();
|
||||
dirs_.clear();
|
||||
|
||||
std::error_code ec;
|
||||
for (const auto& entry : fs::directory_iterator(root_dir, ec)) {
|
||||
if (!entry.is_directory()) continue;
|
||||
fs::path ini = entry.path() / "preset.ini";
|
||||
if (!fs::exists(ini)) continue;
|
||||
|
||||
std::string preset_name = entry.path().filename().string();
|
||||
names_.push_back(preset_name);
|
||||
dirs_[preset_name] = entry.path().string();
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
SDL_Log("ShaderManager: scan error on %s: %s", root_dir.c_str(), ec.message().c_str());
|
||||
}
|
||||
|
||||
std::sort(names_.begin(), names_.end());
|
||||
SDL_Log("ShaderManager: found %d preset(s) in %s", (int)names_.size(), root_dir.c_str());
|
||||
}
|
||||
|
||||
GpuShaderPreset* ShaderManager::load(SDL_GPUDevice* device,
|
||||
const std::string& name,
|
||||
SDL_GPUTextureFormat swapchain_fmt,
|
||||
int w, int h) {
|
||||
auto it = loaded_.find(name);
|
||||
if (it != loaded_.end()) return it->second.get();
|
||||
|
||||
auto dir_it = dirs_.find(name);
|
||||
if (dir_it == dirs_.end()) {
|
||||
SDL_Log("ShaderManager: preset '%s' not found", name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto preset = std::make_unique<GpuShaderPreset>();
|
||||
if (!preset->load(device, dir_it->second, swapchain_fmt, w, h)) {
|
||||
SDL_Log("ShaderManager: failed to load preset '%s'", name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GpuShaderPreset* raw = preset.get();
|
||||
loaded_[name] = std::move(preset);
|
||||
return raw;
|
||||
}
|
||||
|
||||
void ShaderManager::onResize(SDL_GPUDevice* device, SDL_GPUTextureFormat /*swapchain_fmt*/, int w, int h) {
|
||||
for (auto& [name, preset] : loaded_) {
|
||||
preset->recreateTargets(device, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderManager::destroyAll(SDL_GPUDevice* device) {
|
||||
for (auto& [name, preset] : loaded_) {
|
||||
preset->destroy(device);
|
||||
}
|
||||
loaded_.clear();
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gpu_shader_preset.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// ShaderManager — discovers and manages runtime shader presets under
|
||||
// a root directory (e.g., data/shaders/).
|
||||
//
|
||||
// Each subdirectory with a preset.ini is treated as a shader preset.
|
||||
// ============================================================================
|
||||
class ShaderManager {
|
||||
public:
|
||||
// Scan root_dir for preset subdirectories (each must contain preset.ini).
|
||||
void scan(const std::string& root_dir);
|
||||
|
||||
// Available preset names (e.g. {"ntsc-md-rainbows"}).
|
||||
const std::vector<std::string>& names() const { return names_; }
|
||||
|
||||
// Load and return a preset (cached). Returns null on failure.
|
||||
GpuShaderPreset* load(SDL_GPUDevice* device,
|
||||
const std::string& name,
|
||||
SDL_GPUTextureFormat swapchain_fmt,
|
||||
int w, int h);
|
||||
|
||||
// Recreate intermediate render targets on resolution change.
|
||||
void onResize(SDL_GPUDevice* device, SDL_GPUTextureFormat swapchain_fmt, int w, int h);
|
||||
|
||||
void destroyAll(SDL_GPUDevice* device);
|
||||
|
||||
private:
|
||||
std::string root_dir_;
|
||||
std::vector<std::string> names_;
|
||||
std::map<std::string, std::string> dirs_;
|
||||
std::map<std::string, std::unique_ptr<GpuShaderPreset>> loaded_;
|
||||
};
|
||||
@@ -38,19 +38,19 @@ bool InputHandler::processEvents(Engine& engine) {
|
||||
|
||||
// Controles de dirección de gravedad con teclas de cursor
|
||||
case SDLK_UP:
|
||||
engine.handleGravityDirectionChange(GravityDirection::UP, "Gravedad Arriba");
|
||||
engine.handleGravityDirectionChange(GravityDirection::UP, "Gravedad arriba");
|
||||
break;
|
||||
|
||||
case SDLK_DOWN:
|
||||
engine.handleGravityDirectionChange(GravityDirection::DOWN, "Gravedad Abajo");
|
||||
engine.handleGravityDirectionChange(GravityDirection::DOWN, "Gravedad abajo");
|
||||
break;
|
||||
|
||||
case SDLK_LEFT:
|
||||
engine.handleGravityDirectionChange(GravityDirection::LEFT, "Gravedad Izquierda");
|
||||
engine.handleGravityDirectionChange(GravityDirection::LEFT, "Gravedad izquierda");
|
||||
break;
|
||||
|
||||
case SDLK_RIGHT:
|
||||
engine.handleGravityDirectionChange(GravityDirection::RIGHT, "Gravedad Derecha");
|
||||
engine.handleGravityDirectionChange(GravityDirection::RIGHT, "Gravedad derecha");
|
||||
break;
|
||||
|
||||
case SDLK_V:
|
||||
@@ -193,40 +193,40 @@ bool InputHandler::processEvents(Engine& engine) {
|
||||
|
||||
// Cambio de número de pelotas (escenarios 1-8)
|
||||
case SDLK_1:
|
||||
engine.changeScenario(0, "10 Pelotas");
|
||||
engine.changeScenario(0, "10 pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_2:
|
||||
engine.changeScenario(1, "50 Pelotas");
|
||||
engine.changeScenario(1, "50 pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_3:
|
||||
engine.changeScenario(2, "100 Pelotas");
|
||||
engine.changeScenario(2, "100 pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_4:
|
||||
engine.changeScenario(3, "500 Pelotas");
|
||||
engine.changeScenario(3, "500 pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_5:
|
||||
engine.changeScenario(4, "1,000 Pelotas");
|
||||
engine.changeScenario(4, "1.000 pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_6:
|
||||
engine.changeScenario(5, "5,000 Pelotas");
|
||||
engine.changeScenario(5, "5.000 pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_7:
|
||||
engine.changeScenario(6, "10,000 Pelotas");
|
||||
engine.changeScenario(6, "10.000 pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_8:
|
||||
engine.changeScenario(7, "50,000 Pelotas");
|
||||
engine.changeScenario(7, "50.000 pelotas");
|
||||
break;
|
||||
|
||||
case SDLK_9:
|
||||
if (engine.isCustomScenarioEnabled()) {
|
||||
std::string custom_notif = std::to_string(engine.getCustomScenarioBalls()) + " Pelotas";
|
||||
std::string custom_notif = std::to_string(engine.getCustomScenarioBalls()) + " pelotas";
|
||||
engine.changeScenario(CUSTOM_SCENARIO_IDX, custom_notif.c_str());
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -24,7 +24,6 @@ void printHelp() {
|
||||
std::cout << " --postfx [efecto] Arrancar con PostFX activo (default: complet): vinyeta, scanlines, cromatica, complet\n";
|
||||
std::cout << " --vignette <float> Sobreescribir vignette_strength (activa PostFX si no hay --postfx)\n";
|
||||
std::cout << " --chroma <float> Sobreescribir chroma_strength (activa PostFX si no hay --postfx)\n";
|
||||
std::cout << " --shader <nombre> Arrancar con shader externo (ej: ntsc-md-rainbows)\n";
|
||||
std::cout << " --help Mostrar esta ayuda\n\n";
|
||||
std::cout << "Ejemplos:\n";
|
||||
std::cout << " vibe3_physics # 320x240 zoom 3 (ventana 960x720)\n";
|
||||
@@ -52,7 +51,6 @@ int main(int argc, char* argv[]) {
|
||||
int initial_postfx = -1;
|
||||
float override_vignette = -1.f;
|
||||
float override_chroma = -1.f;
|
||||
std::string initial_shader;
|
||||
AppMode initial_mode = AppMode::SANDBOX; // Modo inicial (default: SANDBOX)
|
||||
|
||||
// Parsear argumentos
|
||||
@@ -177,13 +175,6 @@ int main(int argc, char* argv[]) {
|
||||
std::cerr << "Error: --max-balls requiere un valor\n";
|
||||
return -1;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--shader") == 0) {
|
||||
if (i + 1 < argc) {
|
||||
initial_shader = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: --shader requiere un nombre de preset\n";
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Error: Opción desconocida '" << argv[i] << "'\n";
|
||||
printHelp();
|
||||
@@ -215,9 +206,6 @@ int main(int argc, char* argv[]) {
|
||||
engine.setPostFXParamOverrides(override_vignette, override_chroma);
|
||||
}
|
||||
|
||||
if (!initial_shader.empty())
|
||||
engine.setInitialShader(initial_shader);
|
||||
|
||||
if (!engine.initialize(width, height, zoom, fullscreen, initial_mode)) {
|
||||
std::cout << "¡Error al inicializar el engine!" << std::endl;
|
||||
return -1;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "shape_manager.hpp"
|
||||
|
||||
#include <algorithm> // for std::min, std::max
|
||||
#include <algorithm> // for std::min, std::max, std::transform
|
||||
#include <cctype> // for ::tolower
|
||||
#include <cstdlib> // for rand
|
||||
#include <string> // for std::string
|
||||
|
||||
@@ -93,7 +94,7 @@ void ShapeManager::toggleShapeMode(bool force_gravity_on_exit) {
|
||||
|
||||
// Mostrar notificación (solo si NO estamos en modo demo o logo)
|
||||
if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
ui_mgr_->showNotification("Modo Física");
|
||||
ui_mgr_->showNotification("Modo física");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +137,7 @@ void ShapeManager::toggleDepthZoom() {
|
||||
|
||||
// Mostrar notificación si está en modo SANDBOX
|
||||
if (ui_mgr_ && state_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
ui_mgr_->showNotification(depth_zoom_enabled_ ? "Profundidad On" : "Profundidad Off");
|
||||
ui_mgr_->showNotification(depth_zoom_enabled_ ? "Profundidad on" : "Profundidad off");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +278,9 @@ void ShapeManager::activateShapeInternal(ShapeType type) {
|
||||
|
||||
// Mostrar notificación con nombre de figura (solo si NO estamos en modo demo o logo)
|
||||
if (active_shape_ && state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
|
||||
std::string notification = std::string("Modo ") + active_shape_->getName();
|
||||
std::string shape_name = active_shape_->getName();
|
||||
std::transform(shape_name.begin(), shape_name.end(), shape_name.begin(), ::tolower);
|
||||
std::string notification = std::string("Modo ") + shape_name;
|
||||
ui_mgr_->showNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,3 +361,11 @@ int TextRenderer::getTextHeight() {
|
||||
|
||||
return TTF_GetFontHeight(font_);
|
||||
}
|
||||
|
||||
int TextRenderer::getGlyphHeight() {
|
||||
if (!isInitialized()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return TTF_GetFontAscent(font_) - TTF_GetFontDescent(font_);
|
||||
}
|
||||
|
||||
@@ -42,9 +42,12 @@ public:
|
||||
// Útil para notificaciones y elementos UI de tamaño fijo
|
||||
int getTextWidthPhysical(const char* text);
|
||||
|
||||
// Obtiene la altura de la fuente
|
||||
// Obtiene la altura de la fuente (incluye line_gap)
|
||||
int getTextHeight();
|
||||
|
||||
// Obtiene la altura real del glifo (ascender + |descendente|, sin line_gap)
|
||||
int getGlyphHeight();
|
||||
|
||||
// Verifica si está inicializado correctamente
|
||||
bool isInitialized() const { return font_ != nullptr && renderer_ != nullptr; }
|
||||
|
||||
|
||||
@@ -18,61 +18,64 @@ HelpOverlay::HelpOverlay()
|
||||
box_y_(0),
|
||||
column1_width_(0),
|
||||
column2_width_(0),
|
||||
column3_width_(0),
|
||||
cached_texture_(nullptr),
|
||||
last_category_color_({0, 0, 0, 255}),
|
||||
last_content_color_({0, 0, 0, 255}),
|
||||
last_bg_color_({0, 0, 0, 255}),
|
||||
texture_needs_rebuild_(true) {
|
||||
// Llenar lista de controles (organizados por categoría, equilibrado en 2 columnas)
|
||||
// Llenar lista de controles (organizados por categoría, equilibrado en 3 columnas)
|
||||
key_bindings_ = {
|
||||
// COLUMNA 1: SIMULACIÓN
|
||||
{"SIMULACIÓN", ""},
|
||||
{"1-8", "Escenarios (10 a 50,000 pelotas)"},
|
||||
{"F", "Toggle Física - Última Figura"},
|
||||
{"B", "Modo Boids (enjambre)"},
|
||||
{"ESPACIO", "Impulso contra gravedad"},
|
||||
{"G", "Toggle Gravedad ON/OFF"},
|
||||
{"CURSORES", "Dirección de gravedad"},
|
||||
{"1-8", "Escenarios (10 a 50.000 pelotas)"},
|
||||
{"F", "Cambia entre figura y física"},
|
||||
{"B", "Cambia entre boids y física"},
|
||||
{"ESPACIO", "Impulso contra la gravedad"},
|
||||
{"G", "Activar / Desactivar gravedad"},
|
||||
{"CURSORES", "Dirección de la gravedad"},
|
||||
{"", ""}, // Separador
|
||||
|
||||
// COLUMNA 1: FIGURAS 3D
|
||||
{"FIGURAS 3D", ""},
|
||||
{"Q/W/E/R", "Esfera/Lissajous/Hélice/Toroide"},
|
||||
{"T/Y/U/I", "Cubo/Cilindro/Icosaedro/Átomo"},
|
||||
{"O", "Forma PNG"},
|
||||
{"Q/W/E/R", "Esfera / Lissajous / Hélice / Toroide"},
|
||||
{"T/Y/U/I", "Cubo / Cilindro / Icosaedro / Átomo"},
|
||||
{"Num+/-", "Escalar figura"},
|
||||
{"Num*", "Reset escala"},
|
||||
{"Num/", "Toggle profundidad"},
|
||||
{"Num/", "Activar / Desactivar profundidad"},
|
||||
{"[new_col]", ""}, // CAMBIO DE COLUMNA -> COLUMNA 2
|
||||
|
||||
// COLUMNA 2: MODOS
|
||||
{"MODOS", ""},
|
||||
{"D", "Activar / Desactivar modo demo"},
|
||||
{"L", "Activar / Desactivar modo demo lite"},
|
||||
{"K", "Activar / Desactivar modo logo"},
|
||||
{"", ""}, // Separador
|
||||
|
||||
// COLUMNA 1: VISUAL
|
||||
// COLUMNA 2: VISUAL
|
||||
{"VISUAL", ""},
|
||||
{"C", "Tema siguiente"},
|
||||
{"Shift+C", "Tema anterior"},
|
||||
{"NumEnter", "Página de temas"},
|
||||
{"N", "Cambiar sprite"},
|
||||
{"[new_col]", ""}, // Separador -> CAMBIO DE COLUMNA
|
||||
|
||||
// COLUMNA 2: PANTALLA
|
||||
{"PANTALLA", ""},
|
||||
{"F1/F2", "Zoom out/in (ventana)"},
|
||||
{"F3", "Fullscreen letterbox"},
|
||||
{"F4", "Fullscreen real"},
|
||||
{"F5", "Escalado (F3 activo)"},
|
||||
{"V", "Toggle V-Sync"},
|
||||
{"", ""}, // Separador
|
||||
|
||||
// COLUMNA 2: MODOS
|
||||
{"MODOS", ""},
|
||||
{"D", "Modo DEMO"},
|
||||
{"Shift+D", "Pausar tema dinámico"},
|
||||
{"L", "Modo DEMO LITE"},
|
||||
{"K", "Modo LOGO (easter egg)"},
|
||||
{"N", "Cambiar tamaño de pelota"},
|
||||
{"X", "Ciclar presets PostFX"},
|
||||
{"[new_col]", ""}, // CAMBIO DE COLUMNA -> COLUMNA 3
|
||||
|
||||
// COLUMNA 3: PANTALLA
|
||||
{"PANTALLA", ""},
|
||||
{"F1", "Disminuye ventana"},
|
||||
{"F2", "Aumenta ventana"},
|
||||
{"F3", "Pantalla completa"},
|
||||
{"F4", "Pantalla completa real"},
|
||||
{"F5", "Activar / Desactivar PostFX"},
|
||||
{"F6", "Cambia el escalado de pantalla"},
|
||||
{"V", "Activar / Desactivar V-Sync"},
|
||||
{"", ""}, // Separador
|
||||
|
||||
// COLUMNA 2: DEBUG/AYUDA
|
||||
{"DEBUG/AYUDA", ""},
|
||||
{"F12", "Toggle info debug"},
|
||||
// COLUMNA 3: DEBUG/AYUDA
|
||||
{"DEBUG / AYUDA", ""},
|
||||
{"F12", "Activar / Desactivar info debug"},
|
||||
{"H", "Esta ayuda"},
|
||||
{"ESC", "Salir"}};
|
||||
}
|
||||
@@ -157,12 +160,13 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
|
||||
// Calcular ancho máximo por columna
|
||||
int max_col1_width = 0;
|
||||
int max_col2_width = 0;
|
||||
int max_col3_width = 0;
|
||||
int current_column = 0;
|
||||
|
||||
for (const auto& binding : key_bindings_) {
|
||||
// Cambio de columna
|
||||
if (strcmp(binding.key, "[new_col]") == 0) {
|
||||
current_column = 1;
|
||||
current_column++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -186,48 +190,51 @@ void HelpOverlay::calculateTextDimensions(int& max_width, int& total_height) {
|
||||
// Actualizar máximo de columna correspondiente
|
||||
if (current_column == 0) {
|
||||
max_col1_width = std::max(max_col1_width, line_width);
|
||||
} else {
|
||||
} else if (current_column == 1) {
|
||||
max_col2_width = std::max(max_col2_width, line_width);
|
||||
} else {
|
||||
max_col3_width = std::max(max_col3_width, line_width);
|
||||
}
|
||||
}
|
||||
|
||||
// Almacenar anchos de columnas en miembros para uso posterior
|
||||
column1_width_ = max_col1_width;
|
||||
column2_width_ = max_col2_width;
|
||||
column3_width_ = max_col3_width;
|
||||
|
||||
// Ancho total: 2 columnas + 3 paddings (izq, medio, der)
|
||||
max_width = max_col1_width + max_col2_width + padding * 3;
|
||||
// Gap entre columnas: doble del padding para dar más respiro
|
||||
int col_gap = padding * 2;
|
||||
|
||||
// Altura: contar líneas REALES en cada columna
|
||||
int col1_lines = 0;
|
||||
int col2_lines = 0;
|
||||
// Ancho total: 3 columnas + padding izq/der + 2 gaps entre columnas
|
||||
max_width = max_col1_width + max_col2_width + max_col3_width + padding * 2 + col_gap * 2;
|
||||
|
||||
// Calcular altura real simulando exactamente lo que hace el render
|
||||
int col_heights[3] = {0, 0, 0};
|
||||
current_column = 0;
|
||||
|
||||
for (const auto& binding : key_bindings_) {
|
||||
// Cambio de columna
|
||||
if (strcmp(binding.key, "[new_col]") == 0) {
|
||||
current_column = 1;
|
||||
current_column++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Separador vacío no cuenta como línea
|
||||
if (binding.key[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Contar línea (ya sea encabezado o contenido)
|
||||
if (current_column == 0) {
|
||||
col1_lines++;
|
||||
col_heights[current_column] += line_height; // separador vacío
|
||||
} else if (binding.description[0] == '\0') {
|
||||
col_heights[current_column] += line_height; // encabezado
|
||||
} else {
|
||||
col2_lines++;
|
||||
col_heights[current_column] += line_height; // línea normal
|
||||
}
|
||||
}
|
||||
|
||||
// Usar la columna más larga para calcular altura
|
||||
int max_column_lines = std::max(col1_lines, col2_lines);
|
||||
int content_height = std::max({col_heights[0], col_heights[1], col_heights[2]});
|
||||
|
||||
// Altura: título (2 líneas) + contenido + padding superior e inferior
|
||||
total_height = line_height * 2 + max_column_lines * line_height + padding * 2;
|
||||
// Eliminar el line_gap de la última línea: ese gap es espacio entre líneas,
|
||||
// pero la última línea no tiene siguiente, así que queda como padding muerto.
|
||||
int glyph_height = text_renderer_->getGlyphHeight();
|
||||
int visual_content_height = content_height - (line_height - glyph_height);
|
||||
|
||||
total_height = visual_content_height + padding * 2;
|
||||
}
|
||||
|
||||
void HelpOverlay::calculateBoxDimensions() {
|
||||
@@ -245,6 +252,7 @@ void HelpOverlay::calculateBoxDimensions() {
|
||||
// Centrar en pantalla
|
||||
box_x_ = (physical_width_ - box_width_) / 2;
|
||||
box_y_ = (physical_height_ - box_height_) / 2;
|
||||
|
||||
}
|
||||
|
||||
void HelpOverlay::rebuildCachedTexture() {
|
||||
@@ -334,56 +342,64 @@ void HelpOverlay::rebuildCachedTexture() {
|
||||
|
||||
// Configuración de espaciado
|
||||
int line_height = text_renderer_->getTextHeight();
|
||||
// Padding dinámico basado en altura física: 25px para >= 600px, escalado proporcionalmente para menores
|
||||
int padding = (physical_height_ >= 600) ? 25 : std::max(10, physical_height_ / 24);
|
||||
int col_gap = padding * 2;
|
||||
|
||||
int current_x = padding; // Coordenadas relativas a la textura (0,0)
|
||||
// Posición X de inicio de cada columna
|
||||
int col_start[3];
|
||||
col_start[0] = padding;
|
||||
col_start[1] = padding + column1_width_ + col_gap;
|
||||
col_start[2] = padding + column1_width_ + col_gap + column2_width_ + col_gap;
|
||||
|
||||
// Ancho de cada columna (para centrado interno)
|
||||
int col_width[3] = {column1_width_, column2_width_, column3_width_};
|
||||
|
||||
int glyph_height = text_renderer_->getGlyphHeight();
|
||||
int current_y = padding;
|
||||
int current_column = 0;
|
||||
|
||||
// Título principal
|
||||
const char* title = "CONTROLES - ViBe3 Physics";
|
||||
int title_width = text_renderer_->getTextWidthPhysical(title);
|
||||
text_renderer_->printAbsolute(box_width_ / 2 - title_width / 2, current_y, title, category_color);
|
||||
current_y += line_height * 2;
|
||||
|
||||
int content_start_y = current_y;
|
||||
|
||||
// Renderizar cada línea
|
||||
for (const auto& binding : key_bindings_) {
|
||||
if (strcmp(binding.key, "[new_col]") == 0 && binding.description[0] == '\0') {
|
||||
if (current_column == 0) {
|
||||
current_column = 1;
|
||||
current_x = padding + column1_width_ + padding; // Usar ancho real de columna 1
|
||||
if (current_column < 2) {
|
||||
current_column++;
|
||||
current_y = content_start_y;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// CHECK PADDING INFERIOR ANTES de escribir la línea (AMBAS COLUMNAS)
|
||||
// Verificar si la PRÓXIMA línea cabrá dentro del box con padding inferior
|
||||
if (current_y + line_height >= box_height_ - padding) {
|
||||
if (current_column == 0) {
|
||||
// Columna 0 llena: cambiar a columna 1
|
||||
current_column = 1;
|
||||
current_x = padding + column1_width_ + padding;
|
||||
current_y = content_start_y;
|
||||
} else {
|
||||
// Columna 1 llena: omitir resto de texto (no cabe)
|
||||
// Preferible omitir que sobresalir del overlay
|
||||
continue;
|
||||
}
|
||||
// CHECK PADDING INFERIOR ANTES de escribir la línea
|
||||
// Usamos glyph_height (no line_height) porque el gap después de la última línea no ocupa espacio visual
|
||||
if (current_y + glyph_height > box_height_ - padding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int cx = col_start[current_column];
|
||||
int cw = col_width[current_column];
|
||||
|
||||
if (binding.description[0] == '\0') {
|
||||
text_renderer_->printAbsolute(current_x, current_y, binding.key, category_color);
|
||||
current_y += line_height + 2;
|
||||
if (binding.key[0] == '\0') {
|
||||
// Separador vacío: avanzar una línea completa
|
||||
current_y += line_height;
|
||||
} else {
|
||||
// Encabezado de sección — centrado en la columna
|
||||
int w = text_renderer_->getTextWidthPhysical(binding.key);
|
||||
text_renderer_->printAbsolute(cx + (cw - w) / 2, current_y, binding.key, category_color);
|
||||
current_y += line_height;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
text_renderer_->printAbsolute(current_x, current_y, binding.key, content_color);
|
||||
// Par tecla+descripción — centrado como bloque en la columna
|
||||
int key_width = text_renderer_->getTextWidthPhysical(binding.key);
|
||||
text_renderer_->printAbsolute(current_x + key_width + 10, current_y, binding.description, content_color);
|
||||
int desc_width = text_renderer_->getTextWidthPhysical(binding.description);
|
||||
int total_width = key_width + 10 + desc_width;
|
||||
int line_x = cx + (cw - total_width) / 2;
|
||||
|
||||
text_renderer_->printAbsolute(line_x, current_y, binding.key, category_color);
|
||||
text_renderer_->printAbsolute(line_x + key_width + 10, current_y, binding.description, content_color);
|
||||
|
||||
current_y += line_height;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ class HelpOverlay {
|
||||
// Anchos individuales de cada columna (para evitar solapamiento)
|
||||
int column1_width_;
|
||||
int column2_width_;
|
||||
int column3_width_;
|
||||
|
||||
// Sistema de caché para optimización de rendimiento
|
||||
SDL_Texture* cached_texture_; // Textura cacheada del overlay completo
|
||||
|
||||
@@ -100,7 +100,7 @@ void UIManager::initialize(SDL_Renderer* renderer, ThemeManager* theme_manager,
|
||||
|
||||
// Crear y configurar sistema de ayuda (overlay)
|
||||
help_overlay_ = new HelpOverlay();
|
||||
help_overlay_->initialize(renderer, theme_manager_, physical_width, physical_height, current_font_size_);
|
||||
help_overlay_->initialize(renderer, theme_manager_, physical_width, physical_height, std::max(9, current_font_size_ - 1));
|
||||
|
||||
// Inicializar FPS counter
|
||||
fps_last_time_ = SDL_GetTicks();
|
||||
@@ -195,7 +195,7 @@ void UIManager::updatePhysicalWindowSize(int width, int height) {
|
||||
|
||||
// Actualizar help overlay con font size actual Y nuevas dimensiones (atómicamente)
|
||||
if (help_overlay_) {
|
||||
help_overlay_->updateAll(current_font_size_, width, height);
|
||||
help_overlay_->updateAll(std::max(9, current_font_size_ - 1), width, height);
|
||||
}
|
||||
|
||||
// Actualizar otros componentes de UI con nuevas dimensiones
|
||||
@@ -336,9 +336,7 @@ void UIManager::renderDebugHUD(const Engine* engine,
|
||||
lines.push_back(refresh_text);
|
||||
lines.push_back(theme_text);
|
||||
std::string postfx_text;
|
||||
if (engine->isExternalShaderActive()) {
|
||||
postfx_text = "Shader: " + engine->getActiveShaderName();
|
||||
} else if (!engine->isPostFXEnabled()) {
|
||||
if (!engine->isPostFXEnabled()) {
|
||||
postfx_text = "PostFX: OFF";
|
||||
} else {
|
||||
static constexpr const char* preset_names[4] = {
|
||||
|
||||
Reference in New Issue
Block a user