summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/DemoCodegen.cmake7
-rw-r--r--doc/AI_RULES.md1
-rw-r--r--doc/ASSET_SYSTEM.md88
-rw-r--r--src/effects/ntsc.wgsl22
-rw-r--r--src/util/asset_manager.cc41
-rw-r--r--src/util/asset_manager.h8
-rw-r--r--tools/asset_packer.cc104
-rw-r--r--workspaces/main/assets.txt166
8 files changed, 260 insertions, 177 deletions
diff --git a/cmake/DemoCodegen.cmake b/cmake/DemoCodegen.cmake
index 32cb06c..4d0d271 100644
--- a/cmake/DemoCodegen.cmake
+++ b/cmake/DemoCodegen.cmake
@@ -239,11 +239,16 @@ add_custom_target(generate_demo_data DEPENDS ${GEN_DEMO_H}.data_stamp)
# assets.h is written via copy_if_different so its mtime only changes when the
# enum content changes (e.g. new asset added). This prevents unrelated
# translation units (audio, 3d, …) from recompiling on every shader edit.
+set(ASSET_PACKER_FLAGS "")
+if(NOT DEMO_STRIP_ALL)
+ set(ASSET_PACKER_FLAGS "--disk_load")
+endif()
+
set(GEN_DEMO_H_STAMP ${GEN_DEMO_H}.gen_stamp)
add_custom_command(
OUTPUT ${GEN_DEMO_H_STAMP} ${GEN_DEMO_CC}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_SOURCE_DIR}/src/generated
- COMMAND ${ASSET_PACKER_CMD} ${WORKSPACE_ASSETS} ${GEN_DEMO_H}.new ${GEN_DEMO_CC}
+ COMMAND ${ASSET_PACKER_CMD} ${WORKSPACE_ASSETS} ${GEN_DEMO_H}.new ${GEN_DEMO_CC} ${ASSET_PACKER_FLAGS}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${GEN_DEMO_H}.new ${GEN_DEMO_H}
COMMAND ${CMAKE_COMMAND} -E touch ${GEN_DEMO_H_STAMP}
DEPENDS ${ASSET_PACKER_DEPENDS} ${WORKSPACE_ASSETS}
diff --git a/doc/AI_RULES.md b/doc/AI_RULES.md
index 5500a9a..27d2d47 100644
--- a/doc/AI_RULES.md
+++ b/doc/AI_RULES.md
@@ -4,6 +4,7 @@
- All changes must keep tests passing
- Prefer small, reviewable commits
- All `cmake --build` commands must use the `-j4` option for parallel building.
+- Medium/large sub-system changes require top-level doc update (summarize, streamline, archive).
- after a task, a 'big' final commit should contain a short handoff tag like "handoff(Gemini):..." if you're gemini-cli, or "handoff(Claude): ..." if you're claude-code.
## Adding Visual Effects
diff --git a/doc/ASSET_SYSTEM.md b/doc/ASSET_SYSTEM.md
index 6104353..87d2a9a 100644
--- a/doc/ASSET_SYSTEM.md
+++ b/doc/ASSET_SYSTEM.md
@@ -1,6 +1,6 @@
# Compact Asset System for the 64k Demo
-All assets are const byte arrays embedded in the binary. Runtime retrieval via `GetAsset(AssetId)` returns direct pointers with O(1) lookup.
+This document outlines the architecture for managing assets, which are compiled into the binary for release builds but loaded from disk during development to speed up iteration.
## Asset Manifest Format
@@ -8,24 +8,56 @@ Files: `workspaces/main/assets.txt`, `workspaces/test/assets.txt`
Format (CSV):
```
-ASSET_NAME, COMPRESSION, path/to/file.ext, "Description"
+ASSET_NAME, ASSET_TYPE, path/to/file.ext, "Description"
```
-### Compression Types
+### Asset Types
-| Type | Behavior |
-|------|----------|
-| `NONE` | Raw binary embed (shaders, meshes, `.spec` files) |
-| `MP3` | MP3 audio; decoded to spectrogram at init |
-| `PROC(func, ...)` | CPU procedural generation at init |
-| `PROC_GPU(func, ...)` | GPU compute procedural generation at init |
+The `ASSET_TYPE` field explicitly defines how the asset is processed and used at runtime.
+
+| Type | Description |
+|------|-------------|
+| `WGSL` | WGSL shader source code. |
+| `SPEC` | Spectrogram data for audio synthesis. |
+| `TEXTURE` | Image file (PNG, JPG, etc.), converted to a raw RGBA8 buffer with a width/height header. |
+| `MESH` | 3D model file (.obj), converted to an interleaved vertex/index buffer. |
+| `BINARY`| Generic raw binary data. |
+| `MP3` | MP3 audio; decoded to a spectrogram at runtime. |
+| `PROC(func, ...)` | CPU-based procedural generation at init time. |
+| `PROC_GPU(func, ...)`| GPU-based compute shader for procedural generation at init time. |
+
+## Dual-Mode Loading Strategy
+
+The asset system operates in two modes, controlled by the `DEMO_STRIP_ALL` CMake flag:
+
+1. **Development Mode (`DEMO_STRIP_ALL=OFF`)**:
+ * Heavy assets (`SPEC`, `MP3`) are **loaded from disk** at runtime.
+ * The `asset_packer` generates a C-string containing the file path for these assets.
+ * The `AssetManager` reads the file on first access, caches it, and returns the content.
+ * This mode allows for rapid iteration on audio assets without requiring a full recompile.
+
+2. **Release Mode (`DEMO_STRIP_ALL=ON`)**:
+ * All assets are **embedded directly into the binary** as `const` byte arrays.
+ * This creates a single, self-contained executable suitable for distribution.
+ * The `asset_packer` generates C++ byte arrays from the asset files.
+
+This dual-mode system provides both developer convenience and release-ready packaging.
## AssetType Enum
-Each `AssetRecord` carries an `AssetType` field (replaces the former `is_procedural`/`is_gpu_procedural` bools):
+The C++ `AssetType` enum mirrors the types defined in the asset manifest:
```cpp
-enum class AssetType : uint8_t { STATIC, PROC, PROC_GPU, MP3 };
+enum class AssetType : uint8_t {
+ WGSL,
+ SPEC,
+ TEXTURE,
+ MESH,
+ BINARY,
+ MP3,
+ PROC,
+ PROC_GPU,
+};
```
Query at runtime:
@@ -41,7 +73,7 @@ if (GetAssetType(AssetId::NEVER_MP3) == AssetType::MP3) { ... }
// Retrieve asset data
size_t size;
const uint8_t* data = GetAsset(AssetId::MY_ASSET, &size);
-DropAsset(AssetId::MY_ASSET, data); // Release procedural/cached data
+DropAsset(AssetId::MY_ASSET, data); // Release procedural or disk-loaded data
// Query type
AssetType t = GetAssetType(AssetId::MY_ASSET);
@@ -51,33 +83,29 @@ AssetType t = GetAssetType(AssetId::MY_ASSET);
Tool: `tools/asset_packer.cc`
-1. Parse workspace `assets.txt`
-2. Read/process files (images → RGBA8 header, meshes → vertex/index binary, others → raw)
-3. Generate `src/generated/assets.h` (enum + declarations)
-4. Generate `src/generated/assets_data.cc` (byte arrays + `AssetRecord` table)
-5. Auto-triggered by CMake on manifest changes
+1. Parse workspace `assets.txt`.
+2. If in **Development Mode**, for `SPEC` and `MP3` assets, it generates a C-string with the file path.
+3. If in **Release Mode**, it reads and processes all files into byte arrays (images → RGBA8, meshes → vertex/index, etc.).
+4. Generate `src/generated/assets.h` (enum + declarations).
+5. Generate `src/generated/assets_data.cc` (byte arrays/paths + `AssetRecord` table).
+6. Auto-triggered by CMake on manifest changes.
## Technical Guarantees
-- **Alignment**: All arrays declared `alignas(16)` for safe `reinterpret_cast`
-- **String Safety**: Null-terminator appended (safe as C-string)
-- **Size**: `size` reflects original file size (buffer is `size + 1`)
+- **Alignment**: All embedded data arrays are declared `alignas(16)` for safe `reinterpret_cast`.
+- **String Safety**: Embedded assets are null-terminated (safe as C-strings). In disk-load mode, the path itself is a null-terminated C-string.
+- **Size**: For embedded assets, `size` reflects the original file size (the buffer is `size + 1`). For disk-loaded assets, it reflects the file path's string length.
## Developer Workflow
-**Add a static asset:**
-```
-MY_ASSET, NONE, path/to/file.ext, "Description"
-```
-
-**Add an MP3 sample:**
+**Add a spectrogram:**
```
-MY_SAMPLE, MP3, music/sample.mp3, "Description"
+MY_KICK, SPEC, music/my_kick.spec, "Description"
```
-**Add a procedural texture:**
+**Add a shader:**
```
-MY_TEX, PROC(gen_noise, 1234, 16), _, "Description"
+MY_SHADER, WGSL, shaders/my_shader.wgsl, "Description"
```
-Rebuild: `cmake --build build -j4` — CMake detects manifest changes automatically.
+Rebuild: `cmake --build build -j4` — CMake detects manifest changes automatically. In development mode, changes to `.spec` or `.mp3` files do not require a rebuild.
diff --git a/src/effects/ntsc.wgsl b/src/effects/ntsc.wgsl
index 31d4e0d..6c9fa37 100644
--- a/src/effects/ntsc.wgsl
+++ b/src/effects/ntsc.wgsl
@@ -3,9 +3,8 @@
#include "render/fullscreen_uv_vs"
#include "math/noise"
-const f32 vignetteRounding = 160.0f;
-const f32 vignetteSmoothness = 0.7f;
-
+const vignetteRounding = 160.0f;
+const vignetteSmoothness = 0.7f;
@group(0) @binding(0) var input_sampler: sampler;
@group(0) @binding(1) var input_texture: texture_2d<f32>;
@@ -13,15 +12,14 @@ const f32 vignetteSmoothness = 0.7f;
// Barrel (fisheye) distortion: strength > 0 = barrel, < 0 = pincushion
fn fisheye(uv: vec2f, strength: f32) -> vec2f {
- let c = uv * 2.0 - 1.0;
- let r2 = c * c;
- return uv * 1.03 * (1.0 + vec2f(.1, .24) * strength * r2);
+ let r2 = uv * uv;
+ return uv * 1.05 * (1.0 + vec2f(.1, .24) * strength * r2);
}
-fn vignette(vec2f uv) -> f32 {
- uv *= 1.99;
- f32 amount = 1.0 - sqrt(pow(abs(uv.x), vignetteRounding) + pow(abs(uv.y), vignetteRounding));
- f32 vhard = smoothstep(0., vignetteSmoothness, amount);
+fn vignette(uv: vec2f) -> f32 {
+ let uv2 = uv * 1.99;
+ let amount = 1.0 - sqrt(pow(abs(uv2.x), vignetteRounding) + pow(abs(uv2.y), vignetteRounding));
+ let vhard = smoothstep(0., vignetteSmoothness, amount);
return vhard;
}
@@ -29,8 +27,8 @@ fn vignette(vec2f uv) -> f32 {
let t = uniforms.time;
// Fisheye/barrel distortion
- let uv = fisheye(in.uv, 0.18);
- uv = vignette(uv);
+ let uv = (fisheye(in.st, 0.18) + 1.) * .5;
+// uv = vignette(uv);
// Black outside screen edges
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
diff --git a/src/util/asset_manager.cc b/src/util/asset_manager.cc
index 2285f3a..52a60ee 100644
--- a/src/util/asset_manager.cc
+++ b/src/util/asset_manager.cc
@@ -76,6 +76,39 @@ const uint8_t* GetAsset(AssetId asset_id, size_t* out_size) {
AssetRecord source_record = assets[index];
+#if !defined(DEMO_STRIP_ALL)
+ if (source_record.type == AssetType::SPEC ||
+ source_record.type == AssetType::MP3) {
+ const char* path = (const char*)source_record.data;
+ FILE* f = fopen(path, "rb");
+ if (!f) {
+ if (out_size)
+ *out_size = 0;
+ return nullptr;
+ }
+ fseek(f, 0, SEEK_END);
+ long size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ uint8_t* buffer = new (std::nothrow) uint8_t[size + 1];
+ if (!buffer) {
+ fclose(f);
+ if (out_size)
+ *out_size = 0;
+ return nullptr;
+ }
+ fread(buffer, 1, size, f);
+ fclose(f);
+ buffer[size] = 0; // Null-terminate
+
+ g_asset_cache[index].data = buffer;
+ g_asset_cache[index].size = size;
+ g_asset_cache[index].type = source_record.type;
+ if (out_size)
+ *out_size = size;
+ return buffer;
+ }
+#endif
+
AssetRecord cached_record = source_record;
if (source_record.type == AssetType::PROC ||
@@ -188,6 +221,14 @@ void DropAsset(AssetId asset_id, const uint8_t* asset) {
delete[] g_asset_cache[index].data;
g_asset_cache[index] = {}; // Zero out the struct to force re-generation
}
+#if !defined(DEMO_STRIP_ALL)
+ if (g_asset_cache[index].data == asset &&
+ (g_asset_cache[index].type == AssetType::SPEC ||
+ g_asset_cache[index].type == AssetType::MP3)) {
+ delete[] g_asset_cache[index].data;
+ g_asset_cache[index] = {};
+ }
+#endif
// For static assets, no dynamic memory to free.
}
diff --git a/src/util/asset_manager.h b/src/util/asset_manager.h
index 5380257..786a8db 100644
--- a/src/util/asset_manager.h
+++ b/src/util/asset_manager.h
@@ -6,10 +6,14 @@
#include "asset_manager_dcl.h"
enum class AssetType : uint8_t {
- STATIC,
+ WGSL,
+ SPEC,
+ TEXTURE,
+ MESH,
+ BINARY,
+ MP3,
PROC,
PROC_GPU,
- MP3
};
struct AssetRecord {
diff --git a/tools/asset_packer.cc b/tools/asset_packer.cc
index c982a39..e28d71f 100644
--- a/tools/asset_packer.cc
+++ b/tools/asset_packer.cc
@@ -37,17 +37,6 @@ static const std::map<std::string, ProcGenFunc> kAssetPackerProcGenFuncMap = {
#endif
};
-static bool HasImageExtension(const std::string& filename) {
- std::string ext = filename.substr(filename.find_last_of(".") + 1);
- return ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "tga" ||
- ext == "bmp";
-}
-
-static bool HasMeshExtension(const std::string& filename) {
- std::string ext = filename.substr(filename.find_last_of(".") + 1);
- return ext == "obj";
-}
-
// Forward declaration
struct AssetBuildInfo;
@@ -348,14 +337,20 @@ static bool ProcessImageFile(const std::string& full_path,
}
int main(int argc, char* argv[]) {
- if (argc != 4) {
+ if (argc < 4) {
fprintf(stderr,
"Usage: %s <assets.txt_path> <output_assets_h_path> "
- "<output_assets_data_cc_path>\n",
+ "<output_assets_data_cc_path> [--disk_load]\n",
argv[0]);
return 1;
}
+ bool disk_load_mode = false;
+ if (argc > 4 && std::strcmp(argv[4], "--disk_load") == 0) {
+ disk_load_mode = true;
+ printf("Asset packer running in disk-load mode.\n");
+ }
+
std::string assets_txt_path = argv[1];
std::string output_assets_h_path = argv[2];
std::string output_assets_data_cc_path = argv[3];
@@ -438,7 +433,7 @@ int main(int argc, char* argv[]) {
info.data_array_name = "ASSET_DATA_" + info.name;
info.params_array_name = "ASSET_PROC_PARAMS_" + info.name;
info.func_name_str_name = "ASSET_PROC_FUNC_STR_" + info.name;
- info.asset_type = "STATIC";
+ info.asset_type = compression_type_str;
if (compression_type_str.rfind("PROC_GPU(", 0) == 0) {
info.asset_type = "PROC_GPU";
@@ -452,7 +447,11 @@ int main(int argc, char* argv[]) {
}
} else if (compression_type_str == "MP3") {
info.asset_type = "MP3";
- } else if (compression_type_str != "NONE") {
+ } else if (compression_type_str != "WGSL" &&
+ compression_type_str != "SPEC" &&
+ compression_type_str != "TEXTURE" &&
+ compression_type_str != "MESH" &&
+ compression_type_str != "BINARY") {
fprintf(stderr,
"Warning: Unknown compression type '%s' for asset: %s\n",
compression_type_str.c_str(), info.name.c_str());
@@ -488,44 +487,50 @@ int main(int argc, char* argv[]) {
std::filesystem::path combined_path = base_path / info.filename;
std::string full_path = combined_path.lexically_normal().string();
- std::vector<uint8_t> buffer;
- bool is_image = HasImageExtension(info.filename);
- bool is_mesh = HasMeshExtension(info.filename);
-
- if (is_image) {
- if (!ProcessImageFile(full_path, &buffer, info.name)) {
- return 1;
- }
- } else if (is_mesh) {
- if (!ProcessMeshFile(full_path, &buffer, info.name)) {
- return 1;
- }
+ if (disk_load_mode &&
+ (info.asset_type == "SPEC" || info.asset_type == "MP3")) {
+ fprintf(assets_data_cc_file,
+ "alignas(16) static const char %s[] = \"%s\";\n",
+ info.data_array_name.c_str(), full_path.c_str());
+ fprintf(assets_data_cc_file, "const size_t ASSET_SIZE_%s = %zu;\n",
+ info.name.c_str(), full_path.length() + 1);
} else {
- std::ifstream asset_file(full_path, std::ios::binary);
- if (!asset_file.is_open()) {
- fprintf(stderr, "Error: Could not open asset file: %s\n",
- full_path.c_str());
- return 1;
+ std::vector<uint8_t> buffer;
+ if (info.asset_type == "TEXTURE") {
+ if (!ProcessImageFile(full_path, &buffer, info.name)) {
+ return 1;
+ }
+ } else if (info.asset_type == "MESH") {
+ if (!ProcessMeshFile(full_path, &buffer, info.name)) {
+ return 1;
+ }
+ } else {
+ std::ifstream asset_file(full_path, std::ios::binary);
+ if (!asset_file.is_open()) {
+ fprintf(stderr, "Error: Could not open asset file: %s\n",
+ full_path.c_str());
+ return 1;
+ }
+ buffer.assign((std::istreambuf_iterator<char>(asset_file)),
+ std::istreambuf_iterator<char>());
}
- buffer.assign((std::istreambuf_iterator<char>(asset_file)),
- std::istreambuf_iterator<char>());
- }
- size_t original_size = buffer.size();
- buffer.push_back(0); // Null terminator for safety
+ size_t original_size = buffer.size();
+ buffer.push_back(0); // Null terminator for safety
- fprintf(assets_data_cc_file, "const size_t ASSET_SIZE_%s = %zu;\n",
- info.name.c_str(), original_size);
- fprintf(assets_data_cc_file,
- "alignas(16) static const uint8_t %s[] = {\n ",
- info.data_array_name.c_str());
- for (size_t i = 0; i < buffer.size(); ++i) {
- if (i > 0 && i % 12 == 0)
- fprintf(assets_data_cc_file, "\n ");
- fprintf(assets_data_cc_file, "0x%02x%s", buffer[i],
- (i == buffer.size() - 1 ? "" : ", "));
+ fprintf(assets_data_cc_file, "const size_t ASSET_SIZE_%s = %zu;\n",
+ info.name.c_str(), original_size);
+ fprintf(assets_data_cc_file,
+ "alignas(16) static const uint8_t %s[] = {\n ",
+ info.data_array_name.c_str());
+ for (size_t i = 0; i < buffer.size(); ++i) {
+ if (i > 0 && i % 12 == 0)
+ fprintf(assets_data_cc_file, "\n ");
+ fprintf(assets_data_cc_file, "0x%02x%s", buffer[i],
+ (i == buffer.size() - 1 ? "" : ", "));
+ }
+ fprintf(assets_data_cc_file, "\n};\n");
}
- fprintf(assets_data_cc_file, "\n};\n");
} else {
fprintf(assets_data_cc_file, "static const float %s[] = {",
info.params_array_name.c_str());
@@ -550,7 +555,8 @@ int main(int argc, char* argv[]) {
info.params_array_name.c_str(), info.proc_params.size());
} else {
fprintf(assets_data_cc_file,
- "%s, ASSET_SIZE_%s, AssetType::%s, nullptr, nullptr, 0",
+ "(const uint8_t*)%s, ASSET_SIZE_%s, AssetType::%s, nullptr, "
+ "nullptr, 0",
info.data_array_name.c_str(), info.name.c_str(),
info.asset_type.c_str());
}
diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt
index 7ce8bdd..5f5bad7 100644
--- a/workspaces/main/assets.txt
+++ b/workspaces/main/assets.txt
@@ -8,22 +8,22 @@
# PROC_GPU(func,..) - GPU compute procedural generation at init
# --- Drum & Percussion Samples ---
-KICK_1, NONE, music/KICK_606.spec, "606 Kick"
-KICK_2, NONE, music/KICK_90S_2.spec, "90s Kick"
-SNARE_1, NONE, music/SNARE_808.spec, "808 Snare"
-SNARE_2, NONE, music/SNARE_909_TUNE_8.spec, "909 Snare"
-SNARE_3, NONE, music/SNARE_BLUE_ROOM.spec, "Snare Blue Room"
-HIHAT_1, NONE, music/HIHAT_CLOSED_DMX.spec, "DMX Closed Hi-hat"
-HIHAT_2, NONE, music/HIHAT_CLOSED_DUFF.spec, "Duff Closed Hi-hat"
-HIHAT_3, NONE, music/HIHAT_CLOSED_ER_1.spec, "ER-1 Closed Hi-hat"
-CRASH_1, NONE, music/CRASH_DMX.spec, "DMX Crash"
-RIDE_1, NONE, music/RIDE_CUP_1.spec, "Ride Cymbal"
-SPLASH_1, NONE, music/SPLASH_GROUNDED.spec, "Splash Cymbal"
+KICK_1, SPEC, music/KICK_606.spec, "606 Kick"
+KICK_2, SPEC, music/KICK_90S_2.spec, "90s Kick"
+SNARE_1, SPEC, music/SNARE_808.spec, "808 Snare"
+SNARE_2, SPEC, music/SNARE_909_TUNE_8.spec, "909 Snare"
+SNARE_3, SPEC, music/SNARE_BLUE_ROOM.spec, "Snare Blue Room"
+HIHAT_1, SPEC, music/HIHAT_CLOSED_DMX.spec, "DMX Closed Hi-hat"
+HIHAT_2, SPEC, music/HIHAT_CLOSED_DUFF.spec, "Duff Closed Hi-hat"
+HIHAT_3, SPEC, music/HIHAT_CLOSED_ER_1.spec, "ER-1 Closed Hi-hat"
+CRASH_1, SPEC, music/CRASH_DMX.spec, "DMX Crash"
+RIDE_1, SPEC, music/RIDE_CUP_1.spec, "Ride Cymbal"
+SPLASH_1, SPEC, music/SPLASH_GROUNDED.spec, "Splash Cymbal"
# --- Melodic Samples ---
-BASS_1, NONE, music/BASS_GUITAR_FEEL.spec, "Bass Guitar"
-BASS_2, NONE, music/BASS_SYNTH_1.spec, "Synth Bass 1"
-BASS_3, NONE, music/SYNTH_BASS_DISTORT.spec, "Distorted Synth Bass"
+BASS_1, SPEC, music/BASS_GUITAR_FEEL.spec, "Bass Guitar"
+BASS_2, SPEC, music/BASS_SYNTH_1.spec, "Synth Bass 1"
+BASS_3, SPEC, music/SYNTH_BASS_DISTORT.spec, "Distorted Synth Bass"
NEVER_MP3, MP3, music/never.mp3, "MP3 Sample"
DRUM_MP3, MP3, music/drum.mp3, "Drum Sample"
@@ -31,74 +31,74 @@ DRUM_MP3, MP3, music/drum.mp3, "Drum Sample"
NOISE_TEX, PROC(gen_noise, 1234, 16), _, "Procedural noise texture for bump mapping"
# --- WGSL Shaders & Snippets ---
-SHADER_RENDERER_3D, NONE, shaders/renderer_3d.wgsl, "Hybrid 3D Renderer Shader"
-SHADER_COMMON_UNIFORMS, NONE, ../../src/shaders/common_uniforms.wgsl, "Common Uniforms Snippet"
-SHADER_CAMERA_COMMON, NONE, ../../src/shaders/camera_common.wgsl, "Camera parameters and raymarching helpers"
-SHADER_SDF_SHAPES, NONE, ../../src/shaders/math/sdf_shapes.wgsl, "SDF Shapes (2D/3D primitives)"
-SHADER_LIGHTING, NONE, ../../src/shaders/lighting.wgsl, "Lighting Snippet"
-SHADER_RAY_BOX, NONE, ../../src/shaders/ray_box.wgsl, "Ray-Box Intersection Snippet"
-SHADER_RAY_TRIANGLE, NONE, ../../src/shaders/ray_triangle.wgsl, "Ray-Triangle Intersection Snippet (Möller-Trumbore)"
-SHADER_MAIN, NONE, shaders/main_shader.wgsl, "Main Heptagon Shader"
-SHADER_PARTICLE_COMPUTE, NONE, ../../src/effects/particle_compute.wgsl, "Particle Compute Shader"
-SHADER_PARTICLE_RENDER, NONE, ../../src/effects/particle_render.wgsl, "Particle Render Shader"
-SHADER_PARTICLE_COMPUTE_V2, NONE, ../../src/effects/particle_compute.wgsl, "Particle Compute Shader"
-SHADER_PARTICLE_RENDER_V2, NONE, ../../src/effects/particle_render.wgsl, "Particle Render Shader"
-SHADER_ROTATING_CUBE_V2, NONE, ../../src/effects/rotating_cube.wgsl, "Rotating Cube Shader"
-SHADER_PASSTHROUGH, NONE, ../../src/shaders/passthrough.wgsl, "Passthrough Shader"
-SHADER_ELLIPSE, NONE, ../../src/effects/ellipse.wgsl, "Ellipse Shader"
-SHADER_PARTICLE_SPRAY_COMPUTE, NONE, ../../src/effects/particle_spray_compute.wgsl, "Particle Spray Compute"
-SHADER_GAUSSIAN_BLUR, NONE, ../../src/effects/gaussian_blur.wgsl, "Gaussian Blur Shader"
-SHADER_CNN_ACTIVATION, NONE, ../../cnn_v1/shaders/cnn_activation.wgsl, "CNN Activation Functions"
-SHADER_CNN_CONV1X1, NONE, ../../cnn_v1/shaders/cnn_conv1x1.wgsl, "CNN 1x1 Convolution"
-SHADER_CNN_CONV3X3, NONE, ../../cnn_v1/shaders/cnn_conv3x3.wgsl, "CNN 3x3 Convolution"
-SHADER_CNN_CONV5X5, NONE, ../../cnn_v1/shaders/cnn_conv5x5.wgsl, "CNN 5x5 Convolution"
-SHADER_CNN_CONV7X7, NONE, ../../cnn_v1/shaders/cnn_conv7x7.wgsl, "CNN 7x7 Convolution"
-SHADER_CNN_WEIGHTS, NONE, ../../cnn_v1/shaders/cnn_weights_generated.wgsl, "CNN Weights (Generated)"
-SHADER_CNN_LAYER, NONE, ../../cnn_v1/shaders/cnn_layer.wgsl, "CNN Layer Shader"
-SHADER_CNN_V2_STATIC, NONE, ../../cnn_v2/shaders/cnn_v2_static.wgsl, "CNN v2 Static Features"
-SHADER_CNN_V2_COMPUTE, NONE, ../../cnn_v2/shaders/cnn_v2_compute.wgsl, "CNN v2 Compute (Storage Buffer)"
-WEIGHTS_CNN_V2, NONE, ../../cnn_v2/weights/cnn_v2_weights.bin, "CNN v2 Binary Weights"
-SHADER_SOLARIZE, NONE, ../../src/effects/solarize.wgsl, "Solarize Shader"
-SHADER_DISTORT, NONE, ../../src/effects/distort.wgsl, "Distort Shader"
-SHADER_CHROMA_ABERRATION, NONE, ../../src/effects/chroma_aberration.wgsl, "Chroma Aberration Shader"
-SHADER_VISUAL_DEBUG, NONE, shaders/visual_debug.wgsl, "Visual Debug Shader"
-SHADER_SKYBOX, NONE, ../../src/shaders/skybox.wgsl, "Skybox background shader"
-SHADER_MATH_SDF_SHAPES, NONE, ../../src/shaders/math/sdf_shapes.wgsl, "SDF Shapes Snippet"
-SHADER_MATH_SDF_UTILS, NONE, ../../src/shaders/math/sdf_utils.wgsl, "SDF Utils Snippet"
-SHADER_MATH_COMMON_UTILS, NONE, ../../src/shaders/math/common_utils.wgsl, "Common Math Utils"
-SHADER_MATH_NOISE, NONE, ../../src/shaders/math/noise.wgsl, "RNG and Noise Functions"
-SHADER_RENDER_SHADOWS, NONE, ../../src/shaders/render/shadows.wgsl, "Shadows Snippet"
-SHADER_RENDER_SCENE_QUERY_BVH, NONE, ../../src/shaders/render/scene_query_bvh.wgsl, "Scene Query Snippet (BVH)"
-SHADER_RENDER_SCENE_QUERY_LINEAR, NONE, ../../src/shaders/render/scene_query_linear.wgsl, "Scene Query Snippet (Linear)"
-SHADER_RENDER_LIGHTING_UTILS, NONE, ../../src/shaders/render/lighting_utils.wgsl, "Lighting Utils Snippet"
-SHADER_MESH, NONE, shaders/mesh_render.wgsl, "Mesh Rasterization Shader"
-MESH_CUBE, NONE, obj/test_mesh.obj, "A simple cube mesh"
-DODECAHEDRON, NONE, obj/dodecahedron.obj, "A dodecahedron mesh"
-SHADER_RENDER_FULLSCREEN_VS, NONE, ../../src/shaders/render/fullscreen_vs.wgsl, "Fullscreen Vertex Shader"
-SHADER_RENDER_FULLSCREEN_UV_VS, NONE, ../../src/shaders/render/fullscreen_uv_vs.wgsl, "Fullscreen Vertex Shader + UV"
-SHADER_MATH_COLOR, NONE, ../../src/shaders/math/color.wgsl, "Color Functions"
-SHADER_MATH_UTILS, NONE, ../../src/shaders/math/utils.wgsl, "Math Utilities"
-SHADER_RENDER_RAYMARCHING, NONE, ../../src/shaders/render/raymarching.wgsl, "Raymarching Functions"
-SHADER_RENDER_RAYMARCHING_ID, NONE, ../../src/shaders/render/raymarching_id.wgsl, "Raymarching-ID Functions"
-SHADER_VIGNETTE, NONE, ../../src/effects/vignette.wgsl, "Vignette Shader"
-SHADER_COMPUTE_GEN_NOISE, NONE, ../../src/shaders/compute/gen_noise.wgsl, "GPU Noise Compute Shader"
-SHADER_COMPUTE_GEN_PERLIN, NONE, ../../src/shaders/compute/gen_perlin.wgsl, "GPU Perlin Noise Compute Shader"
-SHADER_COMPUTE_GEN_GRID, NONE, ../../src/shaders/compute/gen_grid.wgsl, "GPU Grid Compute Shader"
-SHADER_COMPUTE_GEN_BLEND, NONE, ../../src/shaders/compute/gen_blend.wgsl, "GPU Blend Composite Shader"
-SHADER_COMPUTE_GEN_MASK, NONE, ../../src/shaders/compute/gen_mask.wgsl, "GPU Mask Composite Shader"
-CIRCLE_MASK_COMPUTE_SHADER, NONE, shaders/circle_mask_compute.wgsl, "Circle mask compute shader"
-CIRCLE_MASK_RENDER_SHADER, NONE, shaders/circle_mask_render.wgsl, "Circle mask render shader"
-MASKED_CUBE_SHADER, NONE, shaders/masked_cube.wgsl, "Masked cube shader"
-SHADER_SCENE1, NONE, ../../src/effects/scene1.wgsl, "Scene1 effect shader"
-SHADER_SCENE2, NONE, ../../src/effects/scene2.wgsl, "Scene2 effect shader"
-SHADER_RENDER_SCRATCH_LINES, NONE, ../../src/shaders/render/scratch_lines.wgsl, "Film scratch lines snippet"
-SHADER_SCRATCH, NONE, ../../src/effects/scratch.wgsl, "Scratch effect shader"
-SHADER_NTSC, NONE, ../../src/effects/ntsc.wgsl, "NTSC effect shader"
+SHADER_RENDERER_3D, WGSL, shaders/renderer_3d.wgsl, "Hybrid 3D Renderer Shader"
+SHADER_COMMON_UNIFORMS, WGSL, ../../src/shaders/common_uniforms.wgsl, "Common Uniforms Snippet"
+SHADER_CAMERA_COMMON, WGSL, ../../src/shaders/camera_common.wgsl, "Camera parameters and raymarching helpers"
+SHADER_SDF_SHAPES, WGSL, ../../src/shaders/math/sdf_shapes.wgsl, "SDF Shapes (2D/3D primitives)"
+SHADER_LIGHTING, WGSL, ../../src/shaders/lighting.wgsl, "Lighting Snippet"
+SHADER_RAY_BOX, WGSL, ../../src/shaders/ray_box.wgsl, "Ray-Box Intersection Snippet"
+SHADER_RAY_TRIANGLE, WGSL, ../../src/shaders/ray_triangle.wgsl, "Ray-Triangle Intersection Snippet (Möller-Trumbore)"
+SHADER_MAIN, WGSL, shaders/main_shader.wgsl, "Main Heptagon Shader"
+SHADER_PARTICLE_COMPUTE, WGSL, ../../src/effects/particle_compute.wgsl, "Particle Compute Shader"
+SHADER_PARTICLE_RENDER, WGSL, ../../src/effects/particle_render.wgsl, "Particle Render Shader"
+SHADER_PARTICLE_COMPUTE_V2, WGSL, ../../src/effects/particle_compute.wgsl, "Particle Compute Shader"
+SHADER_PARTICLE_RENDER_V2, WGSL, ../../src/effects/particle_render.wgsl, "Particle Render Shader"
+SHADER_ROTATING_CUBE_V2, WGSL, ../../src/effects/rotating_cube.wgsl, "Rotating Cube Shader"
+SHADER_PASSTHROUGH, WGSL, ../../src/shaders/passthrough.wgsl, "Passthrough Shader"
+SHADER_ELLIPSE, WGSL, ../../src/effects/ellipse.wgsl, "Ellipse Shader"
+SHADER_PARTICLE_SPRAY_COMPUTE, WGSL, ../../src/effects/particle_spray_compute.wgsl, "Particle Spray Compute"
+SHADER_GAUSSIAN_BLUR, WGSL, ../../src/effects/gaussian_blur.wgsl, "Gaussian Blur Shader"
+SHADER_CNN_ACTIVATION, WGSL, ../../cnn_v1/shaders/cnn_activation.wgsl, "CNN Activation Functions"
+SHADER_CNN_CONV1X1, WGSL, ../../cnn_v1/shaders/cnn_conv1x1.wgsl, "CNN 1x1 Convolution"
+SHADER_CNN_CONV3X3, WGSL, ../../cnn_v1/shaders/cnn_conv3x3.wgsl, "CNN 3x3 Convolution"
+SHADER_CNN_CONV5X5, WGSL, ../../cnn_v1/shaders/cnn_conv5x5.wgsl, "CNN 5x5 Convolution"
+SHADER_CNN_CONV7X7, WGSL, ../../cnn_v1/shaders/cnn_conv7x7.wgsl, "CNN 7x7 Convolution"
+SHADER_CNN_WEIGHTS, WGSL, ../../cnn_v1/shaders/cnn_weights_generated.wgsl, "CNN Weights (Generated)"
+SHADER_CNN_LAYER, WGSL, ../../cnn_v1/shaders/cnn_layer.wgsl, "CNN Layer Shader"
+SHADER_CNN_V2_STATIC, WGSL, ../../cnn_v2/shaders/cnn_v2_static.wgsl, "CNN v2 Static Features"
+SHADER_CNN_V2_COMPUTE, WGSL, ../../cnn_v2/shaders/cnn_v2_compute.wgsl, "CNN v2 Compute (Storage Buffer)"
+WEIGHTS_CNN_V2, BINARY, ../../cnn_v2/weights/cnn_v2_weights.bin, "CNN v2 Binary Weights"
+SHADER_SOLARIZE, WGSL, ../../src/effects/solarize.wgsl, "Solarize Shader"
+SHADER_DISTORT, WGSL, ../../src/effects/distort.wgsl, "Distort Shader"
+SHADER_CHROMA_ABERRATION, WGSL, ../../src/effects/chroma_aberration.wgsl, "Chroma Aberration Shader"
+SHADER_VISUAL_DEBUG, WGSL, shaders/visual_debug.wgsl, "Visual Debug Shader"
+SHADER_SKYBOX, WGSL, ../../src/shaders/skybox.wgsl, "Skybox background shader"
+SHADER_MATH_SDF_SHAPES, WGSL, ../../src/shaders/math/sdf_shapes.wgsl, "SDF Shapes Snippet"
+SHADER_MATH_SDF_UTILS, WGSL, ../../src/shaders/math/sdf_utils.wgsl, "SDF Utils Snippet"
+SHADER_MATH_COMMON_UTILS, WGSL, ../../src/shaders/math/common_utils.wgsl, "Common Math Utils"
+SHADER_MATH_NOISE, WGSL, ../../src/shaders/math/noise.wgsl, "RNG and Noise Functions"
+SHADER_RENDER_SHADOWS, WGSL, ../../src/shaders/render/shadows.wgsl, "Shadows Snippet"
+SHADER_RENDER_SCENE_QUERY_BVH, WGSL, ../../src/shaders/render/scene_query_bvh.wgsl, "Scene Query Snippet (BVH)"
+SHADER_RENDER_SCENE_QUERY_LINEAR, WGSL, ../../src/shaders/render/scene_query_linear.wgsl, "Scene Query Snippet (Linear)"
+SHADER_RENDER_LIGHTING_UTILS, WGSL, ../../src/shaders/render/lighting_utils.wgsl, "Lighting Utils Snippet"
+SHADER_MESH, WGSL, shaders/mesh_render.wgsl, "Mesh Rasterization Shader"
+MESH_CUBE, MESH, obj/test_mesh.obj, "A simple cube mesh"
+DODECAHEDRON, MESH, obj/dodecahedron.obj, "A dodecahedron mesh"
+SHADER_RENDER_FULLSCREEN_VS, WGSL, ../../src/shaders/render/fullscreen_vs.wgsl, "Fullscreen Vertex Shader"
+SHADER_RENDER_FULLSCREEN_UV_VS, WGSL, ../../src/shaders/render/fullscreen_uv_vs.wgsl, "Fullscreen Vertex Shader + UV"
+SHADER_MATH_COLOR, WGSL, ../../src/shaders/math/color.wgsl, "Color Functions"
+SHADER_MATH_UTILS, WGSL, ../../src/shaders/math/utils.wgsl, "Math Utilities"
+SHADER_RENDER_RAYMARCHING, WGSL, ../../src/shaders/render/raymarching.wgsl, "Raymarching Functions"
+SHADER_RENDER_RAYMARCHING_ID, WGSL, ../../src/shaders/render/raymarching_id.wgsl, "Raymarching-ID Functions"
+SHADER_VIGNETTE, WGSL, ../../src/effects/vignette.wgsl, "Vignette Shader"
+SHADER_COMPUTE_GEN_NOISE, WGSL, ../../src/shaders/compute/gen_noise.wgsl, "GPU Noise Compute Shader"
+SHADER_COMPUTE_GEN_PERLIN, WGSL, ../../src/shaders/compute/gen_perlin.wgsl, "GPU Perlin Noise Compute Shader"
+SHADER_COMPUTE_GEN_GRID, WGSL, ../../src/shaders/compute/gen_grid.wgsl, "GPU Grid Compute Shader"
+SHADER_COMPUTE_GEN_BLEND, WGSL, ../../src/shaders/compute/gen_blend.wgsl, "GPU Blend Composite Shader"
+SHADER_COMPUTE_GEN_MASK, WGSL, ../../src/shaders/compute/gen_mask.wgsl, "GPU Mask Composite Shader"
+CIRCLE_MASK_COMPUTE_SHADER, WGSL, shaders/circle_mask_compute.wgsl, "Circle mask compute shader"
+CIRCLE_MASK_RENDER_SHADER, WGSL, shaders/circle_mask_render.wgsl, "Circle mask render shader"
+MASKED_CUBE_SHADER, WGSL, shaders/masked_cube.wgsl, "Masked cube shader"
+SHADER_SCENE1, WGSL, ../../src/effects/scene1.wgsl, "Scene1 effect shader"
+SHADER_SCENE2, WGSL, ../../src/effects/scene2.wgsl, "Scene2 effect shader"
+SHADER_RENDER_SCRATCH_LINES, WGSL, ../../src/shaders/render/scratch_lines.wgsl, "Film scratch lines snippet"
+SHADER_SCRATCH, WGSL, ../../src/effects/scratch.wgsl, "Scratch effect shader"
+SHADER_NTSC, WGSL, ../../src/effects/ntsc.wgsl, "NTSC effect shader"
# --- Sequence Shaders ---
-SHADER_SEQUENCE_V2_UNIFORMS, NONE, ../../src/shaders/sequence_uniforms.wgsl, "Sequence Uniforms Snippet"
-SHADER_POSTPROCESS_INLINE, NONE, ../../src/shaders/postprocess_inline.wgsl, "Inline Post-Process Functions"
-SHADER_PASSTHROUGH_V2, NONE, ../../src/shaders/passthrough.wgsl, "Passthrough Shader"
-SHADER_GAUSSIAN_BLUR_V2, NONE, ../../src/shaders/gaussian_blur.wgsl, "Gaussian Blur Shader"
-SHADER_HEPTAGON_V2, NONE, ../../src/shaders/heptagon.wgsl, "Heptagon Shader"
-SHADER_FLASH, NONE, ../../src/effects/flash.wgsl, "Flash Shader"
+SHADER_SEQUENCE_V2_UNIFORMS, WGSL, ../../src/shaders/sequence_uniforms.wgsl, "Sequence Uniforms Snippet"
+SHADER_POSTPROCESS_INLINE, WGSL, ../../src/shaders/postprocess_inline.wgsl, "Inline Post-Process Functions"
+SHADER_PASSTHROUGH_V2, WGSL, ../../src/shaders/passthrough.wgsl, "Passthrough Shader"
+SHADER_GAUSSIAN_BLUR_V2, WGSL, ../../src/shaders/gaussian_blur.wgsl, "Gaussian Blur Shader"
+SHADER_HEPTAGON_V2, WGSL, ../../src/shaders/heptagon.wgsl, "Heptagon Shader"
+SHADER_FLASH, WGSL, ../../src/effects/flash.wgsl, "Flash Shader"