209 lines
7.1 KiB
Markdown
209 lines
7.1 KiB
Markdown
# AGENTS.md — Prism
|
|
|
|
## Project Identity
|
|
|
|
**Prism** is a node-based image compositing tool. Images flow through a directed
|
|
acyclic graph (DAG) of processing nodes (read, blend, colour-grade, etc.) and
|
|
are rendered via GPU shaders.
|
|
|
|
- **Language**: C11 / C++11 (dual-mode, like `src/wapp/`)
|
|
- **GPU API**: Vulkan, abstracted behind a Rendering Hardware Interface (RHI)
|
|
- **Shading language**: Slang, stored in external `.slang` files under `src/shaders/`
|
|
- **Build**: TBD — either a standalone shell build script or a `justfile` (Just)
|
|
- **Dependencies**: `src/wapp/` (local utility library, already vendored)
|
|
|
|
## Coding Conventions
|
|
|
|
All code follows the patterns established in `src/wapp/`. The project prefix is
|
|
`pr` (functions/macros) / `Pr` (types).
|
|
|
|
### Naming
|
|
|
|
| Category | Convention | Examples |
|
|
|-----------------------|------------------------|---------------------------------|
|
|
| Types | `Pr` + PascalCase | `PrNode`, `PrGraph`, `PrImage` |
|
|
| Functions | `pr` + PascalCase | `prGraphCreate`, `prNodeEval` |
|
|
| Macros | `pr` + PascalCase | `prArrayOf`, `prNodeType` |
|
|
| Enum constants | `PR_` + SCREAMING_SNAKE| `PR_NODE_BLEND`, `PR_OK` |
|
|
| Internal/static funcs | `_` + camelCase | `_resolveTopology`, `_execCmd` |
|
|
| File-scope globals | `_` + camelCase | `_default_allocator` |
|
|
| File-scope constants | SCREAMING_SNAKE | `MAX_NODE_NAME` |
|
|
| Variables | snake_case | `vertex_count`, `edge_node` |
|
|
|
|
- **Variable names**: Prefer descriptive names (e.g. `vertex_count` over `vc`, `edge_node` over `en`). Short names (`i`, `j`, `n`) are acceptable only in tight loop counters or trivial index variables. Avoid single-letter names in non-trivial scopes, and avoid abbreviated names that require the reader to hold a mental dictionary.
|
|
|
|
### Formatting
|
|
|
|
- **Indentation**: tabs (no spaces). Tab width is a viewer preference.
|
|
- **Braces**: always required after `if`, `else`, `for`, `while`, `do` — even
|
|
when the body is a single statement. This avoids ambiguity and makes diffs
|
|
cleaner.
|
|
|
|
```c
|
|
// correct
|
|
if (condition) {
|
|
do_thing();
|
|
}
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
process(i);
|
|
}
|
|
|
|
// wrong — no braces, spaces instead of tabs
|
|
if (condition)
|
|
do_thing();
|
|
```
|
|
|
|
### Storage qualifiers
|
|
|
|
Use the `wp_extern` / `wp_intern` / `wp_persist` aliases from wapp:
|
|
|
|
```c
|
|
wp_extern void prGraphInit(PrGraph *g); // extern linkage
|
|
wp_intern void _helper(void); // static (file-scope)
|
|
wp_persist u32 _counter; // static (file-scope, mutable)
|
|
```
|
|
|
|
### Structs
|
|
|
|
POD structs with typedef, following `wapp/base/` patterns. Functions operate
|
|
on structs by pointer rather than through vtables:
|
|
|
|
```c
|
|
typedef struct PrNode PrNode;
|
|
struct PrNode {
|
|
PrNodeType type;
|
|
u32 input_count;
|
|
PrNode **inputs;
|
|
// ...
|
|
};
|
|
|
|
void prNodeInit(PrNode *n, PrAllocator *alloc);
|
|
void prNodeEval(PrNode *n, PrRhiCmdList *cl);
|
|
void prNodeDestroy(PrNode *n, PrAllocator *alloc);
|
|
```
|
|
|
|
### Memory management
|
|
|
|
Use wapp allocators (`WpAllocator`, arena-based). Stack-allocate where
|
|
possible; pass allocators explicitly.
|
|
|
|
```c
|
|
PrGraph *prGraphCreate(PrAllocator *alloc);
|
|
void prGraphDestroy(PrGraph *g, PrAllocator *alloc);
|
|
```
|
|
|
|
### File organisation
|
|
|
|
One `.h` / `.c` pair per module, grouped in subdirectories by subsystem.
|
|
A top-level umbrella header re-exports everything (see `src/wapp/wapp.h`).
|
|
|
|
```
|
|
src/prism/prism.h ← umbrella include
|
|
src/prism/core/ ← graph, node types, DAG
|
|
src/prism/rhi/ ← rendering hardware interface
|
|
src/prism/node/ ← node implementations
|
|
src/prism/app/ ← entry point, window, main loop
|
|
```
|
|
|
|
Shaders live in a flat-ish `src/shaders/` tree:
|
|
|
|
```
|
|
src/shaders/blit.slang
|
|
src/shaders/blend.slang
|
|
src/shaders/common/ ← shared include files
|
|
```
|
|
|
|
### Data-oriented design
|
|
|
|
For hot paths (per-frame graph evaluation, image transfers, shader dispatch)
|
|
prefer SoA layouts, batch processing, and minimise pointer chasing. Keep
|
|
the DAG in contiguous arrays (e.g. adjacency lists packed in flat buffers)
|
|
rather than individually allocated linked structures.
|
|
|
|
### Graph / adjacency lists
|
|
|
|
Adjacency list nodes must be **separately allocated from the vertex array**.
|
|
Never use the vertex struct itself as a linked-list node in another vertex's
|
|
adjacency chain — that shares the `next` pointer between two roles (vertex state
|
|
vs. edge linking) and causes edges to splice into unintended chains.
|
|
|
|
```c
|
|
// correct — per-edge copy on the arena
|
|
static void addEdge(PrGraph *g, const WpAllocator *alloc, u64 from, u64 to) {
|
|
PrVertex *src = &g->vertices[from];
|
|
PrVertex *dst = wpMemAllocatorAlloc(alloc, sizeof(PrVertex));
|
|
if (!dst) { /* handle OOM */ return; }
|
|
dst->id = g->vertices[to].id;
|
|
dst->value = g->vertices[to].value;
|
|
dst->next = src->next;
|
|
src->next = dst;
|
|
}
|
|
|
|
// wrong — reuses the destination vertex as the list node
|
|
static void addEdge_bad(PrGraph *g, u64 from, u64 to) {
|
|
PrVertex *src = &g->vertices[from];
|
|
PrVertex *dest = &g->vertices[to];
|
|
dest->next = src->next;
|
|
src->next = dest; // overwrites dest->next used elsewhere
|
|
}
|
|
```
|
|
|
|
Arena bump allocation (`wpMemAllocatorAlloc`) is the natural fit for building
|
|
graph structures: per-edge nodes live in the arena and are freed in one shot
|
|
when the arena is destroyed.
|
|
|
|
Always NULL-check the result of `wpMemAllocatorAlloc` — even arena allocators
|
|
can fail if the backing buffer is exhausted.
|
|
|
|
### WpArray usage
|
|
|
|
| Scenario | API | Size must be… |
|
|
|----------|-----|---------------|
|
|
| Stack (local, short-lived) | `wpArrayWithCapacity` | compile-time constant |
|
|
| Heap (arena-backed) | `wpArrayAllocCapacity` | runtime value |
|
|
|
|
`wpArrayWithCapacity` creates a VLA-like compound literal on the stack —
|
|
passing a runtime variable triggers undefined behaviour and compiler warnings.
|
|
Use `wpArrayAllocCapacity` with an arena allocator for runtime sizes.
|
|
|
|
Initialise arrays with `WP_ARRAY_INIT_FILLED` to set `count = capacity`
|
|
immediately, allowing direct indexing.
|
|
|
|
## Documentation
|
|
|
|
Save research notes and implementation plans as markdown in `documents/`:
|
|
|
|
```
|
|
documents/
|
|
├── ARCHITECTURE.md
|
|
├── RENDERING_HARDWARE_INTERFACE.md
|
|
├── NODE_SYSTEM.md
|
|
├── ROADMAP.md
|
|
└── research/
|
|
└── vulkan-baseline.md
|
|
```
|
|
|
|
## Workflows for AI agents
|
|
|
|
### Research / planning
|
|
|
|
1. Save findings to `documents/research/<topic>.md`.
|
|
2. When asked for a plan, write it to `documents/<PLAN_NAME>.md` and present
|
|
a summary; iterate on the plan before writing any code.
|
|
3. Only start implementing after the plan is approved.
|
|
|
|
### Learning from edits
|
|
|
|
The user may edit code produced by AI agents. When this happens, infer the
|
|
reason for the change and update AGENTS.md with any new conventions, patterns,
|
|
or constraints that the edit reveals. This keeps the guide aligned with the
|
|
user's evolving preferences.
|
|
|
|
### Committing
|
|
|
|
Only commit when explicitly asked. When asked:
|
|
- Stage only intended files.
|
|
- Write a short, conventional commit message in present tense.
|
|
- Never amend, force-push, or create PRs without a request.
|