7 Commits

82 changed files with 5125 additions and 1303 deletions

View File

@@ -4,7 +4,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## 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.
This is a cross-platform Shadertoy-like fragment shader viewer built with SDL3. It supports two rendering backends:
- **SDL3 GPU** (default): renders via Vulkan on Linux/Windows and Metal on macOS.
- **OpenGL 3.3** (fallback): the original backend, used automatically if SDL3 GPU init fails.
Selection at launch:
```bash
./shadertoy --backend=gpu # force SDL3 GPU
./shadertoy --backend=opengl # force OpenGL
./shadertoy --backend=auto # default: GPU first, fall back to OpenGL on failure
```
## Build and Development Commands
@@ -30,13 +39,21 @@ make macos_debug
make linux_debug
```
### Compiling Shaders to SPIR-V
The SDL3 GPU backend reads compiled SPIR-V (`.frag.spv`) at runtime. Run this after touching any `.vk.glsl` file:
```bash
cmake --build build --target compile_shaders
```
Requires `glslc` (Debian/Ubuntu: `apt install glslang-tools`; macOS: `brew install glslang`).
### Running the Application
```bash
./shadertoy [SHADER_PATH] [-F|--fullscreen]
./shadertoy [SHADER_NAME_OR_PATH] [-F|--fullscreen] [--backend=auto|gpu|opengl]
# Examples:
./shadertoy shaders/test.frag.glsl
./shadertoy -F shaders/fractal_pyramid.frag.glsl
./shadertoy test # auto backend, shaders/test/
./shadertoy --backend=gpu seascape # force SDL3 GPU (Vulkan/Metal)
./shadertoy -F shaders/fractal_pyramid # explicit folder path, fullscreen
```
**Runtime Controls:**
@@ -46,28 +63,49 @@ make linux_debug
## Architecture
### Core Design
All application logic resides in `src/main.cpp` (~469 lines) - a monolithic design that's straightforward to understand. Key components:
### Layered design
- `src/main.cpp` — SDL3 window, event loop, shader-list scanning, dispatch to a backend.
- `src/rendering/shader_backend.hpp` — abstract `IShaderBackend` interface + `ShaderMetadata` / `ShaderUniforms` / `ShaderProgramSpec` shared types. Two factories: `makeOpenGLBackend()`, `makeSdl3GpuBackend()`.
- `src/rendering/opengl_shader_backend.{hpp,cpp}` — OpenGL 3.3 implementation. Loads `<folder>/<name>.gl.glsl` at runtime.
- `src/rendering/sdl3gpu/sdl3gpu_shader_backend.{hpp,cpp}` — SDL3 GPU implementation. Loads `<folder>/<name>.frag.spv` (Linux/Windows) or `<folder>/<name>.frag.msl` (macOS); shares one passthrough vertex shader from `shaders/_common/passthrough.vert.{spv,msl}`.
- `src/rendering/sdl3gpu/shader_factory.hpp` — small helper that loads a shader binary/source from disk and creates an `SDL_GPUShader`.
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
### Shader folder layout
One folder per shader, under `shaders/`:
```
shaders/<name>/
<name>.gl.glsl # OpenGL GLSL 3.30 (handwritten)
<name>.vk.glsl # Vulkan GLSL 4.50 (handwritten)
<name>.frag.msl # Metal Shading Language (handwritten)
<name>.frag.spv # generated by `make compile_shaders` from .vk.glsl
meta.txt # Name / Author / iChannelN
shaders/_common/
passthrough.vk.glsl # shared fullscreen-triangle vertex (Vulkan)
passthrough.vert.spv # generated
passthrough.vert.msl # handwritten Metal
```
Folders starting with `_` or `.` are skipped by the scanner. The `meta.txt` parser is in `Rendering::parseMetaFile`.
### Backend selection logic (`main.cpp`)
1. Parse `--backend=...` flag (default `auto`).
2. If not OpenGL: create a window with no flags, instantiate `Sdl3GpuShaderBackend`. If `init()` fails AND choice was `auto`, destroy the window and continue. If choice was `gpu`, exit with error.
3. Otherwise: create a window with `SDL_WINDOW_OPENGL` and instantiate `OpenGLShaderBackend`.
### Global State (main.cpp)
```cpp
shader_list_ // Vector of discovered .glsl shader paths
shader_list_ // Vector<ShaderEntry { folder, base_name }>
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)
backend_ // unique_ptr<IShaderBackend>
shaders_directory_ // Shaders root 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
- **SDL3** ≥ 3.2 — windowing, events, GPU API (`SDL_gpu.h`)
- **GLAD** OpenGL 3.3 loader (used only by the OpenGL backend, statically linked via `third_party/glad/`)
- **C++17 stdlib** filesystem, fstream, vector, algorithms
- Build-time tool: `glslc` (only required to regenerate `.frag.spv`)
### Platform-Specific Code
Uses preprocessor defines (`WINDOWS_BUILD`, `MACOS_BUILD`, `LINUX_BUILD`) for:
@@ -77,6 +115,22 @@ Uses preprocessor defines (`WINDOWS_BUILD`, `MACOS_BUILD`, `LINUX_BUILD`) for:
## Shader System
### Authoring a shader (manual steps)
For each new shader, three handwritten files live in `shaders/<name>/`:
1. **`<name>.gl.glsl`** — OpenGL GLSL 3.30 (the original Shadertoy-style format described below).
2. **`<name>.vk.glsl`** — Vulkan GLSL 4.50 with explicit `layout(set=3, binding=0) uniform ShadertoyUBO { float iTime; vec2 iResolution; } u;` and `layout(set=2, binding=N) uniform sampler2D` for any texture channel.
3. **`<name>.frag.msl`** — Metal Shading Language. Entry point must be named `<name>_fs`. Uniforms come in via `[[buffer(0)]]`; samplers via `[[texture(N)]] [[sampler(N)]]`.
After editing any `.vk.glsl`, rebuild SPIR-V: `cmake --build build --target compile_shaders`.
The MSL is **not** auto-generated — write it by hand following the same convention as `aee_2026/source/core/rendering/sdl3gpu/msl/*.msl.h`.
### Shadertoy → Vulkan/Metal porting cheatsheet
- `iResolution` is `vec2` here (no `.xy`). Reconstruct vec3 manually if needed.
- Replace `texture(iChannel0, ...)` calls — no texture channels currently wired through the GPU backend (planned).
- The shared vertex stage emits `vUV` already in Shadertoy convention `(0,0)` bottom-left → `(1,1)` top-right; multiply by `iResolution` to get `fragCoord`.
- The Y axis is flipped in NDC inside the shared vertex shader so Vulkan/Metal render right-side-up like OpenGL.
### Shader Format (GLSL 3.3 Core)
All shaders must follow this structure:

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

@@ -0,0 +1,75 @@
#include <metal_stdlib>
using namespace metal;
// Cineshader Lava — edankwan
// MSL port of cineshader_lava.vk.glsl.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
static 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);
}
static float sdSphere(float3 p, float s) {
return length(p) - s;
}
static float map(float3 p, float iTime) {
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 * float3(52.5126, 64.62744, 632.25)) * float3(2.0, 2.0, 0.8),
mix(0.5, 1.0, fract(fi * 412.531 + 0.5124))),
d,
0.4
);
}
return d;
}
static float3 calcNormal(float3 p, float iTime) {
const float h = 1e-5;
const float2 k = float2(1, -1);
return normalize(k.xyy * map(p + k.xyy * h, iTime) +
k.yyx * map(p + k.yyx * h, iTime) +
k.yxy * map(p + k.yxy * h, iTime) +
k.xxx * map(p + k.xxx * h, iTime));
}
fragment float4 cineshader_lava_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float2 uv = fragCoord / U.iResolution;
float3 rayOri = float3((uv - 0.5) * float2(U.iResolution.x / U.iResolution.y, 1.0) * 6.0, 3.0);
float3 rayDir = float3(0.0, 0.0, -1.0);
float depth = 0.0;
float3 p = float3(0.0);
for (int i = 0; i < 64; i++) {
p = rayOri + rayDir * depth;
float dist = map(p, U.iTime);
depth += dist;
if (dist < 1e-6) { break; }
}
depth = min(6.0, depth);
float3 n = calcNormal(p, U.iTime);
float b = max(0.0, dot(n, float3(0.577)));
float3 col = (0.5 + 0.5 * cos((b + U.iTime * 3.0) + uv.xyx * 2.0 + float3(0, 2, 4))) * (0.85 + b * 0.35);
col *= exp(-depth * 0.15);
return float4(col, 1.0 - (depth - 0.5) / 2.0);
}

Binary file not shown.

View File

@@ -0,0 +1,77 @@
#version 450
// Name: Cineshader Lava
// Author: edankwan
// URL: https://www.shadertoy.com/view/3sySRK
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
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;
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;
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);
fragColor = vec4(col, 1.0 - (depth - 0.5) / 2.0);
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

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

View File

@@ -0,0 +1,42 @@
#include <metal_stdlib>
using namespace metal;
// Creation by Silexars — Danguafer
// MSL port of creation.vk.glsl.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
fragment float4 creation_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float t = U.iTime;
float2 r = U.iResolution;
float3 c = float3(0.0);
float l = 0.0;
float z = t;
for (int i = 0; i < 3; i++) {
float2 p = fragCoord / r;
float2 uv = p;
p -= 0.5;
p.x *= r.x / r.y;
z += 0.07;
l = length(p);
float invl = (l > 0.0) ? (1.0 / l) : 0.0;
uv += p * invl * (sin(z) + 1.0) * abs(sin(l * 9.0 - z - z));
float2 m = fract(uv) - 0.5;
float denom = length(m);
if (denom < 1e-6) denom = 1e-6;
c[i] = 0.01 / denom;
}
float L = (l > 0.0) ? l : 1.0;
return float4(c / L, t);
}

Binary file not shown.

View File

@@ -0,0 +1,45 @@
#version 450
// Name: Creation by Silexars
// Author: Danguafer
// URL: https://www.shadertoy.com/view/XsXXDn
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
#define t iTime
#define r iResolution.xy
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec3 c = vec3(0.0);
float l;
float z = t;
for (int i = 0; i < 3; i++) {
vec2 uv, p = fragCoord.xy / r;
uv = p;
p -= 0.5;
p.x *= r.x / r.y;
z += 0.07;
l = length(p);
float invl = (l > 0.0) ? (1.0 / l) : 0.0;
uv += p * invl * (sin(z) + 1.0) * abs(sin(l * 9.0 - z - z));
vec2 m = mod(uv, 1.0) - 0.5;
float denom = length(m);
if (denom < 1e-6) denom = 1e-6;
c[i] = 0.01 / denom;
}
float L = (l > 0.0) ? l : 1.0;
fragColor = vec4(c / L, t);
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

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

View File

