Compare commits

..

2 Commits

Author SHA1 Message Date
abdelrahman f5ff6c70ea Fixes and research 2026-08-08 20:25:25 +01:00
abdelrahman 30408ff244 Revert "First pass at nodes"
This reverts commit 618f09689d.
2026-08-08 20:24:24 +01:00
17 changed files with 637 additions and 1630 deletions
+2 -19
View File
@@ -5,7 +5,6 @@ default: build
CC := "clang" CC := "clang"
CXX := "clang++" CXX := "clang++"
SLANGC := "slangc"
BUILDDIR := "build" BUILDDIR := "build"
# Resolve VULKAN_SDK once via backtick # Resolve VULKAN_SDK once via backtick
@@ -31,19 +30,8 @@ vendor:
cmake --build {{BUILDDIR}}/ktx --config Release cmake --build {{BUILDDIR}}/ktx --config Release
cmake --install {{BUILDDIR}}/ktx 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 all objects, then link
build: vendor shaders build: vendor
mkdir -p {{BUILDDIR}}/bin mkdir -p {{BUILDDIR}}/bin
bear -- {{CXX}} -g -c -Wno-nullability-completeness {{VK_FLAGS}} \ bear -- {{CXX}} -g -c -Wno-nullability-completeness {{VK_FLAGS}} \
src/prism/rhi/vulkan/profiles/vulkan_profiles.cpp \ 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/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 {{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 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 \ bear -a -- {{CXX}} -g -c {{VK_FLAGS}} -Wno-nullability-completeness -DVK_NO_PROTOTYPES \
{{APP_INC}} \ {{APP_INC}} \
src/main.cpp \ src/main.cpp \
@@ -67,7 +50,7 @@ build: vendor shaders
bear -a -- {{CXX}} -g {{VK_FLAGS}} \ bear -a -- {{CXX}} -g {{VK_FLAGS}} \
-L{{VK_SDK}}/lib -L{{VENDOR_LIB}} \ -L{{VK_SDK}}/lib -L{{VENDOR_LIB}} \
build/*.o \ build/*.o \
-lSDL3 -lktx -lvulkan \ -lSDL3 -lglm -ltinyobjloader -lktx -lslang -lvulkan \
-Wl,-rpath,{{VENDOR_LIB}} -Wl,-rpath,{{VK_SDK}}/lib \ -Wl,-rpath,{{VENDOR_LIB}} -Wl,-rpath,{{VK_SDK}}/lib \
-o {{BUILDDIR}}/bin/prism -o {{BUILDDIR}}/bin/prism
@echo "--- build done: {{BUILDDIR}}/bin/prism ---" @echo "--- build done: {{BUILDDIR}}/bin/prism ---"
+635 -364
View File
File diff suppressed because it is too large Load Diff
-212
View File
@@ -1,212 +0,0 @@
// vim:fileencoding=utf-8:foldmethod=marker
#include "pr_graph.h"
#include "../../vendor/wapp/wapp.h"
#include <string.h>
// ---------------------------------------------------------------------------
// 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;
}
-63
View File
@@ -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
-99
View File
@@ -1,99 +0,0 @@
// vim:fileencoding=utf-8:foldmethod=marker
#include "pr_node.h"
#include "../../vendor/wapp/wapp.h"
#include <string.h>
// ---------------------------------------------------------------------------
// 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);
}
-93
View File
@@ -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
-358
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
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];
}
}
-95
View File
@@ -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
-101
View File
@@ -1,101 +0,0 @@
#include "pr_texture_pool.h"
#include "../rhi/pr_rhi.h"
#include "../../vendor/wapp/wapp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}
-38
View File
@@ -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
-44
View File
@@ -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<float4> background : register(t0);
[[vk::binding(1, 0)]]
Texture2D<float4> 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;
}
-18
View File
@@ -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;
}
-10
View File
@@ -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<float4> 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);
}
-29
View File
@@ -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;
}
-40
View File
@@ -1,40 +0,0 @@
// BLUR node — Gaussian blur with configurable radius.
// 1 input texture, push constant: f32 radius.
[[vk::binding(0, 0)]]
Texture2D<float4> 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;
}
-31
View File
@@ -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<float4> 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;
}
-16
View File
@@ -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<float4> 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);
}