diff --git a/cfg/launcher.cfg b/cfg/launcher.cfg new file mode 100644 index 0000000..b6c0bf7 --- /dev/null +++ b/cfg/launcher.cfg @@ -0,0 +1,6 @@ +rt_Renderer = vk +rt_WindowTitle = "rtengine" +rt_WindowWidth = 1024 +rt_WindowHeight = 768 +rt_WindowMode = 0 +rt_GameLib = "(null)" diff --git a/src/launcher/game_api.h b/src/launcher/game_api.h index 09aad92..a692fef 100644 --- a/src/launcher/game_api.h +++ b/src/launcher/game_api.h @@ -2,15 +2,20 @@ #define RT_LAUNCHER_GAME_API_H #include +#include typedef void rt_game_register_cvars_fn(void); typedef rt_result rt_game_initialize_fn(void); -typedef void rt_game_shutdown(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); typedef struct { rt_game_register_cvars_fn *RegisterCVARs; rt_game_initialize_fn *Init; - rt_game_shutdown *Shutdown; + rt_game_shutdown_fn *Shutdown; + rt_game_update_fn *Update; + rt_game_render_fn *Render; } rt_game_api; diff --git a/src/launcher/launcher.c b/src/launcher/launcher.c index 83afd14..e29a127 100644 --- a/src/launcher/launcher.c +++ b/src/launcher/launcher.c @@ -1,6 +1,6 @@ -#include "runtime/aio.h" #include "runtime/file_tab.h" #include "runtime/mem_arena.h" +#include "runtime/timing.h" #ifdef __WIN32 #define GLFW_EXPOSE_NATIVE_WIN32 @@ -29,6 +29,13 @@ RT_CVAR_I(rt_WindowMode, "The window mode. Available options: 0 (=Windowed), 1 ( RT_CVAR_I(rt_FullscreenRefreshRate, "Requested refresh rate for exclusive fullscreen. Set to 0 to use the monitors current setting. (Default: 0)", 0); RT_CVAR_S(rt_Monitor, "Name of the monitor on which the window should be created. Leave empty to use the primary monitor. (Default: "")", ""); +RT_CVAR_F(rt_Framerate, "Target framerate in FPS. (Default: 60.0)", 60.0); + +/* These are for experiments and debugging */ +RT_CVAR_I(rt_LauncherCreateGLContext, "Create an OpenGL context in the launcher. 1: on, 0: off. (Default: 0)", 0); +RT_CVAR_I(rt_LauncherGLContextMajor, "OpenGL Major version. (Default: 4)", 4); +RT_CVAR_I(rt_LauncherGLContextMinor, "OpenGL minor version. (Default: 5)", 5); + RT_CVAR_S(rt_GameLib, "Path to the game library. Only usable in internal builds. (Default: "")", ""); enum { @@ -57,6 +64,12 @@ static void SetupConfig(void) { rtRegisterCVAR(&rt_WindowMode); rtRegisterCVAR(&rt_Monitor); + rtRegisterCVAR(&rt_Framerate); + + rtRegisterCVAR(&rt_LauncherCreateGLContext); + rtRegisterCVAR(&rt_LauncherGLContextMajor); + rtRegisterCVAR(&rt_LauncherGLContextMinor); + rtRegisterCVAR(&rt_GameLib); rt_file_id config_fid = rtAddFile("cfg/launcher.cfg"); @@ -73,9 +86,46 @@ static void LoadGameAndRendererConfig() { rtProcessConfigFiles(RT_ARRAY_COUNT(fids), fids); } +static void ParseCommandLineCVARs(int argc, char **argv) { + for (int i = 1; i < argc - 1; ++i) { + const char *name = argv[i]; + if (name[0] != '-' || name[1] != '-') + continue; + name = &name[2]; + rt_cvar *cvar = rtGetCVAR(name); + if (!cvar) { + ++i; /* Skip value */ + continue; + } + const char *value = argv[i + 1]; + + if (rtSetCVARFromString(cvar, value) != RT_SUCCESS) { + rtLog("LAUNCHER", "Failed to set %s to %s. Invalid value?", cvar->name, value); + } + + /* Skip value */ + ++i; + } +} + +static char *ParseCommandLineGameLib(int argc, char **argv) { + char *game_lib_cmdline = NULL; +#ifdef RT_DEBUG + for (int i = 1; i < argc - 1; ++i) { + if (strcmp(argv[i], "--game") == 0) { + game_lib_cmdline = argv[i + 1]; + break; + } + } +#endif + 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 rt_game_api LoadGame(const char *cmdline_gamelib) { @@ -111,6 +161,8 @@ out: .RegisterCVARs = __NullGame_RegisterCVARs, .Init = __NullGame_Init, .Shutdown = __NullGame_Shutdown, + .Update = __NullGame_Update, + .Render = __NullGame_Render, }; } @@ -169,25 +221,26 @@ static int Entry(int argc, char **argv) { g_renderer.RegisterCVARs(); /* Load the game */ - const char *game_lib_cmdline = NULL; -#ifdef RT_DEBUG - for (int i = 1; i < argc - 1; ++i) { - if (strcmp(argv[i], "--game") == 0) { - game_lib_cmdline = argv[i + 1]; - break; - } - } -#endif + const char *game_lib_cmdline = ParseCommandLineGameLib(argc, argv); rt_game_api game = LoadGame(game_lib_cmdline); game.RegisterCVARs(); LoadGameAndRendererConfig(); + ParseCommandLineCVARs(argc, argv); /* Create the window */ GLFWmonitor *monitor = ChooseMonitor(); GLFWwindow *window = NULL; - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + if (!rt_LauncherCreateGLContext.i) { + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + } else { + rtLog("LAUNCHER", "Creating an OpenGL %d.%d context", rt_LauncherGLContextMajor.i, rt_LauncherGLContextMinor.i); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, rt_LauncherGLContextMajor.i); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, rt_LauncherGLContextMinor.i); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + } glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); glfwWindowHint(GLFW_CENTER_CURSOR, GLFW_TRUE); @@ -239,6 +292,10 @@ static int Entry(int argc, char **argv) { return -1; } + if (rt_LauncherCreateGLContext.i) { + glfwMakeContextCurrent(window); + } + /* Initialize the renderer */ rt_renderer_init_info renderer_init_info; #ifdef _WIN32 @@ -268,8 +325,29 @@ static int Entry(int argc, char **argv) { return -1; } + rt_time_delta time_per_update = 1.0 / (double)rt_Framerate.f; + rt_timestamp previous = rtTimeNow(); + rt_time_delta lag = time_per_update; while (!glfwWindowShouldClose(window)) { glfwPollEvents(); + + rt_timestamp current = rtTimeNow(); + rt_time_delta elapsed = rtTimeBetween(previous, current); + previous = current; + lag += elapsed; + + /* TODO: Process input */ + + while (lag >= time_per_update) { + game.Update(time_per_update); + lag -= time_per_update; + } + + game.Render(); + + if (rt_LauncherCreateGLContext.i) { + glfwSwapBuffers(window); + } } game.Shutdown(); diff --git a/src/runtime/config.c b/src/runtime/config.c index cb13c30..1eb4a0b 100644 --- a/src/runtime/config.c +++ b/src/runtime/config.c @@ -143,38 +143,7 @@ static int Handler(void *user, const char *section, const char *name, const char return 1; } - int num_read = 0; - switch (cvar->type) { - case RT_CVAR_TYPE_INT: - num_read = sscanf(value, "%d", &cvar->i); - break; - case RT_CVAR_TYPE_FLOAT: - num_read = sscanf(value, "%f", &cvar->f); - break; - case RT_CVAR_TYPE_STRING: { - num_read = 1; - char *copy = rtStoreString(value); - if (!copy) { - rtReportError("CVAR", - "Failed to store string value of cvar %s in config file %s.", - name, - file_path); - return 0; - } - cvar->s = copy; - break; - } - case RT_CVAR_TYPE_SIZE: - num_read = sscanf(value, "%zu", &cvar->sz); - break; - default: - rtReportError("CVAR", "CVar %s has an invalid type.", cvar->name); - return 0; - } - - if (num_read == 1) { - rtNotifyCVARChange(cvar); - } else { + if (rtSetCVARFromString(cvar, value) != RT_SUCCESS) { rtLog("CVAR", "Failed to read value of CVar %s in config file %s.", cvar->name, file_path); } @@ -303,4 +272,40 @@ void ProcessEarlyEngineConfigs(void) { } } free(buf); -} \ No newline at end of file +} + +RT_DLLEXPORT rt_result rtSetCVARFromString(rt_cvar *cvar, const char *value_str) { + + int num_read = 0; + switch (cvar->type) { + case RT_CVAR_TYPE_INT: + num_read = sscanf(value_str, "%d", &cvar->i); + break; + case RT_CVAR_TYPE_FLOAT: + num_read = sscanf(value_str, "%f", &cvar->f); + break; + case RT_CVAR_TYPE_STRING: { + num_read = 1; + char *copy = rtStoreString(value_str); + if (!copy) { + rtReportError("CVAR", + "Failed to store string value of cvar %s.", + cvar->name); + return RT_OUT_OF_MEMORY; + } + cvar->s = copy; + break; + } + case RT_CVAR_TYPE_SIZE: + num_read = sscanf(value_str, "%zu", &cvar->sz); + break; + default: + rtReportError("CVAR", "CVar %s has an invalid type.", cvar->name); + return 0; + } + + if (num_read == 1) { + rtNotifyCVARChange(cvar); + } + return (num_read) ? RT_SUCCESS : RT_INVALID_VALUE; +} diff --git a/src/runtime/config.h b/src/runtime/config.h index 42fc03f..e5ce05f 100644 --- a/src/runtime/config.h +++ b/src/runtime/config.h @@ -57,8 +57,10 @@ RT_DLLEXPORT void rtNotifyCVARChange(const rt_cvar *cvar); * They are processed in-order, meaning later files can overwrite earlier files. */ RT_DLLEXPORT rt_result rtProcessConfigFiles(unsigned int count, const rt_file_id *fids); +RT_DLLEXPORT rt_result rtSetCVARFromString(rt_cvar *cvar, const char *value_str); + #ifdef __cplusplus } #endif -#endif \ No newline at end of file +#endif diff --git a/src/runtime/timing.c b/src/runtime/timing.c index 50efdc4..a0f5c96 100644 --- a/src/runtime/timing.c +++ b/src/runtime/timing.c @@ -52,7 +52,7 @@ rt_result InitTiming(void) { rtReportError("TIMING", "Clock reports resolution greater than 1 second."); return RT_INVALID_VALUE; } - _gettime_freq = res.tv_nsec * 1000000000; + _gettime_freq = 1000000000; double us_res = (double)res.tv_nsec / 1e3; rtLog("TIMING", "clock_gettime resolution: %.3lf us.", us_res);