8 Commits

Author SHA1 Message Date
8d42d5741f Format window title with copyright prefix and backend driver 2026-05-04 11:55:34 +02:00
f18d6143d1 Add SDL3 GPU backend (Vulkan/Metal) with OpenGL fallback 2026-05-04 11:45:43 +02:00
0a03509323 corregit makefile per a macos 2026-05-04 09:12:19 +02:00
8f3d013c8e Remove voxel_descent shader, add cube_lines shader, update README
- Removed voxel_descent.frag.glsl (incompatible with OpenGL - multiple overflow issues)
- Added cube_lines.frag.glsl by Danil (raytraced refraction cube with self-intersections)
- Updated README.md:
  - Corrected executable name (shadertoy_sdl3 → shadertoy)
  - Added features section (FPS counter, VSync, metadata, feedback system)
  - Updated keyboard shortcuts (F3, F4, arrows)
  - Added Shadertoy compatibility guide
  - Updated usage examples with correct paths
  - Added shader conversion guide from Shadertoy

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 19:16:27 +01:00
9110d689a5 corregit makefile 2025-11-16 17:54:52 +01:00
b290cee689 corregit shader water. afegides metadades en els shaders 2025-11-16 16:04:29 +01:00
194726f823 Add self-feedback system and water shader
Features:
- Self-feedback rendering system for shaders with feedback loops
- Automatic FBO/texture management for feedback
- Metadata parser detects iChannel feedback configuration
- Adaptive render loop (with/without feedback)
- Water shader from Shadertoy (adapted and working)
- Fixed variable initialization issues in shader code

Technical details:
- FBO creation/destruction on shader switch
- Texture binding to iChannel0-3 based on metadata
- Auto-resize feedback buffers on window resize
- Cleanup on exit and shader switch

Files:
- src/main.cpp: Feedback system implementation
- shaders/water.glsl: Water shader with fixes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 15:45:18 +01:00
44de2c7013 Add FPS counter, VSync toggle, shader metadata system, and multi-pass infrastructure
- FPS counter in window title (updates every 500ms)
- F4 key toggles VSync on/off
- Shader metadata: Name and Author from comments
- iChannel metadata parsing for multi-pass support
- Base structures: ShaderBuffer, ShaderPass
- FBO/texture management functions
- Updated all 11 shaders with Name/Author metadata

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 15:22:06 +01:00
64 changed files with 9510 additions and 300 deletions

290
CLAUDE.md Normal file
View File

@@ -0,0 +1,290 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a cross-platform Shadertoy-like fragment shader viewer built with SDL3. It supports two rendering backends:
- **SDL3 GPU** (default): renders via Vulkan on Linux/Windows and Metal on macOS.
- **OpenGL 3.3** (fallback): the original backend, used automatically if SDL3 GPU init fails.
Selection at launch:
```bash
./shadertoy --backend=gpu # force SDL3 GPU
./shadertoy --backend=opengl # force OpenGL
./shadertoy --backend=auto # default: GPU first, fall back to OpenGL on failure
```
## Build and Development Commands
### Building (CMake - Development)
```bash
mkdir build && cd build
cmake ..
cmake --build . --config Release
```
### Building (Makefile - Release Packages)
```bash
make windows_release # Creates .zip with DLLs
make macos_release # Creates .dmg with app bundle
make linux_release # Creates .tar.gz
make show_version # Display build version (YYYY-MM-DD format)
```
Platform-specific debug builds:
```bash
make windows_debug
make macos_debug
make linux_debug
```
### Compiling Shaders to SPIR-V
The SDL3 GPU backend reads compiled SPIR-V (`.frag.spv`) at runtime. Run this after touching any `.vk.glsl` file:
```bash
cmake --build build --target compile_shaders
```
Requires `glslc` (Debian/Ubuntu: `apt install glslang-tools`; macOS: `brew install glslang`).
### Running the Application
```bash
./shadertoy [SHADER_NAME_OR_PATH] [-F|--fullscreen] [--backend=auto|gpu|opengl]
# Examples:
./shadertoy test # auto backend, shaders/test/
./shadertoy --backend=gpu seascape # force SDL3 GPU (Vulkan/Metal)
./shadertoy -F shaders/fractal_pyramid # explicit folder path, fullscreen
```
**Runtime Controls:**
- `ESC` - Exit
- `F3` - Toggle fullscreen
- `LEFT/RIGHT ARROW` - Cycle through shaders in directory
## Architecture
### Layered design
- `src/main.cpp` — SDL3 window, event loop, shader-list scanning, dispatch to a backend.
- `src/rendering/shader_backend.hpp` — abstract `IShaderBackend` interface + `ShaderMetadata` / `ShaderUniforms` / `ShaderProgramSpec` shared types. Two factories: `makeOpenGLBackend()`, `makeSdl3GpuBackend()`.
- `src/rendering/opengl_shader_backend.{hpp,cpp}` — OpenGL 3.3 implementation. Loads `<folder>/<name>.gl.glsl` at runtime.
- `src/rendering/sdl3gpu/sdl3gpu_shader_backend.{hpp,cpp}` — SDL3 GPU implementation. Loads `<folder>/<name>.frag.spv` (Linux/Windows) or `<folder>/<name>.frag.msl` (macOS); shares one passthrough vertex shader from `shaders/_common/passthrough.vert.{spv,msl}`.
- `src/rendering/sdl3gpu/shader_factory.hpp` — small helper that loads a shader binary/source from disk and creates an `SDL_GPUShader`.
### Shader folder layout
One folder per shader, under `shaders/`:
```
shaders/<name>/
<name>.gl.glsl # OpenGL GLSL 3.30 (handwritten)
<name>.vk.glsl # Vulkan GLSL 4.50 (handwritten)
<name>.frag.msl # Metal Shading Language (handwritten)
<name>.frag.spv # generated by `make compile_shaders` from .vk.glsl
meta.txt # Name / Author / iChannelN
shaders/_common/
passthrough.vk.glsl # shared fullscreen-triangle vertex (Vulkan)
passthrough.vert.spv # generated
passthrough.vert.msl # handwritten Metal
```
Folders starting with `_` or `.` are skipped by the scanner. The `meta.txt` parser is in `Rendering::parseMetaFile`.
### Backend selection logic (`main.cpp`)
1. Parse `--backend=...` flag (default `auto`).
2. If not OpenGL: create a window with no flags, instantiate `Sdl3GpuShaderBackend`. If `init()` fails AND choice was `auto`, destroy the window and continue. If choice was `gpu`, exit with error.
3. Otherwise: create a window with `SDL_WINDOW_OPENGL` and instantiate `OpenGLShaderBackend`.
### Global State (main.cpp)
```cpp
shader_list_ // Vector<ShaderEntry { folder, base_name }>
current_shader_index_ // Active shader in rotation
shader_start_ticks_ // Base time for iTime uniform calculation
window_ // SDL3 window pointer
backend_ // unique_ptr<IShaderBackend>
shaders_directory_ // Shaders root path (resolved at startup)
```
### Dependencies
- **SDL3** ≥ 3.2 — windowing, events, GPU API (`SDL_gpu.h`)
- **GLAD** — OpenGL 3.3 loader (used only by the OpenGL backend, statically linked via `third_party/glad/`)
- **C++17 stdlib** — filesystem, fstream, vector, algorithms
- Build-time tool: `glslc` (only required to regenerate `.frag.spv`)
### Platform-Specific Code
Uses preprocessor defines (`WINDOWS_BUILD`, `MACOS_BUILD`, `LINUX_BUILD`) for:
- `getExecutableDirectory()` - Windows API, mach-o dyld, or /proc/self/exe
- `getResourcesDirectory()` - Special macOS app bundle handling (Contents/Resources)
- Linking flags - Windows uses static linking, macOS links OpenGL framework
## Shader System
### Authoring a shader (manual steps)
For each new shader, three handwritten files live in `shaders/<name>/`:
1. **`<name>.gl.glsl`** — OpenGL GLSL 3.30 (the original Shadertoy-style format described below).
2. **`<name>.vk.glsl`** — Vulkan GLSL 4.50 with explicit `layout(set=3, binding=0) uniform ShadertoyUBO { float iTime; vec2 iResolution; } u;` and `layout(set=2, binding=N) uniform sampler2D` for any texture channel.
3. **`<name>.frag.msl`** — Metal Shading Language. Entry point must be named `<name>_fs`. Uniforms come in via `[[buffer(0)]]`; samplers via `[[texture(N)]] [[sampler(N)]]`.
After editing any `.vk.glsl`, rebuild SPIR-V: `cmake --build build --target compile_shaders`.
The MSL is **not** auto-generated — write it by hand following the same convention as `aee_2026/source/core/rendering/sdl3gpu/msl/*.msl.h`.
### Shadertoy → Vulkan/Metal porting cheatsheet
- `iResolution` is `vec2` here (no `.xy`). Reconstruct vec3 manually if needed.
- Replace `texture(iChannel0, ...)` calls — no texture channels currently wired through the GPU backend (planned).
- The shared vertex stage emits `vUV` already in Shadertoy convention `(0,0)` bottom-left → `(1,1)` top-right; multiply by `iResolution` to get `fragCoord`.
- The Y axis is flipped in NDC inside the shared vertex shader so Vulkan/Metal render right-side-up like OpenGL.
### Shader Format (GLSL 3.3 Core)
All shaders must follow this structure:
```glsl
#version 330 core
precision highp float;
out vec4 FragColor;
in vec2 vUV; // Normalized [0,1] coordinates from vertex shader
uniform vec2 iResolution; // Window resolution in pixels
uniform float iTime; // Time since shader loaded (seconds)
// Shadertoy-style entry point
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// fragCoord is in pixel coordinates
// Your shader code here
}
// Wrapper converts vUV to pixel coordinates
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}
```
### Adding New Shaders
1. Create `.glsl` file in `shaders/` directory (use `.frag.glsl` convention)
2. Follow required format above
3. File automatically appears in runtime shader rotation (arrow keys to navigate)
4. **No code changes required** - directory is scanned at startup
### Shader Loading Pipeline
1. Directory scan on startup (`scanShaderDirectory()`)
2. Sort alphabetically
3. Load fragment shader source from disk
4. Compile vertex shader (fixed, embedded in main.cpp)
5. Compile fragment shader with error logging
6. Link program with error handling
7. Update uniforms each frame (iResolution, iTime)
### Supported Shadertoy Features
-`iTime` - Time uniform
-`iResolution` - Window resolution (vec2, not vec3)
-`mainImage()` function signature
-`iMouse` - Not implemented
-`iChannel0-3` - No texture channels (multi-pass not supported)
-`iFrame`, `iTimeDelta`, `iDate` - Not implemented
### Converting from Shadertoy
When porting Shadertoy shaders:
1. Copy the `mainImage()` function as-is
2. Add standard header (see format above)
3. Add wrapper `main()` function
4. Remove texture channel references (iChannel0-3)
5. Change `iResolution.xy` to `iResolution` (this project uses vec2)
### Common Conversion Issues and Solutions
Based on experience converting complex shaders like ddla_light_tunnel:
#### iResolution vec3 vs vec2
- **Shadertoy:** `iResolution` is `vec3(width, height, width/height)`
- **This project:** `iResolution` is `vec2(width, height)`
- **Solution:** Create vec3 manually: `vec3 r = vec3(iResolution.xy, iResolution.x/iResolution.y);`
- **Then use:** `r.xy` for resolution, `r.y` for height (as in original)
#### Uninitialized Variables
- **Problem:** Shadertoy code may have `vec3 rgb;` without initialization
- **Shadertoy behavior:** Likely initializes to `vec3(0.0)` (black)
- **This project:** Uninitialized variables contain undefined values (often causes black screen or wrong colors)
- **Solution:** Always initialize: `vec3 rgb = vec3(0.0);`
- **Wrong approach:** Don't use random noise unless shader explicitly uses iChannel texture for noise
#### Low mix() Factors Are Intentional
- **Example:** `rgb = mix(rgb, calculated_color, 0.01);` means 1% new color, 99% existing
- **Don't change these factors** - they create subtle effects intentionally
- **If output is black:** Problem is likely the base value (rgb), not the mix factor
#### iChannel Textures
- **Shadertoy shows iChannel0-3** in UI, but shader may not use them
- **If iChannel3 has RGB noise but code doesn't reference it:** The noise is not actually used
- **Check code for `texture(iChannelN, ...)` calls** - if none exist, ignore the iChannel setup
- **Procedural noise replacement:** Only if shader explicitly samples the texture
#### Color Swapping Issues
- **If colors are completely wrong:** Don't randomly swap color variables
- **First verify:** Code matches original Shadertoy exactly (except required GLSL changes)
- **Common mistake:** Changing color applications that were correct in original
- **Debug approach:** Revert to exact original code, only modify for GLSL 3.3 compatibility
#### Division by Small Values - Color Overflow Artifacts
- **Problem:** Division by values near zero causes extreme values → color overflow artifacts (green points, strange colors)
- **Example:** `.01*vec4(6,2,1,0)/length(u*sin(iTime))` can divide by ~0.0 when near center and sin≈0
- **Symptoms:** Bright white center that pulses to green, or random color artifacts in specific areas
- **Root cause:** Even with tonemap (tanh/clamp), extreme intermediate values cause precision issues or saturation
- **Solution - Double Protection:**
1. **Add epsilon to denominator:** `max(denominator, 0.001)` prevents division by exact zero
2. **Clamp the result:** `min(result, vec4(50.0))` prevents extreme accumulation
3. **Only clamp problematic terms:** Don't clamp everything or scene becomes dim
- **Example fix:**
```glsl
// Original (causes overflow):
o += .01*vec4(6,2,1,0)/length(u*sin(t+t+t)) + 1./s * length(u);
// Fixed (prevents overflow while maintaining brightness):
vec4 brightTerm = min(.01*vec4(6,2,1,0)/max(length(u*sin(t+t+t)), 0.001), vec4(50.0));
o += brightTerm + 1./s * length(u);
```
- **Key values:** epsilon ~0.001 (not too small, not too large), clamp ~50.0 (allows brightness without explosion)
- **Why this works in Shadertoy:** WebGL/browsers may handle float overflow differently than native OpenGL drivers
#### Debugging Black/Wrong Output
1. **Check compilation errors first** - shader must compile without errors
2. **Initialize all variables** - especially vec3 colors
3. **Verify vec3 iResolution handling** - create vec3 from vec2 if needed
4. **Don't modify mix factors** - keep original values
5. **Compare with original code** - ensure logic is identical
6. **Test progressive changes** - add header, test; add wrapper, test; etc.
7. **Check for division by small values** - if you see color artifacts (especially green), look for divisions that can approach zero
## Build System Details
### Version Numbering
Releases use build date format: `shadertoy-YYYY-MM-DD-{platform}` (auto-generated by Makefile)
### Release Artifacts
- **Windows:** `.zip` with `shadertoy.exe` + `SDL3.dll` + shaders
- **macOS:** `.dmg` with app bundle containing embedded SDL3.framework (arm64 only)
- **Linux:** `.tar.gz` with binary + shaders
### Resource Bundling
- Shaders copied to release packages
- LICENSE and README.md included
- Platform-specific dependencies bundled (DLLs on Windows, frameworks on macOS)
## Important Notes
### Vertex Shader
The vertex shader is hardcoded in `main.cpp` and creates a fullscreen quad. It:
- Takes `vec2 aPos` (location 0) in NDC space [-1, 1]
- Outputs `vec2 vUV` normalized to [0, 1]
- No transformation matrices needed
### Shader Hot-Reloading
Currently **not implemented**. Shader changes require application restart. The architecture would support adding this via file watching.
### Multi-Pass Rendering
Single-pass only. To add multi-pass (BufferA/B/C like Shadertoy):
- Create FBOs and textures per buffer
- Render buffers in dependency order
- Pass textures as `iChannel0-3` uniforms
- Use ping-pong for feedback loops
### OpenGL Context
Created via SDL3 with core profile (no deprecated functions). Context version: 3.3 core.

View File

