From 1e3fcf5b38d67fb54102786be74af42be5c6792f Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Tue, 3 Jan 2023 08:49:54 -0800 Subject: Initial commit. --- gfx-app/CMakeLists.txt | 14 +++++ gfx-app/README.md | 3 + gfx-app/include/gfx/gfx_app.h | 23 ++++++++ gfx-app/src/gfx_app.c | 126 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 gfx-app/CMakeLists.txt create mode 100644 gfx-app/README.md create mode 100644 gfx-app/include/gfx/gfx_app.h create mode 100644 gfx-app/src/gfx_app.c (limited to 'gfx-app') diff --git a/gfx-app/CMakeLists.txt b/gfx-app/CMakeLists.txt new file mode 100644 index 0000000..d9cb80f --- /dev/null +++ b/gfx-app/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.0) + +project(gfx-app) + +add_library(gfx-app + src/gfx_app.c) + +target_include_directories(gfx-app PUBLIC + include/) + +target_link_libraries(gfx-app PUBLIC + glfw + log + timer) diff --git a/gfx-app/README.md b/gfx-app/README.md new file mode 100644 index 0000000..b21d1aa --- /dev/null +++ b/gfx-app/README.md @@ -0,0 +1,3 @@ +# Gfx App + +A small library to more conveniently create a window and run a game loop. diff --git a/gfx-app/include/gfx/gfx_app.h b/gfx-app/include/gfx/gfx_app.h new file mode 100644 index 0000000..bdb3550 --- /dev/null +++ b/gfx-app/include/gfx/gfx_app.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +typedef struct GfxAppDesc { + int argc; // Number of application arguments. + const char** argv; // Application arguments. + int width; // Window width. + int height; // Window height. + int max_fps; // Desired maximum framerate. 0 to disable. + double update_delta_time; // Desired delta time between frame updates. +} GfxAppDesc; + +typedef struct GfxAppCallbacks { + bool (*init)(const GfxAppDesc*, void** app_state); + void (*update)(void* app_state, double t, double dt); + void (*render)(void* app_state); + void (*resize)(void* app_state, int width, int height); + void (*shutdown)(void* app_state); +} GfxAppCallbacks; + +/// Create a window with an OpenGL context and run the main loop. +bool gfx_app_run(const GfxAppDesc*, const GfxAppCallbacks*); diff --git a/gfx-app/src/gfx_app.c b/gfx-app/src/gfx_app.c new file mode 100644 index 0000000..f3f0473 --- /dev/null +++ b/gfx-app/src/gfx_app.c @@ -0,0 +1,126 @@ +#include + +#include +#include +#include + +#include +#include + +/// Application state. +typedef struct GfxApp { + void* app_state; + GfxAppCallbacks callbacks; + int max_fps; + double update_delta_time; + GLFWwindow* window; +} GfxApp; + +/// Storing the application state in a global variable so that we can call the +/// application's callbacks from GLFW callbacks. +static GfxApp g_gfx_app; + +/// Called by GLFW when the window is resized. +static void on_resize(GLFWwindow* window, int width, int height) { + (*g_gfx_app.callbacks.resize)(g_gfx_app.app_state, width, height); +} + +/// Run the application's main loop. +static void loop(GfxApp* app) { + assert(app); + assert(app->window); + + const double min_frame_time = + app->max_fps > 0 ? 1.0 / (double)(app->max_fps) : 0.0; + const double update_dt = app->update_delta_time; + double time = 0.0; + double time_budget = 0.0; + Timer timer = timer_make(); + + // Warm up the rendering before entering the main loop. A renderer can compile + // shaders and do other initialization the first time it renders a scene. + (*app->callbacks.render)(app->app_state); + glfwSwapBuffers(app->window); + + timer_start(&timer); + while (!glfwWindowShouldClose(app->window)) { + timer_tick(&timer); + time_budget += time_delta_to_sec(timer.delta_time); + + while (time_budget >= update_dt) { + (*app->callbacks.update)(app->app_state, time, update_dt); + time += update_dt; + time_budget -= update_dt; + } + + (*app->callbacks.render)(app->app_state); + glfwSwapBuffers(app->window); + glfwPollEvents(); + + const time_point frame_end = time_now(); + const time_delta frame_time = time_diff(timer.last_tick, frame_end); + if (min_frame_time > 0.0 && frame_time < min_frame_time) { + time_sleep(min_frame_time - frame_time); + } + } +} + +bool gfx_app_run(const GfxAppDesc* desc, const GfxAppCallbacks* callbacks) { + assert(desc); + assert(callbacks); + + bool success = false; + + g_gfx_app.callbacks = *callbacks; + g_gfx_app.max_fps = desc->max_fps; + g_gfx_app.update_delta_time = desc->update_delta_time; + g_gfx_app.window = 0; + + if (!glfwInit()) { + LOGE("glfwInit() failed"); + return false; + } + + const int major = 4; + const int minor = 4; + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, major); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, minor); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); + // TODO: Test antialiasing later on. + // glfwWindowHint(GLFW_SAMPLES, 4); + + g_gfx_app.window = + glfwCreateWindow(desc->width, desc->height, "space", NULL, NULL); + if (!g_gfx_app.window) { + LOGE("glfwCreateWindow() failed"); + goto cleanup; + } + glfwMakeContextCurrent(g_gfx_app.window); + + // Initialize the application's state before setting any callbacks. + if (!(*g_gfx_app.callbacks.init)(desc, &g_gfx_app.app_state)) { + LOGE("Failed to initialize application"); + goto cleanup; + } + + // Trigger an initial resize for convenience. + (*g_gfx_app.callbacks.resize)(g_gfx_app.app_state, desc->width, desc->height); + + // Set GLFW callbacks now that the application has been initialized. + glfwSetWindowSizeCallback(g_gfx_app.window, on_resize); + + loop(&g_gfx_app); + + (*g_gfx_app.callbacks.shutdown)(g_gfx_app.app_state); + + success = true; + +cleanup: + if (g_gfx_app.window) { + glfwDestroyWindow(g_gfx_app.window); + } + glfwTerminate(); + return success; +} -- cgit v1.2.3