From 4212e57d06afac8a19b09fdebc24bad10b78f1ac Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 19 Jan 2024 07:21:45 -0800 Subject: Plugin refactor, moving scene from game to plugin. --- gltfview/src/game.c | 138 ++++++++++++++++++++++-------------- gltfview/src/game.h | 2 - gltfview/src/plugins/gltf_view.c | 77 +++++++++++++------- gltfview/src/plugins/gltf_view.h | 9 --- gltfview/src/plugins/plugin.h | 59 +++++++-------- gltfview/src/plugins/texture_view.c | 91 +++++++++++++++++++++--- gltfview/src/plugins/texture_view.h | 3 - 7 files changed, 249 insertions(+), 130 deletions(-) delete mode 100644 gltfview/src/plugins/gltf_view.h delete mode 100644 gltfview/src/plugins/texture_view.h diff --git a/gltfview/src/game.c b/gltfview/src/game.c index c331190..64be4f3 100644 --- a/gltfview/src/game.c +++ b/gltfview/src/game.c @@ -39,6 +39,78 @@ static const int WIDTH = 1350; static const int HEIGHT = 900; static const int MAX_FPS = 60; +/// Initialize the game's plugin. +static bool init_plugin(Game* game) { + assert(game); + assert(game->plugin); + // Plugin state is allowed to be null, either when the plugin does not + // expose an init() or when init() does not initialize a state. + if (plugin_resolve(game->plugin, plugin_init, "init")) { + State* plugin_state = 0; + if (!plugin_call(game->plugin, plugin_init, "init", game, &plugin_state)) { + return false; + } + set_plugin_state(game->plugin, plugin_state); + } + return true; // Plugin does not need to expose an init(). +} + +/// Shutdown the game's plugin. +/// The game's plugin is allowed to be null in the call to this function. +static void shutdown_plugin(Game* game) { + assert(game); + if (game->plugin && + (plugin_resolve(game->plugin, plugin_shutdown, "shutdown"))) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call(game->plugin, plugin_shutdown, "shutdown", game, plugin_state); + set_plugin_state(game->plugin, 0); + } +} + +/// Boot the game's plugin. +static bool boot_plugin(Game* game) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_boot, "boot")) { + void* plugin_state = get_plugin_state(game->plugin); + return plugin_call(game->plugin, plugin_boot, "boot", game, plugin_state); + } + return true; // Plugin does not need to expose a boot(). +} + +/// Update the plugin's state. +static void update_plugin(Game* game, double t, double dt) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_update, "update")) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call( + game->plugin, plugin_update, "update", game, plugin_state, t, dt); + } +} + +/// Plugin render. +static void render_plugin(const Game* game) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_render, "render")) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call(game->plugin, plugin_render, "render", game, plugin_state); + } +} + +/// Plugin resize. +static void resize_plugin(Game* game, int width, int height) { + assert(game); + assert(game->plugin); + if (plugin_resolve(game->plugin, plugin_resize, "resize")) { + void* plugin_state = get_plugin_state(game->plugin); + plugin_call( + game->plugin, plugin_resize, "resize", game, plugin_state, width, + height); + } +} + void app_end(Game* game); bool app_init(const GfxAppDesc* desc, void** app_state) { @@ -88,30 +160,14 @@ bool app_init(const GfxAppDesc* desc, void** app_state) { if (!(game->gfx = gfx_init())) { goto cleanup; } - if (!(game->scene = gfx_make_scene())) { + + if (!init_plugin(game)) { goto cleanup; } - if (!(game->camera = gfx_make_camera())) { + if (!boot_plugin(game)) { goto cleanup; } - if (plugin_resolve(game->plugin, plugin_init, "init")) { - void* plugin_state = plugin_call(game->plugin, plugin_init, "init", game); - if (!plugin_state) { - goto cleanup; - } - set_plugin_state(game->plugin, plugin_state); - } - - if (plugin_resolve(game->plugin, plugin_boot, "boot")) { - void* plugin_state = get_plugin_state(game->plugin); - bool boot_success = - plugin_call(game->plugin, plugin_boot, "boot", plugin_state, game); - if (!boot_success) { - goto cleanup; - } - } - *app_state = game; return true; @@ -123,6 +179,7 @@ cleanup: void app_end(Game* game) { assert(game); + shutdown_plugin(game); if (game->gfx) { gfx_destroy(&game->gfx); } @@ -136,53 +193,24 @@ void app_end(Game* game) { void app_update(Game* game, double t, double dt) { plugin_engine_update(game->plugin_engine); - if (plugin_reloaded(game->plugin) && - plugin_resolve(game->plugin, plugin_init, "init")) { - void* plugin_state = plugin_call(game->plugin, plugin_init, "init", game); - assert(plugin_state); // TODO: handle error better. - set_plugin_state(game->plugin, plugin_state); + if (plugin_reloaded(game->plugin)) { + shutdown_plugin(game); + const bool result = init_plugin(game); + assert(result); // TODO: handle error better. } - if (plugin_resolve(game->plugin, plugin_update, "update")) { - // Plugin state may be null if plugin does not expose init(). - void* plugin_state = get_plugin_state(game->plugin); - plugin_call( - game->plugin, plugin_update, "update", plugin_state, game, t, dt); - } + update_plugin(game, t, dt); } void app_render(const Game* game) { RenderBackend* render_backend = gfx_get_render_backend(game->gfx); - Renderer* renderer = gfx_get_renderer(game->gfx); - gfx_start_frame(render_backend); - - gfx_render_scene( - renderer, - &(RenderSceneParams){ - .mode = RenderDefault, .scene = game->scene, .camera = game->camera}); - - if (plugin_resolve(game->plugin, plugin_render, "render")) { - // Plugin state may be null if plugin does not expose init(). - void* plugin_state = get_plugin_state(game->plugin); - plugin_call(game->plugin, plugin_render, "render", plugin_state, game); - } - + render_plugin(game); gfx_end_frame(render_backend); } void app_resize(Game* game, int width, int height) { - RenderBackend* render_backend = gfx_get_render_backend(game->gfx); - gfx_set_viewport(render_backend, width, height); - - const R fovy = 90 * TO_RAD; - const R aspect = (R)width / (R)height; - const R near = 0.1; - const R far = 1000; - const mat4 projection = mat4_perspective(fovy, aspect, near, far); - - Camera* camera = gfx_get_camera_camera(game->camera); - camera->projection = projection; + resize_plugin(game, width, height); } GFX_APP_MAIN(WIDTH, HEIGHT, MAX_FPS); diff --git a/gltfview/src/game.h b/gltfview/src/game.h index 53725c7..93a5e39 100644 --- a/gltfview/src/game.h +++ b/gltfview/src/game.h @@ -16,6 +16,4 @@ typedef struct { PluginEngine* plugin_engine; Plugin* plugin; Gfx* gfx; - Scene* scene; // TODO: Move scene graph to plugin? - SceneCamera* camera; // TODO: Move too. } Game; diff --git a/gltfview/src/plugins/gltf_view.c b/gltfview/src/plugins/gltf_view.c index c3457a8..c19d1b8 100644 --- a/gltfview/src/plugins/gltf_view.c +++ b/gltfview/src/plugins/gltf_view.c @@ -1,6 +1,7 @@ -#include "gltf_view.h" +#include "plugin.h" #include +#include #include #include #include @@ -23,6 +24,11 @@ static const char* GIRL = #define DEFAULT_SCENE_FILE GIRL +struct State { + Scene* scene; + SceneCamera* camera; +}; + /// Load the skyquad texture. static Texture* load_environment_map(RenderBackend* render_backend) { return gfx_load_texture( @@ -57,15 +63,17 @@ static SceneNode* load_skyquad(RenderBackend* render_backend, SceneNode* root) { } /// Load the 3D scene. -static SceneNode* load_scene(Game* game, const char* scene_filepath) { +static SceneNode* load_scene( + Game* game, State* state, const char* scene_filepath) { assert(game); assert(game->gfx); - assert(game->scene); + assert(state); + assert(state->scene); - SceneNode* root = gfx_get_scene_root(game->scene); + SceneNode* root = gfx_get_scene_root(state->scene); RenderBackend* render_backend = gfx_get_render_backend(game->gfx); - Camera* camera = gfx_get_camera_camera(game->camera); + Camera* camera = gfx_get_camera_camera(state->camera); spatial3_set_position(&camera->spatial, vec3_make(0, 0, 2)); SceneNode* sky_light_node = load_skyquad(render_backend, root); @@ -85,16 +93,20 @@ static SceneNode* load_scene(Game* game, const char* scene_filepath) { return scene_node; } -State* init(Game* game) { +bool init(Game* game, State** pp_state) { assert(game); State* state = calloc(1, sizeof(State)); - return state; -} + if (!state) { + goto cleanup; + } -bool boot(State* state, Game* game) { - assert(state); - assert(game); + if (!(state->scene = gfx_make_scene())) { + goto cleanup; + } + if (!(state->camera = gfx_make_camera())) { + goto cleanup; + } const int argc = game->argc; const char** argv = game->argv; @@ -102,27 +114,44 @@ bool boot(State* state, Game* game) { // Usage: const char* scene_filepath = argc > 1 ? argv[1] : DEFAULT_SCENE_FILE; - SceneNode* node = load_scene(game, scene_filepath); + SceneNode* node = load_scene(game, state, scene_filepath); if (!node) { - return false; + goto cleanup; } Anima* anima = gfx_get_node_anima(node); gfx_play_animation( anima, &(AnimationPlaySettings){.name = "Walk", .loop = true}); + *pp_state = state; return true; + +cleanup: + shutdown(game, state); + if (state) { + free(state); + } + return false; } -void update(State* state, Game* game, double t, double dt) { - assert(state); +void shutdown(Game* game, State* state) { + assert(game); + if (state) { + gfx_destroy_camera(&state->camera); + gfx_destroy_scene(&state->scene); + // State freed by plugin engine. + } +} + +void update(Game* game, State* state, double t, double dt) { assert(game); - assert(game->scene); - assert(game->camera); + assert(state); + assert(state->scene); + assert(state->camera); - gfx_animate_scene(game->scene, (R)t); + gfx_animate_scene(state->scene, (R)t); const vec3 orbit_point = vec3_make(0, 2, 0); - Camera* camera = gfx_get_camera_camera(game->camera); + Camera* camera = gfx_get_camera_camera(state->camera); spatial3_orbit( &camera->spatial, orbit_point, /*radius=*/2.5, @@ -150,18 +179,18 @@ static void render_bounding_boxes(ImmRenderer* imm, const SceneNode* node) { } } -void render(State* state, const Game* game) { +void render(const Game* game, const State* state) { assert(state); assert(game); assert(game->gfx); - assert(game->scene); - assert(game->camera); + assert(state->scene); + assert(state->camera); ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); assert(imm); gfx_imm_start(imm); - gfx_imm_set_camera(imm, gfx_get_camera_camera(game->camera)); + gfx_imm_set_camera(imm, gfx_get_camera_camera(state->camera)); gfx_imm_set_colour(imm, vec4_make(0.2, 0.2, 1.0, 0.3)); - render_bounding_boxes(imm, gfx_get_scene_root(game->scene)); + render_bounding_boxes(imm, gfx_get_scene_root(state->scene)); gfx_imm_end(imm); } diff --git a/gltfview/src/plugins/gltf_view.h b/gltfview/src/plugins/gltf_view.h deleted file mode 100644 index 670d88d..0000000 --- a/gltfview/src/plugins/gltf_view.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "plugin.h" - -#include - -typedef struct State { - int unused; -} State; diff --git a/gltfview/src/plugins/plugin.h b/gltfview/src/plugins/plugin.h index 0e0e12c..a2632cd 100644 --- a/gltfview/src/plugins/plugin.h +++ b/gltfview/src/plugins/plugin.h @@ -1,21 +1,5 @@ /* * Game plugin. - * - * A game plugin exposes three functions: - * - boot(): called once when the plugin is first loaded during the lifetime of - * the game. - * - init() -> state: creates and returns the plugin's state. - * - update(state): takes and updates the state, possibly with side effects. - * - render(): performs custom rendering. - * - * boot() is convenient for one-time initialization of the scene. - * - * init() is called every time the plugin is loaded. It is assumed that the - * plugin's state is encapsulated in the object returned. - * - * update() updates the plugin state and has side effects on the scene. It is - * assumed that update does not reference any global, mutable state outside of - * the scene and the plugin state returned by init(). */ #pragma once @@ -28,22 +12,41 @@ typedef struct State State; -/// Initialize the plugin's state. -State* init(Game*); +/// Initialize the plugin, which may optionally return a state object. +/// +/// This function is called every time the plugin is (re)loaded. +/// +/// It is assumed that the plugin's state is fully encapsulated in the returned +/// state object. The plugin should not store any (mutable) state outside of the +/// returned state object (e.g., no mutable global variables.) +bool init(Game*, State**); + +/// Shut down the plugin. +/// +/// This function is called before the plugin is unloaded. +/// +/// The plugin should perform any destruction needed, but not free the state +/// object; freeing the state object's memory is handled by the caller. +void shutdown(Game*, State*); /// Function called the first time the plugin is loaded throughout the -/// application's lifetime. Allows the plugin to do one-time initialization of -/// the game state. -bool boot(State*, Game*); +/// application's lifetime. This allows the plugin to do one-time initialization +/// of the game state. +bool boot(Game*, State*); /// Update the plugin's and the game's state. -void update(State*, Game*, double t, double dt); +void update(Game*, State*, double t, double dt); -/// Optional plugin rendering hook. -void render(State*, const Game*); +/// Render hook. +void render(const Game*, const State*); + +/// Called when the game's window is resized. +void resize(Game* game, State* state, int width, int height); // Signatures for the plugin's exposed functions. -typedef void* (*plugin_init)(Game*); -typedef bool (*plugin_boot)(State*, Game*); -typedef void (*plugin_update)(State*, Game*, double t, double dt); -typedef void (*plugin_render)(State*, const Game*); +typedef bool (*plugin_init)(Game*, State**); +typedef bool (*plugin_shutdown)(Game*, State*); +typedef bool (*plugin_boot)(Game*, State*); +typedef void (*plugin_update)(Game*, State*, double t, double dt); +typedef void (*plugin_render)(const Game*, const State*); +typedef void (*plugin_resize)(Game* game, State* state, int width, int height); diff --git a/gltfview/src/plugins/texture_view.c b/gltfview/src/plugins/texture_view.c index f16c8d1..b424158 100644 --- a/gltfview/src/plugins/texture_view.c +++ b/gltfview/src/plugins/texture_view.c @@ -1,6 +1,7 @@ -#include "texture_view.h" +#include "plugin.h" #include +#include #include #include #include @@ -9,13 +10,25 @@ #include #include +#include // Default texture to load if no texture is provided. static const char* DEFAULT_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; // static const char* DEFAULT_TEXTURE = "/assets/checkerboard.jpg"; -bool boot(State* state, Game* game) { +struct State { + Scene* scene; + SceneCamera* camera; +}; + +bool init(Game* game, State** pp_state) { assert(game); + assert(pp_state); + + State* state = calloc(1, sizeof(State)); + if (!state) { + goto cleanup; + } // Usage: [texture file] const char* texture_file = game->argc > 1 ? game->argv[1] : DEFAULT_TEXTURE; @@ -30,17 +43,17 @@ bool boot(State* state, Game* game) { .mipmaps = false, .data.texture.filepath = mstring_make(texture_file)}); if (!texture) { - return false; + goto cleanup; } ShaderProgram* shader = gfx_make_view_texture_shader(render_backend); if (!shader) { - return false; + goto cleanup; } Geometry* geometry = gfx_make_quad_11(render_backend); if (!geometry) { - return false; + goto cleanup; } MaterialDesc material_desc = (MaterialDesc){.num_uniforms = 1}; @@ -50,25 +63,85 @@ bool boot(State* state, Game* game) { .name = sstring_make("Texture")}; Material* material = gfx_make_material(&material_desc); if (!material) { - return false; + goto cleanup; } const MeshDesc mesh_desc = (MeshDesc){.geometry = geometry, .material = material, .shader = shader}; Mesh* mesh = gfx_make_mesh(&mesh_desc); if (!mesh) { - return false; + goto cleanup; } SceneObject* object = gfx_make_object(); if (!object) { - return false; + goto cleanup; } gfx_add_object_mesh(object, mesh); + if (!(state->scene = gfx_make_scene())) { + goto cleanup; + } + SceneNode* node = gfx_make_object_node(object); - SceneNode* root = gfx_get_scene_root(game->scene); + if (!node) { + goto cleanup; + } + SceneNode* root = gfx_get_scene_root(state->scene); + if (!root) { + goto cleanup; + } gfx_set_node_parent(node, root); + if (!(state->camera = gfx_make_camera())) { + goto cleanup; + } + + *pp_state = state; return true; + +cleanup: + shutdown(game, state); + if (state) { + free(state); + } + return false; +} + +void shutdown(Game* game, State* state) { + assert(game); + if (state) { + gfx_destroy_camera(&state->camera); + gfx_destroy_scene(&state->scene); + // State freed by plugin engine. + } +} + +void render(const Game* game, const State* state) { + assert(game); + assert(state); + + Renderer* renderer = gfx_get_renderer(game->gfx); + gfx_render_scene( + renderer, &(RenderSceneParams){ + .mode = RenderDefault, + .scene = state->scene, + .camera = state->camera}); +} + +void resize(Game* game, State* state, int width, int height) { + assert(game); + assert(state); + + RenderBackend* render_backend = gfx_get_render_backend(game->gfx); + gfx_set_viewport(render_backend, width, height); + + const R fovy = 90 * TO_RAD; + const R aspect = (R)width / (R)height; + const R near = 0.1; + const R far = 1000; + const mat4 projection = mat4_perspective(fovy, aspect, near, far); + + Camera* camera = gfx_get_camera_camera(state->camera); + camera->projection = projection; } diff --git a/gltfview/src/plugins/texture_view.h b/gltfview/src/plugins/texture_view.h deleted file mode 100644 index 956f34a..0000000 --- a/gltfview/src/plugins/texture_view.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#include "plugin.h" -- cgit v1.2.3