Converted to render graph, and made hex region list dynamic

main
noah metz 2026-08-02 22:20:56 -06:00
parent 9016fda882
commit bde72b0a15
19 changed files with 794 additions and 253 deletions

@ -5,18 +5,20 @@ LDFLAGS = -lfreetype -lz -lglfw -lvulkan -ldl -Xlinker -rpath -Xlinker /opt/home
CFLAGS += $(shell pkg-config --cflags lua) CFLAGS += $(shell pkg-config --cflags lua)
LDFLAGS += $(shell pkg-config --libs lua) LDFLAGS += $(shell pkg-config --libs lua)
ENGINE_SOURCES = src/engine.c src/draw.c src/ui.c src/ui_lua.c src/events.c src/camera.c src/gpu.c src/hex.c src/hsv.c lib/spng.c lib/vma.cpp ENGINE_SOURCES = src/engine.c src/draw.c src/render_graph.c src/ui.c src/ui_lua.c src/events.c src/camera.c src/gpu.c src/hex.c src/hsv.c lib/spng.c lib/vma.cpp
APP_SOURCES = src/main.c $(ENGINE_SOURCES) APP_SOURCES = src/main.c $(ENGINE_SOURCES)
EDITOR_SOURCES = src/editor_main.c src/editor.c src/editor_lua.c $(ENGINE_SOURCES) EDITOR_SOURCES = src/editor_main.c src/editor.c src/editor_lua.c $(ENGINE_SOURCES)
TEST_SOURCES = test/hsv.c $(ENGINE_SOURCES) TEST_SOURCES = test/hsv.c $(ENGINE_SOURCES)
TEST_EDITOR_SOURCES = test/editor.c src/editor.c src/editor_lua.c $(ENGINE_SOURCES) TEST_EDITOR_SOURCES = test/editor.c src/editor.c src/editor_lua.c $(ENGINE_SOURCES)
PERF_SOURCES = test/perf.c $(ENGINE_SOURCES)
APP_OBJECTS = $(addsuffix .o, $(basename $(APP_SOURCES))) APP_OBJECTS = $(addsuffix .o, $(basename $(APP_SOURCES)))
EDITOR_OBJECTS = $(addsuffix .o, $(basename $(EDITOR_SOURCES))) EDITOR_OBJECTS = $(addsuffix .o, $(basename $(EDITOR_SOURCES)))
TEST_OBJECTS = $(addsuffix .o, $(basename $(TEST_SOURCES))) TEST_OBJECTS = $(addsuffix .o, $(basename $(TEST_SOURCES)))
TEST_EDITOR_OBJECTS = $(addsuffix .o, $(basename $(TEST_EDITOR_SOURCES))) TEST_EDITOR_OBJECTS = $(addsuffix .o, $(basename $(TEST_EDITOR_SOURCES)))
PERF_OBJECTS = $(addsuffix .o, $(basename $(PERF_SOURCES)))
ALL_OBJECTS = $(sort $(APP_OBJECTS) $(EDITOR_OBJECTS) $(TEST_OBJECTS) $(TEST_EDITOR_OBJECTS)) ALL_OBJECTS = $(sort $(APP_OBJECTS) $(EDITOR_OBJECTS) $(TEST_OBJECTS) $(TEST_EDITOR_OBJECTS) $(PERF_OBJECTS))
DEP_FILES = $(ALL_OBJECTS:.o=.d) DEP_FILES = $(ALL_OBJECTS:.o=.d)
VERT_SPV = $(addsuffix .vert.spv, $(basename $(wildcard shader/*.vert))) VERT_SPV = $(addsuffix .vert.spv, $(basename $(wildcard shader/*.vert)))
@ -66,10 +68,17 @@ test/test_hsv: $(TEST_OBJECTS)
test/test_editor: $(TEST_EDITOR_OBJECTS) | $(SPV_FILES) test/test_editor: $(TEST_EDITOR_OBJECTS) | $(SPV_FILES)
$(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^
.PHONY: test test/test_perf: $(PERF_OBJECTS)
test: test/test_hsv test/test_editor $(CXX) $(CFLAGS) $(LDFLAGS) -o $@ $^
.PHONY: test perf
test: test/test_hsv test/test_editor test/test_perf
./test/test_hsv ./test/test_hsv
./test/test_editor ./test/test_editor
./test/test_perf
perf: test/test_perf
./test/test_perf
%.o: %.cpp %.o: %.cpp
$(CXX) $(CFLAGS) -std=c++17 -Wno-nullability-completeness -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unused-private-field -Wno-unused-variable -c -o $@ $< $(CXX) $(CFLAGS) -std=c++17 -Wno-nullability-completeness -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unused-private-field -Wno-unused-variable -c -o $@ $<
@ -89,7 +98,7 @@ compile_commands.json: Makefile
clean: clean:
rm -f $(SPV_FILES) rm -f $(SPV_FILES)
rm -f $(ALL_OBJECTS) $(DEP_FILES) rm -f $(ALL_OBJECTS) $(DEP_FILES)
rm -f roleplay editor test/test_hsv test/test_editor rm -f roleplay editor test/test_hsv test/test_editor test/test_perf
rm -rf $(EXTRA_DEBUG_REQUIREMENTS) rm -rf $(EXTRA_DEBUG_REQUIREMENTS)
clean_compdb: clean_compdb:

@ -62,6 +62,8 @@ typedef struct CameraStruct {
VkBuffer gpu_buffer[MAX_FRAMES_IN_FLIGHT]; VkBuffer gpu_buffer[MAX_FRAMES_IN_FLIGHT];
VmaAllocation gpu_buffer_memory[MAX_FRAMES_IN_FLIGHT]; VmaAllocation gpu_buffer_memory[MAX_FRAMES_IN_FLIGHT];
VkDeviceAddress gpu_address[MAX_FRAMES_IN_FLIGHT]; VkDeviceAddress gpu_address[MAX_FRAMES_IN_FLIGHT];
uint32_t rg_node; // handle returned by render_graph_add_camera_node, UINT32_MAX when not registered
} Camera; } Camera;
VkResult create_camera(RenderContext* gpu, Camera* camera); VkResult create_camera(RenderContext* gpu, Camera* camera);

@ -5,7 +5,16 @@
#include "ui.h" #include "ui.h"
#include "hex.h" #include "hex.h"
// Forward declaration so draw_frame can accept RenderGraph* without pulling in
// the full render_graph.h include chain here.
typedef struct RenderGraphStruct RenderGraph;
void record_hex_draw(VkCommandBuffer, HexContext*, VkDeviceAddress, double, uint32_t);
void record_container_draw(VkCommandBuffer, RenderContext*, UIContext*, double, uint32_t);
void record_ui_compute(VkCommandBuffer, UIContext*, uint32_t);
VkResult draw_frame( VkResult draw_frame(
RenderGraph* graph,
RenderContext* context, RenderContext* context,
UIContext* ui, UIContext* ui,
HexContext* hex, HexContext* hex,

@ -7,6 +7,7 @@
#include "hex.h" #include "hex.h"
#include "events.h" #include "events.h"
#include "camera.h" #include "camera.h"
#include "render_graph.h"
typedef struct ClientContextStruct ClientContext; typedef struct ClientContextStruct ClientContext;
@ -53,6 +54,7 @@ struct ClientContextStruct {
HexContext hex; HexContext hex;
EventBus events; EventBus events;
Camera camera; Camera camera;
RenderGraph render_graph;
void* app_data; void* app_data;
app_frame_function app_frame; app_frame_function app_frame;

@ -7,15 +7,13 @@
#define MAX_RAYS 10 #define MAX_RAYS 10
// Initial capacity, not a hard cap - ensure_highlight_capacity/ // Initial capacity, not a hard cap - ensure_highlight_capacity/
// ensure_point_capacity (hex.c) grow HexContext.highlights/points on // ensure_point_capacity/ensure_region_capacity (hex.c) grow their
// demand, same "start small, double when a fixed pool fills up" pattern as // respective buffers on demand, same "start small, double when full"
// ui.h's CONTAINER_MIN_DRAWABLES/CONTAINER_MIN_STRINGS. The last slot of // pattern as ui.h's CONTAINER_MIN_DRAWABLES/CONTAINER_MIN_STRINGS.
// whatever the current capacity is stays reserved for the hover preview // The last highlight/point slot is reserved for the hover preview (editor.c).
// (see editor.c).
#define MIN_HIGHLIGHTS 16 #define MIN_HIGHLIGHTS 16
#define MIN_POINTS 16 #define MIN_POINTS 16
#define MIN_REGIONS 16
#define MAX_LOADED_REGIONS 2500
#define REGION_SIZE 10 #define REGION_SIZE 10
#define REGION_HEX_COUNT (3*REGION_SIZE*(REGION_SIZE-1)+1) #define REGION_HEX_COUNT (3*REGION_SIZE*(REGION_SIZE-1)+1)
#define HEX_X 0.75 #define HEX_X 0.75
@ -100,26 +98,31 @@ typedef struct GPUHexContextStruct {
VkDeviceAddress rays; VkDeviceAddress rays;
VkDeviceAddress points; VkDeviceAddress points;
VkDeviceAddress highlights; VkDeviceAddress highlights;
VkDeviceAddress regions[MAX_LOADED_REGIONS]; VkDeviceAddress regions; // device address of VkDeviceAddress[] region list
} GPUHexContext; } GPUHexContext;
typedef struct HexContextStruct { typedef struct HexContextStruct {
VkDeviceAddress address[MAX_FRAMES_IN_FLIGHT]; VkDeviceAddress address[MAX_FRAMES_IN_FLIGHT];
VkBuffer context[MAX_FRAMES_IN_FLIGHT]; VkBuffer context[MAX_FRAMES_IN_FLIGHT];
VmaAllocation context_memory[MAX_FRAMES_IN_FLIGHT]; VmaAllocation context_memory[MAX_FRAMES_IN_FLIGHT];
VkBuffer rays[MAX_FRAMES_IN_FLIGHT]; VkBuffer rays[MAX_FRAMES_IN_FLIGHT];
VkBuffer points[MAX_FRAMES_IN_FLIGHT]; VkBuffer points[MAX_FRAMES_IN_FLIGHT];
VkBuffer highlights[MAX_FRAMES_IN_FLIGHT]; VkBuffer highlights[MAX_FRAMES_IN_FLIGHT];
VkBuffer regions_buf[MAX_FRAMES_IN_FLIGHT];
VmaAllocation rays_memory[MAX_FRAMES_IN_FLIGHT]; VmaAllocation rays_memory[MAX_FRAMES_IN_FLIGHT];
VmaAllocation points_memory[MAX_FRAMES_IN_FLIGHT]; VmaAllocation points_memory[MAX_FRAMES_IN_FLIGHT];
VmaAllocation highlights_memory[MAX_FRAMES_IN_FLIGHT]; VmaAllocation highlights_memory[MAX_FRAMES_IN_FLIGHT];
VmaAllocation regions_mem[MAX_FRAMES_IN_FLIGHT];
// Current allocated size of points[]/highlights[] (in elements, same for // Current allocated size (in elements) for each sub-buffer.
// both frames-in-flight) - grows via ensure_point_capacity/ // All grow on demand via ensure_*_capacity; regions also tracks the
// ensure_highlight_capacity, independent of each other. // highest occupied slot (max_active_slot) to bound the draw call.
uint32_t cap_points; uint32_t cap_points;
uint32_t cap_highlights; uint32_t cap_highlights;
uint32_t cap_regions;
uint32_t active_region_count;
uint32_t max_active_slot;
GPURay rays_buffer[MAX_RAYS]; GPURay rays_buffer[MAX_RAYS];
@ -130,7 +133,7 @@ typedef struct HexContextStruct {
GraphicsPipeline point_pipeline; GraphicsPipeline point_pipeline;
GraphicsPipeline ray_pipeline; GraphicsPipeline ray_pipeline;
HexRegion* regions[MAX_LOADED_REGIONS]; HexRegion** regions;
GPUHexContext data; GPUHexContext data;
} HexContext; } HexContext;
@ -160,6 +163,7 @@ void destroy_hex_context(
// see editor.c), so this is just retire-old/create-new/patch-address. // see editor.c), so this is just retire-old/create-new/patch-address.
VkResult ensure_highlight_capacity(HexContext* context, RenderContext* gpu, uint32_t needed); VkResult ensure_highlight_capacity(HexContext* context, RenderContext* gpu, uint32_t needed);
VkResult ensure_point_capacity(HexContext* context, RenderContext* gpu, uint32_t needed); VkResult ensure_point_capacity(HexContext* context, RenderContext* gpu, uint32_t needed);
VkResult ensure_region_capacity(HexContext* context, RenderContext* gpu, uint32_t needed);
VkResult set_hex_region( VkResult set_hex_region(
HexRegion* region, HexRegion* region,

@ -0,0 +1,104 @@
#ifndef RENDER_GRAPH_H
#define RENDER_GRAPH_H
#include <stdint.h>
#include <stdbool.h>
#include "gpu.h"
typedef struct UIContextStruct UIContext;
typedef struct HexContextStruct HexContext;
typedef uint32_t RGResource;
#define RG_RESOURCE_SWAPCHAIN UINT32_MAX
typedef struct RGResourceUsageStruct {
RGResource resource[MAX_FRAMES_IN_FLIGHT];
VkPipelineStageFlags stage;
VkAccessFlags access;
VkImageLayout layout;
} RGResourceUsage;
typedef enum {
RG_NODE_INPUT = 0,
RG_NODE_COMPUTE = 1,
RG_NODE_RENDER = 2,
RG_NODE_DISPLAY = 3,
} RGNodeType;
typedef void (*RGRecordFn)(
VkCommandBuffer,
void* userdata,
RenderContext*,
UIContext*,
HexContext*,
uint32_t image_index,
uint32_t frame,
double time);
typedef struct RGBarrierStruct {
VkImageMemoryBarrier barrier;
VkPipelineStageFlags src_stage;
VkPipelineStageFlags dst_stage;
RGResource resource[MAX_FRAMES_IN_FLIGHT];
} RGBarrier;
#define RG_MAX_NODES 64
#define RG_MAX_RESOURCES_PER_NODE 16
typedef struct RGNodeStruct {
RGNodeType type;
RGResourceUsage reads[RG_MAX_RESOURCES_PER_NODE];
uint32_t read_count;
RGResourceUsage writes[RG_MAX_RESOURCES_PER_NODE];
uint32_t write_count;
RGBarrier pre_barriers[RG_MAX_RESOURCES_PER_NODE];
uint32_t pre_barrier_count;
RGRecordFn record;
void* userdata;
bool active;
} RGNode;
typedef struct RenderGraphStruct {
RGNode nodes[RG_MAX_NODES];
uint32_t node_count;
uint32_t topo_order[RG_MAX_NODES];
uint32_t topo_count;
bool compiled;
} RenderGraph;
// Returns stable node handle (index). Copies reads/writes arrays internally.
uint32_t render_graph_add_node(
RenderGraph*,
RGNodeType,
RGResourceUsage* reads, uint32_t read_count,
RGResourceUsage* writes, uint32_t write_count,
RGRecordFn, void* userdata);
// Permanent-node helpers — call once at startup from run_app.
// Each registers the appropriate node with the correct callback and resource declarations.
uint32_t render_graph_add_ui_compute_node(RenderGraph*);
uint32_t render_graph_add_ui_composite_node(RenderGraph*);
uint32_t render_graph_add_display_node(RenderGraph*);
// Convenience: registers a camera's hex-draw RENDER node with correct resource declarations.
struct CameraStruct;
uint32_t render_graph_add_camera_node(RenderGraph*, struct CameraStruct*);
void render_graph_remove_node(RenderGraph*, uint32_t handle);
// Derives topo order and image barriers from declared resource usages.
void render_graph_compile(RenderGraph*, UIContext* ui);
// Records all nodes into their respective command buffers in topo order.
// COMPUTE nodes → compute_cmd. RENDER/DISPLAY nodes → graphics_cmd.
// Caller handles queue submit and semaphore chain.
VkResult render_graph_execute(
RenderGraph*,
VkCommandBuffer graphics_cmd,
VkCommandBuffer compute_cmd,
RenderContext*, UIContext*, HexContext*,
uint32_t image_index, double time);
void render_graph_invalidate(RenderGraph*);
#endif

@ -134,6 +134,7 @@ typedef struct EventBusStruct EventBus;
// Forward declaration only (no #include "camera.h") - see camera.h's // Forward declaration only (no #include "camera.h") - see camera.h's
// matching note on why these two headers don't include each other. // matching note on why these two headers don't include each other.
typedef struct CameraStruct Camera; typedef struct CameraStruct Camera;
typedef struct RenderGraphStruct RenderGraph;
struct ContainerStruct { struct ContainerStruct {
VkBuffer container[MAX_FRAMES_IN_FLIGHT]; VkBuffer container[MAX_FRAMES_IN_FLIGHT];
@ -270,6 +271,12 @@ struct UIContextStruct {
uint32_t active_element; uint32_t active_element;
double cursor[2]; double cursor[2];
// Non-owning pointer to the client's render graph; set by run_app after
// render_graph init. camera_display / camera_undisplay use this to
// register / remove camera RENDER nodes without a signature change.
RenderGraph* render_graph;
bool render_graph_dirty;
}; };
VkResult create_ui_context( VkResult create_ui_context(

@ -16,7 +16,7 @@ vec4 int2color(uint color_int) {
void main() { void main() {
int region_index = gl_InstanceIndex/region_hex_count; int region_index = gl_InstanceIndex/region_hex_count;
Region region = pc.context.regions[region_index]; Region region = pc.context.regions.r[region_index];
if(uvec2(region) == uvec2(0)) { if(uvec2(region) == uvec2(0)) {
gl_Position = vec4(0, 0, 0, 0); gl_Position = vec4(0, 0, 0, 0);
return; return;

@ -0,0 +1,7 @@
#version 450
layout(location = 0) out vec4 color;
void main() {
color = vec4(1, 1, 1, 1);
}

@ -0,0 +1,5 @@
#version 450
void main() {
gl_Position = vec4(0, 0, 0, 1);
}

@ -54,12 +54,16 @@ layout(std430, buffer_reference) readonly buffer RayList {
Ray r[]; Ray r[];
}; };
layout(std430, buffer_reference) readonly buffer RegionList {
Region r[];
};
layout(std430, buffer_reference) readonly buffer HexContext { layout(std430, buffer_reference) readonly buffer HexContext {
uint current_map; uint current_map;
RayList rays; RayList rays;
PointList points; PointList points;
HighlightList highlights; HighlightList highlights;
Region regions[]; RegionList regions;
}; };
layout(std430, push_constant) uniform PushConstant { layout(std430, push_constant) uniform PushConstant {

@ -18,7 +18,7 @@ void main() {
return; return;
} }
if(hex_index != 0xFFFFFFFF) { if(hex_index != 0xFFFFFFFF) {
Region region = pc.context.regions[pc.context.highlights.h[gl_InstanceIndex].region]; Region region = pc.context.regions.r[pc.context.highlights.h[gl_InstanceIndex].region];
color = pc.context.highlights.h[gl_InstanceIndex].color; color = pc.context.highlights.h[gl_InstanceIndex].color;
float raise = pc.context.highlights.h[gl_InstanceIndex].offset; float raise = pc.context.highlights.h[gl_InstanceIndex].offset;

@ -8,7 +8,7 @@ layout(location = 0) flat out vec4 color;
void main() { void main() {
uint hex_index = pc.context.points.p[gl_InstanceIndex].hex; uint hex_index = pc.context.points.p[gl_InstanceIndex].hex;
uint vertex_index = pc.context.points.p[gl_InstanceIndex].vertex; uint vertex_index = pc.context.points.p[gl_InstanceIndex].vertex;
Region region = pc.context.regions[pc.context.points.p[gl_InstanceIndex].region]; Region region = pc.context.regions.r[pc.context.points.p[gl_InstanceIndex].region];
float raise = pc.context.points.p[gl_InstanceIndex].offset; float raise = pc.context.points.p[gl_InstanceIndex].offset;
if(hex_index != 0xFFFFFFFF) { if(hex_index != 0xFFFFFFFF) {
color = pc.context.points.p[gl_InstanceIndex].color; color = pc.context.points.p[gl_InstanceIndex].color;

@ -1,5 +1,6 @@
#include "camera.h" #include "camera.h"
#include "ui.h" #include "ui.h"
#include "render_graph.h"
#include <cglm/cam.h> #include <cglm/cam.h>
#include <math.h> #include <math.h>
@ -142,6 +143,13 @@ VkResult camera_display(Camera* camera, Container* container, RenderContext* gpu
VK_RESULT(camera_init_texture_target(gpu, ui, camera, w, h)); VK_RESULT(camera_init_texture_target(gpu, ui, camera, w, h));
} }
if(ui->render_graph != NULL) {
camera->rg_node = render_graph_add_camera_node(ui->render_graph, camera);
ui->render_graph_dirty = true;
} else {
camera->rg_node = UINT32_MAX;
}
GPUDrawable bg = { GPUDrawable bg = {
.pos = {0.0f, 0.0f}, .pos = {0.0f, 0.0f},
.size = {container->data.size[0], container->data.size[1]}, .size = {container->data.size[0], container->data.size[1]},
@ -176,8 +184,12 @@ VkResult camera_display(Camera* camera, Container* container, RenderContext* gpu
} }
VkResult camera_undisplay(Camera* camera, Container* container, RenderContext* gpu, UIContext* ui) { VkResult camera_undisplay(Camera* camera, Container* container, RenderContext* gpu, UIContext* ui) {
(void)camera; if(ui->render_graph != NULL && camera->rg_node != UINT32_MAX) {
(void)ui; render_graph_remove_node(ui->render_graph, camera->rg_node);
camera->rg_node = UINT32_MAX;
ui->render_graph_dirty = true;
}
VkResult result = VK_SUCCESS; VkResult result = VK_SUCCESS;
if(container->camera_background_drawable != UINT32_MAX) { if(container->camera_background_drawable != UINT32_MAX) {
result = ui_destroy_drawable(container, container->camera_background_drawable, gpu); result = ui_destroy_drawable(container, container->camera_background_drawable, gpu);

@ -1,4 +1,5 @@
#include "draw.h" #include "draw.h"
#include "render_graph.h"
#include "hex.h" #include "hex.h"
#include "camera.h" #include "camera.h"
#include "vulkan/vulkan_core.h" #include "vulkan/vulkan_core.h"
@ -13,7 +14,8 @@ void record_hex_draw(VkCommandBuffer command_buffer, HexContext* hex, VkDeviceAd
vkCmdPushConstants(command_buffer, hex->graphics.layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(HexPushConstant), &push); vkCmdPushConstants(command_buffer, hex->graphics.layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(HexPushConstant), &push);
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, hex->graphics.pipeline); vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, hex->graphics.pipeline);
vkCmdDraw(command_buffer, 18, REGION_HEX_COUNT*MAX_LOADED_REGIONS, 0, 0); if(hex->active_region_count > 0)
vkCmdDraw(command_buffer, 18, REGION_HEX_COUNT*(hex->max_active_slot + 1), 0, 0);
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, hex->point_pipeline.pipeline); vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, hex->point_pipeline.pipeline);
vkCmdDraw(command_buffer, 1, hex->cap_points, 0, 0); vkCmdDraw(command_buffer, 1, hex->cap_points, 0, 0);
@ -33,11 +35,7 @@ static void bind_ui_pipeline(VkCommandBuffer command_buffer, UIContext* ui) {
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ui->pipeline.layout, 3, 1, &ui->textures, 0, NULL); vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, ui->pipeline.layout, 3, 1, &ui->textures, 0, NULL);
} }
// UI-only pass over container_order (back to front). Camera scenes are void record_container_draw(
// rendered to offscreen textures in draw_frame before this is called; each
// camera-attached container samples its texture via a background IMAGE
// drawable created by camera_display (camera.c).
static void record_container_draw(
VkCommandBuffer command_buffer, VkCommandBuffer command_buffer,
RenderContext* gpu, RenderContext* gpu,
UIContext* ui, UIContext* ui,
@ -75,6 +73,7 @@ void record_ui_compute(VkCommandBuffer command_buffer, UIContext* ui, uint32_t f
} }
VkResult draw_frame( VkResult draw_frame(
RenderGraph* graph,
RenderContext* context, RenderContext* context,
UIContext* ui, UIContext* ui,
HexContext* hex, HexContext* hex,
@ -87,23 +86,23 @@ VkResult draw_frame(
VK_RESULT(vkWaitForFences(context->device, 1, fences, VK_TRUE, UINT64_MAX)); VK_RESULT(vkWaitForFences(context->device, 1, fences, VK_TRUE, UINT64_MAX));
VK_RESULT(vkResetFences(context->device, 1, fences)); VK_RESULT(vkResetFences(context->device, 1, fences));
// This frame slot's previous submission has completed, so buffers retired
// MAX_FRAMES_IN_FLIGHT frames ago can no longer be referenced by the GPU
destroy_retired(context); destroy_retired(context);
VkCommandBufferBeginInfo begin_info = { VkCommandBufferBeginInfo begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
}; };
TransferBuffer* transfer = &frame->transfers[frame->transfer_index % 2]; TransferBuffer* transfer = &frame->transfers[frame->transfer_index % 2];
if(transfer->count > 0) { bool has_transfers = (transfer->count > 0);
if(has_transfers) {
VkCommandBuffer transfer_commands = frame->transfer_commands; VkCommandBuffer transfer_commands = frame->transfer_commands;
VK_RESULT(vkResetCommandBuffer(transfer_commands, 0)); VK_RESULT(vkResetCommandBuffer(transfer_commands, 0));
VK_RESULT(vkBeginCommandBuffer(transfer_commands, &begin_info)); VK_RESULT(vkBeginCommandBuffer(transfer_commands, &begin_info));
VkDeviceSize src_offset = 0; VkDeviceSize src_offset = 0;
for(uint32_t tid = 0; tid < transfer->count; tid++) { for(uint32_t tid = 0; tid < transfer->count; tid++) {
#ifdef DEBUG_GPU_TRANSFERS #ifdef DEBUG_GPU_TRANSFERS
fprintf(stderr, "Transferring %lld bytes: ", transfer->infos[tid].size); fprintf(stderr, "Transferring %lld bytes: ", transfer->infos[tid].size);
for(VkDeviceSize i = 0; i < transfer->infos[tid].size; i++) { for(VkDeviceSize i = 0; i < transfer->infos[tid].size; i++) {
fprintf(stderr, "%02X", ((uint8_t*)transfer->mapped)[src_offset + i]); fprintf(stderr, "%02X", ((uint8_t*)transfer->mapped)[src_offset + i]);
@ -139,19 +138,33 @@ VkResult draw_frame(
.pNext = &transfer_timeline, .pNext = &transfer_timeline,
}; };
VK_RESULT(vkQueueSubmit(context->transfer_queue.handle, 1, &transfer_submit, VK_NULL_HANDLE)); VK_RESULT(vkQueueSubmit(context->transfer_queue.handle, 1, &transfer_submit, VK_NULL_HANDLE));
}
VkCommandBuffer compute_commands = frame->compute_commands; uint32_t image_index;
VK_RESULT(vkResetCommandBuffer(compute_commands, 0)); result = vkAcquireNextImageKHR(context->device, context->swapchain, UINT64_MAX, frame->image, VK_NULL_HANDLE, &image_index);
VK_RESULT(vkBeginCommandBuffer(compute_commands, &begin_info)); if(result == VK_ERROR_OUT_OF_DATE_KHR) {
VkMemoryBarrier compute_barrier = { VK_RESULT(recreate_framebuffer(context));
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, } else if(result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, return result;
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, }
};
vkCmdPipelineBarrier(compute_commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &compute_barrier, 0, NULL, 0, NULL); // Reset and begin both command buffers before executing the render graph,
record_ui_compute(compute_commands, ui, context->current_frame); // which records compute nodes into compute_commands and RENDER/DISPLAY nodes
VK_RESULT(vkEndCommandBuffer(compute_commands)); // into the graphics command buffer simultaneously.
VkCommandBuffer compute_commands = frame->compute_commands;
VK_RESULT(vkResetCommandBuffer(compute_commands, 0));
VK_RESULT(vkBeginCommandBuffer(compute_commands, &begin_info));
VkCommandBuffer command_buffer = context->frame_command_buffers[context->current_frame];
VK_RESULT(vkResetCommandBuffer(command_buffer, 0));
VK_RESULT(vkBeginCommandBuffer(command_buffer, &begin_info));
VK_RESULT(render_graph_execute(graph, command_buffer, compute_commands, context, ui, hex, image_index, time));
VK_RESULT(vkEndCommandBuffer(compute_commands));
VK_RESULT(vkEndCommandBuffer(command_buffer));
if(has_transfers) {
frame->compute_index += 1; frame->compute_index += 1;
VkPipelineStageFlags compute_wait_stages[] = {VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT}; VkPipelineStageFlags compute_wait_stages[] = {VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT};
VkTimelineSemaphoreSubmitInfo compute_timeline = { VkTimelineSemaphoreSubmitInfo compute_timeline = {
@ -175,191 +188,6 @@ VkResult draw_frame(
VK_RESULT(vkQueueSubmit(context->transfer_queue.handle, 1, &compute_submit, VK_NULL_HANDLE)); VK_RESULT(vkQueueSubmit(context->transfer_queue.handle, 1, &compute_submit, VK_NULL_HANDLE));
} }
uint32_t image_index;
result = vkAcquireNextImageKHR(context->device, context->swapchain, UINT64_MAX, frame->image, VK_NULL_HANDLE, &image_index);
if(result == VK_ERROR_OUT_OF_DATE_KHR) {
VK_RESULT(recreate_framebuffer(context));
} else if(result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
return result;
}
VkCommandBuffer command_buffer = context->frame_command_buffers[context->current_frame];
VK_RESULT(vkResetCommandBuffer(command_buffer, 0));
VK_RESULT(vkBeginCommandBuffer(command_buffer, &begin_info));
VkImageSubresourceRange color_range = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1,
};
// Render each camera's hex scene to its offscreen texture before the
// main scene pass. Iterate container_order to discover cameras; skip
// duplicates (same camera referenced by multiple containers).
// Cameras are independent so this order carries no semantic dependency.
for(uint32_t o = 0; o < ui->container_order_count; o++) {
Container* c = &ui->containers[ui->container_order[o]];
Camera* cam = c->camera;
if(cam == NULL || !cam->has_texture_target) continue;
bool already_rendered = false;
for(uint32_t p = 0; p < o; p++) {
if(ui->containers[ui->container_order[p]].camera == cam) {
already_rendered = true;
break;
}
}
if(already_rendered) continue;
uint32_t f = context->current_frame;
Texture* target = &ui->texture_slots[cam->texture.texture_slot[f]];
VkExtent2D cam_extent = {cam->texture.width, cam->texture.height};
VkViewport cam_viewport = {
.width = (float)cam_extent.width,
.height = (float)cam_extent.height,
.maxDepth = 1.0f,
.minDepth = 0.0f,
};
vkCmdSetViewport(command_buffer, 0, 1, &cam_viewport);
VkRect2D cam_scissor = {.extent = cam_extent};
vkCmdSetScissor(command_buffer, 0, 1, &cam_scissor);
VkImageMemoryBarrier cam_acquire_barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = target->image,
.subresourceRange = color_range,
};
vkCmdPipelineBarrier(command_buffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, 0, NULL, 0, NULL, 1, &cam_acquire_barrier);
VkRenderingAttachmentInfo cam_color = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = target->view,
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue = {.color = {{0.0f, 0.0f, 0.0f, 0.0f}}},
};
VkRenderingAttachmentInfo cam_depth = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = cam->texture.depth_image_view[f],
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.clearValue = {.depthStencil = {1.0f, 0}},
};
VkRenderingInfo cam_rendering = {
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea = {{0, 0}, cam_extent},
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &cam_color,
.pDepthAttachment = &cam_depth,
};
vkCmdBeginRendering(command_buffer, &cam_rendering);
record_hex_draw(command_buffer, hex, cam->gpu_address[f], time, f);
vkCmdEndRendering(command_buffer);
VkImageMemoryBarrier cam_read_barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = target->image,
.subresourceRange = color_range,
};
vkCmdPipelineBarrier(command_buffer,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0, 0, NULL, 0, NULL, 1, &cam_read_barrier);
}
VkViewport viewport = {
.width = context->swapchain_extent.width,
.height = context->swapchain_extent.height,
.maxDepth = 1.0f,
.minDepth = 0.0f,
};
vkCmdSetViewport(command_buffer, 0, 1, &viewport);
VkRect2D scissor = {
.extent = context->swapchain_extent,
};
vkCmdSetScissor(command_buffer, 0, 1, &scissor);
VkImageMemoryBarrier acquire_barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = context->swapchain_images[image_index],
.subresourceRange = color_range,
};
vkCmdPipelineBarrier(command_buffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, 0, NULL, 0, NULL, 1, &acquire_barrier);
VkRenderingAttachmentInfo color_attachment = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = context->swapchain_image_views[image_index],
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue = {.color = {{0.0f, 0.0f, 0.0f, 0.0f}}},
};
VkRenderingAttachmentInfo depth_attachment = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = context->depth_image_view,
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.clearValue = {.depthStencil = {1.0f, 0}},
};
VkRenderingInfo scene_rendering = {
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea = {{0, 0}, context->swapchain_extent},
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &color_attachment,
.pDepthAttachment = &depth_attachment,
};
vkCmdBeginRendering(command_buffer, &scene_rendering);
record_container_draw(command_buffer, context, ui, time, context->current_frame);
vkCmdEndRendering(command_buffer);
VkImageMemoryBarrier present_barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = 0,
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = context->swapchain_images[image_index],
.subresourceRange = color_range,
};
vkCmdPipelineBarrier(command_buffer,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0, 0, NULL, 0, NULL, 1, &present_barrier);
VK_RESULT(vkEndCommandBuffer(command_buffer));
VkPipelineStageFlags wait_stages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT}; VkPipelineStageFlags wait_stages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT};
VkSemaphore wait_semaphores[] = {frame->image, frame->compute}; VkSemaphore wait_semaphores[] = {frame->image, frame->compute};
VkSemaphore signal_semaphores[] = {frame->render}; VkSemaphore signal_semaphores[] = {frame->render};

@ -134,7 +134,7 @@ uint32_t add_hex_region(ClientContext* context) {
set_hex_region(region, &context->hex, &context->render); set_hex_region(region, &context->hex, &context->render);
uint32_t i = 0; uint32_t i = 0;
for(; i < MAX_LOADED_REGIONS; i++) { for(; i < context->hex.cap_regions; i++) {
if(region == context->hex.regions[i]) { if(region == context->hex.regions[i]) {
break; break;
} }
@ -364,7 +364,7 @@ static uint32_t resolve_vertex_group(
for(uint32_t i = 0; i < 2; i++) { for(uint32_t i = 0; i < 2; i++) {
uint32_t actual_region; uint32_t actual_region;
first_matching_region(n_region_coord[i], r->data.y, &actual_region, &context->hex); first_matching_region(n_region_coord[i], r->data.y, &actual_region, &context->hex);
if(actual_region == MAX_LOADED_REGIONS) continue; if(actual_region == UINT32_MAX) continue;
out_region[count] = actual_region; out_region[count] = actual_region;
out_hex[count] = n_hex[i]; out_hex[count] = n_hex[i];
out_vertex[count] = n_vertex[i]; out_vertex[count] = n_vertex[i];

@ -3,6 +3,7 @@
#include "gpu.h" #include "gpu.h"
#include "draw.h" #include "draw.h"
#include "camera.h" #include "camera.h"
#include "render_graph.h"
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
@ -257,7 +258,12 @@ int app_main(ClientContext* context) {
} }
} }
VkResult result = draw_frame(&context->render, &context->ui, &context->hex, frame_time); if(context->ui.render_graph_dirty) {
render_graph_compile(&context->render_graph, &context->ui);
context->ui.render_graph_dirty = false;
}
VkResult result = draw_frame(&context->render_graph, &context->render, &context->ui, &context->hex, frame_time);
if(result != VK_SUCCESS) { if(result != VK_SUCCESS) {
fprintf(stderr, "draw_frame error: %s\n", string_VkResult(result)); fprintf(stderr, "draw_frame error: %s\n", string_VkResult(result));
glfwDestroyWindow(context->window); glfwDestroyWindow(context->window);
@ -349,6 +355,15 @@ int run_app(
if(event_bus_init(&context->events, context->ui.lua) != VK_SUCCESS) return -5; if(event_bus_init(&context->events, context->ui.lua) != VK_SUCCESS) return -5;
context->ui.events = &context->events; context->ui.events = &context->events;
// Link render graph into UIContext so camera_display/undisplay can register nodes.
context->ui.render_graph = &context->render_graph;
// Register the three permanent nodes (camera nodes are added by camera_display).
render_graph_add_ui_compute_node(&context->render_graph);
render_graph_add_ui_composite_node(&context->render_graph);
render_graph_add_display_node(&context->render_graph);
context->ui.render_graph_dirty = true;
if(app_startup != NULL) app_startup(context); if(app_startup != NULL) app_startup(context);
int result = app_main(context); int result = app_main(context);

@ -745,10 +745,16 @@ VkResult create_hex_context(
VK_RESULT(create_hex_highlight_pipeline(gpu, &context->highlight_pipeline)); VK_RESULT(create_hex_highlight_pipeline(gpu, &context->highlight_pipeline));
VK_RESULT(create_point_pipeline(gpu, &context->point_pipeline)); VK_RESULT(create_point_pipeline(gpu, &context->point_pipeline));
context->regions = calloc(MIN_REGIONS, sizeof(HexRegion*));
if(context->regions == NULL) return VK_ERROR_OUT_OF_HOST_MEMORY;
context->cap_regions = MIN_REGIONS;
context->active_region_count = 0;
context->max_active_slot = 0;
memset(&context->data, 0, sizeof(GPUHexContext)); memset(&context->data, 0, sizeof(GPUHexContext));
for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VK_RESULT(create_storage_buffer( VK_RESULT(create_storage_buffer(
gpu->allocator, gpu->allocator,
0, 0,
sizeof(GPUHexContext), sizeof(GPUHexContext),
&context->context[i], &context->context[i],
@ -774,6 +780,13 @@ VkResult create_hex_context(
sizeof(GPUHighlight)*MIN_HIGHLIGHTS, sizeof(GPUHighlight)*MIN_HIGHLIGHTS,
&context->highlights[i], &context->highlights[i],
&context->highlights_memory[i])); &context->highlights_memory[i]));
VK_RESULT(create_storage_buffer(
gpu->allocator,
0,
sizeof(VkDeviceAddress)*MIN_REGIONS,
&context->regions_buf[i],
&context->regions_mem[i]));
} }
context->cap_points = MIN_POINTS; context->cap_points = MIN_POINTS;
context->cap_highlights = MIN_HIGHLIGHTS; context->cap_highlights = MIN_HIGHLIGHTS;
@ -834,6 +847,15 @@ VkResult create_hex_context(
sizeof(VkDeviceAddress), sizeof(VkDeviceAddress),
i, i,
gpu)); gpu));
VkDeviceAddress regions_addr = buffer_address(gpu->device, context->regions_buf[i]);
VK_RESULT(add_transfer(
&regions_addr,
context->context[i],
offsetof(GPUHexContext, regions),
sizeof(VkDeviceAddress),
i,
gpu));
} }
return VK_SUCCESS; return VK_SUCCESS;
@ -875,6 +897,34 @@ VkResult ensure_point_capacity(HexContext* context, RenderContext* gpu, uint32_t
return VK_SUCCESS; return VK_SUCCESS;
} }
VkResult ensure_region_capacity(HexContext* context, RenderContext* gpu, uint32_t needed) {
VkResult result;
if(needed <= context->cap_regions) return VK_SUCCESS;
uint32_t new_cap = context->cap_regions;
while(new_cap < needed) new_cap *= 2;
for(uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; f++) {
retire_buffer(context->regions_buf[f], context->regions_mem[f], gpu);
VK_RESULT(create_storage_buffer(gpu->allocator, 0, sizeof(VkDeviceAddress)*new_cap, &context->regions_buf[f], &context->regions_mem[f]));
VkDeviceAddress list_addr = buffer_address(gpu->device, context->regions_buf[f]);
VK_RESULT(add_transfer(&list_addr, context->context[f], offsetof(GPUHexContext, regions), sizeof(VkDeviceAddress), f, gpu));
for(uint32_t i = 0; i <= context->max_active_slot; i++) {
VkDeviceAddress addr = (context->regions[i] != NULL) ? context->regions[i]->address : 0;
VK_RESULT(add_transfer(&addr, context->regions_buf[f], sizeof(VkDeviceAddress)*i, sizeof(VkDeviceAddress), f, gpu));
}
}
HexRegion** new_regions = calloc(new_cap, sizeof(HexRegion*));
if(new_regions == NULL) return VK_ERROR_OUT_OF_HOST_MEMORY;
memcpy(new_regions, context->regions, sizeof(HexRegion*) * context->cap_regions);
free(context->regions);
context->regions = new_regions;
context->cap_regions = new_cap;
return VK_SUCCESS;
}
void destroy_hex_context(RenderContext* gpu, HexContext* context) { void destroy_hex_context(RenderContext* gpu, HexContext* context) {
vkDestroyPipeline(gpu->device, context->graphics.pipeline, NULL); vkDestroyPipeline(gpu->device, context->graphics.pipeline, NULL);
vkDestroyPipelineLayout(gpu->device, context->graphics.layout, NULL); vkDestroyPipelineLayout(gpu->device, context->graphics.layout, NULL);
@ -888,24 +938,26 @@ void destroy_hex_context(RenderContext* gpu, HexContext* context) {
// Direct teardown rather than free_hex_region: its GPU-address-nulling // Direct teardown rather than free_hex_region: its GPU-address-nulling
// transfer is pointless work when the context's own buffers are about to // transfer is pointless work when the context's own buffers are about to
// be destroyed right below, unread. // be destroyed right below, unread.
for(uint32_t i = 0; i < MAX_LOADED_REGIONS; i++) { for(uint32_t i = 0; i < context->cap_regions; i++) {
if(context->regions[i] == NULL) continue; if(context->regions[i] == NULL) continue;
vmaDestroyBuffer(gpu->allocator, context->regions[i]->region, context->regions[i]->region_memory); vmaDestroyBuffer(gpu->allocator, context->regions[i]->region, context->regions[i]->region_memory);
free(context->regions[i]); free(context->regions[i]);
context->regions[i] = NULL; context->regions[i] = NULL;
} }
free(context->regions);
for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { for(uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vmaDestroyBuffer(gpu->allocator, context->context[i], context->context_memory[i]); vmaDestroyBuffer(gpu->allocator, context->context[i], context->context_memory[i]);
vmaDestroyBuffer(gpu->allocator, context->rays[i], context->rays_memory[i]); vmaDestroyBuffer(gpu->allocator, context->rays[i], context->rays_memory[i]);
vmaDestroyBuffer(gpu->allocator, context->points[i], context->points_memory[i]); vmaDestroyBuffer(gpu->allocator, context->points[i], context->points_memory[i]);
vmaDestroyBuffer(gpu->allocator, context->highlights[i], context->highlights_memory[i]); vmaDestroyBuffer(gpu->allocator, context->highlights[i], context->highlights_memory[i]);
vmaDestroyBuffer(gpu->allocator, context->regions_buf[i], context->regions_mem[i]);
} }
} }
VkResult set_hex_region(HexRegion* region, HexContext* hex, RenderContext* gpu) { VkResult set_hex_region(HexRegion* region, HexContext* hex, RenderContext* gpu) {
uint32_t index = UINT32_MAX; uint32_t index = UINT32_MAX;
for(uint32_t i = 0; i < MAX_LOADED_REGIONS; i++) { for(uint32_t i = 0; i < hex->cap_regions; i++) {
if(hex->regions[i] == region) { if(hex->regions[i] == region) {
index = i; index = i;
break; break;
@ -928,16 +980,23 @@ VkResult free_hex_region(
uint32_t region_index, uint32_t region_index,
HexContext* hex, HexContext* hex,
RenderContext* gpu) { RenderContext* gpu) {
if(hex->regions[region_index] == NULL) return VK_ERROR_VALIDATION_FAILED_EXT; if(region_index >= hex->cap_regions || hex->regions[region_index] == NULL)
return VK_ERROR_VALIDATION_FAILED_EXT;
free(hex->regions[region_index]); free(hex->regions[region_index]);
hex->regions[region_index] = NULL; hex->regions[region_index] = NULL;
hex->active_region_count--;
if(region_index == hex->max_active_slot) {
while(hex->max_active_slot > 0 && hex->regions[hex->max_active_slot] == NULL)
hex->max_active_slot--;
}
VkDeviceAddress null_address = 0x00; VkDeviceAddress null_address = 0x00;
return add_transfers( return add_transfers(
&null_address, &null_address,
hex->context, hex->regions_buf,
offsetof(GPUHexContext, regions) + sizeof(VkDeviceAddress)*region_index, sizeof(VkDeviceAddress)*region_index,
sizeof(VkDeviceAddress), sizeof(VkDeviceAddress),
gpu); gpu);
} }
@ -953,18 +1012,17 @@ VkResult allocate_hex_region(
VkResult result; VkResult result;
uint32_t i = 0; uint32_t i = 0;
for(; i < MAX_LOADED_REGIONS; i++) { for(; i < hex->cap_regions; i++) {
if(hex->regions[i] == NULL) { if(hex->regions[i] == NULL) break;
hex->regions[i] = malloc(sizeof(HexRegion));
*region = hex->regions[i];
break;
}
} }
if(i == hex->cap_regions) {
if(*region == NULL) { VK_RESULT(ensure_region_capacity(hex, gpu, hex->cap_regions + 1));
return VK_ERROR_OUT_OF_HOST_MEMORY;
} }
hex->regions[i] = malloc(sizeof(HexRegion));
if(hex->regions[i] == NULL) return VK_ERROR_OUT_OF_HOST_MEMORY;
*region = hex->regions[i];
(*region)->data.position.q = q; (*region)->data.position.q = q;
(*region)->data.position.r = r; (*region)->data.position.r = r;
(*region)->data.y = y; (*region)->data.y = y;
@ -985,10 +1043,14 @@ VkResult allocate_hex_region(
(*region)->address = buffer_address(gpu->device, (*region)->region); (*region)->address = buffer_address(gpu->device, (*region)->region);
if(i > hex->max_active_slot || hex->active_region_count == 0)
hex->max_active_slot = i;
hex->active_region_count++;
return add_transfers( return add_transfers(
&(*region)->address, &(*region)->address,
hex->context, hex->regions_buf,
offsetof(GPUHexContext, regions) + sizeof(VkDeviceAddress)*i, sizeof(VkDeviceAddress)*i,
sizeof(VkDeviceAddress), sizeof(VkDeviceAddress),
gpu); gpu);
} }
@ -1175,7 +1237,8 @@ bool ray_world_intersect(
*distance = INFINITY; *distance = INFINITY;
for(uint32_t intersect_rid = 0; intersect_rid < MAX_LOADED_REGIONS; intersect_rid++) { uint32_t limit = (context->active_region_count > 0) ? context->max_active_slot + 1 : 0;
for(uint32_t intersect_rid = 0; intersect_rid < limit; intersect_rid++) {
HexRegion* region = context->regions[intersect_rid]; HexRegion* region = context->regions[intersect_rid];
if(region == NULL) { if(region == NULL) {
continue; continue;
@ -1391,7 +1454,8 @@ void hex_vertex_neighbors(
} }
void first_matching_region(HexCoord coord, int y, uint32_t* region, HexContext* context) { void first_matching_region(HexCoord coord, int y, uint32_t* region, HexContext* context) {
for(*region = 0; *region < MAX_LOADED_REGIONS; (*region)++) { uint32_t limit = (context->active_region_count > 0) ? context->max_active_slot + 1 : 0;
for(*region = 0; *region < limit; (*region)++) {
if(context->regions[*region] != NULL if(context->regions[*region] != NULL
&& context->regions[*region]->data.position.q == coord.q && context->regions[*region]->data.position.q == coord.q
&& context->regions[*region]->data.position.r == coord.r && context->regions[*region]->data.position.r == coord.r
@ -1399,4 +1463,5 @@ void first_matching_region(HexCoord coord, int y, uint32_t* region, HexContext*
return; return;
} }
} }
*region = UINT32_MAX;
} }

@ -0,0 +1,468 @@
#include "render_graph.h"
#include "draw.h"
#include "camera.h"
#include "ui.h"
#include <string.h>
#include <assert.h>
#include <stddef.h>
// ---------------------------------------------------------------------------
// Record callbacks (shims that drive the actual render/compute functions)
// ---------------------------------------------------------------------------
static void record_hex_draw_for_camera(
VkCommandBuffer cmd,
void* userdata,
RenderContext* gpu,
UIContext* ui,
HexContext* hex,
uint32_t image_index,
uint32_t frame,
double time) {
(void)gpu; (void)image_index;
Camera* cam = (Camera*)userdata;
VkExtent2D extent = {cam->texture.width, cam->texture.height};
VkViewport viewport = {
.width = (float)extent.width,
.height = (float)extent.height,
.maxDepth = 1.0f,
.minDepth = 0.0f,
};
vkCmdSetViewport(cmd, 0, 1, &viewport);
VkRect2D scissor = {.extent = extent};
vkCmdSetScissor(cmd, 0, 1, &scissor);
Texture* target = &ui->texture_slots[cam->texture.texture_slot[frame]];
VkRenderingAttachmentInfo cam_color = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = target->view,
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue = {.color = {{0.0f, 0.0f, 0.0f, 0.0f}}},
};
VkRenderingAttachmentInfo cam_depth = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = cam->texture.depth_image_view[frame],
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.clearValue = {.depthStencil = {1.0f, 0}},
};
VkRenderingInfo cam_rendering = {
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea = {{0, 0}, extent},
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &cam_color,
.pDepthAttachment = &cam_depth,
};
vkCmdBeginRendering(cmd, &cam_rendering);
record_hex_draw(cmd, hex, cam->gpu_address[frame], time, frame);
vkCmdEndRendering(cmd);
}
static void record_container_draw_wrapper(
VkCommandBuffer cmd,
void* userdata,
RenderContext* gpu,
UIContext* ui,
HexContext* hex,
uint32_t image_index,
uint32_t frame,
double time) {
(void)userdata; (void)hex;
VkViewport viewport = {
.width = (float)gpu->swapchain_extent.width,
.height = (float)gpu->swapchain_extent.height,
.maxDepth = 1.0f,
.minDepth = 0.0f,
};
vkCmdSetViewport(cmd, 0, 1, &viewport);
VkRect2D scissor = {.extent = gpu->swapchain_extent};
vkCmdSetScissor(cmd, 0, 1, &scissor);
VkRenderingAttachmentInfo color_attachment = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = gpu->swapchain_image_views[image_index],
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue = {.color = {{0.0f, 0.0f, 0.0f, 0.0f}}},
};
VkRenderingAttachmentInfo depth_attachment = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = gpu->depth_image_view,
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.clearValue = {.depthStencil = {1.0f, 0}},
};
VkRenderingInfo scene_rendering = {
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea = {{0, 0}, gpu->swapchain_extent},
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &color_attachment,
.pDepthAttachment = &depth_attachment,
};
vkCmdBeginRendering(cmd, &scene_rendering);
record_container_draw(cmd, gpu, ui, time, frame);
vkCmdEndRendering(cmd);
}
static void record_ui_compute_wrapper(
VkCommandBuffer cmd,
void* userdata,
RenderContext* gpu,
UIContext* ui,
HexContext* hex,
uint32_t image_index,
uint32_t frame,
double time) {
(void)userdata; (void)gpu; (void)hex; (void)image_index; (void)time;
VkMemoryBarrier compute_barrier = {
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_TRANSFER_READ_BIT
| VK_ACCESS_TRANSFER_WRITE_BIT,
};
vkCmdPipelineBarrier(cmd,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, 1, &compute_barrier, 0, NULL, 0, NULL);
record_ui_compute(cmd, ui, frame);
}
// ---------------------------------------------------------------------------
// API
// ---------------------------------------------------------------------------
uint32_t render_graph_add_node(
RenderGraph* graph,
RGNodeType type,
RGResourceUsage* reads, uint32_t read_count,
RGResourceUsage* writes, uint32_t write_count,
RGRecordFn record, void* userdata) {
assert(read_count <= RG_MAX_RESOURCES_PER_NODE);
assert(write_count <= RG_MAX_RESOURCES_PER_NODE);
for(uint32_t i = 0; i < RG_MAX_NODES; i++) {
if(!graph->nodes[i].active) {
RGNode* node = &graph->nodes[i];
memset(node, 0, sizeof(RGNode));
node->type = type;
if(reads && read_count > 0) {
memcpy(node->reads, reads, read_count * sizeof(RGResourceUsage));
node->read_count = read_count;
}
if(writes && write_count > 0) {
memcpy(node->writes, writes, write_count * sizeof(RGResourceUsage));
node->write_count = write_count;
}
node->record = record;
node->userdata = userdata;
node->active = true;
if(i + 1 > graph->node_count) graph->node_count = i + 1;
graph->compiled = false;
return i;
}
}
assert(false && "RG_MAX_NODES exceeded");
return UINT32_MAX;
}
uint32_t render_graph_add_ui_compute_node(RenderGraph* graph) {
return render_graph_add_node(graph, RG_NODE_COMPUTE,
NULL, 0, NULL, 0,
record_ui_compute_wrapper, NULL);
}
uint32_t render_graph_add_ui_composite_node(RenderGraph* graph) {
RGResourceUsage ui_writes[] = {{
.resource = {RG_RESOURCE_SWAPCHAIN, RG_RESOURCE_SWAPCHAIN},
.stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
}};
// reads are rebuilt at compile time from camera RENDER nodes' writes
return render_graph_add_node(graph, RG_NODE_RENDER,
NULL, 0,
ui_writes, 1,
record_container_draw_wrapper, NULL);
}
uint32_t render_graph_add_display_node(RenderGraph* graph) {
// Reading PRESENT_SRC_KHR creates a dependency edge from the UI composite
// (which writes the swapchain) → this node, ensuring correct topo order
// and auto-deriving the present barrier.
RGResourceUsage display_reads[] = {{
.resource = {RG_RESOURCE_SWAPCHAIN, RG_RESOURCE_SWAPCHAIN},
.stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
.access = 0,
.layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
}};
return render_graph_add_node(graph, RG_NODE_DISPLAY,
display_reads, 1,
NULL, 0,
NULL, NULL);
}
uint32_t render_graph_add_camera_node(RenderGraph* graph, struct CameraStruct* camera) {
RGResourceUsage cam_writes[] = {{
.resource = {camera->texture.texture_slot[0], camera->texture.texture_slot[1]},
.stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
}};
return render_graph_add_node(graph, RG_NODE_RENDER,
NULL, 0,
cam_writes, 1,
record_hex_draw_for_camera, camera);
}
void render_graph_remove_node(RenderGraph* graph, uint32_t handle) {
if(handle >= RG_MAX_NODES) return;
graph->nodes[handle].active = false;
graph->compiled = false;
}
void render_graph_compile(RenderGraph* graph, UIContext* ui) {
(void)ui;
// Step 1: Rebuild the UI composite node's reads from all other RENDER nodes'
// writes, so that topo sort derives the camera → UI composite dependency.
// The UI composite is identified as the RENDER node that writes RG_RESOURCE_SWAPCHAIN.
uint32_t ui_composite = UINT32_MAX;
for(uint32_t i = 0; i < graph->node_count; i++) {
if(!graph->nodes[i].active || graph->nodes[i].type != RG_NODE_RENDER) continue;
for(uint32_t j = 0; j < graph->nodes[i].write_count; j++) {
if(graph->nodes[i].writes[j].resource[0] == RG_RESOURCE_SWAPCHAIN) {
ui_composite = i;
break;
}
}
if(ui_composite != UINT32_MAX) break;
}
if(ui_composite != UINT32_MAX) {
RGNode* ui_node = &graph->nodes[ui_composite];
ui_node->read_count = 0;
for(uint32_t i = 0; i < graph->node_count; i++) {
if(!graph->nodes[i].active || i == ui_composite) continue;
if(graph->nodes[i].type != RG_NODE_RENDER) continue;
for(uint32_t j = 0; j < graph->nodes[i].write_count; j++) {
if(ui_node->read_count >= RG_MAX_RESOURCES_PER_NODE) break;
ui_node->reads[ui_node->read_count++] = (RGResourceUsage){
.resource = {
graph->nodes[i].writes[j].resource[0],
graph->nodes[i].writes[j].resource[1],
},
.stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
.access = VK_ACCESS_SHADER_READ_BIT,
.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
};
}
}
}
// Step 2: Topological sort (Kahn's algorithm).
graph->topo_count = 0;
uint32_t active[RG_MAX_NODES];
uint32_t active_count = 0;
for(uint32_t i = 0; i < graph->node_count; i++) {
if(graph->nodes[i].active) active[active_count++] = i;
}
// Edge matrix: edges[w][r] = true means node w must execute before node r.
bool edges[RG_MAX_NODES][RG_MAX_NODES];
uint32_t in_degree[RG_MAX_NODES];
memset(edges, 0, sizeof(edges));
memset(in_degree, 0, sizeof(in_degree));
for(uint32_t ai = 0; ai < active_count; ai++) {
uint32_t r = active[ai];
for(uint32_t ri = 0; ri < graph->nodes[r].read_count; ri++) {
RGResource res0 = graph->nodes[r].reads[ri].resource[0];
for(uint32_t bi = 0; bi < active_count; bi++) {
uint32_t w = active[bi];
if(w == r) continue;
for(uint32_t wi = 0; wi < graph->nodes[w].write_count; wi++) {
if(graph->nodes[w].writes[wi].resource[0] == res0) {
if(!edges[w][r]) {
edges[w][r] = true;
in_degree[r]++;
}
}
}
}
}
}
uint32_t queue[RG_MAX_NODES];
uint32_t qhead = 0, qtail = 0;
for(uint32_t ai = 0; ai < active_count; ai++) {
if(in_degree[active[ai]] == 0) queue[qtail++] = active[ai];
}
while(qhead < qtail) {
uint32_t n = queue[qhead++];
graph->topo_order[graph->topo_count++] = n;
for(uint32_t ai = 0; ai < active_count; ai++) {
uint32_t m = active[ai];
if(edges[n][m] && --in_degree[m] == 0) queue[qtail++] = m;
}
}
assert(graph->topo_count == active_count && "render graph has a cycle");
// Step 3: Derive image barriers.
// For each resource a node WRITES: find the prior writer's layout, or
// UNDEFINED if none → emit a pre-write (acquire) barrier.
// For each resource a node READS: find the most recent writer's layout
// → emit a pre-read (transition) barrier.
for(uint32_t ti = 0; ti < graph->topo_count; ti++) {
uint32_t n = graph->topo_order[ti];
RGNode* node = &graph->nodes[n];
node->pre_barrier_count = 0;
VkImageSubresourceRange color_range = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = 1,
.layerCount = 1,
};
// Pre-write barriers
for(uint32_t wi = 0; wi < node->write_count; wi++) {
RGResource res0 = node->writes[wi].resource[0];
VkPipelineStageFlags src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags src_access = 0;
VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED;
for(int32_t tj = (int32_t)ti - 1; tj >= 0; tj--) {
uint32_t m = graph->topo_order[tj];
bool found = false;
for(uint32_t pwi = 0; pwi < graph->nodes[m].write_count; pwi++) {
if(graph->nodes[m].writes[pwi].resource[0] == res0) {
src_stage = graph->nodes[m].writes[pwi].stage;
src_access = graph->nodes[m].writes[pwi].access;
old_layout = graph->nodes[m].writes[pwi].layout;
found = true;
break;
}
}
if(found) break;
}
if(node->pre_barrier_count < RG_MAX_RESOURCES_PER_NODE) {
RGBarrier* bar = &node->pre_barriers[node->pre_barrier_count++];
bar->resource[0] = node->writes[wi].resource[0];
bar->resource[1] = node->writes[wi].resource[1];
bar->src_stage = src_stage;
bar->dst_stage = node->writes[wi].stage;
bar->barrier = (VkImageMemoryBarrier){
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = src_access,
.dstAccessMask = node->writes[wi].access,
.oldLayout = old_layout,
.newLayout = node->writes[wi].layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = VK_NULL_HANDLE,
.subresourceRange = color_range,
};
}
}
// Pre-read barriers
for(uint32_t ri = 0; ri < node->read_count; ri++) {
RGResource res0 = node->reads[ri].resource[0];
for(int32_t tj = (int32_t)ti - 1; tj >= 0; tj--) {
uint32_t m = graph->topo_order[tj];
bool found = false;
for(uint32_t pwi = 0; pwi < graph->nodes[m].write_count; pwi++) {
if(graph->nodes[m].writes[pwi].resource[0] == res0) {
if(node->pre_barrier_count < RG_MAX_RESOURCES_PER_NODE) {
RGBarrier* bar = &node->pre_barriers[node->pre_barrier_count++];
bar->resource[0] = node->reads[ri].resource[0];
bar->resource[1] = node->reads[ri].resource[1];
bar->src_stage = graph->nodes[m].writes[pwi].stage;
bar->dst_stage = node->reads[ri].stage;
bar->barrier = (VkImageMemoryBarrier){
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = graph->nodes[m].writes[pwi].access,
.dstAccessMask = node->reads[ri].access,
.oldLayout = graph->nodes[m].writes[pwi].layout,
.newLayout = node->reads[ri].layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = VK_NULL_HANDLE,
.subresourceRange = color_range,
};
}
found = true;
break;
}
}
if(found) break;
}
}
}
graph->compiled = true;
}
VkResult render_graph_execute(
RenderGraph* graph,
VkCommandBuffer graphics_cmd,
VkCommandBuffer compute_cmd,
RenderContext* gpu,
UIContext* ui,
HexContext* hex,
uint32_t image_index,
double time) {
assert(graph->compiled && "render_graph_compile must be called before execute");
uint32_t frame = gpu->current_frame;
for(uint32_t ti = 0; ti < graph->topo_count; ti++) {
uint32_t n = graph->topo_order[ti];
RGNode* node = &graph->nodes[n];
VkCommandBuffer cmd = (node->type == RG_NODE_COMPUTE) ? compute_cmd : graphics_cmd;
// Emit pre-barriers, resolving the per-frame image for each one.
for(uint32_t bi = 0; bi < node->pre_barrier_count; bi++) {
RGBarrier* bar = &node->pre_barriers[bi];
VkImageMemoryBarrier img_barrier = bar->barrier;
uint32_t slot = bar->resource[frame];
if(slot == RG_RESOURCE_SWAPCHAIN) {
img_barrier.image = gpu->swapchain_images[image_index];
} else {
img_barrier.image = ui->texture_slots[slot].image;
}
vkCmdPipelineBarrier(cmd,
bar->src_stage, bar->dst_stage,
0, 0, NULL, 0, NULL,
1, &img_barrier);
}
if(node->record != NULL) {
node->record(cmd, node->userdata, gpu, ui, hex, image_index, frame, time);
}
}
return VK_SUCCESS;
}
void render_graph_invalidate(RenderGraph* graph) {
graph->compiled = false;
}