@@ -17,17 +17,36 @@ set(OpenGL_GL_PREFERENCE GLVND)
# --- LISTA EXPLÍCITA DE FUENTES --- # --- LISTA EXPLÍCITA DE FUENTES ---
set(APP_SOURCES set(APP_SOURCES
src/main.cpp src/main.cpp
src/rendering/shader_backend.cpp
src/rendering/opengl_shader_backend.cpp
src/rendering/sdl3gpu/sdl3gpu_shader_backend.cpp
) )
# Fuentes de librerías de terceros # Fuentes de librerías de terceros
set(EXTERNAL_SOURCES set(EXTERNAL_SOURCES
third_party/glad/src/glad.c third_party/glad/src/glad.c
third_party/jail_audio.cpp
) )
# Configuración de SDL3 # Configuración de SDL3
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}") message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
# --- COMPILACIÓN DE SHADERS (Vulkan SPIR-V) ---
find_program(GLSLC_EXE NAMES glslc)
if(GLSLC_EXE)
message(STATUS "glslc encontrado: ${GLSLC_EXE}")
add_custom_target(compile_shaders
COMMAND ${CMAKE_COMMAND}
-D GLSLC=${GLSLC_EXE}
-D SHADERS_DIR=${CMAKE_SOURCE_DIR}/shaders
-P ${CMAKE_SOURCE_DIR}/tools/shaders/compile_shaders.cmake
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Compiling .vk.glsl shaders to SPIR-V")
else()
message(STATUS "glslc no encontrado — el target compile_shaders no estará disponible")
endif()
# --- AÑADIR EJECUTABLE --- # --- AÑADIR EJECUTABLE ---
add_executable(${PROJECT_NAME} ${APP_SOURCES} ${EXTERNAL_SOURCES}) add_executable(${PROJECT_NAME} ${APP_SOURCES} ${EXTERNAL_SOURCES})
@@ -35,6 +54,7 @@ add_executable(${PROJECT_NAME} ${APP_SOURCES} ${EXTERNAL_SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC target_include_directories(${PROJECT_NAME} PUBLIC
"${CMAKE_SOURCE_DIR}/src" "${CMAKE_SOURCE_DIR}/src"
"${CMAKE_SOURCE_DIR}/third_party/glad/include" "${CMAKE_SOURCE_DIR}/third_party/glad/include"
"${CMAKE_SOURCE_DIR}/third_party"
) )
# Enlazar la librería SDL3 # Enlazar la librería SDL3

View File

@@ -11,11 +11,12 @@ APP_NAME := Shadertoy
RELEASE_FOLDER := shadertoy_release RELEASE_FOLDER := shadertoy_release
RESOURCE_FILE := release/shadertoy.res RESOURCE_FILE := release/shadertoy.res
# Versión automática basada en la fecha actual (específica por SO) # Versión automática basada en la fecha actual (formato YYYY.MM.DD para que
# CFBundleShortVersionString del bundle macOS sea conforme a la spec de Apple).
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
VERSION := $(shell powershell -Command "Get-Date -Format 'yyyy-MM-dd'") VERSION := $(shell powershell -Command "Get-Date -Format 'yyyy.MM.dd'")
else else
VERSION := $(shell date +%Y-%m-%d) VERSION := $(shell date +%Y.%m.%d)
endif endif
# Variables específicas para Windows (usando APP_NAME) # Variables específicas para Windows (usando APP_NAME)
@@ -35,10 +36,14 @@ LINUX_RELEASE := $(TARGET_NAME)-$(VERSION)-linux.tar.gz
# Lista completa de archivos fuente # Lista completa de archivos fuente
APP_SOURCES := \ APP_SOURCES := \
src/main.cpp \ src/main.cpp \
third_party/glad/src/glad.c src/rendering/shader_backend.cpp \
src/rendering/opengl_shader_backend.cpp \
src/rendering/sdl3gpu/sdl3gpu_shader_backend.cpp \
third_party/glad/src/glad.c \
third_party/jail_audio.cpp
# Includes # Includes
INCLUDES := -Isrc -Ithird_party/glad/include INCLUDES := -Isrc -Ithird_party/glad/include -Ithird_party
# Variables según el sistema operativo # Variables según el sistema operativo
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
@@ -94,6 +99,7 @@ windows_release:
# Copia la carpeta 'shaders' # Copia la carpeta 'shaders'
powershell Copy-Item -Path "shaders" -Destination "$(RELEASE_FOLDER)" -recurse -Force powershell Copy-Item -Path "shaders" -Destination "$(RELEASE_FOLDER)" -recurse -Force
powershell Copy-Item -Path "data" -Destination "$(RELEASE_FOLDER)" -recurse -Force
# Copia los ficheros que están en la raíz del proyecto # Copia los ficheros que están en la raíz del proyecto
powershell Copy-Item "LICENSE" -Destination "$(RELEASE_FOLDER)" powershell Copy-Item "LICENSE" -Destination "$(RELEASE_FOLDER)"
@@ -126,8 +132,28 @@ macos_debug:
macos_release: macos_release:
@echo "Creando release para macOS - Version: $(VERSION)" @echo "Creando release para macOS - Version: $(VERSION)"
# Verificar e instalar create-dmg si es necesario # Verifica dependencias necesarias (create-dmg). Si falta, intenta instalarla
@which create-dmg > /dev/null || (echo "Instalando create-dmg..." && brew install create-dmg) # con brew; si brew tampoco está, indica el comando exacto al usuario.
@command -v create-dmg >/dev/null 2>&1 || { \
echo ""; \
echo "============================================"; \
echo " Falta la dependencia: create-dmg"; \
echo "============================================"; \
if command -v brew >/dev/null 2>&1; then \
echo " Instalando con: brew install create-dmg"; \
brew install create-dmg || { \
echo ""; \
echo " ERROR: 'brew install create-dmg' ha fallado."; \
echo " Ejecuta el comando manualmente y vuelve a probar."; \
exit 1; \
}; \
else \
echo " Homebrew no está instalado."; \
echo " Instálalo desde https://brew.sh y luego ejecuta:"; \
echo " brew install create-dmg"; \
exit 1; \
fi; \
}
# Elimina datos de compilaciones anteriores # Elimina datos de compilaciones anteriores
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
@@ -141,12 +167,19 @@ macos_release:
# Copia carpetas y ficheros # Copia carpetas y ficheros
cp -R shaders "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" cp -R shaders "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp -R data "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp -R release/frameworks/SDL3.xcframework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks" cp -R release/frameworks/SDL3.xcframework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
cp release/icon.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources" cp release/icon.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp release/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents" cp release/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
cp LICENSE "$(RELEASE_FOLDER)" cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)" cp README.md "$(RELEASE_FOLDER)"
# Actualiza versión en Info.plist
@echo "Actualizando Info.plist con versión $(VERSION)..."
@RAW_VERSION=$$(echo "$(VERSION)" | sed 's/^v//'); \
sed -i '' '/<key>CFBundleShortVersionString<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"; \
sed -i '' '/<key>CFBundleVersion<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"
# Compila la versión para procesadores Apple Silicon # Compila la versión para procesadores Apple Silicon
$(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11 $(CXX) $(APP_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11
@@ -192,6 +225,7 @@ linux_release:
# Copia ficheros # Copia ficheros
cp -R shaders "$(RELEASE_FOLDER)" cp -R shaders "$(RELEASE_FOLDER)"
cp -R data "$(RELEASE_FOLDER)"
cp LICENSE "$(RELEASE_FOLDER)" cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)" cp README.md "$(RELEASE_FOLDER)"

133
README.md
View File

@@ -1,27 +1,37 @@
# Shadertoy # Shadertoy
Proyecto minimal para ejecutar fragment shaders tipo Shadertoy usando SDL3 + GLAD + OpenGL 3.3. Proyecto minimal para ejecutar fragment shaders tipo Shadertoy usando SDL3 + GLAD + OpenGL 3.3.
![Shadertoy - Gameplay](https://php.sustancia.synology.me/images/shadertoy/shadertoy01.png) ![Shadertoy - Gameplay](https://php.sustancia.synology.me/images/shadertoy/shadertoy01.png)
## Características
- **Visualizador de shaders fragment** compatible con formato Shadertoy
- **Contador de FPS** en barra de título
- **Toggle VSync** con tecla F4
- **Cambio de shaders en runtime** con flechas ←/→
- **Sistema de metadata** en shaders (Name/Author automático en título)
- **Sistema de feedback** para shaders que requieren frame anterior (opcional)
- **Soporte de audio** con jail_audio (música de fondo)
## Estructura ## Estructura
- src/ — código fuente C++ (incluye main.cpp). - src/ — código fuente C++ (incluye main.cpp)
- shaders/ — shaders fragment (.frag.glsl) que se cargan en runtime
- shaders/ — shaders fragment (.frag.glsl) que se cargan en runtime. - data/ — recursos (música, texturas, etc.)
- third_party/glad/ — glad.c + headers
- third_party/glad/ — glad.c + headers. - third_party/ — jail_audio y otras dependencias
- CMakeLists.txt — configuración de build
- CMakeLists.txt — configuración de build. - Makefile — builds de release para Windows/macOS/Linux
- .gitignore — recomendado.
@@ -63,55 +73,74 @@ brew install cmake sdl3
## Uso ## Uso
Por defecto el ejecutable carga shaders/test.frag.glsl. Ejecutar con un shader específico:
./shadertoy shaders/fractal_pyramid.frag.glsl
Ejecutar en ventana:
./shadertoy_sdl3 ../shaders/test.frag.glsl
Ejecutar en fullscreen:
Ejecutar en fullscreen nativo: ./shadertoy -F shaders/seascape.frag.glsl
# o
./shadertoy --fullscreen shaders/seascape.frag.glsl
./shadertoy_sdl3 -F ../shaders/test.frag.glsl
Si ejecutas desde la raíz del repo: Si ejecutas desde la raíz del repo:
./build/shadertoy_sdl3 shaders/frag_tile_iter.frag.glsl ./build/shadertoy shaders/water.glsl
Atajos en ejecución
- Escape — salir. ### Atajos de teclado
- F11 — alternar fullscreen desktop. - **ESC**Salir de la aplicación
- **F3** — Toggle fullscreen
- **F4** — Toggle VSync (ON/OFF)
- **← / →** — Cambiar al shader anterior/siguiente en la carpeta shaders/
## Formato de shader esperado ## Formato de shader esperado
- #version 330 core ### Header obligatorio:
```glsl
// Name: Nombre del shader
// Author: Autor
#version 330 core
precision highp float;
- Debe declarar: out vec4 FragColor;
in vec2 vUV;
uniform vec2 iResolution;
uniform float iTime;
```
uniform vec2 iResolution; ### Función mainImage (estilo Shadertoy):
```glsl
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
// Tu código aquí
// fragCoord está en píxeles
vec2 uv = fragCoord / iResolution.xy;
fragColor = vec4(uv, 0.5, 1.0);
}
```
uniform float iTime; ### Wrapper main():
```glsl
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}
```
in vec2 vUV; ### Metadata opcional:
- `// Name: Nombre` - Aparece en barra de título
out vec4 FragColor; - `// Author: Autor` - Aparece en barra de título
- `// iChannel3: self` - Habilita feedback (frame anterior)
- Función de entrada esperada (opcional, el main llama a mainImage):
void mainImage(out vec4 fragColor, in vec2 fragCoord);
- main() debe convertir vUV a fragCoord y llamar a mainImage.
@@ -133,15 +162,33 @@ Si obtienes "Failed to load fragment shader file", ejecuta desde la carpeta buil
## Multi-pass / Buffers (nota) ## Compatibilidad con Shadertoy
Ejemplo actual es single-pass. Para portar Shadertoy con BufferA/B/C necesitas: ### Características soportadas:
`iTime` - Tiempo en segundos
`iResolution` - Resolución de ventana (vec2)
`mainImage()` - Función de entrada estándar
✅ Self-feedback - Frame anterior con `// iChannel3: self`
- crear FBOs y textures por buffer ### No soportado actualmente:
`iMouse` - Interacción con ratón
`iChannel0-2` - Texturas externas
❌ Multi-pass completo (BufferA/B/C/D)
`iFrame`, `iTimeDelta`, `iDate`
- renderizar buffers en orden y pasar las texturas como iChannelN a pases posteriores ### Diferencias importantes:
- **`iResolution`**: En Shadertoy es `vec3(width, height, width/height)`, aquí es `vec2(width, height)`
- Solución: `vec3 r = vec3(iResolution.xy, iResolution.x/iResolution.y);`
- **Inicialización de variables**: OpenGL nativo requiere inicializar variables explícitamente
- **División por valores pequeños**: Puede causar overflow - usar `max(divisor, epsilon)`
- evitar leer y escribir la misma textura (usar ping-pong si hace falta) ### Conversión de shaders de Shadertoy:
1. Copiar función `mainImage()` tal cual
2. Añadir header estándar (ver arriba)
3. Añadir wrapper `main()`
4. Eliminar referencias a `iChannel0-3` si no se usan
5. Adaptar `iResolution` de vec3 a vec2 si es necesario
6. Inicializar variables: `vec3 col = vec3(0.0);` en lugar de `vec3 col;`

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,41 @@
Pack downloaded from Freesound
----------------------------------------
"Ambient pads"
This Pack of sounds contains sounds by the following user:
- Mat397 ( https://freesound.org/people/Mat397/ )
You can find this pack online at: https://freesound.org/people/Mat397/packs/27416/
Licenses in this Pack (see below for individual sound licenses)
---------------------------------------------------------------
Creative Commons 0: http://creativecommons.org/publicdomain/zero/1.0/
Attribution 3.0: http://creativecommons.org/licenses/by/3.0/
Sounds in this Pack
-------------------
* 618042__mat397__bad-electric-dark-pad.wav.wav
* url: https://freesound.org/s/618042/
* license: Creative Commons 0
* 618041__mat397__mangle-dark-pad.wav.wav
* url: https://freesound.org/s/618041/
* license: Creative Commons 0
* 486083__mat397__world-of-ants-pad.wav.wav
* url: https://freesound.org/s/486083/
* license: Attribution 3.0
* 485079__mat397__melancholic-flutes-pad.wav.wav
* url: https://freesound.org/s/485079/
* license: Attribution 3.0
* 485078__mat397__polyflute-pad.wav.wav
* url: https://freesound.org/s/485078/
* license: Attribution 3.0
* 485077__mat397__confused-voices.wav.wav
* url: https://freesound.org/s/485077/
* license: Attribution 3.0

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

After

Width:  |  Height:  |  Size: 364 KiB

BIN
release/icon/icon.afdesign Normal file

Binary file not shown.

BIN
release/icon/icon.pxd Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,19 @@
#include <metal_stdlib>
using namespace metal;
// Shared fullscreen-triangle vertex shader for the SDL3 GPU backend.
// Emits uv in Shadertoy convention: (0,0) bottom-left, (1,1) top-right.
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
vertex PassthroughVOut passthrough_vs(uint vid [[vertex_id]]) {
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
PassthroughVOut out;
float2 pos = positions[vid];
out.uv = pos * 0.5 + 0.5;
out.pos = float4(pos.x, -pos.y, 0.0, 1.0);
return out;
}

Binary file not shown.

View File

@@ -0,0 +1,18 @@
#version 450
// Shared fullscreen-triangle vertex shader for the SDL3 GPU backend.
// Emits vUV in Shadertoy convention: (0,0) bottom-left, (1,1) top-right.
// Y flipped in NDC because Vulkan/Metal point Y down by default.
layout(location = 0) out vec2 vUV;
void main() {
const vec2 positions[3] = vec2[3](
vec2(-1.0, -1.0),
vec2( 3.0, -1.0),
vec2(-1.0, 3.0)
);
vec2 pos = positions[gl_VertexIndex];
vUV = pos * 0.5 + 0.5;
gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);
}

View File

@@ -0,0 +1,83 @@
// Name: Cineshader Lava
// Author: edankwan
// URL: https://www.shadertoy.com/view/3sySRK
#version 330 core
precision highp float;
out vec4 FragColor;
in vec2 vUV;
uniform vec2 iResolution;
uniform float iTime;
float opSmoothUnion( float d1, float d2, float k )
{
float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
return mix( d2, d1, h ) - k*h*(1.0-h);
}
float sdSphere( vec3 p, float s )
{
return length(p)-s;
}
float map(vec3 p)
{
float d = 2.0;
for (int i = 0; i < 16; i++) {
float fi = float(i);
float time = iTime * (fract(fi * 412.531 + 0.513) - 0.5) * 2.0;
d = opSmoothUnion(
sdSphere(p + sin(time + fi * vec3(52.5126, 64.62744, 632.25)) * vec3(2.0, 2.0, 0.8), mix(0.5, 1.0, fract(fi * 412.531 + 0.5124))),
d,
0.4
);
}
return d;
}
vec3 calcNormal( in vec3 p )
{
const float h = 1e-5; // or some other value
const vec2 k = vec2(1,-1);
return normalize( k.xyy*map( p + k.xyy*h ) +
k.yyx*map( p + k.yyx*h ) +
k.yxy*map( p + k.yxy*h ) +
k.xxx*map( p + k.xxx*h ) );
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
// screen size is 6m x 6m
vec3 rayOri = vec3((uv - 0.5) * vec2(iResolution.x/iResolution.y, 1.0) * 6.0, 3.0);
vec3 rayDir = vec3(0.0, 0.0, -1.0);
float depth = 0.0;
vec3 p;
for(int i = 0; i < 64; i++) {
p = rayOri + rayDir * depth;
float dist = map(p);
depth += dist;
if (dist < 1e-6) {
break;
}
}
depth = min(6.0, depth);
vec3 n = calcNormal(p);
float b = max(0.0, dot(n, vec3(0.577)));
vec3 col = (0.5 + 0.5 * cos((b + iTime * 3.0) + uv.xyx * 2.0 + vec3(0,2,4))) * (0.85 + b * 0.35);
col *= exp( -depth * 0.15 );
// maximum thickness is 2m in alpha channel
fragColor = vec4(col, 1.0 - (depth - 0.5) / 2.0);
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

@@ -0,0 +1,2 @@
Name: Cineshader Lava
Author: edankwan

View File

@@ -1,3 +1,6 @@
// Name: Creation by Silexars
// Author: Danguafer
// URL: https://www.shadertoy.com/view/XsXXDn
#version 330 core #version 330 core
precision highp float; precision highp float;

View File

@@ -0,0 +1,2 @@
Name: Creation by Silexars
Author: Danguafer

View File

@@ -0,0 +1,778 @@
// Name: Cube lines
// Author: Danil
// URL: https://www.shadertoy.com/view/NslGRN
#version 330 core
precision highp float;
out vec4 FragColor;
in vec2 vUV;
uniform vec2 iResolution;
uniform float iTime;
// Note: Original shader uses iChannel0 for background blending (optional feature)
// Since we don't support texture channels, that line is commented out
// The shader works perfectly without it in default mode
// Created by Danil (2021+) https://cohost.org/arugl
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// self https://www.shadertoy.com/view/NslGRN
// --defines for "DESKTOP WALLPAPERS" that use this shader--
// comment or uncomment every define to make it work (add or remove "//" before #define)
// this shadertoy use ALPHA, NO_ALPHA set alpha to 1, BG_ALPHA set background as alpha
// iChannel0 used as background if alpha ignored by wallpaper-app
//#define NO_ALPHA
//#define BG_ALPHA
//#define SHADOW_ALPHA
//#define ONLY_BOX
// save PERFORMANCE by disabling shadow
//#define NO_SHADOW
// static CAMERA position, 0.49 on top, 0.001 horizontal
//#define CAMERA_POS 0.049
// speed of ROTATION
#define ROTATION_SPEED 0.8999
// static SHAPE form, default 0.5
//#define STATIC_SHAPE 0.15
// static SCALE far/close to camera, 2.0 is default, exampe 0.5 or 10.0
//#define CAMERA_FAR 0.1
// ANIMATION shape change
//#define ANIM_SHAPE
// ANIMATION color change
//#define ANIM_COLOR
// custom COLOR, and change those const values
//#define USE_COLOR
const vec3 color_blue=vec3(0.5,0.65,0.8);
const vec3 color_red=vec3(0.99,0.2,0.1);
// use 4xAA for cube only (set 2-4-etc level of AA)
//#define AA_CUBE 4
// use 4xAA for everything - derivative filtering will not be used, look fcos2
// this is very slow - DO NOT USE
//#define AA_ALL 4
// --shader code--
// Layers sorted and support transparency and self-intersection-transparency
// Antialiasing is only dFd. (with some dFd fixes around edges)
// using iq's intersectors: https://iquilezles.org/articles/intersectors
// using https://www.shadertoy.com/view/ltKBzG
// using https://www.shadertoy.com/view/tsVXzh
// using https://www.shadertoy.com/view/WlffDn
// using https://www.shadertoy.com/view/WslGz4
#define tshift 53.
// reflect back side
//#define backside_refl
// Camera with mouse
// #define MOUSE_control (disabled - no iMouse support)
// min(iFrame,0) does not speedup compilation in ANGLE
#define ANGLE_loops 0
// this shader discover Nvidia bug with arrays https://www.shadertoy.com/view/NslGR4
// use DEBUG with BUG, BUG trigger that bug and one layer will be white on Nvidia in OpenGL
//#define DEBUG
//#define BUG
#define FDIST 0.7
#define PI 3.1415926
#define GROUNDSPACING 0.5
#define GROUNDGRID 0.05
#define BOXDIMS vec3(0.75, 0.75, 1.25)
#define IOR 1.33
mat3 rotx(float a){float s = sin(a);float c = cos(a);return mat3(vec3(1.0, 0.0, 0.0), vec3(0.0, c, s), vec3(0.0, -s, c)); }
mat3 roty(float a){float s = sin(a);float c = cos(a);return mat3(vec3(c, 0.0, s), vec3(0.0, 1.0, 0.0), vec3(-s, 0.0, c));}
mat3 rotz(float a){float s = sin(a);float c = cos(a);return mat3(vec3(c, s, 0.0), vec3(-s, c, 0.0), vec3(0.0, 0.0, 1.0 ));}
vec3 fcos1(vec3 x) {
vec3 w = fwidth(x);
//if((length(w)==0.))return vec3(0.); // dFd fix2
//w*=0.; //test
float lw=length(w);
if((lw==0.)||isnan(lw)||isinf(lw)){vec3 tc=vec3(0.); for(int i=0;i<8;i++)tc+=cos(x+x*float(i-4)*(0.01*400./iResolution.y));return tc/8.;}
return cos(x) * smoothstep(3.14 * 2.0, 0.0, w);
}
vec3 fcos2( vec3 x){return cos(x);}
vec3 fcos( vec3 x){
#ifdef AA_ALL
return fcos2(x);
#else
return fcos1(x);
#endif
}
vec3 getColor(vec3 p)
{
// dFd fix, dFd broken on borders, but it fix only top level dFd, self intersection has border
//if (length(p) > 0.99)return vec3(0.);
p = abs(p);
p *= 01.25;
p = 0.5 * p / dot(p, p);
#ifdef ANIM_COLOR
p+=0.072*iTime;
#endif
float t = (0.13) * length(p);
vec3 col = vec3(0.3, 0.4, 0.5);
col += 0.12 * fcos(6.28318 * t * 1.0 + vec3(0.0, 0.8, 1.1));
col += 0.11 * fcos(6.28318 * t * 3.1 + vec3(0.3, 0.4, 0.1));
col += 0.10 * fcos(6.28318 * t * 5.1 + vec3(0.1, 0.7, 1.1));
col += 0.10 * fcos(6.28318 * t * 17.1 + vec3(0.2, 0.6, 0.7));
col += 0.10 * fcos(6.28318 * t * 31.1 + vec3(0.1, 0.6, 0.7));
col += 0.10 * fcos(6.28318 * t * 65.1 + vec3(0.0, 0.5, 0.8));
col += 0.10 * fcos(6.28318 * t * 115.1 + vec3(0.1, 0.4, 0.7));
col += 0.10 * fcos(6.28318 * t * 265.1 + vec3(1.1, 1.4, 2.7));
col = clamp(col, 0., 1.);
return col;
}
void calcColor(vec3 ro, vec3 rd, vec3 nor, float d, float len, int idx, bool si, float td, out vec4 colx,
out vec4 colsi)
{
vec3 pos = (ro + rd * d);
#ifdef DEBUG
float a = 1. - smoothstep(len - 0.15, len + 0.00001, length(pos));
if (idx == 0)colx = vec4(1., 0., 0., a);
if (idx == 1)colx = vec4(0., 1., 0., a);
if (idx == 2)colx = vec4(0., 0., 1., a);
if (si)
{
pos = (ro + rd * td);
float ta = 1. - smoothstep(len - 0.15, len + 0.00001, length(pos));
if (idx == 0)colsi = vec4(1., 0., 0., ta);
if (idx == 1)colsi = vec4(0., 1., 0., ta);
if (idx == 2)colsi = vec4(0., 0., 1., ta);
}
#else
float a = 1. - smoothstep(len - 0.15*0.5, len + 0.00001, length(pos));
//a=1.;
vec3 col = getColor(pos);
colx = vec4(col, a);
if (si)
{
pos = (ro + rd * td);
float ta = 1. - smoothstep(len - 0.15*0.5, len + 0.00001, length(pos));
//ta=1.;
col = getColor(pos);
colsi = vec4(col, ta);
}
#endif
}
// xSI is self intersect data, fade to fix dFd on edges
bool iBilinearPatch(in vec3 ro, in vec3 rd, in vec4 ps, in vec4 ph, in float sz, out float t, out vec3 norm,
out bool si, out float tsi, out vec3 normsi, out float fade, out float fadesi)
{
vec3 va = vec3(0.0, 0.0, ph.x + ph.w - ph.y - ph.z);
vec3 vb = vec3(0.0, ps.w - ps.y, ph.z - ph.x);
vec3 vc = vec3(ps.z - ps.x, 0.0, ph.y - ph.x);
vec3 vd = vec3(ps.xy, ph.x);
t = -1.;
tsi = -1.;
si = false;
fade = 1.;
fadesi = 1.;
norm=vec3(0.,1.,0.);normsi=vec3(0.,1.,0.);
float tmp = 1.0 / (vb.y * vc.x);
float a = 0.0;
float b = 0.0;
float c = 0.0;
float d = va.z * tmp;
float e = 0.0;
float f = 0.0;
float g = (vc.z * vb.y - vd.y * va.z) * tmp;
float h = (vb.z * vc.x - va.z * vd.x) * tmp;
float i = -1.0;
float j = (vd.x * vd.y * va.z + vd.z * vb.y * vc.x) * tmp - (vd.y * vb.z * vc.x + vd.x * vc.z * vb.y) * tmp;
float p = dot(vec3(a, b, c), rd.xzy * rd.xzy) + dot(vec3(d, e, f), rd.xzy * rd.zyx);
float q = dot(vec3(2.0, 2.0, 2.0) * ro.xzy * rd.xyz, vec3(a, b, c)) + dot(ro.xzz * rd.zxy, vec3(d, d, e)) +
dot(ro.yyx * rd.zxy, vec3(e, f, f)) + dot(vec3(g, h, i), rd.xzy);
float r =
dot(vec3(a, b, c), ro.xzy * ro.xzy) + dot(vec3(d, e, f), ro.xzy * ro.zyx) + dot(vec3(g, h, i), ro.xzy) + j;
if (abs(p) < 0.000001)
{
float tt = -r / q;
if (tt <= 0.)
return false;
t = tt;
// normal
vec3 pos = ro + t * rd;
if(length(pos)>sz)return false;
vec3 grad =
vec3(2.0) * pos.xzy * vec3(a, b, c) + pos.zxz * vec3(d, d, e) + pos.yyx * vec3(f, e, f) + vec3(g, h, i);
norm = -normalize(grad);
return true;
}
else
{
float sq = q * q - 4.0 * p * r;
if (sq < 0.0)
{
return false;
}
else
{
float s = sqrt(sq);
float t0 = (-q + s) / (2.0 * p);
float t1 = (-q - s) / (2.0 * p);
float tt1 = min(t0 < 0.0 ? t1 : t0, t1 < 0.0 ? t0 : t1);
float tt2 = max(t0 > 0.0 ? t1 : t0, t1 > 0.0 ? t0 : t1);
float tt0 = tt1;
if (tt0 <= 0.)
return false;
vec3 pos = ro + tt0 * rd;
// black border on end of circle and self intersection with alpha come because dFd
// uncomment this to see or rename fcos2 to fcos
//sz+=0.3;
bool ru = step(sz, length(pos)) > 0.5;
if (ru)
{
tt0 = tt2;
pos = ro + tt0 * rd;
}
if (tt0 <= 0.)
return false;
bool ru2 = step(sz, length(pos)) > 0.5;
if (ru2)
return false;
// self intersect
if ((tt2 > 0.) && ((!ru)) && !(step(sz, length(ro + tt2 * rd)) > 0.5))
{
si = true;
fadesi=s;
tsi = tt2;
vec3 tpos = ro + tsi * rd;
// normal
vec3 tgrad = vec3(2.0) * tpos.xzy * vec3(a, b, c) + tpos.zxz * vec3(d, d, e) +
tpos.yyx * vec3(f, e, f) + vec3(g, h, i);
normsi = -normalize(tgrad);
}
fade=s;
t = tt0;
// normal
vec3 grad =
vec3(2.0) * pos.xzy * vec3(a, b, c) + pos.zxz * vec3(d, d, e) + pos.yyx * vec3(f, e, f) + vec3(g, h, i);
norm = -normalize(grad);
return true;
}
}
}
float dot2( in vec3 v ) { return dot(v,v); }
float segShadow( in vec3 ro, in vec3 rd, in vec3 pa, float sh )
{
float dm = dot(rd.yz,rd.yz);
float k1 = (ro.x-pa.x)*dm;
float k2 = (ro.x+pa.x)*dm;
vec2 k5 = (ro.yz+pa.yz)*dm;
float k3 = dot(ro.yz+pa.yz,rd.yz);
vec2 k4 = (pa.yz+pa.yz)*rd.yz;
vec2 k6 = (pa.yz+pa.yz)*dm;
for( int i=0; i<4 + ANGLE_loops; i++ )
{
vec2 s = vec2(i&1,i>>1);
float t = dot(s,k4) - k3;
if( t>0.0 )
sh = min(sh,dot2(vec3(clamp(-rd.x*t,k1,k2),k5-k6*s)+rd*t)/(t*t));
}
return sh;
}
float boxSoftShadow( in vec3 ro, in vec3 rd, in vec3 rad, in float sk )
{
rd += 0.0001 * (1.0 - abs(sign(rd)));
vec3 rdd = rd;
vec3 roo = ro;
vec3 m = 1.0/rdd;
vec3 n = m*roo;
vec3 k = abs(m)*rad;
vec3 t1 = -n - k;
vec3 t2 = -n + k;
float tN = max( max( t1.x, t1.y ), t1.z );
float tF = min( min( t2.x, t2.y ), t2.z );
if( tN<tF && tF>0.0) return 0.0;
float sh = 1.0;
sh = segShadow( roo.xyz, rdd.xyz, rad.xyz, sh );
sh = segShadow( roo.yzx, rdd.yzx, rad.yzx, sh );
sh = segShadow( roo.zxy, rdd.zxy, rad.zxy, sh );
sh = clamp(sk*sqrt(sh),0.0,1.0);
return sh*sh*(3.0-2.0*sh);
}
float box(in vec3 ro, in vec3 rd, in vec3 r, out vec3 nn, bool entering)
{
rd += 0.0001 * (1.0 - abs(sign(rd)));
vec3 dr = 1.0 / rd;
vec3 n = ro * dr;
vec3 k = r * abs(dr);
vec3 pin = -k - n;
vec3 pout = k - n;
float tin = max(pin.x, max(pin.y, pin.z));
float tout = min(pout.x, min(pout.y, pout.z));
if (tin > tout)
return -1.;
if (entering)
{
nn = -sign(rd) * step(pin.zxy, pin.xyz) * step(pin.yzx, pin.xyz);
}
else
{
nn = sign(rd) * step(pout.xyz, pout.zxy) * step(pout.xyz, pout.yzx);
}
return entering ? tin : tout;
}
vec3 bgcol(in vec3 rd)
{
return mix(vec3(0.01), vec3(0.336, 0.458, .668), 1. - pow(abs(rd.z+0.25), 1.3));
}
vec3 background(in vec3 ro, in vec3 rd , vec3 l_dir, out float alpha)
{
#ifdef ONLY_BOX
alpha=0.;
return vec3(0.01);
#endif
float t = (-BOXDIMS.z - ro.z) / rd.z;
alpha=0.;
vec3 bgc = bgcol(rd);
if (t < 0.)
return bgc;
vec2 uv = ro.xy + t * rd.xy;
#ifdef NO_SHADOW
float shad=1.;
#else
float shad = boxSoftShadow((ro + t * rd), normalize(l_dir+vec3(0.,0.,1.))*rotz(PI*0.65) , BOXDIMS, 1.5);
#endif
float aofac = smoothstep(-0.95, .75, length(abs(uv) - min(abs(uv), vec2(0.45))));
aofac = min(aofac,smoothstep(-0.65, 1., shad));
float lght=max(dot(normalize(ro + t * rd+vec3(0.,-0.,-5.)), normalize(l_dir-vec3(0.,0.,1.))*rotz(PI*0.65)), 0.0);
vec3 col = mix(vec3(0.4), vec3(.71,.772,0.895), lght*lght* aofac+ 0.05) * aofac;
alpha=1.-smoothstep(7.,10.,length(uv));
#ifdef SHADOW_ALPHA
//alpha=clamp(alpha*max(lght*lght*0.95,(1.-aofac)*1.25),0.,1.);
alpha=clamp(alpha*(1.-aofac)*1.25,0.,1.);
#endif
return mix(col*length(col)*0.8,bgc,smoothstep(7.,10.,length(uv)));
}
#define swap(a,b) tv=a;a=b;b=tv
vec4 insides(vec3 ro, vec3 rd, vec3 nor_c, vec3 l_dir, out float tout)
{
tout = -1.;
vec3 trd=rd;
vec3 col = vec3(0.);
float pi = 3.1415926;
if (abs(nor_c.x) > 0.5)
{
rd = rd.xzy * nor_c.x;
ro = ro.xzy * nor_c.x;
}
else if (abs(nor_c.z) > 0.5)
{
l_dir *= roty(pi);
rd = rd.yxz * nor_c.z;
ro = ro.yxz * nor_c.z;
}
else if (abs(nor_c.y) > 0.5)
{
l_dir *= rotz(-pi * 0.5);
rd = rd * nor_c.y;
ro = ro * nor_c.y;
}
#ifdef ANIM_SHAPE
float curvature = (0.001+1.5-1.5*smoothstep(0.,8.5,mod((iTime+tshift)*0.44,20.))*(1.-smoothstep(10.,18.5,mod((iTime+tshift)*0.44,20.))));
// curvature(to not const above) make compilation on Angle 15+ sec
#else
#ifdef STATIC_SHAPE
const float curvature = STATIC_SHAPE;
#else
const float curvature = .5;
#endif
#endif
float bil_size = 1.;
vec4 ps = vec4(-bil_size, -bil_size, bil_size, bil_size) * curvature;
vec4 ph = vec4(-bil_size, bil_size, bil_size, -bil_size) * curvature;
vec4 [3]colx=vec4[3](vec4(0.),vec4(0.),vec4(0.));
vec3 [3]dx=vec3[3](vec3(-1.),vec3(-1.),vec3(-1.));
vec4 [3]colxsi=vec4[3](vec4(0.),vec4(0.),vec4(0.));
int [3]order=int[3](0,1,2);
for (int i = 0; i < 3 + ANGLE_loops; i++)
{
if (abs(nor_c.x) > 0.5)
{
ro *= rotz(-pi * (1. / float(3)));
rd *= rotz(-pi * (1. / float(3)));
}
else if (abs(nor_c.z) > 0.5)
{
ro *= rotz(pi * (1. / float(3)));
rd *= rotz(pi * (1. / float(3)));
}
else if (abs(nor_c.y) > 0.5)
{
ro *= rotx(pi * (1. / float(3)));
rd *= rotx(pi * (1. / float(3)));
}
vec3 normnew;
float tnew;
bool si;
float tsi;
vec3 normsi;
float fade;
float fadesi;
if (iBilinearPatch(ro, rd, ps, ph, bil_size, tnew, normnew, si, tsi, normsi, fade, fadesi))
{
if (tnew > 0.)
{
vec4 tcol, tcolsi;
calcColor(ro, rd, normnew, tnew, bil_size, i, si, tsi, tcol, tcolsi);
if (tcol.a > 0.0)
{
{
vec3 tvalx = vec3(tnew, float(si), tsi);
dx[i]=tvalx;
}
#ifdef DEBUG
colx[i]=tcol;
if (si)colxsi[i]=tcolsi;
#else
float dif = clamp(dot(normnew, l_dir), 0.0, 1.0);
float amb = clamp(0.5 + 0.5 * dot(normnew, l_dir), 0.0, 1.0);
{
#ifdef USE_COLOR
vec3 shad = 0.57 * color_blue * amb + 1.5*color_blue.bgr * dif;
const vec3 tcr = color_red;
#else
vec3 shad = vec3(0.32, 0.43, 0.54) * amb + vec3(1.0, 0.9, 0.7) * dif;
const vec3 tcr = vec3(1.,0.21,0.11);
#endif
float ta = clamp(length(tcol.rgb),0.,1.);
tcol=clamp(tcol*tcol*2.,0.,1.);
vec4 tvalx =
vec4((tcol.rgb*shad*1.4 + 3.*(tcr*tcol.rgb)*clamp(1.-(amb+dif),0.,1.)), min(tcol.a,ta));
tvalx.rgb=clamp(2.*tvalx.rgb*tvalx.rgb,0.,1.);
tvalx*=(min(fade*5.,1.));
colx[i]=tvalx;
}
if (si)
{
dif = clamp(dot(normsi, l_dir), 0.0, 1.0);
amb = clamp(0.5 + 0.5 * dot(normsi, l_dir), 0.0, 1.0);
{
#ifdef USE_COLOR
vec3 shad = 0.57 * color_blue * amb + 1.5*color_blue.bgr * dif;
const vec3 tcr = color_red;
#else
vec3 shad = vec3(0.32, 0.43, 0.54) * amb + vec3(1.0, 0.9, 0.7) * dif;
const vec3 tcr = vec3(1.,0.21,0.11);
#endif
float ta = clamp(length(tcolsi.rgb),0.,1.);
tcolsi=clamp(tcolsi*tcolsi*2.,0.,1.);
vec4 tvalx =
vec4(tcolsi.rgb * shad + 3.*(tcr*tcolsi.rgb)*clamp(1.-(amb+dif),0.,1.), min(tcolsi.a,ta));
tvalx.rgb=clamp(2.*tvalx.rgb*tvalx.rgb,0.,1.);
tvalx.rgb*=(min(fadesi*5.,1.));
colxsi[i]=tvalx;
}
}
#endif
}
}
}
}
// transparency logic and layers sorting
float a = 1.;
if (dx[0].x < dx[1].x){{vec3 swap(dx[0], dx[1]);}{int swap(order[0], order[1]);}}
if (dx[1].x < dx[2].x){{vec3 swap(dx[1], dx[2]);}{int swap(order[1], order[2]);}}
if (dx[0].x < dx[1].x){{vec3 swap(dx[0], dx[1]);}{int swap(order[0], order[1]);}}
tout = max(max(dx[0].x, dx[1].x), dx[2].x);
if (dx[0].y < 0.5)
{
a=colx[order[0]].a;
}
#if !(defined(DEBUG)&&defined(BUG))
// self intersection
bool [3] rul= bool[3](
((dx[0].y > 0.5) && (dx[1].x <= 0.)),
((dx[1].y > 0.5) && (dx[0].x > dx[1].z)),
((dx[2].y > 0.5) && (dx[1].x > dx[2].z))
);
for(int k=0;k<3;k++){
if(rul[k]){
vec4 tcolxsi = vec4(0.);
tcolxsi=colxsi[order[k]];
vec4 tcolx = vec4(0.);
tcolx=colx[order[k]];
vec4 tvalx = mix(tcolxsi, tcolx, tcolx.a);
colx[order[k]]=tvalx;
vec4 tvalx2 = mix(vec4(0.), tvalx, max(tcolx.a, tcolxsi.a));
colx[order[k]]=tvalx2;
}
}
#endif
float a1 = (dx[1].y < 0.5) ? colx[order[1]].a : ((dx[1].z > dx[0].x) ? colx[order[1]].a : 1.);
float a2 = (dx[2].y < 0.5) ? colx[order[2]].a : ((dx[2].z > dx[1].x) ? colx[order[2]].a : 1.);
col = mix(mix(colx[order[0]].rgb, colx[order[1]].rgb, a1), colx[order[2]].rgb, a2);
a = max(max(a, a1), a2);
return vec4(col, a);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
float osc = 0.5;
vec3 l_dir = normalize(vec3(0., 1., 0.));
l_dir *= rotz(0.5);
float mouseY = 1.0 * 0.5 * PI;
// Note: MOUSE_control disabled (no iMouse support)
// #ifdef MOUSE_control
// mouseY = (1.0 - 1.15 * iMouse.y / iResolution.y) * 0.5 * PI;
// if(iMouse.y < 1.)
// #endif
#ifdef CAMERA_POS
mouseY = PI*CAMERA_POS;
#else
mouseY = PI*0.49 - smoothstep(0.,8.5,mod((iTime+tshift)*0.33,25.))*(1.-smoothstep(14.,24.0,mod((iTime+tshift)*0.33,25.))) * 0.55 * PI;
#endif
#ifdef ROTATION_SPEED
float mouseX = -2.*PI-0.25*(iTime*ROTATION_SPEED+tshift);
#else
float mouseX = -2.*PI-0.25*(iTime+tshift);
#endif
// Note: MOUSE_control disabled (no iMouse support)
// #ifdef MOUSE_control
// mouseX+=-(iMouse.x / iResolution.x) * 2. * PI;
// #endif
#ifdef CAMERA_FAR
vec3 eye = (2. + CAMERA_FAR) * vec3(cos(mouseX) * cos(mouseY), sin(mouseX) * cos(mouseY), sin(mouseY));
#else
vec3 eye = 4. * vec3(cos(mouseX) * cos(mouseY), sin(mouseX) * cos(mouseY), sin(mouseY));
#endif
vec3 w = normalize(-eye);
vec3 up = vec3(0., 0., 1.);
vec3 u = normalize(cross(w, up));
vec3 v = cross(u, w);
vec4 tot=vec4(0.);
#if defined(AA_CUBE)||defined(AA_ALL)
#ifdef AA_CUBE
const int AA = AA_CUBE;
#else
const int AA = AA_ALL;
#endif
vec3 incol_once=vec3(0.);
bool in_once=false;
vec4 incolbg_once=vec4(0.);
bool bg_in_once=false;
vec4 outcolbg_once=vec4(0.);
bool bg_out_once=false;
for( int mx=0; mx<AA; mx++ )
for( int nx=0; nx<AA; nx++ )
{
vec2 o = vec2(mod(float(mx+AA/2),float(AA)),mod(float(nx+AA/2),float(AA))) / float(AA) - 0.5;
vec2 uv = (fragCoord + o - 0.5 * iResolution.xy) / iResolution.x;
#else
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.x;
#endif
vec3 rd = normalize(w * FDIST + uv.x * u + uv.y * v);
vec3 ni;
float t = box(eye, rd, BOXDIMS, ni, true);
vec3 ro = eye + t * rd;
vec2 coords = ro.xy * ni.z/BOXDIMS.xy + ro.yz * ni.x/BOXDIMS.yz + ro.zx * ni.y/BOXDIMS.zx;
float fadeborders = (1.-smoothstep(0.915,1.05,abs(coords.x)))*(1.-smoothstep(0.915,1.05,abs(coords.y)));
if (t > 0.)
{
float ang = -iTime * 0.33;
vec3 col = vec3(0.);
#ifdef AA_CUBE
if(in_once)col=incol_once;
else{
in_once=true;
#endif
float R0 = (IOR - 1.) / (IOR + 1.);
R0 *= R0;
vec2 theta = vec2(0.);
vec3 n = vec3(cos(theta.x) * sin(theta.y), sin(theta.x) * sin(theta.y), cos(theta.y));
vec3 nr = n.zxy * ni.x + n.yzx * ni.y + n.xyz * ni.z;
vec3 rdr = reflect(rd, nr);
float talpha;
vec3 reflcol = background(ro, rdr, l_dir,talpha);
vec3 rd2 = refract(rd, nr, 1. / IOR);
float accum = 1.;
vec3 no2 = ni;
vec3 ro_refr = ro;
vec4 [2] colo = vec4[2](vec4(0.),vec4(0.));
for (int j = 0; j < 2 + ANGLE_loops; j++)
{
float tb;
vec2 coords2 = ro_refr.xy * no2.z + ro_refr.yz * no2.x + ro_refr.zx * no2.y;
vec3 eye2 = vec3(coords2, -1.);
vec3 rd2trans = rd2.yzx * no2.x + rd2.zxy * no2.y + rd2.xyz * no2.z;
rd2trans.z = -rd2trans.z;
vec4 internalcol = insides(eye2, rd2trans, no2, l_dir, tb);
if (tb > 0.)
{
internalcol.rgb *= accum;
colo[j]=internalcol;
}
if ((tb <= 0.) || (internalcol.a < 1.))
{
float tout = box(ro_refr, rd2, BOXDIMS, no2, false);
no2 = n.zyx * no2.x + n.xzy * no2.y + n.yxz * no2.z;
vec3 rout = ro_refr + tout * rd2;
vec3 rdout = refract(rd2, -no2, IOR);
float fresnel2 = R0 + (1. - R0) * pow(1. - dot(rdout, no2), 1.3);
rd2 = reflect(rd2, -no2);
#ifdef backside_refl
if((dot(rdout, no2))>0.5){fresnel2=1.;}
#endif
ro_refr = rout;
ro_refr.z = max(ro_refr.z, -0.999);
accum *= fresnel2;
}
}
float fresnel = R0 + (1. - R0) * pow(1. - dot(-rd, nr), 5.);
col = mix(mix(colo[1].rgb * colo[1].a, colo[0].rgb, colo[0].a)*fadeborders, reflcol, pow(fresnel, 1.5));
col=clamp(col,0.,1.);
#ifdef AA_CUBE
}
incol_once=col;
if(!bg_in_once){
bg_in_once=true;
float alpha;
incolbg_once = vec4(background(eye, rd, l_dir, alpha), 0.15);
#if defined(BG_ALPHA)||defined(ONLY_BOX)||defined(SHADOW_ALPHA)
incolbg_once.w = alpha;
#endif
}
#endif
float cineshader_alpha = 0.;
cineshader_alpha = clamp(0.15*dot(eye,ro),0.,1.);
vec4 tcolx = vec4(col, cineshader_alpha);
#if defined(BG_ALPHA)||defined(ONLY_BOX)||defined(SHADOW_ALPHA)
tcolx.w = 1.;
#endif
tot += tcolx;
}
else
{
vec4 tcolx = vec4(0.);
#ifdef AA_CUBE
if(!bg_out_once){
bg_out_once=true;
#endif
float alpha;
tcolx = vec4(background(eye, rd, l_dir, alpha), 0.15);
#if defined(BG_ALPHA)||defined(ONLY_BOX)||defined(SHADOW_ALPHA)
tcolx.w = alpha;
#endif
#ifdef AA_CUBE
outcolbg_once=tcolx;
}else tcolx=max(outcolbg_once,incolbg_once);
#endif
tot += tcolx;
}
#if defined(AA_CUBE)||defined(AA_ALL)
}
tot /= float(AA*AA);
#endif
fragColor = tot;
#ifdef NO_ALPHA
fragColor.w = 1.;
#endif
fragColor.rgb=clamp(fragColor.rgb,0.,1.);
// Note: iChannel0 line removed (texture channel not supported)
// Original line was for optional background blending when BG_ALPHA/ONLY_BOX/SHADOW_ALPHA defined
// #if defined(BG_ALPHA)||defined(ONLY_BOX)||defined(SHADOW_ALPHA)
// fragColor.rgb=fragColor.rgb*fragColor.w+texture(iChannel0, fragCoord/iResolution.xy).rgb*(1.-fragColor.w);
// #endif
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

@@ -0,0 +1,2 @@
Name: Cube lines
Author: Danil

190
shaders/dbz/dbz.gl.glsl Normal file
View File

@@ -0,0 +1,190 @@
// Name: New Leaked 3I/Atlas NASA Footage
// Author: msm01
// URL: https://www.shadertoy.com/view/3ftcRr
#version 330 core
precision highp float;
out vec4 FragColor;
in vec2 vUV;
uniform vec2 iResolution;
uniform float iTime;
// A small improv/fanart from yesterday.
#define s(a,b,c) smoothstep(a,b,c)
#define PI 3.14159
#define NBCaps 3.
mat2 r2d( float a ){ float c = cos(a), s = sin(a); return mat2( c, s, -s, c ); }
float metaDiamond(vec2 p, vec2 pixel, float r){ vec2 d = abs(p-pixel); return r / (d.x + d.y); }
// Dave Hoskins's hash ! Noone can hash hashes like he hashes !
float hash12(vec2 p)
{
vec3 p3 = fract(vec3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
// Smoothed 1D-noise. Just like Zoltraak : stupid simple. Powerful.
float fbm(in vec2 v_p)
{
float pvpx = v_p.x;
vec2 V1 = vec2(floor(pvpx ));
vec2 V2 = vec2(floor(pvpx + 1.0));
return mix(hash12(V1),hash12(V2),smoothstep(0.0,1.0,fract(pvpx)));
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Get Base Coordinates
vec2 p = vec2( (1.0/iResolution.y)*(fragCoord.x - iResolution.x/2.0),fragCoord.y / iResolution.y - 0.5);
// reversed, for accurate Martian perspective... :)
p.x=-p.x;
// Zoom out
p*=150.0;
// Init the Accumulator
vec4 col = vec4(0.05,0.05,0.15,1.0);
// Make up other boxes and save base in them.
vec2 save1 = p;
vec2 save2 = p;
// Faint Nebula Background
// Tilt the camera
p*= r2d(-0.05);
// Space Background Gradient
col = mix(col,vec4(0.2,0.3,0.5,1.0),smoothstep(75.0,0.0,abs(p.y - 5.0*fbm(vec2(0.01*(p.x - 33.333*iTime))) + 3.5)));
// Untilt the camera
p*= r2d( 0.05);
// BG Starfield
// Rotate
p*= r2d(-0.05);
// Zoom In
p*= 0.35;
// Scroll Left
p+= vec2(-5.0*iTime,0.0);
// Hack the coords...
vec2 b = fract(5.0*p);
p = floor(5.0*p);
// Draw the stars
if( fbm(vec2(p.x*p.y)) > 0.996)col += clamp(1.0-pow(3.0*length(b+vec2(-0.5)),0.5),0.0,1.0);
// Reload because the coords are all f.. up now !
p = save1;
// Another Box...
vec2 save3;
// We're going to draw max 4 capsules max.
// Yes we could draw more but Earth must survive, man. Have mercy !
float Nb_Capsules = clamp(NBCaps,0.0,4.0);
for( float i = 0.0;i<Nb_Capsules; i++ )
{
// Reloooooaaaaad !
p = save1;
// Tilt as much as we tilted the stars...
p*= r2d(-0.05);
// Zoom out a bit
p*=2.5;
// Then zoom in a bit closer every loop.
p*=1.0-0.25*i;
// Compute Random Coordinates For Each Capsule Position
// Move To That Position.
p += vec2(150.0*fbm(vec2(0.15*iTime + i*54.321)) - 75.0, // X varies randomly
50.0*sin( 0.25*iTime + i*54.321) - 25.0); // Y varies periodically
// Save Position
save3 = p;
// Mega Zoom
p*=0.04;
// (Ox)-axis Symetry for jets
p.y = abs(p.y);
if( p.x > 0.0 )
{
// Green Jet
col += vec4(0.0,1.0,0.5,1.0)*smoothstep(0.2,0.0,abs(p.y - 0.05*fbm(vec2(1.5*p.x - 40.0*iTime)))-0.05)*smoothstep(29.0,0.0,abs(p.x));
// White Jet
col += vec4(1.0,1.0,1.0,1.0)*smoothstep(0.1,0.0,abs(p.y - 0.05*fbm(vec2(1.5*p.x - 40.0*iTime)))-0.05)*smoothstep(29.0,0.0,abs(p.x));
};
// Reload !
p = save3;
// (Ox)-axis Symetry for the flames
p.y = abs(p.y);
// Fine-tuning Flames position
p+= vec2(-10.0,0.0);
// Fine-tuning Flames Shape
p *= vec2(0.75,1.0);
// Green Flames
col += 0.8*vec4(0.0,1.0,0.5,1.0)*s(20.0,0.0,length(p)-25.0+7.0*sin(0.30*length(p)*atan(p.y,p.x) + 55.0*iTime));
// White flames
col += 0.8*vec4(1.0,1.0,1.0,1.0)*s(20.0,0.0,length(p)-20.0+7.0*sin(0.30*length(p)*atan(p.y,p.x) + 55.0*iTime));
p = save3;
// Fat Aura
col = mix(col,vec4(1.0),0.5*s(10.0,0.0,length(p + vec2(5.0,0.0))-20.0)*abs(sin(50.0*iTime)));
// Less-Fat Aura
col = mix(col,vec4(1.0),0.5*s(20.0,0.0,length(p + vec2(5.0,0.0))-20.0));
// Frieren : "Aura ? Shader yourself !"
// The Pod
// White Disk
col = mix(col,vec4(1.0),s(0.01,0.0,length(p)-20.0));
if( length(p) - 20.0 < 0.0 ) // Basic Masking
{
// 2D Shading : bluish large shadow
col = mix(col,vec4(0.65,0.68,0.68 + 0.1*(3.0-i),1.0),s(0.5,0.0,length(p - vec2(2.0,0.0))-17.0));
// 2D Shading : dark small shadow
// If Outside Porthole Zone
if(s(0.0,1.0,length(vec2(3.0,2.0)*p + vec2(33.5,0.0))-23.0)>0.0)
col = mix(col,vec4(0.45,0.55,0.55 + 0.1*(3.0-i),1.0),0.75*s(0.5,0.0,length(p - vec2(2.0,0.0)+ 0.5*fbm(vec2(4.5*atan(p.y,p.x))))-9.0));
// Small 2D Indentation Details On The Spheres Using A Procedural Texture
// NOTE: Original used texture(iChannel0, ...) which is not supported
// Texture detail removed - not essential for the effect
// vec4 colorCapsule = vec4(hash12(0.0003*p*dot(p,p) + 0.789*i));
// if(colorCapsule.x>0.75)if(s(0.0,1.0,length(vec2(3.0,2.0)*p + vec2(33.5,0.0))-23.0)>0.0)col *= vec4(0.25,0.25,0.25,1.0);
// Bigger Dark Line All Around The Pod
col = mix(col,vec4(0.0),s(0.2,0.0,abs(length(p)-19.9)-0.20));
// Draw The Porthole :
col = mix(col,vec4(0.5,0.2,0.3,1.0) // Base Color
-s(5.0,0.0,length(p + vec2(-6.0,15.0))-20.0) // Main Shadow
-s(0.25,0.0,abs(length(p + vec2(0.0,3.0))-15.0)-0.4)// Vertical Shadow
-s(0.0,1.5,p.y-8.5) // top Shadow
+0.25*vec4(1.0,0.5,0.0,1.0)*s(10.0,0.0,abs(p.y)) // Fake Glass Gradient
,
s(0.5,0.0,length(vec2(3.0,2.0)*p + vec2(35.0,0.0))-19.9));
// Porthole Black Rings
// Internal
col = mix(col,vec4(0.0,0.0,0.0,1.0),s(1.0,0.0,abs(length(vec2(3.0,2.0)*p + vec2(35.0,0.0))-19.9)-0.1));
// External
col = mix(col,vec4(0.0,0.0,0.0,1.0),s(1.0,0.0,abs(length(vec2(3.0,2.0)*p + vec2(33.5,0.0))-23.0)-0.1));
// Pod Tennis-Ball Door Line...
if(p.y>0.0)col = mix(col,vec4(0.0,0.0,0.0,1.0),s(1.0,0.0,abs(length(vec2(3.0,2.0)*p + vec2( 29.0,0.0))-30.0)-0.1));
if(p.y<0.0)col = mix(col,vec4(0.0,0.0,0.0,1.0),s(1.0,0.0,abs(length(vec2(3.0,2.0)*p + vec2(-31.0,0.0))-30.0)-0.1));
};
};
// WAKE UP SHEEPLE !
fragColor = clamp(col,0.0,1.0);
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

2
shaders/dbz/meta.txt Normal file
View File

@@ -0,0 +1,2 @@
Name: New Leaked 3I/Atlas NASA Footage
Author: msm01

View File

@@ -1,3 +1,6 @@
// Name: Fractal Pyramid
// Author: bradjamesgrant
// URL: https://www.shadertoy.com/view/tsXBzS
#version 330 core #version 330 core
precision highp float; precision highp float;

View File

@@ -0,0 +1,2 @@
Name: Fractal Pyramid
Author: bradjamesgrant

View File

@@ -0,0 +1,159 @@
// Name: Just Another Cube
// Author: mrange
// URL: https://www.shadertoy.com/view/3XdXRr
#version 330 core
precision highp float;
out vec4 FragColor;
in vec2 vUV;
uniform vec2 iResolution;
uniform float iTime;
// CC0: Just another cube
// Glowtracers are great for compact coding, but I wanted to see how much
// I could squeeze a more normal raymarcher in terms of characters used.
// Twigl: https://twigl.app?ol=true&ss=-OW-y9xgRgWubwKcn0Nd
// == Globals ==
// Single-letter variable names are used to save characters (code golfing).
mat2 R; // A 2D rotation matrix, calculated once per frame in mainImage and used by D.
float d=1. // Stores the most recent distance to the scene from the ray's position.
, z=0. // Stores the total distance traveled along the ray (initialized to avoid undefined behavior)
, G=9. // "Glow" variable. Tracks the closest the ray comes to the object (for volumetric glow effect).
, M=1e-3
;
// == Distance Function (SDF - Signed Distance Field) ==
// This function calculates the shortest distance from a given point 'p' to the scene geometry.
// A positive result means the point is outside an object, negative is inside, and zero is on the surface.
// This is the core of "raymarching", as it tells us the largest safe step we can take along a ray.
float D(vec3 p) {
// Apply two rotations to the point's coordinates. This twists the space the object
// exists in, making the simple cube shape appear more complex and animated.
p.xy *= R;
p.xz *= R;
// Create a higher-frequency version of the coordinate for detailed surface patterns.
vec3 S = sin(123.*p);
// This creates a volumetric glow effect by tracking the minimum distance
// to either the existing glow value or a glowing shell around the object.
G = min(
G
// The glowing shell
, max(
abs(length(p)-.6)
// The main object distance calculation:
// 1. A superquadric (rounded cube shape) is created using an L8-norm.
// The expression `pow(dot(p=p*p*p*p,p),.125)` is a golfed version of
// `pow(pow(p.x,8)+pow(p.y,8)+pow(p.z,8), 1./8.)`.
// The `- .5` defines the object's size.
, d = pow(dot(p*=p*p*p,p),.125) - .5
// 2. Surface detail subtraction. This creates small surface variations
// using high-frequency sine waves for more appealing reflections.
- pow(1.+S.x*S.y*S.z,8.)/1e5
)
);
return d;
}
// == Main Render Function ==
// This function is called for every pixel on the screen to determine its color.
// 'o' is the final output color (rgba). 'C' is the input pixel coordinate (xy).
void mainImage(out vec4 o, vec2 C) {
// Single-letter variable names are used to save characters (code golfing).
vec3 p // The current point in 3D space along the ray.
, O // Multi-purpose vector: color accumulator, then normal vector, then final color.
, r=vec3(iResolution.xy, iResolution.y) // 'r' holds screen resolution, later re-used for the epsilon vector and reflection.
// 'I' is the Ray Direction vector. It's calculated once per pixel.
// This converts the 2D screen coordinate 'C' into a 3D direction, creating the camera perspective.
, I=normalize(vec3(C-.5*r.xy, r.y))
// Base glow color (dark bluish tint).
, B=vec3(1,2,9)*M
;
// == Raymarching Loop ==
// This loop "marches" a ray from the camera out into the scene to find what it hits.
// It uses a golfed structure where the body of the loop updates the ray position 'p',
// and the "advancement" step moves the ray forward.
for(
// -- Initializer (runs once before the loop) --
// Calculate the rotation matrix for this frame based on time.
R = mat2(cos(.3*iTime+vec4(0,11,33,0)))
// -- Condition --
// Loop while total distance 'z' is less than 9 and we are not yet touching a surface (d > 1e-3).
; z<9. && d > M
// -- Advancement --
// The ray advances by the safe distance 'd' returned by D(p).
// The result of D(p) is also assigned to the global 'd' inside the function.
; z += D(p)
)
// -- Loop Body --
// Calculate the current position 'p' in world space.
// The camera starts at (0,0,-2) and points forward.
p = z*I
, p.z -= 2.
;
// -- Hit Condition --
// If the loop finished because z exceeded the max distance, we hit nothing. Otherwise, we hit the surface.
if (z < 9.) {
// -- Calculate Surface Normal --
// Estimate the gradient ∇D at the hit point 'p' via central differences on the SDF D.
// We use ε = 1e-3 and loop over each axis (x, y, z):
// • Zero r, then set r[i] = ε.
// • Compute O[i] = D(p + r) D(p r).
// After the loop, O holds the unnormalized normal vector.
for (
int i=0 // axis index: 0→x, 1→y, 2→z (initialized to avoid warnings)
; i < 3
; O[i++] = D(p+r) - D(p-r)
)
r -= r // clear r to vec3(0)
, r[i] = M // set only the i-th component
;
// -- Lighting and Shading --
// 'z' is re-purposed to store a fresnel factor (1 - cos(angle)) for edge brightness.
// `dot(O, I)` calculates how much the surface faces away from the camera.
// O is also normalized here to become a proper normal vector.
z = 1.+dot(O = normalize(O),I);
// 'r' is re-purposed to store the reflection vector.
r = reflect(I,O);
// Calculate a point 'C' along the reflection vector 'r' to sample a background color.
// For upward reflections (r.y > 0), this finds the intersection with the plane y=5.
C = (p+r*(5.-p.y)/abs(r.y)).xz;
// Calculate the final color 'O' of the hit point.
O =
// Multiply by the fresnel factor squared for stronger edge reflections.
z*z *
// Use a ternary operator to decide the color based on where the reflection ray goes.
(
// If the reflection vector points upward...
r.y>0.
// ...sample a procedural "sky" with a radial gradient and blue tint.
? 5e2*smoothstep(5., 4., d = sqrt(length(C*C))+1.)*d*B
// ...otherwise, sample a "floor" with a deep blue exponential falloff.
: exp(-2.*length(C))*(B/M-1.)
)
// Add rim lighting (brighter on upward-facing surfaces).
+ pow(1.+O.y,5.)*B
;
}
// == Tonemapping & Output ==
// Apply final effects and map the High Dynamic Range (HDR) color to a displayable range.
// Add glow contribution: smaller G values (closer ray passes) create a brighter blue glow.
o = sqrt(O+B/G).xyzx;
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

@@ -0,0 +1,2 @@
Name: Just Another Cube
Author: mrange

View File

@@ -0,0 +1,2 @@
Name: Octograms
Author: whisky_shusuky

View File

@@ -1,3 +1,6 @@
// Name: Octograms
// Author: whisky_shusuky
// URL: https://www.shadertoy.com/view/tlVGDt
#version 330 core #version 330 core
precision highp float; precision highp float;

View File

@@ -0,0 +1,2 @@
Name: Remember
Author: diatribes

View File

@@ -0,0 +1,73 @@
// Name: Remember
// Author: diatribes
// URL: https://www.shadertoy.com/view/tXSBDK
#version 330 core
precision highp float;
out vec4 FragColor;
in vec2 vUV;
uniform vec2 iResolution;
uniform float iTime;
// fuzzy brain
// Hash function to replace iChannel0 texture noise
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
void mainImage(out vec4 o, vec2 u) {
vec3 q,p = vec3(iResolution.xy, iResolution.x / iResolution.y);
float i = 0.0, s,
// start the ray at a small random distance,
// this will reduce banding
// Replaced texelFetch(iChannel0, ...) with hash function
d = .125 * hash12(u),
t = iTime * .1;
// scale coords
u = (u+u-p.xy)/p.y;
if (abs(u.y) > .8) { o = vec4(0); return; }
// Initialize output color (out parameter must be initialized before use)
o = vec4(0.0);
for(; i<64.; i++) {
// shorthand for standard raymarch sample, then move forward:
// p = ro + rd * d, p.z + t
q = p = vec3(u * d, d + t*5.);
p.xy *= mat2(cos(.1*p.z+.1*t+vec4(0,33,11,0)));
q.xz = cos(q.xz);
p.z = cos(p.z) ;
// turbulence
for (s = 1.; s++ <6.;
q += sin(.6*t+p.zxy*.6),
p += sin(t+t+p.yzx*s)*.6);
// distance to spheres
d += s = .02 + abs(min(length(p+3.*sin(p.z*.5))-4., length(q-2.*sin(p.z*.4))-6.))*.2;
// color: 1.+cos so we don't go negative, cos(d+vec4(6,4,2,0)) samples from the palette
// divide by s for form and distance
// Clamp only the first term to prevent extreme overflow, leave second term free
vec4 brightTerm = min(.01*vec4(6,2,1,0)/max(length(u*sin(t+t+t)), 0.001), vec4(50.0));
o += brightTerm + 1. / s * length(u);
}
// tonemap and divide brightness
o = tanh(max(o /6e2 + dot(u,u)*.35, 0.));
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

@@ -0,0 +1,2 @@
Name: Seascape
Author: Alexander Alekseev

View File

@@ -0,0 +1,223 @@
// Name: Seascape
// Author: Alexander Alekseev
// URL: https://www.shadertoy.com/view/Ms2SD1
#version 330 core
precision highp float;
out vec4 FragColor;
in vec2 vUV;
uniform vec2 iResolution;
uniform float iTime;
/*
* "Seascape" by Alexander Alekseev aka TDM - 2014
* License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
* Contact: tdmaav@gmail.com
*/
const int NUM_STEPS = 32;
const float PI = 3.141592;
const float EPSILON = 1e-3;
#define EPSILON_NRM (0.1 / iResolution.x)
//#define AA
// sea
const int ITER_GEOMETRY = 3;
const int ITER_FRAGMENT = 5;
const float SEA_HEIGHT = 0.6;
const float SEA_CHOPPY = 4.0;
const float SEA_SPEED = 0.8;
const float SEA_FREQ = 0.16;
const vec3 SEA_BASE = vec3(0.0,0.09,0.18);
const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6)*0.6;
#define SEA_TIME (1.0 + iTime * SEA_SPEED)
const mat2 octave_m = mat2(1.6,1.2,-1.2,1.6);
// math
mat3 fromEuler(vec3 ang) {
vec2 a1 = vec2(sin(ang.x),cos(ang.x));
vec2 a2 = vec2(sin(ang.y),cos(ang.y));
vec2 a3 = vec2(sin(ang.z),cos(ang.z));
mat3 m;
m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x);
m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x);
m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y);
return m;
}
float hash( vec2 p ) {
float h = dot(p,vec2(127.1,311.7));
return fract(sin(h)*43758.5453123);
}
float noise( in vec2 p ) {
vec2 i = floor( p );
vec2 f = fract( p );
vec2 u = f*f*(3.0-2.0*f);
return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ),
hash( i + vec2(1.0,0.0) ), u.x),
mix( hash( i + vec2(0.0,1.0) ),
hash( i + vec2(1.0,1.0) ), u.x), u.y);
}
// lighting
float diffuse(vec3 n,vec3 l,float p) {
return pow(dot(n,l) * 0.4 + 0.6,p);
}
float specular(vec3 n,vec3 l,vec3 e,float s) {
float nrm = (s + 8.0) / (PI * 8.0);
return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
}
// sky
vec3 getSkyColor(vec3 e) {
e.y = (max(e.y,0.0)*0.8+0.2)*0.8;
return vec3(pow(1.0-e.y,2.0), 1.0-e.y, 0.6+(1.0-e.y)*0.4) * 1.1;
}
// sea
float sea_octave(vec2 uv, float choppy) {
uv += noise(uv);
vec2 wv = 1.0-abs(sin(uv));
vec2 swv = abs(cos(uv));
wv = mix(wv,swv,wv);
return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
}
float map(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
float d, h = 0.0;
for(int i = 0; i < ITER_GEOMETRY; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= octave_m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
float map_detailed(vec3 p) {
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
vec2 uv = p.xz; uv.x *= 0.75;
float d, h = 0.0;
for(int i = 0; i < ITER_FRAGMENT; i++) {
d = sea_octave((uv+SEA_TIME)*freq,choppy);
d += sea_octave((uv-SEA_TIME)*freq,choppy);
h += d * amp;
uv *= octave_m; freq *= 1.9; amp *= 0.22;
choppy = mix(choppy,1.0,0.2);
}
return p.y - h;
}
vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) {
float fresnel = clamp(1.0 - dot(n, -eye), 0.0, 1.0);
fresnel = min(fresnel * fresnel * fresnel, 0.5);
vec3 reflected = getSkyColor(reflect(eye, n));
vec3 refracted = SEA_BASE + diffuse(n, l, 80.0) * SEA_WATER_COLOR * 0.12;
vec3 color = mix(refracted, reflected, fresnel);
float atten = max(1.0 - dot(dist, dist) * 0.001, 0.0);
color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
color += specular(n, l, eye, 600.0 * inversesqrt(dot(dist,dist)));
return color;
}
// tracing
vec3 getNormal(vec3 p, float eps) {
vec3 n;
n.y = map_detailed(p);
n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
n.y = eps;
return normalize(n);
}
float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) {
float tm = 0.0;
float tx = 1000.0;
float hx = map(ori + dir * tx);
if(hx > 0.0) {
p = ori + dir * tx;
return tx;
}
float hm = map(ori);
for(int i = 0; i < NUM_STEPS; i++) {
float tmid = mix(tm, tx, hm / (hm - hx));
p = ori + dir * tmid;
float hmid = map(p);
if(hmid < 0.0) {
tx = tmid;
hx = hmid;
} else {
tm = tmid;
hm = hmid;
}
if(abs(hmid) < EPSILON) break;
}
return mix(tm, tx, hm / (hm - hx));
}
vec3 getPixel(in vec2 coord, float time) {
vec2 uv = coord / iResolution.xy;
uv = uv * 2.0 - 1.0;
uv.x *= iResolution.x / iResolution.y;
// ray
vec3 ang = vec3(sin(time*3.0)*0.1,sin(time)*0.2+0.3,time);
vec3 ori = vec3(0.0,3.5,time*5.0);
vec3 dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.14;
dir = normalize(dir) * fromEuler(ang);
// tracing
vec3 p;
heightMapTracing(ori,dir,p);
vec3 dist = p - ori;
vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM);
vec3 light = normalize(vec3(0.0,1.0,0.8));
// color
return mix(
getSkyColor(dir),
getSeaColor(p,n,light,dir,dist),
pow(smoothstep(0.0,-0.02,dir.y),0.2));
}
// main
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
// Removed mouse interaction (iMouse not available)
float time = iTime * 0.3;
#ifdef AA
vec3 color = vec3(0.0);
for(int i = -1; i <= 1; i++) {
for(int j = -1; j <= 1; j++) {
vec2 uv = fragCoord+vec2(i,j)/3.0;
color += getPixel(uv, time);
}
}
color /= 9.0;
#else
vec3 color = getPixel(fragCoord, time);
#endif
// post
fragColor = vec4(pow(color,vec3(0.65)), 1.0);
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

@@ -0,0 +1,2 @@
Name: Shader Art Coding Introduction
Author: kishimisu

View File

@@ -1,3 +1,6 @@
// Name: Shader Art Coding Introduction
// Author: kishimisu
// URL: https://www.shadertoy.com/view/mtyGWy
#version 330 core #version 330 core
precision highp float; precision highp float;

2
shaders/test/meta.txt Normal file
View File

@@ -0,0 +1,2 @@
Name: Test
Author: JailDesigner

View File

@@ -0,0 +1,36 @@
#include <metal_stdlib>
using namespace metal;
// Test shader (Metal Shading Language port of test.vk.glsl).
// Author: JailDesigner
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
static float3 palette(float t) {
float3 a = float3(1.0, 0.5, 0.5);
float3 b = float3(1.0, 0.5, 0.5);
float3 c = float3(1.0, 1.0, 1.0);
float3 d = float3(0.263, 0.416, 0.557);
return a + b * cos(6.28318 * (c * t * d));
}
fragment float4 test_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& u [[buffer(0)]]) {
float2 fragCoord = in.uv * u.iResolution;
float2 uv = (fragCoord * 2.0 - u.iResolution) / u.iResolution.y;
float d = length(uv);
float3 col = palette(d);
d = sin(d * 8.0 + u.iTime) / 8.0;
d = abs(d);
d = 0.02 / d;
col *= d;
return float4(col, 1.0);
}

BIN
shaders/test/test.frag.spv Normal file

Binary file not shown.

View File

@@ -1,3 +1,5 @@
// Name: Test
// Author: JailDesigner
#version 330 core #version 330 core
out vec4 FragColor; out vec4 FragColor;
in vec2 vUV; in vec2 vUV;

41
shaders/test/test.vk.glsl Normal file
View File

@@ -0,0 +1,41 @@
#version 450
// Name: Test
// Author: JailDesigner
//
// Vulkan port of test.gl.glsl. Bindings follow the SDL3 GPU convention:
// set=3, binding=0 — fragment uniform buffer (ShadertoyUniforms in C++).
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
} u;
vec3 palette(float t) {
vec3 a = vec3(1.0, 0.5, 0.5);
vec3 b = vec3(1.0, 0.5, 0.5);
vec3 c = vec3(1.0, 1.0, 1.0);
vec3 d = vec3(0.263, 0.416, 0.557);
return a + b * cos(6.28318 * (c * t * d));
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord * 2.0 - u.iResolution) / u.iResolution.y;
float d = length(uv);
vec3 col = palette(d);
d = sin(d * 8.0 + u.iTime) / 8.0;
d = abs(d);
d = 0.02 / d;
col *= d;
fragColor = vec4(col, 1.0);
}
void main() {
vec2 fragCoordPixels = vUV * u.iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

2
shaders/water/meta.txt Normal file
View File

@@ -0,0 +1,2 @@
Name: Water
Author: diatribes

View File

@@ -0,0 +1,44 @@
// Name: Water
// Author: diatribes
// URL: https://www.shadertoy.com/view/tXjXDy
#version 330 core
precision highp float;
out vec4 FragColor;
in vec2 vUV;
uniform vec2 iResolution;
uniform float iTime;
/*
-2 by @FabriceNeyret2
thanks!! :D
If it doesn't display correctly, change line 17 "r/r" to "vec3(1)"
*/
void mainImage( out vec4 o, vec2 u ) {
float s=.002, i=0., n; // FIXED: Initialize i=0
vec3 r = vec3(iResolution.xy, iResolution.x/iResolution.y);
vec3 p = vec3(0);
u = (u-r.xy/2.)/r.y-.3;
o = vec4(0); // FIXED: Initialize output to black
for(; i < 32. && s > .001; i++) {
// Clamp only extreme overflow values, let normal brightness through
vec4 term = vec4(5,2,1,0)/max(length(u-.1), 0.001);
o += min(term, vec4(100.0));
for (p += vec3(u*s,s), s = 1. + p.y, n =.01; n < 1.; n+=n) {
s += abs(dot(sin(p.z+iTime+p / n), vec3(1))) * n*.1;
}
}
o = tanh(o/5e2);
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

@@ -6,6 +6,9 @@
// Nombre de la aplicación // Nombre de la aplicación
constexpr const char* APP_NAME = "Shadertoy"; constexpr const char* APP_NAME = "Shadertoy";
// Prefijo del título de la ventana (estilo aee_2026).
constexpr const char* WINDOW_TITLE_PREFIX = "\xC2\xA9 2025 Shadertoy \xE2\x80\x94 JailDesigner";
// Tamaño de ventana por defecto // Tamaño de ventana por defecto
constexpr int WINDOW_WIDTH = 800; constexpr int WINDOW_WIDTH = 800;
constexpr int WINDOW_HEIGHT = 800; constexpr int WINDOW_HEIGHT = 800;

View File

@@ -1,29 +1,29 @@
// src/main.cpp // src/main.cpp
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm> #include <algorithm>
#include <type_traits> #include <ctime>
#include <filesystem> #include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <glad/glad.h>
#include "defines.hpp" #include "defines.hpp"
#include "jail_audio.h"
#include "rendering/shader_backend.hpp"
// Simple logger compatible con el estilo que usas
struct Logger { struct Logger {
static void info(const std::string& s) { std::cout << "[INFO] " << s << '\n'; } static void info(const std::string& s) { std::cout << "[INFO] " << s << '\n'; }
static void error(const std::string& s) { std::cerr << "[ERROR] " << s << '\n'; } static void error(const std::string& s) { std::cerr << "[ERROR] " << s << '\n'; }
}; };
// Opciones mínimas parecidas a las tuyas
struct VideoOptions { struct VideoOptions {
bool fullscreen = false; bool fullscreen = false;
bool vsync = true;
} Options_video; } Options_video;
// Estructura para guardar info del display
struct DisplayMonitor { struct DisplayMonitor {
std::string name; std::string name;
int width = 0; int width = 0;
@@ -31,152 +31,186 @@ struct DisplayMonitor {
int refresh_rate = 0; int refresh_rate = 0;
}; };
// Globales simplificados (tu proyecto puede integrarlo en clases)
static DisplayMonitor display_monitor_; static DisplayMonitor display_monitor_;
static SDL_Window* window_ = nullptr; static SDL_Window* window_ = nullptr;
static std::unique_ptr<Rendering::IShaderBackend> backend_;
// Sistema de shaders struct ShaderEntry {
static std::vector<std::filesystem::path> shader_list_; std::filesystem::path folder;
std::string base_name;
};
static std::vector<ShaderEntry> shader_list_;
static std::vector<std::string> shader_names_;
static std::vector<std::string> shader_authors_;
static size_t current_shader_index_ = 0; static size_t current_shader_index_ = 0;
static std::filesystem::path shaders_directory_; static std::filesystem::path shaders_directory_;
static GLuint current_program_ = 0;
static Uint32 shader_start_ticks_ = 0; static Uint32 shader_start_ticks_ = 0;
// Vertex shader embebido static Uint32 fps_frame_count_ = 0;
static const char* vertexShaderSrc = R"glsl( static Uint32 fps_last_update_ticks_ = 0;
#version 330 core static float current_fps_ = 0.0f;
layout(location = 0) in vec2 aPos;
out vec2 vUV;
void main() {
vUV = aPos * 0.5 + 0.5;
gl_Position = vec4(aPos, 0.0, 1.0);
}
)glsl";
// Helpers de carga static std::vector<std::filesystem::path> music_list_;
static bool loadFileToString(const std::filesystem::path& path, std::string& out) { static size_t current_music_index_ = 0;
std::ifstream ifs(path, std::ios::in | std::ios::binary); static JA_Music_t* current_music_ = nullptr;
if (!ifs) return false;
std::ostringstream ss;
ss << ifs.rdbuf();
out = ss.str();
return true;
}
static std::vector<std::filesystem::path> scanShaderDirectory(const std::filesystem::path& directory) { static std::vector<ShaderEntry> scanShaderDirectory(const std::filesystem::path& directory) {
std::vector<std::filesystem::path> shaders; std::vector<ShaderEntry> shaders;
if (!std::filesystem::exists(directory) || !std::filesystem::is_directory(directory)) { if (!std::filesystem::exists(directory) || !std::filesystem::is_directory(directory)) {
Logger::error("Shader directory does not exist: " + directory.string()); Logger::error("Shader directory does not exist: " + directory.string());
return shaders; return shaders;
} }
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
if (!entry.is_directory()) { continue; }
const std::string folder_name = entry.path().filename().string();
if (folder_name.empty() || folder_name[0] == '_' || folder_name[0] == '.') { continue; }
const std::filesystem::path gl_source = entry.path() / (folder_name + ".gl.glsl");
if (!std::filesystem::exists(gl_source)) {
Logger::info("Skipping " + folder_name + ": missing " + gl_source.filename().string());
continue;
}
shaders.push_back(ShaderEntry{entry.path(), folder_name});
}
std::sort(shaders.begin(), shaders.end(),
[](const ShaderEntry& a, const ShaderEntry& b) { return a.base_name < b.base_name; });
Logger::info("Found " + std::to_string(shaders.size()) + " shader(s) in " + directory.string());
shader_names_.resize(shaders.size(), "");
shader_authors_.resize(shaders.size(), "");
return shaders;
}
static std::vector<std::filesystem::path> scanMusicDirectory(const std::filesystem::path& directory) {
std::vector<std::filesystem::path> music_files;
if (!std::filesystem::exists(directory) || !std::filesystem::is_directory(directory)) {
Logger::info("Music directory does not exist: " + directory.string());
return music_files;
}
for (const auto& entry : std::filesystem::directory_iterator(directory)) { for (const auto& entry : std::filesystem::directory_iterator(directory)) {
if (entry.is_regular_file()) { if (entry.is_regular_file()) {
auto ext = entry.path().extension().string(); const auto ext = entry.path().extension().string();
if (ext == ".glsl") { if (ext == ".ogg") {
shaders.push_back(entry.path()); music_files.push_back(entry.path());
} }
} }
} }
// Ordenar alfabéticamente std::sort(music_files.begin(), music_files.end());
std::sort(shaders.begin(), shaders.end());
Logger::info("Found " + std::to_string(shaders.size()) + " shader(s) in " + directory.string()); Logger::info("Found " + std::to_string(music_files.size()) + " music file(s) in " + directory.string());
return shaders; return music_files;
}
static void playRandomMusic() {
if (music_list_.empty()) { return; }
if (current_music_ != nullptr) {
JA_DeleteMusic(current_music_);
current_music_ = nullptr;
}
current_music_index_ = static_cast<size_t>(rand()) % music_list_.size();
const auto& music_path = music_list_[current_music_index_];
current_music_ = JA_LoadMusic(music_path.string().c_str());
if (current_music_ != nullptr) {
JA_PlayMusic(current_music_, 0);
Logger::info("Now playing: " + music_path.filename().string());
} else {
Logger::error("Failed to load music: " + music_path.string());
}
} }
static void updateWindowTitle() { static void updateWindowTitle() {
if (!window_ || shader_list_.empty()) return; if (window_ == nullptr || shader_list_.empty()) { return; }
std::string shaderName;
if (!shader_names_.empty() && !shader_names_[current_shader_index_].empty()) {
shaderName = shader_names_[current_shader_index_];
} else {
shaderName = shader_list_[current_shader_index_].base_name;
}
if (!shader_authors_.empty() && !shader_authors_[current_shader_index_].empty()) {
shaderName += " by " + shader_authors_[current_shader_index_];
}
std::string title = WINDOW_TITLE_PREFIX;
title += " (";
title += shaderName;
if (backend_) {
title += " - ";
title += backend_->driverName();
}
if (current_fps_ > 0.0f) {
title += " - ";
title += std::to_string(static_cast<int>(current_fps_ + 0.5f)) + " FPS";
}
if (Options_video.vsync) {
title += " - VSync";
}
title += ")";
std::string filename = shader_list_[current_shader_index_].filename().string();
std::string title = std::string(APP_NAME) + " (" + filename + ")";
SDL_SetWindowTitle(window_, title.c_str()); SDL_SetWindowTitle(window_, title.c_str());
} }
static GLuint compileShader(GLenum type, const char* src) { static bool loadShaderAtIndex(size_t index) {
GLuint s = glCreateShader(type);
glShaderSource(s, 1, &src, nullptr);
glCompileShader(s);
GLint ok = 0;
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) {
GLint len = 0;
glGetShaderiv(s, GL_INFO_LOG_LENGTH, &len);
std::string log(len > 0 ? len : 1, ' ');
glGetShaderInfoLog(s, len, nullptr, &log[0]);
Logger::error("Shader compile error: " + log);
glDeleteShader(s);
return 0;
}
return s;
}
static GLuint linkProgram(GLuint vs, GLuint fs) {
GLuint p = glCreateProgram();
glAttachShader(p, vs);
glAttachShader(p, fs);
glLinkProgram(p);
GLint ok = 0;
glGetProgramiv(p, GL_LINK_STATUS, &ok);
if (!ok) {
GLint len = 0;
glGetProgramiv(p, GL_INFO_LOG_LENGTH, &len);
std::string log(len > 0 ? len : 1, ' ');
glGetProgramInfoLog(p, len, nullptr, &log[0]);
Logger::error("Program link error: " + log);
glDeleteProgram(p);
return 0;
}
return p;
}
static GLuint loadAndCompileShader(size_t index) {
if (index >= shader_list_.size()) { if (index >= shader_list_.size()) {
Logger::error("Invalid shader index: " + std::to_string(index)); Logger::error("Invalid shader index: " + std::to_string(index));
return 0; return false;
} }
const auto& shaderPath = shader_list_[index]; const auto& entry = shader_list_[index];
Logger::info("Loading shader: " + shaderPath.string()); Logger::info("Loading shader: " + entry.folder.string());
std::string fragSrc; Rendering::ShaderProgramSpec spec;
if (!loadFileToString(shaderPath, fragSrc)) { spec.folder = entry.folder;
Logger::error("Failed to load shader file: " + shaderPath.string()); spec.base_name = entry.base_name;
return 0;
const std::filesystem::path meta_path = entry.folder / "meta.txt";
if (std::filesystem::exists(meta_path)) {
spec.metadata = Rendering::parseMetaFile(meta_path);
} else {
std::string source;
const std::filesystem::path gl_source = entry.folder / (entry.base_name + ".gl.glsl");
if (Rendering::loadFileToString(gl_source, source)) {
spec.metadata = Rendering::extractShaderMetadata(source);
}
} }
GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc); if (!spec.metadata.name.empty()) {
GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragSrc.c_str()); shader_names_[index] = spec.metadata.name;
Logger::info("Shader name: " + spec.metadata.name);
if (!vs || !fs) { }
if (vs) glDeleteShader(vs); if (!spec.metadata.author.empty()) {
if (fs) glDeleteShader(fs); shader_authors_[index] = spec.metadata.author;
Logger::error("Shader compilation failed for: " + shaderPath.string()); Logger::info("Shader author: " + spec.metadata.author);
return 0;
} }
GLuint program = linkProgram(vs, fs); return backend_->loadShader(spec);
glDeleteShader(vs);
glDeleteShader(fs);
if (!program) {
Logger::error("Program linking failed for: " + shaderPath.string());
return 0;
}
Logger::info("Shader loaded successfully: " + shaderPath.filename().string());
return program;
} }
// --- Funciones basadas en tu código ---
void getDisplayInfo() { void getDisplayInfo() {
int num_displays = 0; int num_displays = 0;
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays); SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
if (displays != nullptr && num_displays > 0) { if (displays != nullptr && num_displays > 0) {
for (int i = 0; i < num_displays; ++i) { for (int i = 0; i < num_displays; ++i) {
SDL_DisplayID instance_id = displays[i]; const SDL_DisplayID instance_id = displays[i];
const char* name = SDL_GetDisplayName(instance_id); const char* name = SDL_GetDisplayName(instance_id);
Logger::info(std::string("Display ") + std::to_string(instance_id) + ": " + (name != nullptr ? name : "Unknown")); Logger::info(std::string("Display ") + std::to_string(instance_id) + ": " + (name != nullptr ? name : "Unknown"));
} }
@@ -184,7 +218,7 @@ void getDisplayInfo() {
const SDL_DisplayMode* dm = SDL_GetCurrentDisplayMode(displays[0]); const SDL_DisplayMode* dm = SDL_GetCurrentDisplayMode(displays[0]);
const char* first_display_name = SDL_GetDisplayName(displays[0]); const char* first_display_name = SDL_GetDisplayName(displays[0]);
display_monitor_.name = (first_display_name != nullptr) ? first_display_name : "Unknown"; display_monitor_.name = (first_display_name != nullptr) ? first_display_name : "Unknown";
if (dm) { if (dm != nullptr) {
display_monitor_.width = static_cast<int>(dm->w); display_monitor_.width = static_cast<int>(dm->w);
display_monitor_.height = static_cast<int>(dm->h); display_monitor_.height = static_cast<int>(dm->h);
display_monitor_.refresh_rate = static_cast<int>(dm->refresh_rate); display_monitor_.refresh_rate = static_cast<int>(dm->refresh_rate);
@@ -197,22 +231,23 @@ void getDisplayInfo() {
} }
void setFullscreenMode() { void setFullscreenMode() {
if (!window_) return; if (window_ == nullptr) { return; }
if (Options_video.fullscreen) { if (Options_video.fullscreen) {
// Intentar fullscreen
if (!SDL_SetWindowFullscreen(window_, true)) { if (!SDL_SetWindowFullscreen(window_, true)) {
// Fallback: volver a modo ventana si falla
Logger::error(std::string("Failed to set fullscreen: ") + SDL_GetError()); Logger::error(std::string("Failed to set fullscreen: ") + SDL_GetError());
Logger::info("Fallback to windowed mode 800x800"); Logger::info("Fallback to windowed mode 800x800");
SDL_SetWindowFullscreen(window_, false); SDL_SetWindowFullscreen(window_, false);
SDL_SetWindowSize(window_, WINDOW_WIDTH, WINDOW_HEIGHT); SDL_SetWindowSize(window_, WINDOW_WIDTH, WINDOW_HEIGHT);
Options_video.fullscreen = false; Options_video.fullscreen = false;
SDL_ShowCursor();
} else {
SDL_HideCursor();
} }
} else { } else {
// Volver a modo ventana 800x800
SDL_SetWindowFullscreen(window_, false); SDL_SetWindowFullscreen(window_, false);
SDL_SetWindowSize(window_, WINDOW_WIDTH, WINDOW_HEIGHT); SDL_SetWindowSize(window_, WINDOW_WIDTH, WINDOW_HEIGHT);
SDL_ShowCursor();
} }
} }
@@ -221,10 +256,16 @@ void toggleFullscreen() {
setFullscreenMode(); setFullscreenMode();
} }
void switchShader(int direction) { void toggleVSync() {
if (shader_list_.empty()) return; Options_video.vsync = !Options_video.vsync;
if (backend_) {
backend_->setVSync(Options_video.vsync);
}
}
void switchShader(int direction) {
if (shader_list_.empty()) { return; }
// Calcular nuevo índice con wrap-around cíclico
size_t new_index = current_shader_index_; size_t new_index = current_shader_index_;
if (direction > 0) { if (direction > 0) {
new_index = (current_shader_index_ + 1) % shader_list_.size(); new_index = (current_shader_index_ + 1) % shader_list_.size();
@@ -232,140 +273,150 @@ void switchShader(int direction) {
new_index = (current_shader_index_ == 0) ? shader_list_.size() - 1 : current_shader_index_ - 1; new_index = (current_shader_index_ == 0) ? shader_list_.size() - 1 : current_shader_index_ - 1;
} }
// Intentar cargar el nuevo shader if (!loadShaderAtIndex(new_index)) {
GLuint new_program = loadAndCompileShader(new_index);
if (new_program == 0) {
Logger::error("Failed to switch shader, keeping current one"); Logger::error("Failed to switch shader, keeping current one");
return; return;
} }
// Éxito: eliminar programa anterior y actualizar
if (current_program_ != 0) {
glDeleteProgram(current_program_);
}
current_program_ = new_program;
current_shader_index_ = new_index; current_shader_index_ = new_index;
shader_start_ticks_ = SDL_GetTicks(); shader_start_ticks_ = SDL_GetTicks();
updateWindowTitle(); updateWindowTitle();
} }
// Manejo de teclas
void handleDebugEvents(const SDL_Event& event) { void handleDebugEvents(const SDL_Event& event) {
// evitar repetición de teclas: event.key.repeat disponible en SDL3
if (event.type == SDL_EVENT_KEY_DOWN && static_cast<int>(event.key.repeat) == 0) { if (event.type == SDL_EVENT_KEY_DOWN && static_cast<int>(event.key.repeat) == 0) {
switch (event.key.key) { switch (event.key.key) {
case SDLK_F3: { case SDLK_F3: { toggleFullscreen(); break; }
toggleFullscreen(); case SDLK_F4: { toggleVSync(); break; }
break; case SDLK_LEFT: { switchShader(-1); break; }
} case SDLK_RIGHT: { switchShader(+1); break; }
case SDLK_LEFT: { default: break;
switchShader(-1);
break;
}
case SDLK_RIGHT: {
switchShader(+1);
break;
}
default:
break;
} }
} }
} }
// --- main (integra todo y soporta -F y argv[1] para shader path) --- enum class BackendChoice { Auto, Gpu, OpenGL };
static auto createWindowForBackend(BackendChoice choice) -> SDL_Window* {
SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE;
if (choice == BackendChoice::OpenGL) {
flags |= SDL_WINDOW_OPENGL;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
}
return SDL_CreateWindow(APP_NAME, WINDOW_WIDTH, WINDOW_HEIGHT, flags);
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
std::string shaderPath; std::string shaderPath;
bool fullscreenFlag = false; bool fullscreenFlag = false;
BackendChoice backend_choice = BackendChoice::Auto;
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
std::string a = argv[i]; const std::string a = argv[i];
if (a == "-F" || a == "--fullscreen") { fullscreenFlag = true; continue; } if (a == "-F" || a == "--fullscreen") { fullscreenFlag = true; continue; }
if (shaderPath.empty()) shaderPath = a; if (a == "--backend=gpu") { backend_choice = BackendChoice::Gpu; continue; }
if (a == "--backend=opengl") { backend_choice = BackendChoice::OpenGL; continue; }
if (a == "--backend=auto") { backend_choice = BackendChoice::Auto; continue; }
if (shaderPath.empty()) { shaderPath = a; }
} }
if (shaderPath.empty()) shaderPath = "test.frag.glsl"; if (shaderPath.empty()) { shaderPath = "test"; }
Options_video.fullscreen = fullscreenFlag; Options_video.fullscreen = fullscreenFlag;
// Inicializar SDL3 auto initResult = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
auto initResult = SDL_Init(SDL_INIT_VIDEO);
if constexpr (std::is_same_v<decltype(initResult), bool>) { if constexpr (std::is_same_v<decltype(initResult), bool>) {
if (!initResult) { Logger::error(SDL_GetError()); return -1; } if (!initResult) { Logger::error(SDL_GetError()); return -1; }
} else { } else {
if (initResult != 0) { Logger::error(SDL_GetError()); return -1; } if (initResult != 0) { Logger::error(SDL_GetError()); return -1; }
} }
// Obtener información del display antes de crear ventana
getDisplayInfo(); getDisplayInfo();
// Atributos GL if (backend_choice != BackendChoice::OpenGL) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); window_ = createWindowForBackend(BackendChoice::Gpu);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); if (window_ != nullptr) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); backend_ = Rendering::makeSdl3GpuBackend();
if (!backend_->init(window_)) {
Logger::info("SDL3 GPU backend init failed, falling back to OpenGL");
backend_.reset();
SDL_DestroyWindow(window_);
window_ = nullptr;
if (backend_choice == BackendChoice::Gpu) {
SDL_Quit();
return -1;
}
}
}
}
// Crear ventana if (backend_ == nullptr) {
window_ = SDL_CreateWindow(APP_NAME, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); window_ = createWindowForBackend(BackendChoice::OpenGL);
if (!window_) { Logger::error(std::string("SDL_CreateWindow error: ") + SDL_GetError()); SDL_Quit(); return -1; } if (window_ == nullptr) {
Logger::error(std::string("SDL_CreateWindow error: ") + SDL_GetError());
SDL_Quit();
return -1;
}
backend_ = Rendering::makeOpenGLBackend();
if (!backend_->init(window_)) {
Logger::error("Failed to initialize shader backend");
SDL_DestroyWindow(window_);
SDL_Quit();
return -1;
}
}
// Aplicar fullscreen si el flag estaba activado
setFullscreenMode(); setFullscreenMode();
backend_->setVSync(Options_video.vsync);
// Crear contexto GL JA_Init(48000, SDL_AUDIO_S16LE, 2);
SDL_GLContext glContext = SDL_GL_CreateContext(window_);
if (!glContext) { const std::string resources_dir = getResourcesDirectory();
Logger::error(std::string("SDL_GL_CreateContext error: ") + SDL_GetError());
SDL_DestroyWindow(window_); srand(static_cast<unsigned int>(time(nullptr)));
SDL_Quit();
return -1; const std::filesystem::path music_directory = std::filesystem::path(resources_dir) / "data" / "music";
music_list_ = scanMusicDirectory(music_directory);
if (!music_list_.empty()) {
playRandomMusic();
} else {
Logger::info("No music files found in " + music_directory.string());
} }
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { const std::filesystem::path arg_path(shaderPath);
Logger::error("Failed to initialize GL loader"); std::filesystem::path target_folder;
SDL_GL_DestroyContext(glContext); if (arg_path.has_parent_path()) {
SDL_DestroyWindow(window_); target_folder = arg_path;
SDL_Quit(); shaders_directory_ = arg_path.parent_path();
return -1;
}
// Obtener directorio de recursos
std::string resources_dir = getResourcesDirectory();
// Determinar carpeta de shaders
std::filesystem::path shaderFile(shaderPath);
if (shaderFile.has_parent_path()) {
shaders_directory_ = shaderFile.parent_path();
} else { } else {
shaders_directory_ = std::filesystem::path(resources_dir) / "shaders"; shaders_directory_ = std::filesystem::path(resources_dir) / "shaders";
target_folder = shaders_directory_ / shaderPath;
} }
// Escanear carpeta de shaders
shader_list_ = scanShaderDirectory(shaders_directory_); shader_list_ = scanShaderDirectory(shaders_directory_);
if (shader_list_.empty()) { if (shader_list_.empty()) {
Logger::error("No shaders found in directory: " + shaders_directory_.string()); Logger::error("No shaders found in directory: " + shaders_directory_.string());
SDL_GL_DestroyContext(glContext); backend_->cleanup();
SDL_DestroyWindow(window_); SDL_DestroyWindow(window_);
SDL_Quit(); SDL_Quit();
return -1; return -1;
} }
// Determinar shader inicial
size_t initial_index = 0; size_t initial_index = 0;
bool found_shader = false; bool found_shader = false;
// Intentar encontrar el shader especificado
std::filesystem::path target_shader = shaderFile.has_parent_path() ? shaderFile : (shaders_directory_ / shaderFile.filename());
for (size_t i = 0; i < shader_list_.size(); ++i) { for (size_t i = 0; i < shader_list_.size(); ++i) {
if (shader_list_[i] == target_shader) { if (shader_list_[i].folder == target_folder) {
initial_index = i; initial_index = i;
found_shader = true; found_shader = true;
break; break;
} }
} }
// Si no se encuentra, intentar con shader por defecto (test.frag.glsl)
if (!found_shader) { if (!found_shader) {
std::filesystem::path default_path = std::filesystem::path(resources_dir) / "shaders" / "test.frag.glsl"; const std::filesystem::path default_folder = std::filesystem::path(resources_dir) / "shaders" / "test";
for (size_t i = 0; i < shader_list_.size(); ++i) { for (size_t i = 0; i < shader_list_.size(); ++i) {
if (shader_list_[i] == default_path) { if (shader_list_[i].folder == default_folder) {
initial_index = i; initial_index = i;
found_shader = true; found_shader = true;
break; break;
@@ -373,96 +424,81 @@ int main(int argc, char** argv) {
} }
} }
// Si aún no se encuentra, usar el primer shader de la lista
if (!found_shader) { if (!found_shader) {
Logger::info("Specified shader not found, using first shader in directory"); Logger::info("Specified shader not found, using first shader in directory");
initial_index = 0; initial_index = 0;
} }
// Cargar shader inicial
current_shader_index_ = initial_index; current_shader_index_ = initial_index;
current_program_ = loadAndCompileShader(current_shader_index_); if (!loadShaderAtIndex(current_shader_index_)) {
if (current_program_ == 0) {
Logger::error("Failed to load initial shader"); Logger::error("Failed to load initial shader");
SDL_GL_DestroyContext(glContext); backend_->cleanup();
SDL_DestroyWindow(window_); SDL_DestroyWindow(window_);
SDL_Quit(); SDL_Quit();
return -1; return -1;
} }
shader_start_ticks_ = SDL_GetTicks(); shader_start_ticks_ = SDL_GetTicks();
fps_last_update_ticks_ = SDL_GetTicks();
updateWindowTitle(); updateWindowTitle();
// Quad setup
float quadVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
GLuint vao = 0, vbo = 0;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
bool running = true; bool running = true;
while (running) { while (running) {
fps_frame_count_++;
const Uint32 current_ticks = SDL_GetTicks();
if (current_ticks - fps_last_update_ticks_ >= 500) {
const float elapsed_seconds = static_cast<float>(current_ticks - fps_last_update_ticks_) / 1000.0f;
current_fps_ = static_cast<float>(fps_frame_count_) / elapsed_seconds;
fps_frame_count_ = 0;
fps_last_update_ticks_ = current_ticks;
updateWindowTitle();
}
JA_Update();
if (!music_list_.empty() && JA_GetMusicState() == JA_MUSIC_STOPPED) {
playRandomMusic();
}
SDL_Event e; SDL_Event e;
while (SDL_PollEvent(&e)) { while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) running = false; if (e.type == SDL_EVENT_QUIT) {
else if (e.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) running = false; running = false;
else if (e.type == SDL_EVENT_KEY_DOWN) { } else if (e.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
// Escape cierra la app running = false;
if (e.key.key == SDLK_ESCAPE) running = false; } else if (e.type == SDL_EVENT_KEY_DOWN) {
// handle your debug keys if (e.key.key == SDLK_ESCAPE) { running = false; }
handleDebugEvents(e); handleDebugEvents(e);
} else if (e.type == SDL_EVENT_WINDOW_RESIZED) {
// opcional: podrías actualizar algo con new size
} }
} }
int w, h; Rendering::ShaderUniforms uniforms;
uniforms.iTime = static_cast<float>(SDL_GetTicks() - shader_start_ticks_) / 1000.0f;
int w = 0;
int h = 0;
SDL_GetWindowSize(window_, &w, &h); SDL_GetWindowSize(window_, &w, &h);
glViewport(0, 0, w, h); uniforms.iResolutionX = static_cast<float>(w);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); uniforms.iResolutionY = static_cast<float>(h);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(current_program_); backend_->render(uniforms);
// Obtener uniform locations (se recalculan porque el shader puede cambiar) if (!Options_video.vsync) {
GLint locRes = glGetUniformLocation(current_program_, "iResolution"); SDL_Delay(1);
GLint locTime = glGetUniformLocation(current_program_, "iTime");
if (locRes >= 0) glUniform2f(locRes, float(w), float(h));
if (locTime >= 0) {
float t = (SDL_GetTicks() - shader_start_ticks_) / 1000.0f;
glUniform1f(locTime, t);
} }
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
SDL_GL_SwapWindow(window_);
SDL_Delay(1);
} }
// Cleanup backend_->cleanup();
glDeleteBuffers(1, &vbo); backend_.reset();
glDeleteVertexArrays(1, &vao);
if (current_program_ != 0) { if (current_music_ != nullptr) {
glDeleteProgram(current_program_); JA_DeleteMusic(current_music_);
} current_music_ = nullptr;
}
JA_Quit();
SDL_GL_DestroyContext(glContext);
SDL_DestroyWindow(window_); SDL_DestroyWindow(window_);
SDL_Quit(); SDL_Quit();
return 0; return 0;

