summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/ASSET_SYSTEM.md136
-rw-r--r--src/audio/tracker.cc13
-rw-r--r--src/util/asset_manager.cc26
-rw-r--r--src/util/asset_manager.h9
-rw-r--r--tools/asset_packer.cc32
-rw-r--r--workspaces/main/assets.txt8
-rw-r--r--workspaces/test/assets.txt6
7 files changed, 102 insertions, 128 deletions
diff --git a/doc/ASSET_SYSTEM.md b/doc/ASSET_SYSTEM.md
index f52e382..6104353 100644
--- a/doc/ASSET_SYSTEM.md
+++ b/doc/ASSET_SYSTEM.md
@@ -1,42 +1,50 @@
# Compact Asset System for the 64k Demo
-This file describes the asset system architecture and runtime API.
-
-## Core Concept
-
All assets are const byte arrays embedded in the binary. Runtime retrieval via `GetAsset(AssetId)` returns direct pointers with O(1) lookup.
-## Runtime Asset Retrieval
-
-```cpp
-#include "assets.h"
-const uint8_t* mysample = GetAsset(ASSET_SAMPLE_142, &size);
-// ... use asset
-DropAsset(ASSET_SAMPLE_142, mysample); // For lazy decompression cleanup
-```
-
## Asset Manifest Format
Files: `workspaces/main/assets.txt`, `workspaces/test/assets.txt`
Format (CSV):
```
-ASSET_NAME, COMPRESSION, filename.ext, "Description"
+ASSET_NAME, COMPRESSION, path/to/file.ext, "Description"
```
-Example:
+### Compression 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 |
+
+## AssetType Enum
+
+Each `AssetRecord` carries an `AssetType` field (replaces the former `is_procedural`/`is_gpu_procedural` bools):
+
+```cpp
+enum class AssetType : uint8_t { STATIC, PROC, PROC_GPU, MP3 };
```
-SAMPLE_142, sample_142.spec, NONE, "This is a drum kick sample"
-SHADER_COMMON, shaders/common.wgsl, NONE, "Common utilities"
+
+Query at runtime:
+```cpp
+if (GetAssetType(AssetId::NEVER_MP3) == AssetType::MP3) { ... }
```
-This generates:
+## Runtime API
+
```cpp
-enum class AssetId : uint16_t {
- ASSET_SAMPLE_142 = 6323,
- SHADER_COMMON = 6324,
- // ...
-};
+#include "util/asset_manager.h"
+
+// Retrieve asset data
+size_t size;
+const uint8_t* data = GetAsset(AssetId::MY_ASSET, &size);
+DropAsset(AssetId::MY_ASSET, data); // Release procedural/cached data
+
+// Query type
+AssetType t = GetAssetType(AssetId::MY_ASSET);
```
## Build Pipeline
@@ -44,80 +52,32 @@ enum class AssetId : uint16_t {
Tool: `tools/asset_packer.cc`
1. Parse workspace `assets.txt`
-2. Read files from workspace `assets/` or `assets/common/`
-3. Generate `assets.h` (enum definitions)
-4. Generate `assets_data.cc` (embedded byte arrays)
+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
## Technical Guarantees
-1. **Alignment**: All arrays declared `alignas(16)` for safe `reinterpret_cast`
-2. **String Safety**: Null-terminator appended (safe as C-string)
-3. **Size Reporting**: `size` reflects original file size (buffer is `size + 1`)
+- **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`)
-## Shader Asset Usage
+## Developer Workflow
-```cpp
-size_t size;
-const uint8_t* shader_src = GetAsset(AssetId::SHADER_COMMON, &size);
-std::string_view code(reinterpret_cast<const char*>(shader_src), size);
-ShaderComposer::register_snippet("common", code);
+**Add a static asset:**
+```
+MY_ASSET, NONE, path/to/file.ext, "Description"
```
-## Procedural Assets
-
-Format: `PROC(function_name, params...)`
-
-Example:
+**Add an MP3 sample:**
```
-NOISE_TEX, PROC(gen_noise, 1234, 16), NONE, "Noise texture seed=1234 freq=16"
+MY_SAMPLE, MP3, music/sample.mp3, "Description"
```
-Runtime: First `GetAsset()` call invokes generator, caches result.
-
-## Design Strengths
-
-- Enum-based type safety (no magic numbers)
-- Zero runtime cost for static assets
-- Procedural generation support (size savings)
-- Thread-safe for immutable assets
-
-## Design Weaknesses
-
-- **No compression**: Only `NONE` supported (critical blocker for 64k)
-- **STL dependencies**: `std::map` in runtime code (conflicts with CRT removal)
-- **Hardcoded procedural dimensions**: Assumes 256×256 RGBA8
-- **No integrity checks**: No CRC/hash validation
-
-## Developer Workflow
-
-**Add new asset:**
-1. Place file in workspace `assets/` (or `assets/common/` for shared)
-2. Edit workspace `assets.txt`:
- ```
- MY_ASSET, NONE, assets/myfile.ext, "Description"
- ```
-3. Rebuild: `cmake --build build -j4` (auto-triggered)
-4. Use in code: `GetAsset(AssetId::MY_ASSET, &size)`
-
-**Modify existing asset:**
-- Edit source file in `assets/final/`
-- CMake auto-detects change and rebuilds
-
-## Planned Improvements
-
-See **TODO.md** for detailed task breakdown:
-- **Task #27**: Asset compression (zlib, RLE)
-- **Task #28**: Spectrogram quantization (float32 → uint16_t)
-- **Task #29**: WGSL shader minification
-- **Task #30**: Procedural asset generalization
-- **Task #31**: Asset integrity validation (CRC32)
-- **Task #32**: Hot-reloading (dev builds)
-- **Task #33**: Dead asset elimination
-
-## Current Manifest
+**Add a procedural texture:**
+```
+MY_TEX, PROC(gen_noise, 1234, 16), _, "Description"
+```
-33 assets total:
-- 15 audio samples (`.spec`)
-- 17 WGSL shaders
-- 1 procedural texture
+Rebuild: `cmake --build build -j4` — CMake detects manifest changes automatically.
diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc
index 333a337..7ce96fc 100644
--- a/src/audio/tracker.cc
+++ b/src/audio/tracker.cc
@@ -44,15 +44,6 @@ static bool g_cache_initialized = false;
// Forward declarations
static int get_free_pool_slot();
-// Returns true if the asset blob looks like an MP3 file.
-static bool is_mp3_asset(const uint8_t* data, size_t size) {
- if (!data || size < 3) return false;
- // ID3v2 tag prefix
- if (data[0] == 'I' && data[1] == 'D' && data[2] == '3') return true;
- // Raw MP3 sync word: 0xFF followed by 0xE0-0xFF
- if (size >= 2 && data[0] == 0xFF && (data[1] & 0xE0) == 0xE0) return true;
- return false;
-}
#if !defined(STRIP_ALL)
// Decode an in-memory MP3 blob to a heap-allocated spectrogram (caller owns).
@@ -147,7 +138,7 @@ void tracker_init() {
size_t size;
const uint8_t* data = GetAsset(aid, &size);
#if !defined(STRIP_ALL)
- if (data && size > 0 && is_mp3_asset(data, size)) {
+ if (data && size > 0 && GetAssetType(aid) == AssetType::MP3) {
int num_frames = 0;
float* spec_data =
convert_mp3_to_spectrogram(data, size, &num_frames);
@@ -165,7 +156,7 @@ void tracker_init() {
}
} else
#else
- FATAL_CHECK(data == nullptr || !is_mp3_asset(data, size),
+ FATAL_CHECK(data == nullptr || GetAssetType(aid) != AssetType::MP3,
"MP3 assets not supported in STRIP_ALL builds\n");
#endif
if (data && size >= sizeof(SpecHeader)) {
diff --git a/src/util/asset_manager.cc b/src/util/asset_manager.cc
index a606b46..2285f3a 100644
--- a/src/util/asset_manager.cc
+++ b/src/util/asset_manager.cc
@@ -37,7 +37,7 @@ static const size_t kNumProcGenFuncs =
sizeof(kAssetManagerProcGenFuncs) / sizeof(kAssetManagerProcGenFuncs[0]);
// Array-based cache for assets.
-// Initialized to all zeros (nullptr data, 0 size, false is_procedural)
+// Initialized to all zeros (nullptr data, 0 size, AssetType::STATIC)
// The size is derived from the generated ASSET_LAST_ID enum value.
static AssetRecord g_asset_cache[(size_t)AssetId::ASSET_LAST_ID] = {};
@@ -78,7 +78,8 @@ const uint8_t* GetAsset(AssetId asset_id, size_t* out_size) {
AssetRecord cached_record = source_record;
- if (source_record.is_procedural) {
+ if (source_record.type == AssetType::PROC ||
+ source_record.type == AssetType::PROC_GPU) {
// Dynamically generate the asset
ProcGenFunc proc_gen_func_ptr = nullptr;
for (size_t i = 0; i < kNumProcGenFuncs; ++i) {
@@ -131,13 +132,8 @@ const uint8_t* GetAsset(AssetId asset_id, size_t* out_size) {
cached_record.data = generated_data;
cached_record.size = data_size;
- cached_record.is_procedural = true;
- // proc_gen_func, proc_params, num_proc_params already copied from
+ // type, proc_gen_func, proc_params, num_proc_params already copied from
// source_record
-
- } else {
- // Static asset (copy from g_assets)
- cached_record.is_procedural = false;
}
// Store in cache for future use
@@ -187,7 +183,8 @@ void DropAsset(AssetId asset_id, const uint8_t* asset) {
// Check if the asset is in cache and is procedural, and if the pointer
// matches. This prevents accidentally freeing static data or freeing twice.
if (g_asset_cache[index].data == asset &&
- g_asset_cache[index].is_procedural) {
+ (g_asset_cache[index].type == AssetType::PROC ||
+ g_asset_cache[index].type == AssetType::PROC_GPU)) {
delete[] g_asset_cache[index].data;
g_asset_cache[index] = {}; // Zero out the struct to force re-generation
}
@@ -203,7 +200,9 @@ bool ReloadAssetsFromFile(const char* config_path) {
// Clear cache to force reload
for (size_t i = 0; i < (size_t)AssetId::ASSET_LAST_ID; ++i) {
- if (g_asset_cache[i].is_procedural && g_asset_cache[i].data) {
+ if ((g_asset_cache[i].type == AssetType::PROC ||
+ g_asset_cache[i].type == AssetType::PROC_GPU) &&
+ g_asset_cache[i].data) {
delete[] g_asset_cache[i].data;
}
g_asset_cache[i] = {};
@@ -213,3 +212,10 @@ bool ReloadAssetsFromFile(const char* config_path) {
return true;
}
#endif // !defined(STRIP_ALL)
+
+AssetType GetAssetType(AssetId asset_id) {
+ uint16_t index = (uint16_t)asset_id;
+ if (index >= (uint16_t)AssetId::ASSET_LAST_ID)
+ return AssetType::STATIC;
+ return GetAssetRecordTable()[index].type;
+}
diff --git a/src/util/asset_manager.h b/src/util/asset_manager.h
index 1714c21..09483e5 100644
--- a/src/util/asset_manager.h
+++ b/src/util/asset_manager.h
@@ -5,12 +5,12 @@
#pragma once
#include "asset_manager_dcl.h"
+enum class AssetType : uint8_t { STATIC, PROC, PROC_GPU, MP3 };
+
struct AssetRecord {
const uint8_t* data; // Pointer to asset data (static or dynamic)
size_t size; // Size of the asset data
- bool is_procedural; // True if data was dynamically allocated by a procedural
- // generator
- bool is_gpu_procedural; // True if GPU compute shader generates texture
+ AssetType type;
const char* proc_func_name_str; // Name of procedural generation function
// (string literal)
const float* proc_params; // Parameters for procedural generation (static,
@@ -30,6 +30,9 @@ void DropAsset(AssetId asset_id, const uint8_t* asset);
// found.
AssetId GetAssetIdByName(const char* name);
+// Returns the AssetType for a given asset id.
+AssetType GetAssetType(AssetId asset_id);
+
#if !defined(STRIP_ALL)
// Hot-reload: Parse and reload assets from config file (debug only)
bool ReloadAssetsFromFile(const char* config_path);
diff --git a/tools/asset_packer.cc b/tools/asset_packer.cc
index 7e186b6..0d48906 100644
--- a/tools/asset_packer.cc
+++ b/tools/asset_packer.cc
@@ -80,9 +80,8 @@ static bool ParseProceduralParams(const std::string& params_str,
// Helper struct to hold all information about an asset during parsing
struct AssetBuildInfo {
std::string name;
- std::string filename; // Original filename for static assets
- bool is_procedural;
- bool is_gpu_procedural;
+ std::string filename; // Original filename for static assets
+ std::string asset_type; // "STATIC", "PROC", "PROC_GPU", "MP3"
std::string proc_func_name; // Function name string
std::vector<float> proc_params; // Parameters for procedural function
@@ -439,21 +438,23 @@ 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.is_procedural = false;
- info.is_gpu_procedural = false;
+ info.asset_type = "STATIC";
if (compression_type_str.rfind("PROC_GPU(", 0) == 0) {
- info.is_procedural = true;
- info.is_gpu_procedural = true;
+ info.asset_type = "PROC_GPU";
if (!ParseProceduralFunction(compression_type_str, &info, true)) {
return 1;
}
} else if (compression_type_str.rfind("PROC(", 0) == 0) {
- info.is_procedural = true;
- info.is_gpu_procedural = false;
+ info.asset_type = "PROC";
if (!ParseProceduralFunction(compression_type_str, &info, false)) {
return 1;
}
+ } else if (compression_type_str == "MP3") {
+ info.asset_type = "MP3";
+ } else if (compression_type_str != "NONE") {
+ fprintf(stderr, "Warning: Unknown compression type '%s' for asset: %s\n",
+ compression_type_str.c_str(), info.name.c_str());
}
asset_build_infos.push_back(info);
@@ -479,7 +480,7 @@ int main(int argc, char* argv[]) {
std::fclose(assets_h_file);
for (const auto& info : asset_build_infos) {
- if (!info.is_procedural) {
+ if (info.asset_type != "PROC" && info.asset_type != "PROC_GPU") {
std::string base_dir =
assets_txt_path.substr(0, assets_txt_path.find_last_of("/\\") + 1);
std::filesystem::path base_path = std::filesystem::absolute(base_dir);
@@ -542,15 +543,16 @@ int main(int argc, char* argv[]) {
fprintf(assets_data_cc_file, " static const AssetRecord assets[] = {\n");
for (const auto& info : asset_build_infos) {
fprintf(assets_data_cc_file, " { ");
- if (info.is_procedural) {
- fprintf(assets_data_cc_file, "nullptr, 0, true, %s, %s, %s, %zu",
- info.is_gpu_procedural ? "true" : "false",
+ if (info.asset_type == "PROC" || info.asset_type == "PROC_GPU") {
+ fprintf(assets_data_cc_file, "nullptr, 0, AssetType::%s, %s, %s, %zu",
+ info.asset_type.c_str(),
info.func_name_str_name.c_str(), info.params_array_name.c_str(),
info.proc_params.size());
} else {
fprintf(assets_data_cc_file,
- "%s, ASSET_SIZE_%s, false, false, nullptr, nullptr, 0",
- info.data_array_name.c_str(), info.name.c_str());
+ "%s, ASSET_SIZE_%s, AssetType::%s, nullptr, nullptr, 0",
+ info.data_array_name.c_str(), info.name.c_str(),
+ info.asset_type.c_str());
}
fprintf(assets_data_cc_file, " },\n");
}
diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt
index 2cc3896..a324cfd 100644
--- a/workspaces/main/assets.txt
+++ b/workspaces/main/assets.txt
@@ -1,5 +1,11 @@
# WORKSPACE: main
# Asset Name, Compression Type, Filename/Placeholder, Description
+#
+# Compression types:
+# NONE - Raw binary embed (shaders, meshes, .spec files)
+# MP3 - MP3 audio; decoded to spectrogram at runtime
+# PROC(func, ...) - CPU procedural generation at init
+# PROC_GPU(func,..) - GPU compute procedural generation at init
# --- Drum & Percussion Samples ---
KICK_1, NONE, music/KICK_606.spec, "606 Kick"
@@ -18,7 +24,7 @@ SPLASH_1, NONE, music/SPLASH_GROUNDED.spec, "Splash Cymbal"
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"
-NEVER_MP3, NONE, music/never.mp3, "MP3 Sample"
+NEVER_MP3, MP3, music/never.mp3, "MP3 Sample"
# --- Procedural Textures ---
NOISE_TEX, PROC(gen_noise, 1234, 16), _, "Procedural noise texture for bump mapping"
diff --git a/workspaces/test/assets.txt b/workspaces/test/assets.txt
index 1e95084..1cdaf32 100644
--- a/workspaces/test/assets.txt
+++ b/workspaces/test/assets.txt
@@ -1,5 +1,11 @@
# WORKSPACE: test
# Asset Name, Compression Type, Filename/Placeholder, Description
+#
+# Compression types:
+# NONE - Raw binary embed (shaders, meshes, .spec files)
+# MP3 - MP3 audio; decoded to spectrogram at runtime
+# PROC(func, ...) - CPU procedural generation at init
+# PROC_GPU(func,..) - GPU compute procedural generation at init
# --- Drum & Percussion Samples ---
KICK_1, NONE, music/KICK_606.spec, "606 Kick"