rtengine/src/launcher/launcher.c
Kevin Trogant faa2b904ea
Some checks failed
Ubuntu Cross to Win64 / Cross Compile with ming64 (1.4.0, ubuntu-latest) (push) Has been cancelled
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).
2024-07-24 16:39:31 +02:00

421 lines
14 KiB
C

#include "runtime/file_tab.h"
#include "runtime/mem_arena.h"
#include "runtime/timing.h"
#ifdef __WIN32
#define GLFW_EXPOSE_NATIVE_WIN32
#elif defined(RT_USE_XLIB)
#define GLFW_EXPOSE_NATIVE_X11
#elif defined(RT_USE_WAYLAND)
#define GLFW_EXPOSE_NATIVE_WAYLAND
#endif
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <runtime/runtime.h>
#include <runtime/config.h>
#include <runtime/dynamic_libs.h>
#include <renderer/common/renderer_api.h>
#include <string.h>
#include <stdlib.h>
#include "game_api.h"
RT_CVAR_S(rt_Renderer, "The used renderer. Available options: vk, dx11. (Default: vk)", "vk");
RT_CVAR_S(rt_WindowTitle, "The title used for the game window. (Default: rtengine)", "rtengine");
RT_CVAR_I(rt_WindowWidth, "The window width. (Default: 1024)", 1024);
RT_CVAR_I(rt_WindowHeight, "The window height. (Default: 768)", 768);
RT_CVAR_I(rt_WindowMode, "The window mode. Available options: 0 (=Windowed), 1 (=Borderless Fullscreen), 2 (=Exclusive Fullscreen) (Default: 0)", 0);
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 {
WINDOW_MODE_WINDOWED,
WINDOW_MODE_BORDERLESS_FULLSCREEN,
WINDOW_MODE_FULLSCREEN,
};
/* This is baked in during compilation to make tampering with it harder.
* In debug (internal) builds, this can be overwritten via cvar or command line argument. */
#ifndef RT_GAME_LIB_PATH
#define RT_GAME_LIB_PATH "(null)"
#endif
static rt_dynlib *_game_lib = NULL;
#ifdef _WIN32
static HINSTANCE _hInstance;
#endif
static void SetupConfig(void) {
rtRegisterCVAR(&rt_Renderer);
rtRegisterCVAR(&rt_WindowTitle);
rtRegisterCVAR(&rt_WindowWidth);
rtRegisterCVAR(&rt_WindowHeight);
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");
if (rtProcessConfigFiles(1, &config_fid) != RT_SUCCESS) {
rtLog("LAUNCHER", "Processing launcher configs failed.");
}
}
static void LoadGameAndRendererConfig() {
rt_file_id renderer_cfg_fid = rtAddFile("cfg/renderer.cfg");
rt_file_id game_cfg_fid = rtAddFile("cfg/game.cfg");
rt_file_id fids[2] = { renderer_cfg_fid, game_cfg_fid };
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(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;
#ifdef RT_DEBUG
if (strlen(rt_GameLib.s) > 0) {
game_lib = rt_GameLib.s;
}
if (cmdline_gamelib && strlen(cmdline_gamelib) > 0) {
game_lib = 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);
goto out;
}
rt_load_game_api_fn *LoadGameAPI = rtGetSymbol(_game_lib, "rtLoadGameAPI");
if (!LoadGameAPI) {
rtReportError("LAUNCHER", "%s is not a valid game library (rtLoadGameAPI symbol is missing).", game_lib);
rtCloseLib(_game_lib);
_game_lib = NULL;
goto out;
}
return LoadGameAPI();
}
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,
.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);
rtLog("LAUNCHER", "Available monitors:");
for (int i = 0; i < count; ++i) {
const char *name =glfwGetMonitorName(monitors[i]);
if (monitors[i] != glfwGetPrimaryMonitor())
rtLog("LAUNCHER", " - %s", name);
else
rtLog("LAUNCHER", " - %s (Primary)", name);
}
}
static GLFWmonitor *ChooseMonitor(void) {
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
int count = 0;
if (strcmp(rt_Monitor.s, "") == 0) {
return monitor;
}
GLFWmonitor **monitors = glfwGetMonitors(&count);
for (int i = 0; i < count; ++i) {
const char *name = glfwGetMonitorName(monitors[i]);
if (strcmp(name, rt_Monitor.s) == 0)
return monitors[i];
}
return monitor;
}
static void GlfwErrorCB(int err, const char *desc) {
rtReportError("GLFW", "GLFW Error %d: %s", err, desc);
}
static int Entry(int argc, char **argv) {
if (rtInitRuntime() != RT_SUCCESS)
return -1;
SetupConfig();
glfwSetErrorCallback(GlfwErrorCB);
if (!glfwInit()) {
rtShutdownRuntime();
return -1;
}
DisplayMonitors();
/* Load the renderer library.
* We need it before window creation, to give it an opportunity to register its cvars */
if (rtLoadRenderer() != RT_SUCCESS) {
rtShutdownRuntime();
glfwTerminate();
return -1;
}
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);
_game = LoadGame(game_lib_cmdline);
_game.RegisterCVARs(&_launcher_api);
LoadGameAndRendererConfig();
ParseCommandLineCVARs(argc, argv);
/* Create the window */
GLFWmonitor *monitor = ChooseMonitor();
GLFWwindow *window = NULL;
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);
if (rt_WindowMode.i == WINDOW_MODE_BORDERLESS_FULLSCREEN) {
const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwWindowHint(GLFW_RED_BITS, mode->redBits);
glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
window = glfwCreateWindow(mode->width, mode->height, rt_WindowTitle.s, monitor, NULL);
}
else if (rt_WindowMode.i == WINDOW_MODE_FULLSCREEN) {
int refresh_rate = rt_FullscreenRefreshRate.i;
if (refresh_rate == 0) {
refresh_rate = glfwGetVideoMode(glfwGetPrimaryMonitor())->refreshRate;
}
else {
int count;
glfwGetVideoModes(NULL, &count);
rt_temp_arena temp = rtGetTemporaryArena(NULL, 0);
GLFWvidmode *modes = RT_ARENA_PUSH_ARRAY(temp.arena, GLFWvidmode, count);
int is_supported = 0;
for (int i = 0; i < count; ++i) {
if (modes[i].refreshRate == refresh_rate) {
is_supported = 1;
break;
}
}
rtReturnTemporaryArena(temp);
if (!is_supported) {
rtLog("LAUNCHER", "Requested refresh rate %d Hz is not supported. Using current setting instead.", refresh_rate);
refresh_rate = glfwGetVideoMode(glfwGetPrimaryMonitor())->refreshRate;
}
}
glfwWindowHint(GLFW_REFRESH_RATE, refresh_rate);
window = glfwCreateWindow(rt_WindowWidth.i, rt_WindowHeight.i, rt_WindowTitle.s, monitor, NULL);
}
else {
window = glfwCreateWindow(rt_WindowWidth.i, rt_WindowHeight.i, rt_WindowTitle.s, NULL, NULL);
}
if (!window) {
rtReportError("LAUNCHER", "Failed to create the game window.");
if (_game_lib)
rtCloseLib(_game_lib);
glfwTerminate();
rtShutdownRuntime();
return -1;
}
_window.type = RT_WINDOW_TYPE_GLFW;
_window.glfw = window;
if (rt_LauncherCreateGLContext.i) {
glfwMakeContextCurrent(window);
}
/* Initialize the renderer */
rt_renderer_init_info renderer_init_info;
#ifdef _WIN32
renderer_init_info.hWnd = glfwGetWin32Window(window);
renderer_init_info.hInstance = _hInstance;
#elif defined(__linux__) && defined(RT_USE_XLIB)
renderer_init_info.display = glfwGetX11Display();
renderer_init_info.window = glfwGetX11Window(window);
#endif
renderer_init_info.is_fullscreen = rt_WindowMode.i != WINDOW_MODE_WINDOWED;
glfwGetFramebufferSize(window, (int*)&renderer_init_info.width, (int*)&renderer_init_info.height);
if (g_renderer.Init(&renderer_init_info) != RT_SUCCESS) {
rtReportError("LAUNCHER", "Failed to initialize the renderer.");
if (_game_lib)
rtCloseLib(_game_lib);
glfwTerminate();
rtShutdownRuntime();
return -1;
}
if (_game.Init(&_launcher_api) != RT_SUCCESS) {
rtReportError("LAUNCHER", "Failed to initialize the renderer.");
if (_game_lib)
rtCloseLib(_game_lib);
glfwTerminate();
rtShutdownRuntime();
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)) {
#ifdef RT_DEBUG
#endif
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(&_launcher_api, time_per_update, _game_obj);
lag -= time_per_update;
}
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(&_launcher_api, _game_obj);
g_renderer.Shutdown();
glfwDestroyWindow(window);
glfwTerminate();
if (_game_lib)
rtCloseLib(_game_lib);
rtShutdownRuntime();
return 0;
}
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow) {
_hInstance = hInstance;
return 0;
}
#else
int main(int argc, char **argv) {
return Entry(argc, argv);
}
#endif