View File

@@ -0,0 +1,297 @@
#include "rendering/opengl_shader_backend.hpp"
#include <iostream>
#include <vector>
namespace Rendering {
namespace {
constexpr const char* VERTEX_SHADER_SRC = R"glsl(
#version 330 core
layout(location = 0) in vec2 aPos;
out vec2 vUV;
void main() {
vUV = aPos * 0.5 + 0.5;
gl_Position = vec4(aPos, 0.0, 1.0);
}
)glsl";
void logInfo(const std::string& msg) { std::cout << "[INFO] " << msg << '\n'; }
void logError(const std::string& msg) { std::cerr << "[ERROR] " << msg << '\n'; }
auto compileShader(GLenum type, const char* src) -> GLuint {
const GLuint s = glCreateShader(type);
glShaderSource(s, 1, &src, nullptr);
glCompileShader(s);
GLint ok = 0;
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (ok == 0) {
GLint len = 0;
glGetShaderiv(s, GL_INFO_LOG_LENGTH, &len);
std::string log(len > 0 ? len : 1, ' ');
glGetShaderInfoLog(s, len, nullptr, log.data());
logError("Shader compile error: " + log);
glDeleteShader(s);
return 0;
}
return s;
}
auto linkProgram(GLuint vs, GLuint fs) -> GLuint {
const GLuint p = glCreateProgram();
glAttachShader(p, vs);
glAttachShader(p, fs);
glLinkProgram(p);
GLint ok = 0;
glGetProgramiv(p, GL_LINK_STATUS, &ok);
if (ok == 0) {
GLint len = 0;
glGetProgramiv(p, GL_INFO_LOG_LENGTH, &len);
std::string log(len > 0 ? len : 1, ' ');
glGetProgramInfoLog(p, len, nullptr, log.data());
logError("Program link error: " + log);
glDeleteProgram(p);
return 0;
}
return p;
}
auto detectFeedbackChannel(const ShaderMetadata& metadata) -> int {
if (metadata.iChannel0 == "self") { return 0; }
if (metadata.iChannel1 == "self") { return 1; }
if (metadata.iChannel2 == "self") { return 2; }
if (metadata.iChannel3 == "self") { return 3; }
return -1;
}
} // namespace
OpenGLShaderBackend::~OpenGLShaderBackend() { cleanup(); }
auto OpenGLShaderBackend::init(SDL_Window* window) -> bool {
window_ = window;
gl_context_ = SDL_GL_CreateContext(window_);
if (gl_context_ == nullptr) {
logError(std::string("SDL_GL_CreateContext error: ") + SDL_GetError());
return false;
}
if (gladLoadGLLoader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress)) == 0) {
logError("Failed to initialize GL loader");
SDL_GL_DestroyContext(gl_context_);
gl_context_ = nullptr;
return false;
}
constexpr float QUAD_VERTICES[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
glGenVertexArrays(1, &vao_);
glGenBuffers(1, &vbo_);
glBindVertexArray(vao_);
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferData(GL_ARRAY_BUFFER, sizeof(QUAD_VERTICES), QUAD_VERTICES, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
return true;
}
auto OpenGLShaderBackend::loadShader(const ShaderProgramSpec& spec) -> bool {
const std::filesystem::path source_path = spec.folder / (spec.base_name + ".gl.glsl");
std::string fragSrc;
if (!loadFileToString(source_path, fragSrc)) {
logError("Failed to load shader file: " + source_path.string());
return false;
}
const int feedback = detectFeedbackChannel(spec.metadata);
const GLuint vs = compileShader(GL_VERTEX_SHADER, VERTEX_SHADER_SRC);
const GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragSrc.c_str());
if (vs == 0 || fs == 0) {
if (vs != 0) { glDeleteShader(vs); }
if (fs != 0) { glDeleteShader(fs); }
logError("Shader compilation failed for: " + source_path.string());
return false;
}
const GLuint program = linkProgram(vs, fs);
glDeleteShader(vs);
glDeleteShader(fs);
if (program == 0) {
logError("Program linking failed for: " + source_path.string());
return false;
}
if (current_program_ != 0) {
glDeleteProgram(current_program_);
}
current_program_ = program;
destroyFeedbackFBO();
feedback_channel_ = feedback;
current_shader_uses_feedback_ = (feedback >= 0);
if (current_shader_uses_feedback_) {
logInfo("Shader uses self-feedback on iChannel" + std::to_string(feedback_channel_));
}
logInfo("Shader loaded successfully: " + spec.base_name);
return true;
}
auto OpenGLShaderBackend::createFeedbackFBO(int width, int height) -> bool {
destroyFeedbackFBO();
glGenTextures(1, &feedback_texture_);
glBindTexture(GL_TEXTURE_2D, feedback_texture_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
std::vector<float> black(static_cast<std::size_t>(width) * static_cast<std::size_t>(height) * 4U, 0.0f);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, black.data());
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, &feedback_fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, feedback_fbo_);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, feedback_texture_, 0);
const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (status != GL_FRAMEBUFFER_COMPLETE) {
logError("Feedback FBO creation failed: " + std::to_string(status));
destroyFeedbackFBO();
return false;
}
feedback_width_ = width;
feedback_height_ = height;
logInfo("Created feedback FBO (" + std::to_string(width) + "x" + std::to_string(height) + ")");
return true;
}
void OpenGLShaderBackend::destroyFeedbackFBO() {
if (feedback_fbo_ != 0) {
glDeleteFramebuffers(1, &feedback_fbo_);
feedback_fbo_ = 0;
}
if (feedback_texture_ != 0) {
glDeleteTextures(1, &feedback_texture_);
feedback_texture_ = 0;
}
feedback_width_ = 0;
feedback_height_ = 0;
}
void OpenGLShaderBackend::render(const ShaderUniforms& uniforms) {
if (current_program_ == 0 || window_ == nullptr) { return; }
int w = 0;
int h = 0;
SDL_GetWindowSize(window_, &w, &h);
if (current_shader_uses_feedback_) {
if (feedback_fbo_ == 0 || feedback_width_ != w || feedback_height_ != h) {
createFeedbackFBO(w, h);
}
}
glUseProgram(current_program_);
const GLint locRes = glGetUniformLocation(current_program_, "iResolution");
const GLint locTime = glGetUniformLocation(current_program_, "iTime");
if (current_shader_uses_feedback_) {
const std::string channel_name = "iChannel" + std::to_string(feedback_channel_);
const GLint locChannel = glGetUniformLocation(current_program_, channel_name.c_str());
if (locChannel >= 0) {
glActiveTexture(GL_TEXTURE0 + feedback_channel_);
glBindTexture(GL_TEXTURE_2D, feedback_texture_);
glUniform1i(locChannel, feedback_channel_);
}
glBindFramebuffer(GL_FRAMEBUFFER, feedback_fbo_);
glViewport(0, 0, w, h);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (locRes >= 0) { glUniform2f(locRes, static_cast<float>(w), static_cast<float>(h)); }
if (locTime >= 0) { glUniform1f(locTime, uniforms.iTime); }
glBindVertexArray(vao_);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, w, h);
if (locRes >= 0) { glUniform2f(locRes, static_cast<float>(w), static_cast<float>(h)); }
if (locTime >= 0) { glUniform1f(locTime, uniforms.iTime); }
glBindVertexArray(vao_);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
glActiveTexture(GL_TEXTURE0 + feedback_channel_);
glBindTexture(GL_TEXTURE_2D, 0);
} else {
glViewport(0, 0, w, h);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (locRes >= 0) { glUniform2f(locRes, static_cast<float>(w), static_cast<float>(h)); }
if (locTime >= 0) { glUniform1f(locTime, uniforms.iTime); }
glBindVertexArray(vao_);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
SDL_GL_SwapWindow(window_);
}
void OpenGLShaderBackend::setVSync(bool vsync) {
const int result = SDL_GL_SetSwapInterval(vsync ? 1 : 0);
if (result == 0) {
logInfo(vsync ? "VSync enabled" : "VSync disabled");
} else {
logError(std::string("Failed to set VSync: ") + SDL_GetError());
}
}
void OpenGLShaderBackend::cleanup() {
if (gl_context_ == nullptr) { return; }
if (vbo_ != 0) { glDeleteBuffers(1, &vbo_); vbo_ = 0; }
if (vao_ != 0) { glDeleteVertexArrays(1, &vao_); vao_ = 0; }
if (current_program_ != 0) { glDeleteProgram(current_program_); current_program_ = 0; }
destroyFeedbackFBO();
current_shader_uses_feedback_ = false;
feedback_channel_ = -1;
SDL_GL_DestroyContext(gl_context_);
gl_context_ = nullptr;
window_ = nullptr;
}
auto makeOpenGLBackend() -> std::unique_ptr<IShaderBackend> {
return std::make_unique<OpenGLShaderBackend>();
}
} // namespace Rendering

