From f232a94f92ea407706117c0c6ce69a453563db23 Mon Sep 17 00:00:00 2001 From: Kevin Trogant Date: Tue, 23 Jul 2024 16:04:56 +0200 Subject: [PATCH] feat: Launcher application that loads the game and creates the window --- meson.build | 14 +- meson_options.txt | 4 +- src/launcher/game_api.h | 25 +++ src/launcher/launcher.c | 253 +++++++++++++++++++++++++++++ src/launcher/meson.build | 28 ++++ src/meson.build | 3 +- src/renderer/common/load_stub.c | 20 ++- src/renderer/common/renderer_api.h | 4 + src/renderer/dx11/init.cpp | 8 +- src/renderer/vk/init.c | 9 +- src/runtime/config.c | 2 + src/runtime/init.c | 2 +- src/runtime/meson.build | 2 +- 13 files changed, 355 insertions(+), 19 deletions(-) create mode 100644 src/launcher/game_api.h create mode 100644 src/launcher/launcher.c create mode 100644 src/launcher/meson.build diff --git a/meson.build b/meson.build index e4eebad..84bb3e1 100644 --- a/meson.build +++ b/meson.build @@ -74,10 +74,10 @@ m_dep = compiler.find_library('m', required : false) meshoptimizer_proj = subproject('meshoptimizer', default_options: ['warning_level=0', 'werror=false'] ) meshoptimizer_dep = meshoptimizer_proj.get_variable('meshoptimizer_dep') -windowing_dep = [] -if get_option('use_xlib') - windowing_dep = dependency('x11', required : true) +if host_machine.system() == 'linux' and get_option('use_xlib') add_project_arguments(['-DRT_USE_XLIB'], language : ['c', 'cpp']) +elif host_machine.system() == 'linux' and get_option('use_wayland') + add_project_arguments(['-DRT_USE_WAYLAND'], language: ['c', 'cpp']) endif # Copy file utility @@ -98,16 +98,16 @@ subdir('src') engine_link_libs = [] if get_option('default_library') == 'static' if get_option('static_renderer') == 'vk' - engine_link_libs = [runtime_lib, app_lib, vk_renderer_lib] + engine_link_libs = [runtime_lib, vk_renderer_lib] elif get_option('static_renderer') == 'null' - engine_link_libs = [runtime_lib, app_lib, null_renderer_lib] + engine_link_libs = [runtime_lib, null_renderer_lib] elif get_option('static_renderer') == 'dx11' - engine_link_libs = [runtime_lib, app_lib, dx11_renderer_lib] + engine_link_libs = [runtime_lib, dx11_renderer_lib] else error('Invalid static_renderer option ', get_option('static_renderer')) endif else - engine_link_libs = [runtime_lib, gfx_lib, app_lib] + engine_link_libs = [runtime_lib] endif # Unit/Integration test driver diff --git a/meson_options.txt b/meson_options.txt index 15a2ab0..9d2e638 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,5 +1,6 @@ option('static_renderer', type : 'string', value : 'dx11', description : 'Name of the renderer used for static builds') -option('use_xlib', type : 'boolean', value : false, description : 'Use Xlib for window creation under linux') +option('use_xlib', type : 'boolean', value : true, description : 'Use Xlib for window creation under linux') +option('use_wayland', type : 'boolean', value : false, description : 'Use wayland for window creation under linux') option('error_report_debugbreak', type : 'boolean', value : true, description : 'Debugbreak in ReportError') option('enable_dxc_shader_compiler', type : 'boolean', value : true, description : 'Enables building the dxc-based shader compiler.') option('enable_dx11_shader_compiler', type : 'boolean', value : true, description : 'Enables building the dx11-bases shader compiler.') @@ -7,3 +8,4 @@ option('game_as_subdir', type : 'boolean', value : false, description : 'If true option('build_dx11', type : 'boolean', value : true, description : 'Enables/disables the build of the dx11 renderer.') option('build_vk', type : 'boolean', value : true, description : 'Enables/disables the build of the vulkan renderer.') option('build_experiments', type : 'boolean', value : false, description : 'Enables/disables building the experiments in src/experimental.') +option('launcher_name', type : 'string', value : 'launcher', description : 'Name of the launcher executable.', yield : true) diff --git a/src/launcher/game_api.h b/src/launcher/game_api.h new file mode 100644 index 0000000..09aad92 --- /dev/null +++ b/src/launcher/game_api.h @@ -0,0 +1,25 @@ +#ifndef RT_LAUNCHER_GAME_API_H +#define RT_LAUNCHER_GAME_API_H + +#include + +typedef void rt_game_register_cvars_fn(void); +typedef rt_result rt_game_initialize_fn(void); +typedef void rt_game_shutdown(void); + +typedef struct { + rt_game_register_cvars_fn *RegisterCVARs; + rt_game_initialize_fn *Init; + rt_game_shutdown *Shutdown; +} rt_game_api; + + +typedef rt_game_api rt_load_game_api_fn(void); + +#ifdef __cplusplus +#define LOAD_GAME_API_FUNC extern "C" RT_DLLEXPORT rt_game_api rtLoadGameAPI() +#else +#define LOAD_GAME_API_FUNC RT_DLLEXPORT rt_game_api rtLoadGameAPI() +#endif + +#endif diff --git a/src/launcher/launcher.c b/src/launcher/launcher.c new file mode 100644 index 0000000..eab6530 --- /dev/null +++ b/src/launcher/launcher.c @@ -0,0 +1,253 @@ +#include "runtime/aio.h" +#include "runtime/file_tab.h" +#include "runtime/mem_arena.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 "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_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_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 __NullGame_RegisterCVARs(void) {} +static rt_result __NullGame_Init(void) { return RT_SUCCESS; } +static void __NullGame_Shutdown(void) {} + + +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 (rt_game_api){ + .RegisterCVARs = __NullGame_RegisterCVARs, + .Init = __NullGame_Init, + .Shutdown = __NullGame_Shutdown, + }; +} + +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; + } + + /* 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(); + + /* 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 + rt_game_api game = LoadGame(game_lib_cmdline); + game.RegisterCVARs(); + + LoadGameAndRendererConfig(); + + /* Create the window */ + GLFWwindow *window = NULL; + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + 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, glfwGetPrimaryMonitor(), 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, glfwGetPrimaryMonitor(), 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; + } + + /* 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) { + + } + + if (game.Init() != RT_SUCCESS) { + + } + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } + + game.Shutdown(); + g_renderer.Shutdown(); + + glfwDestroyWindow(window); + glfwTerminate(); + if (_game_lib) + rtCloseLib(_game_lib); + rtShutdownRuntime(); + + return 0; +} + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCVE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { + _hInstance = hInstance; + return 0; +} +#else +int main(int argc, char **argv) { + return Entry(argc, argv); +} +#endif diff --git a/src/launcher/meson.build b/src/launcher/meson.build new file mode 100644 index 0000000..b88f771 --- /dev/null +++ b/src/launcher/meson.build @@ -0,0 +1,28 @@ +launcher_deps = [thread_dep, m_dep] + +glfw_proj = subproject('glfw', default_options: ['default_library=static', 'warning_level=0', 'werror=false']) +glfw_dep = glfw_proj.get_variable('glfw_dep') + +launcher_deps += glfw_dep +launcher_link_libs = [runtime_lib] + +if get_option('default_library') == 'static' + if get_option('static_renderer') == 'vk' + launcher_link_libs += vk_renderer_lib + elif get_option('static_renderer') == 'dx11' + launcher_link_libs += dx11_renderer_lib + else + error('Invalid static_renderer option ', get_option('static_renderer')) + endif +endif + +launcher_name = get_option('launcher_name') + +executable(launcher_name, + 'launcher.c', + '../renderer/common/load_stub.c', + include_directories: engine_incdir, + dependencies: launcher_deps, + link_with: launcher_link_libs, + win_subsystem: 'windows') + diff --git a/src/meson.build b/src/meson.build index 2d8f0f8..d8afb53 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,11 +1,12 @@ subdir('runtime') subdir('asset_compiler') -subdir('app_framework') subdir('renderer/common') subdir('renderer/dx11') subdir('renderer/vk') +subdir('launcher') + if get_option('build_experiments') subdir('experimental') endif diff --git a/src/renderer/common/load_stub.c b/src/renderer/common/load_stub.c index 1fcaecd..a5710e1 100644 --- a/src/renderer/common/load_stub.c +++ b/src/renderer/common/load_stub.c @@ -1,10 +1,10 @@ #include "renderer_api.h" #include "runtime/dynamic_libs.h" #include "runtime/config.h" +#include "runtime/runtime.h" #include -static RT_CVAR_S(rt_Renderer, "The used renderer. Available options: dx11.", "dx11"); RT_DLLEXPORT rt_renderer_api g_renderer; @@ -16,16 +16,28 @@ extern rt_renderer_api rtLoadRendererImpl(void); static rt_dynlib _renderer_lib; #endif +#define DEFAULT_RENDERER "vk" + RT_DLLEXPORT rt_result rtLoadRenderer(void) { rt_load_renderer_impl_fn *LoadRendererImpl = NULL; - if (!rtGetCVAR("rt_Renderer")) - rtRegisterCVAR(&rt_Renderer); + rt_cvar *rt_Renderer = rtGetCVAR("rt_Renderer"); + const char *renderer = DEFAULT_RENDERER; + if (!rt_Renderer) { + rtReportError("RENDERER", "rt_Renderer CVAR is not registered. Falling back to '%s'.", DEFAULT_RENDERER); + } + else { + renderer = rt_Renderer->s; + } + #ifdef RT_STATIC_LIB + RT_UNUSED((void*)renderer); LoadRendererImpl = rtLoadRendererImpl; #else const char *dllname = NULL; - if (strcmp(rt_Renderer.s, "dx11")==0) + if (strcmp(renderer, "dx11")==0) dllname = RT_DLLNAME("rtdx11"); + else if (strcmp(renderer, "vk") == 0) + dllname = RTODLLNAME("rtvk"); else { rtReportError("RENDERER", "Invalid renderer selected: %s", rt_Renderer.s); return RT_INVALID_VALUE; diff --git a/src/renderer/common/renderer_api.h b/src/renderer/common/renderer_api.h index 62e3e96..faf7d9e 100644 --- a/src/renderer/common/renderer_api.h +++ b/src/renderer/common/renderer_api.h @@ -27,6 +27,7 @@ typedef struct { int is_fullscreen; } rt_renderer_init_info; +typedef void rt_renderer_register_cvars_fn(void); typedef rt_result rt_renderer_init_fn(const rt_renderer_init_info *info); typedef void rt_renderer_shutdown_fn(void); @@ -39,6 +40,7 @@ typedef enum { /* Public renderer interface */ typedef struct { + rt_renderer_register_cvars_fn *RegisterCVARs; rt_renderer_init_fn *Init; rt_renderer_shutdown_fn *Shutdown; } rt_renderer_api; @@ -47,6 +49,8 @@ typedef struct { /* Global renderer object */ RT_DLLIMPORT extern rt_renderer_api g_renderer; +/* This function is declared here, but it's definition is not necessarily compiled, + * where you think it is. Normally, you will not have to call this. */ RT_DLLEXPORT rt_result rtLoadRenderer(void); #endif diff --git a/src/renderer/dx11/init.cpp b/src/renderer/dx11/init.cpp index a5ebc66..c37928d 100644 --- a/src/renderer/dx11/init.cpp +++ b/src/renderer/dx11/init.cpp @@ -3,6 +3,9 @@ #include "device.hpp" +void Dx11RegisterCVARs(void) { +} + rt_result Dx11Init(const rt_renderer_init_info *info) { rt_result res = rt_dx11_device::GetInstance()->Initialize(info); if (res != RT_SUCCESS) @@ -20,8 +23,9 @@ void Dx11Shutdown(void) { // Called by the application to retrieve the renderer api extern "C" RT_DLLEXPORT rt_renderer_api rtLoadRendererImpl(void) { rt_renderer_api api = { - .Init = Dx11Init, - .Shutdown = Dx11Shutdown, + .RegisterCVARs = Dx11RegisterCVARs, + .Init = Dx11Init, + .Shutdown = Dx11Shutdown, }; return api; } diff --git a/src/renderer/vk/init.c b/src/renderer/vk/init.c index b256aa6..0559cbe 100644 --- a/src/renderer/vk/init.c +++ b/src/renderer/vk/init.c @@ -1,5 +1,9 @@ #include "renderer/common/renderer_api.h" +void VkRegisterCVARs(void) { + +} + rt_result VkInit(const rt_renderer_init_info *info) { return RT_SUCCESS; } @@ -10,8 +14,9 @@ void VkShutdown(void) { // Called by the application to retrieve the renderer api RT_DLLEXPORT rt_renderer_api rtLoadRendererImpl(void) { rt_renderer_api api = { - .Init = VkInit, - .Shutdown = VkShutdown, + .RegisterCVARs = VkRegisterCVARs, + .Init = VkInit, + .Shutdown = VkShutdown, }; return api; } diff --git a/src/runtime/config.c b/src/runtime/config.c index 0d3994b..cb13c30 100644 --- a/src/runtime/config.c +++ b/src/runtime/config.c @@ -251,6 +251,8 @@ RT_DLLEXPORT rt_result rtProcessConfigFiles(unsigned int count, const rt_file_id } for (unsigned int i = 0; i < count; ++i) { + if (aios[i] == RT_AIO_INVALID_HANDLE) + continue; rt_aio_state state = rtWaitForAIOCompletion(aios[i]); if (state == RT_AIO_STATE_FINISHED) { res = ProcessConfigFile(configs[i].buffer, configs[i].fsz, configs[i].path); diff --git a/src/runtime/init.c b/src/runtime/init.c index e7725f9..88ae051 100644 --- a/src/runtime/init.c +++ b/src/runtime/init.c @@ -83,4 +83,4 @@ RT_DLLEXPORT void rtShutdownRuntime(void) { ShutdownAIO(); ShutdownFileTab(); ShutdownBufferManager(); -} \ No newline at end of file +} diff --git a/src/runtime/meson.build b/src/runtime/meson.build index fe8d412..c027030 100644 --- a/src/runtime/meson.build +++ b/src/runtime/meson.build @@ -6,7 +6,7 @@ lz4_dep = lz4_proj.get_variable('liblz4_dep') xxhash_proj = subproject('xxhash', default_options: ['default_library=static', 'b_sanitize=none']) xxhash_dep = xxhash_proj.get_variable('xxhash_dep') -runtime_deps = [thread_dep, m_dep, windowing_dep, inih_dep, lz4_dep, xxhash_dep] +runtime_deps = [thread_dep, m_dep, inih_dep, lz4_dep, xxhash_dep] runtime_incdirs = contrib_incdir runtime_lib = library('rt', # Project Sources