Compare commits
3 Commits
0a269449a3
...
b290cee689
| Author | SHA1 | Date | |
|---|---|---|---|
| b290cee689 | |||
| 194726f823 | |||
| 44de2c7013 |
236
CLAUDE.md
Normal file
236
CLAUDE.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# 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 + OpenGL 3.3. The application loads and displays GLSL shaders from the `shaders/` directory with runtime switching capabilities.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the Application
|
||||||
|
```bash
|
||||||
|
./shadertoy [SHADER_PATH] [-F|--fullscreen]
|
||||||
|
|
||||||
|
# Examples:
|
||||||
|
./shadertoy shaders/test.frag.glsl
|
||||||
|
./shadertoy -F shaders/fractal_pyramid.frag.glsl
|
||||||
|
```
|
||||||
|
|
||||||
|
**Runtime Controls:**
|
||||||
|
- `ESC` - Exit
|
||||||
|
- `F3` - Toggle fullscreen
|
||||||
|
- `LEFT/RIGHT ARROW` - Cycle through shaders in directory
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Design
|
||||||
|
All application logic resides in `src/main.cpp` (~469 lines) - a monolithic design that's straightforward to understand. Key components:
|
||||||
|
|
||||||
|
1. **Shader Loading System** - Automatic directory scanning of `.glsl` files, sorted alphabetically
|
||||||
|
2. **OpenGL Rendering** - Single fullscreen quad with fragment shader using GL_TRIANGLE_STRIP
|
||||||
|
3. **Event Loop** - SDL3-based with vsync (SDL_GL_SwapWindow + 1ms delay)
|
||||||
|
4. **Resource Path Resolution** - Multi-path fallback system for executable, relative, and macOS bundle paths
|
||||||
|
|
||||||
|
### Global State (main.cpp)
|
||||||
|
```cpp
|
||||||
|
shader_list_ // Vector of discovered .glsl shader paths
|
||||||
|
current_shader_index_ // Active shader in rotation
|
||||||
|
current_program_ // OpenGL shader program handle
|
||||||
|
shader_start_ticks_ // Base time for iTime uniform calculation
|
||||||
|
window_ // SDL3 window pointer
|
||||||
|
shaders_directory_ // Shader directory path (resolved at startup)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- **SDL3** - Window/input management, OpenGL context
|
||||||
|
- **GLAD** - OpenGL 3.3 loader (statically linked via `third_party/glad/`)
|
||||||
|
- **C++17 stdlib** - filesystem, fstream, vector, algorithms
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
### 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.
|
||||||
@@ -22,6 +22,7 @@ set(APP_SOURCES
|
|||||||
# 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
|
||||||
@@ -35,6 +36,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
|
||||||
|
|||||||
BIN
data/music/485077__mat397__confused-voices.ogg
Normal file
BIN
data/music/485077__mat397__confused-voices.ogg
Normal file
Binary file not shown.
BIN
data/music/485078__mat397__polyflute-pad.ogg
Normal file
BIN
data/music/485078__mat397__polyflute-pad.ogg
Normal file
Binary file not shown.
BIN
data/music/485079__mat397__melancholic-flutes-pad.ogg
Normal file
BIN
data/music/485079__mat397__melancholic-flutes-pad.ogg
Normal file
Binary file not shown.
BIN
data/music/486083__mat397__world-of-ants-pad.ogg
Normal file
BIN
data/music/486083__mat397__world-of-ants-pad.ogg
Normal file
Binary file not shown.
BIN
data/music/618041__mat397__mangle-dark-pad.ogg
Normal file
BIN
data/music/618041__mat397__mangle-dark-pad.ogg
Normal file
Binary file not shown.
BIN
data/music/618042__mat397__bad-electric-dark-pad.ogg
Normal file
BIN
data/music/618042__mat397__bad-electric-dark-pad.ogg
Normal file
Binary file not shown.
41
data/music/_readme_and_license.txt
Normal file
41
data/music/_readme_and_license.txt
Normal 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.
BIN
release/icon.ico
BIN
release/icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 137 KiB |
BIN
release/icon.png
BIN
release/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 426 KiB After Width: | Height: | Size: 364 KiB |
BIN
release/icon/icon.afdesign
Normal file
BIN
release/icon/icon.afdesign
Normal file
Binary file not shown.
BIN
release/icon/icon.pxd
Normal file
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.
83
shaders/cineshader_lava.frag.glsl
Normal file
83
shaders/cineshader_lava.frag.glsl
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
190
shaders/dbz.frag.glsl
Normal file
190
shaders/dbz.frag.glsl
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
159
shaders/just_another_cube.frag.glsl
Normal file
159
shaders/just_another_cube.frag.glsl
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
73
shaders/remember.frag.glsl
Normal file
73
shaders/remember.frag.glsl
Normal 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;
|
||||||
|
}
|
||||||
223
shaders/seascape.frag.glsl
Normal file
223
shaders/seascape.frag.glsl
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
|||||||
52
shaders/voxel_descent.frag.glsl
Normal file
52
shaders/voxel_descent.frag.glsl
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Name: Voxel Descent
|
||||||
|
// Author: Jaenman
|
||||||
|
// URL: https://www.shadertoy.com/view/Wc3cRr
|
||||||
|
#version 330 core
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
in vec2 vUV;
|
||||||
|
uniform vec2 iResolution;
|
||||||
|
uniform float iTime;
|
||||||
|
|
||||||
|
/*================================
|
||||||
|
= Voxel Descent =
|
||||||
|
= Author: Jaenam =
|
||||||
|
================================*/
|
||||||
|
// Date: 2025-11-14
|
||||||
|
// License: Creative Commons (CC BY-NC-SA 4.0)
|
||||||
|
|
||||||
|
// Thanks to @diatribes for the plasma :D
|
||||||
|
|
||||||
|
void mainImage( out vec4 O, vec2 I )
|
||||||
|
{
|
||||||
|
float i=0.,d=0.,s=0.,t = iTime*.8;
|
||||||
|
vec3 p,q,z,k;
|
||||||
|
vec4 c = vec4(1,2,3,1);
|
||||||
|
mat2 R = mat2(cos(t/3.+vec4(0,33,11,0)));
|
||||||
|
|
||||||
|
// Initialize O before loop (was O*=i where i=0, reading uninitialized out parameter)
|
||||||
|
O = vec4(0.0);
|
||||||
|
|
||||||
|
for(; i++<1e2;O+=sin(.5*c+i*.2)/s)
|
||||||
|
|
||||||
|
z = normalize(vec3(I+I, 0) - vec3(iResolution.xy, iResolution.y)),
|
||||||
|
z.xy*=R,
|
||||||
|
p = d*z+sqrt(i)*i,
|
||||||
|
k=z/=length(z.xy),
|
||||||
|
z.z-=t, p.z+=t,
|
||||||
|
|
||||||
|
d+=s=.012+.07*abs(mix(cos(dot(sin(floor(p/8.)).yzx,cos(ceil(z/.1)))),length(k),.2)-i/1e2);
|
||||||
|
|
||||||
|
O*= exp(-.02*d*d);
|
||||||
|
// Add epsilon protection to prevent division overflow
|
||||||
|
O=tanh(O*O*mix(length(abs(c/max(dot(cos(t+p),vec3(6)), 0.001))),length(k),.5)/6e4);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 fragCoordPixels = vUV * iResolution;
|
||||||
|
vec4 outColor;
|
||||||
|
mainImage(outColor, fragCoordPixels);
|
||||||
|
FragColor = outColor;
|
||||||
|
}
|
||||||
44
shaders/water.glsl
Normal file
44
shaders/water.glsl
Normal 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;
|
||||||
|
}
|
||||||
610
src/main.cpp
610
src/main.cpp
@@ -7,8 +7,10 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <ctime>
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
#include "jail_audio.h"
|
||||||
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
|
|
||||||
@@ -21,6 +23,7 @@ struct Logger {
|
|||||||
// Opciones mínimas parecidas a las tuyas
|
// 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
|
// Estructura para guardar info del display
|
||||||
@@ -31,17 +34,66 @@ struct DisplayMonitor {
|
|||||||
int refresh_rate = 0;
|
int refresh_rate = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Forward declarations of structs
|
||||||
|
struct ShaderMetadata {
|
||||||
|
std::string name;
|
||||||
|
std::string author;
|
||||||
|
std::string iChannel0; // "BufferA", "BufferB", "none", etc.
|
||||||
|
std::string iChannel1;
|
||||||
|
std::string iChannel2;
|
||||||
|
std::string iChannel3;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderBuffer {
|
||||||
|
GLuint program = 0; // Shader program for this buffer
|
||||||
|
GLuint fbo = 0; // Framebuffer object
|
||||||
|
GLuint texture = 0; // Output texture
|
||||||
|
std::string name; // "BufferA", "BufferB", etc.
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShaderPass {
|
||||||
|
std::string shaderName; // Base name (e.g., "water")
|
||||||
|
std::string displayName; // Custom name from metadata
|
||||||
|
std::string author; // Author from metadata
|
||||||
|
GLuint imageProgram = 0; // Main image shader program
|
||||||
|
std::vector<ShaderBuffer> buffers; // BufferA, BufferB, etc.
|
||||||
|
ShaderMetadata metadata; // iChannel configuration
|
||||||
|
};
|
||||||
|
|
||||||
// Globales simplificados (tu proyecto puede integrarlo en clases)
|
// 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;
|
||||||
|
|
||||||
// Sistema de shaders
|
// Sistema de shaders (legacy - kept for backward compatibility with single-pass shaders)
|
||||||
static std::vector<std::filesystem::path> shader_list_;
|
static std::vector<std::filesystem::path> shader_list_;
|
||||||
|
static std::vector<std::string> shader_names_; // Custom names from "// Name: XXX" comments
|
||||||
|
static std::vector<std::string> shader_authors_; // Custom authors from "// Author: XXX" comments
|
||||||
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 GLuint current_program_ = 0;
|
||||||
static Uint32 shader_start_ticks_ = 0;
|
static Uint32 shader_start_ticks_ = 0;
|
||||||
|
|
||||||
|
// Multi-pass shader system
|
||||||
|
static std::vector<ShaderPass> shader_passes_;
|
||||||
|
static int current_window_width_ = 0;
|
||||||
|
static int current_window_height_ = 0;
|
||||||
|
|
||||||
|
// Self-feedback system (for shaders that use their own output as input)
|
||||||
|
static GLuint feedback_fbo_ = 0;
|
||||||
|
static GLuint feedback_texture_ = 0;
|
||||||
|
static bool current_shader_uses_feedback_ = false;
|
||||||
|
static int feedback_channel_ = -1; // Which iChannel (0-3) is used for feedback
|
||||||
|
|
||||||
|
// FPS tracking
|
||||||
|
static Uint32 fps_frame_count_ = 0;
|
||||||
|
static Uint32 fps_last_update_ticks_ = 0;
|
||||||
|
static float current_fps_ = 0.0f;
|
||||||
|
|
||||||
|
// Sistema de música
|
||||||
|
static std::vector<std::filesystem::path> music_list_;
|
||||||
|
static size_t current_music_index_ = 0;
|
||||||
|
static JA_Music_t* current_music_ = nullptr;
|
||||||
|
|
||||||
// Vertex shader embebido
|
// Vertex shader embebido
|
||||||
static const char* vertexShaderSrc = R"glsl(
|
static const char* vertexShaderSrc = R"glsl(
|
||||||
#version 330 core
|
#version 330 core
|
||||||
@@ -63,6 +115,64 @@ static bool loadFileToString(const std::filesystem::path& path, std::string& out
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string trimString(const std::string& str) {
|
||||||
|
size_t start = str.find_first_not_of(" \t\r\n");
|
||||||
|
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 "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static ShaderMetadata extractShaderMetadata(const std::string& shaderSource) {
|
||||||
|
ShaderMetadata metadata;
|
||||||
|
metadata.iChannel0 = "none";
|
||||||
|
metadata.iChannel1 = "none";
|
||||||
|
metadata.iChannel2 = "none";
|
||||||
|
metadata.iChannel3 = "none";
|
||||||
|
|
||||||
|
std::istringstream stream(shaderSource);
|
||||||
|
std::string line;
|
||||||
|
int lineCount = 0;
|
||||||
|
const int maxLinesToCheck = 30;
|
||||||
|
|
||||||
|
while (std::getline(stream, line) && lineCount < maxLinesToCheck) {
|
||||||
|
lineCount++;
|
||||||
|
|
||||||
|
// Look for "// XXX: YYY" patterns (case-insensitive)
|
||||||
|
size_t pos = line.find("//");
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
std::string comment = line.substr(pos + 2);
|
||||||
|
std::string commentLower = comment;
|
||||||
|
std::transform(commentLower.begin(), commentLower.end(), commentLower.begin(), ::tolower);
|
||||||
|
|
||||||
|
// Check for Name:
|
||||||
|
if (commentLower.find("name:") != std::string::npos) {
|
||||||
|
metadata.name = trimString(comment.substr(comment.find(":") + 1));
|
||||||
|
}
|
||||||
|
// Check for Author:
|
||||||
|
else if (commentLower.find("author:") != std::string::npos) {
|
||||||
|
metadata.author = trimString(comment.substr(comment.find(":") + 1));
|
||||||
|
}
|
||||||
|
// Check for iChannel0-3:
|
||||||
|
else if (commentLower.find("ichannel0:") != std::string::npos) {
|
||||||
|
metadata.iChannel0 = trimString(comment.substr(comment.find(":") + 1));
|
||||||
|
}
|
||||||
|
else if (commentLower.find("ichannel1:") != std::string::npos) {
|
||||||
|
metadata.iChannel1 = trimString(comment.substr(comment.find(":") + 1));
|
||||||
|
}
|
||||||
|
else if (commentLower.find("ichannel2:") != std::string::npos) {
|
||||||
|
metadata.iChannel2 = trimString(comment.substr(comment.find(":") + 1));
|
||||||
|
}
|
||||||
|
else if (commentLower.find("ichannel3:") != std::string::npos) {
|
||||||
|
metadata.iChannel3 = trimString(comment.substr(comment.find(":") + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
static std::vector<std::filesystem::path> scanShaderDirectory(const std::filesystem::path& directory) {
|
static std::vector<std::filesystem::path> scanShaderDirectory(const std::filesystem::path& directory) {
|
||||||
std::vector<std::filesystem::path> shaders;
|
std::vector<std::filesystem::path> shaders;
|
||||||
|
|
||||||
@@ -84,14 +194,333 @@ static std::vector<std::filesystem::path> scanShaderDirectory(const std::filesys
|
|||||||
std::sort(shaders.begin(), shaders.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(shaders.size()) + " shader(s) in " + directory.string());
|
||||||
|
|
||||||
|
// Initialize shader metadata vectors with empty strings (will be filled when shaders are loaded)
|
||||||
|
shader_names_.resize(shaders.size(), "");
|
||||||
|
shader_authors_.resize(shaders.size(), "");
|
||||||
|
|
||||||
return shaders;
|
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)) {
|
||||||
|
if (entry.is_regular_file()) {
|
||||||
|
auto ext = entry.path().extension().string();
|
||||||
|
if (ext == ".ogg") {
|
||||||
|
music_files.push_back(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordenar alfabéticamente
|
||||||
|
std::sort(music_files.begin(), music_files.end());
|
||||||
|
|
||||||
|
Logger::info("Found " + std::to_string(music_files.size()) + " music file(s) in " + directory.string());
|
||||||
|
return music_files;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void playRandomMusic() {
|
||||||
|
if (music_list_.empty()) return;
|
||||||
|
|
||||||
|
// Liberar música anterior si existe
|
||||||
|
if (current_music_) {
|
||||||
|
JA_DeleteMusic(current_music_);
|
||||||
|
current_music_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elegir índice aleatorio
|
||||||
|
current_music_index_ = rand() % music_list_.size();
|
||||||
|
|
||||||
|
// Cargar y reproducir música (sin loop, loop=0)
|
||||||
|
const auto& music_path = music_list_[current_music_index_];
|
||||||
|
current_music_ = JA_LoadMusic(music_path.string().c_str());
|
||||||
|
|
||||||
|
if (current_music_) {
|
||||||
|
JA_PlayMusic(current_music_, 0); // 0 = no loop, se reproduce una vez
|
||||||
|
Logger::info("Now playing: " + music_path.filename().string());
|
||||||
|
} else {
|
||||||
|
Logger::error("Failed to load music: " + music_path.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Multi-pass FBO/Texture Management =====
|
||||||
|
|
||||||
|
static bool createBufferFBO(ShaderBuffer& buffer, int width, int height) {
|
||||||
|
// Create texture
|
||||||
|
glGenTextures(1, &buffer.texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, buffer.texture);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
|
||||||
|
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);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
// Create FBO
|
||||||
|
glGenFramebuffers(1, &buffer.fbo);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, buffer.fbo);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, buffer.texture, 0);
|
||||||
|
|
||||||
|
// Check FBO completeness
|
||||||
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
|
||||||
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
Logger::error("FBO creation failed for " + buffer.name + ": " + std::to_string(status));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::info("Created FBO for " + buffer.name + " (" + std::to_string(width) + "x" + std::to_string(height) + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroyBuffer(ShaderBuffer& buffer) {
|
||||||
|
if (buffer.fbo != 0) {
|
||||||
|
glDeleteFramebuffers(1, &buffer.fbo);
|
||||||
|
buffer.fbo = 0;
|
||||||
|
}
|
||||||
|
if (buffer.texture != 0) {
|
||||||
|
glDeleteTextures(1, &buffer.texture);
|
||||||
|
buffer.texture = 0;
|
||||||
|
}
|
||||||
|
if (buffer.program != 0) {
|
||||||
|
glDeleteProgram(buffer.program);
|
||||||
|
buffer.program = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroyShaderPass(ShaderPass& pass) {
|
||||||
|
if (pass.imageProgram != 0) {
|
||||||
|
glDeleteProgram(pass.imageProgram);
|
||||||
|
pass.imageProgram = 0;
|
||||||
|
}
|
||||||
|
for (auto& buffer : pass.buffers) {
|
||||||
|
destroyBuffer(buffer);
|
||||||
|
}
|
||||||
|
pass.buffers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool resizeBuffersIfNeeded(ShaderPass& pass, int width, int height) {
|
||||||
|
if (current_window_width_ == width && current_window_height_ == height) {
|
||||||
|
return false; // No resize needed
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::info("Resizing buffers: " + std::to_string(width) + "x" + std::to_string(height));
|
||||||
|
|
||||||
|
// Destroy and recreate all buffers with new size
|
||||||
|
for (auto& buffer : pass.buffers) {
|
||||||
|
// Keep program, destroy FBO/texture only
|
||||||
|
if (buffer.fbo != 0) glDeleteFramebuffers(1, &buffer.fbo);
|
||||||
|
if (buffer.texture != 0) glDeleteTextures(1, &buffer.texture);
|
||||||
|
buffer.fbo = 0;
|
||||||
|
buffer.texture = 0;
|
||||||
|
|
||||||
|
if (!createBufferFBO(buffer, width, height)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_window_width_ = width;
|
||||||
|
current_window_height_ = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Self-Feedback System =====
|
||||||
|
|
||||||
|
static bool createFeedbackFBO(int width, int height) {
|
||||||
|
// Delete existing if any
|
||||||
|
if (feedback_fbo_ != 0) glDeleteFramebuffers(1, &feedback_fbo_);
|
||||||
|
if (feedback_texture_ != 0) glDeleteTextures(1, &feedback_texture_);
|
||||||
|
|
||||||
|
// Create texture
|
||||||
|
glGenTextures(1, &feedback_texture_);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, feedback_texture_);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Clear to black initially
|
||||||
|
std::vector<float> black(width * height * 4, 0.0f);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, black.data());
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
// Create FBO
|
||||||
|
glGenFramebuffers(1, &feedback_fbo_);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, feedback_fbo_);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, feedback_texture_, 0);
|
||||||
|
|
||||||
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
|
||||||
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
Logger::error("Feedback FBO creation failed: " + std::to_string(status));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::info("Created feedback FBO (" + std::to_string(width) + "x" + std::to_string(height) + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroyFeedbackFBO() {
|
||||||
|
if (feedback_fbo_ != 0) {
|
||||||
|
glDeleteFramebuffers(1, &feedback_fbo_);
|
||||||
|
feedback_fbo_ = 0;
|
||||||
|
}
|
||||||
|
if (feedback_texture_ != 0) {
|
||||||
|
glDeleteTextures(1, &feedback_texture_);
|
||||||
|
feedback_texture_ = 0;
|
||||||
|
}
|
||||||
|
current_shader_uses_feedback_ = false;
|
||||||
|
feedback_channel_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int detectFeedbackChannel(const ShaderMetadata& metadata) {
|
||||||
|
// Check which iChannel uses "self" feedback
|
||||||
|
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; // No feedback
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Multi-pass Shader Loading =====
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
static GLuint compileShader(GLenum type, const char* src);
|
||||||
|
static GLuint linkProgram(GLuint vs, GLuint fs);
|
||||||
|
|
||||||
|
static std::vector<std::string> findBufferFiles(const std::filesystem::path& imagePath) {
|
||||||
|
std::vector<std::string> buffers;
|
||||||
|
std::filesystem::path dir = imagePath.parent_path();
|
||||||
|
std::string basename = imagePath.stem().stem().string(); // Remove .image.glsl -> get base name
|
||||||
|
|
||||||
|
// Check for BufferA, BufferB, BufferC, BufferD
|
||||||
|
std::vector<std::string> bufferNames = {"bufferA", "bufferB", "bufferC", "bufferD"};
|
||||||
|
for (const auto& bufName : bufferNames) {
|
||||||
|
std::filesystem::path bufferPath = dir / (basename + "." + bufName + ".glsl");
|
||||||
|
if (std::filesystem::exists(bufferPath)) {
|
||||||
|
buffers.push_back(bufName);
|
||||||
|
Logger::info("Found buffer: " + bufferPath.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLuint loadMultiPassShader(const std::filesystem::path& imagePath, ShaderPass& outPass, int width, int height) {
|
||||||
|
std::string basename = imagePath.stem().stem().string();
|
||||||
|
outPass.shaderName = basename;
|
||||||
|
|
||||||
|
// Load and compile Image shader
|
||||||
|
std::string imageSrc;
|
||||||
|
if (!loadFileToString(imagePath, imageSrc)) {
|
||||||
|
Logger::error("Failed to load image shader: " + imagePath.string());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract metadata from Image shader
|
||||||
|
outPass.metadata = extractShaderMetadata(imageSrc);
|
||||||
|
outPass.displayName = outPass.metadata.name.empty() ? basename : outPass.metadata.name;
|
||||||
|
outPass.author = outPass.metadata.author;
|
||||||
|
|
||||||
|
GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
|
||||||
|
GLuint fs = compileShader(GL_FRAGMENT_SHADER, imageSrc.c_str());
|
||||||
|
|
||||||
|
if (!vs || !fs) {
|
||||||
|
if (vs) glDeleteShader(vs);
|
||||||
|
if (fs) glDeleteShader(fs);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
outPass.imageProgram = linkProgram(vs, fs);
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
|
||||||
|
if (!outPass.imageProgram) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and load buffer shaders
|
||||||
|
std::vector<std::string> bufferNames = findBufferFiles(imagePath);
|
||||||
|
std::filesystem::path dir = imagePath.parent_path();
|
||||||
|
|
||||||
|
for (const auto& bufName : bufferNames) {
|
||||||
|
ShaderBuffer buffer;
|
||||||
|
buffer.name = bufName;
|
||||||
|
|
||||||
|
// Load buffer shader source
|
||||||
|
std::filesystem::path bufferPath = dir / (basename + "." + bufName + ".glsl");
|
||||||
|
std::string bufferSrc;
|
||||||
|
if (!loadFileToString(bufferPath, bufferSrc)) {
|
||||||
|
Logger::error("Failed to load buffer: " + bufferPath.string());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile buffer shader
|
||||||
|
GLuint bufVs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
|
||||||
|
GLuint bufFs = compileShader(GL_FRAGMENT_SHADER, bufferSrc.c_str());
|
||||||
|
|
||||||
|
if (!bufVs || !bufFs) {
|
||||||
|
if (bufVs) glDeleteShader(bufVs);
|
||||||
|
if (bufFs) glDeleteShader(bufFs);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.program = linkProgram(bufVs, bufFs);
|
||||||
|
glDeleteShader(bufVs);
|
||||||
|
glDeleteShader(bufFs);
|
||||||
|
|
||||||
|
if (!buffer.program) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create FBO and texture for this buffer
|
||||||
|
if (!createBufferFBO(buffer, width, height)) {
|
||||||
|
glDeleteProgram(buffer.program);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
outPass.buffers.push_back(buffer);
|
||||||
|
Logger::info("Loaded buffer: " + bufName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::info("Multi-pass shader loaded: " + outPass.displayName + " (" + std::to_string(outPass.buffers.size()) + " buffers)");
|
||||||
|
return outPass.imageProgram;
|
||||||
|
}
|
||||||
|
|
||||||
static void updateWindowTitle() {
|
static void updateWindowTitle() {
|
||||||
if (!window_ || shader_list_.empty()) return;
|
if (!window_ || shader_list_.empty()) return;
|
||||||
|
|
||||||
std::string filename = shader_list_[current_shader_index_].filename().string();
|
// Use custom shader name if available, otherwise fallback to filename
|
||||||
std::string title = std::string(APP_NAME) + " (" + filename + ")";
|
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_].filename().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add author if available
|
||||||
|
if (!shader_authors_.empty() && !shader_authors_[current_shader_index_].empty()) {
|
||||||
|
shaderName += " by " + shader_authors_[current_shader_index_];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string title = std::string(APP_NAME) + " (" + shaderName + ")";
|
||||||
|
|
||||||
|
if (current_fps_ > 0.0f) {
|
||||||
|
title += " - " + std::to_string(static_cast<int>(current_fps_ + 0.5f)) + " FPS";
|
||||||
|
}
|
||||||
|
|
||||||
|
title += Options_video.vsync ? " [VSync ON]" : " [VSync OFF]";
|
||||||
|
|
||||||
SDL_SetWindowTitle(window_, title.c_str());
|
SDL_SetWindowTitle(window_, title.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +576,25 @@ static GLuint loadAndCompileShader(size_t index) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract custom shader metadata (name, author, iChannels) from source code
|
||||||
|
ShaderMetadata metadata = extractShaderMetadata(fragSrc);
|
||||||
|
if (!metadata.name.empty()) {
|
||||||
|
shader_names_[index] = metadata.name;
|
||||||
|
Logger::info("Shader name: " + metadata.name);
|
||||||
|
}
|
||||||
|
if (!metadata.author.empty()) {
|
||||||
|
shader_authors_[index] = metadata.author;
|
||||||
|
Logger::info("Shader author: " + metadata.author);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect self-feedback
|
||||||
|
feedback_channel_ = detectFeedbackChannel(metadata);
|
||||||
|
current_shader_uses_feedback_ = (feedback_channel_ >= 0);
|
||||||
|
|
||||||
|
if (current_shader_uses_feedback_) {
|
||||||
|
Logger::info("Shader uses self-feedback on iChannel" + std::to_string(feedback_channel_));
|
||||||
|
}
|
||||||
|
|
||||||
GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
|
GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
|
||||||
GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragSrc.c_str());
|
GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragSrc.c_str());
|
||||||
|
|
||||||
@@ -208,11 +656,15 @@ void setFullscreenMode() {
|
|||||||
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(); // Show cursor on fallback to windowed
|
||||||
|
} else {
|
||||||
|
SDL_HideCursor(); // Hide cursor in fullscreen
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Volver a modo ventana 800x800
|
// 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(); // Show cursor in windowed mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +673,17 @@ void toggleFullscreen() {
|
|||||||
setFullscreenMode();
|
setFullscreenMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void toggleVSync() {
|
||||||
|
Options_video.vsync = !Options_video.vsync;
|
||||||
|
int result = SDL_GL_SetSwapInterval(Options_video.vsync ? 1 : 0);
|
||||||
|
|
||||||
|
if (result == 0) {
|
||||||
|
Logger::info(Options_video.vsync ? "VSync enabled" : "VSync disabled");
|
||||||
|
} else {
|
||||||
|
Logger::error(std::string("Failed to set VSync: ") + SDL_GetError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void switchShader(int direction) {
|
void switchShader(int direction) {
|
||||||
if (shader_list_.empty()) return;
|
if (shader_list_.empty()) return;
|
||||||
|
|
||||||
@@ -244,6 +707,9 @@ void switchShader(int direction) {
|
|||||||
glDeleteProgram(current_program_);
|
glDeleteProgram(current_program_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroy feedback FBO from previous shader
|
||||||
|
destroyFeedbackFBO();
|
||||||
|
|
||||||
current_program_ = new_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();
|
||||||
@@ -260,6 +726,10 @@ void handleDebugEvents(const SDL_Event& event) {
|
|||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case SDLK_F4: {
|
||||||
|
toggleVSync();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case SDLK_LEFT: {
|
case SDLK_LEFT: {
|
||||||
switchShader(-1);
|
switchShader(-1);
|
||||||
break;
|
break;
|
||||||
@@ -287,7 +757,7 @@ int main(int argc, char** argv) {
|
|||||||
Options_video.fullscreen = fullscreenFlag;
|
Options_video.fullscreen = fullscreenFlag;
|
||||||
|
|
||||||
// Inicializar SDL3
|
// Inicializar SDL3
|
||||||
auto initResult = SDL_Init(SDL_INIT_VIDEO);
|
auto initResult = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
|
||||||
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 {
|
||||||
@@ -326,9 +796,34 @@ int main(int argc, char** argv) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set initial vsync state
|
||||||
|
int vsync_result = SDL_GL_SetSwapInterval(Options_video.vsync ? 1 : 0);
|
||||||
|
if (vsync_result == 0) {
|
||||||
|
Logger::info(Options_video.vsync ? "VSync enabled" : "VSync disabled");
|
||||||
|
} else {
|
||||||
|
Logger::error(std::string("Failed to set initial VSync: ") + SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializar jail_audio
|
||||||
|
JA_Init(48000, SDL_AUDIO_S16LE, 2);
|
||||||
|
|
||||||
// Obtener directorio de recursos
|
// Obtener directorio de recursos
|
||||||
std::string resources_dir = getResourcesDirectory();
|
std::string resources_dir = getResourcesDirectory();
|
||||||
|
|
||||||
|
// Inicializar generador de números aleatorios
|
||||||
|
srand(static_cast<unsigned int>(time(nullptr)));
|
||||||
|
|
||||||
|
// Escanear directorio de música
|
||||||
|
std::filesystem::path music_directory = std::filesystem::path(resources_dir) / "data" / "music";
|
||||||
|
music_list_ = scanMusicDirectory(music_directory);
|
||||||
|
|
||||||
|
// Reproducir primera canción aleatoria
|
||||||
|
if (!music_list_.empty()) {
|
||||||
|
playRandomMusic();
|
||||||
|
} else {
|
||||||
|
Logger::info("No music files found in " + music_directory.string());
|
||||||
|
}
|
||||||
|
|
||||||
// Determinar carpeta de shaders
|
// Determinar carpeta de shaders
|
||||||
std::filesystem::path shaderFile(shaderPath);
|
std::filesystem::path shaderFile(shaderPath);
|
||||||
if (shaderFile.has_parent_path()) {
|
if (shaderFile.has_parent_path()) {
|
||||||
@@ -391,6 +886,7 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shader_start_ticks_ = SDL_GetTicks();
|
shader_start_ticks_ = SDL_GetTicks();
|
||||||
|
fps_last_update_ticks_ = SDL_GetTicks();
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
|
|
||||||
// Quad setup
|
// Quad setup
|
||||||
@@ -415,6 +911,27 @@ int main(int argc, char** argv) {
|
|||||||
bool running = true;
|
bool running = true;
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
|
// Update FPS counter
|
||||||
|
fps_frame_count_++;
|
||||||
|
Uint32 current_ticks = SDL_GetTicks();
|
||||||
|
|
||||||
|
// Update FPS display every 500ms
|
||||||
|
if (current_ticks - fps_last_update_ticks_ >= 500) {
|
||||||
|
float elapsed_seconds = (current_ticks - fps_last_update_ticks_) / 1000.0f;
|
||||||
|
current_fps_ = fps_frame_count_ / elapsed_seconds;
|
||||||
|
fps_frame_count_ = 0;
|
||||||
|
fps_last_update_ticks_ = current_ticks;
|
||||||
|
updateWindowTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar audio (necesario para streaming y loops)
|
||||||
|
JA_Update();
|
||||||
|
|
||||||
|
// Verificar si la música actual terminó y reproducir siguiente aleatoria
|
||||||
|
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) running = false;
|
||||||
@@ -431,28 +948,81 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
int w, h;
|
int w, h;
|
||||||
SDL_GetWindowSize(window_, &w, &h);
|
SDL_GetWindowSize(window_, &w, &h);
|
||||||
|
|
||||||
|
// Create/resize feedback FBO if needed
|
||||||
|
if (current_shader_uses_feedback_) {
|
||||||
|
if (feedback_fbo_ == 0 || current_window_width_ != w || current_window_height_ != h) {
|
||||||
|
createFeedbackFBO(w, h);
|
||||||
|
current_window_width_ = w;
|
||||||
|
current_window_height_ = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glUseProgram(current_program_);
|
||||||
|
|
||||||
|
// Obtener uniform locations
|
||||||
|
GLint locRes = glGetUniformLocation(current_program_, "iResolution");
|
||||||
|
GLint locTime = glGetUniformLocation(current_program_, "iTime");
|
||||||
|
|
||||||
|
float t = (SDL_GetTicks() - shader_start_ticks_) / 1000.0f;
|
||||||
|
|
||||||
|
// === FEEDBACK RENDERING ===
|
||||||
|
if (current_shader_uses_feedback_) {
|
||||||
|
// Step 1: Bind feedback texture to iChannel
|
||||||
|
std::string channelName = "iChannel" + std::to_string(feedback_channel_);
|
||||||
|
GLint locChannel = glGetUniformLocation(current_program_, channelName.c_str());
|
||||||
|
|
||||||
|
if (locChannel >= 0) {
|
||||||
|
glActiveTexture(GL_TEXTURE0 + feedback_channel_);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, feedback_texture_);
|
||||||
|
glUniform1i(locChannel, feedback_channel_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Render to feedback FBO
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, feedback_fbo_);
|
||||||
glViewport(0, 0, w, h);
|
glViewport(0, 0, w, h);
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
glUseProgram(current_program_);
|
|
||||||
|
|
||||||
// Obtener uniform locations (se recalculan porque el shader puede cambiar)
|
|
||||||
GLint locRes = glGetUniformLocation(current_program_, "iResolution");
|
|
||||||
GLint locTime = glGetUniformLocation(current_program_, "iTime");
|
|
||||||
|
|
||||||
if (locRes >= 0) glUniform2f(locRes, float(w), float(h));
|
if (locRes >= 0) glUniform2f(locRes, float(w), float(h));
|
||||||
if (locTime >= 0) {
|
if (locTime >= 0) glUniform1f(locTime, t);
|
||||||
float t = (SDL_GetTicks() - shader_start_ticks_) / 1000.0f;
|
|
||||||
glUniform1f(locTime, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindVertexArray(vao);
|
glBindVertexArray(vao);
|
||||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
// Step 3: Render to screen (using the same FBO texture)
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
glViewport(0, 0, w, h);
|
||||||
|
|
||||||
|
if (locRes >= 0) glUniform2f(locRes, float(w), float(h));
|
||||||
|
if (locTime >= 0) glUniform1f(locTime, t);
|
||||||
|
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
// Unbind texture
|
||||||
|
glActiveTexture(GL_TEXTURE0 + feedback_channel_);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
} else {
|
||||||
|
// === NORMAL RENDERING (no feedback) ===
|
||||||
|
glViewport(0, 0, w, h);
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
if (locRes >= 0) glUniform2f(locRes, float(w), float(h));
|
||||||
|
if (locTime >= 0) glUniform1f(locTime, t);
|
||||||
|
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_GL_SwapWindow(window_);
|
SDL_GL_SwapWindow(window_);
|
||||||
SDL_Delay(1);
|
if (!Options_video.vsync) {
|
||||||
|
SDL_Delay(1); // Prevent CPU spinning when vsync is off
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
@@ -462,6 +1032,16 @@ int main(int argc, char** argv) {
|
|||||||
glDeleteProgram(current_program_);
|
glDeleteProgram(current_program_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup feedback FBO
|
||||||
|
destroyFeedbackFBO();
|
||||||
|
|
||||||
|
// Cleanup audio
|
||||||
|
if (current_music_) {
|
||||||
|
JA_DeleteMusic(current_music_);
|
||||||
|
current_music_ = nullptr;
|
||||||
|
}
|
||||||
|
JA_Quit();
|
||||||
|
|
||||||
SDL_GL_DestroyContext(glContext);
|
SDL_GL_DestroyContext(glContext);
|
||||||
SDL_DestroyWindow(window_);
|
SDL_DestroyWindow(window_);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
|||||||
477
third_party/jail_audio.cpp
vendored
Normal file
477
third_party/jail_audio.cpp
vendored
Normal 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(¤t_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
43
third_party/jail_audio.h
vendored
Normal 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
5631
third_party/stb_vorbis.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user