summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt22
-rw-r--r--PROJECT_CONTEXT.md25
-rw-r--r--assets/demo.seq8
-rw-r--r--src/gpu/demo_effects.cc12
-rw-r--r--src/gpu/demo_effects.h7
-rw-r--r--src/gpu/gpu.cc3
-rw-r--r--tools/seq_compiler.cc130
7 files changed, 184 insertions, 23 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c7b7508..3812fb7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -120,6 +120,25 @@ else()
set(ASSET_PACKER_DEPENDS asset_packer)
endif()
+# Sequence Compiler Tool
+add_executable(seq_compiler tools/seq_compiler.cc)
+
+# Configure Timeline Generation
+set(DEMO_SEQ_PATH ${CMAKE_CURRENT_SOURCE_DIR}/assets/demo.seq)
+set(GENERATED_TIMELINE_CC ${CMAKE_CURRENT_BINARY_DIR}/src/timeline.cc)
+
+add_custom_command(
+ OUTPUT ${GENERATED_TIMELINE_CC}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/src
+ COMMAND $<TARGET_FILE:seq_compiler> ${DEMO_SEQ_PATH} ${GENERATED_TIMELINE_CC}
+ DEPENDS seq_compiler ${DEMO_SEQ_PATH}
+ COMMENT "Compiling demo sequence..."
+)
+
+add_custom_target(generate_timeline ALL
+ DEPENDS ${GENERATED_TIMELINE_CC}
+)
+
# Configure DEMO asset generation
set(DEMO_ASSETS_TXT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/assets/final/demo_assets.txt)
set(GENERATED_DEMO_ASSETS_H ${CMAKE_CURRENT_BINARY_DIR}/src/assets.h)
@@ -170,9 +189,10 @@ add_executable(demo64k
src/util/asset_manager.cc
third_party/glfw3webgpu/glfw3webgpu.c
${GENERATED_DEMO_ASSETS_DATA_CC}
+ ${GENERATED_TIMELINE_CC}
)
-add_dependencies(demo64k generate_demo_assets)
+add_dependencies(demo64k generate_demo_assets generate_timeline)
target_link_libraries(demo64k PRIVATE ${DEMO_LIBS})
target_include_directories(demo64k PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src)
diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md
index 24b1517..5afc7a7 100644
--- a/PROJECT_CONTEXT.md
+++ b/PROJECT_CONTEXT.md
@@ -37,13 +37,30 @@ Incoming tasks in no particular order:
- [x] 9. work on the compact in-line and off-line asset system (@ASSET_SYSTEM.md)
- 10. optimize spectool to remove trailing zeroes from a spec file before saving it
- [x] 11. implement a general [time / timer / beat] system in the demo, for effects timing
-- 12. Implement 'Sequence' and 'Effect' system:
- - `Effect` interface: `start()`, `draw(time, beat, intensity)`, `end()`.
- - `Sequence` manager: Handles a list of potentially overlapping effects.
- - `time` is relative to sequence start. `beat` is a [0..1] pulse. `intensity` is signal strength (e.g. music level).
+- [x] 12. Implement 'Sequence' and 'Effect' system:
+ - `Effect` interface: `init`, `start`, `compute`, `render`, `end`, `priority`.
+ - `Sequence` manager: Handles a list of temporally sorted `SequenceItem`s.
+ - `MainSequence`: Manages global resources (`device`, `queue`) and orchestrates the frame render.
+ - Refactored `gpu.cc` to use `MainSequence`.
+ - Moved concrete effects to `src/gpu/demo_effects.cc`.
## Session Decisions and Current State
+### Sequence & Effect System
+- **Architecture**: Implemented a hierarchical sequencing system.
+ - **Effect**: Abstract base for visual elements. Supports `compute` (physics) and `render` (draw) phases. Idempotent `init` for shared asset loading.
+ - **Sequence**: Manages a timeline of effects with `start_time` and `end_time`. Handles activation/deactivation and sorting by priority.
+ - **MainSequence**: The top-level coordinator. Holds the WebGPU device/queue/surface. Manages multiple overlapping `Sequence` layers (sorted by priority).
+- **Implementation**:
+ - `src/gpu/gpu.cc`: Simplified to be a thin wrapper initializing and driving `MainSequence`.
+ - `src/gpu/demo_effects.h/cc`: Concrete implementations of `HeptagonEffect` and `ParticlesEffect`.
+- **Integration**: The main loop now calculates fractional beats and passes them along with `time` and `aspect_ratio` to the rendering system.
+
+### Debugging Features
+- **Fast Forward / Seek**: Implemented `--seek <seconds>` CLI option.
+ - **Mechanism**: Simulates logic, audio (silent render), and GPU compute (physics) frame-by-frame from `t=0` to `target_time` before starting real-time playback.
+ - **Audio Refactor**: Split `audio_init()` (resource allocation) from `audio_start()` (device callback start) to allow manual `audio_render_silent()` during the seek phase.
+
### Asset Management System:
- **Architecture**: Implemented a C++ tool (`asset_packer`) that bundles external files into hex-encoded C arrays.
- **Lookup**: Uses a highly efficient array-based lookup (AssetRecord) for O(1) retrieval of raw byte data at runtime.
diff --git a/assets/demo.seq b/assets/demo.seq
new file mode 100644
index 0000000..12d70b1
--- /dev/null
+++ b/assets/demo.seq
@@ -0,0 +1,8 @@
+# Main demo timeline
+# Format:
+# SEQUENCE <start_time> <priority>
+# EFFECT <ClassName> <start> <end> <priority> [extra_args...]
+
+SEQUENCE 0.0 0
+ EFFECT HeptagonEffect 0.0 1000.0 0
+ EFFECT ParticlesEffect 0.0 1000.0 1
diff --git a/src/gpu/demo_effects.cc b/src/gpu/demo_effects.cc
index 5fc7c15..869cd12 100644
--- a/src/gpu/demo_effects.cc
+++ b/src/gpu/demo_effects.cc
@@ -300,15 +300,3 @@ void ParticlesEffect::render(WGPURenderPassEncoder pass, float time, float beat,
wgpuRenderPassEncoderDraw(pass, render_pass_.vertex_count,
render_pass_.instance_count, 0, 0);
}
-
-std::shared_ptr<Sequence> create_demo_sequence(WGPUDevice device,
- WGPUQueue queue,
- WGPUTextureFormat format) {
- auto seq = std::make_shared<Sequence>();
- // Overlap them for now to replicate original behavior
- seq->add_effect(std::make_shared<HeptagonEffect>(device, queue, format), 0.0f,
- 1000.0f);
- seq->add_effect(std::make_shared<ParticlesEffect>(device, queue, format),
- 0.0f, 1000.0f);
- return seq;
-}
diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h
index 81321d9..befb1fe 100644
--- a/src/gpu/demo_effects.h
+++ b/src/gpu/demo_effects.h
@@ -34,7 +34,6 @@ private:
GpuBuffer uniforms_;
};
-// Factory
-std::shared_ptr<Sequence> create_demo_sequence(WGPUDevice device,
- WGPUQueue queue,
- WGPUTextureFormat format);
+// Auto-generated function to populate the timeline
+void LoadTimeline(MainSequence &main_seq, WGPUDevice device, WGPUQueue queue,
+ WGPUTextureFormat format);
diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc
index d119e60..e76aecc 100644
--- a/src/gpu/gpu.cc
+++ b/src/gpu/gpu.cc
@@ -417,8 +417,7 @@ void gpu_init(GLFWwindow *window) {
g_main_sequence.init(g_device, g_queue, g_config.format);
- auto seq = create_demo_sequence(g_device, g_queue, g_config.format);
- g_main_sequence.add_sequence(seq, 0.0f, 0);
+ LoadTimeline(g_main_sequence, g_device, g_queue, g_config.format);
}
void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat) {
diff --git a/tools/seq_compiler.cc b/tools/seq_compiler.cc
new file mode 100644
index 0000000..8da4e4e
--- /dev/null
+++ b/tools/seq_compiler.cc
@@ -0,0 +1,130 @@
+// This file is part of the 64k demo project.
+// It implements the sequence compiler tool.
+// Converts a text-based timeline description into C++ code.
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+struct EffectEntry {
+ std::string class_name;
+ std::string start;
+ std::string end;
+ std::string priority;
+ std::string extra_args;
+};
+
+struct SequenceEntry {
+ std::string start_time;
+ std::string priority;
+ std::vector<EffectEntry> effects;
+};
+
+std::string trim(const std::string &str) {
+ size_t first = str.find_first_not_of(" ");
+ if (std::string::npos == first)
+ return str;
+ size_t last = str.find_last_not_of(" ");
+ return str.substr(first, (last - first + 1));
+}
+
+int main(int argc, char *argv[]) {
+ if (argc != 3) {
+ std::cerr << "Usage: " << argv[0] << " <input.seq> <output.cc>\n";
+ return 1;
+ }
+
+ std::ifstream in_file(argv[1]);
+ if (!in_file.is_open()) {
+ std::cerr << "Error: Could not open input file " << argv[1] << "\n";
+ return 1;
+ }
+
+ std::vector<SequenceEntry> sequences;
+ SequenceEntry *current_seq = nullptr;
+
+ std::string line;
+ int line_num = 0;
+ while (std::getline(in_file, line)) {
+ line_num++;
+ std::string trimmed = trim(line);
+ if (trimmed.empty() || trimmed[0] == '#')
+ continue;
+
+ std::stringstream ss(trimmed);
+ std::string command;
+ ss >> command;
+
+ if (command == "SEQUENCE") {
+ std::string start, priority;
+ if (!(ss >> start >> priority)) {
+ std::cerr << "Error line " << line_num
+ << ": SEQUENCE requires <start> <priority>\n";
+ return 1;
+ }
+ sequences.push_back({start, priority, {}});
+ current_seq = &sequences.back();
+ } else if (command == "EFFECT") {
+ if (!current_seq) {
+ std::cerr << "Error line " << line_num
+ << ": EFFECT found outside of SEQUENCE\n";
+ return 1;
+ }
+ std::string class_name, start, end, priority;
+ if (!(ss >> class_name >> start >> end >> priority)) {
+ std::cerr << "Error line " << line_num
+ << ": EFFECT requires <Class> <start> <end> <priority>\n";
+ return 1;
+ }
+
+ // Capture remaining args
+ std::string extra_args;
+ std::getline(ss, extra_args); // Read rest of line
+ // Remove leading whitespace from getline if any (getline reads the space after priority)
+ extra_args = trim(extra_args);
+ if (!extra_args.empty()) {
+ extra_args = ", " + extra_args;
+ }
+
+ current_seq->effects.push_back(
+ {class_name, start, end, priority, extra_args});
+ } else {
+ std::cerr << "Error line " << line_num << ": Unknown command '" << command
+ << "'\n";
+ return 1;
+ }
+ }
+
+ std::ofstream out_file(argv[2]);
+ if (!out_file.is_open()) {
+ std::cerr << "Error: Could not open output file " << argv[2] << "\n";
+ return 1;
+ }
+
+ out_file << "// Auto-generated by seq_compiler. Do not edit.\n";
+ out_file << "#include \"gpu/demo_effects.h\"\n";
+ out_file << "#include \"gpu/effect.h\"\n\n";
+ out_file << "void LoadTimeline(MainSequence& main_seq, WGPUDevice device, "
+ "WGPUQueue queue, WGPUTextureFormat format) {\n";
+
+ for (const auto &seq : sequences) {
+ out_file << " {\n";
+ out_file << " auto seq = std::make_shared<Sequence>();\n";
+ for (const auto &eff : seq.effects) {
+ out_file << " seq->add_effect(std::make_shared<" << eff.class_name
+ << ">(device, queue, format" << eff.extra_args << "), "
+ << eff.start << "f, " << eff.end << "f, " << eff.priority << ");\n";
+ }
+ out_file << " main_seq.add_sequence(seq, " << seq.start_time << "f, "
+ << seq.priority << ");\n";
+ out_file << " }\n";
+ }
+
+ out_file << "}\n";
+
+ std::cout << "Successfully generated timeline with " << sequences.size() << " sequences.\n";
+
+ return 0;
+}