View File

@@ -0,0 +1,40 @@
#pragma once
#include <glad/glad.h>
#include "rendering/shader_backend.hpp"
namespace Rendering {
class OpenGLShaderBackend final : public IShaderBackend {
public:
OpenGLShaderBackend() = default;
~OpenGLShaderBackend() override;
auto init(SDL_Window* window) -> bool override;
auto loadShader(const ShaderProgramSpec& spec) -> bool override;
void render(const ShaderUniforms& uniforms) override;
void setVSync(bool vsync) override;
void cleanup() override;
[[nodiscard]] auto driverName() const -> std::string override { return "OpenGL"; }
private:
auto createFeedbackFBO(int width, int height) -> bool;
void destroyFeedbackFBO();
SDL_Window* window_{nullptr};
SDL_GLContext gl_context_{nullptr};
GLuint vao_{0};
GLuint vbo_{0};
GLuint current_program_{0};
GLuint feedback_fbo_{0};
GLuint feedback_texture_{0};
bool current_shader_uses_feedback_{false};
int feedback_channel_{-1};
int feedback_width_{0};
int feedback_height_{0};
};
} // namespace Rendering

View File

@@ -0,0 +1,238 @@
#include "rendering/sdl3gpu/sdl3gpu_shader_backend.hpp"
#include <iostream>
#include "rendering/sdl3gpu/shader_factory.hpp"
namespace Rendering {
namespace {
void logInfo(const std::string& msg) { std::cout << "[INFO] " << msg << '\n'; }
void logError(const std::string& msg) { std::cerr << "[ERROR] " << msg << '\n'; }
#ifdef __APPLE__
constexpr SDL_GPUShaderFormat SHADER_FORMAT = SDL_GPU_SHADERFORMAT_MSL;
constexpr const char* VERTEX_ENTRY = "passthrough_vs";
constexpr const char* FRAGMENT_ENTRY = "test_fs"; // overridden per-shader (see loadShader)
constexpr const char* VERTEX_SUFFIX = ".vert.msl";
constexpr const char* FRAGMENT_SUFFIX = ".frag.msl";
#else
constexpr SDL_GPUShaderFormat SHADER_FORMAT = SDL_GPU_SHADERFORMAT_SPIRV;
constexpr const char* VERTEX_ENTRY = "main";
constexpr const char* FRAGMENT_ENTRY = "main";
constexpr const char* VERTEX_SUFFIX = ".vert.spv";
constexpr const char* FRAGMENT_SUFFIX = ".frag.spv";
#endif
} // namespace
Sdl3GpuShaderBackend::~Sdl3GpuShaderBackend() { cleanup(); }
auto Sdl3GpuShaderBackend::init(SDL_Window* window) -> bool {
window_ = window;
return createDevice();
}
auto Sdl3GpuShaderBackend::createDevice() -> bool {
device_ = SDL_CreateGPUDevice(SHADER_FORMAT, false, nullptr);
if (device_ == nullptr) {
logError(std::string("SDL_CreateGPUDevice failed: ") + SDL_GetError());
return false;
}
if (!SDL_ClaimWindowForGPUDevice(device_, window_)) {
logError(std::string("SDL_ClaimWindowForGPUDevice failed: ") + SDL_GetError());
SDL_DestroyGPUDevice(device_);
device_ = nullptr;
return false;
}
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode());
const char* name = SDL_GetGPUDeviceDriver(device_);
const std::string raw = (name != nullptr) ? name : "GPU";
if (raw == "vulkan") { driver_name_ = "Vulkan"; }
else if (raw == "metal") { driver_name_ = "Metal"; }
else if (raw == "d3d12") { driver_name_ = "D3D12"; }
else if (!raw.empty()) {
driver_name_ = raw;
driver_name_[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(driver_name_[0])));
} else {
driver_name_ = "GPU";
}
logInfo("GPU driver: " + driver_name_);
return true;
}
auto Sdl3GpuShaderBackend::loadVertexShaderFor(const ShaderProgramSpec& spec) -> bool {
if (vertex_shader_ != nullptr) { return true; }
const std::filesystem::path common_dir = spec.folder.parent_path() / "_common";
const std::filesystem::path vertex_path = common_dir / (std::string("passthrough") + VERTEX_SUFFIX);
vertex_shader_ = Sdl3Gpu::loadShaderFromFile(device_, vertex_path, SHADER_FORMAT,
VERTEX_ENTRY, SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
if (vertex_shader_ == nullptr) {
logError("Failed to load shared vertex shader: " + vertex_path.string() + " (" + SDL_GetError() + ")");
return false;
}
logInfo("Loaded shared vertex shader: " + vertex_path.filename().string());
return true;
}
auto Sdl3GpuShaderBackend::buildPipeline(SDL_GPUShader* fragment) -> SDL_GPUGraphicsPipeline* {
const SDL_GPUTextureFormat SWAPCHAIN_FORMAT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
SDL_GPUColorTargetBlendState no_blend{};
SDL_GPUColorTargetDescription color_target{};
color_target.format = SWAPCHAIN_FORMAT;
color_target.blend_state = no_blend;
SDL_GPUGraphicsPipelineCreateInfo info{};
info.vertex_shader = vertex_shader_;
info.fragment_shader = fragment;
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
info.target_info.num_color_targets = 1;
info.target_info.color_target_descriptions = &color_target;
SDL_GPUGraphicsPipeline* pipeline = SDL_CreateGPUGraphicsPipeline(device_, &info);
if (pipeline == nullptr) {
logError(std::string("SDL_CreateGPUGraphicsPipeline failed: ") + SDL_GetError());
}
return pipeline;
}
auto Sdl3GpuShaderBackend::loadShader(const ShaderProgramSpec& spec) -> bool {
if (device_ == nullptr) { return false; }
if (!loadVertexShaderFor(spec)) { return false; }
const std::filesystem::path frag_path = spec.folder / (spec.base_name + FRAGMENT_SUFFIX);
#ifdef __APPLE__
const std::string entry = spec.base_name + "_fs";
const char* fragment_entry = entry.c_str();
#else
const char* fragment_entry = FRAGMENT_ENTRY;
#endif
SDL_GPUShader* new_fragment = Sdl3Gpu::loadShaderFromFile(device_, frag_path, SHADER_FORMAT,
fragment_entry, SDL_GPU_SHADERSTAGE_FRAGMENT, 0, 1);
if (new_fragment == nullptr) {
logError("Failed to load fragment shader: " + frag_path.string() + " (" + SDL_GetError() + ")");
return false;
}
SDL_GPUGraphicsPipeline* new_pipeline = buildPipeline(new_fragment);
if (new_pipeline == nullptr) {
SDL_ReleaseGPUShader(device_, new_fragment);
return false;
}
SDL_WaitForGPUIdle(device_);
if (pipeline_ != nullptr) { SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_); }
if (fragment_shader_ != nullptr) { SDL_ReleaseGPUShader(device_, fragment_shader_); }
pipeline_ = new_pipeline;
fragment_shader_ = new_fragment;
logInfo("Shader loaded successfully: " + spec.base_name);
return true;
}
void Sdl3GpuShaderBackend::render(const ShaderUniforms& uniforms) {
if (device_ == nullptr || pipeline_ == nullptr) { return; }
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
if (cmd == nullptr) { return; }
SDL_GPUTexture* swapchain = nullptr;
Uint32 sw = 0;
Uint32 sh = 0;
if (!SDL_AcquireGPUSwapchainTexture(cmd, window_, &swapchain, &sw, &sh) || swapchain == nullptr) {
SDL_SubmitGPUCommandBuffer(cmd);
return;
}
SDL_GPUColorTargetInfo color_target{};
color_target.texture = swapchain;
color_target.load_op = SDL_GPU_LOADOP_CLEAR;
color_target.store_op = SDL_GPU_STOREOP_STORE;
color_target.clear_color = {.r = 0.0f, .g = 0.0f, .b = 0.0f, .a = 1.0f};
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &color_target, 1, nullptr);
if (pass != nullptr) {
SDL_GPUViewport vp{};
vp.x = 0.0f;
vp.y = 0.0f;
vp.w = static_cast<float>(sw);
vp.h = static_cast<float>(sh);
vp.min_depth = 0.0f;
vp.max_depth = 1.0f;
SDL_SetGPUViewport(pass, &vp);
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
UniformsStd140 ubo{};
ubo.iTime = uniforms.iTime;
ubo.iResolutionX = uniforms.iResolutionX;
ubo.iResolutionY = uniforms.iResolutionY;
SDL_PushGPUFragmentUniformData(cmd, 0, &ubo, sizeof(ubo));
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
SDL_EndGPURenderPass(pass);
}
SDL_SubmitGPUCommandBuffer(cmd);
}
void Sdl3GpuShaderBackend::setVSync(bool vsync) {
vsync_ = vsync;
if (device_ != nullptr && window_ != nullptr) {
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode());
logInfo(vsync ? "VSync enabled" : "VSync disabled");
}
}
auto Sdl3GpuShaderBackend::bestPresentMode() const -> SDL_GPUPresentMode {
if (vsync_) { return SDL_GPU_PRESENTMODE_VSYNC; }
if (device_ != nullptr && window_ != nullptr) {
if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_IMMEDIATE)) {
return SDL_GPU_PRESENTMODE_IMMEDIATE;
}
if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_MAILBOX)) {
return SDL_GPU_PRESENTMODE_MAILBOX;
}
}
return SDL_GPU_PRESENTMODE_VSYNC;
}
void Sdl3GpuShaderBackend::cleanup() {
if (device_ == nullptr) { return; }
SDL_WaitForGPUIdle(device_);
if (pipeline_ != nullptr) {
SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_);
pipeline_ = nullptr;
}
if (fragment_shader_ != nullptr) {
SDL_ReleaseGPUShader(device_, fragment_shader_);
fragment_shader_ = nullptr;
}
if (vertex_shader_ != nullptr) {
SDL_ReleaseGPUShader(device_, vertex_shader_);
vertex_shader_ = nullptr;
}
if (window_ != nullptr) {
SDL_ReleaseWindowFromGPUDevice(device_, window_);
}
SDL_DestroyGPUDevice(device_);
device_ = nullptr;
window_ = nullptr;
}
auto makeSdl3GpuBackend() -> std::unique_ptr<IShaderBackend> {
return std::make_unique<Sdl3GpuShaderBackend>();
}
} // namespace Rendering

