From faa2b904eac043df1a33f9e04548a5a10d429339 Mon Sep 17 00:00:00 2001 From: Kevin Trogant Date: Wed, 24 Jul 2024 16:39:31 +0200 Subject: [PATCH] feat(launcher): Offer API for the game This also turns the meshlet experiment into a proper "game" as the first testcase. It is necessary to build glfw as a shared library, to allow multiple targets (i.e. the launcher and the game) to link against it. This is necessary if the game wants to use glfw for something (imgui, for instance). --- meson.build | 8 +- src/experimental/meshlets/main.cpp | 118 ++++++++++++++------------ src/experimental/meshlets/meson.build | 6 +- src/launcher/game_api.h | 68 +++++++++++++-- src/launcher/launcher.c | 79 +++++++++++++---- src/launcher/meson.build | 7 +- 6 files changed, 201 insertions(+), 85 deletions(-) diff --git a/meson.build b/meson.build index 26a9922..6cc23ea 100644 --- a/meson.build +++ b/meson.build @@ -66,15 +66,21 @@ if build_machine.system() == 'linux' and host_machine.system() == 'windows' endif fs = import('fs') +cmake = import('cmake') # Gather dependencies thread_dep = dependency('threads') m_dep = compiler.find_library('m', required : false) # Subprojects installed via wraps -meshoptimizer_proj = subproject('meshoptimizer', default_options: ['warning_level=0', 'werror=false'] ) +meshoptimizer_opts = cmake.subproject_options() +meshoptimizer_opts.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': true}) +meshoptimizer_proj = cmake.subproject('meshoptimizer', options: meshoptimizer_opts) meshoptimizer_dep = meshoptimizer_proj.get_variable('meshoptimizer_dep') +glfw_proj = subproject('glfw', default_options: ['default_library=shared', 'warning_level=0', 'werror=false']) +glfw_dep = glfw_proj.get_variable('glfw_dep') + if host_machine.system() == 'linux' and get_option('use_xlib') add_project_arguments(['-DRT_USE_XLIB'], language : ['c', 'cpp']) elif host_machine.system() == 'linux' and get_option('use_wayland') diff --git a/src/experimental/meshlets/main.cpp b/src/experimental/meshlets/main.cpp index a07ece7..7c047e6 100644 --- a/src/experimental/meshlets/main.cpp +++ b/src/experimental/meshlets/main.cpp @@ -1,18 +1,17 @@ #include #include #include +#include #include #include #include +#include + #include "meshlet_generator.hpp" #include "meshlet_renderer.hpp" -static void GlfwErrorCallback(int errnum, const char *desc) { - rtReportError("GLFW", "Error %d: %s", errnum, desc); -} - static void GLDebugCallback(GLenum source, GLenum type, GLuint id, @@ -144,26 +143,18 @@ struct file_picker { int m_active_input = 0; }; -int main() { - if (rtInitRuntime() != RT_SUCCESS) - return -1; - glfwSetErrorCallback(GlfwErrorCallback); - if (!glfwInit()) - return -1; +struct meshlets_exp { + file_picker picker; + meshlet_generator gen; + meshlet_renderer ren; +}; +void MeshletsRegisterCVARs(rt_launcher_api *api) {} + +rt_result MeshletsInit(rt_launcher_api *api) { rtLog("EXP", "Meshlets experiment starting."); - - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); - glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); - GLFWwindow *window = glfwCreateWindow(1280, 720, "MESHLETS!", NULL, NULL); - if (!window) - return -1; - glfwMakeContextCurrent(window); if (!gladLoadGL()) - return -1; + return RT_UNKNOWN_ERROR; glfwSwapInterval(1); int flags; @@ -175,6 +166,10 @@ int main() { glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); } + rt_window window = api->GetWindow(); + if (window.type != RT_WINDOW_TYPE_GLFW) + return RT_INVALID_VALUE; + // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -183,44 +178,63 @@ int main() { io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls ImGui::StyleColorsDark(); - ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplGlfw_InitForOpenGL(window.glfw, true); ImGui_ImplOpenGL3_Init("#version 130"); - file_picker picker; - meshlet_generator gen; - meshlet_renderer ren; - ren.Initialize(); + void *game_obj = api->AllocGameObject(sizeof(meshlets_exp)); + meshlets_exp *exp = new (game_obj) meshlets_exp; + exp->ren.Initialize(); - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); + return RT_SUCCESS; +} - if (picker.RunFlat()) { - gen.LoadObj(picker.GetPicked()); - gen.RunFlat(); - } - ren.SettingMenu(); +void MeshletsShutdown(rt_launcher_api *api, void *game_obj) {} - ImGui::Render(); - int display_w, display_h; - glfwGetFramebufferSize(window, &display_w, &display_h); - glViewport(0, 0, display_w, display_h); - glClearColor(0.f, 0.f, 0.f, 1.f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +void MeshletsUpdate(rt_launcher_api *api, rt_time_delta delta, void *game_obj) { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); - if (picker.HasPickedFile()) { - ren.m_aspect = (float)display_w / (float)display_h; - ren.RenderFlat(gen.m_meshlets, gen.m_num_meshlets); - } + meshlets_exp *exp = (meshlets_exp *)game_obj; + if (exp->picker.RunFlat()) { + exp->gen.LoadObj(exp->picker.GetPicked()); + exp->gen.RunFlat(); + } + exp->ren.SettingMenu(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - glfwSwapBuffers(window); + ImGui::Render(); +} + +void MeshletsRender(rt_launcher_api *api, void *game_obj) { + rt_window window = api->GetWindow(); + if (window.type != RT_WINDOW_TYPE_GLFW) + return; + + meshlets_exp *exp = (meshlets_exp *)game_obj; + + int display_w, display_h; + glfwGetFramebufferSize(window.glfw, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + if (exp->picker.HasPickedFile()) { + exp->ren.m_aspect = (float)display_w / (float)display_h; + exp->ren.RenderFlat(exp->gen.m_meshlets, exp->gen.m_num_meshlets); } - glfwDestroyWindow(window); - glfwTerminate(); - rtShutdownRuntime(); - return 0; + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); +} + +LOAD_GAME_API_FUNC { + return (rt_game_api){ + .RegisterCVARs = MeshletsRegisterCVARs, + .Init = MeshletsInit, + .Shutdown = MeshletsShutdown, + .Update = MeshletsUpdate, + .Render = MeshletsRender, + .OnGameObjectFree = NULL, + .OnReload = NULL, + .OnUnload = NULL, + }; } diff --git a/src/experimental/meshlets/meson.build b/src/experimental/meshlets/meson.build index b7958ab..29fa855 100644 --- a/src/experimental/meshlets/meson.build +++ b/src/experimental/meshlets/meson.build @@ -1,11 +1,9 @@ -glfw_proj = subproject('glfw', default_options: ['default_library=static', 'b_sanitize=none']) -glfw_dep = glfw_proj.get_variable('glfw_dep') imgui_proj = subproject('imgui', default_options: ['warning_level=0', 'werror=false']) imgui_dep = imgui_proj.get_variable('imgui_dep') glm_proj = subproject('glm') glm_dep = glm_proj.get_variable('glm_dep') -executable('meshlet_experiment', +shared_library('meshlet_experiment', 'main.cpp', 'meshlet_generator.hpp', 'meshlet_generator.cpp', @@ -14,5 +12,5 @@ executable('meshlet_experiment', contrib_dir / 'glad/glad.c', extra_files: ['flat_cull.glsl', 'hierarchical_cull.glsl'], include_directories: [engine_incdir, contrib_incdir], - dependencies: [m_dep, meshoptimizer_dep, glfw_dep, imgui_dep, glm_dep], + dependencies: [m_dep, meshoptimizer_dep, imgui_dep, glfw_dep, glm_dep], link_with: runtime_lib) diff --git a/src/launcher/game_api.h b/src/launcher/game_api.h index a692fef..adf7115 100644 --- a/src/launcher/game_api.h +++ b/src/launcher/game_api.h @@ -4,21 +4,79 @@ #include #include -typedef void rt_game_register_cvars_fn(void); -typedef rt_result rt_game_initialize_fn(void); -typedef void rt_game_shutdown_fn(void); -typedef void rt_game_update_fn(rt_time_delta delta); -typedef void rt_game_render_fn(void); +/* Functions offered by the launcher */ + +struct GLFWwindow; + +typedef enum { + RT_WINDOW_TYPE_GLFW, +} rt_window_type; typedef struct { + rt_window_type type; + union { + GLFWwindow *glfw; + }; +} rt_window; + +typedef rt_window rt_launcher_get_window_fn(void); +typedef void *rt_launcher_alloc_game_object_fn(size_t sz); +typedef void *rt_launcher_get_game_object_fn(void); + +typedef struct { + /* Returns the window used by the game */ + rt_launcher_get_window_fn *GetWindow; + + /* Allocates storage for the "game" object passed to the game functions. */ + rt_launcher_alloc_game_object_fn *AllocGameObject; + + /* Returns the object allocated via AllocGameObject */ + rt_launcher_get_game_object_fn *GetGameObject; +} rt_launcher_api; + +/* Functions called by the launcher */ + +typedef void rt_game_register_cvars_fn(rt_launcher_api *api); +typedef rt_result rt_game_initialize_fn(rt_launcher_api *api); +typedef void rt_game_shutdown_fn(rt_launcher_api *api, void *game_obj); +typedef void rt_game_update_fn(rt_launcher_api *api, rt_time_delta delta, void *game_obj); +typedef void rt_game_render_fn(rt_launcher_api *api, void *game_obj); +typedef void rt_game_on_game_object_free_fn(rt_launcher_api *api, void *game_obj); +typedef void rt_game_on_reload_fn(rt_launcher_api *api, void *game_obj); +typedef void rt_game_on_unload_fn(rt_launcher_api *api, void *game_obj); + +typedef struct { + /* Called before initialization and before configs are read */ rt_game_register_cvars_fn *RegisterCVARs; + + /* Called before entering the game-loop. + * It is expected that this is where the game object is created. */ rt_game_initialize_fn *Init; + + /* Called after exiting the game-loop. */ rt_game_shutdown_fn *Shutdown; + + /* Update game state */ rt_game_update_fn *Update; + + /* Render the game */ rt_game_render_fn *Render; + + /* These are optional and may be NULL */ + + /* Called by rt_laucher_api::AllocGameObject, if an old object is freed. */ + rt_game_on_game_object_free_fn *OnGameObjectFree; + + /* Called after the game was reloaded during live re-compilation */ + rt_game_on_reload_fn *OnReload; + + /* Called before the game gets unloaded during live re-compilation */ + rt_game_on_unload_fn *OnUnload; } rt_game_api; +/* This is the function retrieved from the game library to get the + * game_api struct used by the launcher. */ typedef rt_game_api rt_load_game_api_fn(void); #ifdef __cplusplus diff --git a/src/launcher/launcher.c b/src/launcher/launcher.c index c3c15a0..9b12bc7 100644 --- a/src/launcher/launcher.c +++ b/src/launcher/launcher.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "game_api.h" @@ -121,12 +122,11 @@ static char *ParseCommandLineGameLib(int argc, char **argv) { return game_lib_cmdline; } -static void __NullGame_RegisterCVARs(void) {} -static rt_result __NullGame_Init(void) { return RT_SUCCESS; } -static void __NullGame_Shutdown(void) {} -static void __NullGame_Update(rt_time_delta delta) { RT_UNUSED(delta); } -static void __NullGame_Render(void) {} - +static void __NullGame_RegisterCVARs(rt_launcher_api *api) {} +static rt_result __NullGame_Init(rt_launcher_api *api) { return RT_SUCCESS; } +static void __NullGame_Shutdown(rt_launcher_api *api, void *game_obj) {} +static void __NullGame_Update(rt_launcher_api *api, rt_time_delta delta, void *game_obj) { RT_UNUSED(delta); } +static void __NullGame_Render(rt_launcher_api *api, void *game_obj) {} static rt_game_api LoadGame(const char *cmdline_gamelib) { const char *game_lib = RT_GAME_LIB_PATH; @@ -140,6 +140,9 @@ static rt_game_api LoadGame(const char *cmdline_gamelib) { #endif if (strcmp(game_lib, "(null)") != 0) { +#if defined(RT_DEBUG) && !defined(RT_DISABLE_LIVE_RECOMPILATION) + /* Copy to temporary location to enable rebuilds */ +#endif _game_lib = rtOpenLib(game_lib); if (!_game_lib) { rtReportError("LAUNCHER", "Failed to open game library: %s", game_lib); @@ -158,14 +161,41 @@ static rt_game_api LoadGame(const char *cmdline_gamelib) { out: /* Fall back to null implementation. */ return (rt_game_api){ - .RegisterCVARs = __NullGame_RegisterCVARs, - .Init = __NullGame_Init, - .Shutdown = __NullGame_Shutdown, - .Update = __NullGame_Update, - .Render = __NullGame_Render, + .RegisterCVARs = __NullGame_RegisterCVARs, + .Init = __NullGame_Init, + .Shutdown = __NullGame_Shutdown, + .Update = __NullGame_Update, + .Render = __NullGame_Render, + .OnGameObjectFree = NULL, + .OnReload = NULL, + .OnUnload = NULL, }; } +static rt_game_api _game; +static rt_window _window; +static void *_game_obj = NULL; +static rt_launcher_api _launcher_api; + +static rt_window LauncherAPIGetWindow(void) { + return _window; +} + +static void *LauncherAPIAllocGameObj(size_t sz) { + if (_game_obj) { + /* Free the old one */ + if (_game.OnGameObjectFree) + _game.OnGameObjectFree(&_launcher_api, _game_obj); + free(_game_obj); + } + _game_obj = malloc(sz); + return _game_obj; +} + +static void *LauncherAPIGetGameObject(void) { + return _game_obj; +} + static void DisplayMonitors(void) { int count = 0; GLFWmonitor **monitors = glfwGetMonitors(&count); @@ -220,10 +250,16 @@ static int Entry(int argc, char **argv) { } g_renderer.RegisterCVARs(); + _launcher_api = (rt_launcher_api){ + .GetWindow = LauncherAPIGetWindow, + .AllocGameObject = LauncherAPIAllocGameObj, + .GetGameObject = LauncherAPIGetGameObject, + }; + /* Load the game */ const char *game_lib_cmdline = ParseCommandLineGameLib(argc, argv); - rt_game_api game = LoadGame(game_lib_cmdline); - game.RegisterCVARs(); + _game = LoadGame(game_lib_cmdline); + _game.RegisterCVARs(&_launcher_api); LoadGameAndRendererConfig(); ParseCommandLineCVARs(argc, argv); @@ -291,6 +327,8 @@ static int Entry(int argc, char **argv) { rtShutdownRuntime(); return -1; } + _window.type = RT_WINDOW_TYPE_GLFW; + _window.glfw = window; if (rt_LauncherCreateGLContext.i) { glfwMakeContextCurrent(window); @@ -316,7 +354,7 @@ static int Entry(int argc, char **argv) { return -1; } - if (game.Init() != RT_SUCCESS) { + if (_game.Init(&_launcher_api) != RT_SUCCESS) { rtReportError("LAUNCHER", "Failed to initialize the renderer."); if (_game_lib) rtCloseLib(_game_lib); @@ -329,6 +367,10 @@ static int Entry(int argc, char **argv) { rt_timestamp previous = rtTimeNow(); rt_time_delta lag = time_per_update; while (!glfwWindowShouldClose(window)) { +#ifdef RT_DEBUG + +#endif + glfwPollEvents(); rt_timestamp current = rtTimeNow(); @@ -339,18 +381,21 @@ static int Entry(int argc, char **argv) { /* TODO: Process input */ while (lag >= time_per_update) { - game.Update(time_per_update); + _game.Update(&_launcher_api, time_per_update, _game_obj); lag -= time_per_update; } - game.Render(); + int disp_w, disp_h; + glfwGetFramebufferSize(window, &disp_w, &disp_h); + rtLog("LAUNCHER", "%d %d", disp_w, disp_h); + _game.Render(&_launcher_api, _game_obj); if (rt_LauncherCreateGLContext.i) { glfwSwapBuffers(window); } } - game.Shutdown(); + _game.Shutdown(&_launcher_api, _game_obj); g_renderer.Shutdown(); glfwDestroyWindow(window); diff --git a/src/launcher/meson.build b/src/launcher/meson.build index b88f771..6284248 100644 --- a/src/launcher/meson.build +++ b/src/launcher/meson.build @@ -1,9 +1,4 @@ -launcher_deps = [thread_dep, m_dep] - -glfw_proj = subproject('glfw', default_options: ['default_library=static', 'warning_level=0', 'werror=false']) -glfw_dep = glfw_proj.get_variable('glfw_dep') - -launcher_deps += glfw_dep +launcher_deps = [thread_dep, glfw_dep, m_dep] launcher_link_libs = [runtime_lib] if get_option('default_library') == 'static'