@@ -0,0 +1,455 @@
#include <metal_stdlib>
using namespace metal;
// Cube lines — Danil
// MSL port of cube_lines.vk.glsl. The original Shadertoy is ~780 lines and
// uses every GLSL trick (mat3 chains, arrays, fwidth-based AA, swap macros).
//
// Notes:
// - fcos() uses fwidth() like the GLSL original to soften high-frequency
// terms. The GLSL fallback path that divides by iResolution.y when
// fwidth returns 0/NaN/Inf is omitted here; the main fwidth-based
// branch covers virtually all fragments and the fallback is rarely
// hit in practice.
// - Optional Shadertoy defines (ANIM_SHAPE, ANIM_COLOR, ROTATION_SPEED,
// CAMERA_*, USE_COLOR, AA_*, ONLY_BOX, etc.) are left at their defaults
// just like the .vk.glsl ships them.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
constant float ROTATION_SPEED = 0.8999;
constant float tshift = 53.0;
constant float FDIST = 0.7;
constant float PI = 3.1415926;
constant float3 BOXDIMS = float3(0.75, 0.75, 1.25);
constant float IOR = 1.33;
static float3x3 rotx(float a) {
float s = sin(a), c = cos(a);
return float3x3(float3(1.0, 0.0, 0.0), float3(0.0, c, s), float3(0.0, -s, c));
}
static float3x3 roty(float a) {
float s = sin(a), c = cos(a);
return float3x3(float3(c, 0.0, s), float3(0.0, 1.0, 0.0), float3(-s, 0.0, c));
}
static float3x3 rotz(float a) {
float s = sin(a), c = cos(a);
return float3x3(float3(c, s, 0.0), float3(-s, c, 0.0), float3(0.0, 0.0, 1.0));
}
static float3 fcos(float3 x) {
float3 w = fwidth(x);
return cos(x) * smoothstep(float3(3.14 * 2.0), float3(0.0), w);
}
static float3 getColor(float3 p) {
p = abs(p);
p *= 1.25;
p = 0.5 * p / dot(p, p);
float t = 0.13 * length(p);
float3 col = float3(0.3, 0.4, 0.5);
col += 0.12 * fcos(6.28318 * t * 1.0 + float3(0.0, 0.8, 1.1));
col += 0.11 * fcos(6.28318 * t * 3.1 + float3(0.3, 0.4, 0.1));
col += 0.10 * fcos(6.28318 * t * 5.1 + float3(0.1, 0.7, 1.1));
col += 0.10 * fcos(6.28318 * t * 17.1 + float3(0.2, 0.6, 0.7));
col += 0.10 * fcos(6.28318 * t * 31.1 + float3(0.1, 0.6, 0.7));
col += 0.10 * fcos(6.28318 * t * 65.1 + float3(0.0, 0.5, 0.8));
col += 0.10 * fcos(6.28318 * t * 115.1 + float3(0.1, 0.4, 0.7));
col += 0.10 * fcos(6.28318 * t * 265.1 + float3(1.1, 1.4, 2.7));
col = clamp(col, 0.0, 1.0);
return col;
}
static void calcColor(float3 ro, float3 rd, float3 nor, float d, float len, int idx,
bool si, float td,
thread float4& colx, thread float4& colsi) {
float3 pos = ro + rd * d;
float a = 1.0 - smoothstep(len - 0.15 * 0.5, len + 0.00001, length(pos));
float3 col = getColor(pos);
colx = float4(col, a);
if (si) {
pos = ro + rd * td;
float ta = 1.0 - smoothstep(len - 0.15 * 0.5, len + 0.00001, length(pos));
col = getColor(pos);
colsi = float4(col, ta);
}
}
static bool iBilinearPatch(float3 ro, float3 rd, float4 ps, float4 ph, float sz,
thread float& t, thread float3& norm,
thread bool& si, thread float& tsi, thread float3& normsi,
thread float& fade, thread float& fadesi) {
float3 va = float3(0.0, 0.0, ph.x + ph.w - ph.y - ph.z);
float3 vb = float3(0.0, ps.w - ps.y, ph.z - ph.x);
float3 vc = float3(ps.z - ps.x, 0.0, ph.y - ph.x);
float3 vd = float3(ps.xy, ph.x);
t = -1.0;
tsi = -1.0;
si = false;
fade = 1.0;
fadesi = 1.0;
norm = float3(0.0, 1.0, 0.0);
normsi = float3(0.0, 1.0, 0.0);
float tmp = 1.0 / (vb.y * vc.x);
float a = 0.0, b = 0.0, c = 0.0;
float d = va.z * tmp;
float e = 0.0, f = 0.0;
float g = (vc.z * vb.y - vd.y * va.z) * tmp;
float h = (vb.z * vc.x - va.z * vd.x) * tmp;
float i = -1.0;
float j = (vd.x * vd.y * va.z + vd.z * vb.y * vc.x) * tmp - (vd.y * vb.z * vc.x + vd.x * vc.z * vb.y) * tmp;
float p = dot(float3(a, b, c), rd.xzy * rd.xzy) + dot(float3(d, e, f), rd.xzy * rd.zyx);
float q = dot(float3(2.0, 2.0, 2.0) * ro.xzy * rd.xyz, float3(a, b, c)) + dot(ro.xzz * rd.zxy, float3(d, d, e)) +
dot(ro.yyx * rd.zxy, float3(e, f, f)) + dot(float3(g, h, i), rd.xzy);
float r = dot(float3(a, b, c), ro.xzy * ro.xzy) + dot(float3(d, e, f), ro.xzy * ro.zyx) + dot(float3(g, h, i), ro.xzy) + j;
if (abs(p) < 0.000001) {
float tt = -r / q;
if (tt <= 0.0) return false;
t = tt;
float3 pos = ro + t * rd;
if (length(pos) > sz) return false;
float3 grad = float3(2.0) * pos.xzy * float3(a, b, c) + pos.zxz * float3(d, d, e) + pos.yyx * float3(f, e, f) + float3(g, h, i);
norm = -normalize(grad);
return true;
} else {
float sq = q * q - 4.0 * p * r;
if (sq < 0.0) return false;
float s = sqrt(sq);
float t0 = (-q + s) / (2.0 * p);
float t1 = (-q - s) / (2.0 * p);
float tt1 = min(t0 < 0.0 ? t1 : t0, t1 < 0.0 ? t0 : t1);
float tt2 = max(t0 > 0.0 ? t1 : t0, t1 > 0.0 ? t0 : t1);
float tt0 = tt1;
if (tt0 <= 0.0) return false;
float3 pos = ro + tt0 * rd;
bool ru = step(sz, length(pos)) > 0.5;
if (ru) {
tt0 = tt2;
pos = ro + tt0 * rd;
}
if (tt0 <= 0.0) return false;
bool ru2 = step(sz, length(pos)) > 0.5;
if (ru2) return false;
if ((tt2 > 0.0) && (!ru) && !(step(sz, length(ro + tt2 * rd)) > 0.5)) {
si = true;
fadesi = s;
tsi = tt2;
float3 tpos = ro + tsi * rd;
float3 tgrad = float3(2.0) * tpos.xzy * float3(a, b, c) + tpos.zxz * float3(d, d, e) +
tpos.yyx * float3(f, e, f) + float3(g, h, i);
normsi = -normalize(tgrad);
}
fade = s;
t = tt0;
float3 grad = float3(2.0) * pos.xzy * float3(a, b, c) + pos.zxz * float3(d, d, e) + pos.yyx * float3(f, e, f) + float3(g, h, i);
norm = -normalize(grad);
return true;
}
}
static float dot2(float3 v) { return dot(v, v); }
static float segShadow(float3 ro, float3 rd, float3 pa, float sh) {
float dm = dot(rd.yz, rd.yz);
float k1 = (ro.x - pa.x) * dm;
float k2 = (ro.x + pa.x) * dm;
float2 k5 = (ro.yz + pa.yz) * dm;
float k3 = dot(ro.yz + pa.yz, rd.yz);
float2 k4 = (pa.yz + pa.yz) * rd.yz;
float2 k6 = (pa.yz + pa.yz) * dm;
for (int i = 0; i < 4; i++) {
float2 s = float2(i & 1, i >> 1);
float t = dot(s, k4) - k3;
if (t > 0.0) {
sh = min(sh, dot2(float3(clamp(-rd.x * t, k1, k2), k5 - k6 * s) + rd * t) / (t * t));
}
}
return sh;
}
static float boxSoftShadow(float3 ro, float3 rd, float3 rad, float sk) {
rd += 0.0001 * (1.0 - abs(sign(rd)));
float3 rdd = rd;
float3 roo = ro;
float3 m = 1.0 / rdd;
float3 n = m * roo;
float3 k = abs(m) * rad;
float3 t1 = -n - k;
float3 t2 = -n + k;
float tN = max(max(t1.x, t1.y), t1.z);
float tF = min(min(t2.x, t2.y), t2.z);
if (tN < tF && tF > 0.0) return 0.0;
float sh = 1.0;
sh = segShadow(roo.xyz, rdd.xyz, rad.xyz, sh);
sh = segShadow(roo.yzx, rdd.yzx, rad.yzx, sh);
sh = segShadow(roo.zxy, rdd.zxy, rad.zxy, sh);
sh = clamp(sk * sqrt(sh), 0.0, 1.0);
return sh * sh * (3.0 - 2.0 * sh);
}
static float boxRay(float3 ro, float3 rd, float3 r, thread float3& nn, bool entering) {
rd += 0.0001 * (1.0 - abs(sign(rd)));
float3 dr = 1.0 / rd;
float3 n = ro * dr;
float3 k = r * abs(dr);
float3 pin = -k - n;
float3 pout = k - n;
float tin = max(pin.x, max(pin.y, pin.z));
float tout = min(pout.x, min(pout.y, pout.z));
if (tin > tout) return -1.0;
if (entering) {
nn = -sign(rd) * step(pin.zxy, pin.xyz) * step(pin.yzx, pin.xyz);
} else {
nn = sign(rd) * step(pout.xyz, pout.zxy) * step(pout.xyz, pout.yzx);
}
return entering ? tin : tout;
}
static float3 bgcol(float3 rd) {
return mix(float3(0.01), float3(0.336, 0.458, 0.668), 1.0 - pow(abs(rd.z + 0.25), 1.3));
}
static float3 background(float3 ro, float3 rd, float3 l_dir, thread float& alpha) {
float t = (-BOXDIMS.z - ro.z) / rd.z;
alpha = 0.0;
float3 bgc = bgcol(rd);
if (t < 0.0) return bgc;
float2 uv = ro.xy + t * rd.xy;
float shad = boxSoftShadow((ro + t * rd), normalize(l_dir + float3(0.0, 0.0, 1.0)) * rotz(PI * 0.65), BOXDIMS, 1.5);
float aofac = smoothstep(-0.95, 0.75, length(abs(uv) - min(abs(uv), float2(0.45))));
aofac = min(aofac, smoothstep(-0.65, 1.0, shad));
float lght = max(dot(normalize(ro + t * rd + float3(0.0, -0.0, -5.0)), normalize(l_dir - float3(0.0, 0.0, 1.0)) * rotz(PI * 0.65)), 0.0);
float3 col = mix(float3(0.4), float3(0.71, 0.772, 0.895), lght * lght * aofac + 0.05) * aofac;
alpha = 1.0 - smoothstep(7.0, 10.0, length(uv));
return mix(col * length(col) * 0.8, bgc, smoothstep(7.0, 10.0, length(uv)));
}
static float4 insides(float3 ro, float3 rd, float3 nor_c, float3 l_dir, thread float& tout) {
tout = -1.0;
float pi = 3.1415926;
if (abs(nor_c.x) > 0.5) {
rd = rd.xzy * nor_c.x;
ro = ro.xzy * nor_c.x;
} else if (abs(nor_c.z) > 0.5) {
l_dir = l_dir * roty(pi);
rd = rd.yxz * nor_c.z;
ro = ro.yxz * nor_c.z;
} else if (abs(nor_c.y) > 0.5) {
l_dir = l_dir * rotz(-pi * 0.5);
rd = rd * nor_c.y;
ro = ro * nor_c.y;
}
const float curvature = 0.5;
float bil_size = 1.0;
float4 ps = float4(-bil_size, -bil_size, bil_size, bil_size) * curvature;
float4 ph = float4(-bil_size, bil_size, bil_size, -bil_size) * curvature;
float4 colx[3] = { float4(0.0), float4(0.0), float4(0.0) };
float3 dx[3] = { float3(-1.0), float3(-1.0), float3(-1.0) };
float4 colxsi[3] = { float4(0.0), float4(0.0), float4(0.0) };
int order[3] = { 0, 1, 2 };
for (int i = 0; i < 3; i++) {
if (abs(nor_c.x) > 0.5) {
ro = ro * rotz(-pi * (1.0 / 3.0));
rd = rd * rotz(-pi * (1.0 / 3.0));
} else if (abs(nor_c.z) > 0.5) {
ro = ro * rotz(pi * (1.0 / 3.0));
rd = rd * rotz(pi * (1.0 / 3.0));
} else if (abs(nor_c.y) > 0.5) {
ro = ro * rotx(pi * (1.0 / 3.0));
rd = rd * rotx(pi * (1.0 / 3.0));
}
float3 normnew;
float tnew;
bool si;
float tsi;
float3 normsi;
float fade;
float fadesi;
if (iBilinearPatch(ro, rd, ps, ph, bil_size, tnew, normnew, si, tsi, normsi, fade, fadesi)) {
if (tnew > 0.0) {
float4 tcol, tcolsi;
calcColor(ro, rd, normnew, tnew, bil_size, i, si, tsi, tcol, tcolsi);
if (tcol.a > 0.0) {
dx[i] = float3(tnew, float(si), tsi);
float dif = clamp(dot(normnew, l_dir), 0.0, 1.0);
float amb = clamp(0.5 + 0.5 * dot(normnew, l_dir), 0.0, 1.0);
{
float3 shad = float3(0.32, 0.43, 0.54) * amb + float3(1.0, 0.9, 0.7) * dif;
const float3 tcr = float3(1.0, 0.21, 0.11);
float ta = clamp(length(tcol.rgb), 0.0, 1.0);
tcol = clamp(tcol * tcol * 2.0, 0.0, 1.0);
float4 tvalx = float4(tcol.rgb * shad * 1.4 + 3.0 * (tcr * tcol.rgb) * clamp(1.0 - (amb + dif), 0.0, 1.0), min(tcol.a, ta));
tvalx.rgb = clamp(2.0 * tvalx.rgb * tvalx.rgb, 0.0, 1.0);
tvalx *= min(fade * 5.0, 1.0);
colx[i] = tvalx;
}
if (si) {
dif = clamp(dot(normsi, l_dir), 0.0, 1.0);
amb = clamp(0.5 + 0.5 * dot(normsi, l_dir), 0.0, 1.0);
float3 shad = float3(0.32, 0.43, 0.54) * amb + float3(1.0, 0.9, 0.7) * dif;
const float3 tcr = float3(1.0, 0.21, 0.11);
float ta = clamp(length(tcolsi.rgb), 0.0, 1.0);
tcolsi = clamp(tcolsi * tcolsi * 2.0, 0.0, 1.0);
float4 tvalx = float4(tcolsi.rgb * shad + 3.0 * (tcr * tcolsi.rgb) * clamp(1.0 - (amb + dif), 0.0, 1.0), min(tcolsi.a, ta));
tvalx.rgb = clamp(2.0 * tvalx.rgb * tvalx.rgb, 0.0, 1.0);
tvalx.rgb *= min(fadesi * 5.0, 1.0);
colxsi[i] = tvalx;
}
}
}
}
}
// sort by dx[*].x descending (3 passes of bubble sort like the GLSL)
if (dx[0].x < dx[1].x) { float3 tv = dx[0]; dx[0] = dx[1]; dx[1] = tv; int ti = order[0]; order[0] = order[1]; order[1] = ti; }
if (dx[1].x < dx[2].x) { float3 tv = dx[1]; dx[1] = dx[2]; dx[2] = tv; int ti = order[1]; order[1] = order[2]; order[2] = ti; }
if (dx[0].x < dx[1].x) { float3 tv = dx[0]; dx[0] = dx[1]; dx[1] = tv; int ti = order[0]; order[0] = order[1]; order[1] = ti; }
tout = max(max(dx[0].x, dx[1].x), dx[2].x);
float a = 1.0;
if (dx[0].y < 0.5) {
a = colx[order[0]].a;
}
bool rul[3] = {
((dx[0].y > 0.5) && (dx[1].x <= 0.0)),
((dx[1].y > 0.5) && (dx[0].x > dx[1].z)),
((dx[2].y > 0.5) && (dx[1].x > dx[2].z))
};
for (int k = 0; k < 3; k++) {
if (rul[k]) {
float4 tcolxsi = colxsi[order[k]];
float4 tcolx = colx[order[k]];
float4 tvalx = mix(tcolxsi, tcolx, tcolx.a);
float4 tvalx2 = mix(float4(0.0), tvalx, max(tcolx.a, tcolxsi.a));
colx[order[k]] = tvalx2;
}
}
float a1 = (dx[1].y < 0.5) ? colx[order[1]].a : ((dx[1].z > dx[0].x) ? colx[order[1]].a : 1.0);
float a2 = (dx[2].y < 0.5) ? colx[order[2]].a : ((dx[2].z > dx[1].x) ? colx[order[2]].a : 1.0);
float3 col = mix(mix(colx[order[0]].rgb, colx[order[1]].rgb, a1), colx[order[2]].rgb, a2);
a = max(max(a, a1), a2);
return float4(col, a);
}
fragment float4 cube_lines_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float iTime = U.iTime;
float3 l_dir = normalize(float3(0.0, 1.0, 0.0));
l_dir = l_dir * rotz(0.5);
float mouseY = PI * 0.49 - smoothstep(0.0, 8.5, fmod((iTime + tshift) * 0.33, 25.0))
* (1.0 - smoothstep(14.0, 24.0, fmod((iTime + tshift) * 0.33, 25.0))) * 0.55 * PI;
float mouseX = -2.0 * PI - 0.25 * (iTime * ROTATION_SPEED + tshift);
float3 eye = 4.0 * float3(cos(mouseX) * cos(mouseY), sin(mouseX) * cos(mouseY), sin(mouseY));
float3 w = normalize(-eye);
float3 up = float3(0.0, 0.0, 1.0);
float3 u = normalize(cross(w, up));
float3 v = cross(u, w);
float4 tot = float4(0.0);
float2 uv = (fragCoord - 0.5 * U.iResolution) / U.iResolution.x;
float3 rd = normalize(w * FDIST + uv.x * u + uv.y * v);
float3 ni;
float t = boxRay(eye, rd, BOXDIMS, ni, true);
float3 ro = eye + t * rd;
float2 coords = ro.xy * ni.z / BOXDIMS.xy + ro.yz * ni.x / BOXDIMS.yz + ro.zx * ni.y / BOXDIMS.zx;
float fadeborders = (1.0 - smoothstep(0.915, 1.05, abs(coords.x))) * (1.0 - smoothstep(0.915, 1.05, abs(coords.y)));
if (t > 0.0) {
float3 col = float3(0.0);
float R0 = (IOR - 1.0) / (IOR + 1.0);
R0 *= R0;
float2 theta = float2(0.0);
float3 n = float3(cos(theta.x) * sin(theta.y), sin(theta.x) * sin(theta.y), cos(theta.y));
float3 nr = n.zxy * ni.x + n.yzx * ni.y + n.xyz * ni.z;
float3 rdr = reflect(rd, nr);
float talpha;
float3 reflcol = background(ro, rdr, l_dir, talpha);
float3 rd2 = refract(rd, nr, 1.0 / IOR);
float accum = 1.0;
float3 no2 = ni;
float3 ro_refr = ro;
float4 colo[2] = { float4(0.0), float4(0.0) };
for (int j = 0; j < 2; j++) {
float tb;
float2 coords2 = ro_refr.xy * no2.z + ro_refr.yz * no2.x + ro_refr.zx * no2.y;
float3 eye2 = float3(coords2, -1.0);
float3 rd2trans = rd2.yzx * no2.x + rd2.zxy * no2.y + rd2.xyz * no2.z;
rd2trans.z = -rd2trans.z;
float4 internalcol = insides(eye2, rd2trans, no2, l_dir, tb);
if (tb > 0.0) {
internalcol.rgb *= accum;
colo[j] = internalcol;
}
if ((tb <= 0.0) || (internalcol.a < 1.0)) {
float tout = boxRay(ro_refr, rd2, BOXDIMS, no2, false);
no2 = n.zyx * no2.x + n.xzy * no2.y + n.yxz * no2.z;
float3 rout = ro_refr + tout * rd2;
float3 rdout = refract(rd2, -no2, IOR);
float fresnel2 = R0 + (1.0 - R0) * pow(1.0 - dot(rdout, no2), 1.3);
rd2 = reflect(rd2, -no2);
ro_refr = rout;
ro_refr.z = max(ro_refr.z, -0.999);
accum *= fresnel2;
}
}
float fresnel = R0 + (1.0 - R0) * pow(1.0 - dot(-rd, nr), 5.0);
col = mix(mix(colo[1].rgb * colo[1].a, colo[0].rgb, colo[0].a) * fadeborders, reflcol, pow(fresnel, 1.5));
col = clamp(col, 0.0, 1.0);
float cineshader_alpha = clamp(0.15 * dot(eye, ro), 0.0, 1.0);
tot += float4(col, cineshader_alpha);
} else {
float alpha;
tot += float4(background(eye, rd, l_dir, alpha), 0.15);
}
float4 outColor = tot;
outColor.rgb = clamp(outColor.rgb, 0.0, 1.0);
return outColor;
}

