Compare commits
2 Commits
0a03509323
...
8d42d5741f
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d42d5741f | |||
| f18d6143d1 |
86
CLAUDE.md
86
CLAUDE.md
@@ -4,7 +4,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Project Overview
|
## 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
|
## Build and Development Commands
|
||||||
|
|
||||||
@@ -30,13 +39,21 @@ make macos_debug
|
|||||||
make linux_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
|
### Running the Application
|
||||||
```bash
|
```bash
|
||||||
./shadertoy [SHADER_PATH] [-F|--fullscreen]
|
./shadertoy [SHADER_NAME_OR_PATH] [-F|--fullscreen] [--backend=auto|gpu|opengl]
|
||||||
|
|
||||||
# Examples:
|
# Examples:
|
||||||
./shadertoy shaders/test.frag.glsl
|
./shadertoy test # auto backend, shaders/test/
|
||||||
./shadertoy -F shaders/fractal_pyramid.frag.glsl
|
./shadertoy --backend=gpu seascape # force SDL3 GPU (Vulkan/Metal)
|
||||||
|
./shadertoy -F shaders/fractal_pyramid # explicit folder path, fullscreen
|
||||||
```
|
```
|
||||||
|
|
||||||
**Runtime Controls:**
|
**Runtime Controls:**
|
||||||
@@ -46,28 +63,49 @@ make linux_debug
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Core Design
|
### Layered design
|
||||||
All application logic resides in `src/main.cpp` (~469 lines) - a monolithic design that's straightforward to understand. Key components:
|
- `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
|
### Shader folder layout
|
||||||
2. **OpenGL Rendering** - Single fullscreen quad with fragment shader using GL_TRIANGLE_STRIP
|
One folder per shader, under `shaders/`:
|
||||||
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
|
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)
|
### Global State (main.cpp)
|
||||||
```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_shader_index_ // Active shader in rotation
|
||||||
current_program_ // OpenGL shader program handle
|
|
||||||
shader_start_ticks_ // Base time for iTime uniform calculation
|
shader_start_ticks_ // Base time for iTime uniform calculation
|
||||||
window_ // SDL3 window pointer
|
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
|
### Dependencies
|
||||||
- **SDL3** - Window/input management, OpenGL context
|
- **SDL3** ≥ 3.2 — windowing, events, GPU API (`SDL_gpu.h`)
|
||||||
- **GLAD** - OpenGL 3.3 loader (statically linked via `third_party/glad/`)
|
- **GLAD** — OpenGL 3.3 loader (used only by the OpenGL backend, statically linked via `third_party/glad/`)
|
||||||
- **C++17 stdlib** - filesystem, fstream, vector, algorithms
|
- **C++17 stdlib** — filesystem, fstream, vector, algorithms
|
||||||
|
- Build-time tool: `glslc` (only required to regenerate `.frag.spv`)
|
||||||
|
|
||||||
### Platform-Specific Code
|
### Platform-Specific Code
|
||||||
Uses preprocessor defines (`WINDOWS_BUILD`, `MACOS_BUILD`, `LINUX_BUILD`) for:
|
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
|
## 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)
|
### Shader Format (GLSL 3.3 Core)
|
||||||
All shaders must follow this structure:
|
All shaders must follow this structure:
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ set(OpenGL_GL_PREFERENCE GLVND)
|
|||||||
# --- LISTA EXPLÍCITA DE FUENTES ---
|
# --- LISTA EXPLÍCITA DE FUENTES ---
|
||||||
set(APP_SOURCES
|
set(APP_SOURCES
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
src/rendering/shader_backend.cpp
|
||||||
|
src/rendering/opengl_shader_backend.cpp
|
||||||
|
src/rendering/sdl3gpu/sdl3gpu_shader_backend.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fuentes de librerías de terceros
|
# Fuentes de librerías de terceros
|
||||||
@@ -29,6 +32,21 @@ set(EXTERNAL_SOURCES
|
|||||||
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
|
||||||
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
||||||
|
|
||||||
|
# --- COMPILACIÓN DE SHADERS (Vulkan SPIR-V) ---
|
||||||
|
find_program(GLSLC_EXE NAMES glslc)
|
||||||
|
if(GLSLC_EXE)
|
||||||
|
message(STATUS "glslc encontrado: ${GLSLC_EXE}")
|
||||||
|
add_custom_target(compile_shaders
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-D GLSLC=${GLSLC_EXE}
|
||||||
|
-D SHADERS_DIR=${CMAKE_SOURCE_DIR}/shaders
|
||||||
|
-P ${CMAKE_SOURCE_DIR}/tools/shaders/compile_shaders.cmake
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
COMMENT "Compiling .vk.glsl shaders to SPIR-V")
|
||||||
|
else()
|
||||||
|
message(STATUS "glslc no encontrado — el target compile_shaders no estará disponible")
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- AÑADIR EJECUTABLE ---
|
# --- AÑADIR EJECUTABLE ---
|
||||||
add_executable(${PROJECT_NAME} ${APP_SOURCES} ${EXTERNAL_SOURCES})
|
add_executable(${PROJECT_NAME} ${APP_SOURCES} ${EXTERNAL_SOURCES})
|
||||||
|
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -36,6 +36,9 @@ LINUX_RELEASE := $(TARGET_NAME)-$(VERSION)-linux.tar.gz
|
|||||||
# Lista completa de archivos fuente
|
# Lista completa de archivos fuente
|
||||||
APP_SOURCES := \
|
APP_SOURCES := \
|
||||||
src/main.cpp \
|
src/main.cpp \
|
||||||
|
src/rendering/shader_backend.cpp \
|
||||||
|
src/rendering/opengl_shader_backend.cpp \
|
||||||
|
src/rendering/sdl3gpu/sdl3gpu_shader_backend.cpp \
|
||||||
third_party/glad/src/glad.c \
|
third_party/glad/src/glad.c \
|
||||||
third_party/jail_audio.cpp
|
third_party/jail_audio.cpp
|
||||||
|
|
||||||
|
|||||||
19
shaders/_common/passthrough.vert.msl
Normal file
19
shaders/_common/passthrough.vert.msl
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#include <metal_stdlib>
|
||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
// Shared fullscreen-triangle vertex shader for the SDL3 GPU backend.
|
||||||
|
// Emits uv in Shadertoy convention: (0,0) bottom-left, (1,1) top-right.
|
||||||
|
|
||||||
|
struct PassthroughVOut {
|
||||||
|
float4 pos [[position]];
|
||||||
|
float2 uv;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex PassthroughVOut passthrough_vs(uint vid [[vertex_id]]) {
|
||||||
|
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
|
||||||
|
PassthroughVOut out;
|
||||||
|
float2 pos = positions[vid];
|
||||||
|
out.uv = pos * 0.5 + 0.5;
|
||||||
|
out.pos = float4(pos.x, -pos.y, 0.0, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
BIN
shaders/_common/passthrough.vert.spv
Normal file
BIN
shaders/_common/passthrough.vert.spv
Normal file
Binary file not shown.
18
shaders/_common/passthrough.vk.glsl
Normal file
18
shaders/_common/passthrough.vk.glsl
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Shared fullscreen-triangle vertex shader for the SDL3 GPU backend.
|
||||||
|
// Emits vUV in Shadertoy convention: (0,0) bottom-left, (1,1) top-right.
|
||||||
|
// Y flipped in NDC because Vulkan/Metal point Y down by default.
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 vUV;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const vec2 positions[3] = vec2[3](
|
||||||
|
vec2(-1.0, -1.0),
|
||||||
|
vec2( 3.0, -1.0),
|
||||||
|
vec2(-1.0, 3.0)
|
||||||
|
);
|
||||||
|
vec2 pos = positions[gl_VertexIndex];
|
||||||
|
vUV = pos * 0.5 + 0.5;
|
||||||
|
gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);
|
||||||
|
}
|
||||||
2
shaders/cineshader_lava/meta.txt
Normal file
2
shaders/cineshader_lava/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Cineshader Lava
|
||||||
|
Author: edankwan
|
||||||
2
shaders/creation/meta.txt
Normal file
2
shaders/creation/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Creation by Silexars
|
||||||
|
Author: Danguafer
|
||||||
2
shaders/cube_lines/meta.txt
Normal file
2
shaders/cube_lines/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Cube lines
|
||||||
|
Author: Danil
|
||||||
2
shaders/dbz/meta.txt
Normal file
2
shaders/dbz/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: New Leaked 3I/Atlas NASA Footage
|
||||||
|
Author: msm01
|
||||||
2
shaders/fractal_pyramid/meta.txt
Normal file
2
shaders/fractal_pyramid/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Fractal Pyramid
|
||||||
|
Author: bradjamesgrant
|
||||||
2
shaders/just_another_cube/meta.txt
Normal file
2
shaders/just_another_cube/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Just Another Cube
|
||||||
|
Author: mrange
|
||||||
2
shaders/octograms/meta.txt
Normal file
2
shaders/octograms/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Octograms
|
||||||
|
Author: whisky_shusuky
|
||||||
2
shaders/remember/meta.txt
Normal file
2
shaders/remember/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Remember
|
||||||
|
Author: diatribes
|
||||||
2
shaders/seascape/meta.txt
Normal file
2
shaders/seascape/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Seascape
|
||||||
|
Author: Alexander Alekseev
|
||||||
2
shaders/shader_art_coding_introduction/meta.txt
Normal file
2
shaders/shader_art_coding_introduction/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Shader Art Coding Introduction
|
||||||
|
Author: kishimisu
|
||||||
2
shaders/test/meta.txt
Normal file
2
shaders/test/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Test
|
||||||
|
Author: JailDesigner
|
||||||
36
shaders/test/test.frag.msl
Normal file
36
shaders/test/test.frag.msl
Normal 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
BIN
shaders/test/test.frag.spv
Normal file
Binary file not shown.
41
shaders/test/test.vk.glsl
Normal file
41
shaders/test/test.vk.glsl
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Name: Test
|
||||||
|
// Author: JailDesigner
|
||||||
|
//
|
||||||
|
// Vulkan port of test.gl.glsl. Bindings follow the SDL3 GPU convention:
|
||||||
|
// set=3, binding=0 — fragment uniform buffer (ShadertoyUniforms in C++).
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 vUV;
|
||||||
|
layout(location = 0) out vec4 FragColor;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform ShadertoyUBO {
|
||||||
|
float iTime;
|
||||||
|
vec2 iResolution;
|
||||||
|
} u;
|
||||||
|
|
||||||
|
vec3 palette(float t) {
|
||||||
|
vec3 a = vec3(1.0, 0.5, 0.5);
|
||||||
|
vec3 b = vec3(1.0, 0.5, 0.5);
|
||||||
|
vec3 c = vec3(1.0, 1.0, 1.0);
|
||||||
|
vec3 d = vec3(0.263, 0.416, 0.557);
|
||||||
|
return a + b * cos(6.28318 * (c * t * d));
|
||||||
|
}
|
||||||
|
|
||||||
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||||
|
vec2 uv = (fragCoord * 2.0 - u.iResolution) / u.iResolution.y;
|
||||||
|
float d = length(uv);
|
||||||
|
vec3 col = palette(d);
|
||||||
|
d = sin(d * 8.0 + u.iTime) / 8.0;
|
||||||
|
d = abs(d);
|
||||||
|
d = 0.02 / d;
|
||||||
|
col *= d;
|
||||||
|
fragColor = vec4(col, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 fragCoordPixels = vUV * u.iResolution;
|
||||||
|
vec4 outColor;
|
||||||
|
mainImage(outColor, fragCoordPixels);
|
||||||
|
FragColor = outColor;
|
||||||
|
}
|
||||||
2
shaders/water/meta.txt
Normal file
2
shaders/water/meta.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Name: Water
|
||||||
|
Author: diatribes
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
// Nombre de la aplicación
|
// Nombre de la aplicación
|
||||||
constexpr const char* APP_NAME = "Shadertoy";
|
constexpr const char* APP_NAME = "Shadertoy";
|
||||||
|
|
||||||
|
// Prefijo del título de la ventana (estilo aee_2026).
|
||||||
|
constexpr const char* WINDOW_TITLE_PREFIX = "\xC2\xA9 2025 Shadertoy \xE2\x80\x94 JailDesigner";
|
||||||
|
|
||||||
// Tamaño de ventana por defecto
|
// Tamaño de ventana por defecto
|
||||||
constexpr int WINDOW_WIDTH = 800;
|
constexpr int WINDOW_WIDTH = 800;
|
||||||
constexpr int WINDOW_HEIGHT = 800;
|
constexpr int WINDOW_HEIGHT = 800;
|
||||||
|
|||||||
910
src/main.cpp
910
src/main.cpp
File diff suppressed because it is too large
Load Diff
297
src/rendering/opengl_shader_backend.cpp
Normal file
297
src/rendering/opengl_shader_backend.cpp
Normal 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
|
||||||
40
src/rendering/opengl_shader_backend.hpp
Normal file
40
src/rendering/opengl_shader_backend.hpp
Normal 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
|
||||||
238
src/rendering/sdl3gpu/sdl3gpu_shader_backend.cpp
Normal file
238
src/rendering/sdl3gpu/sdl3gpu_shader_backend.cpp
Normal 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
|
||||||
44
src/rendering/sdl3gpu/sdl3gpu_shader_backend.hpp
Normal file
44
src/rendering/sdl3gpu/sdl3gpu_shader_backend.hpp
Normal 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
|
||||||
41
src/rendering/sdl3gpu/shader_factory.hpp
Normal file
41
src/rendering/sdl3gpu/shader_factory.hpp
Normal 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
|
||||||
99
src/rendering/shader_backend.cpp
Normal file
99
src/rendering/shader_backend.cpp
Normal 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
|
||||||
57
src/rendering/shader_backend.hpp
Normal file
57
src/rendering/shader_backend.hpp
Normal 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
|
||||||
70
tools/shaders/compile_shaders.cmake
Normal file
70
tools/shaders/compile_shaders.cmake
Normal 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()
|
||||||
Reference in New Issue
Block a user