View File

@@ -0,0 +1,44 @@
#pragma once
#include <SDL3/SDL.h>
#include "rendering/shader_backend.hpp"
namespace Rendering {
class Sdl3GpuShaderBackend final : public IShaderBackend {
public:
Sdl3GpuShaderBackend() = default;
~Sdl3GpuShaderBackend() override;
auto init(SDL_Window* window) -> bool override;
auto loadShader(const ShaderProgramSpec& spec) -> bool override;
void render(const ShaderUniforms& uniforms) override;
void setVSync(bool vsync) override;
void cleanup() override;
[[nodiscard]] auto driverName() const -> std::string override { return driver_name_; }
private:
struct UniformsStd140 {
float iTime{0.0f};
float pad0{0.0f};
float iResolutionX{0.0f};
float iResolutionY{0.0f};
};
auto createDevice() -> bool;
auto loadVertexShaderFor(const ShaderProgramSpec& spec) -> bool;
auto buildPipeline(SDL_GPUShader* fragment) -> SDL_GPUGraphicsPipeline*;
[[nodiscard]] auto bestPresentMode() const -> SDL_GPUPresentMode;
SDL_Window* window_{nullptr};
SDL_GPUDevice* device_{nullptr};
SDL_GPUShader* vertex_shader_{nullptr};
SDL_GPUShader* fragment_shader_{nullptr};
SDL_GPUGraphicsPipeline* pipeline_{nullptr};
bool vsync_{true};
std::string driver_name_;
};
} // namespace Rendering