Binary file not shown.

View File

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

View File

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

127
shaders/dbz/dbz.frag.msl Normal file
View File

@@ -0,0 +1,127 @@
#include <metal_stdlib>
using namespace metal;
// New Leaked 3I/Atlas NASA Footage — msm01
// MSL port of dbz.vk.glsl.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
constant float NBCaps = 3.0;
static float2x2 r2d(float a) {
float c = cos(a), s = sin(a);
return float2x2(c, s, -s, c);
}
static float hash12(float2 p) {
float3 p3 = fract(float3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
static float fbm(float2 v_p) {
float pvpx = v_p.x;
float2 V1 = float2(floor(pvpx));
float2 V2 = float2(floor(pvpx + 1.0));
return mix(hash12(V1), hash12(V2), smoothstep(0.0, 1.0, fract(pvpx)));
}
#define S(a,b,c) smoothstep(a,b,c)
fragment float4 dbz_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float2 p = float2((1.0 / U.iResolution.y) * (fragCoord.x - U.iResolution.x / 2.0),
fragCoord.y / U.iResolution.y - 0.5);
p.x = -p.x;
p *= 150.0;
float4 col = float4(0.05, 0.05, 0.15, 1.0);
float2 save1 = p;
p = p * r2d(-0.05);
col = mix(col, float4(0.2, 0.3, 0.5, 1.0),
smoothstep(75.0, 0.0, abs(p.y - 5.0 * fbm(float2(0.01 * (p.x - 33.333 * U.iTime))) + 3.5)));
p = p * r2d(0.05);
p = p * r2d(-0.05);
p *= 0.35;
p += float2(-5.0 * U.iTime, 0.0);
float2 b = fract(5.0 * p);
p = floor(5.0 * p);
if (fbm(float2(p.x * p.y)) > 0.996) {
col += clamp(1.0 - pow(3.0 * length(b + float2(-0.5)), 0.5), 0.0, 1.0);
}
p = save1;
float2 save3;
float Nb_Capsules = clamp(NBCaps, 0.0, 4.0);
for (float i = 0.0; i < Nb_Capsules; i++) {
p = save1;
p = p * r2d(-0.05);
p *= 2.5;
p *= 1.0 - 0.25 * i;
p += float2(150.0 * fbm(float2(0.15 * U.iTime + i * 54.321)) - 75.0,
50.0 * sin(0.25 * U.iTime + i * 54.321) - 25.0);
save3 = p;
p *= 0.04;
p.y = abs(p.y);
if (p.x > 0.0) {
col += float4(0.0, 1.0, 0.5, 1.0) * smoothstep(0.2, 0.0, abs(p.y - 0.05 * fbm(float2(1.5 * p.x - 40.0 * U.iTime))) - 0.05) * smoothstep(29.0, 0.0, abs(p.x));
col += float4(1.0, 1.0, 1.0, 1.0) * smoothstep(0.1, 0.0, abs(p.y - 0.05 * fbm(float2(1.5 * p.x - 40.0 * U.iTime))) - 0.05) * smoothstep(29.0, 0.0, abs(p.x));
}
p = save3;
p.y = abs(p.y);
p += float2(-10.0, 0.0);
p *= float2(0.75, 1.0);
col += 0.8 * float4(0.0, 1.0, 0.5, 1.0) * S(20.0, 0.0, length(p) - 25.0 + 7.0 * sin(0.30 * length(p) * atan2(p.y, p.x) + 55.0 * U.iTime));
col += 0.8 * float4(1.0, 1.0, 1.0, 1.0) * S(20.0, 0.0, length(p) - 20.0 + 7.0 * sin(0.30 * length(p) * atan2(p.y, p.x) + 55.0 * U.iTime));
p = save3;
col = mix(col, float4(1.0), 0.5 * S(10.0, 0.0, length(p + float2(5.0, 0.0)) - 20.0) * abs(sin(50.0 * U.iTime)));
col = mix(col, float4(1.0), 0.5 * S(20.0, 0.0, length(p + float2(5.0, 0.0)) - 20.0));
col = mix(col, float4(1.0), S(0.01, 0.0, length(p) - 20.0));
if (length(p) - 20.0 < 0.0) {
col = mix(col, float4(0.65, 0.68, 0.68 + 0.1 * (3.0 - i), 1.0), S(0.5, 0.0, length(p - float2(2.0, 0.0)) - 17.0));
if (S(0.0, 1.0, length(float2(3.0, 2.0) * p + float2(33.5, 0.0)) - 23.0) > 0.0) {
col = mix(col, float4(0.45, 0.55, 0.55 + 0.1 * (3.0 - i), 1.0),
0.75 * S(0.5, 0.0, length(p - float2(2.0, 0.0) + 0.5 * fbm(float2(4.5 * atan2(p.y, p.x)))) - 9.0));
}
col = mix(col, float4(0.0), S(0.2, 0.0, abs(length(p) - 19.9) - 0.20));
col = mix(col, float4(0.5, 0.2, 0.3, 1.0)
- S(5.0, 0.0, length(p + float2(-6.0, 15.0)) - 20.0)
- S(0.25, 0.0, abs(length(p + float2(0.0, 3.0)) - 15.0) - 0.4)
- S(0.0, 1.5, p.y - 8.5)
+ 0.25 * float4(1.0, 0.5, 0.0, 1.0) * S(10.0, 0.0, abs(p.y))
,
S(0.5, 0.0, length(float2(3.0, 2.0) * p + float2(35.0, 0.0)) - 19.9));
col = mix(col, float4(0.0, 0.0, 0.0, 1.0), S(1.0, 0.0, abs(length(float2(3.0, 2.0) * p + float2(35.0, 0.0)) - 19.9) - 0.1));
col = mix(col, float4(0.0, 0.0, 0.0, 1.0), S(1.0, 0.0, abs(length(float2(3.0, 2.0) * p + float2(33.5, 0.0)) - 23.0) - 0.1));
if (p.y > 0.0) col = mix(col, float4(0.0, 0.0, 0.0, 1.0), S(1.0, 0.0, abs(length(float2(3.0, 2.0) * p + float2(29.0, 0.0)) - 30.0) - 0.1));
if (p.y < 0.0) col = mix(col, float4(0.0, 0.0, 0.0, 1.0), S(1.0, 0.0, abs(length(float2(3.0, 2.0) * p + float2(-31.0, 0.0)) - 30.0) - 0.1));
}
}
return clamp(col, 0.0, 1.0);
}

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

Binary file not shown.

127
shaders/dbz/dbz.vk.glsl Normal file
View File

@@ -0,0 +1,127 @@
#version 450
// Name: New Leaked 3I/Atlas NASA Footage
// Author: msm01
// URL: https://www.shadertoy.com/view/3ftcRr
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
#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); }
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);
}
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) {
vec2 p = vec2((1.0 / iResolution.y) * (fragCoord.x - iResolution.x / 2.0),
fragCoord.y / iResolution.y - 0.5);
p.x = -p.x;
p *= 150.0;
vec4 col = vec4(0.05, 0.05, 0.15, 1.0);
vec2 save1 = p;
vec2 save2 = p;
p *= r2d(-0.05);
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)));
p *= r2d(0.05);
p *= r2d(-0.05);
p *= 0.35;
p += vec2(-5.0 * iTime, 0.0);
vec2 b = fract(5.0 * p);
p = floor(5.0 * p);
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);
p = save1;
vec2 save3;
float Nb_Capsules = clamp(NBCaps, 0.0, 4.0);
for (float i = 0.0; i < Nb_Capsules; i++) {
p = save1;
p *= r2d(-0.05);
p *= 2.5;
p *= 1.0 - 0.25 * i;
p += vec2(150.0 * fbm(vec2(0.15 * iTime + i * 54.321)) - 75.0,
50.0 * sin(0.25 * iTime + i * 54.321) - 25.0);
save3 = p;
p *= 0.04;
p.y = abs(p.y);
if (p.x > 0.0) {
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));
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));
}
p = save3;
p.y = abs(p.y);
p += vec2(-10.0, 0.0);
p *= vec2(0.75, 1.0);
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));
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;
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)));
col = mix(col, vec4(1.0), 0.5 * s(20.0, 0.0, length(p + vec2(5.0, 0.0)) - 20.0));
col = mix(col, vec4(1.0), s(0.01, 0.0, length(p) - 20.0));
if (length(p) - 20.0 < 0.0) {
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));
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));
col = mix(col, vec4(0.0), s(0.2, 0.0, abs(length(p) - 19.9) - 0.20));
col = mix(col, vec4(0.5, 0.2, 0.3, 1.0)
- s(5.0, 0.0, length(p + vec2(-6.0, 15.0)) - 20.0)
- s(0.25, 0.0, abs(length(p + vec2(0.0, 3.0)) - 15.0) - 0.4)
- s(0.0, 1.5, p.y - 8.5)
+ 0.25 * vec4(1.0, 0.5, 0.0, 1.0) * s(10.0, 0.0, abs(p.y))
,
s(0.5, 0.0, length(vec2(3.0, 2.0) * p + vec2(35.0, 0.0)) - 19.9));
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));
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));
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));
}
}
fragColor = clamp(col, 0.0, 1.0);
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

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

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

