30408ff244
This reverts commit 618f09689d.
276 lines
10 KiB
Markdown
276 lines
10 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**: `justfile` (Just) as a task runner
|
|
- **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
|
|
|
|
- **Tabs for indentation**, 8-column tab width.
|
|
- **Braces** on the same line as control statements (Attach style).
|
|
- **Braces on single-line if statements**: Always use braces, even for single-line bodies:
|
|
```c
|
|
// correct
|
|
if (!buffer) { return; }
|
|
if (!texture) { _abort("alloc failed"); }
|
|
|
|
// wrong
|
|
if (!buffer) return;
|
|
if (!texture) _abort("alloc failed");
|
|
```
|
|
- **Pointers**: `*` against the name, not the type (`PrRhiBuffer *buf`, not `PrRhiBuffer* buf`).
|
|
- **Line width**: 120 columns.
|
|
- **Continuation lines** align to the opening parenthesis.
|
|
- **Return-type alignment**: Within each `// =====` section, align function
|
|
declaration names so the first letter of every function occupies the same
|
|
column. For pointer return types, place `*` directly against the function
|
|
name (no space) and put all alignment padding between the type name and `*`.
|
|
```c
|
|
// correct — * against fn name, padding before *
|
|
PrRhiSwapchain *prRhiCreateSwapchain(…);
|
|
void prRhiDestroySwapchain(…);
|
|
PrRhiSwapchainResult prRhiAcquireNextImage(…);
|
|
```
|
|
|
|
### 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.
|
|
|
|
### Frame-by-frame command batching
|
|
|
|
Commands that execute every frame must avoid arena allocation. Use stack
|
|
arrays with a while-loop to batch operations in fixed-size chunks:
|
|
|
|
```c
|
|
while (count > 0) {
|
|
VkTypeArray batch = wpArrayWithCapacity(VkType, 16, WP_ARRAY_INIT_FILLED);
|
|
u32 batch_size = count < wpArrayCapacity(batch) ? count : (u32)wpArrayCapacity(batch);
|
|
// ... process batch ...
|
|
vkCmd*(cb->handle, ...);
|
|
count -= batch_size;
|
|
first += batch_size;
|
|
}
|
|
```
|
|
|
|
This avoids per-frame arena churn while handling arbitrarily large inputs.
|
|
|
|
### 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.
|
|
|
|
Always use typed array aliases (`WpU64Array`, `PrNodeIdArray`, etc.) rather
|
|
than raw pointers when declaring array variables. Follow the existing typedef
|
|
pattern in the module (`typedef Type *TypeArray`).
|
|
|
|
Typedef pattern:
|
|
- Opaque handles use `**` (pointer-to-pointer)
|
|
- Value types use `*` (contiguous block)
|
|
```c
|
|
typedef PrRhiBuffer **PrRhiBufferArray; // opaque handles → **
|
|
typedef PrRhiColorAttachment *PrRhiColorAttachmentArray; // value types → *
|
|
```
|
|
Group opaque handle arrays first, value type arrays second, separated by a
|
|
blank line.
|
|
|
|
Use named init flags (`WP_ARRAY_INIT_NONE`, `WP_ARRAY_INIT_FILLED`) instead of
|
|
bare `0` — they make the initialisation policy explicit.
|
|
|
|
- `WP_ARRAY_INIT_FILLED`: sets `count = capacity` on allocation. Required when
|
|
you plan to index into the array directly (not via append/push), since the
|
|
array's `count` must reflect valid elements for any downstream use.
|
|
- `WP_ARRAY_INIT_NONE`: leaves `count = 0`. Use when you'll fill the array
|
|
incrementally via `wpArrayAppendCapped` / `wpArrayAppendAlloc`.
|
|
|
|
Use `wpArrayCapacity(arr)`, `wpArrayCount(arr)`, `wpArraySetCount(arr, n)` to
|
|
query and control array state rather than computing sizes manually.
|
|
|
|
### Local/scratch arenas
|
|
|
|
For function-local scratch allocations, use `wpMemArenaAllocatorInitZero` with a
|
|
fixed size rather than a stack buffer + `InitWithBuffer`:
|
|
|
|
```c
|
|
WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16));
|
|
```
|
|
|
|
## Documentation
|
|
|
|
Save research notes and implementation plans as markdown in `documents/`:
|
|
|
|
```
|
|
documents/
|
|
├── ARCHITECTURE.md
|
|
├── RENDERING_HARDWARE_INTERFACE.md
|
|
├── NODE_SYSTEM.md
|
|
├── ROADMAP.md
|
|
├── research/
|
|
│ └── <topic>.md
|
|
└── session-logs/
|
|
└── YYYY-MM-DD.md
|
|
```
|
|
|
|
## Skills
|
|
|
|
Domain-specific conventions are stored as skills in `.opencode/skills/<name>/SKILL.md`.
|
|
These are loaded on-demand by the AI agent when a task matches their description,
|
|
keeping AGENTS.md lean.
|
|
|
|
- **`prism-rhi`** — RHI backend dispatch, by-value descs, file layout
|
|
- **`prism-dag`** — DAG adjacency lists, arena allocation, Kahn's algorithm
|
|
|
|
## 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.
|
|
|
|
### Skill maintenance
|
|
|
|
When you observe the user correcting your output (e.g. formatting, conventions),
|
|
infer the rule and add it to the relevant skill's `SKILL.md`. If no skill
|
|
matches, add a new one. This keeps AGENTS.md focused on project identity and
|
|
critical workflow rules rather than accumulating domain details.
|
|
|
|
### 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.
|