View File

@@ -0,0 +1,41 @@
#pragma once
#include <SDL3/SDL.h>
#include <filesystem>
namespace Rendering::Sdl3Gpu {
// Loads a compiled shader binary or source from disk and creates an SDL_GPUShader.
// For SPIR-V: pass the .spv path with format = SDL_GPU_SHADERFORMAT_SPIRV.
// For MSL: pass the .msl text path with format = SDL_GPU_SHADERFORMAT_MSL.
inline auto loadShaderFromFile(SDL_GPUDevice* device,
const std::filesystem::path& path,
SDL_GPUShaderFormat format,
const char* entrypoint,
SDL_GPUShaderStage stage,
Uint32 num_samplers,
Uint32 num_uniform_buffers) -> SDL_GPUShader* {
std::size_t size = 0;
void* data = SDL_LoadFile(path.string().c_str(), &size);
if (data == nullptr) {
return nullptr;
}
SDL_GPUShaderCreateInfo info{};
info.code_size = size;
info.code = static_cast<Uint8*>(data);
info.entrypoint = entrypoint;
info.format = format;
info.stage = stage;
info.num_samplers = num_samplers;
info.num_storage_textures = 0;
info.num_storage_buffers = 0;
info.num_uniform_buffers = num_uniform_buffers;
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
SDL_free(data);
return shader;
}
} // namespace Rendering::Sdl3Gpu