View File

@@ -0,0 +1,67 @@
#include <metal_stdlib>
using namespace metal;
// Fractal Pyramid — bradjamesgrant
// MSL port of fractal_pyramid.vk.glsl.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
static float3 palette(float d) {
return mix(float3(0.2, 0.7, 0.9), float3(1.0, 0.0, 1.0), d);
}
static float2 rotate(float2 p, float a) {
float c = cos(a);
float s = sin(a);
return p * float2x2(c, s, -s, c);
}
static float mapScene(float3 p, float iTime) {
for (int i = 0; i < 8; ++i) {
float t = iTime * 0.2;
p.xz = rotate(p.xz, t);
p.xy = rotate(p.xy, t * 1.89);
p.xz = abs(p.xz);
p.xz -= 0.5;
}
return dot(sign(p), p) / 5.0;
}
static float4 rm(float3 ro, float3 rd, float iTime) {
float t = 0.0;
float3 col = float3(0.0);
float d = 1.0;
for (int i = 0; i < 64; ++i) {
float3 p = ro + rd * t;
d = mapScene(p, iTime) * 0.5;
if (d < 0.02) break;
if (d > 100.0) break;
col += palette(length(p) * 0.1) / (400.0 * d);
t += d;
}
return float4(col, 1.0 / (d * 100.0));
}
fragment float4 fractal_pyramid_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float2 uv = (fragCoord - (U.iResolution * 0.5)) / U.iResolution.x;
float3 ro = float3(0.0, 0.0, -50.0);
ro.xz = rotate(ro.xz, U.iTime);
float3 cf = normalize(-ro);
float3 cs = normalize(cross(cf, float3(0.0, 1.0, 0.0)));
float3 cu = normalize(cross(cf, cs));
float3 uuv = ro + cf * 3.0 + uv.x * cs + uv.y * cu;
float3 rd = normalize(uuv - ro);
return rm(ro, rd, U.iTime);
}

Binary file not shown.

View File

@@ -0,0 +1,71 @@
#version 450
// Name: Fractal Pyramid
// Author: bradjamesgrant
// URL: https://www.shadertoy.com/view/tsXBzS
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
vec3 palette(float d) {
return mix(vec3(0.2, 0.7, 0.9), vec3(1.0, 0.0, 1.0), d);
}
vec2 rotate(vec2 p, float a) {
float c = cos(a);
float s = sin(a);
return p * mat2(c, s, -s, c);
}
float mapScene(vec3 p) {
for (int i = 0; i < 8; ++i) {
float t = iTime * 0.2;
p.xz = rotate(p.xz, t);
p.xy = rotate(p.xy, t * 1.89);
p.xz = abs(p.xz);
p.xz -= 0.5;
}
return dot(sign(p), p) / 5.0;
}
vec4 rm(vec3 ro, vec3 rd) {
float t = 0.0;
vec3 col = vec3(0.0);
float d = 1.0;
for (int i = 0; i < 64; ++i) {
vec3 p = ro + rd * t;
d = mapScene(p) * 0.5;
if (d < 0.02) break;
if (d > 100.0) break;
col += palette(length(p) * 0.1) / (400.0 * d);
t += d;
}
return vec4(col, 1.0 / (d * 100.0));
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord - (iResolution.xy * 0.5)) / iResolution.x;
vec3 ro = vec3(0.0, 0.0, -50.0);
ro.xz = rotate(ro.xz, iTime);
vec3 cf = normalize(-ro);
vec3 cs = normalize(cross(cf, vec3(0.0, 1.0, 0.0)));
vec3 cu = normalize(cross(cf, cs));
vec3 uuv = ro + cf * 3.0 + uv.x * cs + uv.y * cu;
vec3 rd = normalize(uuv - ro);
vec4 col = rm(ro, rd);
fragColor = col;
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

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

View File

@@ -0,0 +1,89 @@
#include <metal_stdlib>
using namespace metal;
// Just Another Cube — mrange
// MSL port of just_another_cube.vk.glsl. The Shadertoy original uses
// mutable file-scope globals; in MSL we pass them by reference.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
constant float M = 1e-3;
static float D(float3 p,
thread float2x2& R,
thread float& d,
thread float& G) {
p.xy = p.xy * R;
p.xz = p.xz * R;
float3 S = sin(123.0 * p);
float shell = abs(length(p) - 0.6);
p *= p * p * p;
d = pow(dot(p, p), 0.125) - 0.5
- pow(1.0 + S.x * S.y * S.z, 8.0) / 1e5;
G = min(G, max(shell, d));
return d;
}
fragment float4 just_another_cube_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 C = in.uv * U.iResolution;
float2x2 R;
float d = 1.0, z = 0.0, G = 9.0;
float3 r = float3(U.iResolution, U.iResolution.y);
float3 I = normalize(float3(C - 0.5 * r.xy, r.y));
float3 B = float3(1, 2, 9) * M;
float3 p = float3(0.0);
float3 O = float3(0.0);
{
float4 cs = cos(0.3 * U.iTime + float4(0, 11, 33, 0));
R = float2x2(cs.x, cs.y, cs.z, cs.w);
}
for (; z < 9.0 && d > M; z += D(p, R, d, G)) {
p = z * I;
p.z -= 2.0;
}
if (z < 9.0) {
for (int i = 0; i < 3; ) {
r = float3(0.0);
r[i] = M;
O[i++] = D(p + r, R, d, G) - D(p - r, R, d, G);
}
O = normalize(O);
z = 1.0 + dot(O, I);
r = reflect(I, O);
C = (p + r * (5.0 - p.y) / abs(r.y)).xz;
float3 colorTerm;
if (r.y > 0.0) {
d = sqrt(length(C * C)) + 1.0;
colorTerm = 5e2 * smoothstep(5.0, 4.0, d) * d * B;
} else {
colorTerm = exp(-2.0 * length(C)) * (B / M - 1.0);
}
O = z * z * colorTerm + pow(1.0 + O.y, 5.0) * B;
}
float4 result = sqrt(float4(O + B / G, O.x + B.x / G));
return result;
}

Binary file not shown.

View File

