diff --git a/.gitignore b/.gitignore index 6a18340..00b2e70 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ build compile_commands.json .vscode *.dSYM -assets/shaders +src/vendor/ktx/build scratchpad/** !scratchpad/**/ !scratchpad/**/*.h diff --git a/AGENTS.md b/AGENTS.md index 998596e..c1645ae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,12 +94,6 @@ void prNodeDestroy(PrNode *n, PrAllocator *alloc); Use wapp allocators (`WpAllocator`, arena-based). Stack-allocate where possible; pass allocators explicitly. -**Never use libc for memory or file I/O.** wapp always takes precedence: -- `wpMemAllocatorAlloc` / `wpMemAllocatorFree` instead of `malloc` / `free` -- `wpFileOpen` / `wpFileRead` / `wpFileClose` instead of `fopen` / `fread` / `fclose` - -For one-shot loads (e.g. SPIR-V at init), use `&_G_RHI_CONTEXT.allocator`. - ```c PrGraph *prGraphCreate(PrAllocator *alloc); void prGraphDestroy(PrGraph *g, PrAllocator *alloc); diff --git a/documents/TEXTURE_POOL_AND_NODE_EVAL.md b/documents/TEXTURE_POOL_AND_NODE_EVAL.md deleted file mode 100644 index 8a72e07..0000000 --- a/documents/TEXTURE_POOL_AND_NODE_EVAL.md +++ /dev/null @@ -1,554 +0,0 @@ -# Plan: Texture Pool + Node Evaluation - -## Goal - -Design the texture pool and node-to-shader dispatch so the node DAG doubles as the -frame graph. Each node type maps to a single Slang shader (no fusion). The texture -pool enables concurrent branches by allowing multiple intermediate textures to -coexist. - ---- - -## 1. Pool Allocator - -### 1.1 Purpose - -A reusable pool allocator for fixed-size blocks. This replaces the ad-hoc -`PrPool` in scratchpad/dag.c and can be used for any fixed-size allocation -throughout the project: node structs, edge structs, texture slots, descriptor -sets, etc. - -Lives in `src/prism/allocators/`, **not** in wapp. wapp is vendored and may be -replaced — the pool allocator must not be part of it. - -The pool owns its memory. No external allocator is passed — the pool allocates -blocks internally via wapp OS allocation and grows on demand when free slots -run out. - -### 1.2 Design - -The pool manages fixed-size slots arranged in contiguous blocks. Free slots are -tracked via an intrusive free list (first `sizeof(void*)` bytes of each free -slot hold a pointer to the next free slot). When the free list is empty, the -pool allocates a new block of `block_slots` slots and carves them into the -free list. - -```c -typedef struct PrPool PrPool; - -struct PrPool { - void **blocks; // array of allocated block pointers (for destroy) - u64 block_count; // number of allocated blocks - u64 block_cap; // capacity of blocks array - void *free_list; // intrusive free list head - u64 slot_size; // user-requested slot size - u64 alloc_size; // actual slot size used internally (>= slot_size, >= sizeof(void*)) - u64 block_slots; // slots per block - u64 total; // total slots ever allocated (diagnostics) - u64 active; // currently in use (diagnostics) -}; -``` - -### 1.3 API - -```c -// Initialise a pool. -// slot_size: fixed size of each slot -// initial_slots: starting capacity in slots (also used as block size) -void prPoolInit(PrPool *pool, u64 slot_size, u64 initial_slots); - -// Allocate one slot. Grows by a new block if the free list is empty. -// Returns NULL only on allocation failure. -void *prPoolAlloc(PrPool *pool); - -// Return a slot to the pool's free list. Safe no-op on NULL. -void prPoolFree(PrPool *pool, void *slot); - -// Free all blocks and zero the pool. -void prPoolDestroy(PrPool *pool); - -// Diagnostics -u64 prPoolTotalSlots(const PrPool *pool); -u64 prPoolActiveSlots(const PrPool *pool); -``` - -### 1.4 Behavior - -| Operation | Implementation | -|-----------|---------------| -| `prPoolAlloc` | Pop from free list if non-empty, otherwise allocate a new block of `block_slots` slots via wapp OS allocation, link it into the `blocks` array, carve it into the free list, and pop. | -| `prPoolFree` | Push slot onto the intrusive free list. Safe no-op on NULL. | -| `prPoolDestroy` | Free every block in the `blocks` array, free the array itself, zero the struct. | - -Block growth: each new block has `block_slots` slots (same size as the initial -block). The minimum block size is 4096 bytes — if `slot_size * initial_slots` -is smaller, `block_slots` is rounded up to the nearest multiple of `slot_size` -that meets the minimum. The `blocks` array starts at capacity 4 and doubles -when full. - -### 1.5 Usage examples - -```c -// Edge pool (replaces PrPool in scratchpad/dag.c): -PrPool edge_pool; -prPoolInit(&edge_pool, sizeof(PrGraphEdge), 64); -PrGraphEdge *edge = prPoolAlloc(&edge_pool); -prPoolFree(&edge_pool, edge); -prPoolDestroy(&edge_pool); - -// Texture slot pool: -PrPool tex_pool; -prPoolInit(&tex_pool, sizeof(PrTextureSlot), 16); -PrTextureSlot *slot = prPoolAlloc(&tex_pool); -prPoolDestroy(&tex_pool); -``` - ---- - -## 2. Texture Pool - -### 2.1 Purpose - -Intermediate textures (node outputs) need GPU resources. The texture pool manages -a set of textures that are reused across graph evaluations. Without a pool, a -linear chain of N nodes would need N textures. With refcount-based reuse, -textures are returned to the pool as soon as all their consumers have executed, -keeping the peak live count low. - -### 2.2 Data structures - -```c -typedef struct PrTextureSlot { - PrRhiTexture *texture; // the GPU texture (SAMPLED | COLOR_ATTACHMENT) - u32 refcount; // how many downstream nodes still need to read this - b8 in_use; // currently assigned to a node's output -} PrTextureSlot; - -typedef struct PrTexturePool { - PrPool slot_pool; // pool allocator for PrTextureSlot structs - PrTextureSlot *slots; // flat array for iteration (backed by slot_pool) - u32 count; // number of allocated slots - u32 max; // hard cap (never allocate beyond this) - u32 width; // texture width (matches window) - u32 height; // texture height (matches window) -} PrTexturePool; -``` - -All pool textures are **RGBA16F, SAMPLED | COLOR_ATTACHMENT**. Any free slot works -for any node — no format/dimension matching needed. - -The `slot_pool` is a `PrPool` allocator for `PrTextureSlot` structs. The `slots` -pointer provides flat-array access for iteration during evaluation. When the pool -grows, a new batch of slots is allocated via the pool allocator and the flat -array is extended. - -### 2.3 Lifecycle - -``` -prTexturePoolInit(pool, device, initial_capacity, max, width, height) - → creates pool allocator, allocates initial slot array - -prTexturePoolReset(pool) - → marks all slots as free, zeroes refcounts (called once per frame) - -prTexturePoolAcquire(pool, device) -> PrTextureSlot* - → returns a free slot (in_use = true) - → if no free slot: allocate new slot + GPU texture, grow array - → if max reached: abort with diagnostic message - -prTexturePoolRelease(pool, slot) - → marks slot as free (in_use = false) - → called when refcount hits 0 - -prTexturePoolDestroy(pool, device) - → destroys all GPU textures, destroys pool allocator -``` - -### 2.4 Allocation strategy (growth) - -The pool does **not** pre-allocate all textures upfront. Instead: - -1. Start with `initial_capacity` textures (e.g., 16) -2. When all slots are occupied and a new one is needed, allocate a batch of - `GROWTH_BATCH` (e.g., 8) additional textures -3. Never exceed `max` (e.g., 128) -4. If `max` is reached, abort with: `"texture pool exhausted: N in use, max M"` - -Growth is amortized (batch allocation) and the pool never shrinks. The `count` -monotonically increases as textures are allocated on demand. - -**Why growth instead of fixed pre-allocation:** -- Small graphs don't pay for 64 unused textures -- Complex graphs can grow beyond the initial allocation -- The hard cap prevents unbounded memory use -- vkCreateImage is only called when actually needed - -### 2.5 Refcount management - -Before evaluation, compute the **initial refcount** for each node's output: - -``` -refcount[node] = out_degree(node) // number of outgoing edges -``` - -During evaluation, when a node executes and reads an input texture: -``` -input_slot->refcount -= 1 -if (input_slot->refcount == 0): - prTexturePoolRelease(pool, input_slot) -``` - -This naturally handles: -- **Linear chains**: A→B→C. A's output refcount=1, freed after B executes. -- **Fan-out**: A→B, A→C. A's output refcount=2, freed after both B and C execute. -- **Fan-in**: B→D, C→D. B and C have independent refcounts, freed independently. - -### 2.6 Texture dimensions - -Pool textures are created at the **window/swapchain resolution**. All nodes -operate at this resolution. If a node needs a different resolution (e.g., a -half-resolution blur), it would need a separate mechanism — out of scope for V1. - ---- - -## 3. Node-to-Shader Mapping - -### 3.1 Type registry - -A static table maps `PrNodeType` → shader modules + pipeline + resource -signatures: - -```c -typedef enum PrShaderType { - PR_SHADER_TYPE_FRAGMENT, // fullscreen triangle, per-pixel - PR_SHADER_TYPE_COMPUTE, // dispatch, shared memory -} PrShaderType; - -typedef struct PrNodeTypeEntry { - PrNodeType type; - PrShaderType shader_type; - - // shaders (pre-compiled SPIR-V, built from .slang via slangc) - const char *vertex_shader_path; // NULL for compute - const char *fragment_shader_path; // NULL for compute - const char *compute_shader_path; // NULL for fragment - - // pipeline (created at init, cached here) - PrRhiPipeline *pipeline; - - // resource signature - u32 input_count; // number of texture inputs (1 for blur, 2 for blend) - u32 output_count; // always 1 for V1 - - // descriptor set layout (created at init) - PrRhiDescriptorSetLayout *set_layout; - - // push constant size (bytes) - u32 push_constant_size; -} PrNodeTypeEntry; -``` - -### 3.2 Registry instance - -```c -wp_persist PrNodeTypeEntry _node_type_table[COUNT_NODE_TYPES] = { - [PR_NODE_TYPE_READ] = { - .type = PR_NODE_TYPE_READ, - .shader_type = PR_SHADER_TYPE_FRAGMENT, - .vertex_shader_path = "assets/shaders/blit.vert.spv", - .fragment_shader_path= "assets/shaders/read.frag.spv", - .input_count = 0, - .output_count = 1, - .push_constant_size = 0, - }, - [PR_NODE_TYPE_BLUR] = { - .type = PR_NODE_TYPE_BLUR, - .shader_type = PR_SHADER_TYPE_FRAGMENT, - .vertex_shader_path = "assets/shaders/blit.vert.spv", - .fragment_shader_path= "assets/shaders/blur.frag.spv", - .input_count = 1, - .output_count = 1, - .push_constant_size = sizeof(PrBlurPushConstants), - }, - [PR_NODE_TYPE_GRADE] = { - .type = PR_NODE_TYPE_GRADE, - .shader_type = PR_SHADER_TYPE_FRAGMENT, - .vertex_shader_path = "assets/shaders/blit.vert.spv", - .fragment_shader_path= "assets/shaders/grade.frag.spv", - .input_count = 1, - .output_count = 1, - .push_constant_size = sizeof(PrGradePushConstants), - }, - [PR_NODE_TYPE_BLEND] = { - .type = PR_NODE_TYPE_BLEND, - .shader_type = PR_SHADER_TYPE_FRAGMENT, - .vertex_shader_path = "assets/shaders/blit.vert.spv", - .fragment_shader_path= "assets/shaders/blend.frag.spv", - .input_count = 2, - .output_count = 1, - .push_constant_size = sizeof(PrBlendPushConstants), - }, -}; -``` - -### 3.3 Shader loading - -Shaders are written in Slang (`src/shaders/*.slang`) and compiled to SPIR-V as -a build step via `slangc`. The `.spv` files are output to `assets/shaders/`. At -init, the application loads pre-compiled SPIR-V directly: - -``` -for each entry in _node_type_table: - load vertex shader SPIR-V from .spv file - load fragment/compute shader SPIR-V from .spv file - create PrRhiShader handles - create descriptor set layout (input_count combined image samplers) - create pipeline layout (set layout + push constant range) - create pipeline (vertex + fragment stages, dynamic rendering) - cache everything in the entry -``` - -### 3.4 Shaders per node type - -| Node | Shader | Inputs | Push constants | -|------|--------|--------|----------------| -| READ | `read.frag.spv` | 0 (samples from KTX texture loaded separately) | — | -| BLUR | `blur.frag.spv` | 1 input texture | `f32 radius` | -| GRADE | `grade.frag.spv` | 1 input texture | `f32 gain, f32 lift, f32 gamma` | -| BLEND | `blend.frag.spv` | 2 input textures | `u32 mode` (over/under/add) | - -All share `blit.vert.spv` (fullscreen triangle, no vertex buffer needed). - ---- - -## 4. Evaluation Loop - -### 4.1 Per-frame sequence - -``` -prGraphEvaluate(graph, device, pool, cb, swapchain_texture): - 1. topo_order = prGraphTopologicalSort(graph) - - 2. // compute initial refcounts - for each node in graph: - node.output_refcount = out_degree(node) - - 3. prTexturePoolReset(pool) - - 4. // reset per-frame descriptor pool (allocated once at init, reset each frame) - prRhiResetDescriptorPool(device, desc_pool) - - 5. for each node_id in topo_order: - node = &nodes[node_id] - entry = &_node_type_table[node->type] - - // acquire output texture from pool - output_slot = prTexturePoolAcquire(pool, device) - - // gather input textures (from upstream nodes' output slots) - input_count = 0 - input_slots[4] // max 4 inputs - for each upstream edge (upstream → node): - input_slots[input_count++] = upstream.output_slot - - // allocate and update descriptor set - desc_set = prRhiAllocateDescriptorSet(device, desc_pool, entry->set_layout) - - writes = stack_array(input_count) - for i in 0..input_count: - writes[i] = { - .dst_set = desc_set, - .dst_binding = i, - .dst_array_element = 0, - .type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .image_info = &(PrRhiDescriptorImageInfo){ - .texture = input_slots[i]->texture, - .sampler = shared_sampler, - .layout = PR_RHI_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - }, - } - prRhiUpdateDescriptorSet(device, writes) - - // record commands - prRhiCmdBeginRendering(cb, output_slot->texture, ...) - prRhiCmdBindPipeline(cb, GRAPHICS, entry->pipeline) - prRhiCmdBindDescriptorSets(cb, GRAPHICS, entry->pipeline_layout, 0, 1, &desc_set, 0, NULL) - prRhiCmdPushConstants(cb, ..., node->params) - prRhiCmdDraw(cb, 3, 1, 0, 0) // fullscreen triangle - prRhiCmdEndRendering(cb) - - // release input textures whose refcount hit 0 - for each input_slot: - input_slot->refcount -= 1 - if input_slot->refcount == 0: - prTexturePoolRelease(pool, input_slot) - - // store output slot on node for downstream consumers - node->output_slot = output_slot - - 6. // final blit to swapchain - final_slot = last_node.output_slot - blit final_slot->texture → swapchain_texture - - 7. prRhiQueueSubmit(cb) -``` - -### 4.2 READ node special case - -READ nodes load a texture from disk (KTX) via `prRhiCreateTextureFromKtx`. -The loaded texture is stored directly on the node (persistent, lives across -frames). Unlike other nodes, READ's input comes from this persistent texture -rather than from an upstream node's output slot. - -READ nodes still render a fullscreen triangle that samples from the loaded -texture and writes to the output pool texture. This allows the user to view -the raw texture before any modifications, and ensures READ nodes participate -uniformly in the evaluation pipeline. - -READ nodes participate in refcount tracking like any other node: their output -slot's refcount is set to `out_degree(READ)`, and downstream consumers -decrement it normally. - -### 4.3 Barrier insertion - -Between nodes that share a texture (one writes, next reads), a pipeline barrier -is needed to transition the texture layout: - -``` -after node A executes (writes to texture T): - barrier: T from COLOR_ATTACHMENT → SHADER_READ_ONLY - -before node B executes (reads texture T): - (barrier already inserted above) -``` - -In practice, the barrier is inserted **after** each node's render pass: -- Transition the output texture from `COLOR_ATTACHMENT_OPTIMAL` to - `SHADER_READ_ONLY_OPTIMAL` - -The **first** node in a chain (READ) needs a transition from `TRANSFER_DST` to -`SHADER_READ_ONLY` after loading from disk. This is already handled by -`prRhiCreateTextureFromKtx`. - -Layout transitions per node: -``` -READ: UNDEFINED → TRANSFER_DST → SHADER_READ_ONLY (done by KTX loader) -BLUR: SHADER_READ_ONLY (input) → COLOR_ATTACHMENT (output, during render) - output transitions to SHADER_READ_ONLY after render pass -GRADE: same as BLUR -BLEND: same as BLUR (two inputs) -``` - -### 4.4 Descriptor management - -Each node needs a descriptor set binding its input textures. The flow: - -**Init (once):** -- Create a **per-node-type descriptor set layout** with `input_count` combined - image sampler bindings. Stored in `PrNodeTypeEntry.set_layout`. -- Create a **persistent descriptor pool** large enough for the worst-case node - count (e.g., 128 sets). Created once, reused every frame. - -**Per frame:** -1. Reset the descriptor pool via `prRhiResetDescriptorPool`. This is much - cheaper than create/destroy — it reuses the pool's internal memory. -2. For each node during evaluation: - - Allocate a descriptor set from the pool using the node type's layout. - - Write each input texture into the set via `prRhiUpdateDescriptorSet`. - Each write specifies: - - `dst_set` / `dst_binding` — which set and binding index - - `type` — `PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` - - `image_info` — texture handle, shared sampler, layout - - Bind the set during rendering via `prRhiCmdBindDescriptorSets`. - -The pool lives for the lifetime of the application. Only its contents are -reset each frame. - ---- - -## 5. File layout - -``` -src/prism/allocators/ -└── pr_pool_allocator.h / .c ← pool allocator (self-managing, wapp OS allocation) - -src/prism/core/ -├── pr_graph.h / .c ← promoted from scratchpad/dag.c -├── pr_node.h / .c ← PrNode, PrNodeType, PrNodeManager -├── pr_texture_pool.h / .c ← PrTexturePool -└── pr_node_eval.h / .c ← evaluation loop, type registry - -src/shaders/ ← Slang source (compiled to assets/shaders/ via slangc) -├── blit.vert.slang ← fullscreen triangle (shared by all fragment nodes) -├── read.frag.slang ← passthrough (samples loaded texture) -├── blur.frag.slang ← gaussian blur -├── grade.frag.slang ← colour grading -└── blend.frag.slang ← alpha compositing - -assets/shaders/ ← compiled SPIR-V output (loaded at runtime) -├── blit.vert.spv -├── read.frag.spv -├── blur.frag.spv -├── grade.frag.spv -└── blend.frag.spv -``` - ---- - -## 6. Implementation order - -1. **Pool allocator**: Implement `PrPool` in `src/prism/allocators/`. - `prPoolInit`, `prPoolAlloc`, `prPoolFree`, `prPoolDestroy`. Self-managing - growth via wapp OS allocation. Replace the ad-hoc `PrPool` in scratchpad/dag.c. - -2. **Promote graph to production**: Move `PrGraph`, `PrNodeManager`, topology - ops from `scratchpad/dag.c` to `src/prism/core/pr_graph.h/.c` and - `pr_node.h/.c`. Clean up — remove the `main()` test harness. - -3. **Define node type registry**: Create `PrNodeTypeEntry` table with resource - signatures (input_count, output_count, push_constant_size). No shaders yet. - -4. **Implement PrTexturePool**: Growth-based pool with refcount tracking. - `prTexturePoolInit`, `prTexturePoolReset`, `prTexturePoolAcquire`, - `prTexturePoolRelease`, `prTexturePoolDestroy`. - -5. **Write blit.vert.slang**: Fullscreen triangle, no vertex buffer. Shared by - all fragment-shader nodes. Compile to SPIR-V via `slangc`. - -6. **Write initial frag shaders**: `read.frag.slang`, `blur.frag.slang`, - `grade.frag.slang`, `blend.frag.slang`. Simple per-pixel operations. - Compile to SPIR-V via `slangc`. - -7. **Wire up shader loading + pipeline creation**: At init, load pre-compiled - SPIR-V from `assets/shaders/`, create descriptor set layouts, pipeline - layouts, pipelines. Cache in the type registry. - -8. **Implement evaluation loop**: `prGraphEvaluate` — topo sort, refcount - compute, pool reset, per-node dispatch, barrier insertion, final blit to - swapchain. - -9. **Integrate with main loop**: Replace the current mesh-rendering demo with - a node graph evaluation. Create a test graph (Read→Blur→Blend) and render - it to the swapchain. - ---- - -## 7. Decisions - -- **Pool allocator**: Self-contained `PrPool` with standalone API. No external - allocator parameter — pool allocates blocks via wapp OS allocation (`wpOsMemAlloc` - / `wpOsMemFree`) and grows on demand. Handles slot sizes smaller than - `sizeof(void*)` transparently via an internal `alloc_size`. Lives in - `src/prism/allocators/`, outside vendored wapp. - -- **READ node texture lifetime**: READ nodes hold a persistent `PrRhiTexture` - (loaded via `prRhiCreateTextureFromKtx`) outside the pool. The pool slot's - `texture` pointer references this persistent texture. This means READ nodes - don't consume pool slots — they just participate in refcount tracking. - -- **Sampler**: Single shared sampler (linear filtering, clamp-to-edge) for all - nodes in V1. Created once at init. - -- **Push constant layout**: Each node type defines its own push constant struct. - The evaluation loop reads the node's params union and passes it via - `prRhiCmdPushConstants`. The shader declares matching layout. diff --git a/justfile b/justfile index 2241472..f9270a9 100644 --- a/justfile +++ b/justfile @@ -5,7 +5,6 @@ default: build CC := "clang" CXX := "clang++" -SLANGC := "slangc" BUILDDIR := "build" # Resolve VULKAN_SDK once via backtick @@ -31,19 +30,8 @@ vendor: cmake --build {{BUILDDIR}}/ktx --config Release cmake --install {{BUILDDIR}}/ktx -# Compile Slang shaders to SPIR-V -shaders: - mkdir -p assets/shaders - {{SLANGC}} -target spirv -stage vertex -entry main src/shaders/blit.vert.slang -o assets/shaders/blit.vert.spv - {{SLANGC}} -target spirv -stage vertex -entry main src/shaders/blit_to_swap.vert.slang -o assets/shaders/blit_to_swap.vert.spv - {{SLANGC}} -target spirv -stage fragment -entry main src/shaders/read.frag.slang -o assets/shaders/read.frag.spv - {{SLANGC}} -target spirv -stage fragment -entry main src/shaders/blur.frag.slang -o assets/shaders/blur.frag.spv - {{SLANGC}} -target spirv -stage fragment -entry main src/shaders/grade.frag.slang -o assets/shaders/grade.frag.spv - {{SLANGC}} -target spirv -stage fragment -entry main src/shaders/blend.frag.slang -o assets/shaders/blend.frag.spv - {{SLANGC}} -target spirv -stage fragment -entry main src/shaders/blit_to_swap.frag.slang -o assets/shaders/blit_to_swap.frag.spv - # Build all objects, then link -build: vendor shaders +build: vendor mkdir -p {{BUILDDIR}}/bin bear -- {{CXX}} -g -c -Wno-nullability-completeness {{VK_FLAGS}} \ src/prism/rhi/vulkan/profiles/vulkan_profiles.cpp \ @@ -55,11 +43,6 @@ build: vendor shaders bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/rhi/pr_rhi.c -o {{BUILDDIR}}/pr_rhi.o bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/rhi/vulkan/pr_rhi_vk.c -o {{BUILDDIR}}/pr_rhi_vk.o bear -a -- {{CC}} -g -c src/vendor/wapp/wapp.c -o {{BUILDDIR}}/wapp.o - bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/allocators/pr_pool_allocator.c -o {{BUILDDIR}}/pr_pool_allocator.o - bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/core/pr_graph.c -o {{BUILDDIR}}/pr_graph.o - bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/core/pr_node.c -o {{BUILDDIR}}/pr_node.o - bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/core/pr_node_eval.c -o {{BUILDDIR}}/pr_node_eval.o - bear -a -- {{CC}} -g -c {{VK_FLAGS}} src/prism/core/pr_texture_pool.c -o {{BUILDDIR}}/pr_texture_pool.o bear -a -- {{CXX}} -g -c {{VK_FLAGS}} -Wno-nullability-completeness -DVK_NO_PROTOTYPES \ {{APP_INC}} \ src/main.cpp \ @@ -67,7 +50,7 @@ build: vendor shaders bear -a -- {{CXX}} -g {{VK_FLAGS}} \ -L{{VK_SDK}}/lib -L{{VENDOR_LIB}} \ build/*.o \ - -lSDL3 -lktx -lvulkan \ + -lSDL3 -lglm -ltinyobjloader -lktx -lslang -lvulkan \ -Wl,-rpath,{{VENDOR_LIB}} -Wl,-rpath,{{VK_SDK}}/lib \ -o {{BUILDDIR}}/bin/prism @echo "--- build done: {{BUILDDIR}}/bin/prism ---" diff --git a/scratchpad/dag.c b/scratchpad/dag.c index 6e0febd..5f8870d 100644 --- a/scratchpad/dag.c +++ b/scratchpad/dag.c @@ -1,6 +1,6 @@ // vim:fileencoding=utf-8:foldmethod=marker -#include "../src/vendor/wapp/wapp.h" +#include "../src/wapp/wapp.h" #include #include #include diff --git a/src/main.cpp b/src/main.cpp index de6acba..5c3d55e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,23 +1,26 @@ // vim:fileencoding=utf-8:foldmethod=marker // -// Prism compositor test app. -// Builds a small node graph (READ → GRADE), evaluates it each frame, -// and blits the result to the swapchain. +// Prism port of how-to-vulkan's main.cpp — uses the RHI API instead of +// direct Vulkan calls. #include "prism/rhi/pr_rhi_types.h" #include "prism/rhi/pr_rhi.h" -#include "prism/core/pr_texture_pool.h" -#include "prism/core/pr_node.h" -#include "prism/core/pr_node_eval.h" +#include +#include +#include +#include +#include #include #include #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include // ============================================================================ // Exit codes @@ -26,437 +29,705 @@ enum ExitCode { EXIT_CODE_SUCCESS, EXIT_CODE_SDL_INIT_FAILED, + EXIT_CODE_VULKAN_LIB_LOAD_FAILED, EXIT_CODE_WINDOW_CREATION_FAILED, + EXIT_CODE_SURFACE_CREATION_FAILED, EXIT_CODE_GET_WINDOW_SIZE_FAILED, + EXIT_CODE_NO_INSTANCE_SUPPORT, EXIT_CODE_NO_PHYSICAL_DEVICES, + EXIT_CODE_ALLOCATION_FAILURE, EXIT_CODE_NO_SUITABLE_PHYSICAL_DEVICE, + EXIT_CODE_NO_PRESENTATION_SUPPORT, + EXIT_CODE_NO_SUITABLE_DEPTH_FORMAT, + EXIT_CODE_MESH_LOAD_FAILED, + EXIT_CODE_SYNC_OBJ_CREATE_FAILED, }; static inline void check(bool result, i32 code) { if (!result) { - fprintf(stderr, "fatal error (code %d)\n", code); + std::cerr << "Call returned an error\n"; exit(code); } } // ============================================================================ -// Blit-to-swapchain state +// Types // ============================================================================ -struct BlitResources { - PrRhiShader *vertex_shader; - PrRhiShader *fragment_shader; - PrRhiDescriptorSetLayout *desc_set_layout; - PrRhiPipelineLayout *pipeline_layout; - PrRhiPipeline *pipeline; - PrRhiDescriptorPool *desc_pool; +struct Vertex { + glm::vec3 pos; + glm::vec3 normal; + glm::vec2 uv; }; -static void _blitInit(BlitResources *blit, PrRhiDevice *device, PrRhiFormat swapchain_format) { - // Load pre-compiled SPIR-V - auto loadSpirv = [](const char *path, u64 *out_size) -> void * { - FILE *f = fopen(path, "rb"); - if (!f) { fprintf(stderr, "cannot open %s\n", path); abort(); } - fseek(f, 0, SEEK_END); - long sz = ftell(f); - fseek(f, 0, SEEK_SET); - void *buf = malloc((size_t)sz); - if (!buf) { fclose(f); abort(); } - size_t read = fread(buf, 1, (size_t)sz, f); - fclose(f); - if ((long)read != sz) { free(buf); abort(); } - *out_size = (u64)sz; - return buf; - }; +struct ShaderData { + glm::mat4 projection; + glm::mat4 view; + glm::mat4 model[3]; + glm::vec4 light_pos{ 0.0f, -10.0f, 10.0f, 0.0f }; + u32 selected{ 1 }; +}; - u64 vert_size = 0, frag_size = 0; - void *vert_code = loadSpirv("assets/shaders/blit_to_swap.vert.spv", &vert_size); - void *frag_code = loadSpirv("assets/shaders/blit_to_swap.frag.spv", &frag_size); +struct TextureResources { + PrRhiTexture *texture; + PrRhiSampler *sampler; +}; - blit->vertex_shader = prRhiCreateShader(device, (PrRhiShaderDesc){ - .spirv_code = vert_code, - .spirv_size = vert_size, - }); - blit->fragment_shader = prRhiCreateShader(device, (PrRhiShaderDesc){ - .spirv_code = frag_code, - .spirv_size = frag_size, - }); - free(vert_code); - free(frag_code); - - // Descriptor set layout: 1 combined image sampler at binding 0 - PrRhiDescriptorSetLayoutBindingArray blit_bindings = wpArray(PrRhiDescriptorSetLayoutBinding, - ((PrRhiDescriptorSetLayoutBinding){ - .type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptor_count = 1, - .stage_flags = PR_RHI_SHADER_STAGE_FRAGMENT, - .binding_flags = (PrRhiDescriptorBindingFlag)0, - })); - blit->desc_set_layout = prRhiCreateDescriptorSetLayout(device, (PrRhiDescriptorSetLayoutDesc){ - .bindings = blit_bindings, - }); - - // Pipeline layout (no push constants) - PrRhiDescriptorSetLayoutArray blit_set_layouts = wpArray(PrRhiDescriptorSetLayout *, blit->desc_set_layout); - blit->pipeline_layout = prRhiCreatePipelineLayout(device, (PrRhiPipelineLayoutDesc){ - .set_layouts = blit_set_layouts, - .push_constant_ranges = NULL, - }); - - // Graphics pipeline - PrRhiFormatArray blit_color_formats = wpArray(PrRhiFormat, swapchain_format); - PrRhiColorBlendAttachmentArray blit_blend_attachments = wpArray(PrRhiColorBlendAttachment, - ((PrRhiColorBlendAttachment){ .color_write_mask = 0xF })); - - blit->pipeline = prRhiCreateGraphicsPipeline(device, (PrRhiGraphicsPipelineDesc){ - .vertex_shader = blit->vertex_shader, - .vertex_shader_entry_point = "main", - .fragment_shader = blit->fragment_shader, - .fragment_shader_entry_point = "main", - .vertex_bindings = NULL, - .vertex_attributes = NULL, - .topology = PR_RHI_TOPOLOGY_TRIANGLE_LIST, - .color_attachment_formats = blit_color_formats, - .depth_attachment_format = PR_RHI_FORMAT_UNDEFINED, - .depth_test_enable = false, - .depth_write_enable = false, - .depth_compare_op = PR_RHI_COMPARE_OP_ALWAYS, - .blend_attachments = blit_blend_attachments, - .dynamic_viewport = true, - .dynamic_scissor = true, - .polygon_mode = PR_RHI_POLYGON_MODE_FILL, - .cull_mode = PR_RHI_CULL_MODE_NONE, - .front_face = PR_RHI_FRONT_FACE_COUNTER_CLOCKWISE, - .line_width = 1.0f, - .multisample_count = PR_RHI_SAMPLE_COUNT_1, - .layout = blit->pipeline_layout, - }); - - // Per-frame descriptor pool - PrRhiDescriptorPoolSizeArray blit_pool_sizes = wpArray(PrRhiDescriptorPoolSize, - ((PrRhiDescriptorPoolSize){ - .type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptor_count = 1, - })); - blit->desc_pool = prRhiCreateDescriptorPool(device, (PrRhiDescriptorPoolDesc){ - .max_sets = 1, - .pool_sizes = blit_pool_sizes, - }); -} - -static void _blitDestroy(BlitResources *blit, PrRhiDevice *device) { - prRhiDestroyDescriptorPool(device, blit->desc_pool); - prRhiDestroyPipeline(device, blit->pipeline); - prRhiDestroyPipelineLayout(device, blit->pipeline_layout); - prRhiDestroyDescriptorSetLayout(device, blit->desc_set_layout); - prRhiDestroyShader(device, blit->fragment_shader); - prRhiDestroyShader(device, blit->vertex_shader); -} +// Typedefs for wapp arrays of our types +typedef Vertex *VertexArray; +typedef u16 *U16Array; +typedef glm::vec3 *GlmVec3Array; +typedef TextureResources *TextureResourcesArray; +typedef PrRhiBuffer **PrRhiBufferArray; +typedef PrRhiFence **PrRhiFenceArray; +typedef PrRhiSemaphore **PrRhiSemaphoreArray; +typedef PrRhiCommandBuffer **PrRhiCommandBufferArray; // ============================================================================ -// Blit-to-swapchain pass +// Global state // ============================================================================ -static void _blitToSwapchain(BlitResources *blit, PrRhiDevice *device, - PrTextureSlot *compositor_output, - PrRhiCommandBuffer *cb, PrRhiSwapchain *swapchain, - u32 image_index, PrRhiSampler *sampler, - i32 win_w, i32 win_h) { - // Transition compositor output: ATTACHMENT_OPTIMAL → SHADER_READ_ONLY_OPTIMAL - { - PrRhiImageMemoryBarrier barrier = { - .texture = compositor_output->texture, - .old_layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL, - .new_layout = PR_RHI_LAYOUT_READ_ONLY_OPTIMAL, - .src_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT, - .src_access_mask = PR_RHI_ACCESS_COLOR_ATTACHMENT_WRITE, - .dst_stage_mask = PR_RHI_PIPELINE_STAGE_FRAGMENT_SHADER, - .dst_access_mask = PR_RHI_ACCESS_SHADER_READ, - }; - prRhiCmdPipelineBarrier(cb, &barrier, NULL); - } +struct AppState { + PrRhiInstance *inst; + PrRhiPhysicalDevice *pdev; + PrRhiDevice *device; + PrRhiSurface *surface; + PrRhiSwapchain *swapchain; - // Reset and allocate descriptor set - prRhiResetDescriptorPool(device, blit->desc_pool); - PrRhiDescriptorSet *desc_set = prRhiAllocateDescriptorSet( - device, blit->desc_pool, blit->desc_set_layout, NULL); + PrRhiFormat swapchain_format; - // Write descriptor: binding 0 = compositor output texture - PrRhiDescriptorImageInfo image_info = { - .texture = compositor_output->texture, - .sampler = sampler, - .layout = PR_RHI_LAYOUT_READ_ONLY_OPTIMAL, - }; - PrRhiWriteDescriptorSet write = { - .dst_set = desc_set, - .dst_binding = 0, - .dst_array_element = 0, - .type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .image_info = &image_info, - .buffer_info = NULL, - }; - prRhiUpdateDescriptorSet(device, &write); + PrRhiBuffer *vert_index_buf; + u64 vertex_buf_size; + u64 index_count; - // Transition swapchain image to attachment optimal - PrRhiTexture *swap_tex = prRhiGetSwapchainTexture(swapchain, image_index); - { - PrRhiImageMemoryBarrier barrier = { - .texture = swap_tex, - .old_layout = PR_RHI_LAYOUT_UNDEFINED, - .new_layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL, - .src_stage_mask = PR_RHI_PIPELINE_STAGE_TOP_OF_PIPE, - .src_access_mask = PR_RHI_ACCESS_NONE, - .dst_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT, - .dst_access_mask = PR_RHI_ACCESS_COLOR_ATTACHMENT_WRITE, - }; - prRhiCmdPipelineBarrier(cb, &barrier, NULL); - } + static constexpr u32 max_frames_in_flight = 2; + static constexpr u32 instance_count = 3; + static constexpr u32 texture_count = 3; - // Render fullscreen triangle to swapchain - { - PrRhiColorAttachment color_att = { - .texture = swap_tex, - .layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL, - .clear = true, - .clear_color = {0.0f, 0.0f, 0.0f, 1.0f}, - }; - prRhiCmdBeginRendering(cb, &color_att, NULL); - } + PrRhiBufferArray shader_data_bufs; + PrRhiFenceArray fences; + PrRhiSemaphoreArray image_acquired_semaphores; + PrRhiSemaphoreArray render_completed_semaphores; + PrRhiCommandPool *cmd_pool; + PrRhiCommandBufferArray cmd_buffers; - prRhiCmdBindPipeline(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS, blit->pipeline); + TextureResourcesArray textures; + PrRhiDescriptorSetLayout *desc_set_layout; + PrRhiDescriptorPool *desc_pool; + PrRhiDescriptorSet *desc_set; - prRhiCmdSetViewport(cb, 0.0f, 0.0f, (f32)win_w, (f32)win_h); - prRhiCmdSetScissor(cb, 0, 0, (u32)win_w, (u32)win_h); + PrRhiShader *shader; + PrRhiPipelineLayout *pipeline_layout; + PrRhiPipeline *pipeline; - PrRhiDescriptorSet *sets[1] = { desc_set }; - prRhiCmdBindDescriptorSets(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS, - blit->pipeline_layout, 0, sets); + ShaderData shader_data; + u32 frame_index; + glm::ivec2 window_size; + GlmVec3Array object_rotations; + bool update_swapchain; - prRhiCmdDraw(cb, 3, 1, 0, 0); - prRhiCmdEndRendering(cb); - - // Transition to present - { - PrRhiImageMemoryBarrier barrier = { - .texture = swap_tex, - .old_layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL, - .new_layout = PR_RHI_LAYOUT_PRESENT_SRC, - .src_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT, - .src_access_mask = PR_RHI_ACCESS_COLOR_ATTACHMENT_WRITE, - .dst_stage_mask = PR_RHI_PIPELINE_STAGE_BOTTOM_OF_PIPE, - .dst_access_mask = PR_RHI_ACCESS_NONE, - }; - prRhiCmdPipelineBarrier(cb, &barrier, NULL); - } -} + SDL_Window *window; + Slang::ComPtr slang_session; +}; // ============================================================================ // Main // ============================================================================ int main() { - // {{{ RHI init + AppState app = {}; + WpAllocator arena = wpMemArenaAllocatorInitZero(MiB(128)); + + // {{{ Initialisation prRhiInit(); - check(SDL_Init(SDL_INIT_VIDEO) != false, EXIT_CODE_SDL_INIT_FAILED); + + check(SDL_Init(SDL_INIT_VIDEO), EXIT_CODE_SDL_INIT_FAILED); f32 display_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); - SDL_Window *window = SDL_CreateWindow("Prism Compositor", - (i32)(display_scale * 1280), - (i32)(display_scale * 720), - SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); - check(window != nullptr, EXIT_CODE_WINDOW_CREATION_FAILED); + app.window = SDL_CreateWindow("How To Vulkan (Prism)", (i32)(display_scale * 1920), + (i32)(display_scale * 1080), + SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); + check(app.window != nullptr, EXIT_CODE_WINDOW_CREATION_FAILED); + // }}} - PrRhiInstance *inst = prRhiCreateInstance((PrRhiInstanceDesc){}); + // {{{ Instance creation + app.inst = prRhiCreateInstance(PrRhiInstanceDesc{}); + // }}} - // Physical device selection - PrRhiPhysicalDeviceArray pdevs = prRhiGetPhysicalDevices(inst); + // {{{ Physical device selection + PrRhiPhysicalDeviceArray pdevs = prRhiGetPhysicalDevices(app.inst); check(wpArrayCount(pdevs) > 0, EXIT_CODE_NO_PHYSICAL_DEVICES); + i32 selected = -1; for (u32 i = 0; i < wpArrayCount(pdevs); ++i) { PrRhiPhysicalDeviceProperties props = prRhiGetPhysicalDeviceProperties(pdevs[i]); - if (props.device_type == PR_RHI_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { - selected = (i32)i; - break; - } - if (props.device_type == PR_RHI_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU && selected == -1) { - selected = (i32)i; + switch (props.device_type) { + case PR_RHI_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + selected = (i32)i; + break; + case PR_RHI_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + if (selected == -1) + selected = (i32)i; + break; + default: continue; } } check(selected != -1, EXIT_CODE_NO_SUITABLE_PHYSICAL_DEVICE); - PrRhiPhysicalDevice *pdev = pdevs[selected]; + app.pdev = pdevs[selected]; - i32 win_w = 0, win_h = 0; - check(SDL_GetWindowSize(window, &win_w, &win_h), EXIT_CODE_GET_WINDOW_SIZE_FAILED); - PrRhiSurface *surface = prRhiCreateSurfaceFromWindow(inst, window); - - PrRhiDevice *device = prRhiCreateDevice(pdev, surface, (PrRhiDeviceDesc){ - .present_mode = PR_RHI_PRESENT_MODE_FIFO, - }); - - PrRhiSwapchain *swapchain = prRhiCreateSwapchain(device, (PrRhiSwapchainDesc){ - .surface = surface, - .width = (u32)win_w, - .height = (u32)win_h, - .has_depth = false, - }); - PrRhiFormat swapchain_format = prRhiGetSwapchainFormat(swapchain); - u32 swapchain_image_count = prRhiGetSwapchainImageCount(swapchain); + // Print device info + WpStr8 dev_name = wpStr8Buf(512); + WpStr8 driver_info = wpStr8Buf(512); + prRhiGetPhysicalDeviceName(app.pdev, &dev_name); + prRhiGetPhysicalDeviceDriverInfo(app.pdev, &driver_info); + std::cout << "Selected GPU: " << std::string_view((const char *)dev_name.buf, dev_name.size) << '\n' + << "Driver version: " << std::string_view((const char *)driver_info.buf, driver_info.size) << '\n'; // }}} - // {{{ Sync objects - PrRhiFence *fence = prRhiCreateFence(device, (PrRhiFenceDesc){ .signaled = true }); - PrRhiSemaphore *image_acquired = prRhiCreateSemaphore(device); - PrRhiSemaphore *render_completed = prRhiCreateSemaphore(device); - - PrRhiCommandPool *cmd_pool = prRhiCreateCommandPool(device); - PrRhiCommandBufferArray cmd_buffers = prRhiAllocateCommandBuffers(device, cmd_pool, 1); + // {{{ Surface creation + check(SDL_GetWindowSize(app.window, &app.window_size.x, &app.window_size.y), + EXIT_CODE_GET_WINDOW_SIZE_FAILED); + app.surface = prRhiCreateSurfaceFromWindow(app.inst, app.window); // }}} - // {{{ Compositor init - PrTexturePool pool; - prTexturePoolInit(&pool, 8, 32, (u32)win_w, (u32)win_h, - &_G_RHI_CONTEXT.allocator); + // {{{ Device creation + PrRhiDeviceDesc dev_desc = {}; + dev_desc.present_mode = PR_RHI_PRESENT_MODE_FIFO; - PrNodeManager mgr; - prNodeManagerInit(&mgr, &_G_RHI_CONTEXT.allocator, 64); - - PrRhiSampler *shared_sampler = prRhiCreateSampler(device, (PrRhiSamplerDesc){ - .mag_filter = PR_RHI_FILTER_LINEAR, - .min_filter = PR_RHI_FILTER_LINEAR, - .mipmap_mode = PR_RHI_MIPMAP_MODE_LINEAR, - .address_mode_u = PR_RHI_ADDRESS_MODE_CLAMP_TO_EDGE, - .address_mode_v = PR_RHI_ADDRESS_MODE_CLAMP_TO_EDGE, - .address_mode_w = PR_RHI_ADDRESS_MODE_CLAMP_TO_EDGE, - .max_anisotropy = 1.0f, - .min_lod = 0.0f, - .max_lod = PR_RHI_LOD_CLAMP_NONE, - }); - - prNodeEvalInit(device, swapchain_format); - - // {{{ Build example graph: READ → GRADE - PrNodeId read_node = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ); - PrNodeId grade_node = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_GRADE); - prNodeManagerAddEdge(&mgr, read_node, grade_node); - - // Load KTX texture for the READ node - PrRhiTexture *read_texture = prRhiCreateTextureFromKtx(device, "assets/suzanne0.ktx", - cmd_pool, cmd_buffers[0]); - mgr.nodes[read_node.index].texture = read_texture; + app.device = prRhiCreateDevice(app.pdev, app.surface, dev_desc); // }}} - // Blit-to-swapchain resources - BlitResources blit; - _blitInit(&blit, device, swapchain_format); + // {{{ Swapchain creation + PrRhiSwapchainDesc swap_desc = {}; + swap_desc.surface = app.surface; + swap_desc.width = (u32)app.window_size.x; + swap_desc.height = (u32)app.window_size.y; + swap_desc.has_depth = true; + swap_desc.depth_format = PR_RHI_FORMAT_D24_UNORM_S8_UINT; + + app.swapchain = prRhiCreateSwapchain(app.device, swap_desc); + app.swapchain_format = prRhiGetSwapchainFormat(app.swapchain); // }}} - // {{{ Render loop - bool running = true; - u32 frame_index = 0; - u32 image_index = 0; + // {{{ Vertex/Index buffers + // {{{ Load mesh + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; - while (running) { - // Wait for previous frame's GPU work - prRhiWaitForFences(device, wpArray(PrRhiFence *, fence), 1, true, UINT64_MAX); - prRhiResetFences(device, wpArray(PrRhiFence *, fence), 1); + check(tinyobj::LoadObj(&attrib, &shapes, &materials, nullptr, nullptr, "assets/suzanne.obj"), + EXIT_CODE_MESH_LOAD_FAILED); - // Acquire swapchain image - PrRhiSwapchainResult acq = prRhiAcquireNextImage(device, swapchain, - image_acquired, &image_index); - if (acq == PR_RHI_SWAPCHAIN_OUT_OF_DATE) { - prRhiDeviceWaitIdle(device); - i32 w = 0, h = 0; - SDL_GetWindowSize(window, &w, &h); - prRhiRecreateSwapchain(device, &swapchain, (u32)w, (u32)h); - swapchain_format = prRhiGetSwapchainFormat(swapchain); - // Recreate blit pipeline for new swapchain format - _blitDestroy(&blit, device); - _blitInit(&blit, device, swapchain_format); - continue; - } + VertexArray vertices = wpArrayAllocCapacity(Vertex, &arena, 128, WP_ARRAY_INIT_NONE); + U16Array indices = wpArrayAllocCapacity(u16, &arena, 128, WP_ARRAY_INIT_NONE); - // Record command buffer - PrRhiCommandBuffer *cb = cmd_buffers[0]; - prRhiResetCommandBuffer(cb); - prRhiBeginCommandBuffer(cb); + for (auto &idx : shapes[0].mesh.indices) { + Vertex v = {}; + v.pos = { + attrib.vertices[idx.vertex_index * 3], + -attrib.vertices[idx.vertex_index * 3 + 1], + attrib.vertices[idx.vertex_index * 3 + 2] + }; + v.normal = { + attrib.normals[idx.normal_index * 3], + -attrib.normals[idx.normal_index * 3 + 1], + attrib.normals[idx.normal_index * 3 + 2] + }; + v.uv = { + attrib.texcoords[idx.texcoord_index * 2], + 1.0f - attrib.texcoords[idx.texcoord_index * 2 + 1] + }; - // 1. Evaluate compositor graph (writes to pool texture, records into cb) - PrTextureSlot *compositor_output = NULL; - prGraphEvaluate(&mgr, device, &pool, cb, shared_sampler, &compositor_output); + u16 index = (u16)wpArrayCount(indices); + vertices = wpArrayAppendAlloc(Vertex, &arena, vertices, &v, WP_ARRAY_INIT_NONE); + indices = wpArrayAppendAlloc(u16, &arena, indices, &index, WP_ARRAY_INIT_NONE); + } - // 2. Blit compositor output to swapchain - if (compositor_output) { - _blitToSwapchain(&blit, device, compositor_output, - cb, swapchain, image_index, shared_sampler, - win_w, win_h); - } + app.vertex_buf_size = sizeof(Vertex) * wpArrayCount(vertices); + app.index_count = wpArrayCount(indices); + u64 index_buf_size = sizeof(u16) * app.index_count; + // }}} - prRhiEndCommandBuffer(cb); + // {{{ Create GPU buffer + PrRhiBufferDesc vert_desc = {}; + vert_desc.size = app.vertex_buf_size + index_buf_size; + vert_desc.usage = (PrRhiBufferUsage)(PR_RHI_BUFFER_USAGE_VERTEX | PR_RHI_BUFFER_USAGE_INDEX); + vert_desc.memory = PR_RHI_MEMORY_CPU_TO_GPU; - // Submit + present - prRhiQueueSubmit(device, cb, image_acquired, render_completed, fence); - PrRhiSwapchainResult pres = prRhiPresent(device, swapchain, render_completed); - if (pres == PR_RHI_SWAPCHAIN_OUT_OF_DATE) { - prRhiDeviceWaitIdle(device); - i32 w = 0, h = 0; - SDL_GetWindowSize(window, &w, &h); - prRhiRecreateSwapchain(device, &swapchain, (u32)w, (u32)h); - swapchain_format = prRhiGetSwapchainFormat(swapchain); - _blitDestroy(&blit, device); - _blitInit(&blit, device, swapchain_format); - } + app.vert_index_buf = prRhiCreateBuffer(app.device, vert_desc); - // Poll events - SDL_Event event; - while (SDL_PollEvent(&event)) { - switch (event.type) { - case SDL_EVENT_QUIT: - running = false; - break; - case SDL_EVENT_KEY_DOWN: - if (event.key.key == SDLK_ESCAPE) { running = false; } - break; - case SDL_EVENT_WINDOW_RESIZED: - prRhiDeviceWaitIdle(device); - SDL_GetWindowSize(window, &win_w, &win_h); - prRhiRecreateSwapchain(device, &swapchain, (u32)win_w, (u32)win_h); - swapchain_format = prRhiGetSwapchainFormat(swapchain); - prTexturePoolDestroy(&pool, device); - prTexturePoolInit(&pool, 8, 32, (u32)win_w, (u32)win_h, - &_G_RHI_CONTEXT.allocator); - _blitDestroy(&blit, device); - _blitInit(&blit, device, swapchain_format); - break; - } - } + void *mapped = prRhiBufferMap(app.device, app.vert_index_buf); + memcpy(mapped, vertices, app.vertex_buf_size); + memcpy((u8 *)mapped + app.vertex_buf_size, indices, index_buf_size); + prRhiBufferUnmap(app.device, app.vert_index_buf); + // }}} + // }}} - frame_index++; + // {{{ Shader data buffers + app.shader_data_bufs = wpArrayAllocCapacity(PrRhiBuffer *, &arena, + AppState::max_frames_in_flight, + WP_ARRAY_INIT_FILLED); + for (u32 i = 0; i < AppState::max_frames_in_flight; ++i) { + PrRhiBufferDesc buf_desc = {}; + buf_desc.size = sizeof(ShaderData); + buf_desc.usage = (PrRhiBufferUsage)(PR_RHI_BUFFER_USAGE_STORAGE | + PR_RHI_BUFFER_USAGE_SHADER_DEVICE_ADDRESS); + buf_desc.memory = PR_RHI_MEMORY_CPU_TO_GPU; + + app.shader_data_bufs[i] = prRhiCreateBuffer(app.device, buf_desc); } // }}} + // {{{ Synchronisation objects + // Fences + app.fences = wpArrayAllocCapacity(PrRhiFence *, &arena, AppState::max_frames_in_flight, + WP_ARRAY_INIT_FILLED); + for (u32 i = 0; i < AppState::max_frames_in_flight; ++i) { + PrRhiFenceDesc fd = {}; + fd.signaled = true; + app.fences[i] = prRhiCreateFence(app.device, fd); + } + + // Image acquired semaphores (per frame) + app.image_acquired_semaphores = wpArrayAllocCapacity(PrRhiSemaphore *, &arena, + AppState::max_frames_in_flight, + WP_ARRAY_INIT_FILLED); + for (u32 i = 0; i < AppState::max_frames_in_flight; ++i) { + app.image_acquired_semaphores[i] = prRhiCreateSemaphore(app.device); + } + + // Render completed semaphores (per swapchain image) + u32 swapchain_image_count = prRhiGetSwapchainImageCount(app.swapchain); + app.render_completed_semaphores = wpArrayAllocCapacity(PrRhiSemaphore *, &arena, + swapchain_image_count, + WP_ARRAY_INIT_FILLED); + for (u32 i = 0; i < swapchain_image_count; ++i) { + app.render_completed_semaphores[i] = prRhiCreateSemaphore(app.device); + } + // }}} + + // {{{ Command pool and buffers + app.cmd_pool = prRhiCreateCommandPool(app.device); + app.cmd_buffers = prRhiAllocateCommandBuffers(app.device, app.cmd_pool, + AppState::max_frames_in_flight); + // }}} + + // {{{ Texture loading + app.textures = wpArrayAllocCapacity(TextureResources, &arena, AppState::texture_count, + WP_ARRAY_INIT_FILLED); + + PrRhiCommandBufferArray upload_cbs = prRhiAllocateCommandBuffers(app.device, app.cmd_pool, 1); + PrRhiCommandBuffer *upload_cb = upload_cbs[0]; + + for (u32 i = 0; i < AppState::texture_count; ++i) { + char buf[2048] = {}; + snprintf(buf, sizeof(buf), "assets/suzanne%u.ktx", i); + PrRhiTexture *tex = prRhiCreateTextureFromKtx(app.device, buf, app.cmd_pool, upload_cb); + + // Create sampler + PrRhiSamplerDesc samp_desc = {}; + samp_desc.mag_filter = PR_RHI_FILTER_LINEAR; + samp_desc.min_filter = PR_RHI_FILTER_LINEAR; + samp_desc.mipmap_mode = PR_RHI_MIPMAP_MODE_LINEAR; + samp_desc.max_anisotropy = 8.0f; + samp_desc.max_lod = PR_RHI_LOD_CLAMP_NONE; + + PrRhiSampler *sampler = prRhiCreateSampler(app.device, samp_desc); + + app.textures[i].texture = tex; + app.textures[i].sampler = sampler; + } + + prRhiFreeCommandBuffers(app.device, app.cmd_pool, upload_cbs); + // }}} + + // {{{ Descriptor set layout, pool, set + PrRhiDescriptorSetLayoutBindingArray ds_layouts = wpArray( + PrRhiDescriptorSetLayoutBinding, + PrRhiDescriptorSetLayoutBinding{ + PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + AppState::texture_count, + PR_RHI_SHADER_STAGE_FRAGMENT, + PR_RHI_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT, + } + ); + + PrRhiDescriptorSetLayoutDesc layout_desc = { ds_layouts }; + app.desc_set_layout = prRhiCreateDescriptorSetLayout(app.device, layout_desc); + + // Pool + PrRhiDescriptorPoolSize pool_size = {}; + pool_size.type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + pool_size.descriptor_count = AppState::texture_count; + + PrRhiDescriptorPoolDesc ds_pool_desc = {}; + ds_pool_desc.max_sets = 1; + ds_pool_desc.pool_sizes = wpArray(PrRhiDescriptorPoolSize, pool_size); + + app.desc_pool = prRhiCreateDescriptorPool(app.device, ds_pool_desc); + + // Allocate descriptor set (variable count) + WpU32Array var_counts = wpArray(u32, AppState::texture_count); + app.desc_set = prRhiAllocateDescriptorSet(app.device, app.desc_pool, app.desc_set_layout, + var_counts); + + // Write descriptor set + PrRhiDescriptorImageInfoArray img_infos = wpArrayAllocCapacity(PrRhiDescriptorImageInfo, + &arena, + AppState::texture_count, + WP_ARRAY_INIT_NONE); + for (u32 i = 0; i < AppState::texture_count; ++i) { + PrRhiDescriptorImageInfo info = {}; + info.texture = app.textures[i].texture; + info.sampler = app.textures[i].sampler; + info.layout = PR_RHI_LAYOUT_READ_ONLY_OPTIMAL; + wpArrayAppendCapped(PrRhiDescriptorImageInfo, img_infos, &info); + } + + PrRhiWriteDescriptorSet write = {}; + write.dst_set = app.desc_set; + write.dst_binding = 0; + write.type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write.image_info = img_infos; + + PrRhiWriteDescriptorSetArray writes = wpArray(PrRhiWriteDescriptorSet, write); + prRhiUpdateDescriptorSet(app.device, writes); + // }}} + + // {{{ Shader compilation (Slang) + slang::createGlobalSession(app.slang_session.writeRef()); + + slang::TargetDesc target = {}; + target.format = SLANG_SPIRV; + target.profile = {app.slang_session->findProfile("spirv_1_4")}; + + Slang::ComPtr slang_session; + slang::SessionDesc session_desc = {}; + session_desc.targets = ⌖ + session_desc.targetCount = 1; + session_desc.defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_COLUMN_MAJOR; + app.slang_session->createSession(session_desc, slang_session.writeRef()); + + Slang::ComPtr slang_module { + slang_session->loadModuleFromSource("triangle", "assets/shader.slang", nullptr, nullptr), + }; + Slang::ComPtr spirv; + slang_module->getTargetCode(0, spirv.writeRef()); + // }}} + + // {{{ Create shader module + PrRhiShaderDesc shader_desc = {}; + shader_desc.spirv_code = spirv->getBufferPointer(); + shader_desc.spirv_size = spirv->getBufferSize(); + + app.shader = prRhiCreateShader(app.device, shader_desc); + // }}} + + // {{{ Pipeline layout + PrRhiPushConstantRange pc_range = {}; + pc_range.stage_flags = PR_RHI_SHADER_STAGE_VERTEX; + pc_range.size = sizeof(u64); + + PrRhiDescriptorSetLayoutArray pl_layouts = wpArray(PrRhiDescriptorSetLayout *, app.desc_set_layout); + + PrRhiPipelineLayoutDesc pl_desc = {}; + pl_desc.set_layouts = pl_layouts; + pl_desc.push_constant_ranges = wpArray(PrRhiPushConstantRange, pc_range); + + app.pipeline_layout = prRhiCreatePipelineLayout(app.device, pl_desc); + // }}} + + // {{{ Graphics pipeline + PrRhiVertexInputBindingArray vertex_bindings = wpArray( + PrRhiVertexInputBinding, + PrRhiVertexInputBinding{ 0, sizeof(Vertex) } + ); + + PrRhiVertexAttributeArray vertex_attrs = wpArray( + PrRhiVertexAttribute, + PrRhiVertexAttribute{ 0, 0, PR_RHI_FORMAT_R32G32B32_SFLOAT, 0 }, + PrRhiVertexAttribute{ 1, 0, PR_RHI_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal) }, + PrRhiVertexAttribute{ 2, 0, PR_RHI_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv) } + ); + + PrRhiColorBlendAttachmentArray blend_attachments = wpArray( + PrRhiColorBlendAttachment, + PrRhiColorBlendAttachment{ 0xf } + ); + + PrRhiFormatArray color_fmt_array = wpArray(PrRhiFormat, app.swapchain_format); + + PrRhiGraphicsPipelineDesc pipe_desc = {}; + pipe_desc.vertex_shader = app.shader; + pipe_desc.vertex_shader_entry_point = "main"; + pipe_desc.fragment_shader = app.shader; + pipe_desc.fragment_shader_entry_point = "main"; + pipe_desc.vertex_bindings = vertex_bindings; + pipe_desc.vertex_attributes = vertex_attrs; + pipe_desc.topology = PR_RHI_TOPOLOGY_TRIANGLE_LIST; + pipe_desc.color_attachment_formats = color_fmt_array; + pipe_desc.depth_attachment_format = swap_desc.depth_format; + pipe_desc.depth_test_enable = true; + pipe_desc.depth_write_enable = true; + pipe_desc.depth_compare_op = PR_RHI_COMPARE_OP_LESS_OR_EQUAL; + pipe_desc.blend_attachments = blend_attachments; + pipe_desc.dynamic_viewport = true; + pipe_desc.dynamic_scissor = true; + pipe_desc.cull_mode = PR_RHI_CULL_MODE_BACK; + pipe_desc.front_face = PR_RHI_FRONT_FACE_COUNTER_CLOCKWISE; + pipe_desc.line_width = 1.0f; + pipe_desc.layout = app.pipeline_layout; + + app.pipeline = prRhiCreateGraphicsPipeline(app.device, pipe_desc); + // }}} + + // {{{ Render loop + app.object_rotations = wpArrayAllocCapacity(glm::vec3, &arena, AppState::instance_count, + WP_ARRAY_INIT_FILLED); + u64 last_time = SDL_GetTicks(); + SDL_Event event = {}; + app.frame_index = 0; + u32 image_index = 0; + + while (true) { + // {{{ Wait on fence + PrRhiFence *wait_fence = app.fences[app.frame_index]; + prRhiWaitForFences(app.device, wpArray(PrRhiFence *, wait_fence), 1, true, UINT64_MAX); + prRhiResetFences(app.device, wpArray(PrRhiFence *, wait_fence), 1); + // }}} + + // {{{ Acquire next image + PrRhiSwapchainResult acq = prRhiAcquireNextImage(app.device, app.swapchain, + app.image_acquired_semaphores[app.frame_index], + &image_index); + if (acq == PR_RHI_SWAPCHAIN_OUT_OF_DATE) { + app.update_swapchain = true; + } + // }}} + + if (app.update_swapchain) { + // Skip this frame — will recreate below + } else { + // {{{ Update shader data + app.shader_data.projection = glm::perspective(glm::radians(45.0f), + (f32)app.window_size.x / (f32)app.window_size.y, + 0.1f, 32.0f); + app.shader_data.view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -6.0f)); + for (i32 i = 0; i < (i32)AppState::instance_count; ++i) { + glm::vec3 instance_pos = glm::vec3((f32)(i - 1) * 3.0f, 0.0f, 0.0f); + app.shader_data.model[i] = glm::translate(glm::mat4(1.0f), instance_pos) * + glm::mat4_cast(glm::quat(app.object_rotations[i])); + } + + void *shader_data_ptr = prRhiBufferMap(app.device, app.shader_data_bufs[app.frame_index]); + memcpy(shader_data_ptr, &app.shader_data, sizeof(ShaderData)); + prRhiBufferUnmap(app.device, app.shader_data_bufs[app.frame_index]); + // }}} + + // {{{ Record command buffer + PrRhiCommandBuffer *cb = app.cmd_buffers[app.frame_index]; + prRhiResetCommandBuffer(cb); + prRhiBeginCommandBuffer(cb); + + // Transition images to attachment optimal + { + PrRhiImageMemoryBarrierArray barriers_arr = + wpArrayWithCapacity(PrRhiImageMemoryBarrier, 2, WP_ARRAY_INIT_FILLED); + + // Color attachment + PrRhiTexture *color_tex = prRhiGetSwapchainTexture(app.swapchain, image_index); + barriers_arr[0].texture = color_tex; + barriers_arr[0].old_layout = PR_RHI_LAYOUT_UNDEFINED; + barriers_arr[0].new_layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL; + barriers_arr[0].src_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT; + barriers_arr[0].src_access_mask = PR_RHI_ACCESS_NONE; + barriers_arr[0].dst_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT; + barriers_arr[0].dst_access_mask = (PrRhiAccess)(PR_RHI_ACCESS_COLOR_ATTACHMENT_READ | PR_RHI_ACCESS_COLOR_ATTACHMENT_WRITE); + + // Depth attachment + PrRhiTexture *depth_tex = prRhiGetSwapchainDepthTexture(app.swapchain); + barriers_arr[1].texture = depth_tex; + barriers_arr[1].old_layout = PR_RHI_LAYOUT_UNDEFINED; + barriers_arr[1].new_layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL; + barriers_arr[1].src_stage_mask = PR_RHI_PIPELINE_STAGE_LATE_FRAGMENT_TESTS; + barriers_arr[1].src_access_mask = PR_RHI_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE; + barriers_arr[1].dst_stage_mask = PR_RHI_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS; + barriers_arr[1].dst_access_mask = PR_RHI_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE; + + prRhiCmdPipelineBarrier(cb, barriers_arr, NULL); + } + + // Rendering + { + PrRhiColorAttachmentArray color_arr = + wpArrayWithCapacity(PrRhiColorAttachment, 1, WP_ARRAY_INIT_FILLED); + color_arr[0].texture = prRhiGetSwapchainTexture(app.swapchain, image_index); + color_arr[0].layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL; + color_arr[0].clear = true; + color_arr[0].clear_color[0] = 0.0f; + color_arr[0].clear_color[1] = 0.0f; + color_arr[0].clear_color[2] = 0.0f; + color_arr[0].clear_color[3] = 0.0f; + + PrRhiDepthAttachment depth_att = {}; + depth_att.texture = prRhiGetSwapchainDepthTexture(app.swapchain); + depth_att.layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL; + depth_att.clear = true; + depth_att.clear_depth = 1.0f; + + prRhiCmdBeginRendering(cb, color_arr, &depth_att); + } + + prRhiCmdSetViewport(cb, 0.0f, 0.0f, (f32)app.window_size.x, (f32)app.window_size.y); + prRhiCmdSetScissor(cb, 0, 0, (u32)app.window_size.x, (u32)app.window_size.y); + + prRhiCmdBindPipeline(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS, app.pipeline); + + PrRhiDescriptorSetArray sets = wpArray(PrRhiDescriptorSet *, app.desc_set); + prRhiCmdBindDescriptorSets(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS, + app.pipeline_layout, 0, sets); + + PrRhiBufferArray vert_buf_arr = wpArray(PrRhiBuffer *, app.vert_index_buf); + WpU64Array vert_offsets = wpArray(u64, 0); + prRhiCmdBindVertexBuffers(cb, 0, vert_buf_arr, vert_offsets); + prRhiCmdBindIndexBuffer(cb, app.vert_index_buf, app.vertex_buf_size, PR_RHI_INDEX_TYPE_UINT16); + + // Push shader data buffer device address + u64 buf_addr = prRhiGetBufferDeviceAddress(app.device, app.shader_data_bufs[app.frame_index]); + prRhiCmdPushConstants(cb, app.pipeline_layout, PR_RHI_SHADER_STAGE_VERTEX, 0, sizeof(u64), &buf_addr); + + prRhiCmdDrawIndexed(cb, (u32)app.index_count, AppState::instance_count, 0, 0, 0); + prRhiCmdEndRendering(cb); + + // Transition to present + { + PrRhiImageMemoryBarrierArray present_barriers = + wpArrayWithCapacity(PrRhiImageMemoryBarrier, 1, WP_ARRAY_INIT_FILLED); + present_barriers[0].texture = prRhiGetSwapchainTexture(app.swapchain, image_index); + present_barriers[0].old_layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL; + present_barriers[0].new_layout = PR_RHI_LAYOUT_PRESENT_SRC; + present_barriers[0].src_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT; + present_barriers[0].src_access_mask = PR_RHI_ACCESS_COLOR_ATTACHMENT_WRITE; + present_barriers[0].dst_stage_mask = PR_RHI_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT; + present_barriers[0].dst_access_mask = PR_RHI_ACCESS_NONE; + + prRhiCmdPipelineBarrier(cb, present_barriers, NULL); + } + + prRhiEndCommandBuffer(cb); + // }}} + + // {{{ Submit + prRhiQueueSubmit(app.device, cb, app.image_acquired_semaphores[app.frame_index], + app.render_completed_semaphores[image_index], app.fences[app.frame_index]); + // }}} + + // {{{ Present + PrRhiSwapchainResult pres = prRhiPresent(app.device, app.swapchain, + app.render_completed_semaphores[image_index]); + if (pres == PR_RHI_SWAPCHAIN_OUT_OF_DATE) { + app.update_swapchain = true; + } + // }}} + } + + // {{{ Poll events + f32 elapsed_time = (SDL_GetTicks() - last_time) / 1000.0f; + last_time = SDL_GetTicks(); + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_EVENT_QUIT: + app.update_swapchain = false; // signal exit — use update_swapchain flag + goto done; + case SDL_EVENT_KEY_DOWN: + if (event.key.key == SDLK_ESCAPE) goto done; + if (event.key.key == SDLK_PLUS || event.key.key == SDLK_KP_PLUS || event.key.key == SDLK_EQUALS) { + app.shader_data.selected = (app.shader_data.selected < 2) ? app.shader_data.selected + 1 : 0; + } + if (event.key.key == SDLK_MINUS || event.key.key == SDLK_KP_MINUS) { + app.shader_data.selected = (app.shader_data.selected > 0) ? app.shader_data.selected - 1 : 2; + } + break; + case SDL_EVENT_MOUSE_MOTION: + if (event.button.button == SDL_BUTTON_LEFT) { + app.object_rotations[app.shader_data.selected].x -= (f32)event.motion.yrel * elapsed_time; + app.object_rotations[app.shader_data.selected].y += (f32)event.motion.xrel * elapsed_time; + } + break; + case SDL_EVENT_MOUSE_WHEEL: + // Camera position handled via shader_data update in loop + break; + case SDL_EVENT_WINDOW_RESIZED: + check(SDL_GetWindowSize(app.window, &app.window_size.x, &app.window_size.y), + EXIT_CODE_GET_WINDOW_SIZE_FAILED); + app.update_swapchain = true; + break; + } + } + // }}} + + // {{{ Swapchain recreate + if (app.update_swapchain) { + prRhiDeviceWaitIdle(app.device); + prRhiRecreateSwapchain(app.device, &app.swapchain, + (u32)app.window_size.x, (u32)app.window_size.y); + + // Re-create render completed semaphores for new image count + // TODO: proper cleanup — for now, just leak old ones + u32 new_count = 0; + prRhiAcquireNextImage(app.device, app.swapchain, NULL, &new_count); + app.render_completed_semaphores = wpArrayAllocCapacity(PrRhiSemaphore *, &arena, + new_count, WP_ARRAY_INIT_FILLED); + for (u32 i = 0; i < new_count; ++i) { + app.render_completed_semaphores[i] = prRhiCreateSemaphore(app.device); + } + + app.update_swapchain = false; + } + // }}} + + app.frame_index = (app.frame_index + 1) % AppState::max_frames_in_flight; + } + done: + // }}} + // {{{ Cleanup - prRhiDeviceWaitIdle(device); + prRhiDeviceWaitIdle(app.device); - _blitDestroy(&blit, device); - prNodeEvalDestroy(device); - prRhiDestroyTexture(device, read_texture); - prRhiDestroySampler(device, shared_sampler); - prTexturePoolDestroy(&pool, device); - prNodeManagerDestroy(&mgr); + prRhiDestroyPipeline(app.device, app.pipeline); + prRhiDestroyPipelineLayout(app.device, app.pipeline_layout); + prRhiDestroyShader(app.device, app.shader); + prRhiDestroyDescriptorPool(app.device, app.desc_pool); + prRhiDestroyDescriptorSetLayout(app.device, app.desc_set_layout); - prRhiFreeCommandBuffers(device, cmd_pool, cmd_buffers); - prRhiDestroyCommandPool(device, cmd_pool); - prRhiDestroySemaphore(device, render_completed); - prRhiDestroySemaphore(device, image_acquired); - prRhiDestroyFence(device, fence); - prRhiDestroySwapchain(device, swapchain); - prRhiDestroyDevice(device); - prRhiDestroySurface(inst, surface); - prRhiDestroyInstance(inst); + for (u32 i = 0; i < AppState::texture_count; ++i) { + prRhiDestroySampler(app.device, app.textures[i].sampler); + prRhiDestroyTexture(app.device, app.textures[i].texture); + } - SDL_DestroyWindow(window); + prRhiFreeCommandBuffers(app.device, app.cmd_pool, app.cmd_buffers); + prRhiDestroyCommandPool(app.device, app.cmd_pool); + + for (u32 i = 0; i < swapchain_image_count; ++i) { + prRhiDestroySemaphore(app.device, app.render_completed_semaphores[i]); + } + for (u32 i = 0; i < AppState::max_frames_in_flight; ++i) { + prRhiDestroySemaphore(app.device, app.image_acquired_semaphores[i]); + prRhiDestroyFence(app.device, app.fences[i]); + prRhiDestroyBuffer(app.device, app.shader_data_bufs[i]); + } + prRhiDestroyBuffer(app.device, app.vert_index_buf); + + prRhiDestroySwapchain(app.device, app.swapchain); + prRhiDestroyDevice(app.device); + prRhiDestroySurface(app.inst, app.surface); + prRhiDestroyInstance(app.inst); + + SDL_DestroyWindow(app.window); SDL_Quit(); + prRhiDestroy(); + wpMemArenaAllocatorDestroy(&arena); // }}} return EXIT_CODE_SUCCESS; diff --git a/src/prism/core/pr_graph.c b/src/prism/core/pr_graph.c deleted file mode 100644 index 89292b5..0000000 --- a/src/prism/core/pr_graph.c +++ /dev/null @@ -1,212 +0,0 @@ -// vim:fileencoding=utf-8:foldmethod=marker - -#include "pr_graph.h" -#include "../../vendor/wapp/wapp.h" -#include - -// --------------------------------------------------------------------------- -// Internal: unlink edge helpers -// --------------------------------------------------------------------------- - -static void _unlinkForward(PrGraph *graph, u64 from_idx, PrGraphEdge *edge) { - PrGraphVertex *vtx = &graph->vertices[from_idx]; - PrGraphEdge *curr = vtx->next_forward; - PrGraphEdge *prev = NULL; - while (curr) { - if (curr == edge) { - if (prev) { - prev->next_forward = curr->next_forward; - } else { - vtx->next_forward = curr->next_forward; - } - return; - } - prev = curr; - curr = curr->next_forward; - } -} - -static void _unlinkBackward(PrGraph *graph, u64 to_idx, PrGraphEdge *edge) { - PrGraphVertex *vtx = &graph->vertices[to_idx]; - PrGraphEdge *curr = vtx->next_backward; - PrGraphEdge *prev = NULL; - while (curr) { - if (curr == edge) { - if (prev) { - prev->next_backward = curr->next_backward; - } else { - vtx->next_backward = curr->next_backward; - } - return; - } - prev = curr; - curr = curr->next_backward; - } -} - -// --------------------------------------------------------------------------- -// Graph lifecycle -// --------------------------------------------------------------------------- - -wp_extern void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity) { - memset(graph, 0, sizeof(*graph)); - graph->capacity = capacity; - - graph->vertices = wpArrayAllocCapacity(PrGraphVertex, allocator, capacity, WP_ARRAY_INIT_FILLED); - if (!graph->vertices) { - graph->capacity = 0; - return; - } - - prPoolInit(&graph->edge_pool, sizeof(PrGraphEdge), 64); -} - -wp_extern void prGraphDestroy(PrGraph *graph) { - prPoolDestroy(&graph->edge_pool); - // vertices are owned by the wapp allocator passed to prGraphInit - memset(graph, 0, sizeof(*graph)); -} - -// --------------------------------------------------------------------------- -// Vertex lifecycle -// --------------------------------------------------------------------------- - -wp_extern void prGraphAddVertex(PrGraph *graph, u64 idx) { - graph->vertices[idx].active = true; - graph->vertex_count++; - if (idx >= graph->max_vertex_ever) { - graph->max_vertex_ever = idx + 1; - } -} - -wp_extern void prGraphRemoveVertex(PrGraph *graph, u64 idx) { - PrGraphVertex *vtx = &graph->vertices[idx]; - if (!vtx->active) { return; } - - // Free outgoing edges: unlink from each target's backward list - PrGraphEdge *curr = vtx->next_forward; - while (curr) { - PrGraphEdge *next = curr->next_forward; - _unlinkBackward(graph, curr->target_idx, curr); - prPoolFree(&graph->edge_pool, curr); - curr = next; - } - - // Free incoming edges: unlink from each source's forward list - curr = vtx->next_backward; - while (curr) { - PrGraphEdge *next = curr->next_backward; - _unlinkForward(graph, curr->source_idx, curr); - prPoolFree(&graph->edge_pool, curr); - curr = next; - } - - vtx->next_forward = NULL; - vtx->next_backward = NULL; - vtx->active = false; - graph->vertex_count--; -} - -// --------------------------------------------------------------------------- -// Edge management -// --------------------------------------------------------------------------- - -wp_extern b8 prGraphEdgeExists(const PrGraph *graph, u64 from_idx, u64 to_idx) { - PrGraphVertex *vtx = &graph->vertices[from_idx]; - PrGraphEdge *curr = vtx->next_forward; - while (curr) { - if (curr->target_idx == to_idx) { return true; } - curr = curr->next_forward; - } - return false; -} - -wp_extern b8 prGraphAddEdge(PrGraph *graph, u64 from_idx, u64 to_idx) { - PrGraphEdge *edge = (PrGraphEdge *)prPoolAlloc(&graph->edge_pool); - if (!edge) { return false; } - - edge->source_idx = from_idx; - edge->target_idx = to_idx; - - // Link into adjacency chains - PrGraphVertex *src = &graph->vertices[from_idx]; - PrGraphVertex *dst = &graph->vertices[to_idx]; - edge->next_forward = src->next_forward; - edge->next_backward = dst->next_backward; - src->next_forward = edge; - dst->next_backward = edge; - - // Check whether the new edge created a cycle - WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16)); - WpU64Array sorted = prGraphTopologicalSort(graph, &scratch); - u64 sorted_n = sorted ? wpArrayCount(sorted) : 0; - if (sorted_n < graph->vertex_count) { - _unlinkForward(graph, from_idx, edge); - _unlinkBackward(graph, to_idx, edge); - prPoolFree(&graph->edge_pool, edge); - return false; - } - return true; -} - -wp_extern u64 prGraphVertexCount(const PrGraph *graph) { - return graph->vertex_count; -} - -// --------------------------------------------------------------------------- -// Kahn's algorithm — topological sort / cycle detection -// --------------------------------------------------------------------------- - -wp_extern WpU64Array prGraphTopologicalSort(const PrGraph *graph, const WpAllocator *allocator) { - if (graph->vertex_count == 0) { return NULL; } - if (!graph->vertices || graph->capacity == 0) { return NULL; } - - WpU64Array result = wpArrayAllocCapacity(u64, allocator, graph->vertex_count, WP_ARRAY_INIT_NONE); - if (!result) { return NULL; } - - WpAllocator local_arena = wpMemArenaAllocatorInitZero(KiB(16)); - - WpU64Array in_degree = wpArrayAllocCapacity(u64, &local_arena, graph->capacity, WP_ARRAY_INIT_FILLED); - if (!in_degree) { return result; } - memset(in_degree, 0, wpArrayCapacity(in_degree) * sizeof(u64)); - - for (u64 i = 0; i < graph->max_vertex_ever; i++) { - if (!graph->vertices[i].active) { continue; } - - PrGraphEdge *curr = graph->vertices[i].next_forward; - while (curr) { - in_degree[curr->target_idx]++; - curr = curr->next_forward; - } - } - - WpQueue queue = wpQueueAlloc(u64, &local_arena, graph->vertex_count); - - for (u64 i = 0; i < graph->max_vertex_ever; i++) { - if (!graph->vertices[i].active) { continue; } - if (in_degree[i] == 0) { - wpQueuePush(u64, &queue, &i); - } - } - - while (queue.count > 0) { - u64 *node_idx = wpQueuePop(u64, &queue); - if (!node_idx) { break; } - - wpArrayAppendCapped(u64, result, node_idx); - - PrGraphEdge *curr = graph->vertices[*node_idx].next_forward; - while (curr) { - u64 target_idx = curr->target_idx; - if (in_degree[target_idx] > 0) { - in_degree[target_idx]--; - if (in_degree[target_idx] == 0) { - wpQueuePush(u64, &queue, &target_idx); - } - } - curr = curr->next_forward; - } - } - - return result; -} diff --git a/src/prism/core/pr_graph.h b/src/prism/core/pr_graph.h deleted file mode 100644 index 35c3a92..0000000 --- a/src/prism/core/pr_graph.h +++ /dev/null @@ -1,63 +0,0 @@ -// vim:fileencoding=utf-8:foldmethod=marker - -#ifndef PR_GRAPH_H -#define PR_GRAPH_H - -#include "../../vendor/wapp/common/aliases/aliases.h" -#include "../../vendor/wapp/base/mem/allocator/mem_allocator.h" -#include "../../vendor/wapp/base/wapp_base.h" -#include "../allocators/pr_pool_allocator.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// --------------------------------------------------------------------------- -// PrGraphEdge — separately allocated adjacency list node -// --------------------------------------------------------------------------- - -typedef struct PrGraphEdge PrGraphEdge; -struct PrGraphEdge { - PrGraphEdge *next_forward; - PrGraphEdge *next_backward; - u64 source_idx; - u64 target_idx; -}; - -// --------------------------------------------------------------------------- -// PrGraphVertex — compact adjacency head -// --------------------------------------------------------------------------- - -typedef struct PrGraphVertex PrGraphVertex; -struct PrGraphVertex { - PrGraphEdge *next_forward; - PrGraphEdge *next_backward; - b8 active; -}; - -// --------------------------------------------------------------------------- -// PrGraph — owns topology (edges + adjacency heads) -// --------------------------------------------------------------------------- - -typedef struct { - PrPool edge_pool; - PrGraphVertex *vertices; - u64 capacity; - u64 max_vertex_ever; - u64 vertex_count; -} PrGraph; - -void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity); -void prGraphDestroy(PrGraph *graph); -void prGraphAddVertex(PrGraph *graph, u64 idx); -void prGraphRemoveVertex(PrGraph *graph, u64 idx); -b8 prGraphAddEdge(PrGraph *graph, u64 from_idx, u64 to_idx); -b8 prGraphEdgeExists(const PrGraph *graph, u64 from_idx, u64 to_idx); -u64 prGraphVertexCount(const PrGraph *graph); -WpU64Array prGraphTopologicalSort(const PrGraph *graph, const WpAllocator *allocator); - -#ifdef __cplusplus -} -#endif - -#endif // !PR_GRAPH_H diff --git a/src/prism/core/pr_node.c b/src/prism/core/pr_node.c deleted file mode 100644 index 8b7cf99..0000000 --- a/src/prism/core/pr_node.c +++ /dev/null @@ -1,99 +0,0 @@ -// vim:fileencoding=utf-8:foldmethod=marker - -#include "pr_node.h" -#include "../../vendor/wapp/wapp.h" -#include - -// --------------------------------------------------------------------------- -// Node manager lifecycle -// --------------------------------------------------------------------------- - -wp_extern void prNodeManagerInit(PrNodeManager *mgr, WpAllocator *allocator, u64 capacity) { - memset(mgr, 0, sizeof(*mgr)); - mgr->capacity = capacity; - - mgr->nodes = wpArrayAllocCapacity(PrNode, allocator, capacity, WP_ARRAY_INIT_FILLED); - if (!mgr->nodes) { - mgr->capacity = 0; - return; - } - - mgr->free_head = 0; - for (u64 i = 0; i < capacity; ++i) { - mgr->nodes[i].next_free = i < capacity - 1 ? i + 1 : INVALID_NODE_INDEX; - } - - prGraphInit(&mgr->graph, allocator, capacity); -} - -wp_extern void prNodeManagerDestroy(PrNodeManager *mgr) { - prGraphDestroy(&mgr->graph); - // nodes are owned by the wapp allocator passed to prNodeManagerInit - memset(mgr, 0, sizeof(*mgr)); -} - -// --------------------------------------------------------------------------- -// Handle queries -// --------------------------------------------------------------------------- - -wp_extern b8 prNodeManagerIsStaleNode(const PrNodeManager *mgr, PrNodeId id) { - u64 generation = mgr->nodes[id.index].generation; - return id.generation != generation; -} - -wp_extern b8 prNodeManagerIsActiveNode(const PrNodeManager *mgr, PrNodeId id) { - u64 next_free = mgr->nodes[id.index].next_free; - return !prNodeManagerIsStaleNode(mgr, id) && next_free == INVALID_NODE_INDEX; -} - -wp_extern PrNodeId prNodeManagerGetNode(const PrNodeManager *mgr, u64 index) { - return (PrNodeId){ .index = index, .generation = mgr->nodes[index].generation }; -} - -// --------------------------------------------------------------------------- -// Node lifecycle -// --------------------------------------------------------------------------- - -wp_extern PrNodeId prNodeManagerAddNode(PrNodeManager *mgr, PrNodeType type) { - u64 idx = mgr->free_head; - if (idx == INVALID_NODE_INDEX) { return INVALID_NODE_ID; } - - PrNode *node = &mgr->nodes[idx]; - - mgr->free_head = node->next_free; - node->next_free = INVALID_NODE_INDEX; - node->type = type; - memset(&node->params, 0, sizeof(node->params)); - - mgr->count++; - if (idx + 1 > mgr->max_count_ever) { mgr->max_count_ever = idx + 1; } - - prGraphAddVertex(&mgr->graph, idx); - - return (PrNodeId){ .index = idx, .generation = node->generation }; -} - -wp_extern void prNodeManagerRemoveNode(PrNodeManager *mgr, PrNodeId id) { - if (!prNodeManagerIsActiveNode(mgr, id)) { return; } - - // Tear down all edges incident to this node and mark vertex inactive - prGraphRemoveVertex(&mgr->graph, id.index); - - // Return node slot to free list with bumped generation - PrNode *node = &mgr->nodes[id.index]; - node->generation++; - node->next_free = mgr->free_head; - mgr->free_head = id.index; - mgr->count--; -} - -// --------------------------------------------------------------------------- -// Edge management -// --------------------------------------------------------------------------- - -wp_extern void prNodeManagerAddEdge(PrNodeManager *mgr, PrNodeId from, PrNodeId to) { - if (!prNodeManagerIsActiveNode(mgr, from) || !prNodeManagerIsActiveNode(mgr, to)) { return; } - if (from.index == to.index) { return; } - if (prGraphEdgeExists(&mgr->graph, from.index, to.index)) { return; } - prGraphAddEdge(&mgr->graph, from.index, to.index); -} diff --git a/src/prism/core/pr_node.h b/src/prism/core/pr_node.h deleted file mode 100644 index 3fb8f54..0000000 --- a/src/prism/core/pr_node.h +++ /dev/null @@ -1,93 +0,0 @@ -// vim:fileencoding=utf-8:foldmethod=marker - -#ifndef PR_NODE_H -#define PR_NODE_H - -#include "../../vendor/wapp/common/aliases/aliases.h" -#include "pr_graph.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// Forward declarations -typedef struct PrRhiTexture PrRhiTexture; - -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -#define INVALID_NODE_INDEX (u64)-1 -#define INVALID_NODE_ID ((PrNodeId){ .index = INVALID_NODE_INDEX, .generation = INVALID_NODE_INDEX }) - -// --------------------------------------------------------------------------- -// PrNodeType -// --------------------------------------------------------------------------- - -typedef enum { - PR_NODE_TYPE_NONE, - PR_NODE_TYPE_READ, - PR_NODE_TYPE_BLUR, - PR_NODE_TYPE_GRADE, - PR_NODE_TYPE_BLEND, - - COUNT_NODE_TYPES -} PrNodeType; - -// --------------------------------------------------------------------------- -// PrNodeId — generational handle -// --------------------------------------------------------------------------- - -typedef struct { - u64 index; - u64 generation; -} PrNodeId; - -// --------------------------------------------------------------------------- -// PrNode — compositor node data -// --------------------------------------------------------------------------- - -typedef struct { - union { - //WpStr8 path; // READ: texture path - f32 radius; // BLUR: radius - struct { // GRADE: colour grading - f32 gain; - f32 offset; - f32 power; - } grade; - u32 mode; // BLEND: 0=over, 1=under, 2=add - } params; - PrRhiTexture *texture; // READ: persistent KTX texture - PrNodeType type; - u64 generation; - u64 next_free; -} PrNode; - -// --------------------------------------------------------------------------- -// PrNodeManager — owns node data + handle lifecycle + topology -// --------------------------------------------------------------------------- - -typedef struct { - PrNode *nodes; - PrGraph graph; - u64 capacity; - u64 max_count_ever; - u64 count; - u64 free_head; -} PrNodeManager; - -void prNodeManagerInit(PrNodeManager *mgr, WpAllocator *allocator, u64 capacity); -void prNodeManagerDestroy(PrNodeManager *mgr); -b8 prNodeManagerIsStaleNode(const PrNodeManager *mgr, PrNodeId id); -b8 prNodeManagerIsActiveNode(const PrNodeManager *mgr, PrNodeId id); -PrNodeId prNodeManagerGetNode(const PrNodeManager *mgr, u64 index); -PrNodeId prNodeManagerAddNode(PrNodeManager *mgr, PrNodeType type); -void prNodeManagerRemoveNode(PrNodeManager *mgr, PrNodeId id); -void prNodeManagerAddEdge(PrNodeManager *mgr, PrNodeId from, PrNodeId to); - -#ifdef __cplusplus -} -#endif - -#endif // !PR_NODE_H diff --git a/src/prism/core/pr_node_eval.c b/src/prism/core/pr_node_eval.c deleted file mode 100644 index 10b2430..0000000 --- a/src/prism/core/pr_node_eval.c +++ /dev/null @@ -1,358 +0,0 @@ -// vim:fileencoding=utf-8:foldmethod=marker - -#include "pr_node_eval.h" -#include "../rhi/pr_rhi.h" -#include "../../vendor/wapp/wapp.h" -#include -#include -#include - -wp_intern PrNodeTypeEntry _node_type_table_data[COUNT_NODE_TYPES] = { - [PR_NODE_TYPE_READ] = { - .type = PR_NODE_TYPE_READ, - .shader_type = PR_SHADER_TYPE_FRAGMENT, - .vertex_shader_path = "assets/shaders/blit.vert.spv", - .fragment_shader_path= "assets/shaders/read.frag.spv", - .input_count = 1, - .output_count = 1, - .push_constant_size = 0, - }, - [PR_NODE_TYPE_BLUR] = { - .type = PR_NODE_TYPE_BLUR, - .shader_type = PR_SHADER_TYPE_FRAGMENT, - .vertex_shader_path = "assets/shaders/blit.vert.spv", - .fragment_shader_path= "assets/shaders/blur.frag.spv", - .input_count = 1, - .output_count = 1, - .push_constant_size = sizeof(PrBlurPushConstants), - }, - [PR_NODE_TYPE_GRADE] = { - .type = PR_NODE_TYPE_GRADE, - .shader_type = PR_SHADER_TYPE_FRAGMENT, - .vertex_shader_path = "assets/shaders/blit.vert.spv", - .fragment_shader_path= "assets/shaders/grade.frag.spv", - .input_count = 1, - .output_count = 1, - .push_constant_size = sizeof(PrGradePushConstants), - }, - [PR_NODE_TYPE_BLEND] = { - .type = PR_NODE_TYPE_BLEND, - .shader_type = PR_SHADER_TYPE_FRAGMENT, - .vertex_shader_path = "assets/shaders/blit.vert.spv", - .fragment_shader_path= "assets/shaders/blend.frag.spv", - .input_count = 2, - .output_count = 1, - .push_constant_size = sizeof(PrBlendPushConstants), - }, -}; - -PrNodeTypeEntry pr_node_type_table[COUNT_NODE_TYPES]; - -wp_persist PrRhiDescriptorPool *_eval_desc_pool; - -wp_intern void *_loadSpirv(const WpAllocator *alloc, const char *path, u64 *out_size) { - u64 path_len = strlen(path); - WpStr8RO filepath = { path_len, path_len, (c8 *)path }; - WpFile *f = wpFileOpen(alloc, &filepath, WP_ACCESS_READ); - if (!f) { - fprintf(stderr, "failed to open SPIR-V: %s\n", path); - abort(); - } - i64 file_size = wpFileGetLength(f); - if (file_size <= 0) { - wpFileClose(f); - fprintf(stderr, "empty SPIR-V file: %s\n", path); - abort(); - } - void *code = wpMemAllocatorAlloc(alloc, (u64)file_size); - if (!code) { wpFileClose(f); abort(); } - u64 bytes_read = wpFileRead(code, f, (u64)file_size); - wpFileClose(f); - if (bytes_read != (u64)file_size) { - fprintf(stderr, "short read on SPIR-V: %s\n", path); - abort(); - } - *out_size = (u64)file_size; - return code; -} - -wp_extern void prNodeEvalInit(PrRhiDevice *device, PrRhiFormat output_format) { - (void)output_format; - memcpy(pr_node_type_table, _node_type_table_data, sizeof(_node_type_table_data)); - - for (u32 i = 0; i < COUNT_NODE_TYPES; ++i) { - PrNodeTypeEntry *entry = &pr_node_type_table[i]; - if (entry->shader_type != PR_SHADER_TYPE_FRAGMENT) { continue; } - if (!entry->vertex_shader_path || !entry->fragment_shader_path) { continue; } - - // load SPIR-V - u64 vert_size = 0, frag_size = 0; - void *vert_code = _loadSpirv(&_G_RHI_CONTEXT.allocator, entry->vertex_shader_path, &vert_size); - void *frag_code = _loadSpirv(&_G_RHI_CONTEXT.allocator, entry->fragment_shader_path, &frag_size); - - // create shaders - entry->vertex_shader = prRhiCreateShader(device, (PrRhiShaderDesc){ - .spirv_code = vert_code, - .spirv_size = vert_size, - }); - entry->fragment_shader = prRhiCreateShader(device, (PrRhiShaderDesc){ - .spirv_code = frag_code, - .spirv_size = frag_size, - }); - - // free SPIR-V (now owned by Vulkan) - wpMemAllocatorFree(&_G_RHI_CONTEXT.allocator, &vert_code, vert_size); - wpMemAllocatorFree(&_G_RHI_CONTEXT.allocator, &frag_code, frag_size); - - // create descriptor set layout - PrRhiDescriptorSetLayoutBindingArray bindings = NULL; - if (entry->input_count > 0) { - bindings = wpArrayWithCapacity(PrRhiDescriptorSetLayoutBinding, 4, WP_ARRAY_INIT_NONE); - for (u32 b = 0; b < entry->input_count; ++b) { - PrRhiDescriptorSetLayoutBinding binding = { - .type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptor_count = 1, - .stage_flags = PR_RHI_SHADER_STAGE_FRAGMENT, - .binding_flags = (PrRhiDescriptorBindingFlag)0, - }; - wpArrayAppendCapped(PrRhiDescriptorSetLayoutBinding, bindings, &binding); - } - } - entry->set_layout = prRhiCreateDescriptorSetLayout(device, (PrRhiDescriptorSetLayoutDesc){ - .bindings = bindings, - }); - - // create pipeline layout - PrRhiPushConstantRange pc_range = { - .stage_flags = PR_RHI_SHADER_STAGE_FRAGMENT, - .offset = 0, - .size = entry->push_constant_size, - }; - PrRhiDescriptorSetLayoutArray set_layouts = entry->set_layout - ? wpArray(PrRhiDescriptorSetLayout *, entry->set_layout) - : NULL; - PrRhiPushConstantRangeArray pc_ranges = entry->push_constant_size > 0 - ? wpArray(PrRhiPushConstantRange, pc_range) - : NULL; - entry->pipeline_layout = prRhiCreatePipelineLayout(device, (PrRhiPipelineLayoutDesc){ - .set_layouts = set_layouts, - .push_constant_ranges = pc_ranges, - }); - - // create graphics pipeline - // pool textures are always RGBA16F - PrRhiFormatArray color_formats = wpArray(PrRhiFormat, PR_RHI_FORMAT_R16G16B16A16_SFLOAT); - PrRhiColorBlendAttachmentArray blend_attachments = wpArray(PrRhiColorBlendAttachment, - ((PrRhiColorBlendAttachment){ .color_write_mask = 0xF })); - entry->pipeline = prRhiCreateGraphicsPipeline(device, (PrRhiGraphicsPipelineDesc){ - .vertex_shader = entry->vertex_shader, - .vertex_shader_entry_point = "main", - .fragment_shader = entry->fragment_shader, - .fragment_shader_entry_point = "main", - .vertex_bindings = NULL, - .vertex_attributes = NULL, - .topology = PR_RHI_TOPOLOGY_TRIANGLE_LIST, - .color_attachment_formats = color_formats, - .depth_attachment_format = PR_RHI_FORMAT_UNDEFINED, - .depth_test_enable = false, - .depth_write_enable = false, - .depth_compare_op = PR_RHI_COMPARE_OP_ALWAYS, - .blend_attachments = blend_attachments, - .dynamic_viewport = true, - .dynamic_scissor = true, - .polygon_mode = PR_RHI_POLYGON_MODE_FILL, - .cull_mode = PR_RHI_CULL_MODE_NONE, - .front_face = PR_RHI_FRONT_FACE_COUNTER_CLOCKWISE, - .line_width = 1.0, - .multisample_count = PR_RHI_SAMPLE_COUNT_1, - .layout = entry->pipeline_layout, - }); - } - - // create per-frame descriptor pool (reset each frame) - PrRhiDescriptorPoolSizeArray pool_sizes = wpArray(PrRhiDescriptorPoolSize, - ((PrRhiDescriptorPoolSize){ .type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptor_count = 64 })); - _eval_desc_pool = prRhiCreateDescriptorPool(device, (PrRhiDescriptorPoolDesc){ - .max_sets = 64, - .pool_sizes = pool_sizes, - }); -} - -wp_extern void prNodeEvalDestroy(PrRhiDevice *device) { - if (_eval_desc_pool) { - prRhiDestroyDescriptorPool(device, _eval_desc_pool); - _eval_desc_pool = NULL; - } - for (u32 i = 0; i < COUNT_NODE_TYPES; ++i) { - PrNodeTypeEntry *entry = &pr_node_type_table[i]; - if (entry->pipeline) { - prRhiDestroyPipeline(device, entry->pipeline); - entry->pipeline = NULL; - } - if (entry->pipeline_layout) { - prRhiDestroyPipelineLayout(device, entry->pipeline_layout); - entry->pipeline_layout = NULL; - } - if (entry->set_layout) { - prRhiDestroyDescriptorSetLayout(device, entry->set_layout); - entry->set_layout = NULL; - } - if (entry->fragment_shader) { - prRhiDestroyShader(device, entry->fragment_shader); - entry->fragment_shader = NULL; - } - if (entry->vertex_shader) { - prRhiDestroyShader(device, entry->vertex_shader); - entry->vertex_shader = NULL; - } - } -} - -// --------------------------------------------------------------------------- -// Per-frame evaluation -// --------------------------------------------------------------------------- - -wp_extern void prGraphEvaluate(PrNodeManager *mgr, PrRhiDevice *device, - PrTexturePool *pool, PrRhiCommandBuffer *cb, - PrRhiSampler *shared_sampler, - PrTextureSlot **out_output) { - PrGraph *graph = &mgr->graph; - u64 vertex_count = prGraphVertexCount(graph); - - if (out_output) { *out_output = NULL; } - - // 1. topological sort - WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(64)); - WpU64Array topo = prGraphTopologicalSort(graph, &scratch); - u64 topo_count = topo ? wpArrayCount(topo) : 0; - - // 2. compute initial refcounts (out-degree per node) - u32 *refcounts = wpMemAllocatorAlloc(&scratch, vertex_count * sizeof(u32)); - memset(refcounts, 0, vertex_count * sizeof(u32)); - for (u64 i = 0; i < graph->max_vertex_ever; ++i) { - if (!graph->vertices[i].active) { continue; } - PrGraphEdge *edge = graph->vertices[i].next_forward; - while (edge) { - refcounts[i]++; - edge = edge->next_forward; - } - } - - // 3. reset texture pool and descriptor pool - prTexturePoolReset(pool); - if (_eval_desc_pool) { - prRhiResetDescriptorPool(device, _eval_desc_pool); - } - - // output slot per node (transient, lives in scratch arena) - PrTextureSlot **output_slots = wpMemAllocatorAlloc(&scratch, vertex_count * sizeof(PrTextureSlot *)); - memset(output_slots, 0, vertex_count * sizeof(PrTextureSlot *)); - - // 4. for each node in topological order - for (u64 t = 0; t < topo_count; ++t) { - u64 node_idx = topo[t]; - PrNode *node = &mgr->nodes[node_idx]; - if (node->generation == 0) { continue; } // inactive node - - PrNodeTypeEntry *entry = &pr_node_type_table[node->type]; - - // acquire output texture - PrTextureSlot *output_slot = prTexturePoolAcquire(pool, device); - if (!output_slot) { abort(); } - - // gather input textures (predecessors via backward edges) - PrTextureSlot *input_slots[4]; - u32 input_count = 0; - PrGraphEdge *edge = graph->vertices[node_idx].next_backward; - while (edge && input_count < 4) { - u64 src_idx = edge->source_idx; - if (output_slots[src_idx]) { - input_slots[input_count++] = output_slots[src_idx]; - } - edge = edge->next_backward; - } - // READ nodes: use the node's own persistent texture if no edges - if (input_count == 0 && node->texture) { - input_slots[0] = NULL; - input_count = 1; - } - - // allocate descriptor set (skip for nodes with no inputs) - PrRhiDescriptorSet *desc_set = NULL; - if (entry->input_count > 0 && input_count > 0) { - desc_set = prRhiAllocateDescriptorSet(device, _eval_desc_pool, entry->set_layout, NULL); - - // build write descriptors - PrRhiDescriptorImageInfo image_infos[4]; - PrRhiWriteDescriptorSet writes[4]; - for (u32 i = 0; i < input_count; ++i) { - PrRhiTexture *tex = input_slots[i] ? input_slots[i]->texture : node->texture; - image_infos[i] = (PrRhiDescriptorImageInfo){ - .texture = tex, - .sampler = shared_sampler, - .layout = PR_RHI_LAYOUT_READ_ONLY_OPTIMAL, - }; - writes[i] = (PrRhiWriteDescriptorSet){ - .dst_set = desc_set, - .dst_binding = i, - .dst_array_element= 0, - .type = PR_RHI_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .image_info = &image_infos[i], - .buffer_info = NULL, - }; - } - prRhiUpdateDescriptorSet(device, writes); - } - - // record commands - PrRhiColorAttachment color_att = { - .texture = output_slot->texture, - .layout = PR_RHI_LAYOUT_ATTACHMENT_OPTIMAL, - .clear = true, - .clear_color = {0.0f, 0.0f, 0.0f, 0.0f}, - }; - prRhiCmdBeginRendering(cb, &color_att, NULL); - prRhiCmdBindPipeline(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS, entry->pipeline); - prRhiCmdSetViewport(cb, 0.0f, 0.0f, (f32)pool->width, (f32)pool->height); - prRhiCmdSetScissor(cb, 0, 0, pool->width, pool->height); - - if (desc_set) { - PrRhiDescriptorSet *sets_arr[1] = { desc_set }; - prRhiCmdBindDescriptorSets(cb, PR_RHI_PIPELINE_BIND_POINT_GRAPHICS, - entry->pipeline_layout, 0, sets_arr); - } - - // push constants - if (entry->push_constant_size > 0) { - prRhiCmdPushConstants(cb, entry->pipeline_layout, - PR_RHI_SHADER_STAGE_FRAGMENT, 0, - entry->push_constant_size, &node->params); - } - - prRhiCmdDraw(cb, 3, 1, 0, 0); - prRhiCmdEndRendering(cb); - - // release input textures whose refcount hit 0 - edge = graph->vertices[node_idx].next_backward; - u32 input_idx = 0; - while (edge && input_idx < input_count) { - u64 src_idx = edge->source_idx; - if (refcounts[src_idx] > 0) { - refcounts[src_idx]--; - if (refcounts[src_idx] == 0) { - prTexturePoolRelease(pool, output_slots[src_idx]); - } - } - edge = edge->next_backward; - input_idx++; - } - - output_slots[node_idx] = output_slot; - } - - // Return the last node's output as the compositor output - if (out_output && topo_count > 0) { - u64 last_idx = topo[topo_count - 1]; - *out_output = output_slots[last_idx]; - } -} diff --git a/src/prism/core/pr_node_eval.h b/src/prism/core/pr_node_eval.h deleted file mode 100644 index 2f69b72..0000000 --- a/src/prism/core/pr_node_eval.h +++ /dev/null @@ -1,95 +0,0 @@ -// vim:fileencoding=utf-8:foldmethod=marker - -#ifndef PR_NODE_EVAL_H -#define PR_NODE_EVAL_H - -#include "../../vendor/wapp/common/aliases/aliases.h" -#include "../rhi/pr_rhi_types.h" -#include "pr_node.h" -#include "pr_texture_pool.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// --------------------------------------------------------------------------- -// Shader type -// --------------------------------------------------------------------------- - -typedef enum PrShaderType { - PR_SHADER_TYPE_FRAGMENT, // fullscreen triangle, per-pixel - PR_SHADER_TYPE_COMPUTE, // dispatch, shared memory -} PrShaderType; - -// --------------------------------------------------------------------------- -// Push constant structs (one per node type) -// --------------------------------------------------------------------------- - -typedef struct { - f32 radius; -} PrBlurPushConstants; - -typedef struct { - f32 gain; - f32 offset; - f32 power; -} PrGradePushConstants; - -typedef struct { - u32 mode; // 0=over, 1=under, 2=add -} PrBlendPushConstants; - -// --------------------------------------------------------------------------- -// Node type entry — maps a node type to its resource signature -// --------------------------------------------------------------------------- - -typedef struct PrNodeTypeEntry { - PrNodeType type; - PrShaderType shader_type; - - // shader paths (pre-compiled SPIR-V, built from .slang via slangc) - const char *vertex_shader_path; // NULL for compute - const char *fragment_shader_path; // NULL for compute - const char *compute_shader_path; // NULL for fragment - - // resource signature - u32 input_count; // number of texture inputs - u32 output_count; // always 1 for V1 - - // push constant size (bytes) - u32 push_constant_size; - - // created at init, cached here - PrRhiShader *vertex_shader; - PrRhiShader *fragment_shader; - PrRhiDescriptorSetLayout *set_layout; - PrRhiPipelineLayout *pipeline_layout; - PrRhiPipeline *pipeline; -} PrNodeTypeEntry; - -// --------------------------------------------------------------------------- -// Global registry -// --------------------------------------------------------------------------- - -extern PrNodeTypeEntry pr_node_type_table[COUNT_NODE_TYPES]; - -// --------------------------------------------------------------------------- -// Evaluation -// --------------------------------------------------------------------------- - -wp_extern void prNodeEvalInit(PrRhiDevice *device, PrRhiFormat output_format); -wp_extern void prNodeEvalDestroy(PrRhiDevice *device); - -// Per-frame graph evaluation. Records commands into cb. -// pool is reset each frame. desc_pool is reset each frame. -// out_output receives the final compositor output slot (last node in topo order). -wp_extern void prGraphEvaluate(PrNodeManager *mgr, PrRhiDevice *device, - PrTexturePool *pool, PrRhiCommandBuffer *cb, - PrRhiSampler *shared_sampler, - PrTextureSlot **out_output); - -#ifdef __cplusplus -} -#endif - -#endif // !PR_NODE_EVAL_H diff --git a/src/prism/core/pr_texture_pool.c b/src/prism/core/pr_texture_pool.c deleted file mode 100644 index 26b995b..0000000 --- a/src/prism/core/pr_texture_pool.c +++ /dev/null @@ -1,101 +0,0 @@ -#include "pr_texture_pool.h" -#include "../rhi/pr_rhi.h" -#include "../../vendor/wapp/wapp.h" -#include -#include -#include - -wp_intern b8 _growPool(PrTexturePool *pool, PrRhiDevice *device) { - u32 old_count = pool->count; - u32 new_count = old_count + PR_TEXTURE_POOL_GROWTH_BATCH; - if (new_count > pool->max) { new_count = pool->max; } - if (old_count >= pool->max) { - fprintf(stderr, "texture pool exhausted: %u in use, max %u\n", pool->in_use, pool->max); - abort(); - } - - PrTextureSlot *new_slots = wpArrayAllocCapacity(PrTextureSlot, pool->alloc, new_count, WP_ARRAY_INIT_FILLED); - if (!new_slots) { return false; } - if (pool->slots) { - memcpy(new_slots, pool->slots, old_count * sizeof(PrTextureSlot)); - wpArrayDealloc(PrTextureSlot, pool->alloc, &pool->slots); - } - pool->slots = new_slots; - - for (u32 i = old_count; i < new_count; ++i) { - PrRhiTextureDesc desc = { - .format = PR_RHI_FORMAT_R16G16B16A16_SFLOAT, - .width = pool->width, - .height = pool->height, - .mip_levels = 1, - .usage = PR_RHI_TEXTURE_USAGE_SAMPLED | PR_RHI_TEXTURE_USAGE_COLOR_ATTACHMENT, - }; - PrRhiTexture *tex = prRhiCreateTexture(device, desc); - if (!tex) { return false; } - pool->slots[i].texture = tex; - pool->slots[i].refcount = 0; - pool->slots[i].in_use = false; - } - pool->count = new_count; - return true; -} - -wp_extern void prTexturePoolInit(PrTexturePool *pool, u32 initial_capacity, u32 max, u32 width, u32 height, const WpAllocator *alloc) { - pool->alloc = alloc; - pool->slots = NULL; - pool->count = 0; - pool->in_use = 0; - pool->max = max; - pool->width = width; - pool->height = height; - if (initial_capacity > 0) { - pool->slots = wpArrayAllocCapacity(PrTextureSlot, alloc, initial_capacity, WP_ARRAY_INIT_FILLED); - if (!pool->slots) { - fprintf(stderr, "texture pool initial allocation failed\n"); - abort(); - } - pool->count = initial_capacity; - } -} - -wp_extern void prTexturePoolReset(PrTexturePool *pool) { - for (u32 i = 0; i < pool->count; ++i) { - pool->slots[i].refcount = 0; - pool->slots[i].in_use = false; - } - pool->in_use = 0; -} - -wp_extern PrTextureSlot *prTexturePoolAcquire(PrTexturePool *pool, PrRhiDevice *device) { - for (u32 i = 0; i < pool->count; ++i) { - if (!pool->slots[i].in_use) { - pool->slots[i].in_use = true; - pool->in_use += 1; - return &pool->slots[i]; - } - } - if (!_growPool(pool, device)) { return NULL; } - PrTextureSlot *slot = &pool->slots[pool->count - 1]; - slot->in_use = true; - pool->in_use += 1; - return slot; -} - -wp_extern void prTexturePoolRelease(PrTexturePool *pool, PrTextureSlot *slot) { - (void)pool; - slot->refcount = 0; - slot->in_use = false; - pool->in_use -= 1; -} - -wp_extern void prTexturePoolDestroy(PrTexturePool *pool, PrRhiDevice *device) { - for (u32 i = 0; i < pool->count; ++i) { - if (pool->slots[i].texture) { - prRhiDestroyTexture(device, pool->slots[i].texture); - } - } - if (pool->slots) { wpArrayDealloc(PrTextureSlot, pool->alloc, &pool->slots); } - pool->slots = NULL; - pool->count = 0; - pool->in_use = 0; -} diff --git a/src/prism/core/pr_texture_pool.h b/src/prism/core/pr_texture_pool.h deleted file mode 100644 index bb67943..0000000 --- a/src/prism/core/pr_texture_pool.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef PR_TEXTURE_POOL_H -#define PR_TEXTURE_POOL_H - -#include "../rhi/pr_rhi_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define PR_TEXTURE_POOL_GROWTH_BATCH 8 - -typedef struct PrTextureSlot { - PrRhiTexture *texture; - u32 refcount; - b8 in_use; -} PrTextureSlot; - -typedef struct PrTexturePool { - PrTextureSlot *slots; - const WpAllocator *alloc; - u32 count; - u32 in_use; - u32 max; - u32 width; - u32 height; -} PrTexturePool; - -wp_extern void prTexturePoolInit(PrTexturePool *pool, u32 initial_capacity, u32 max, u32 width, u32 height, const WpAllocator *alloc); -wp_extern void prTexturePoolReset(PrTexturePool *pool); -wp_extern PrTextureSlot *prTexturePoolAcquire(PrTexturePool *pool, PrRhiDevice *device); -wp_extern void prTexturePoolRelease(PrTexturePool *pool, PrTextureSlot *slot); -wp_extern void prTexturePoolDestroy(PrTexturePool *pool, PrRhiDevice *device); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/prism/rhi/pr_rhi.h b/src/prism/rhi/pr_rhi.h index 064b91b..ae4bcdd 100644 --- a/src/prism/rhi/pr_rhi.h +++ b/src/prism/rhi/pr_rhi.h @@ -162,8 +162,6 @@ PrRhiDescriptorPool *prRhiCreateDescriptorPool(PrRhiDevice *device, PrRhiDescriptorPoolDesc desc); void prRhiDestroyDescriptorPool(PrRhiDevice *device, PrRhiDescriptorPool *pool); -void prRhiResetDescriptorPool(PrRhiDevice *device, - PrRhiDescriptorPool *pool); // ====================================================================== // Descriptor sets diff --git a/src/prism/rhi/vulkan/pr_rhi_vk.c b/src/prism/rhi/vulkan/pr_rhi_vk.c index 8ef9e03..60b9cdd 100644 --- a/src/prism/rhi/vulkan/pr_rhi_vk.c +++ b/src/prism/rhi/vulkan/pr_rhi_vk.c @@ -1399,33 +1399,27 @@ PrRhiPipelineLayout *prRhiCreatePipelineLayoutVk(PrRhiDevice *device, u32 layout_count = (desc.set_layouts && wpArrayCount(desc.set_layouts) > 0) ? (u32)wpArrayCount(desc.set_layouts) : 0; - VkDescriptorSetLayoutArray vk_layouts = NULL; - if (layout_count > 0) { - vk_layouts = - wpArrayAllocCapacity(VkDescriptorSetLayout, &_G_RHI_CONTEXT.allocator, layout_count, - WP_ARRAY_INIT_FILLED); - if (!vk_layouts) { _abort("alloc failed for VkDescriptorSetLayout array"); } + VkDescriptorSetLayoutArray vk_layouts = + wpArrayAllocCapacity(VkDescriptorSetLayout, &_G_RHI_CONTEXT.allocator, layout_count, + WP_ARRAY_INIT_FILLED); + if (!vk_layouts) { _abort("alloc failed for VkDescriptorSetLayout array"); } - for (u32 i = 0; i < layout_count; ++i) { - vk_layouts[i] = desc.set_layouts[i]->handle; - } + for (u32 i = 0; i < layout_count; ++i) { + vk_layouts[i] = desc.set_layouts[i]->handle; } // Gather push constant ranges u32 pc_count = (desc.push_constant_ranges && wpArrayCount(desc.push_constant_ranges) > 0) ? (u32)wpArrayCount(desc.push_constant_ranges) : 0; - VkPushConstantRangeArray pc_ranges = NULL; - if (pc_count > 0) { - pc_ranges = - wpArrayAllocCapacity(VkPushConstantRange, &_G_RHI_CONTEXT.allocator, pc_count, WP_ARRAY_INIT_NONE); - if (!pc_ranges) { _abort("alloc failed for VkPushConstantRange array"); } + VkPushConstantRangeArray pc_ranges = + wpArrayAllocCapacity(VkPushConstantRange, &_G_RHI_CONTEXT.allocator, pc_count, WP_ARRAY_INIT_NONE); + if (!pc_ranges) { _abort("alloc failed for VkPushConstantRange array"); } - for (u32 i = 0; i < pc_count; ++i) { - pc_ranges[i].stageFlags = _toVkShaderStage(desc.push_constant_ranges[i].stage_flags); - pc_ranges[i].offset = desc.push_constant_ranges[i].offset; - pc_ranges[i].size = desc.push_constant_ranges[i].size; - } + for (u32 i = 0; i < pc_count; ++i) { + pc_ranges[i].stageFlags = _toVkShaderStage(desc.push_constant_ranges[i].stage_flags); + pc_ranges[i].offset = desc.push_constant_ranges[i].offset; + pc_ranges[i].size = desc.push_constant_ranges[i].size; } VkPipelineLayoutCreateInfo pl_info = { @@ -1439,8 +1433,8 @@ PrRhiPipelineLayout *prRhiCreatePipelineLayoutVk(PrRhiDevice *device, VkPipelineLayout vk_layout = VK_NULL_HANDLE; _checkVk(vkCreatePipelineLayout(vk_device, &pl_info, NULL, &vk_layout), "vkCreatePipelineLayout"); - if (vk_layouts) { wpArrayDealloc(VkDescriptorSetLayout, &_G_RHI_CONTEXT.allocator, &vk_layouts); } - if (pc_ranges) { wpArrayDealloc(VkPushConstantRange, &_G_RHI_CONTEXT.allocator, &pc_ranges); } + wpArrayDealloc(VkDescriptorSetLayout, &_G_RHI_CONTEXT.allocator, &vk_layouts); + wpArrayDealloc(VkPushConstantRange, &_G_RHI_CONTEXT.allocator, &pc_ranges); PrRhiPipelineLayout *layout = wpMemAllocatorAlloc(&_G_RHI_CONTEXT.allocator, sizeof(PrRhiPipelineLayout)); if (!layout) { _abort("alloc failed for PrRhiPipelineLayout"); } @@ -1764,10 +1758,6 @@ void prRhiDestroyDescriptorPoolVk(PrRhiDevice *device, PrRhiDescriptorPool *pool wpMemAllocatorFree(&_G_RHI_CONTEXT.allocator, (void**)&pool, sizeof(PrRhiDescriptorPool)); } -void prRhiResetDescriptorPoolVk(PrRhiDevice *device, PrRhiDescriptorPool *pool) { - vkResetDescriptorPool(device->handle, pool->handle, 0); -} - // ============================================================================ // Descriptor sets // ============================================================================ diff --git a/src/prism/rhi/vulkan/pr_rhi_vk.h b/src/prism/rhi/vulkan/pr_rhi_vk.h index 361ca1b..b601723 100644 --- a/src/prism/rhi/vulkan/pr_rhi_vk.h +++ b/src/prism/rhi/vulkan/pr_rhi_vk.h @@ -193,8 +193,6 @@ PrRhiDescriptorPool *prRhiCreateDescriptorPoolVk(PrRhiDevice *device, PrRhiDescriptorPoolDesc desc); void prRhiDestroyDescriptorPoolVk(PrRhiDevice *device, PrRhiDescriptorPool *pool); -void prRhiResetDescriptorPoolVk(PrRhiDevice *device, - PrRhiDescriptorPool *pool); PrRhiDescriptorSet *prRhiAllocateDescriptorSetVk(PrRhiDevice *device, PrRhiDescriptorPool *pool, diff --git a/src/prism/rhi/vulkan/pr_rhi_vk_aliases.h b/src/prism/rhi/vulkan/pr_rhi_vk_aliases.h index d43adda..4f0b38d 100644 --- a/src/prism/rhi/vulkan/pr_rhi_vk_aliases.h +++ b/src/prism/rhi/vulkan/pr_rhi_vk_aliases.h @@ -48,7 +48,6 @@ #define prRhiDestroyDescriptorSetLayout prRhiDestroyDescriptorSetLayoutVk #define prRhiCreateDescriptorPool prRhiCreateDescriptorPoolVk #define prRhiDestroyDescriptorPool prRhiDestroyDescriptorPoolVk -#define prRhiResetDescriptorPool prRhiResetDescriptorPoolVk #define prRhiAllocateDescriptorSet prRhiAllocateDescriptorSetVk #define prRhiFreeDescriptorSet prRhiFreeDescriptorSetVk #define prRhiUpdateDescriptorSet prRhiUpdateDescriptorSetVk diff --git a/src/shaders/blend.frag.slang b/src/shaders/blend.frag.slang deleted file mode 100644 index d5aea10..0000000 --- a/src/shaders/blend.frag.slang +++ /dev/null @@ -1,44 +0,0 @@ -// BLEND node — composites two textures. -// 2 input textures, push constant: u32 mode (0=over, 1=under, 2=add). - -[[vk::binding(0, 0)]] -Texture2D background : register(t0); -[[vk::binding(1, 0)]] -Texture2D foreground : register(t1); -[[vk::binding(2, 0)]] -SamplerState input_sampler : register(s0); - -struct PushConstants { - uint mode; -}; - -[[vk::push_constant]] -PushConstants pc; - -struct VSOutput { - float4 position : SV_Position; - float2 uv : TEXCOORD0; -}; - -float4 main(VSOutput input) : SV_Target { - float4 bg = background.Sample(input_sampler, input.uv); - float4 fg = foreground.Sample(input_sampler, input.uv); - - float4 result; - switch (pc.mode) { - case 0: // over: foreground over background - result = fg.a * fg + (1.0 - fg.a) * bg; - break; - case 1: // under: background over foreground - result = bg.a * bg + (1.0 - bg.a) * fg; - break; - case 2: // add: additive blend - result = bg + fg; - break; - default: - result = fg; - break; - } - - return result; -} diff --git a/src/shaders/blit.vert.slang b/src/shaders/blit.vert.slang deleted file mode 100644 index 77fd950..0000000 --- a/src/shaders/blit.vert.slang +++ /dev/null @@ -1,18 +0,0 @@ -// Fullscreen triangle — no vertex buffer needed. -// Uses gl_VertexIndex to generate a single triangle that covers the viewport. - -struct VSOutput { - float4 position : SV_Position; - float2 uv : TEXCOORD0; -}; - -VSOutput main(uint vertex_id : SV_VertexID) { - VSOutput output; - // Generate UV from vertex ID (0, 1, 2) - output.uv = float2((vertex_id << 1) & 2, vertex_id & 2); - // Generate clip-space position - output.position = float4(output.uv * 2.0 - 1.0, 0.0, 1.0); - // Flip Y for Vulkan - output.position.y = -output.position.y; - return output; -} diff --git a/src/shaders/blit_to_swap.frag.slang b/src/shaders/blit_to_swap.frag.slang deleted file mode 100644 index ef7e205..0000000 --- a/src/shaders/blit_to_swap.frag.slang +++ /dev/null @@ -1,10 +0,0 @@ -// Blit-to-swapchain fragment shader. -// Samples the compositor output (RGBA16F pool texture) and writes it -// to the swapchain color attachment. - -[vk::binding(0, 0)] Texture2D tex : register(t0); -[vk::binding(1, 0)] SamplerState smp : register(s0); - -float4 main(float4 position : SV_Position, float2 uv : SV_Target0) : SV_Target0 { - return tex.Sample(smp, uv); -} diff --git a/src/shaders/blit_to_swap.vert.slang b/src/shaders/blit_to_swap.vert.slang deleted file mode 100644 index 7cca830..0000000 --- a/src/shaders/blit_to_swap.vert.slang +++ /dev/null @@ -1,29 +0,0 @@ -// Fullscreen triangle — no vertex buffer, no inputs. -// Draws a single triangle that covers the entire viewport. -// Reused for all blit / composit passes. - -struct VsOut { - float4 position : SV_Position; - float2 uv : SV_Target0; -}; - -VsOut main(uint vertex_id : SV_VertexID) { - // Generate fullscreen triangle from vertex ID. - // vertex_id 0 → (-1,-1), 1 → (-1,3), 2 → (3,-1) - // UV flips Y so image top maps to screen top. - float2 positions[3] = { - float2(-1.0, -1.0), - float2(-1.0, 3.0), - float2( 3.0, -1.0) - }; - float2 uvs[3] = { - float2(0.0, 0.0), - float2(0.0, 2.0), - float2(2.0, 0.0) - }; - - VsOut output; - output.position = float4(positions[vertex_id], 0.0, 1.0); - output.uv = uvs[vertex_id]; - return output; -} diff --git a/src/shaders/blur.frag.slang b/src/shaders/blur.frag.slang deleted file mode 100644 index d15ff20..0000000 --- a/src/shaders/blur.frag.slang +++ /dev/null @@ -1,40 +0,0 @@ -// BLUR node — Gaussian blur with configurable radius. -// 1 input texture, push constant: f32 radius. - -[[vk::binding(0, 0)]] -Texture2D input_texture : register(t0); -[[vk::binding(1, 0)]] -SamplerState input_sampler : register(s0); - -struct PushConstants { - float radius; -}; - -[[vk::push_constant]] -PushConstants pc; - -struct VSOutput { - float4 position : SV_Position; - float2 uv : TEXCOORD0; -}; - -float4 main(VSOutput input) : SV_Target { - uint width, height; - input_texture.GetDimensions(width, height); - float2 texel_size = 1.0 / float2(width, height); - float4 result = float4(0.0, 0.0, 0.0, 0.0); - - int radius = int(pc.radius); - float weight_sum = 0.0; - - for (int x = -radius; x <= radius; ++x) { - for (int y = -radius; y <= radius; ++y) { - float2 offset = float2(x, y) * texel_size; - float weight = 1.0 / (1.0 + float(x * x + y * y)); - result += input_texture.Sample(input_sampler, input.uv + offset) * weight; - weight_sum += weight; - } - } - - return result / weight_sum; -} diff --git a/src/shaders/grade.frag.slang b/src/shaders/grade.frag.slang deleted file mode 100644 index 746106a..0000000 --- a/src/shaders/grade.frag.slang +++ /dev/null @@ -1,31 +0,0 @@ -// GRADE node — colour grading with gain, offset, power. -// 1 input texture, push constants: f32 gain, f32 offset, f32 power. - -[[vk::binding(0, 0)]] -Texture2D input_texture : register(t0); -[[vk::binding(1, 0)]] -SamplerState input_sampler : register(s0); - -struct PushConstants { - float gain; - float offset; - float power; -}; - -[[vk::push_constant]] -PushConstants pc; - -struct VSOutput { - float4 position : SV_Position; - float2 uv : TEXCOORD0; -}; - -float4 main(VSOutput input) : SV_Target { - float4 color = input_texture.Sample(input_sampler, input.uv); - - // Apply gain, offset, power per channel - color.rgb = color.rgb * pc.gain + pc.offset; - color.rgb = pow(max(color.rgb, float3(0.0, 0.0, 0.0)), pc.power); - - return color; -} diff --git a/src/shaders/read.frag.slang b/src/shaders/read.frag.slang deleted file mode 100644 index 534dd12..0000000 --- a/src/shaders/read.frag.slang +++ /dev/null @@ -1,16 +0,0 @@ -// READ node — samples from a persistent KTX texture. -// No push constants, no inputs (texture loaded separately). - -[[vk::binding(0, 0)]] -Texture2D input_texture : register(t0); -[[vk::binding(1, 0)]] -SamplerState input_sampler : register(s0); - -struct VSOutput { - float4 position : SV_Position; - float2 uv : TEXCOORD0; -}; - -float4 main(VSOutput input) : SV_Target { - return input_texture.Sample(input_sampler, input.uv); -}