View File

@@ -0,0 +1,99 @@
#include "rendering/shader_backend.hpp"
#include <algorithm>
#include <fstream>
#include <sstream>
namespace Rendering {
namespace {
auto trimString(const std::string& str) -> std::string {
const std::size_t start = str.find_first_not_of(" \t\r\n");
const std::size_t end = str.find_last_not_of(" \t\r\n");
if (start != std::string::npos && end != std::string::npos) {
return str.substr(start, end - start + 1);
}
return "";
}
} // namespace
auto loadFileToString(const std::filesystem::path& path, std::string& out) -> bool {
std::ifstream ifs(path, std::ios::in | std::ios::binary);
if (!ifs) { return false; }
std::ostringstream ss;
ss << ifs.rdbuf();
out = ss.str();
return true;
}
auto parseMetaFile(const std::filesystem::path& meta_path) -> ShaderMetadata {
ShaderMetadata metadata;
std::ifstream ifs(meta_path);
if (!ifs) { return metadata; }
std::string line;
while (std::getline(ifs, line)) {
const std::size_t colon = line.find(':');
if (colon == std::string::npos) { continue; }
std::string key = line.substr(0, colon);
std::string value = trimString(line.substr(colon + 1));
std::transform(key.begin(), key.end(), key.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
key = trimString(key);
if (key == "name") { metadata.name = value; }
else if (key == "author") { metadata.author = value; }
else if (key == "ichannel0") { metadata.iChannel0 = value; }
else if (key == "ichannel1") { metadata.iChannel1 = value; }
else if (key == "ichannel2") { metadata.iChannel2 = value; }
else if (key == "ichannel3") { metadata.iChannel3 = value; }
}
return metadata;
}
auto extractShaderMetadata(const std::string& source) -> ShaderMetadata {
ShaderMetadata metadata;
std::istringstream stream(source);
std::string line;
int line_count = 0;
constexpr int MAX_LINES_TO_CHECK = 30;
while (std::getline(stream, line) && line_count < MAX_LINES_TO_CHECK) {
line_count++;
const std::size_t pos = line.find("//");
if (pos == std::string::npos) { continue; }
const std::string comment = line.substr(pos + 2);
std::string lower = comment;
std::transform(lower.begin(), lower.end(), lower.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
auto valueAfterColon = [&]() {
return trimString(comment.substr(comment.find(':') + 1));
};
if (lower.find("name:") != std::string::npos) {
metadata.name = valueAfterColon();
} else if (lower.find("author:") != std::string::npos) {
metadata.author = valueAfterColon();
} else if (lower.find("ichannel0:") != std::string::npos) {
metadata.iChannel0 = valueAfterColon();
} else if (lower.find("ichannel1:") != std::string::npos) {
metadata.iChannel1 = valueAfterColon();
} else if (lower.find("ichannel2:") != std::string::npos) {
metadata.iChannel2 = valueAfterColon();
} else if (lower.find("ichannel3:") != std::string::npos) {
metadata.iChannel3 = valueAfterColon();
}
}
return metadata;
}
} // namespace Rendering

View File

@@ -0,0 +1,57 @@
#pragma once
#include <SDL3/SDL.h>
#include <filesystem>
#include <memory>
#include <string>
namespace Rendering {
struct ShaderMetadata {
std::string name;
std::string author;
std::string iChannel0{"none"};
std::string iChannel1{"none"};
std::string iChannel2{"none"};
std::string iChannel3{"none"};
};
struct ShaderUniforms {
float iTime{0.0f};
float iResolutionX{0.0f};
float iResolutionY{0.0f};
};
struct ShaderProgramSpec {
std::filesystem::path folder;
std::string base_name;
ShaderMetadata metadata;
};
class IShaderBackend {
public:
IShaderBackend() = default;
virtual ~IShaderBackend() = default;
IShaderBackend(const IShaderBackend&) = delete;
IShaderBackend(IShaderBackend&&) = delete;
auto operator=(const IShaderBackend&) -> IShaderBackend& = delete;
auto operator=(IShaderBackend&&) -> IShaderBackend& = delete;
virtual auto init(SDL_Window* window) -> bool = 0;
virtual auto loadShader(const ShaderProgramSpec& spec) -> bool = 0;
virtual void render(const ShaderUniforms& uniforms) = 0;
virtual void setVSync(bool vsync) = 0;
virtual void cleanup() = 0;
[[nodiscard]] virtual auto driverName() const -> std::string = 0;
};
[[nodiscard]] auto makeOpenGLBackend() -> std::unique_ptr<IShaderBackend>;
[[nodiscard]] auto makeSdl3GpuBackend() -> std::unique_ptr<IShaderBackend>;
[[nodiscard]] auto extractShaderMetadata(const std::string& source) -> ShaderMetadata;
[[nodiscard]] auto loadFileToString(const std::filesystem::path& path, std::string& out) -> bool;
[[nodiscard]] auto parseMetaFile(const std::filesystem::path& meta_path) -> ShaderMetadata;
} // namespace Rendering

477
third_party/jail_audio.cpp vendored Normal file
View File

@@ -0,0 +1,477 @@
#ifndef JA_USESDLMIXER
#include "jail_audio.h"
#include <SDL3/SDL.h> // Para SDL_AudioFormat, SDL_BindAudioStream, SDL_SetAudioStreamGain, SDL_PutAudioStreamData, SDL_DestroyAudioStream, SDL_GetAudioStreamAvailable, Uint8, SDL_CreateAudioStream, SDL_UnbindAudioStream, Uint32, SDL_CloseAudioDevice, SDL_GetTicks, SDL_Log, SDL_free, SDL_AudioSpec, SDL_AudioStream, SDL_IOFromMem, SDL_LoadWAV, SDL_LoadWAV_IO, SDL_OpenAudioDevice, SDL_clamp, SDL_malloc, SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, SDL_AudioDeviceID, SDL_memcpy
#include <stdint.h> // Para uint32_t, uint8_t
#include <stdio.h> // Para NULL, fseek, printf, fclose, fopen, fread, ftell, FILE, SEEK_END, SEEK_SET
#include <stdlib.h> // Para free, malloc
#include <string.h> // Para strcpy, strlen
#include "stb_vorbis.h" // Para stb_vorbis_decode_memory
#define JA_MAX_SIMULTANEOUS_CHANNELS 20
#define JA_MAX_GROUPS 2
struct JA_Sound_t
{
SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 };
Uint32 length { 0 };
Uint8 *buffer { NULL };
};
struct JA_Channel_t
{
JA_Sound_t *sound { nullptr };
int pos { 0 };
int times { 0 };
int group { 0 };
SDL_AudioStream *stream { nullptr };
JA_Channel_state state { JA_CHANNEL_FREE };
};
struct JA_Music_t
{
SDL_AudioSpec spec { SDL_AUDIO_S16, 2, 48000 };
Uint32 length { 0 };
Uint8 *buffer { nullptr };
char *filename { nullptr };
int pos { 0 };
int times { 0 };
SDL_AudioStream *stream { nullptr };
JA_Music_state state { JA_MUSIC_INVALID };
};
JA_Music_t *current_music { nullptr };
JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];
SDL_AudioSpec JA_audioSpec { SDL_AUDIO_S16, 2, 48000 };
float JA_musicVolume { 1.0f };
float JA_soundVolume[JA_MAX_GROUPS];
bool JA_musicEnabled { true };
bool JA_soundEnabled { true };
SDL_AudioDeviceID sdlAudioDevice { 0 };
//SDL_TimerID JA_timerID { 0 };
bool fading = false;
int fade_start_time;
int fade_duration;
int fade_initial_volume;
void JA_Update()
{
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING)
{
if (fading) {
int time = SDL_GetTicks();
if (time > (fade_start_time+fade_duration)) {
fading = false;
JA_StopMusic();
return;
} else {
const int time_passed = time - fade_start_time;
const float percent = (float)time_passed / (float)fade_duration;
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume*(1.0 - percent));
}
}
if (current_music->times != 0)
{
if ((Uint32)SDL_GetAudioStreamAvailable(current_music->stream) < (current_music->length/2)) {
SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length);
}
if (current_music->times>0) current_music->times--;
}
else
{
if (SDL_GetAudioStreamAvailable(current_music->stream) == 0) JA_StopMusic();
}
}
if (JA_soundEnabled)
{
for (int i=0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
if (channels[i].state == JA_CHANNEL_PLAYING)
{
if (channels[i].times != 0)
{
if ((Uint32)SDL_GetAudioStreamAvailable(channels[i].stream) < (channels[i].sound->length/2)) {
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length);
if (channels[i].times>0) channels[i].times--;
}
}
else
{
if (SDL_GetAudioStreamAvailable(channels[i].stream) == 0) JA_StopChannel(i);
}
}
}
return;
}
void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels)
{
#ifdef DEBUG
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
#endif
JA_audioSpec = {format, num_channels, freq };
if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
if (sdlAudioDevice==0) SDL_Log("Failed to initialize SDL audio!");
for (int i=0; i<JA_MAX_SIMULTANEOUS_CHANNELS; ++i) channels[i].state = JA_CHANNEL_FREE;
for (int i=0; i<JA_MAX_GROUPS; ++i) JA_soundVolume[i] = 0.5f;
}
void JA_Quit()
{
if (!sdlAudioDevice) SDL_CloseAudioDevice(sdlAudioDevice);
sdlAudioDevice = 0;
}
JA_Music_t *JA_LoadMusic(Uint8* buffer, Uint32 length)
{
JA_Music_t *music = new JA_Music_t();
int chan, samplerate;
short *output;
music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;
music->spec.channels = chan;
music->spec.freq = samplerate;
music->spec.format = SDL_AUDIO_S16;
music->buffer = (Uint8*)SDL_malloc(music->length);
SDL_memcpy(music->buffer, output, music->length);
free(output);
music->pos = 0;
music->state = JA_MUSIC_STOPPED;
return music;
}
JA_Music_t *JA_LoadMusic(const char* filename)
{
// [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid.
FILE *f = fopen(filename, "rb");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
Uint8 *buffer = (Uint8*)malloc(fsize + 1);
if (fread(buffer, fsize, 1, f)!=1) return NULL;
fclose(f);
JA_Music_t *music = JA_LoadMusic(buffer, fsize);
music->filename = (char*)malloc(strlen(filename)+1);
strcpy(music->filename, filename);
free(buffer);
return music;
}
void JA_PlayMusic(JA_Music_t *music, const int loop)
{
if (!JA_musicEnabled) return;
JA_StopMusic();
current_music = music;
current_music->pos = 0;
current_music->state = JA_MUSIC_PLAYING;
current_music->times = loop;
current_music->stream = SDL_CreateAudioStream(&current_music->spec, &JA_audioSpec);
if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length)) printf("[ERROR] SDL_PutAudioStreamData failed!\n");
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n");
//SDL_ResumeAudioStreamDevice(current_music->stream);
}
char *JA_GetMusicFilename(JA_Music_t *music)
{
if (!music) music = current_music;
return music->filename;
}
void JA_PauseMusic()
{
if (!JA_musicEnabled) return;
if (!current_music || current_music->state == JA_MUSIC_INVALID) return;
current_music->state = JA_MUSIC_PAUSED;
//SDL_PauseAudioStreamDevice(current_music->stream);
SDL_UnbindAudioStream(current_music->stream);
}
void JA_ResumeMusic()
{
if (!JA_musicEnabled) return;
if (!current_music || current_music->state == JA_MUSIC_INVALID) return;
current_music->state = JA_MUSIC_PLAYING;
//SDL_ResumeAudioStreamDevice(current_music->stream);
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
}
void JA_StopMusic()
{
if (!JA_musicEnabled) return;
if (!current_music || current_music->state == JA_MUSIC_INVALID) return;
current_music->pos = 0;
current_music->state = JA_MUSIC_STOPPED;
//SDL_PauseAudioStreamDevice(current_music->stream);
SDL_DestroyAudioStream(current_music->stream);
current_music->stream = nullptr;
free(current_music->filename);
current_music->filename = nullptr;
}
void JA_FadeOutMusic(const int milliseconds)
{
if (!JA_musicEnabled) return;
if (current_music == NULL || current_music->state == JA_MUSIC_INVALID) return;
fading = true;
fade_start_time = SDL_GetTicks();
fade_duration = milliseconds;
fade_initial_volume = JA_musicVolume;
}
JA_Music_state JA_GetMusicState()
{
if (!JA_musicEnabled) return JA_MUSIC_DISABLED;
if (!current_music) return JA_MUSIC_INVALID;
return current_music->state;
}
void JA_DeleteMusic(JA_Music_t *music)
{
if (current_music == music) current_music = nullptr;
SDL_free(music->buffer);
if (music->stream) SDL_DestroyAudioStream(music->stream);
delete music;
}
float JA_SetMusicVolume(float volume)
{
JA_musicVolume = SDL_clamp( volume, 0.0f, 1.0f );
if (current_music) SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
return JA_musicVolume;
}
void JA_SetMusicPosition(float value)
{
if (!current_music) return;
current_music->pos = value * current_music->spec.freq;
}
float JA_GetMusicPosition()
{
if (!current_music) return 0;
return float(current_music->pos)/float(current_music->spec.freq);
}
void JA_EnableMusic(const bool value)
{
if ( !value && current_music && (current_music->state==JA_MUSIC_PLAYING) ) JA_StopMusic();
JA_musicEnabled = value;
}
JA_Sound_t *JA_NewSound(Uint8* buffer, Uint32 length)
{
JA_Sound_t *sound = new JA_Sound_t();
sound->buffer = buffer;
sound->length = length;
return sound;
}
JA_Sound_t *JA_LoadSound(uint8_t* buffer, uint32_t size)
{
JA_Sound_t *sound = new JA_Sound_t();
SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size),1, &sound->spec, &sound->buffer, &sound->length);
return sound;
}
JA_Sound_t *JA_LoadSound(const char* filename)
{
JA_Sound_t *sound = new JA_Sound_t();
SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length);
return sound;
}
int JA_PlaySound(JA_Sound_t *sound, const int loop, const int group)
{
if (!JA_soundEnabled) return -1;
int channel = 0;
while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE) { channel++; }
if (channel == JA_MAX_SIMULTANEOUS_CHANNELS) channel = 0;
JA_StopChannel(channel);
channels[channel].sound = sound;
channels[channel].times = loop;
channels[channel].pos = 0;
channels[channel].state = JA_CHANNEL_PLAYING;
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
return channel;
}
int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop, const int group)
{
if (!JA_soundEnabled) return -1;
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return -1;
JA_StopChannel(channel);
channels[channel].sound = sound;
channels[channel].times = loop;
channels[channel].pos = 0;
channels[channel].state = JA_CHANNEL_PLAYING;
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume[group]);
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
return channel;
}
void JA_DeleteSound(JA_Sound_t *sound)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
if (channels[i].sound == sound) JA_StopChannel(i);
}
SDL_free(sound->buffer);
delete sound;
}
void JA_PauseChannel(const int channel)
{
if (!JA_soundEnabled) return;
if (channel == -1)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
if (channels[i].state == JA_CHANNEL_PLAYING)
{
channels[i].state = JA_CHANNEL_PAUSED;
//SDL_PauseAudioStreamDevice(channels[i].stream);
SDL_UnbindAudioStream(channels[i].stream);
}
}
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
{
if (channels[channel].state == JA_CHANNEL_PLAYING)
{
channels[channel].state = JA_CHANNEL_PAUSED;
//SDL_PauseAudioStreamDevice(channels[channel].stream);
SDL_UnbindAudioStream(channels[channel].stream);
}
}
}
void JA_ResumeChannel(const int channel)
{
if (!JA_soundEnabled) return;
if (channel == -1)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
if (channels[i].state == JA_CHANNEL_PAUSED)
{
channels[i].state = JA_CHANNEL_PLAYING;
//SDL_ResumeAudioStreamDevice(channels[i].stream);
SDL_BindAudioStream(sdlAudioDevice, channels[i].stream);
}
}
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
{
if (channels[channel].state == JA_CHANNEL_PAUSED)
{
channels[channel].state = JA_CHANNEL_PLAYING;
//SDL_ResumeAudioStreamDevice(channels[channel].stream);
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
}
}
}
void JA_StopChannel(const int channel)
{
if (!JA_soundEnabled) return;
if (channel == -1)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
if (channels[i].state != JA_CHANNEL_FREE) SDL_DestroyAudioStream(channels[i].stream);
channels[i].stream = nullptr;
channels[i].state = JA_CHANNEL_FREE;
channels[i].pos = 0;
channels[i].sound = NULL;
}
}
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
{
if (channels[channel].state != JA_CHANNEL_FREE) SDL_DestroyAudioStream(channels[channel].stream);
channels[channel].stream = nullptr;
channels[channel].state = JA_CHANNEL_FREE;
channels[channel].pos = 0;
channels[channel].sound = NULL;
}
}
JA_Channel_state JA_GetChannelState(const int channel)
{
if (!JA_soundEnabled) return JA_SOUND_DISABLED;
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS) return JA_CHANNEL_INVALID;
return channels[channel].state;
}
float JA_SetSoundVolume(float volume, const int group)
{
const float v = SDL_clamp( volume, 0.0f, 1.0f );
for (int i = 0; i < JA_MAX_GROUPS; ++i) {
if (group==-1 || group==i) JA_soundVolume[i]=v;
}
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
if ( ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED)) &&
((group==-1) || (channels[i].group==group)) )
SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume[i]);
return v;
}
void JA_EnableSound(const bool value)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
{
if (channels[i].state == JA_CHANNEL_PLAYING) JA_StopChannel(i);
}
JA_soundEnabled = value;
}
float JA_SetVolume(float volume)
{
JA_SetSoundVolume(JA_SetMusicVolume(volume) / 2.0f);
return JA_musicVolume;
}
#endif