@@ -0,0 +1,88 @@
#version 450
// Name: Just Another Cube
// Author: mrange
// URL: https://www.shadertoy.com/view/3XdXRr
// CC0: Just another cube
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
mat2 R;
float d = 1.;
float z = 0.;
float G = 9.;
float M = 1e-3;
float D(vec3 p) {
p.xy *= R;
p.xz *= R;
vec3 S = sin(123. * p);
G = min(
G,
max(
abs(length(p) - .6),
d = pow(dot(p *= p * p * p, p), .125) - .5
- pow(1. + S.x * S.y * S.z, 8.) / 1e5
)
);
return d;
}
void mainImage(out vec4 o, vec2 C) {
vec3 p,
O,
r = vec3(iResolution.xy, iResolution.y),
I = normalize(vec3(C - .5 * r.xy, r.y)),
B = vec3(1, 2, 9) * M;
for (
R = mat2(cos(.3 * iTime + vec4(0, 11, 33, 0)));
z < 9. && d > M;
z += D(p)
)
p = z * I,
p.z -= 2.;
O = vec3(0.0);
if (z < 9.) {
for (int i = 0; i < 3; ) {
r -= r;
r[i] = M;
O[i++] = D(p + r) - D(p - r);
}
z = 1. + dot(O = normalize(O), I);
r = reflect(I, O);
C = (p + r * (5. - p.y) / abs(r.y)).xz;
O =
z * z *
(
r.y > 0.
? 5e2 * smoothstep(5., 4., d = sqrt(length(C * C)) + 1.) * d * B
: exp(-2. * length(C)) * (B / M - 1.)
)
+ pow(1. + O.y, 5.) * B;
}
o = sqrt(O + B / G).xyzx;
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

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

View File

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

View File

@@ -0,0 +1,95 @@
#include <metal_stdlib>
using namespace metal;
// Octograms — whisky_shusuky
// MSL port of octograms.vk.glsl. The Shadertoy original uses a mutable
// file-scope `gTime`; in MSL we pass it as a parameter through the chain.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
static float2x2 rot(float a) {
float c = cos(a), s = sin(a);
return float2x2(c, s, -s, c);
}
static float sdBox(float3 p, float3 b) {
float3 q = abs(p) - b;
return length(max(q, float3(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0);
}
static float boxGeom(float3 pos, float scale) {
pos *= scale;
float base = sdBox(pos, float3(0.4, 0.4, 0.1)) / 1.5;
pos.xy = (pos.xy * 5.0 - float2(0.0, 3.5)) * rot(0.75);
return -base;
}
static float box_set(float3 pos, float gTime) {
float3 pos_origin = pos;
pos = pos_origin;
pos.y += sin(gTime * 0.4) * 2.5;
pos.xy = pos.xy * rot(0.8);
float box1 = boxGeom(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.y -= sin(gTime * 0.4) * 2.5;
pos.xy = pos.xy * rot(0.8);
float box2 = boxGeom(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.x += sin(gTime * 0.4) * 2.5;
pos.xy = pos.xy * rot(0.8);
float box3 = boxGeom(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.x -= sin(gTime * 0.4) * 2.5;
pos.xy = pos.xy * rot(0.8);
float box4 = boxGeom(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.xy = pos.xy * rot(0.8);
float box5 = boxGeom(pos, 0.5) * 6.0;
pos = pos_origin;
float box6 = boxGeom(pos, 0.5) * 6.0;
return max(max(max(max(max(box1, box2), box3), box4), box5), box6);
}
fragment float4 octograms_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float2 p = (fragCoord * 2.0 - U.iResolution) / min(U.iResolution.x, U.iResolution.y);
float3 ro = float3(0.0, -0.2, U.iTime * 4.0);
float3 ray = normalize(float3(p, 1.5));
ray.xy = ray.xy * rot(sin(U.iTime * 0.03) * 5.0);
ray.yz = ray.yz * rot(sin(U.iTime * 0.05) * 0.2);
float t = 0.1;
float ac = 0.0;
for (int i = 0; i < 99; i++) {
float3 pos = ro + ray * t;
pos = fract((pos - 2.0) / 4.0) * 4.0 - 2.0;
float gTime = U.iTime - float(i) * 0.01;
float d = box_set(pos, gTime);
d = max(abs(d), 0.01);
ac += exp(-d * 23.0);
t += d * 0.55;
}
float3 col = float3(ac * 0.02);
col += float3(0.0, 0.2 * abs(sin(U.iTime)), 0.5 + sin(U.iTime) * 0.2);
return float4(col, 1.0 - t * (0.02 + 0.02 * sin(U.iTime)));
}

Binary file not shown.

View File

@@ -0,0 +1,109 @@
#version 450
// Name: Octograms
// Author: whisky_shusuky
// URL: https://www.shadertoy.com/view/tlVGDt
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
float gTime = 0.0;
const float REPEAT = 5.0;
mat2 rot(float a) {
float c = cos(a), s = sin(a);
return mat2(c, s, -s, c);
}
float sdBox(vec3 p, vec3 b) {
vec3 q = abs(p) - b;
return length(max(q, vec3(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0);
}
float boxGeom(vec3 pos, float scale) {
pos *= scale;
float base = sdBox(pos, vec3(.4, .4, .1)) / 1.5;
pos.xy *= 5.0;
pos.y -= 3.5;
pos.xy *= rot(.75);
float result = -base;
return result;
}
float box_set(vec3 pos, float iTimeLocal) {
vec3 pos_origin = pos;
pos = pos_origin;
pos.y += sin(gTime * 0.4) * 2.5;
pos.xy *= rot(.8);
float box1 = boxGeom(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.y -= sin(gTime * 0.4) * 2.5;
pos.xy *= rot(.8);
float box2 = boxGeom(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.x += sin(gTime * 0.4) * 2.5;
pos.xy *= rot(.8);
float box3 = boxGeom(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.x -= sin(gTime * 0.4) * 2.5;
pos.xy *= rot(.8);
float box4 = boxGeom(pos, 2.0 - abs(sin(gTime * 0.4)) * 1.5);
pos = pos_origin;
pos.xy *= rot(.8);
float box5 = boxGeom(pos, .5) * 6.0;
pos = pos_origin;
float box6 = boxGeom(pos, .5) * 6.0;
float result = max(max(max(max(max(box1, box2), box3), box4), box5), box6);
return result;
}
float mapScene(vec3 pos, float iTimeLocal) {
return box_set(pos, iTimeLocal);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 p = (fragCoord.xy * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);
vec3 ro = vec3(0.0, -0.2, iTime * 4.0);
vec3 ray = normalize(vec3(p, 1.5));
ray.xy = ray.xy * rot(sin(iTime * .03) * 5.0);
ray.yz = ray.yz * rot(sin(iTime * .05) * .2);
float t = 0.1;
vec3 col = vec3(0.0);
float ac = 0.0;
for (int i = 0; i < 99; i++) {
vec3 pos = ro + ray * t;
pos = mod(pos - 2.0, 4.0) - 2.0;
gTime = iTime - float(i) * 0.01;
float d = mapScene(pos, iTime);
d = max(abs(d), 0.01);
ac += exp(-d * 23.0);
t += d * 0.55;
}
col = vec3(ac * 0.02);
col += vec3(0.0, 0.2 * abs(sin(iTime)), 0.5 + sin(iTime) * 0.2);
fragColor = vec4(col, 1.0 - t * (0.02 + 0.02 * sin(iTime)));
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

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

View File

@@ -0,0 +1,64 @@
#include <metal_stdlib>
using namespace metal;
// Remember — diatribes
// MSL port of remember.vk.glsl.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
static float hash12(float2 p) {
float3 p3 = fract(float3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
fragment float4 remember_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float2 u = fragCoord;
float3 q, p = float3(U.iResolution, U.iResolution.x / U.iResolution.y);
float i = 0.0, s = 0.0;
float d = 0.125 * hash12(u);
float t = U.iTime * 0.1;
u = (u + u - p.xy) / p.y;
if (abs(u.y) > 0.8) { return float4(0); }
float4 o = float4(0.0);
for (; i < 64.0; i++) {
q = p = float3(u * d, d + t * 5.0);
// mat2(cos(.1*p.z+.1*t+vec4(0,33,11,0))) — column-major: (m00,m10,m01,m11)
float4 cs = cos(0.1 * p.z + 0.1 * t + float4(0, 33, 11, 0));
float2x2 m = float2x2(cs.x, cs.y, cs.z, cs.w);
p.xy = p.xy * m;
q.xz = cos(q.xz);
p.z = cos(p.z);
for (s = 1.0; s++ < 6.0;) {
q += sin(0.6 * t + p.zxy * 0.6);
p += sin(t + t + p.yzx * s) * 0.6;
}
s = 0.02 + abs(min(length(p + 3.0 * sin(p.z * 0.5)) - 4.0, length(q - 2.0 * sin(p.z * 0.4)) - 6.0)) * 0.2;
d += s;
float4 brightTerm = min(0.01 * float4(6, 2, 1, 0) / max(length(u * sin(t + t + t)), 0.001), float4(50.0));
o += brightTerm + 1.0 / s * length(u);
}
o = tanh(max(o / 6e2 + dot(u, u) * 0.35, 0.0));
return o;
}

Binary file not shown.

View File

@@ -0,0 +1,61 @@
#version 450
// Name: Remember
// Author: diatribes
// URL: https://www.shadertoy.com/view/tXSBDK
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
// 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,
d = .125 * hash12(u),
t = iTime * .1;
u = (u + u - p.xy) / p.y;
if (abs(u.y) > .8) { o = vec4(0); return; }
o = vec4(0.0);
for (; i < 64.; i++) {
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);
for (s = 1.; s++ < 6.;
q += sin(.6 * t + p.zxy * .6),
p += sin(t + t + p.yzx * s) * .6);
d += s = .02 + abs(min(length(p + 3. * sin(p.z * .5)) - 4., length(q - 2. * sin(p.z * .4)) - 6.)) * .2;
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);
}
o = tanh(max(o / 6e2 + dot(u, u) * .35, 0.));
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

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

View File

@@ -0,0 +1,181 @@
#include <metal_stdlib>
using namespace metal;
// Seascape — Alexander Alekseev (TDM, 2014)
// MSL port of seascape.vk.glsl. iTime threaded through helpers because
// MSL does not allow file-scope mutable globals.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
constant int NUM_STEPS = 32;
constant float PI = 3.141592;
constant float EPSILON = 1e-3;
constant int ITER_GEOMETRY = 3;
constant int ITER_FRAGMENT = 5;
constant float SEA_HEIGHT = 0.6;
constant float SEA_CHOPPY = 4.0;
constant float SEA_SPEED = 0.8;
constant float SEA_FREQ = 0.16;
constant float3 SEA_BASE = float3(0.0, 0.09, 0.18);
constant float3 SEA_WATER_COLOR = float3(0.8, 0.9, 0.6) * 0.6;
constant float2x2 octave_m = float2x2(1.6, 1.2, -1.2, 1.6);
static float3x3 fromEuler(float3 ang) {
float2 a1 = float2(sin(ang.x), cos(ang.x));
float2 a2 = float2(sin(ang.y), cos(ang.y));
float2 a3 = float2(sin(ang.z), cos(ang.z));
float3x3 m;
m[0] = float3(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] = float3(-a2.y * a1.x, a1.y * a2.y, a2.x);
m[2] = float3(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;
}
static float hash(float2 p) {
float h = dot(p, float2(127.1, 311.7));
return fract(sin(h) * 43758.5453123);
}
static float noise(float2 p) {
float2 i = floor(p);
float2 f = fract(p);
float2 u = f * f * (3.0 - 2.0 * f);
return -1.0 + 2.0 * mix(mix(hash(i + float2(0.0, 0.0)),
hash(i + float2(1.0, 0.0)), u.x),
mix(hash(i + float2(0.0, 1.0)),
hash(i + float2(1.0, 1.0)), u.x), u.y);
}
static float diffuseLight(float3 n, float3 l, float p) {
return pow(dot(n, l) * 0.4 + 0.6, p);
}
static float specularLight(float3 n, float3 l, float3 e, float s) {
float nrm = (s + 8.0) / (PI * 8.0);
return pow(max(dot(reflect(e, n), l), 0.0), s) * nrm;
}
static float3 getSkyColor(float3 e) {
e.y = (max(e.y, 0.0) * 0.8 + 0.2) * 0.8;
return float3(pow(1.0 - e.y, 2.0), 1.0 - e.y, 0.6 + (1.0 - e.y) * 0.4) * 1.1;
}
static float sea_octave(float2 uv, float choppy) {
uv += noise(uv);
float2 wv = 1.0 - abs(sin(uv));
float2 swv = abs(cos(uv));
wv = mix(wv, swv, wv);
return pow(1.0 - pow(wv.x * wv.y, 0.65), choppy);
}
static float seaMap(float3 p, float iTime, int iter) {
float SEA_TIME = 1.0 + iTime * SEA_SPEED;
float freq = SEA_FREQ;
float amp = SEA_HEIGHT;
float choppy = SEA_CHOPPY;
float2 uv = p.xz; uv.x *= 0.75;
float d, h = 0.0;
for (int i = 0; i < iter; i++) {
d = sea_octave((uv + SEA_TIME) * freq, choppy);
d += sea_octave((uv - SEA_TIME) * freq, choppy);
h += d * amp;
uv = uv * octave_m;
freq *= 1.9;
amp *= 0.22;
choppy = mix(choppy, 1.0, 0.2);
}
return p.y - h;
}
static float3 getSeaColor(float3 p, float3 n, float3 l, float3 eye, float3 dist) {
float fresnel = clamp(1.0 - dot(n, -eye), 0.0, 1.0);
fresnel = min(fresnel * fresnel * fresnel, 0.5);
float3 reflected = getSkyColor(reflect(eye, n));
float3 refracted = SEA_BASE + diffuseLight(n, l, 80.0) * SEA_WATER_COLOR * 0.12;
float3 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 += specularLight(n, l, eye, 600.0 * rsqrt(dot(dist, dist)));
return color;
}
static float3 getNormal(float3 p, float eps, float iTime) {
float3 n;
n.y = seaMap(p, iTime, ITER_FRAGMENT);
n.x = seaMap(float3(p.x + eps, p.y, p.z), iTime, ITER_FRAGMENT) - n.y;
n.z = seaMap(float3(p.x, p.y, p.z + eps), iTime, ITER_FRAGMENT) - n.y;
n.y = eps;
return normalize(n);
}
static float heightMapTracing(float3 ori, float3 dir, thread float3& p, float iTime) {
float tm = 0.0;
float tx = 1000.0;
float hx = seaMap(ori + dir * tx, iTime, ITER_GEOMETRY);
if (hx > 0.0) {
p = ori + dir * tx;
return tx;
}
float hm = seaMap(ori, iTime, ITER_GEOMETRY);
for (int i = 0; i < NUM_STEPS; i++) {
float tmid = mix(tm, tx, hm / (hm - hx));
p = ori + dir * tmid;
float hmid = seaMap(p, iTime, ITER_GEOMETRY);
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));
}
static float3 getPixel(float2 coord, float time, float2 iResolution, float iTime) {
float2 uv = coord / iResolution;
uv = uv * 2.0 - 1.0;
uv.x *= iResolution.x / iResolution.y;
float3 ang = float3(sin(time * 3.0) * 0.1, sin(time) * 0.2 + 0.3, time);
float3 ori = float3(0.0, 3.5, time * 5.0);
float3 dir = normalize(float3(uv.xy, -2.0));
dir.z += length(uv) * 0.14;
dir = normalize(dir) * fromEuler(ang);
float3 p;
heightMapTracing(ori, dir, p, iTime);
float3 dist = p - ori;
float EPSILON_NRM = 0.1 / iResolution.x;
float3 n = getNormal(p, dot(dist, dist) * EPSILON_NRM, iTime);
float3 light = normalize(float3(0.0, 1.0, 0.8));
return mix(
getSkyColor(dir),
getSeaColor(p, n, light, dir, dist),
pow(smoothstep(0.0, -0.02, dir.y), 0.2));
}
fragment float4 seascape_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float time = U.iTime * 0.3;
float3 color = getPixel(fragCoord, time, U.iResolution, U.iTime);
return float4(pow(color, float3(0.65)), 1.0);
}

Binary file not shown.

View File

@@ -0,0 +1,200 @@
#version 450
// Name: Seascape
// Author: Alexander Alekseev
// URL: https://www.shadertoy.com/view/Ms2SD1
//
// "Seascape" by Alexander Alekseev aka TDM - 2014
// CC BY-NC-SA 3.0 Unported License.
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
const int NUM_STEPS = 32;
const float PI = 3.141592;
const float EPSILON = 1e-3;
#define EPSILON_NRM (0.1 / iResolution.x)
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);
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);
}
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;
}
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;
}
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;
}
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;
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);
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));
return mix(
getSkyColor(dir),
getSeaColor(p, n, light, dir, dist),
pow(smoothstep(0.0, -0.02, dir.y), 0.2));
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
float time = iTime * 0.3;
vec3 color = getPixel(fragCoord, time);
fragColor = vec4(pow(color, vec3(0.65)), 1.0);
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

View File

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

View File

@@ -0,0 +1,47 @@
#include <metal_stdlib>
using namespace metal;
// Shader Art Coding Introduction — kishimisu
// MSL port of shader_art_coding_introduction.vk.glsl.
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
static float3 palette(float t) {
float3 a = float3(0.5, 0.5, 0.5);
float3 b = float3(0.5, 0.5, 0.5);
float3 c = float3(1.0, 1.0, 1.0);
float3 d = float3(0.263, 0.416, 0.557);
return a + b * cos(6.28318 * (c * t * d));
}
fragment float4 shader_art_coding_introduction_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float2 uv = (fragCoord * 2.0 - U.iResolution) / U.iResolution.y;
float2 uv0 = uv;
float3 finalColor = float3(0.0);
for (float i = 0.0; i < 4.0; i++) {
uv = fract(uv * 1.5) - 0.5;
float d = length(uv) * exp(-length(uv0));
float3 col = palette(length(uv0) + i * 0.4 + U.iTime * 0.4);
d = sin(d * 8.0 + U.iTime) / 8.0;
d = abs(d);
d = pow(0.01 / d, 1.2);
finalColor += col * d;
}
return float4(finalColor, 1.0);
}

