diff --git a/documents/research/dags.md b/documents/research/dags.md new file mode 100644 index 0000000..4b21f3b --- /dev/null +++ b/documents/research/dags.md @@ -0,0 +1,184 @@ +# DAGs for Node-Based Compositing + +## What is a DAG? + +A **Directed Acyclic Graph (DAG)** is a graph where: + +- **Directed** — every edge has a direction (A → B means "A feeds into B") +- **Acyclic** — no path forms a cycle; you cannot loop back to a node you've already visited + +In compositing, nodes are the vertices and image/data flow defines the edges. A **Read** node feeds into a **Blur** node, which feeds into a **Merge** node, etc. + +## Why DAGs for Compositing + +| Property | Benefit | +|---|---| +| Non-linear | Any node can be tweaked without rebuilding the whole comp | +| Dependency-driven | Only re-evaluate nodes whose inputs changed ("dirty propagation") | +| Parallelism | Independent branches can run concurrently | +| Modular | Nodes are self-contained; easy to add new types | + +### Real-world examples + +- **Nuke** (Foundry) — the industry standard; everything is a DAG +- **Fusion** (Blackmagic) — same concept, node graph as the primary interface +- **Blender Compositor** — uses a dependency graph internally +- **Shadertoy / MaterialX** — similar DAG concepts for shader/node graphs + +## Core Algorithms + +**BFS** (Breadth-First Search) explores a graph level by level — you visit all neighbours of a node before moving to their neighbours. Useful for shortest paths and spreading outward. + +**DFS** (Depth-First Search) goes deep first — you follow one path as far as it goes, then backtrack. Useful for cycle detection, pathfinding, and topological sort. + +### Topological Sort + +A **topological ordering** of a DAG is a linear sequence of all vertices such that for every edge u → v, u appears before v. This is the evaluation order for a node graph — you must process a node's inputs before processing the node itself. + +There are two standard approaches, one based on each traversal strategy: + +### 1. Kahn's Algorithm (BFS-based) + +Uses **in-degree** (number of incoming edges) to determine which nodes are ready to execute. + +``` +1. Compute in-degree for every node +2. Queue all nodes with in-degree == 0 (no dependencies) +3. While queue is not empty: + a. Dequeue node n, add to result order + b. For each downstream node m of n: + - Decrement m's in-degree + - If m's in-degree reaches 0, enqueue m +4. If result count != node count → there is a cycle +``` + +**Why Kahn's for compositing:** +- Naturally detects cycles (a graph editor must prevent the user from creating cycles) +- Gives you ready-to-evaluate layers (all in-degree-0 nodes at a given step can run in parallel) +- O(V + E) time, O(V) space +- Easy to implement with arrays + +### 2. DFS-based (Post-order) + +``` +1. For each unvisited node, run DFS +2. After visiting all descendants of a node, prepend it to the result +``` + +Simpler to code but less practical for incremental / parallel evaluation. Used more for DAG verification in build systems. + +## Data Structures for a DAG + +### Adjacency List (recommended for Prism) + +Store the graph as two flat arrays: + +```c +// Node i's outgoing edges are adjacency[i] .. adjacency[i + 1] +u32 *adjacency; // flat list of edge destinations +u32 *adjacency_begin; // start index into adjacency for each node +u32 node_count; +u32 edge_count; +``` + +Or simpler: each node stores its outputs: + +```c +typedef struct PrNode PrNode; +struct PrNode { + u32 id; + PrNodeType type; + u32 input_count; // number of inputs (incoming edges) + u32 input_nodes[4]; // fixed-size or pointer to array + u32 output_count; // number of downstream nodes + u32 *output_nodes; // allocated with arena + // ... data for this node type +}; +``` + +For a **data-oriented** approach in hot paths (graph evaluation), pack fields into parallel arrays: + +```c +// SoA layout for evaluation +u8 *node_types; // PrNodeType for each node +u32 *in_degrees; // current in-degree (Kahn's state) +u32 *topo_order; // result of topological sort +``` + +### Struct-of-Arrays (SoA) Layout + +For the graph evaluation hot path, you can use parallel arrays instead of an array of structs: + +```c +struct PrGraphEvalState { + u32 node_count; + u32 *topo_order; // [0..node_count-1] in eval order + u32 *in_degrees; // temp space for Kahn's + u8 *dirty_flags; // per-node dirty bit + u32 *output_counts; + u32 **output_lists; // adjacency +}; +``` + +This keeps only the data needed for traversal in cache-friendly contiguous memory. + +## Incremental / Dirty Evaluation + +For interactive use, re-running the full topological sort every frame is wasteful. Instead: + +1. When a node's parameter changes, mark it **dirty** +2. Propagate the dirty flag downstream (BFS along edges) +3. Only re-evaluate dirty nodes in topological order + +Alternatively, skip dirty propagation and just always eval in topo order — each node checks if its inputs are dirty or if its own parameters changed. Simpler, but does more work. + +## Cycles in a Graph Editor + +A graph editor must **prevent** the user from creating cycles in real time. Approaches: + +1. **Check on each edge creation:** before adding edge A → B, check if there's already a path from B to A (DFS from B). O(V + E) per edge add. +2. **Incremental cycle detection:** more sophisticated data structures for dynamic graphs. Probably overkill for V1. +3. **Kahn's validation:** Run Kahn's after every edit; if it doesn't produce a full ordering, reject the edit. + +Approach 1 (DFS reachability test) is the simplest for V1. + +## Evaluation Pipeline + +``` +User edits graph + │ + ▼ +Validate no cycles ← (reject edit if cycle detected) + │ + ▼ +Topological sort ← (Kahn's algorithm → list of node IDs) + │ + ▼ +Mark dirty nodes ← (only nodes downstream of changes) + │ + ▼ +For each node in topo order: + If node is dirty: + Gather input images (from upstream node outputs) + Execute node (CPU or dispatch GPU shader) + Store output image + │ + ▼ +Render final output → display +``` + +## Key Takeaways for Prism + +1. **Kahn's algorithm** is the right choice — simple, O(V+E), built-in cycle detection, parallel-layer grouping +2. **Adjacency list** with flat arrays for the graph structure +3. **Evaluate in topological order**; skip clean nodes for efficiency +4. **Cycle check on edge creation** via DFS from the target node +5. **SoA layout** for evaluation state if profiling shows cache misses on the hot path +6. Node types (Read, Blur, Merge, etc.) can use a `PrNodeType` enum with a function dispatch table, or a union of type-specific data in the node struct + +## References + +- Kahn, A. B. (1962). "Topological sorting of large networks." *Communications of the ACM* +- Cormen et al., *Introduction to Algorithms*, 3rd ed., Ch. 22.4 (Topological Sort) +- Foundry Nuke documentation: [https://learn.foundry.com/nuke](https://learn.foundry.com/nuke) +- Taskflow C++ library: [https://taskflow.github.io](https://taskflow.github.io) diff --git a/documents/research/node-graph-ui-with-imgui.md b/documents/research/node-graph-ui-with-imgui.md new file mode 100644 index 0000000..ba76a92 --- /dev/null +++ b/documents/research/node-graph-ui-with-imgui.md @@ -0,0 +1,212 @@ +# Node Graph UI with DearImGui — Research + +## Overview + +Three main approaches exist for building a node graph UI with DearImGui: +1. Use a standalone library like **ImNodes** or **imgui-node-editor** +2. Build from scratch using ImGui's draw list API +3. Use a hybrid — custom canvas with ImGui widgets + +--- + +## 1. Existing Libraries + +### 1.1 imnodes (Nelarius) + +- **Stars**: ~2.4k | **License**: MIT | **Status**: Active (last push 2024) +- **Files**: `imnodes.h`, `imnodes_internal.h`, `imnodes.cpp` — drop-in, no deps beyond ImGui +- **API style**: Immediate-mode, mirrors ImGui idioms + +```cpp +imnodes::BeginNodeEditor(); +imnodes::BeginNode(node_id); +imnodes::BeginNodeTitleBar(); +ImGui::Text("Node Name"); +imnodes::EndNodeTitleBar(); +imnodes::BeginInputAttribute(pin_id); +ImGui::Text("input"); +imnodes::EndAttribute(); +imnodes::BeginOutputAttribute(pin_id); +ImGui::Text("output"); +imnodes::EndAttribute(); +imnodes::EndNode(); +imnodes::EndNodeEditor(); +``` + +**Strengths:** +- Minimal, dependency-free, easy to vendor +- True immediate-mode — user owns all state +- Pins auto-align with embedded ImGui widgets +- Simple `ImNodes::Link(id, from, to)` API + +**Weaknesses:** +- Less feature-rich (no built-in minimap, no grouping, limited theming) +- No built-in serialization of layout +- Slower development velocity + +**Under the hood:** +- Uses `ImDrawList::ChannelsSplit()` to layer node backgrounds behind UI +- Pins are detected via `ImGui::BeginGroup` bounding box capture +- Link picking uses hierarchical bezier subdivision + +### 1.2 imgui-node-editor (thedmd / Michal Cichon) + +- **Stars**: ~4.4k | **License**: MIT | **Status**: Active +- **Files**: `imgui_node_editor.h/.cpp` + `imgui_canvas.h/.cpp` — also drop-in +- **API style**: retained-state editor context, user draws content + +```cpp +ax::NodeEditor::Begin("Editor"); +ax::NodeEditor::BeginNode(node_id); +ax::NodeEditor::BeginPin(pin_id, ax::NodeEditor::PinKind::Input); +ImGui::Text("input"); +ax::NodeEditor::EndPin(); +ax::NodeEditor::EndNode(); +ax::NodeEditor::End(); +``` + +**Strengths:** +- Rich feature set: zoom/pan, minimap, selection, context menus, copy/paste +- Blueprint-UE4-inspired default theme +- Bézier curve links with flow animation +- Configurable zoom levels, drag/navigate/select button mapping +- `ImGuiEx::Canvas` can be used independently for custom infinite-workspace UIs +- Built-in serialization callbacks (`SaveSettings`/`LoadSettings`) + +**Weaknesses:** +- Heavier than imnodes — more code, larger API surface +- Editor context is a retained object (less "pure" immediate mode) +- Can conflict with ImGui's own ID stack during complex widget embedding + +**Key API patterns:** + +| Concern | API | +|---|---| +| Create link | `BeginCreate()` / `QueryNewLink()` / `AcceptNewItem()` / `EndCreate()` | +| Delete | `BeginDelete()` / `QueryDeletedLink()` / `AcceptDeletedItem()` / `EndDelete()` | +| Suspend for popups | `Suspend()` / `Resume()` — pops out of canvas coordinate space | +| Styling | `PushStyleColor()` / `PushStyleVar()` — 20+ style variables | + +### 1.3 ImNodeFlow (Fattorino) + +- **Stars**: Newer (2024–2025) | **License**: MIT +- Even more feature-packed: node categories, commenting, layout algorithms +- Still maturing; less battle-tested than the two above + +> **Recommendation for Prism**: start with **imgui-node-editor** if we want a polished editor quickly, or **imnodes** if we want minimal deps and full state control. The custom approach (next section) is best if we have very specific rendering needs. + +--- + +## 2. Custom Node Graph from Scratch + +Building a node graph manually using `ImDrawList` gives maximum control but requires handling: + +### 2.1 Canvas / Coordinate System + +An infinite-zoom canvas requires: +- An offset (`ImVec2`) and scale (`float`) transform +- Conversion functions between screen ↔ canvas space +- Clipping to the parent ImGui window + +The `imgui-node-editor` library includes an `ImGuiEx::Canvas` utility that handles this standalone — it can be extracted and reused. + +### 2.2 Rendering Nodes (DrawList) + +Nodes are typically rendered in layers: +1. **Background layer**: grid dots/lines, selection rectangle +2. **Node bodies**: rounded rectangles (`AddRectFilled`) +3. **Node borders**: rect strokes, optionally thicker on hover/select +4. **Pins**: small circles or squares on left/right edges +5. **Links**: cubic Bézier curves between pin centers +6. **UI overlay**: selection handles, context menus + +Use `ImDrawList::ChannelsSplit()` for correct z-ordering when mixing drawn shapes with ImGui widgets. + +### 2.3 Interaction Handling + +| Interaction | Implementation | +|---|---| +| Pan | Track middle-mouse drag → modify canvas offset | +| Zoom | Mouse wheel → modify scale (clamp to range, center on cursor) | +| Drag node | Hit-test node bodies (invis buttons or rect test), track delta → update node position | +| Select | Rectangular marquee — track shift+drag → compute selection rect → test intersection with node rects | +| Create link | Detect drag from pin, draw preview bezier, test against other pins on release | +| Delete | Keyboard shortcut, query selection, or context menu | + +### 2.4 Link-Picking (Bezier Hit Test) + +Cubic Bézier curves require a hierarchical hit test: +1. Subdivide curve into N segments +2. Find segment closest to mouse cursor +3. Recursively subdivide that segment +4. Return hit if distance < threshold + +## 3. Architecture Patterns + +### 3.1 Data Model vs. View Separation + +From Guillaume Boissé's RogueEngine post: +- Define a **data model** independent of UI: `PrNode`, `PrGraph`, `PrPin` +- The data model is used both by the runtime (graph evaluation) and the editor (UI rendering) +- This enables easy serialization, undo/redo, and multi-context editing + +### 3.2 Immediate-Mode Node Rendering Loop + +``` +For each node in graph: + BeginNode(node.id) + Render node title bar (colored rect + text) + For each input pin: + BeginInputPin(pin.id) + Render ImGui widget (e.g. DragFloat, ColorEdit) + EndInputPin() + For each output pin: + BeginOutputPin(pin.id) + Render ImGui widget + EndOutputPin() + EndNode() + +For each link in graph: + DrawBezierLink(from_pos, to_pos, color, thickness) +``` + +Positions are stored per-node in user state and updated on drag. + +### 3.3 DrawList Channels (Z-Order) + +```cpp +draw_list->ChannelsSplit(3); +draw_list->ChannelsSetCurrent(0); // Background: grid, selection rect +// ... draw nodes, pins, links +draw_list->ChannelsSetCurrent(1); // UI: ImGui widgets inside nodes +// ... BeginNode/EndNode calls +draw_list->ChannelsSetCurrent(2); // Foreground: tooltips, drag previews +draw_list->ChannelsMerge(); +``` + +### 3.4 Undo/Redo + +Simplest viable approach (from RogueEngine / @voxagonlabs): serialize the entire project state on every change. Store snapshots in an undo stack. Works well for small-to-medium projects (node graphs are typically small data). + +## 4. Summary Comparison + +| Criteria | imnodes | imgui-node-editor | Custom | +|---|---|---|---| +| Integration effort | Copy 3 files | Copy 6-8 files | Full implementation | +| Feature depth | Basic | Rich (minimap, flow, groups, copy/paste) | Whatever you build | +| Immediate mode | Yes | Partial (retained context) | Yes | +| Performance | High | High | Depends on impl | +| Styling control | Minimal | Extensive | Full control | +| Serialization | None | Built-in callbacks | Build your own | +| Community / maturity | Mature, stable | Mature, active | N/A | + +## 5. References + +- **imnodes**: https://github.com/Nelarius/imnodes +- **imgui-node-editor**: https://github.com/thedmd/imgui-node-editor +- **ImNodeFlow**: https://github.com/Fattorino/ImNodeFlow +- **Blog post — Visual node graph with ImGui**: https://gboisse.github.io/posts/node-graph/ +- **Blog post — Writing imnodes**: https://nelari.us/post/imnodes +- **ImGui issue #306** (node editor discussion): https://github.com/ocornut/imgui/issues/306 +- **ImGui useful extensions wiki**: https://github.com/ocornut/imgui/wiki/Useful-Extensions +- **Voxagon undo/redo**: https://blog.voxagon.se/2018/07/10/undo-for-lazy-programmers.html