All checks were successful
Ubuntu Cross to Win64 / Cross Compile with ming64 (1.4.0, ubuntu-latest) (push) Successful in 1m46s
445 lines
14 KiB
C
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
|