View File

@@ -0,0 +1,50 @@
#version 450
// Name: Shader Art Coding Introduction
// Author: kishimisu
// URL: https://www.shadertoy.com/view/mtyGWy
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
vec3 palette(float t) {
vec3 a = vec3(0.5, 0.5, 0.5);
vec3 b = vec3(0.5, 0.5, 0.5);
vec3 c = vec3(1.0, 1.0, 1.0);
vec3 d = vec3(0.263, 0.416, 0.557);
return a + b * cos(6.28318 * (c * t * d));
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord * 2.0 - iResolution.xy) / iResolution.y;
vec2 uv0 = uv;
vec3 finalColor = vec3(0.0);
for (float i = 0.0; i < 4.0; i++) {
uv = fract(uv * 1.5) - 0.5;
float d = length(uv) * exp(-length(uv0));
vec3 col = palette(length(uv0) + i * 0.4 + iTime * 0.4);
d = sin(d * 8.0 + iTime) / 8.0;
d = abs(d);
d = pow(0.01 / d, 1.2);
finalColor += col * d;
}
fragColor = vec4(finalColor, 1.0);
}
void main() {
vec2 fragCoordPixels = vUV * iResolution;
vec4 outColor;
mainImage(outColor, fragCoordPixels);
FragColor = outColor;
}

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

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

View File

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

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

Binary file not shown.

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

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

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

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

View File

@@ -0,0 +1,40 @@
#include <metal_stdlib>
using namespace metal;
// Water — diatribes
// MSL port of water.vk.glsl. Entry point is named after the shader (water_fs).
struct ShadertoyUBO {
float iTime;
float2 iResolution;
};
struct PassthroughVOut {
float4 pos [[position]];
float2 uv;
};
fragment float4 water_fs(PassthroughVOut in [[stage_in]],
constant ShadertoyUBO& U [[buffer(0)]]) {
float2 fragCoord = in.uv * U.iResolution;
float2 u = fragCoord;
float s = 0.002, i = 0.0, n;
float3 r = float3(U.iResolution, U.iResolution.x / U.iResolution.y);
float3 p = float3(0);
u = (u - r.xy / 2.0) / r.y - 0.3;
float4 o = float4(0);
for (; i < 32.0 && s > 0.001; i++) {
float4 term = float4(5, 2, 1, 0) / max(length(u - 0.1), 0.001);
o += min(term, float4(100.0));
p += float3(u * s, s);
s = 1.0 + p.y;
for (n = 0.01; n < 1.0; n += n) {
s += abs(dot(sin(p.z + U.iTime + p / n), float3(1))) * n * 0.1;
}
}
o = tanh(o / 5e2);
return o;
}

Binary file not shown.

View File

