#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 #include #include #include #include #include #include #include #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