rtengine/src/launcher/launcher.c
Kevin Trogant dd76b924c0
All checks were successful
Ubuntu Cross to Win64 / Cross Compile with ming64 (1.4.0, ubuntu-latest) (push) Successful in 1m46s
feat(launcher): Convert windows command line to UTF-8
2024-07-24 19:57:23 +02:00

445 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 <runtime/fsutils.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.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;
static rt_game_api _game;
static rt_window _window;
static void *_game_obj = NULL;
static rt_launcher_api _launcher_api;
#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 _null_api = {
.RegisterCVARs = __NullGame_RegisterCVARs,
.Init = __NullGame_Init,
.Shutdown = __NullGame_Shutdown,
.Update = __NullGame_Update,
.Render = __NullGame_Render,
.OnGameObjectFree = NULL,
};
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) {
_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 _null_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)) {
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);
_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
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow) {
_hInstance = hInstance;
/* Convert to UTF-8 argv array */
LPWSTR pWCmdLine = GetCommandLineW();
int argc = 0;
LPWSTR *pWArgv = CommandLineToArgvW(pWCmdLine, &argc);
/* Determine total amount of memory needed */
size_t mem_required = sizeof(char *) * argc; /* array of pointers */
for (int i = 0; i < argc; ++i) {
mem_required += (size_t)WideCharToMultiByte(CP_UTF8, WC_COMPOSITECHECK, pWArgv[i], -1, NULL, 0, NULL, NULL);
}
void *argv_mem = VirtualAlloc(NULL, mem_required, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
char **argv = argv_mem;
char *arg = (char *)(argv + argc);
for (int i = 0; i < argc; ++i) {
int len = WideCharToMultiByte(CP_UTF8, WC_COMPOSITECHECK, pWArgv[i], -1, NULL, 0, NULL, NULL);
WideCharToMultiByte(CP_UTF8, WC_COMPOSITECHECK, pWArgv[i], -1, arg, len, NULL, NULL);
argv[i] = arg;
arg += len;
}
LocalFree(pWArgv);
for (int i = 0; i < argc; ++i) {
rtLog("LAUNCHER", "argv[%d]: %s", i, argv[i]);
}
int res = Entry(argc, argv);
VirtualFree(argv_mem, 0, MEM_RELEASE);
return res;
}
#else
int main(int argc, char **argv) {
return Entry(argc, argv);
}
#endif