@@ -0,0 +1,45 @@
#version 450
// Name: Water
// Author: diatribes
// URL: https://www.shadertoy.com/view/tXjXDy
layout(location = 0) in vec2 vUV;
layout(location = 0) out vec4 FragColor;
layout(set = 3, binding = 0) uniform ShadertoyUBO {
float iTime;
vec2 iResolution;
};
/*
-2 by @FabriceNeyret2
thanks!! :D
If it doesn't display correctly, change line "r/r" to "vec3(1)"
*/
void mainImage( out vec4 o, vec2 u ) {
float s=.002, i=0., n;
vec3 r = vec3(iResolution.xy, iResolution.x/iResolution.y);
vec3 p = vec3(0);
u = (u-r.xy/2.)/r.y-.3;
o = vec4(0);
for(; i < 32. && s > .001; i++) {
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;
}

578
src/audio/jail_audio.cpp Normal file
View File

@@ -0,0 +1,578 @@
#include "audio/jail_audio.hpp"
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <memory>
#include <optional>
#include <vector>
// Només declaracions de stb_vorbis: STB_VORBIS_HEADER_ONLY omet el bloc
// d'implementació. Les definicions les aporta source/external/stb_vorbis_impl.cpp
// (TU aïllat perquè clang-analyzer no dispari fals positius al nostre codi).
#define STB_VORBIS_HEADER_ONLY
// clang-format off
// NOLINTNEXTLINE(bugprone-suspicious-include)
#include "stb_vorbis.c"
// clang-format on
namespace Ja {
// --- Streaming internals (file-scope constants) ---
namespace {
// Bytes-per-sample per canal (sempre s16)
constexpr int MUSIC_BYTES_PER_SAMPLE = 2;
// Quants shorts decodifiquem per crida a get_samples_short_interleaved.
// 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz.
constexpr int MUSIC_CHUNK_SHORTS = 8192;
// Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a
// l'SDL_AudioStream per absorbir jitter de frame i evitar underruns.
constexpr float MUSIC_LOW_WATER_SECONDS = 0.5F;
} // namespace
// --- Engine::active_ storage ---
Engine* Engine::active_ = nullptr;
auto Engine::active() noexcept -> Engine* { return active_; }
// --- Ctor/Dtor ---
Engine::Engine(const int freq, const SDL_AudioFormat format, const int num_channels) {
assert(active_ == nullptr && "Ja::Engine: més d'una instància activa no està suportat");
active_ = this;
audio_spec_ = {.format = format, .channels = num_channels, .freq = freq};
sdl_audio_device_ = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec_);
if (sdl_audio_device_ == 0) { std::fprintf(stderr, "Ja::Engine: Failed to initialize SDL audio!\n"); }
for (auto& channel : channels_) { channel.state = ChannelState::FREE; }
}
Engine::~Engine() {
if (outgoing_music_.stream != nullptr) {
SDL_DestroyAudioStream(outgoing_music_.stream);
outgoing_music_.stream = nullptr;
}
if (sdl_audio_device_ != 0) { SDL_CloseAudioDevice(sdl_audio_device_); }
sdl_audio_device_ = 0;
if (active_ == this) { active_ = nullptr; }
}
// --- Helpers stateless (no toquen membres d'Engine) ---
namespace {
auto feedMusicChunk(Music* music) -> int {
if (music == nullptr || music->vorbis == nullptr || music->stream == nullptr) { return 0; }
short chunk[MUSIC_CHUNK_SHORTS];
const int NUM_CHANNELS = music->spec.channels;
const int SAMPLES_PER_CHANNEL = stb_vorbis_get_samples_short_interleaved(
music->vorbis,
NUM_CHANNELS,
static_cast<short*>(chunk),
MUSIC_CHUNK_SHORTS);
if (SAMPLES_PER_CHANNEL <= 0) { return 0; }
const int BYTES = SAMPLES_PER_CHANNEL * NUM_CHANNELS * MUSIC_BYTES_PER_SAMPLE;
SDL_PutAudioStreamData(music->stream, static_cast<const void*>(chunk), BYTES);
return SAMPLES_PER_CHANNEL;
}
void pumpMusic(Music* music) {
if (music == nullptr || music->vorbis == nullptr || music->stream == nullptr) { return; }
const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE;
const int LOW_WATER_BYTES = static_cast<int>(MUSIC_LOW_WATER_SECONDS * static_cast<float>(BYTES_PER_SECOND));
while (SDL_GetAudioStreamAvailable(music->stream) < LOW_WATER_BYTES) {
const int DECODED = feedMusicChunk(music);
if (DECODED > 0) { continue; }
// EOF: si queden loops, rebobinar; si no, tallar i deixar drenar.
if (music->times != 0) {
stb_vorbis_seek_start(music->vorbis);
if (music->times > 0) { music->times--; }
} else {
break;
}
}
}
void preFillOutgoing(Music* music, const int duration_ms) {
if (music == nullptr || music->vorbis == nullptr || music->stream == nullptr) { return; }
const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE;
const int NEEDED_BYTES = static_cast<int>((static_cast<std::int64_t>(duration_ms) * BYTES_PER_SECOND) / 1000);
while (SDL_GetAudioStreamAvailable(music->stream) < NEEDED_BYTES) {
const int DECODED = feedMusicChunk(music);
if (DECODED <= 0) { break; }
}
}
// Retorna el progrés lineal [0..1] d'un fade. 1.0 vol dir completat. Única
// font de la corba del fade: si es vol canviar a logarítmica/quadràtica,
// s'edita aquí i afecta fade-in i fade-out alhora.
auto fadeProgress(const FadeState& fade) -> float {
if (fade.duration_ms <= 0) { return 1.0F; }
const Uint64 ELAPSED = SDL_GetTicks() - fade.start_time;
if (ELAPSED >= static_cast<Uint64>(fade.duration_ms)) { return 1.0F; }
return static_cast<float>(ELAPSED) / static_cast<float>(fade.duration_ms);
}
} // namespace
void Engine::updateOutgoingFade() {
if (outgoing_music_.stream == nullptr || !outgoing_music_.fade.active) { return; }
const float PROGRESS = fadeProgress(outgoing_music_.fade);
if (PROGRESS >= 1.0F) {
SDL_DestroyAudioStream(outgoing_music_.stream);
outgoing_music_.stream = nullptr;
outgoing_music_.fade.active = false;
} else {
SDL_SetAudioStreamGain(outgoing_music_.stream, outgoing_music_.fade.initial_volume * (1.0F - PROGRESS));
}
}
void Engine::updateIncomingFade() {
if (!incoming_fade_.active) { return; }
const float PROGRESS = fadeProgress(incoming_fade_);
if (PROGRESS >= 1.0F) {
incoming_fade_.active = false;
SDL_SetAudioStreamGain(current_music_->stream, music_volume_);
} else {
SDL_SetAudioStreamGain(current_music_->stream, music_volume_ * PROGRESS);
}
}
void Engine::updateCurrentMusic() {
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
updateIncomingFade();
pumpMusic(current_music_);
if (current_music_->times == 0 && SDL_GetAudioStreamAvailable(current_music_->stream) == 0) {
// La pista ha acabat de drenar naturalment. L'aturem primer (deixa
// l'engine en estat consistent) i llavors invoquem el callback;
// així un eventual playMusic des del callback comença net.
stopMusic();
if (on_music_ended_) { on_music_ended_(); }
}
}
void Engine::updateSoundChannels() {
for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; ++i) {
if (channels_[i].state != ChannelState::PLAYING) { continue; }
if (channels_[i].times != 0) {
if (static_cast<Uint32>(SDL_GetAudioStreamAvailable(channels_[i].stream)) < (channels_[i].sound->length / 2)) {
SDL_PutAudioStreamData(channels_[i].stream, channels_[i].sound->buffer.get(), channels_[i].sound->length);
if (channels_[i].times > 0) { channels_[i].times--; }
}
} else if (SDL_GetAudioStreamAvailable(channels_[i].stream) == 0) {
stopChannel(i);
}
}
}
void Engine::stealCurrentIntoOutgoing(const int duration_ms) {
if (outgoing_music_.stream != nullptr) {
SDL_DestroyAudioStream(outgoing_music_.stream);
outgoing_music_.stream = nullptr;
outgoing_music_.fade.active = false;
}
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING || current_music_->stream == nullptr) {
return;
}
preFillOutgoing(current_music_, duration_ms);
outgoing_music_.stream = current_music_->stream;
outgoing_music_.fade = {
.active = true,
.start_time = SDL_GetTicks(),
.duration_ms = duration_ms,
.initial_volume = music_volume_,
};
current_music_->stream = nullptr;
current_music_->state = MusicState::STOPPED;
if (current_music_->vorbis != nullptr) { stb_vorbis_seek_start(current_music_->vorbis); }
}
template <typename Fn>
void Engine::forEachTargetChannel(const int channel, Fn&& fn) {
if (channel == -1) {
for (auto& ch : channels_) { fn(ch); }
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
fn(channels_[channel]);
}
}
// --- Engine public API ---
void Engine::update() {
updateOutgoingFade();
updateCurrentMusic();
updateSoundChannels();
}
void Engine::playMusic(Music* music, const int loop) {
if (music == nullptr || music->vorbis == nullptr) { return; }
stopMusic();
current_music_ = music;
current_music_->state = MusicState::PLAYING;
current_music_->times = loop;
stb_vorbis_seek_start(current_music_->vorbis);
current_music_->stream = SDL_CreateAudioStream(&current_music_->spec, &audio_spec_);
if (current_music_->stream == nullptr) {
std::fprintf(stderr, "Ja::Engine::playMusic: Failed to create audio stream!\n");
current_music_->state = MusicState::STOPPED;
return;
}
SDL_SetAudioStreamGain(current_music_->stream, music_volume_);
pumpMusic(current_music_);
if (!SDL_BindAudioStream(sdl_audio_device_, current_music_->stream)) {
std::fprintf(stderr, "Ja::Engine::playMusic: SDL_BindAudioStream failed!\n");
}
}
void Engine::pauseMusic() {
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
current_music_->state = MusicState::PAUSED;
SDL_UnbindAudioStream(current_music_->stream);
}
void Engine::resumeMusic() {
if (current_music_ == nullptr || current_music_->state != MusicState::PAUSED) { return; }
current_music_->state = MusicState::PLAYING;
SDL_BindAudioStream(sdl_audio_device_, current_music_->stream);
}
void Engine::stopMusic() {
if (outgoing_music_.stream != nullptr) {
SDL_DestroyAudioStream(outgoing_music_.stream);
outgoing_music_.stream = nullptr;
outgoing_music_.fade.active = false;
}
incoming_fade_.active = false;
if (current_music_ == nullptr || current_music_->state == MusicState::INVALID || current_music_->state == MusicState::STOPPED) { return; }
current_music_->state = MusicState::STOPPED;
if (current_music_->stream != nullptr) {
SDL_DestroyAudioStream(current_music_->stream);
current_music_->stream = nullptr;
}
if (current_music_->vorbis != nullptr) {
stb_vorbis_seek_start(current_music_->vorbis);
}
}
void Engine::fadeOutMusic(const int milliseconds) {
if (current_music_ == nullptr || current_music_->state != MusicState::PLAYING) { return; }
stealCurrentIntoOutgoing(milliseconds);
incoming_fade_.active = false;
}
void Engine::crossfadeMusic(Music* music, const int crossfade_ms, const int loop) {
if (music == nullptr || music->vorbis == nullptr) { return; }
stealCurrentIntoOutgoing(crossfade_ms);
current_music_ = music;
current_music_->state = MusicState::PLAYING;
current_music_->times = loop;
stb_vorbis_seek_start(current_music_->vorbis);
current_music_->stream = SDL_CreateAudioStream(&current_music_->spec, &audio_spec_);
if (current_music_->stream == nullptr) {
std::fprintf(stderr, "Ja::Engine::crossfadeMusic: Failed to create audio stream!\n");
current_music_->state = MusicState::STOPPED;
return;
}
SDL_SetAudioStreamGain(current_music_->stream, 0.0F);
pumpMusic(current_music_);
SDL_BindAudioStream(sdl_audio_device_, current_music_->stream);
incoming_fade_ = {
.active = true,
.start_time = SDL_GetTicks(),
.duration_ms = crossfade_ms,
.initial_volume = 0.0F,
};
}
auto Engine::getMusicState() const -> MusicState {
if (current_music_ == nullptr) { return MusicState::INVALID; }
return current_music_->state;
}
auto Engine::setMusicVolume(float volume) -> float {
music_volume_ = SDL_clamp(volume, 0.0F, 1.0F);
if (current_music_ != nullptr && current_music_->stream != nullptr) {
SDL_SetAudioStreamGain(current_music_->stream, music_volume_);
}
return music_volume_;
}
void Engine::setOnMusicEnded(std::function<void()> callback) {
on_music_ended_ = std::move(callback);
}
void Engine::onMusicDeleted(const Music* music) {
if (music == nullptr) { return; }
if (current_music_ == music) {
stopMusic();
current_music_ = nullptr;
}
}
// --- Sound ---
auto Engine::playSound(Sound* sound, const int loop, const int group) -> int {
if (sound == nullptr) { return -1; }
int channel = 0;
while (channel < MAX_SIMULTANEOUS_CHANNELS && channels_[channel].state != ChannelState::FREE) { channel++; }
if (channel == MAX_SIMULTANEOUS_CHANNELS) {
// No hay canal libre, reemplazamos el primero
channel = 0;
}
return playSoundOnChannel(sound, channel, loop, group);
}
auto Engine::playSoundOnChannel(Sound* sound, const int channel, const int loop, const int group) -> int {
if (sound == nullptr) { return -1; }
if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return -1; }
stopChannel(channel);
channels_[channel].sound = sound;
channels_[channel].times = loop;
channels_[channel].pos = 0;
channels_[channel].group = group;
channels_[channel].state = ChannelState::PLAYING;
channels_[channel].stream = SDL_CreateAudioStream(&channels_[channel].sound->spec, &audio_spec_);
if (channels_[channel].stream == nullptr) {
std::fprintf(stderr, "Ja::Engine::playSoundOnChannel: Failed to create audio stream!\n");
channels_[channel].state = ChannelState::FREE;
return -1;
}
SDL_PutAudioStreamData(channels_[channel].stream, channels_[channel].sound->buffer.get(), channels_[channel].sound->length);
SDL_SetAudioStreamGain(channels_[channel].stream, sound_volume_[group]);
SDL_BindAudioStream(sdl_audio_device_, channels_[channel].stream);
return channel;
}
void Engine::pauseChannel(const int channel) {
forEachTargetChannel(channel, [](Channel& ch) {
if (ch.state == ChannelState::PLAYING) {
ch.state = ChannelState::PAUSED;
SDL_UnbindAudioStream(ch.stream);
}
});
}
void Engine::resumeChannel(const int channel) {
const SDL_AudioDeviceID DEVICE = sdl_audio_device_;
forEachTargetChannel(channel, [DEVICE](Channel& ch) {
if (ch.state == ChannelState::PAUSED) {
ch.state = ChannelState::PLAYING;
SDL_BindAudioStream(DEVICE, ch.stream);
}
});
}
void Engine::stopChannel(const int channel) {
forEachTargetChannel(channel, [this](Channel& ch) {
if (ch.state != ChannelState::FREE) {
if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); }
ch.stream = nullptr;
ch.state = ChannelState::FREE;
ch.pos = 0;
ch.sound = nullptr;
if (ch.has_effect) {
ch.has_effect = false;
if (effect_channels_active_ > 0) { --effect_channels_active_; }
}
}
});
}
auto Engine::setSoundVolume(float volume, const int group) -> float {
const float V = SDL_clamp(volume, 0.0F, 1.0F);
if (group == -1) {
std::fill(std::begin(sound_volume_), std::end(sound_volume_), V);
} else if (group >= 0 && group < MAX_GROUPS) {
sound_volume_[group] = V;
} else {
return V;
}
for (auto& ch : channels_) {
if ((ch.state == ChannelState::PLAYING) || (ch.state == ChannelState::PAUSED)) {
if (group == -1 || ch.group == group) {
if (ch.stream != nullptr) {
SDL_SetAudioStreamGain(ch.stream, sound_volume_[ch.group]);
}
}
}
}
return V;
}
void Engine::onSoundDeleted(const Sound* sound) {
if (sound == nullptr) { return; }
for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; ++i) {
if (channels_[i].sound == sound) { stopChannel(i); }
}
}
auto Engine::playProcessedOnFreeChannel(const std::vector<std::uint8_t>& bytes, const SDL_AudioSpec& spec, const int group) -> int {
// El cap de canals amb efecte es valida abans de reservar slot —
// així evitem crear i destruir un stream només per descartar el play.
if (effect_channels_active_ >= MAX_EFFECT_CHANNELS) { return -1; }
int channel = 0;
while (channel < MAX_SIMULTANEOUS_CHANNELS && channels_[channel].state != ChannelState::FREE) { ++channel; }
if (channel == MAX_SIMULTANEOUS_CHANNELS) { channel = 0; }
stopChannel(channel);
// El stream es crea contra l'spec del buffer processat (S16, ...)
// perquè SDL faci el resampling cap a audio_spec_ del device.
channels_[channel].stream = SDL_CreateAudioStream(&spec, &audio_spec_);
if (channels_[channel].stream == nullptr) {
std::fprintf(stderr, "Ja::Engine::playProcessedOnFreeChannel: Failed to create audio stream!\n");
return -1;
}
channels_[channel].sound = nullptr; // El buffer no és propietat de cap Ja::Sound.
channels_[channel].times = 0;
channels_[channel].pos = 0;
const int CLAMPED_GROUP = (group >= 0 && group < MAX_GROUPS) ? group : 0;
channels_[channel].group = CLAMPED_GROUP;
channels_[channel].state = ChannelState::PLAYING;
channels_[channel].has_effect = true;
++effect_channels_active_;
SDL_PutAudioStreamData(channels_[channel].stream, bytes.data(), static_cast<int>(bytes.size()));
SDL_SetAudioStreamGain(channels_[channel].stream, sound_volume_[CLAMPED_GROUP]);
SDL_BindAudioStream(sdl_audio_device_, channels_[channel].stream);
return channel;
}
// shadertoy build: AudioEffects is not bundled. Both echo/reverb entry
// points are stubbed; if an effect is requested, fall back to playing
// the dry sound so callers don't lose audio.
auto Engine::playSoundWithEcho(const Sound* sound, const EchoParams& /*params*/, const int group) -> int {
if (sound == nullptr) { return -1; }
return playSound(const_cast<Sound*>(sound), 0, group);
}
auto Engine::playSoundWithReverb(const Sound* sound, const ReverbParams& /*params*/, const int group) -> int {
if (sound == nullptr) { return -1; }
return playSound(const_cast<Sound*>(sound), 0, group);
}
// --- Factories i destructors (permanents) ---
auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* {
if (buffer == nullptr || length == 0) { return nullptr; }
// Allocem el Music primer per aprofitar el seu `std::vector<Uint8>`
// com a propietari del OGG comprimit. stb_vorbis guarda un punter
// persistent al buffer; com que ací no el resize'jem, el .data() és
// estable durant tot el cicle de vida del music.
auto music = std::make_unique<Music>();
music->ogg_data.assign(buffer, buffer + length);
int vorbis_error = 0;
music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(),
static_cast<int>(length),
&vorbis_error,
nullptr);
if (music->vorbis == nullptr) {
std::fprintf(stderr, "Ja::loadMusic: stb_vorbis_open_memory failed (error %d)\n", vorbis_error);
return nullptr;
}
const stb_vorbis_info INFO = stb_vorbis_get_info(music->vorbis);
music->spec.channels = static_cast<int>(INFO.channels);
music->spec.freq = static_cast<int>(INFO.sample_rate);
music->spec.format = SDL_AUDIO_S16;
music->state = MusicState::STOPPED;
return music.release();
}
// Overload amb filename. Resource::Cache l'usa per registrar el path dins
// del propi Ja::Music (camp `filename`); la capa Audio l'usa per recuperar
// el nom després d'un playMusic(Ja::Music*, ...) — veure PATCH-02.
auto loadMusic(const Uint8* buffer, Uint32 length, const char* filename) -> Music* {
Music* music = loadMusic(buffer, length);
if (music != nullptr && filename != nullptr) { music->filename = filename; }
return music;
}
void deleteMusic(Music* music) {
if (music == nullptr) { return; }
// Notifiquem el motor actiu perquè pari la pista si és la current_music.
// Si no hi ha motor (shutdown-order invertit), passem: els recursos
// propis del Music es lliberen igualment a sota.
if (Engine* eng = Engine::active()) { eng->onMusicDeleted(music); }
if (music->stream != nullptr) { SDL_DestroyAudioStream(music->stream); }
if (music->vorbis != nullptr) { stb_vorbis_close(music->vorbis); }
delete music;
}
auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound* {
auto sound = std::make_unique<Sound>();
Uint8* raw = nullptr;
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), true, &sound->spec, &raw, &sound->length)) {
std::fprintf(stderr, "Ja::loadSound: Failed to load WAV from memory: %s\n", SDL_GetError());
return nullptr;
}
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
return sound.release();
}
void deleteSound(Sound* sound) {
if (sound == nullptr) { return; }
if (Engine* eng = Engine::active()) { eng->onSoundDeleted(sound); }
// buffer es destrueix automàticament via RAII (SdlFreeDeleter).
delete sound;
}
} // namespace Ja
// --- stb_vorbis macro leak cleanup ---
// stb_vorbis.c filtra noms curts (L, C, R i PLAYBACK_*) al TU que el compila.
// Xocarien amb paràmetres de plantilla d'altres headers si aquestes definicions
// s'escapessin. Els netegem al final del TU per tancar la porta.
// clang-format off
#undef L
#undef C
#undef R
#undef PLAYBACK_MONO
#undef PLAYBACK_LEFT
#undef PLAYBACK_RIGHT
// clang-format on

