From abe05f378078025d66bc242950e74f5533ff9c7e Mon Sep 17 00:00:00 2001 From: Kevin Trogant Date: Thu, 8 Feb 2024 23:06:23 +0100 Subject: [PATCH] Add basic infrastructure for running update and render threads Communicate progress via a pair of semaphores --- src/asset_compiler/asset_compiler.c | 4 ++ src/game/main.c | 65 ------------------- src/runtime/app.c | 7 +++ src/runtime/main_loop.c | 98 +++++++++++++++++++++++++++++ src/runtime/main_loop.h | 27 ++++++++ src/runtime/meson.build | 3 + 6 files changed, 139 insertions(+), 65 deletions(-) create mode 100644 src/runtime/main_loop.c create mode 100644 src/runtime/main_loop.h diff --git a/src/asset_compiler/asset_compiler.c b/src/asset_compiler/asset_compiler.c index e6e643b..c1b4424 100644 --- a/src/asset_compiler/asset_compiler.c +++ b/src/asset_compiler/asset_compiler.c @@ -134,6 +134,10 @@ RT_DLLEXPORT rt_result rtInitAssetCompiler(void) { RT_DLLEXPORT void rtShutdownAssetCompiler(void) { _keep_running = false; + /* Wake the threads */ + rtLockConditionVar(_processing_queue.lock); + rtUnlockConditionVar(_processing_queue.lock, true); + rtJoinThread(_compiler_thread); for (int i = 0; i < rt_AssetProcessingThreads.i; ++i) rtJoinThread(_processing_threads[i]); diff --git a/src/game/main.c b/src/game/main.c index 413774c..e48a880 100644 --- a/src/game/main.c +++ b/src/game/main.c @@ -4,8 +4,6 @@ #include "asset_compiler/asset_compiler.h" -static rt_framegraph *_framegraph; - void RegisterCVars(void) { rtRegisterAssetCompilerCVars(); } @@ -15,73 +13,10 @@ void Init(void) { rtLog("GAME", "Init"); rtInitAssetCompiler(); - - #if 0 - rt_render_target_id rt_ids[4] = {rtCalculateRenderTargetID("rt0", sizeof("rt0")), - rtCalculateRenderTargetID("rt1", sizeof("rt1")), - rtCalculateRenderTargetID("rt2", sizeof("rt2")), - rtCalculateRenderTargetID("rt3", sizeof("rt3")), - }; - - rt_render_target_info rts[4] = { - {.id = rt_ids[0], .width = 1024, .height = 768, .format = RT_PIXEL_FORMAT_R8G8B8A8_SRGB, .sample_count = 4}, - {.id = rt_ids[1], .width = 1024, .height = 768, .format = RT_PIXEL_FORMAT_R8G8B8A8_SRGB, .sample_count = 4}, - {.id = rt_ids[2], .width = 1024, .height = 768, .format = RT_PIXEL_FORMAT_R8G8B8A8_SRGB, .sample_count = 4}, - {.id = rt_ids[3], .width = 1024, .height = 768, .format = RT_PIXEL_FORMAT_R8G8B8A8_SRGB, .sample_count = 4} - }; - - rt_render_target_write pass0_writes[] = {{.render_target = rt_ids[0]}, - {.render_target = rt_ids[1]}}; - rt_render_target_read pass1_reads[] = {{.render_target = rt_ids[0]}, - {.render_target = rt_ids[1]}}; - rt_render_target_write pass1_writes[] = {{.render_target = rt_ids[2]}}; - rt_render_target_read pass2_reads[] = {{.render_target = rt_ids[2]}, - {.render_target = rt_ids[1]}}; - rt_render_target_write pass2_writes[] = {{.render_target = rt_ids[3]}}; - rt_render_target_write pass3_writes[] = {{.render_target = rt_ids[2]}}; - - rt_render_pass_info passes[4]; - rtSetRelptr(&passes[0].read_render_targets, NULL); - rtSetRelptr(&passes[0].write_render_targets, pass0_writes); - passes[0].read_render_target_count = 0; - passes[0].write_render_target_count = RT_ARRAY_COUNT(pass0_writes); - rtSetRelptr(&passes[1].read_render_targets, pass1_reads); - rtSetRelptr(&passes[1].write_render_targets, pass1_writes); - passes[1].read_render_target_count = RT_ARRAY_COUNT(pass1_reads); - passes[1].write_render_target_count = RT_ARRAY_COUNT(pass1_writes); - rtSetRelptr(&passes[2].read_render_targets, pass2_reads); - rtSetRelptr(&passes[2].write_render_targets, pass2_writes); - passes[2].read_render_target_count = RT_ARRAY_COUNT(pass2_reads); - passes[2].write_render_target_count = RT_ARRAY_COUNT(pass2_writes); - rtSetRelptr(&passes[3].read_render_targets, NULL); - rtSetRelptr(&passes[3].write_render_targets, pass3_writes); - passes[3].read_render_target_count = 0; - passes[3].write_render_target_count = RT_ARRAY_COUNT(pass3_writes); - - - rt_framegraph_info info = { - .render_pass_count = 4, - .render_target_count = 4, - }; - rtSetRelptr(&info.render_passes, passes); - rtSetRelptr(&info.render_targets, rts); - #endif - - rt_resource_id id = rtGetResourceID("assets/test.framegraph"); - rt_temp_arena temp = rtGetTemporaryArena(NULL, 0); - while (rtGetResourceSize(id) == 0) - ; - rt_resource *resource = - rtArenaPush(temp.arena, rtGetResourceSize(id)); - - rtGetResource(id, resource); - - _framegraph = rtCreateFramegraph(resource->data); } /* Called after exiting the main-loop and before the runtime starts its shutdown */ void Shutdown(void) { rtLog("GAME", "Shutdown"); rtShutdownAssetCompiler(); - rtDestroyFramegraph(_framegraph); } \ No newline at end of file diff --git a/src/runtime/app.c b/src/runtime/app.c index b8571ce..7e267f8 100644 --- a/src/runtime/app.c +++ b/src/runtime/app.c @@ -4,6 +4,7 @@ #include "config.h" #include "gfx.h" #include "renderer_api.h" +#include "main_loop.h" #include @@ -97,6 +98,10 @@ rtWin32Entry(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int n app_callbacks.Init(); + if (rtInitMainLoop() != RT_SUCCESS) { + return 1; + } + /* Main Loop */ bool keep_running = true; while (keep_running) { @@ -113,6 +118,8 @@ rtWin32Entry(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int n app_callbacks.Shutdown(); + rtShutdownMainLoop(); + rtShutdownGFX(); DestroyWindow(wnd); diff --git a/src/runtime/main_loop.c b/src/runtime/main_loop.c new file mode 100644 index 0000000..bde2501 --- /dev/null +++ b/src/runtime/main_loop.c @@ -0,0 +1,98 @@ +#include "main_loop.h" +#include "runtime.h" +#include "config.h" + +RT_CVAR_I(rt_MaxFrameLatency, "Maximum latency between update and rendering. Default: 2", 2); + +RT_DLLEXPORT rt_main_loop g_main_loop; + +/* **************** + * Update Thread + * ****************/ + +void UpdateThreadEntry(void *param) { + rtLog("UT", "UpdateThread Entry"); + while (!g_main_loop.shutdown) { + /* Wait until the render thread has catched up */ + rtWaitOnSemaphore(&g_main_loop.update_proceed); + rtLog("UT", "Processing %d", g_main_loop.u_frame_id); + + rtSleep(250); + + rtLog("UT", "Finished %d", g_main_loop.u_frame_id); + g_main_loop.u_frame_id += 1; + /* Signal the render thread that data is available */ + rtSignalSemaphore(&g_main_loop.render_proceed); + } +} + +/* **************** + * Render Thread + * ****************/ + +void RenderThreadEntry(void *param) { + rtLog("RT", "RenderThread Entry"); + while (!g_main_loop.shutdown) { + rtWaitOnSemaphore(&g_main_loop.render_proceed); + rtLog("RT", "Processing %d", g_main_loop.r_frame_id); + + rtSleep(500); + + rtLog("RT", "Finished %d", g_main_loop.r_frame_id); + g_main_loop.r_frame_id += 1; + /* Signal the update thread that we have finished and it can proceed */ + rtSignalSemaphore(&g_main_loop.update_proceed); + } +} + +RT_DLLEXPORT rt_result rtInitMainLoop(void) { + RT_ASSERT(g_main_loop.render_thread == NULL && g_main_loop.update_thread == NULL, + "InitMainLoop called multiple times."); + g_main_loop.r_frame_id = 0; + g_main_loop.u_frame_id = 0; + g_main_loop.shutdown = 0; + + int frame_latency = rt_MaxFrameLatency.i; + if (frame_latency < 2) { + rtLog("ML", "Forcing rt_MaxFrameLatency to minimum value 2."); + frame_latency = 2; + } else if (frame_latency > 3) { + rtLog("ML", "Warning: rt_MaxFrameLatency is larger than 3 (%d)", frame_latency); + } + + rt_create_semaphore_result update_proceed_create = rtCreateSemaphore(frame_latency, frame_latency); + if (!update_proceed_create.ok) { + rtReportError("ML", "Failed to create the update proceed semaphore."); + return RT_UNKNOWN_ERROR; + } + g_main_loop.update_proceed = update_proceed_create.semaphore; + + rt_create_semaphore_result render_proceed_create = rtCreateSemaphore(0, frame_latency); + if (!render_proceed_create.ok) { + rtReportError("ML", "Failed to create the update proceed semaphore."); + return RT_UNKNOWN_ERROR; + } + g_main_loop.render_proceed = render_proceed_create.semaphore; + + g_main_loop.update_thread = rtSpawnThread(UpdateThreadEntry, NULL, "UpdateThread"); + if (!g_main_loop.update_thread) { + rtReportError("ML", "Failed to spawn the update thread."); + return RT_UNKNOWN_ERROR; + } + + g_main_loop.render_thread = rtSpawnThread(RenderThreadEntry, NULL, "RenderThread"); + if (!g_main_loop.render_thread) { + rtReportError("ML", "Failed to spawn the render thread."); + return RT_UNKNOWN_ERROR; + } + + return RT_SUCCESS; +} + +RT_DLLEXPORT void rtShutdownMainLoop(void) { + g_main_loop.shutdown = 1; + rtJoinThread(g_main_loop.update_thread); + rtJoinThread(g_main_loop.render_thread); + rtDestroySemaphore(&g_main_loop.update_proceed); + rtDestroySemaphore(&g_main_loop.render_proceed); +} \ No newline at end of file diff --git a/src/runtime/main_loop.h b/src/runtime/main_loop.h new file mode 100644 index 0000000..f9ef237 --- /dev/null +++ b/src/runtime/main_loop.h @@ -0,0 +1,27 @@ +#ifndef RT_MAIN_LOOP_H +#define RT_MAIN_LOOP_H + +#include "runtime.h" +#include "threading.h" + +typedef struct { + int u_frame_id; + int r_frame_id; + + rt_semaphore update_proceed; + rt_semaphore render_proceed; + + rt_thread *update_thread; + rt_thread *render_thread; + + 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 void rtShutdownMainLoop(void); + +#endif diff --git a/src/runtime/meson.build b/src/runtime/meson.build index 9af01cb..258f84e 100644 --- a/src/runtime/meson.build +++ b/src/runtime/meson.build @@ -16,6 +16,7 @@ runtime_lib = library('rt', 'handles.h', 'hashing.h', 'jobs.h', + 'main_loop.h', 'mem_arena.h', 'renderer_api.h', 'resources.h', @@ -38,6 +39,7 @@ runtime_lib = library('rt', 'hashing.c', 'init.c', 'jobs.c', + 'main_loop.c', 'mem_arena.c', 'resource_manager.c', 'sprint.c', @@ -45,6 +47,7 @@ runtime_lib = library('rt', 'threading_cond.c', 'threading_mutex.c', 'threading_rwlock.c', + 'threading_semaphore.c', 'threading_thread.c', # Contrib Sources