feat(launcher): Offer API for the game
Some checks failed
Ubuntu Cross to Win64 / Cross Compile with ming64 (1.4.0, ubuntu-latest) (push) Has been cancelled

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).
This commit is contained in:
Kevin Trogant 2024-07-24 16:39:31 +02:00
parent 578722a0c5
commit faa2b904ea
6 changed files with 201 additions and 85 deletions

View File

@ -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')

View File

@ -1,18 +1,17 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <runtime/runtime.h>
#include <launcher/game_api.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <new>
#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,
};
}

View File

@ -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)

View File

@ -4,21 +4,79 @@
#include <runtime/runtime.h>
#include <runtime/timing.h>
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

View File

@ -18,6 +18,7 @@
#include <runtime/dynamic_libs.h>
#include <renderer/common/renderer_api.h>
#include <string.h>
#include <stdlib.h>
#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);

View File

@ -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'