From c23f03e62264d799332dbb32209b8ad109a80e66 Mon Sep 17 00:00:00 2001 From: Kevin Trogant Date: Fri, 9 Feb 2024 00:07:35 +0100 Subject: [PATCH] Add single-threaded framegraph execute function --- src/game/entry.c | 10 ++++-- src/game/main.c | 56 +++++++++++++++++++++++++++++++-- src/runtime/app.c | 2 +- src/runtime/app.h | 7 +++++ src/runtime/gfx.h | 31 +++++++++++++----- src/runtime/gfx_framegraph.c | 61 +++++++++++++++++++++++++++++++++--- src/runtime/main_loop.c | 12 +++++-- src/runtime/main_loop.h | 9 +++++- 8 files changed, 167 insertions(+), 21 deletions(-) diff --git a/src/game/entry.c b/src/game/entry.c index 2d9d313..b151aff 100644 --- a/src/game/entry.c +++ b/src/game/entry.c @@ -1,11 +1,17 @@ #include "runtime/app.h" +extern void RegisterCVars(void); extern void Init(void); extern void Shutdown(void); +extern void Update(void); +extern void Render(void); static rt_app_callbacks _callbacks = { - .Init = Init, - .Shutdown = Shutdown, + .RegisterCVars = RegisterCVars, + .Init = Init, + .Shutdown = Shutdown, + .Update = Update, + .Render = Render, }; #ifdef _WIN32 diff --git a/src/game/main.c b/src/game/main.c index e48a880..92fb21a 100644 --- a/src/game/main.c +++ b/src/game/main.c @@ -1,6 +1,7 @@ #include "runtime/gfx.h" -#include "runtime/resources.h" #include "runtime/mem_arena.h" +#include "runtime/resources.h" +#include "runtime/threading.h" #include "asset_compiler/asset_compiler.h" @@ -8,15 +9,66 @@ void RegisterCVars(void) { rtRegisterAssetCompilerCVars(); } +static rt_framegraph *_framegraph; + +static void PassPrepare(rt_render_pass_id pass, + const rt_render_target_write *writes, + uint32_t write_count, + const rt_render_target_read *reads, + uint32_t read_count) { + rtLog("GAME", "Prepare pass %x", pass); +} + +static void PassExecute(rt_render_pass_id pass, + const rt_render_target_write *writes, + uint32_t write_count, + const rt_render_target_read *reads, + uint32_t read_count) { + rtLog("GAME", "Execute pass %x", pass); +} + +static void PassFinalize(rt_render_pass_id pass, + const rt_render_target_write *writes, + uint32_t write_count, + const rt_render_target_read *reads, + uint32_t read_count) { + rtLog("GAME", "Finalize pass %x", pass); +} + /* Called after the runtime has finished its initialization and before entering the main-loop*/ void Init(void) { rtLog("GAME", "Init"); - rtInitAssetCompiler(); + + rt_resource_id resid = rtGetResourceID("assets/test.framegraph"); + while (rtGetResourceSize(resid) == 0) + rtSleep(10); + + rt_temp_arena temp = rtGetTemporaryArena(NULL, 0); + + size_t size = rtGetResourceSize(resid); + rt_resource *res = rtArenaPush(temp.arena, size); + rtGetResource(resid, res); + + _framegraph = rtCreateFramegraph(res->data); + + rt_render_pass_bind_fns bind = {.Execute = PassExecute, + .Prepare = PassPrepare, + .Finalize = PassFinalize}; + rtBindRenderPass(_framegraph, rtCalculateRenderPassID("pass0", sizeof("pass0")-1), &bind); + rtBindRenderPass(_framegraph, rtCalculateRenderPassID("pass1", sizeof("pass1")-1), &bind); } /* Called after exiting the main-loop and before the runtime starts its shutdown */ void Shutdown(void) { rtLog("GAME", "Shutdown"); rtShutdownAssetCompiler(); + rtDestroyFramegraph(_framegraph); +} + +void Update(void) { +} + +void Render(void) { + rtExecuteFramegraph(_framegraph); } \ No newline at end of file diff --git a/src/runtime/app.c b/src/runtime/app.c index 7e267f8..b307b14 100644 --- a/src/runtime/app.c +++ b/src/runtime/app.c @@ -98,7 +98,7 @@ rtWin32Entry(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int n app_callbacks.Init(); - if (rtInitMainLoop() != RT_SUCCESS) { + if (rtInitMainLoop(app_callbacks.Update, app_callbacks.Render) != RT_SUCCESS) { return 1; } diff --git a/src/runtime/app.h b/src/runtime/app.h index 6afea75..ebe3446 100644 --- a/src/runtime/app.h +++ b/src/runtime/app.h @@ -4,6 +4,7 @@ /* Platform specific application entry point */ #include "runtime.h" +#include "main_loop.h" #ifdef __cplusplus extern "C" { @@ -23,6 +24,12 @@ typedef struct { /* Called after the main-loop and before the runtime starts its shutdown. */ rt_app_shutdown_fn *Shutdown; + + /* Called by the update thread for each frame */ + rt_main_loop_update_fn *Update; + + /* Called by the render thread for each frame */ + rt_main_loop_render_fn *Render; } rt_app_callbacks; #ifdef _WIN32 diff --git a/src/runtime/gfx.h b/src/runtime/gfx.h index 84d8fa0..4357de2 100644 --- a/src/runtime/gfx.h +++ b/src/runtime/gfx.h @@ -28,7 +28,7 @@ typedef union { } rt_color; /* NOTE(kevin): When you add a value here, you need to handle them in - * framegraph_processor.c : ParseFramegraph + * framegraph_processor.c : ParseFramegraph * and in the render target and texture functions of all renderers. */ typedef enum { @@ -62,8 +62,8 @@ RT_DLLEXPORT rt_result rtInitGFX(rt_renderer_init_info *renderer_info); RT_DLLEXPORT void rtShutdownGFX(void); /* ********************************************************************* - * Framegraph API - * + * Framegraph API + * * The framegraph is used to organize and schedule the work for a frame. * *********************************************************************/ @@ -131,9 +131,21 @@ typedef struct { uint32_t render_pass_count; } rt_framegraph_info; -typedef void rt_render_pass_prepare_fn(rt_render_pass_id id); -typedef void rt_render_pass_execute_fn(rt_render_pass_id id); -typedef void rt_render_pass_finalize_fn(rt_render_pass_id id); +typedef void rt_render_pass_prepare_fn(rt_render_pass_id id, + const rt_render_target_write *writes, + uint32_t write_count, + const rt_render_target_read *reads, + uint32_t read_count); +typedef void rt_render_pass_execute_fn(rt_render_pass_id id, + const rt_render_target_write *writes, + uint32_t write_count, + const rt_render_target_read *reads, + uint32_t read_count); +typedef void rt_render_pass_finalize_fn(rt_render_pass_id id, + const rt_render_target_write *writes, + uint32_t write_count, + const rt_render_target_read *reads, + uint32_t read_count); typedef struct { rt_render_pass_prepare_fn *Prepare; @@ -141,14 +153,17 @@ typedef struct { rt_render_pass_finalize_fn *Finalize; } rt_render_pass_bind_fns; - typedef struct rt_framegraph_s rt_framegraph; RT_DLLEXPORT rt_framegraph *rtCreateFramegraph(const rt_framegraph_info *info); RT_DLLEXPORT void rtDestroyFramegraph(rt_framegraph *framegraph); -RT_DLLEXPORT void rtBindRenderPass(rt_framegraph *framegraph, rt_render_pass_id pass, const rt_render_pass_bind_fns *bind_fns); +RT_DLLEXPORT void rtBindRenderPass(rt_framegraph *framegraph, + rt_render_pass_id pass, + const rt_render_pass_bind_fns *bind_fns); + +RT_DLLEXPORT void rtExecuteFramegraph(rt_framegraph *framegraph); /* Utility to turn a string into a usable render target id. */ RT_DLLEXPORT rt_render_target_id rtCalculateRenderTargetID(const char *name, size_t len); diff --git a/src/runtime/gfx_framegraph.c b/src/runtime/gfx_framegraph.c index dec4afb..dfb9020 100644 --- a/src/runtime/gfx_framegraph.c +++ b/src/runtime/gfx_framegraph.c @@ -18,6 +18,7 @@ RT_CVAR_I(rt_MaxFramegraphs, "Maximum number of framegraphs. Default 16", 16); #define RT_RENDERPASS_MAX_WRITES 8 typedef struct { + rt_render_target_id id; rt_pixel_format format; unsigned int width; unsigned int height; @@ -26,6 +27,7 @@ typedef struct { } rt_render_target; typedef struct { + rt_render_pass_id id; int execution_level; unsigned int read_count; unsigned int write_count; @@ -40,10 +42,8 @@ struct rt_framegraph_s { rt_framegraph *next_free; - rt_render_pass_id pass_ids[RT_FRAMEGRAPH_MAX_PASSES]; rt_render_pass passes[RT_FRAMEGRAPH_MAX_PASSES]; - rt_render_target_id render_target_ids[RT_FRAMEGRAPH_MAX_RENDER_TARGETS]; rt_render_target render_targets[RT_FRAMEGRAPH_MAX_RENDER_TARGETS]; }; @@ -224,6 +224,7 @@ CreateRenderPasses(rt_framegraph *graph, const rt_framegraph_info *info, rt_aren pass_info[i].read_render_target_count * sizeof(rt_render_target_read)); graph->passes[i].write_count = pass_info[i].write_render_target_count; graph->passes[i].read_count = pass_info[i].read_render_target_count; + graph->passes[i].id = pass_info[i].id; } /* Sort by execution level */ @@ -244,7 +245,7 @@ CreateRenderTargets(rt_framegraph *graph, const rt_framegraph_info *info, rt_are /* TODO(Kevin): determine aliasing opportunities */ const rt_render_target_info *render_targets = rtResolveConstRelptr(&info->render_targets); for (uint32_t i = 0; i < info->render_target_count; ++i) { - graph->render_target_ids[i] = render_targets[i].id; + graph->render_targets[i].id = render_targets[i].id; graph->render_targets[i].format = render_targets[i].format; graph->render_targets[i].width = render_targets[i].width; graph->render_targets[i].height = render_targets[i].height; @@ -375,7 +376,7 @@ RT_DLLEXPORT void rtBindRenderPass(rt_framegraph *framegraph, rt_render_pass_id id, const rt_render_pass_bind_fns *bind_fns) { for (uint32_t i = 0; i < framegraph->pass_count; ++i) { - if (framegraph->pass_ids[i] == id) { + if (framegraph->passes[i].id == id) { if (framegraph->passes[i].bound_fns.Execute) rtLog("GFX", "Rebound pass %x to new functions", id); framegraph->passes[i].bound_fns = *bind_fns; @@ -385,6 +386,58 @@ RT_DLLEXPORT void rtBindRenderPass(rt_framegraph *framegraph, rtLog("GFX", "Tried to bind functions to unknown render pass %x", id); } +RT_DLLEXPORT void rtExecuteFramegraph(rt_framegraph *framegraph) { + int execution_level = framegraph->passes[0].execution_level; + uint32_t level_start = 0; + + for (uint32_t i = 0; i <= framegraph->pass_count && level_start < framegraph->pass_count; ++i) { + if ((i == framegraph->pass_count) || + (framegraph->passes[i].execution_level > execution_level)) { + /* Dispatch all passes in the current execution level */ + for (uint32_t pass_idx = level_start; pass_idx < i; ++pass_idx) { + bool pass_bound = framegraph->passes[pass_idx].bound_fns.Prepare != NULL && + framegraph->passes[pass_idx].bound_fns.Execute != NULL && + framegraph->passes[pass_idx].bound_fns.Finalize != NULL; + if (!pass_bound) { + rtLog("GFX", + "Framegraph pass %u (%x) is not bound to any function.", + pass_idx, + framegraph->passes[pass_idx].id); + continue; + } + rt_render_pass_id id = framegraph->passes[pass_idx].id; + const rt_render_target_write *writes = framegraph->passes[pass_idx].writes; + const rt_render_target_read *reads = framegraph->passes[pass_idx].reads; + uint32_t write_count = framegraph->passes[pass_idx].write_count; + uint32_t read_count = framegraph->passes[pass_idx].read_count; + + /* TODO(Kevin): Every one of these should be a job-dispatch*/ + + framegraph->passes[pass_idx].bound_fns.Prepare(id, + writes, + write_count, + reads, + read_count); + framegraph->passes[pass_idx].bound_fns.Execute(id, + writes, + write_count, + reads, + read_count); + framegraph->passes[pass_idx].bound_fns.Finalize(id, + writes, + write_count, + reads, + read_count); + } + + /* Start next level */ + level_start = i; + if (i < framegraph->pass_count) + execution_level = framegraph->passes[i].execution_level; + } + } +} + RT_DLLEXPORT rt_render_target_id rtCalculateRenderTargetID(const char *name, size_t len) { rt_render_target_id id = rtHashBytes32(name, len); if (id == 0) diff --git a/src/runtime/main_loop.c b/src/runtime/main_loop.c index bde2501..2d859db 100644 --- a/src/runtime/main_loop.c +++ b/src/runtime/main_loop.c @@ -17,7 +17,7 @@ void UpdateThreadEntry(void *param) { rtWaitOnSemaphore(&g_main_loop.update_proceed); rtLog("UT", "Processing %d", g_main_loop.u_frame_id); - rtSleep(250); + (g_main_loop.GameUpdate)(); rtLog("UT", "Finished %d", g_main_loop.u_frame_id); g_main_loop.u_frame_id += 1; @@ -36,7 +36,7 @@ void RenderThreadEntry(void *param) { rtWaitOnSemaphore(&g_main_loop.render_proceed); rtLog("RT", "Processing %d", g_main_loop.r_frame_id); - rtSleep(500); + (g_main_loop.GameRender)(); rtLog("RT", "Finished %d", g_main_loop.r_frame_id); g_main_loop.r_frame_id += 1; @@ -45,12 +45,18 @@ void RenderThreadEntry(void *param) { } } -RT_DLLEXPORT rt_result rtInitMainLoop(void) { +RT_DLLEXPORT rt_result rtInitMainLoop(rt_main_loop_update_fn *update_cb, + rt_main_loop_render_fn *render_cb) { RT_ASSERT(g_main_loop.render_thread == NULL && g_main_loop.update_thread == NULL, "InitMainLoop called multiple times."); + RT_ASSERT(update_cb != NULL && render_cb != NULL, + "Valid update and render functions must be provided."); + g_main_loop.r_frame_id = 0; g_main_loop.u_frame_id = 0; g_main_loop.shutdown = 0; + g_main_loop.GameUpdate = update_cb; + g_main_loop.GameRender = render_cb; int frame_latency = rt_MaxFrameLatency.i; if (frame_latency < 2) { diff --git a/src/runtime/main_loop.h b/src/runtime/main_loop.h index f9ef237..41eb279 100644 --- a/src/runtime/main_loop.h +++ b/src/runtime/main_loop.h @@ -4,6 +4,9 @@ #include "runtime.h" #include "threading.h" +typedef void rt_main_loop_update_fn(void); +typedef void rt_main_loop_render_fn(void); + typedef struct { int u_frame_id; int r_frame_id; @@ -14,13 +17,17 @@ typedef struct { rt_thread *update_thread; rt_thread *render_thread; + rt_main_loop_update_fn *GameUpdate; + rt_main_loop_render_fn *GameRender; + volatile int shutdown; } rt_main_loop; /* The applications main-loop */ extern RT_DLLIMPORT rt_main_loop g_main_loop; -RT_DLLEXPORT rt_result rtInitMainLoop(void); +RT_DLLEXPORT rt_result rtInitMainLoop(rt_main_loop_update_fn *update_cb, + rt_main_loop_render_fn *render_cb); RT_DLLEXPORT void rtShutdownMainLoop(void);