229
src/audio/jail_audio.hpp Normal file
View File

@@ -0,0 +1,229 @@
#pragma once
// --- Includes ---
#include <SDL3/SDL.h>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
// Forward-declaració del decoder de vorbis. La implementació viu a
// jail_audio.cpp (únic TU que compila external/stb_vorbis.c). Qualsevol caller
// només necessita `stb_vorbis*` per punter — mai per valor — així que el
// forward decl n'hi ha prou i evita arrossegar el .c a tots els TU.
// NOLINTNEXTLINE(readability-identifier-naming) — nom imposat per l'API de stb_vorbis
struct stb_vorbis;
// Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`.
// Compatible amb `std::unique_ptr<Uint8[], SdlFreeDeleter>` — zero size overhead
// gràcies a EBO, igual que un unique_ptr amb default_delete.
struct SdlFreeDeleter {
void operator()(Uint8* p) const noexcept {
if (p != nullptr) { SDL_free(p); }
}
};
// Motor de baix nivell d'àudio del projecte jailgames: streaming OGG
// (stb_vorbis) + N canals d'efectes (SDL3 audio). No depèn d'Options ni de cap
// singleton del joc; només de SDL3 i stb_vorbis. La capa superior (Audio) li
// passa recursos pel punter i fa el bookkeeping d'usuari.
namespace Ja {
// --- Public Enums ---
enum class ChannelState : std::uint8_t {
FREE,
PLAYING,
PAUSED,
};
enum class MusicState : std::uint8_t {
INVALID, // Music carregat però mai play-ejat
PLAYING,
PAUSED,
STOPPED,
};
// --- Constants ---
inline constexpr int MAX_SIMULTANEOUS_CHANNELS = 20;
inline constexpr int MAX_GROUPS = 2;
// Cap superior de canals que poden estar simultàniament reproduint un so
// amb efecte (eco/reverb). Si està al límit, les noves crides amb efecte
// cauen al camí sec — l'usuari sent el so igualment, sense la cua.
inline constexpr int MAX_EFFECT_CHANNELS = 4;
// --- Paràmetres d'efectes ---
// Els camps els fixa el caller (Audio) llegint sounds.yaml; el motor només
// els passa a AudioEffects::applyEcho/applyReverb. Els defaults són
// sensats però els presets els sobreescriuen.
struct EchoParams {
float delay_ms{220.0F}; // Temps fins al primer rebot.
float feedback{0.45F}; // Reinjecció (0..0.95).
float wet{0.35F}; // Mescla humida (0..1).
};
struct ReverbParams {
float room_size{0.7F}; // Mida percebuda (0..1).
float damping{0.5F}; // Atenuació d'aguts per rebot (0..1).
float wet{0.4F}; // Mescla humida (0..1).
};
// Spec de fallback del dispositiu. S'aplica abans que l'Engine s'iniciï i
// com a valor inicial de Sound/Music. L'spec real d'ús l'imposa el ctor
// d'Engine, alimentat des de Defaults::Audio via Audio.
inline constexpr SDL_AudioSpec DEFAULT_SPEC{SDL_AUDIO_S16, 2, 48000};
// --- Struct Definitions ---
struct Sound {
SDL_AudioSpec spec{DEFAULT_SPEC};
Uint32 length{0};
// Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV
// via SDL_malloc; el deleter `SdlFreeDeleter` allibera amb SDL_free.
std::unique_ptr<Uint8[], SdlFreeDeleter> buffer;
};
// L'ordre (punters primer, ints després, enum de 8 bits al final) minimitza
// el padding a 64-bit (evita avisos de clang-analyzer-optin.performance.Padding).
struct Channel {
Sound* sound{nullptr};
SDL_AudioStream* stream{nullptr};
int pos{0};
int times{0};
int group{0};
ChannelState state{ChannelState::FREE};
// Marca si aquest canal va arrencar amb so processat per un efecte.
// El motor compta canals actius amb efecte per fer complir
// MAX_EFFECT_CHANNELS i alliberar el comptador en parar.
bool has_effect{false};
};
struct Music {
SDL_AudioSpec spec{DEFAULT_SPEC};
// OGG comprimit en memòria. Propietat nostra; es copia des del buffer
// d'entrada una sola vegada en loadMusic i es descomprimix en chunks
// per streaming. Com que stb_vorbis guarda un punter persistent al
// `.data()` d'aquest vector, no el podem resize'jar un cop establert
// (una reallocation invalidaria el punter que el decoder conserva).
std::vector<Uint8> ogg_data;
stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del Music
std::string filename;
int times{0}; // loops restants (-1 = infinit, 0 = un sol play)
SDL_AudioStream* stream{nullptr};
MusicState state{MusicState::INVALID};
};
struct FadeState {
bool active{false};
Uint64 start_time{0};
int duration_ms{0};
float initial_volume{0.0F};
};
struct OutgoingMusic {
SDL_AudioStream* stream{nullptr};
FadeState fade;
};
// --- Engine ---
// Encapsula tot l'estat que abans vivia com a globals inline. Un sol Engine
// viu per procés (enforceat via assert al ctor contra `active_`). El ctor
// obre el device SDL; el dtor el tanca (RAII). Els deleters
// `Ja::deleteMusic`/`Ja::deleteSound` accedeixen al motor actiu via
// `Engine::active()` per parar canals abans d'alliberar.
class Engine {
public:
Engine(int freq, SDL_AudioFormat format, int num_channels);
~Engine();
Engine(const Engine&) = delete;
auto operator=(const Engine&) -> Engine& = delete;
Engine(Engine&&) = delete;
auto operator=(Engine&&) -> Engine& = delete;
// Retorna el motor actiu o nullptr si cap ha estat construït. L'usen
// els deleters de recursos perquè no els arriba cap referència directa.
[[nodiscard]] static auto active() noexcept -> Engine*;
void update();
// --- Música ---
void playMusic(Music* music, int loop = -1);
void pauseMusic();
void resumeMusic();
void stopMusic();
void fadeOutMusic(int milliseconds);
void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1);
[[nodiscard]] auto getMusicState() const -> MusicState;
auto setMusicVolume(float volume) -> float;
// Registra un callback que es disparà quan la música actual acabi de
// drenar naturalment (times == 0 + stream buit). Es crida DESPRÉS de
// stopMusic, així que el callback pot invocar playMusic sense córrer.
// S'executa al mateix thread que Engine::update (render loop); no fer
// operacions blocants.
void setOnMusicEnded(std::function<void()> callback);
// Notifica al motor que un Music s'està destruint: si és el current_music
// s'atura abans que els seus recursos (stream/vorbis) deixin de ser vàlids.
void onMusicDeleted(const Music* music);
// --- So ---
auto playSound(Sound* sound, int loop = 0, int group = 0) -> int;
auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int;
// Reproducció amb so processat per un efecte. Retorna el canal
// assignat o -1 si no queden slots d'efecte (MAX_EFFECT_CHANNELS).
// El sound original només s'usa per consultar el spec/buffer; el
// canal manipula el buffer ja processat (no reapunta a `sound`).
auto playSoundWithEcho(const Sound* sound, const EchoParams& params, int group = 0) -> int;
auto playSoundWithReverb(const Sound* sound, const ReverbParams& params, int group = 0) -> int;
void pauseChannel(int channel);
void resumeChannel(int channel);
void stopChannel(int channel);
auto setSoundVolume(float volume, int group = -1) -> float;
// Notifica al motor que un Sound s'està destruint: els canals que el
// referenciïn es paren abans d'alliberar el buffer.
void onSoundDeleted(const Sound* sound);
private:
void stealCurrentIntoOutgoing(int duration_ms);
void updateOutgoingFade();
void updateIncomingFade();
void updateCurrentMusic();
void updateSoundChannels();
// Empenta un buffer ja processat (S16) a un canal lliure i el deixa
// sonar sense bucle. Camí comú dels dos overloads playSoundWith*.
// Retorna el canal o -1 si no queden slots.
auto playProcessedOnFreeChannel(const std::vector<std::uint8_t>& bytes, const SDL_AudioSpec& spec, int group) -> int;
template <typename Fn>
void forEachTargetChannel(int channel, Fn&& fn);
Music* current_music_{nullptr};
Channel channels_[MAX_SIMULTANEOUS_CHANNELS]{};
SDL_AudioSpec audio_spec_{DEFAULT_SPEC};
float music_volume_{1.0F};
float sound_volume_[MAX_GROUPS]{};
SDL_AudioDeviceID sdl_audio_device_{0};
OutgoingMusic outgoing_music_;
FadeState incoming_fade_;
std::function<void()> on_music_ended_;
// Comptador derivat de Channel::has_effect — evita haver-lo de
// recalcular cada vegada que algú demana un play amb efecte.
int effect_channels_active_{0};
// NOLINTNEXTLINE(readability-identifier-naming) — convenció projecte: private static amb sufix _
static Engine* active_;
};
// --- Factories i destructors (permanents) ---
// No depenen de l'estat del motor: loadMusic/loadSound només construeixen
// objectes, deleteMusic/deleteSound consulten Engine::active() per parar
// canals abans d'alliberar (si el motor encara viu).
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length) -> Music*;
[[nodiscard]] auto loadMusic(const Uint8* buffer, Uint32 length, const char* filename) -> Music*;
void deleteMusic(Music* music);
[[nodiscard]] auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound*;
void deleteSound(Sound* sound);
} // namespace Ja

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,43 +0,0 @@
#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);

7
third_party/stb_vorbis_impl.cpp vendored Normal file
View File

@@ -0,0 +1,7 @@
// Isolated TU for the stb_vorbis implementation.
// jail_audio.cpp defines STB_VORBIS_HEADER_ONLY before including stb_vorbis.c
// (it only sees declarations there); this TU provides the definitions and
// the linker resolves them.
// NOLINTNEXTLINE(bugprone-suspicious-include)
#include "stb_vorbis.c"

View File

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