43
third_party/jail_audio.h vendored Normal file
View File

@@ -0,0 +1,43 @@
#pragma once
#include <SDL3/SDL.h>
enum JA_Channel_state { JA_CHANNEL_INVALID, JA_CHANNEL_FREE, JA_CHANNEL_PLAYING, JA_CHANNEL_PAUSED, JA_SOUND_DISABLED };
enum JA_Music_state { JA_MUSIC_INVALID, JA_MUSIC_PLAYING, JA_MUSIC_PAUSED, JA_MUSIC_STOPPED, JA_MUSIC_DISABLED };
struct JA_Sound_t;
struct JA_Music_t;
void JA_Update();
void JA_Init(const int freq, const SDL_AudioFormat format, const int num_channels);
void JA_Quit();
JA_Music_t *JA_LoadMusic(const char* filename);
JA_Music_t *JA_LoadMusic(Uint8* buffer, Uint32 length);
void JA_PlayMusic(JA_Music_t *music, const int loop = -1);
char *JA_GetMusicFilename(JA_Music_t *music = nullptr);
void JA_PauseMusic();
void JA_ResumeMusic();
void JA_StopMusic();
void JA_FadeOutMusic(const int milliseconds);
JA_Music_state JA_GetMusicState();
void JA_DeleteMusic(JA_Music_t *music);
float JA_SetMusicVolume(float volume);
void JA_SetMusicPosition(float value);
float JA_GetMusicPosition();
void JA_EnableMusic(const bool value);
JA_Sound_t *JA_NewSound(Uint8* buffer, Uint32 length);
JA_Sound_t *JA_LoadSound(Uint8* buffer, Uint32 length);
JA_Sound_t *JA_LoadSound(const char* filename);
int JA_PlaySound(JA_Sound_t *sound, const int loop = 0, const int group=0);
int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop = 0, const int group=0);
void JA_PauseChannel(const int channel);
void JA_ResumeChannel(const int channel);
void JA_StopChannel(const int channel);
JA_Channel_state JA_GetChannelState(const int channel);
void JA_DeleteSound(JA_Sound_t *sound);
float JA_SetSoundVolume(float volume, const int group=0);
void JA_EnableSound(const bool value);
float JA_SetVolume(float volume);

5631
third_party/stb_vorbis.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
# compile_shaders.cmake — invoked via `cmake -P`.
#
# Required cache vars:
# GLSLC — path to the glslc executable.
# SHADERS_DIR — path to the shaders/ directory.
#
# Walks SHADERS_DIR for:
# _common/passthrough.vk.glsl -> _common/passthrough.vert.spv
# <name>/<name>.vk.glsl -> <name>/<name>.frag.spv
#
# Shaders are recompiled only if the .spv is missing or older than its source.
if(NOT GLSLC)
message(FATAL_ERROR "compile_shaders.cmake: GLSLC not provided")
endif()
if(NOT SHADERS_DIR)
message(FATAL_ERROR "compile_shaders.cmake: SHADERS_DIR not provided")
endif()
if(NOT EXISTS "${SHADERS_DIR}")
message(FATAL_ERROR "compile_shaders.cmake: SHADERS_DIR does not exist: ${SHADERS_DIR}")
endif()
function(_compile_to_spv SOURCE STAGE OUTPUT)
if(EXISTS "${OUTPUT}")
file(TIMESTAMP "${SOURCE}" SRC_T "%s")
file(TIMESTAMP "${OUTPUT}" OUT_T "%s")
if(NOT "${SRC_T}" STREQUAL "" AND NOT "${OUT_T}" STREQUAL "")
if(SRC_T LESS_EQUAL OUT_T)
return()
endif()
endif()
endif()
message(STATUS "glslc (${STAGE}) ${SOURCE} -> ${OUTPUT}")
execute_process(
COMMAND "${GLSLC}" "-fshader-stage=${STAGE}" "${SOURCE}" -o "${OUTPUT}"
RESULT_VARIABLE RC
OUTPUT_VARIABLE STDOUT
ERROR_VARIABLE STDERR
)
if(NOT RC EQUAL 0)
message(FATAL_ERROR "glslc failed for ${SOURCE}:\n${STDERR}")
endif()
endfunction()
set(VERT_SOURCE "${SHADERS_DIR}/_common/passthrough.vk.glsl")
set(VERT_OUTPUT "${SHADERS_DIR}/_common/passthrough.vert.spv")
if(EXISTS "${VERT_SOURCE}")
_compile_to_spv("${VERT_SOURCE}" "vert" "${VERT_OUTPUT}")
else()
message(WARNING "Missing shared vertex shader: ${VERT_SOURCE}")
endif()
file(GLOB SHADER_DIRS LIST_DIRECTORIES true RELATIVE "${SHADERS_DIR}" "${SHADERS_DIR}/*")
foreach(DIR ${SHADER_DIRS})
set(ABS_DIR "${SHADERS_DIR}/${DIR}")
if(NOT IS_DIRECTORY "${ABS_DIR}")
continue()
endif()
string(SUBSTRING "${DIR}" 0 1 FIRST_CHAR)
if(FIRST_CHAR STREQUAL "_" OR FIRST_CHAR STREQUAL ".")
continue()
endif()
set(VK_SOURCE "${ABS_DIR}/${DIR}.vk.glsl")
set(SPV_OUTPUT "${ABS_DIR}/${DIR}.frag.spv")
if(EXISTS "${VK_SOURCE}")
_compile_to_spv("${VK_SOURCE}" "frag" "${SPV_OUTPUT}")
endif()
endforeach()