diff --git a/assets/test/textures/test_terrain2.png b/assets/test/textures/test_terrain2.png new file mode 100644 index 0000000000..100205bfa2 Binary files /dev/null and b/assets/test/textures/test_terrain2.png differ diff --git a/assets/test/textures/test_terrain2.terrain b/assets/test/textures/test_terrain2.terrain new file mode 100644 index 0000000000..2ac912d439 --- /dev/null +++ b/assets/test/textures/test_terrain2.terrain @@ -0,0 +1,12 @@ +# Copyright 2023-2023 the openage authors. See copying.md for legal info. +# openage terrain definition file + +version 2 + +texture 0 "test_terrain2.texture" + +scalefactor 1.0 + +layer 0 + +frame 0 0 0 0 diff --git a/assets/test/textures/test_terrain2.texture b/assets/test/textures/test_terrain2.texture new file mode 100644 index 0000000000..a2d431bd63 --- /dev/null +++ b/assets/test/textures/test_terrain2.texture @@ -0,0 +1,12 @@ +# Copyright 2023-2023 the openage authors. +# openage texture definition file + +version 1 + +imagefile "test_terrain2.png" + +size 500 500 + +pxformat rgba8 cbits=True + +subtex 0 0 500 500 0 0 diff --git a/doc/code/renderer/level1.md b/doc/code/renderer/level1.md index 8a7d4e1eb0..659c51036c 100644 --- a/doc/code/renderer/level1.md +++ b/doc/code/renderer/level1.md @@ -6,7 +6,7 @@ Low-level renderer for communicating with the OpenGL and Vulkan APIs. 1. [Overview](#overview) 2. [Architecture](#architecture) -3. [Basic Usage](#basic--usage) +3. [Basic Usage](#basic-usage) 1. [Window/Renderer Creation](#windowrenderer-creation) 2. [Adding a Shader Program](#adding-a-shader-program) 3. [Creating a Renderable](#creating-a-renderable) @@ -14,8 +14,9 @@ Low-level renderer for communicating with the OpenGL and Vulkan APIs. 4. [Advanced Usage](#advanced-usage) 1. [Addressing Uniforms via numeric IDs](#addressing-uniforms-via-numeric-ids) 2. [Framebuffers / Multiple Render Passes](#framebuffers--multiple-render-passes) - 3. [Complex Geometry](#complex-geometry) - 4. [Uniform Buffers](#uniform-buffers) + 3. [Defining Layers in a Render Pass](#defining-layers-in-a-render-pass) + 4. [Complex Geometry](#complex-geometry) + 5. [Uniform Buffers](#uniform-buffers) 5. [Thread-safety](#thread-safety) @@ -44,7 +45,7 @@ The `resources` namespace provides classes for initializing and loading meshes, These classes are independent from the specific OpenGL/Vulkan backends and can thus be passed to the abstract interface of the renderer renderer to make them usable with graphics hardware. -## Basic Usage +## Basic Usage Code examples can be found in the [renderer demos](/libopenage/renderer/demo/). See the [testing docs](/doc/code/testing.md#python-demos) on how to try them out. @@ -200,9 +201,11 @@ Finally, we can execute the rendering pipeline for all objects in the render pas renderer->render(pass); ``` + After rendering is finished, the window has to be updated to display the rendered result. @@ -312,6 +315,69 @@ Renderable obj { obj.depth_test = true; ``` + +### Defining Layers in a Render Pass + +Layers give more fine-grained control over the draw order of renderables in a render pass. Every +layer has a priority that determines when associated renderables are drawn. Lower priority +renderables are drawn earlier, higher priority renderables are drawn later. + +In comparison to using multiple render passes, layers do not require the (expensive) switching +of framebuffers between passes. The tradeoff is a slight overhead when inserting new renderables +into the render pass. + +To assign renderables to a layer, we have to specify the priority in the `RenderPass::add_renderables(..)` +function call. + +```c++ +Renderable obj { + input, + geom +}; +pass->add_renderables({ obj }, 42); +``` + +For existing layers, new renderables are always appended to the end of the layer. Renderables +are sorted into the correct position automatically when they are added: + +```c++ +pass->add_renderables({ obj1, obj2, obj3 }, 42); +pass->add_renderables({ obj4 }, 0); +pass->add_renderables({ obj5, obj6 }, 1337); +pass->add_renderables({ obj7 }, 0); +// draw order: obj4, obj7, obj1, obj2, obj3, obj5, obj6 +// layers: prio 0 | prio 42 | prio 1337 +``` + +When no priority is specified when calling `RenderPass::add_renderables(..)`, the highest +priority is assumed (which is `std::numeric_limits::max()`). Therefore, +objects added like this are always drawn last. It also means that these two calls are equal: + +```c++ +pass->add_renderables({ obj }); +pass->add_renderables({ obj }, std::numeric_limits::max()); +``` + + +Layers are created lazily during insertion if no layer with the specified priority exists yet. +We can also create layers explicitly for a specific priority: + +```c++ +pass->add_layer(42); +``` + +When executing the rendering pipeline for a specific pass, renderables are drawn layer by layer. +By default, the renderer clears the depth buffer when switching to a new layer. This is done +under the assumption that layers with higher priority should always draw over layers with lower +priority, even when depth tests are active. This behavior can be deactivated when explicitly +creating a layer: + +```c++ +// keep depth testing +pass->add_layer(42, false); +``` + + ### Complex Geometry For displaying complex geometry like 3D objects or non-rectangular surfaces, the renderer diff --git a/libopenage/gamestate/terrain_chunk.cpp b/libopenage/gamestate/terrain_chunk.cpp index 9fc5422b65..e988260333 100644 --- a/libopenage/gamestate/terrain_chunk.cpp +++ b/libopenage/gamestate/terrain_chunk.cpp @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. #include "terrain_chunk.h" @@ -22,22 +22,6 @@ void TerrainChunk::set_render_entity(const std::shared_ptrrender_entity = entity; } -void TerrainChunk::render_update(const time::time_t &time, - const std::string &terrain_path) { - if (this->render_entity != nullptr) { - // TODO: Update individual tiles instead of the whole chunk - std::vector> tiles; - tiles.reserve(this->tiles.size()); - for (const auto &tile : this->tiles) { - tiles.emplace_back(tile.elevation, terrain_path); - } - - this->render_entity->update(this->size, - tiles, - time); - } -} - const util::Vector2s &TerrainChunk::get_size() const { return this->size; } @@ -46,17 +30,13 @@ const coord::tile_delta &TerrainChunk::get_offset() const { return this->offset; } -void TerrainChunk::set_terrain_path(const std::string &terrain_path) { - this->terrain_path = terrain_path; -} - void TerrainChunk::render_update(const time::time_t &time) { if (this->render_entity != nullptr) { // TODO: Update individual tiles instead of the whole chunk std::vector> tiles; tiles.reserve(this->tiles.size()); for (const auto &tile : this->tiles) { - tiles.emplace_back(tile.elevation, terrain_path); + tiles.emplace_back(tile.elevation, tile.terrain_asset_path); } this->render_entity->update(this->size, diff --git a/libopenage/gamestate/terrain_chunk.h b/libopenage/gamestate/terrain_chunk.h index 0cd1840028..684d3af357 100644 --- a/libopenage/gamestate/terrain_chunk.h +++ b/libopenage/gamestate/terrain_chunk.h @@ -34,15 +34,6 @@ class TerrainChunk { */ void set_render_entity(const std::shared_ptr &entity); - /** - * Update the render entity. - * - * @param time Simulation time of the update. - * @param terrain_path Path to the terrain definition used at \p time. - */ - void render_update(const time::time_t &time, - const std::string &terrain_path); - /** * Get the size of this terrain chunk. * @@ -57,14 +48,11 @@ class TerrainChunk { */ const coord::tile_delta &get_offset() const; - // TODO: Remove test texture references - - // Set the terrain path of this terrain chunk. - // TODO: Remove later - void set_terrain_path(const std::string &terrain_path); - - // Send the current texture to the renderer. - // TODO: Replace later with render_update(time, terrain_path) + /** + * Update the render entity. + * + * @param time Simulation time of the update. + */ void render_update(const time::time_t &time); private: @@ -81,7 +69,9 @@ class TerrainChunk { coord::tile_delta offset; /** - * Height map of the terrain chunk. + * Terrain tile info of the terrain chunk. + * + * Layout is row-major. */ std::vector tiles; @@ -89,9 +79,6 @@ class TerrainChunk { * Render entity for pushing updates to the renderer. Can be \p nullptr. */ std::shared_ptr render_entity; - - // TODO: Remove test texture references - std::string terrain_path; }; } // namespace openage::gamestate diff --git a/libopenage/gamestate/terrain_factory.cpp b/libopenage/gamestate/terrain_factory.cpp index 128b92a043..17b5d452cb 100644 --- a/libopenage/gamestate/terrain_factory.cpp +++ b/libopenage/gamestate/terrain_factory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "terrain_factory.h" @@ -20,33 +20,35 @@ namespace openage::gamestate { +// TODO: Remove test terrain references +static const std::vector test_terrain_paths = { + "../test/textures/test_terrain.terrain", + "../test/textures/test_terrain2.terrain", +}; + static const std::vector aoe1_test_terrain = {}; static const std::vector de1_test_terrain = {}; static const std::vector aoe2_test_terrain = { "aoe2_base.data.terrain.foundation.foundation.Foundation", "aoe2_base.data.terrain.grass.grass.Grass", "aoe2_base.data.terrain.dirt.dirt.Dirt", + "aoe2_base.data.terrain.water.water.Water", }; static const std::vector de2_test_terrain = {}; static const std::vector hd_test_terrain = { "hd_base.data.terrain.foundation.foundation.Foundation", "hd_base.data.terrain.grass.grass.Grass", "hd_base.data.terrain.dirt.dirt.Dirt", + "hd_base.data.terrain.water.water.Water", }; static const std::vector swgb_test_terrain = { - "swgb_base.data.terrain.desert0.desert0.Desert0", - "swgb_base.data.terrain.grass2.grass2.Grass2", "swgb_base.data.terrain.foundation.foundation.Foundation", + "swgb_base.data.terrain.grass2.grass2.Grass2", + "swgb_base.data.terrain.desert0.desert0.Desert0", + "swgb_base.data.terrain.water1.water1.Water1", }; static const std::vector trial_test_terrain = {}; -std::shared_ptr TerrainFactory::add_terrain() { - // TODO: Replace this with a proper terrain generator. - auto terrain = std::make_shared(); - - return terrain; -} - // TODO: Remove hardcoded test texture references static std::vector test_terrains; // declare static so we only have to do this once @@ -91,12 +93,94 @@ void build_test_terrains(const std::shared_ptr &gstate) { } } +// Layout of terrain tiles on chunk 0 +// values are the terrain index +static const std::array layout_chunk0{ + // clang-format off + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 0, 0, 0, 2, + 0, 0, 0, 0, 1, 1, 0, 0, 0, 2, + 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, + 0, 0, 1, 1, 3, 3, 1, 1, 0, 0, + 0, 1, 1, 1, 3, 3, 1, 1, 1, 0, + 0, 0, 1, 1, 3, 3, 1, 0, 0, 0, + // clang-format on +}; + +// Layout of terrain tiles on chunk 1 +// values are the terrain index +static const std::array layout_chunk1{ + // clang-format off + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, + // clang-format on +}; + +// Layout of terrain tiles on chunk 2 +// values are the terrain index +static const std::array layout_chunk2{ + // clang-format off + 1, 1, 1, 1, 3, 3, 1, 0, 0, 0, + 1, 1, 1, 1, 3, 3, 1, 1, 0, 0, + 1, 1, 1, 1, 3, 3, 1, 1, 0, 0, + 1, 1, 1, 3, 3, 3, 3, 1, 0, 0, + 1, 3, 3, 3, 3, 3, 3, 3, 1, 2, + 1, 3, 3, 3, 2, 2, 2, 3, 1, 2, + 3, 3, 3, 3, 2, 2, 2, 3, 1, 2, + 1, 3, 3, 3, 2, 2, 2, 3, 1, 1, + 1, 3, 3, 3, 3, 3, 3, 3, 3, 1, + 1, 3, 3, 3, 3, 3, 3, 3, 1, 0, + // clang-format on +}; + +// Layout of terrain tiles on chunk 3 +// values are the terrain index +static const std::array layout_chunk3{ + // clang-format off + 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 1, 1, 2, 0, + 0, 2, 2, 2, 2, 2, 1, 2, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, + // clang-format on +}; + + +static const std::vector> layout_chunks{ + layout_chunk0, + layout_chunk1, + layout_chunk2, + layout_chunk3, +}; + + +std::shared_ptr TerrainFactory::add_terrain() { + // TODO: Replace this with a proper terrain generator. + auto terrain = std::make_shared(); + + return terrain; +} std::shared_ptr TerrainFactory::add_chunk(const std::shared_ptr &gstate, const util::Vector2s size, const coord::tile_delta offset) { - // TODO: Remove test texture references - std::string test_texture_path = "../test/textures/test_terrain.terrain"; + std::string terrain_info_path; // TODO: Remove test texture references // ========== @@ -105,25 +189,40 @@ std::shared_ptr TerrainFactory::add_chunk(const std::shared_ptr tiles{}; + tiles.reserve(size[0] * size[1]); + + static size_t test_chunk_index = 0; if (not test_terrains.empty()) { // use one of the modpack terrain textures - if (test_terrain_index >= test_terrains.size()) { - test_terrain_index = 0; + if (test_chunk_index >= layout_chunks.size()) { + test_chunk_index = 0; } - terrain_obj = gstate->get_db_view()->get_object(test_terrains[test_terrain_index]); - test_texture_path = api::APITerrain::get_terrain_path(terrain_obj.value()); - test_terrain_index += 1; + for (size_t i = 0; i < size[0] * size[1]; ++i) { + size_t terrain_index = layout_chunks.at(test_chunk_index).at(i); + terrain_obj = gstate->get_db_view()->get_object(test_terrains.at(terrain_index)); + terrain_info_path = api::APITerrain::get_terrain_path(terrain_obj.value()); + tiles.push_back({terrain_obj, terrain_info_path, terrain_elevation_t::zero()}); + } + + test_chunk_index += 1; } - // ========== + else { + // use a test texture + if (test_chunk_index >= test_terrain_paths.size()) { + test_chunk_index = 0; + } + terrain_info_path = test_terrain_paths.at(test_chunk_index); - // fill the chunk with tiles - std::vector tiles{}; - tiles.reserve(size[0] * size[1]); - for (size_t i = 0; i < size[0] * size[1]; ++i) { - tiles.push_back({terrain_obj, test_texture_path, terrain_elevation_t::zero()}); + for (size_t i = 0; i < size[0] * size[1]; ++i) { + tiles.push_back({terrain_obj, terrain_info_path, terrain_elevation_t::zero()}); + } + + test_chunk_index += 1; } + // ========== auto chunk = std::make_shared(size, offset, std::move(tiles)); @@ -131,12 +230,9 @@ std::shared_ptr TerrainFactory::add_chunk(const std::shared_ptrrender_factory->add_terrain_render_entity(size, offset); chunk->set_render_entity(render_entity); - chunk->render_update(time::TIME_ZERO, - test_texture_path); + chunk->render_update(time::TIME_ZERO); } - chunk->set_terrain_path(test_texture_path); - return chunk; } diff --git a/libopenage/presenter/presenter.cpp b/libopenage/presenter/presenter.cpp index 64df9bc05a..62435074d4 100644 --- a/libopenage/presenter/presenter.cpp +++ b/libopenage/presenter/presenter.cpp @@ -21,6 +21,8 @@ #include "renderer/gui/gui.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/render_factory.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/assets/asset_manager.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" @@ -34,6 +36,7 @@ #include "time/time_loop.h" #include "util/path.h" + namespace openage::presenter { Presenter::Presenter(const util::Path &root_dir, diff --git a/libopenage/renderer/CMakeLists.txt b/libopenage/renderer/CMakeLists.txt index 756e9e4d98..dfcb5e8b9e 100644 --- a/libopenage/renderer/CMakeLists.txt +++ b/libopenage/renderer/CMakeLists.txt @@ -3,6 +3,9 @@ add_sources(libopenage definitions.cpp geometry.cpp render_factory.cpp + render_pass.cpp + render_target.cpp + renderable.cpp renderer.cpp shader_program.cpp texture.cpp diff --git a/libopenage/renderer/definitions.h b/libopenage/renderer/definitions.h index 594f817d1a..49e07c30c1 100644 --- a/libopenage/renderer/definitions.h +++ b/libopenage/renderer/definitions.h @@ -1,9 +1,12 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once +#include + #include "coord/scene.h" + /** * Hardcoded definitions for parameters used in the renderer. * @@ -16,4 +19,9 @@ namespace openage::renderer { */ constexpr coord::scene3 SCENE_ORIGIN = coord::scene3{0, 0, 0}; +/** + * Maximum priority value for a render pass layer. + */ +static constexpr int64_t LAYER_PRIORITY_MAX = std::numeric_limits::max(); + } // namespace openage::renderer diff --git a/libopenage/renderer/demo/demo_0.cpp b/libopenage/renderer/demo/demo_0.cpp index 617b7c6f75..d82eb62381 100644 --- a/libopenage/renderer/demo/demo_0.cpp +++ b/libopenage/renderer/demo/demo_0.cpp @@ -4,10 +4,13 @@ #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" #include "renderer/shader_program.h" + namespace openage::renderer::tests { void renderer_demo_0(const util::Path &path) { diff --git a/libopenage/renderer/demo/demo_1.cpp b/libopenage/renderer/demo/demo_1.cpp index 312efb47c4..1ecd7815a1 100644 --- a/libopenage/renderer/demo/demo_1.cpp +++ b/libopenage/renderer/demo/demo_1.cpp @@ -8,12 +8,15 @@ #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_data.h" #include "renderer/shader_program.h" #include "renderer/texture.h" #include "util/math_constants.h" + namespace openage::renderer::tests { void renderer_demo_1(const util::Path &path) { diff --git a/libopenage/renderer/demo/demo_2.cpp b/libopenage/renderer/demo/demo_2.cpp index ac30adc78a..7f6f398e12 100644 --- a/libopenage/renderer/demo/demo_2.cpp +++ b/libopenage/renderer/demo/demo_2.cpp @@ -8,6 +8,8 @@ #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/animation/angle_info.h" #include "renderer/resources/animation/frame_info.h" #include "renderer/resources/parser/parse_sprite.h" @@ -17,6 +19,7 @@ #include "renderer/shader_program.h" #include "renderer/texture.h" + namespace openage::renderer::tests { void renderer_demo_2(const util::Path &path) { diff --git a/libopenage/renderer/demo/demo_3.cpp b/libopenage/renderer/demo/demo_3.cpp index c143bd0a13..23de242fd7 100644 --- a/libopenage/renderer/demo/demo_3.cpp +++ b/libopenage/renderer/demo/demo_3.cpp @@ -10,6 +10,8 @@ #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_factory.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/assets/asset_manager.h" #include "renderer/resources/shader_source.h" #include "renderer/stages/camera/manager.h" diff --git a/libopenage/renderer/demo/demo_4.cpp b/libopenage/renderer/demo/demo_4.cpp index 7bac7a1654..fb39057e5d 100644 --- a/libopenage/renderer/demo/demo_4.cpp +++ b/libopenage/renderer/demo/demo_4.cpp @@ -7,6 +7,8 @@ #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/animation/angle_info.h" #include "renderer/resources/animation/frame_info.h" #include "renderer/resources/frame_timing.h" @@ -16,6 +18,7 @@ #include "renderer/shader_program.h" #include "time/clock.h" + namespace openage::renderer::tests { void renderer_demo_4(const util::Path &path) { auto qtapp = std::make_shared(); diff --git a/libopenage/renderer/demo/demo_5.cpp b/libopenage/renderer/demo/demo_5.cpp index 73c2c15226..85752db18c 100644 --- a/libopenage/renderer/demo/demo_5.cpp +++ b/libopenage/renderer/demo/demo_5.cpp @@ -9,6 +9,8 @@ #include "renderer/camera/camera.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/buffer_info.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_data.h" diff --git a/libopenage/renderer/demo/stresstest_0.cpp b/libopenage/renderer/demo/stresstest_0.cpp index 01169b0849..76c53d7b21 100644 --- a/libopenage/renderer/demo/stresstest_0.cpp +++ b/libopenage/renderer/demo/stresstest_0.cpp @@ -9,6 +9,8 @@ #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_factory.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/assets/asset_manager.h" #include "renderer/resources/shader_source.h" #include "renderer/stages/camera/manager.h" diff --git a/libopenage/renderer/gui/gui.cpp b/libopenage/renderer/gui/gui.cpp index f51320c24c..879a709d0c 100644 --- a/libopenage/renderer/gui/gui.cpp +++ b/libopenage/renderer/gui/gui.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "gui.h" @@ -7,6 +7,8 @@ #include "renderer/gui/guisys/public/gui_renderer.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" @@ -14,6 +16,7 @@ #include "renderer/window.h" #include "util/path.h" + namespace openage::renderer::gui { GUI::GUI(std::shared_ptr app, @@ -31,10 +34,10 @@ GUI::GUI(std::shared_ptr app, engine, source.resolve_native_path(), rootdir.resolve_native_path()}, - //input{&gui_renderer, &game_logic_updater} - //image_provider_by_filename{ - // &render_updater, - // openage::gui::GuiGameSpecImageProvider::Type::ByFilename}, + // input{&gui_renderer, &game_logic_updater} + // image_provider_by_filename{ + // &render_updater, + // openage::gui::GuiGameSpecImageProvider::Type::ByFilename}, renderer{renderer} { // everything alright before we create the gui stuff? renderer::opengl::GlContext::check_error(); diff --git a/libopenage/renderer/opengl/render_pass.cpp b/libopenage/renderer/opengl/render_pass.cpp index 526663d324..6d2ab052c1 100644 --- a/libopenage/renderer/opengl/render_pass.cpp +++ b/libopenage/renderer/opengl/render_pass.cpp @@ -7,27 +7,32 @@ namespace openage::renderer::opengl { -GlRenderPass::GlRenderPass(std::vector renderables, +GlRenderPass::GlRenderPass(std::vector &&renderables, const std::shared_ptr &target) : - RenderPass(renderables, target), - is_optimised(false) { - log::log(MSG(dbg) << "Created OpenGL render pass"); + RenderPass(std::move(renderables), target), + is_optimized(false) { } -const std::vector &GlRenderPass::get_renderables() const { - return this->renderables; +void GlRenderPass::set_renderables(std::vector &&renderables) { + RenderPass::set_renderables(std::move(renderables)); + this->is_optimized = false; } -void GlRenderPass::set_renderables(std::vector renderables) { - this->renderables = renderables; - this->is_optimised = false; +void GlRenderPass::add_renderables(std::vector &&renderables, int64_t priority) { + RenderPass::add_renderables(std::move(renderables), priority); + this->is_optimized = false; } -bool GlRenderPass::get_is_optimised() const { - return this->is_optimised; +void GlRenderPass::add_renderables(Renderable &&renderable, int64_t priority) { + RenderPass::add_renderables(std::move(renderable), priority); + this->is_optimized = false; } -void GlRenderPass::set_is_optimised(bool flag) { - this->is_optimised = flag; +bool GlRenderPass::get_is_optimized() const { + return this->is_optimized; +} + +void GlRenderPass::set_is_optimized(bool flag) { + this->is_optimized = flag; } } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/render_pass.h b/libopenage/renderer/opengl/render_pass.h index 2994a0e7f7..87ea8448c9 100644 --- a/libopenage/renderer/opengl/render_pass.h +++ b/libopenage/renderer/opengl/render_pass.h @@ -1,24 +1,28 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #pragma once -#include "../renderer.h" +#include "renderer/render_pass.h" +#include "renderer/renderable.h" + namespace openage::renderer::opengl { class GlRenderPass final : public RenderPass { public: - GlRenderPass(std::vector, - const std::shared_ptr &); + GlRenderPass(std::vector &&renderables, + const std::shared_ptr &target); + + void set_renderables(std::vector &&renderables); + void add_renderables(std::vector &&renderables, int64_t priority = LAYER_PRIORITY_MAX); + void add_renderables(Renderable &&renderable, int64_t priority = LAYER_PRIORITY_MAX); - void set_renderables(std::vector); - const std::vector &get_renderables() const; - void set_is_optimised(bool); - bool get_is_optimised() const; + void set_is_optimized(bool); + bool get_is_optimized() const; private: /// Whether the renderables order is optimised - bool is_optimised; + bool is_optimized; }; } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/render_target.cpp b/libopenage/renderer/opengl/render_target.cpp index 4a54f4c0d8..b965dc58ea 100644 --- a/libopenage/renderer/opengl/render_target.cpp +++ b/libopenage/renderer/opengl/render_target.cpp @@ -41,6 +41,10 @@ resources::Texture2dData GlRenderTarget::into_data() { return resources::Texture2dData{info, std::move(pxdata)}; } +gl_render_target_t GlRenderTarget::get_type() const { + return this->type; +} + std::vector> GlRenderTarget::get_texture_targets() { std::vector> textures{}; if (this->framebuffer->get_type() == gl_framebuffer_t::display) { diff --git a/libopenage/renderer/opengl/render_target.h b/libopenage/renderer/opengl/render_target.h index ccf67bfd87..5c6061e070 100644 --- a/libopenage/renderer/opengl/render_target.h +++ b/libopenage/renderer/opengl/render_target.h @@ -5,6 +5,7 @@ #include #include "renderer/opengl/framebuffer.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" @@ -63,6 +64,13 @@ class GlRenderTarget final : public RenderTarget { */ resources::Texture2dData into_data() override; + /** + * Get the type of this render target. + * + * @return Render target type. + */ + gl_render_target_t get_type() const; + /** * Get the targeted textures. * diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index d26ba7b96c..48639dc368 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -33,9 +33,9 @@ GlRenderer::GlRenderer(const std::shared_ptr &ctx, // global GL alpha blending settings // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFuncSeparate( - GL_SRC_ALPHA, // source (overlaying) RGB factor + GL_SRC_ALPHA, // source (incoming) RGB factor GL_ONE_MINUS_SRC_ALPHA, // destination (underlying) RGB factor - GL_ONE, // source (overlaying) alpha factor + GL_ONE, // source (incoming) alpha factor GL_ONE_MINUS_SRC_ALPHA // destination (underlying) alpha factor ); @@ -145,10 +145,9 @@ void GlRenderer::resize_display_target(size_t width, size_t height) { this->display->resize(width, height); } -void GlRenderer::optimise(const std::shared_ptr &pass) { - if (!pass->get_is_optimised()) { - auto renderables = pass->get_renderables(); - std::stable_sort(renderables.begin(), renderables.end(), [](const Renderable &a, const Renderable &b) { +void GlRenderer::optimize(const std::shared_ptr &pass) { + if (!pass->get_is_optimized()) { + pass->sort([](const Renderable &a, const Renderable &b) { GLuint shader_a = std::dynamic_pointer_cast( std::dynamic_pointer_cast(a.uniform)->get_program()) ->get_handle(); @@ -158,8 +157,7 @@ void GlRenderer::optimise(const std::shared_ptr &pass) { return shader_a < shader_b; }); - pass->set_renderables(renderables); - pass->set_is_optimised(true); + pass->set_is_optimized(true); } } @@ -178,34 +176,49 @@ void GlRenderer::render(const std::shared_ptr &pass) { // glEnable(GL_CULL_FACE); auto gl_pass = std::dynamic_pointer_cast(pass); - GlRenderer::optimise(gl_pass); + // TODO: Optimization is disabled for now. Figure out how to do this without calling it every frame + // GlRenderer::optimize(gl_pass); - for (auto const &obj : gl_pass->get_renderables()) { - if (obj.alpha_blending) { - glEnable(GL_BLEND); - } - else { - glDisable(GL_BLEND); - } - - if (obj.depth_test) { - glEnable(GL_DEPTH_TEST); - } - else { - glDisable(GL_DEPTH_TEST); - } + // render all objects in the pass + const auto &layers = gl_pass->get_layers(); + const auto &renderables = gl_pass->get_renderables(); - auto in = std::dynamic_pointer_cast(obj.uniform); - auto program = std::static_pointer_cast(in->get_program()); + // Draw by layers + for (size_t i = 0; i < layers.size(); i++) { + const auto &layer = layers[i]; + const auto &objects = renderables[i]; - // this also calls program->use() - program->update_uniforms(in); + if (layer.clear_depth) { + glClear(GL_DEPTH_BUFFER_BIT); + } - // draw the geometry - if (obj.geometry != nullptr) { - auto geom = std::dynamic_pointer_cast(obj.geometry); - // TODO read obj.blend + family - geom->draw(); + for (auto const &obj : objects) { + if (obj.alpha_blending) { + glEnable(GL_BLEND); + } + else { + glDisable(GL_BLEND); + } + + if (obj.depth_test) { + glEnable(GL_DEPTH_TEST); + } + else { + glDisable(GL_DEPTH_TEST); + } + + auto in = std::dynamic_pointer_cast(obj.uniform); + auto program = std::static_pointer_cast(in->get_program()); + + // this also calls program->use() + program->update_uniforms(in); + + // draw the geometry + if (obj.geometry != nullptr) { + auto geom = std::dynamic_pointer_cast(obj.geometry); + // TODO read obj.blend + family + geom->draw(); + } } } } diff --git a/libopenage/renderer/opengl/renderer.h b/libopenage/renderer/opengl/renderer.h index 4d9038808a..458af24358 100644 --- a/libopenage/renderer/opengl/renderer.h +++ b/libopenage/renderer/opengl/renderer.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -60,7 +60,7 @@ class GlRenderer final : public Renderer { private: /// Optimize the render pass by reordering stuff - static void optimise(const std::shared_ptr &); + static void optimize(const std::shared_ptr &pass); /// The GL context. std::shared_ptr gl_context; diff --git a/libopenage/renderer/opengl/shader_program.cpp b/libopenage/renderer/opengl/shader_program.cpp index 31b0d17778..cf59be1edb 100644 --- a/libopenage/renderer/opengl/shader_program.cpp +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -447,7 +447,6 @@ void GlShaderProgram::bind_uniform_buffer(const char *block_name, std::shared_pt // Check if the uniform buffer matches the block definition for (auto const &pair : block.uniforms) { - auto const &unif = pair.second; ENSURE(gl_buffer->has_uniform(pair.first.c_str()), "Uniform buffer does not contain uniform '" << pair.first << "' required by block " << block_name); } diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp new file mode 100644 index 0000000000..c925fc0a89 --- /dev/null +++ b/libopenage/renderer/render_pass.cpp @@ -0,0 +1,118 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "render_pass.h" + +#include "log/log.h" + + +namespace openage::renderer { + +RenderPass::RenderPass(std::vector &&renderables, + const std::shared_ptr &target) : + renderables{}, + target{target}, + layers{} { + // Add a default layer with the lowest priority + this->add_layer(0, LAYER_PRIORITY_MAX); + + // Add the renderables to the pass + this->add_renderables(std::move(renderables)); + + log::log(MSG(dbg) << "Created render pass"); +} + +const std::vector> &RenderPass::get_renderables() const { + return this->renderables; +} + +const std::vector &RenderPass::get_layers() const { + return this->layers; +} + +const std::shared_ptr &RenderPass::get_target() const { + return this->target; +} + +void RenderPass::set_target(const std::shared_ptr &target) { + this->target = target; +} + +void RenderPass::set_renderables(std::vector &&renderables) { + this->clear_renderables(); + this->add_renderables(std::move(renderables)); +} + +void RenderPass::add_renderables(std::vector &&renderables, int64_t priority) { + if (priority == LAYER_PRIORITY_MAX) { + // Add the renderables to the last (default) layer + this->renderables.back().insert(this->renderables.back().end(), + std::make_move_iterator(renderables.begin()), + std::make_move_iterator(renderables.end())); + return; + } + + // Index of the layer where the renderables will be inserted + size_t layer_index = 0; + + // Priority of the last observed layer + int64_t current_priority = LAYER_PRIORITY_MAX; + + // Find the index in renderables to insert the renderables + for (size_t i = 0; i < this->layers.size(); i++) { + auto &layer = this->layers.at(i); + if (layer.priority > priority) { + // Priority of the next layer is lower than the desired priority + // Insert the renderables directly before this layer + break; + } + current_priority = layer.priority; + layer_index = i; + } + + if (current_priority != priority) { + // Lazily add a new layer with the desired priority + this->add_layer(layer_index, priority); + } + + this->renderables[layer_index].insert(this->renderables[layer_index].end(), + std::make_move_iterator(renderables.begin()), + std::make_move_iterator(renderables.end())); +} + +void RenderPass::add_renderables(Renderable &&renderable, int64_t priority) { + this->add_renderables(std::vector{std::move(renderable)}, priority); +} + +void RenderPass::add_layer(int64_t priority, bool clear_depth) { + size_t layer_index = 0; + for (const auto &layer : this->layers) { + if (layer.priority > priority) { + break; + } + layer_index++; + } + + this->add_layer(layer_index, priority, clear_depth); +} + +void RenderPass::add_layer(size_t index, int64_t priority, bool clear_depth) { + this->layers.insert(this->layers.begin() + index, Layer{priority, clear_depth}); + this->renderables.insert(this->renderables.begin() + index, std::vector{}); +} + +void RenderPass::clear_renderables() { + // Keep layer definitions, but reset the length of each layer to 0 + for (size_t i = 0; i < this->layers.size(); i++) { + this->renderables[i].clear(); + } +} + +void RenderPass::sort(const compare_func &compare) { + for (size_t i = 0; i < this->layers.size(); i++) { + std::stable_sort(this->renderables[i].begin(), + this->renderables[i].end(), + compare); + } +} + +} // namespace openage::renderer diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h new file mode 100644 index 0000000000..43c99bd659 --- /dev/null +++ b/libopenage/renderer/render_pass.h @@ -0,0 +1,161 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "renderer/definitions.h" +#include "renderer/renderable.h" + + +namespace openage { +namespace renderer { +class RenderTarget; + +/** + * Defines a layer in the render pass. A layer is a slice of the renderables + * that have the same priority. Each layer can have its own settings. + * + * // TODO: We could also move these settings to the render pass itself and use + * // multiple render passes to achieve the same effect. Then we might + * // not need layers at all. + */ +struct Layer { + /// Priority of the renderables. + int64_t priority; + /// Whether to clear the depth buffer before rendering this layer. + bool clear_depth = true; +}; + +/** + * A render pass is a series of draw calls represented by renderables that output + * into the given render target. + */ +class RenderPass { +public: + virtual ~RenderPass() = default; + + /** + * Get the renderables of the render pass. + * + * @return Renderables of the render pass. + */ + const std::vector> &get_renderables() const; + + /** + * Get the layers of the render pass. + * + * @return Layers of the render pass. + */ + const std::vector &get_layers() const; + + /** + * Set the render target to write to. + * + * @param target Render target. + */ + void set_target(const std::shared_ptr &target); + + /** + * Get the render target of the render pass. + * + * @return Render target. + */ + const std::shared_ptr &get_target() const; + + /** + * Replace the current renderables with the given list of renderables. + * + * @param renderables New renderables. + */ + void set_renderables(std::vector &&renderables); + + /** + * Append renderables to the render pass with a given priority. + * + * @param renderables New renderables. + * @param priority Priority of the renderables. Layers with higher priority are drawn later. + */ + void add_renderables(std::vector &&renderables, + int64_t priority = LAYER_PRIORITY_MAX); + + /** + * Append a single renderable to the render pass with a given priority. + * + * @param renderable New renderable. + * @param priority Priority of the renderable. Layers with higher priority are drawn later. + */ + void add_renderables(Renderable &&renderable, + int64_t priority = LAYER_PRIORITY_MAX); + + /** + * Add a new layer to the render pass. + * + * @param priority Priority of the layer. Layers with higher priority are drawn first. + * @param clear_depth If true clears the depth buffer before rendering this layer. + */ + void add_layer(int64_t priority, bool clear_depth = true); + + /** + * Clear the list of renderables + */ + void clear_renderables(); + + using compare_func = std::function; + + /** + * Sort the renderables using the given comparison function. + * + * Layers are sorted individually, so the order of layers is not changed. + * + * @param compare Comparison function. + */ + void sort(const compare_func &compare); + +protected: + /** + * Create a new RenderPass. This is called from Renderer::add_render_pass, + * which then creates the proper subclass of RenderPass, depending on the backend. + * + * @param renderables The renderables to draw. + * @param target Render target to write to. + */ + RenderPass(std::vector &&renderables, const std::shared_ptr &target); + + /** + * The renderables to draw. + * + * Kept sorted by layer priorities (lowest to highest priority). + */ + std::vector> renderables; + +private: + /** + * Add a new layer to the render pass at the given index. + * + * @param index Index in \p layers member to insert the new layer. + * @param priority Priority of the layer. Layers with higher priority are drawn first. + * @param clear_depth If true clears the depth buffer before rendering this layer. + */ + void add_layer(size_t index, int64_t priority, bool clear_depth = true); + + /** + * Render target to write to. + */ + std::shared_ptr target; + + /** + * Stores the layers of the render pass. + * + * Layers are slices of the renderables that have the same priority. + * They can assign different settings to the renderables in the slice. + * + * Sorted from lowest to highest priority. + */ + std::vector layers; +}; + +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/render_target.cpp b/libopenage/renderer/render_target.cpp new file mode 100644 index 0000000000..158687cc42 --- /dev/null +++ b/libopenage/renderer/render_target.cpp @@ -0,0 +1,8 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "render_target.h" + + +namespace openage::renderer { + +} // namespace openage::renderer diff --git a/libopenage/renderer/render_target.h b/libopenage/renderer/render_target.h new file mode 100644 index 0000000000..4db95701f2 --- /dev/null +++ b/libopenage/renderer/render_target.h @@ -0,0 +1,38 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + + +namespace openage { +namespace renderer { +class Texture2d; + +namespace resources { +class Texture2dData; +} // namespace resources + +/// The abstract base for a render target. +class RenderTarget : public std::enable_shared_from_this { +protected: + RenderTarget() = default; + +public: + virtual ~RenderTarget() = default; + + /** + * Get an image from the pixels in the render target's framebuffer. + * + * This should only be called _after_ rendering to the framebuffer has finished. + * + * @return RGBA texture data. + */ + virtual resources::Texture2dData into_data() = 0; + + virtual std::vector> get_texture_targets() = 0; +}; + +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/renderable.cpp b/libopenage/renderer/renderable.cpp new file mode 100644 index 0000000000..213abdf36e --- /dev/null +++ b/libopenage/renderer/renderable.cpp @@ -0,0 +1,8 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "renderable.h" + + +namespace openage::renderer { + +} // namespace openage::renderer diff --git a/libopenage/renderer/renderable.h b/libopenage/renderer/renderable.h new file mode 100644 index 0000000000..a122db9679 --- /dev/null +++ b/libopenage/renderer/renderable.h @@ -0,0 +1,62 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + + +namespace openage { +namespace renderer { +class Geometry; +class UniformInput; + +/** + * A renderable is a set of a shader UniformInput and a possible draw call. + * Usually it is one "step" in a RenderPass. + * + * The UniformInput only stores the values the CPU at first. When the renderer + * "executes" the Renderable in a pass, the UniformInput values are uploaded to + * the shader on the GPU they were created with. + * + * If the geometry is nullptr, the uniform values are uploaded to the shader, + * but no draw call is performed. This can be used to, for example, first set + * the values of uniforms that many objects have in common, and then only + * upload the uniforms that vary between them in each draw call. This works + * because uniform values in any given shader are preserved across a render + * pass. + * + * If geometry is set (i.e. it is not nullptr), the renderer draws the geometry + * with the shader and other settings in the renderable. The result is written + * into the render target, defined in the RenderPass. + */ +struct Renderable { + /// Uniform values to be set in the appropriate shader. Contains a reference + /// to the correct shader, and this is the shader that will be used for + /// drawing if geometry is present. + std::shared_ptr uniform; + /// The geometry. It can be a simple primitive or a complex mesh. + /// Can be nullptr to only set uniforms but do not perform draw call. + std::shared_ptr geometry; + /// Whether to perform alpha-based color blending with whatever was in the + /// render target before. + bool alpha_blending = true; + /// Whether to perform depth testing and discard occluded fragments. + bool depth_test = true; +}; + +/** + * Simplified form of Renderable, which is just an update for a shader. + * When the ShaderUpdate is processed in a RenderPass, + * the new uniform values (set on the CPU first with unif_in.update(...)) + * are uploaded to the GPU. + */ +struct ShaderUpdate : Renderable { + ShaderUpdate(std::shared_ptr const &uniform) : + Renderable{uniform, nullptr} {} + + ShaderUpdate(std::shared_ptr &&uniform) : + Renderable{std::move(uniform), nullptr} {} +}; + +} // namespace renderer +} // namespace openage diff --git a/libopenage/renderer/renderer.cpp b/libopenage/renderer/renderer.cpp index 63f61da76e..e59d1ff421 100644 --- a/libopenage/renderer/renderer.cpp +++ b/libopenage/renderer/renderer.cpp @@ -1,39 +1,7 @@ -// Copyright 2019-2023 the openage authors. See copying.md for legal info. +// Copyright 2019-2024 the openage authors. See copying.md for legal info. #include "renderer.h" namespace openage::renderer { -RenderPass::RenderPass(std::vector renderables, - const std::shared_ptr &target) : - renderables(std::move(renderables)), - target{target} {} - - -const std::shared_ptr &RenderPass::get_target() const { - return this->target; -} - - -void RenderPass::set_target(const std::shared_ptr &target) { - this->target = target; -} - -void RenderPass::set_renderables(std::vector renderables) { - this->renderables = std::move(renderables); -} -void RenderPass::add_renderables(std::vector renderables) { - for (auto item : renderables) { - this->renderables.push_back(item); - } -} - -void RenderPass::add_renderables(Renderable renderable) { - this->renderables.push_back(std::move(renderable)); -} - -void RenderPass::clear_renderables() { - this->renderables.clear(); -} - } // namespace openage::renderer diff --git a/libopenage/renderer/renderer.h b/libopenage/renderer/renderer.h index 8f6e4ed3b9..907d9079df 100644 --- a/libopenage/renderer/renderer.h +++ b/libopenage/renderer/renderer.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #pragma once @@ -7,6 +7,8 @@ #include #include +#include "renderer/renderable.h" + namespace openage { namespace renderer { @@ -21,106 +23,13 @@ class UniformBufferInfo; class ShaderProgram; class Geometry; +class RenderPass; +class RenderTarget; class Texture2d; class UniformBuffer; class UniformInput; -/// The abstract base for a render target. -class RenderTarget : public std::enable_shared_from_this { -protected: - RenderTarget() = default; - -public: - virtual ~RenderTarget() = default; - - /** - * Get an image from the pixels in the render target's framebuffer. - * - * This should only be called _after_ rendering to the framebuffer has finished. - * - * @return RGBA texture data. - */ - virtual resources::Texture2dData into_data() = 0; - - virtual std::vector> get_texture_targets() = 0; -}; - -/// A renderable is a set of a shader UniformInput and a possible draw call. -/// Usually it is one "step" in a RenderPass. -/// -/// The UniformInput only stores the values the CPU at first. When the renderer -/// "executes" the Renderable in a pass, the UniformInput values are uploaded to -/// the shader on the GPU they were created with. -/// -/// If the geometry is nullptr, the uniform values are uploaded to the shader, -/// but no draw call is performed. This can be used to, for example, first set -/// the values of uniforms that many objects have in common, and then only -/// upload the uniforms that vary between them in each draw call. This works -/// because uniform values in any given shader are preserved across a render -/// pass. -/// -/// If geometry is set (i.e. it is not nullptr), the renderer draws the geometry -/// with the shader and other settings in the renderable. The result is written -/// into the render target, defined in the RenderPass. -struct Renderable { - /// Uniform values to be set in the appropriate shader. Contains a reference - /// to the correct shader, and this is the shader that will be used for - /// drawing if geometry is present. - std::shared_ptr uniform; - /// The geometry. It can be a simple primitive or a complex mesh. - /// Can be nullptr to only set uniforms but do not perform draw call. - std::shared_ptr geometry; - /// Whether to perform alpha-based color blending with whatever was in the - /// render target before. - bool alpha_blending = true; - /// Whether to perform depth testing and discard occluded fragments. - bool depth_test = true; -}; - - -/// Simplified form of Renderable, which is just an update for a shader. -/// When the ShaderUpdate is processed in a RenderPass, -/// the new uniform values (set on the CPU first with unif_in.update(...)) -/// are uploaded to the GPU. -struct ShaderUpdate : Renderable { - ShaderUpdate(std::shared_ptr const &uniform) : - Renderable{uniform, nullptr} {} - - ShaderUpdate(std::shared_ptr &&uniform) : - Renderable{std::move(uniform), nullptr} {} -}; - - -/// A render pass is a series of draw calls represented by renderables that output into the given render target. -class RenderPass { -protected: - /// Create a new RenderPass. This is called from Renderer::add_render_pass, - /// which then creates the proper subclass of RenderPass, depending on the backend. - RenderPass(std::vector, const std::shared_ptr &); - /// The renderables to parse and possibly execute. - std::vector renderables; - -public: - virtual ~RenderPass() = default; - void set_target(const std::shared_ptr &); - const std::shared_ptr &get_target() const; - - // Replace the current renderables - void set_renderables(std::vector); - // Append renderables to the end of the list of renderables - void add_renderables(std::vector); - // Append a single renderable to the end of the list of renderables - void add_renderables(Renderable); - // Clear the list of renderables - void clear_renderables(); - -private: - /// The render target to write into. - std::shared_ptr target; -}; - - /// The renderer. This class is used for performing all graphics operations. It is abstract and has implementations /// for various low-level graphics APIs like OpenGL. class Renderer { diff --git a/libopenage/renderer/resources/parser/parse_sprite.cpp b/libopenage/renderer/resources/parser/parse_sprite.cpp index 2156cc8422..9ea4afac1c 100644 --- a/libopenage/renderer/resources/parser/parse_sprite.cpp +++ b/libopenage/renderer/resources/parser/parse_sprite.cpp @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #include "parse_sprite.h" @@ -188,7 +188,7 @@ Animation2dInfo parse_sprite_file(const util::Path &file, frames.at(frame.angle).push_back(frame); // check for the largest index, so we can use it to - // interpolate the total animation length + // interpolate the total animation length if (frame.index > largest_frame_idx) { largest_frame_idx = frame.index; } @@ -228,6 +228,13 @@ Animation2dInfo parse_sprite_file(const util::Path &file, return a1.degree < a2.degree; }); + // Order layers by position + std::sort(layers.begin(), + layers.end(), + [](LayerData &l1, LayerData &l2) { + return l1.position < l2.position; + }); + // Create ID map. Resolves IDs used in the file to array indices std::unordered_map texture_id_map; for (size_t i = 0; i < textures.size(); ++i) { diff --git a/libopenage/renderer/resources/parser/parse_terrain.cpp b/libopenage/renderer/resources/parser/parse_terrain.cpp index cc7b2f4814..4330606091 100644 --- a/libopenage/renderer/resources/parser/parse_terrain.cpp +++ b/libopenage/renderer/resources/parser/parse_terrain.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "parse_terrain.h" @@ -193,7 +193,7 @@ TerrainInfo parse_terrain_file(const util::Path &file, frames.at(frame.layer_id).push_back(frame); // check for the largest index, so we can use it to - // interpolate the total animation length + // interpolate the total animation length if (frame.index > largest_frame_idx) { largest_frame_idx = frame.index; } @@ -226,6 +226,13 @@ TerrainInfo parse_terrain_file(const util::Path &file, }); } + // Order layers by position + std::sort(layers.begin(), + layers.end(), + [](TerrainLayerData &l1, TerrainLayerData &l2) { + return l1.position < l2.position; + }); + // Create ID map. Resolves IDs used in the file to array indices std::unordered_map texture_id_map; for (size_t i = 0; i < textures.size(); ++i) { diff --git a/libopenage/renderer/stages/hud/render_stage.cpp b/libopenage/renderer/stages/hud/render_stage.cpp index b7ecaef31e..a55751ec5f 100644 --- a/libopenage/renderer/stages/hud/render_stage.cpp +++ b/libopenage/renderer/stages/hud/render_stage.cpp @@ -1,9 +1,11 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "render_stage.h" #include "renderer/camera/camera.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/assets/asset_manager.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" @@ -78,7 +80,7 @@ void HudRenderStage::update() { true, }; - this->render_pass->add_renderables(display_obj); + this->render_pass->add_renderables(std::move(display_obj)); this->drag_object->clear_requires_renderable(); this->drag_object->set_uniforms(transform_unifs); diff --git a/libopenage/renderer/stages/screen/render_stage.cpp b/libopenage/renderer/stages/screen/render_stage.cpp index 786c547f62..3d07e4ed3e 100644 --- a/libopenage/renderer/stages/screen/render_stage.cpp +++ b/libopenage/renderer/stages/screen/render_stage.cpp @@ -1,8 +1,10 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #include "render_stage.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" @@ -87,7 +89,7 @@ void ScreenRenderStage::update_render_pass() { output_layers.push_back(display_obj); } this->render_pass->clear_renderables(); - this->render_pass->add_renderables(output_layers); + this->render_pass->add_renderables(std::move(output_layers)); } } // namespace openage::renderer::screen diff --git a/libopenage/renderer/stages/screen/screenshot.cpp b/libopenage/renderer/stages/screen/screenshot.cpp index e910fb34ec..583641b6c0 100644 --- a/libopenage/renderer/stages/screen/screenshot.cpp +++ b/libopenage/renderer/stages/screen/screenshot.cpp @@ -1,4 +1,4 @@ -// Copyright 2014-2023 the openage authors. See copying.md for legal info. +// Copyright 2014-2024 the openage authors. See copying.md for legal info. #include "screenshot.h" @@ -13,7 +13,8 @@ #include "job/job_manager.h" #include "log/log.h" -#include "renderer/renderer.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/texture_data.h" #include "renderer/stages/screen/render_stage.h" #include "util/strings.h" diff --git a/libopenage/renderer/stages/skybox/render_stage.cpp b/libopenage/renderer/stages/skybox/render_stage.cpp index c8b2fe76d2..b6fe47a7db 100644 --- a/libopenage/renderer/stages/skybox/render_stage.cpp +++ b/libopenage/renderer/stages/skybox/render_stage.cpp @@ -1,8 +1,10 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #include "render_stage.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" #include "renderer/resources/mesh_data.h" #include "renderer/resources/shader_source.h" diff --git a/libopenage/renderer/stages/terrain/chunk.cpp b/libopenage/renderer/stages/terrain/chunk.cpp index 2bb836a673..12b207bdc9 100644 --- a/libopenage/renderer/stages/terrain/chunk.cpp +++ b/libopenage/renderer/stages/terrain/chunk.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "chunk.h" @@ -33,10 +33,17 @@ void TerrainChunk::fetch_updates(const time::time_t & /* time */) { } // TODO: Change mesh instead of recreating it // TODO: Multiple meshes - auto new_mesh = this->create_mesh(); - new_mesh->create_model_matrix(this->offset); this->meshes.clear(); - this->meshes.push_back(new_mesh); + for (const auto &terrain_path : this->render_entity->get_terrain_paths()) { + auto new_mesh = this->create_mesh(terrain_path); + new_mesh->create_model_matrix(this->offset); + this->meshes.push_back(new_mesh); + } + + // auto new_mesh = this->create_mesh(); + // new_mesh->create_model_matrix(this->offset); + // this->meshes.clear(); + // this->meshes.push_back(new_mesh); // Indicate to the render entity that its updates have been processed. this->render_entity->clear_changed_flag(); @@ -52,42 +59,68 @@ const std::vector> &TerrainChunk::get_meshes( return this->meshes; } -std::shared_ptr TerrainChunk::create_mesh() { - // Update mesh +std::shared_ptr TerrainChunk::create_mesh(const std::string &texture_path) { auto size = this->render_entity->get_size(); - auto src_verts = this->render_entity->get_vertices(); - - // dst_verts places vertices in order - // (left to right, bottom to top) - std::vector dst_verts{}; - dst_verts.reserve(src_verts.size() * 5); - for (auto v : src_verts) { - // Transform to scene coords - auto v_vec = v.to_world_space(); - dst_verts.push_back(v_vec[0]); - dst_verts.push_back(v_vec[1]); - dst_verts.push_back(v_vec[2]); - // TODO: Texture scaling - dst_verts.push_back((v.ne / 10).to_float()); - dst_verts.push_back((v.se / 10).to_float()); - } - - // split the grid into triangles using an index array - std::vector idxs; - idxs.reserve((size[0] - 1) * (size[1] - 1) * 6); - // iterate over all tiles in the grid by columns, i.e. starting - // from the left corner to the bottom corner if you imagine it from - // the camera's point of view - for (size_t i = 0; i < size[0] - 1; ++i) { - for (size_t j = 0; j < size[1] - 1; ++j) { - // since we are working on tiles, we split each tile into two triangles - // with counter-clockwise vertex order - idxs.push_back(j + i * size[1]); // bottom left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + i * size[1]); // top left - idxs.push_back(j + 1 + i * size[1]); // bottom right - idxs.push_back(j + size[1] + 1 + i * size[1]); // top right - idxs.push_back(j + size[1] + i * size[1]); // top left + auto v_width = size[0]; + auto v_height = size[1]; + + auto tiles = this->render_entity->get_tiles(); + auto heightmap_verts = this->render_entity->get_vertices(); + + // vertex data for the mesh + std::vector mesh_verts{}; + + // vertex indices for the mesh + std::vector idxs{}; + + // maps indices of verts in the heightmap to indices in the vertex data vector + std::unordered_map index_map; + + for (size_t i = 0; i < v_width - 1; ++i) { + for (size_t j = 0; j < v_height - 1; ++j) { + auto tile = tiles.at(j + i * (v_height - 1)); + if (tile.second != texture_path) { + // Skip tiles with different textures + continue; + } + + // indices of the vertices of the current tile + // in the hightmap + std::array tile_verts{ + j + i * v_height, // top left + j + (i + 1) * v_height, // bottom left + j + 1 + (i + 1) * v_height, // bottom right + j + 1 + i * v_height, // top right + }; + + // add the vertices of the current tile to the vertex data vector + for (size_t v_idx : tile_verts) { + // skip if the vertex is already in the vertex data vector + if (not index_map.contains(v_idx)) { + auto v = heightmap_verts[v_idx]; + auto v_vec = v.to_world_space(); + mesh_verts.push_back(v_vec[0]); + mesh_verts.push_back(v_vec[1]); + mesh_verts.push_back(v_vec[2]); + mesh_verts.push_back((v.ne / 10).to_float()); + mesh_verts.push_back((v.se / 10).to_float()); + + // update the index map + // since new verts are added to the end of the vertex data vector + // the mapped index is the current size of the index map + index_map[v_idx] = index_map.size(); + } + } + + // first triangle + idxs.push_back(index_map[tile_verts[0]]); // top left + idxs.push_back(index_map[tile_verts[1]]); // bottom left + idxs.push_back(index_map[tile_verts[2]]); // bottom right + + // second triangle + idxs.push_back(index_map[tile_verts[0]]); // top left + idxs.push_back(index_map[tile_verts[2]]); // bottom right + idxs.push_back(index_map[tile_verts[3]]); // top right } } @@ -97,24 +130,21 @@ std::shared_ptr TerrainChunk::create_mesh() { resources::vertex_primitive_t::TRIANGLES, resources::index_t::U16}; - auto const vert_data_size = dst_verts.size() * sizeof(float); - std::vector vert_data(vert_data_size); - std::memcpy(vert_data.data(), dst_verts.data(), vert_data_size); + auto const vert_data_size_new = mesh_verts.size() * sizeof(float); + std::vector vert_data_new(vert_data_size_new); + std::memcpy(vert_data_new.data(), mesh_verts.data(), vert_data_size_new); auto const idx_data_size = idxs.size() * sizeof(uint16_t); std::vector idx_data(idx_data_size); std::memcpy(idx_data.data(), idxs.data(), idx_data_size); - resources::MeshData meshdata{std::move(vert_data), std::move(idx_data), info}; - - // Update textures - auto tex_manager = this->asset_manager->get_texture_manager(); - - // TODO: Support multiple textures per chunk + resources::MeshData meshdata{std::move(vert_data_new), std::move(idx_data), info}; + // Create the terrain mesh + auto terrain_info = this->asset_manager->request_terrain(texture_path); auto terrain_mesh = std::make_shared( this->asset_manager, - this->render_entity->get_terrain_path(), + terrain_info, std::move(meshdata)); return terrain_mesh; diff --git a/libopenage/renderer/stages/terrain/chunk.h b/libopenage/renderer/stages/terrain/chunk.h index 9dc7f028e0..b64133e5b6 100644 --- a/libopenage/renderer/stages/terrain/chunk.h +++ b/libopenage/renderer/stages/terrain/chunk.h @@ -87,7 +87,7 @@ class TerrainChunk { * * @return New terrain mesh. */ - std::shared_ptr create_mesh(); + std::shared_ptr create_mesh(const std::string &texture_path); /** * Size of the chunk in tiles (width x height). diff --git a/libopenage/renderer/stages/terrain/mesh.cpp b/libopenage/renderer/stages/terrain/mesh.cpp index ca8b19937c..b5af5bee69 100644 --- a/libopenage/renderer/stages/terrain/mesh.cpp +++ b/libopenage/renderer/stages/terrain/mesh.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #include "mesh.h" @@ -20,21 +20,21 @@ TerrainRenderMesh::TerrainRenderMesh(const std::shared_ptr &asset_manager, - const curve::Discrete &terrain_path, + const std::shared_ptr &info, renderer::resources::MeshData &&mesh) : require_renderable{true}, changed{true}, asset_manager{asset_manager}, - terrain_info{nullptr, 0}, + terrain_info{nullptr}, uniforms{nullptr}, mesh{std::move(mesh)} { - this->set_terrain_path(terrain_path); + this->set_terrain_info(info); } void TerrainRenderMesh::set_mesh(renderer::resources::MeshData &&mesh) { @@ -47,19 +47,12 @@ const renderer::resources::MeshData &TerrainRenderMesh::get_mesh() { return this->mesh; } -void TerrainRenderMesh::set_terrain_path(const curve::Discrete &terrain_path) { +void TerrainRenderMesh::set_terrain_info(const std::shared_ptr &info) { this->changed = true; - this->terrain_info.sync(terrain_path, - std::function(const std::string &)>( - [&](const std::string &path) { - if (path.empty()) { - return std::shared_ptr{nullptr}; - } - return this->asset_manager->request_terrain(path); - })); + this->terrain_info = info; } -void TerrainRenderMesh::update_uniforms(const time::time_t &time) { +void TerrainRenderMesh::update_uniforms(const time::time_t & /* time */) { // TODO: Only update uniforms that changed since last update if (this->uniforms == nullptr) [[unlikely]] { return; @@ -72,7 +65,7 @@ void TerrainRenderMesh::update_uniforms(const time::time_t &time) { // local space -> world space this->uniforms->update("model", this->model_matrix); - auto tex_info = this->terrain_info.get(time)->get_texture(0); + auto tex_info = this->terrain_info->get_texture(0); auto tex_manager = this->asset_manager->get_texture_manager(); auto texture = tex_manager->request(tex_info->get_image_path().value()); diff --git a/libopenage/renderer/stages/terrain/mesh.h b/libopenage/renderer/stages/terrain/mesh.h index 118af0ca00..00bab1b1ed 100644 --- a/libopenage/renderer/stages/terrain/mesh.h +++ b/libopenage/renderer/stages/terrain/mesh.h @@ -8,7 +8,6 @@ #include #include "coord/scene.h" -#include "curve/discrete.h" #include "renderer/resources/mesh_data.h" #include "time/time.h" #include "util/vector.h" @@ -46,7 +45,7 @@ class TerrainRenderMesh { * @param mesh Vertex data of the mesh. */ TerrainRenderMesh(const std::shared_ptr &asset_manager, - const curve::Discrete &terrain_path, + const std::shared_ptr &info, renderer::resources::MeshData &&mesh); ~TerrainRenderMesh() = default; @@ -86,7 +85,7 @@ class TerrainRenderMesh { * * @param texture Terrain info. */ - void set_terrain_path(const curve::Discrete &info); + void set_terrain_info(const std::shared_ptr &info); /** * Update the uniforms of the renderable associated with this object. @@ -150,7 +149,7 @@ class TerrainRenderMesh { /** * Terrain information for the renderables. */ - curve::Discrete> terrain_info; + std::shared_ptr terrain_info; /** * Shader uniforms for the renderable in the terrain render pass. diff --git a/libopenage/renderer/stages/terrain/render_entity.cpp b/libopenage/renderer/stages/terrain/render_entity.cpp index fdfd7e486d..7f2e6d825e 100644 --- a/libopenage/renderer/stages/terrain/render_entity.cpp +++ b/libopenage/renderer/stages/terrain/render_entity.cpp @@ -1,4 +1,4 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #include "render_entity.h" @@ -12,8 +12,12 @@ namespace openage::renderer::terrain { TerrainRenderEntity::TerrainRenderEntity() : changed{false}, size{0, 0}, - vertices{}, - terrain_path{nullptr, 0} { + tiles{}, + last_update{0.0}, + terrain_paths{}, + vertices{} +// terrain_path{nullptr, 0}, +{ } void TerrainRenderEntity::update_tile(const util::Vector2s size, @@ -31,13 +35,19 @@ void TerrainRenderEntity::update_tile(const util::Vector2s size, auto left_corner = pos.ne * size[0] + pos.se; // update the 4 vertices of the tile - this->vertices[left_corner].up = elevation.to_float(); - this->vertices[left_corner + 1].up = elevation.to_float(); // bottom corner + this->vertices[left_corner].up = elevation.to_float(); // left corner + this->vertices[left_corner + 1].up = elevation.to_float(); // bottom corner this->vertices[left_corner + (size[0] + 1)].up = elevation.to_float(); // top corner this->vertices[left_corner + (size[0] + 2)].up = elevation.to_float(); // right corner - // set texture path - this->terrain_path.set_last(time, terrain_path); + // update tile + this->tiles[left_corner] = {elevation, terrain_path}; + + // update the last update time + this->last_update = time; + + // update the terrain paths + this->terrain_paths.insert(terrain_path); this->changed = true; } @@ -56,8 +66,8 @@ void TerrainRenderEntity::update(const util::Vector2s size, auto vert_count = this->size[0] * this->size[1]; this->vertices.clear(); this->vertices.reserve(vert_count); - for (int i = 0; i < (int)this->size[0]; ++i) { - for (int j = 0; j < (int)this->size[1]; ++j) { + for (int j = 0; j < (int)this->size[1]; ++j) { + for (int i = 0; i < (int)this->size[0]; ++i) { // for each vertex, compare the surrounding tiles std::vector surround{}; if (j - 1 >= 0 and i - 1 >= 0) { @@ -83,8 +93,17 @@ void TerrainRenderEntity::update(const util::Vector2s size, } } - // set texture path - this->terrain_path.set_last(time, tiles[0].second); + // update tiles + this->tiles = tiles; + + // update the last update time + this->last_update = time; + + // update the terrain paths + this->terrain_paths.clear(); + for (const auto &tile : this->tiles) { + this->terrain_paths.insert(tile.second); + } this->changed = true; } @@ -95,10 +114,16 @@ const std::vector &TerrainRenderEntity::get_vertices() { return this->vertices; } -const curve::Discrete &TerrainRenderEntity::get_terrain_path() { +const TerrainRenderEntity::tiles_t &TerrainRenderEntity::get_tiles() { + std::shared_lock lock{this->mutex}; + + return this->tiles; +} + +const std::unordered_set &TerrainRenderEntity::get_terrain_paths() { std::shared_lock lock{this->mutex}; - return this->terrain_path; + return this->terrain_paths; } const util::Vector2s &TerrainRenderEntity::get_size() { diff --git a/libopenage/renderer/stages/terrain/render_entity.h b/libopenage/renderer/stages/terrain/render_entity.h index 054ee73931..4136c7c81a 100644 --- a/libopenage/renderer/stages/terrain/render_entity.h +++ b/libopenage/renderer/stages/terrain/render_entity.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "coord/scene.h" @@ -63,13 +64,18 @@ class TerrainRenderEntity { const std::vector &get_vertices(); /** - * Get the texture mapping for the terrain. + * Get the tiles of the terrain. * - * TODO: Return the actual mapping. + * @return Terrain tiles. + */ + const tiles_t &get_tiles(); + + /** + * Get the terrain paths used in the terrain. * - * @return Texture mapping of textures to vertex area. + * @return Terrain paths. */ - const curve::Discrete &get_terrain_path(); + const std::unordered_set &get_terrain_paths(); /** * Get the number of vertices on each side of the terrain. @@ -105,16 +111,24 @@ class TerrainRenderEntity { util::Vector2s size; /** - * Terrain vertices (ingame coordinates). + * Terrain tile information (elevation, terrain path). */ - std::vector vertices; + tiles_t tiles; + + /** + * Time of the last update call. + */ + time::time_t last_update; /** - * Path to the terrain definition file. + * Terrain texture paths used in \p tiles . */ - curve::Discrete terrain_path; + std::unordered_set terrain_paths; - // std::unordered_map texture_map; // texture -> vertex indices + /** + * Terrain vertices (ingame coordinates). + */ + std::vector vertices; /** * Mutex for protecting threaded access. diff --git a/libopenage/renderer/stages/terrain/render_stage.cpp b/libopenage/renderer/stages/terrain/render_stage.cpp index 518ebd7c9b..740fe260f6 100644 --- a/libopenage/renderer/stages/terrain/render_stage.cpp +++ b/libopenage/renderer/stages/terrain/render_stage.cpp @@ -1,9 +1,11 @@ -// Copyright 2022-2023 the openage authors. See copying.md for legal info. +// Copyright 2022-2024 the openage authors. See copying.md for legal info. #include "render_stage.h" #include "renderer/camera/camera.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/renderer.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" @@ -75,7 +77,7 @@ void TerrainRenderStage::update() { }; // TODO: Remove old renderable instead of clearing everything - this->render_pass->add_renderables(display_obj); + this->render_pass->add_renderables(std::move(display_obj)); mesh->clear_requires_renderable(); mesh->set_uniforms(transform_unifs); diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp index c58e9faa98..3b9edd6d3d 100644 --- a/libopenage/renderer/stages/world/object.cpp +++ b/libopenage/renderer/stages/world/object.cpp @@ -33,14 +33,13 @@ namespace openage::renderer::world { WorldObject::WorldObject(const std::shared_ptr &asset_manager) : require_renderable{true}, changed{false}, - camera{nullptr}, asset_manager{asset_manager}, render_entity{nullptr}, ref_id{0}, position{nullptr, 0, "", nullptr, SCENE_ORIGIN}, angle{nullptr, 0, "", nullptr, 0}, animation_info{nullptr, 0}, - uniforms{nullptr}, + layer_uniforms{}, last_update{0.0} { } @@ -49,15 +48,19 @@ void WorldObject::set_render_entity(const std::shared_ptr &en this->fetch_updates(); } -void WorldObject::set_camera(const std::shared_ptr &camera) { - this->camera = camera; -} - void WorldObject::fetch_updates(const time::time_t &time) { + // TODO: Calling this once per frame is very expensive + auto layer_count = this->get_required_layer_count(time); + if (this->layer_uniforms.size() != layer_count) { + // The number of layers changed, so we need to update the renderables + this->require_renderable = true; + } + if (not this->render_entity->is_changed()) { - // exit early because there is nothing to do + // exit early because there is nothing to update return; } + // Get data from render entity this->ref_id = this->render_entity->get_id(); this->position.sync(this->render_entity->get_position()); @@ -84,79 +87,85 @@ void WorldObject::fetch_updates(const time::time_t &time) { void WorldObject::update_uniforms(const time::time_t &time) { // TODO: Only update uniforms that changed since last update - if (this->uniforms == nullptr) [[unlikely]] { + if (this->layer_uniforms.empty()) [[unlikely]] { return; } // Object world position auto current_pos = this->position.get(time); - this->uniforms->update(this->obj_world_position, current_pos.to_world_space()); // Direction angle the object is facing towards currently auto angle_degrees = this->angle.get(time).to_float(); - // Frame subtexture + // Animation information auto animation_info = this->animation_info.get(time); - auto &layer = animation_info->get_layer(0); // TODO: Support multiple layers - auto &angle = layer.get_direction_angle(angle_degrees); - - // Flip subtexture horizontally if angle is mirrored - if (angle->is_mirrored()) { - this->uniforms->update(this->flip_x, true); - } - else { - this->uniforms->update(this->flip_x, false); - } - // Current frame index considering current time - size_t frame_idx; - switch (layer.get_display_mode()) { - case renderer::resources::display_mode::ONCE: - case renderer::resources::display_mode::LOOP: { - // ONCE and LOOP are animated based on time - auto &timing = layer.get_frame_timing(); - frame_idx = timing->get_frame(time, this->render_entity->get_update_time()); - } break; - case renderer::resources::display_mode::OFF: - default: - // OFF only shows the first frame - frame_idx = 0; - break; + for (size_t layer_idx = 0; layer_idx < this->layer_uniforms.size(); ++layer_idx) { + auto &layer_unifs = this->layer_uniforms.at(layer_idx); + layer_unifs->update(this->obj_world_position, current_pos.to_world_space()); + + // Frame subtexture + auto &layer = animation_info->get_layer(layer_idx); + auto &angle = layer.get_direction_angle(angle_degrees); + + // Flip subtexture horizontally if angle is mirrored + if (angle->is_mirrored()) { + layer_unifs->update(this->flip_x, true); + } + else { + layer_unifs->update(this->flip_x, false); + } + + // Current frame index considering current time + size_t frame_idx; + switch (layer.get_display_mode()) { + case renderer::resources::display_mode::ONCE: + case renderer::resources::display_mode::LOOP: { + // ONCE and LOOP are animated based on time + auto &timing = layer.get_frame_timing(); + frame_idx = timing->get_frame(time, this->render_entity->get_update_time()); + } break; + case renderer::resources::display_mode::OFF: + default: + // OFF only shows the first frame + frame_idx = 0; + break; + } + + // Index of texture and subtexture where the frame's pixels are located + auto &frame_info = angle->get_frame(frame_idx); + auto tex_idx = frame_info->get_texture_idx(); + auto subtex_idx = frame_info->get_subtexture_idx(); + + auto &tex_info = animation_info->get_texture(tex_idx); + auto &tex_manager = this->asset_manager->get_texture_manager(); + auto &texture = tex_manager->request(tex_info->get_image_path().value()); + layer_unifs->update(this->tex, texture); + + // Subtexture coordinates.inside texture + auto coords = tex_info->get_subtex_info(subtex_idx).get_tile_params(); + layer_unifs->update(this->tile_params, coords); + + // Animation scale factor + // Scales the subtex up or down in the shader + auto scale = animation_info->get_scalefactor(); + layer_unifs->update(this->scale, scale); + + // Subtexture size in pixels + auto subtex_size = tex_info->get_subtex_info(subtex_idx).get_size(); + Eigen::Vector2f subtex_size_vec{ + static_cast(subtex_size[0]), + static_cast(subtex_size[1])}; + layer_unifs->update(this->subtex_size, subtex_size_vec); + + // Anchor point offset (in pixels) + // moves the subtex in the shader so that the anchor point is at the object's position + auto anchor = tex_info->get_subtex_info(subtex_idx).get_anchor_params(); + Eigen::Vector2f anchor_offset{ + static_cast(anchor[0]), + static_cast(anchor[1])}; + layer_unifs->update(this->anchor_offset, anchor_offset); } - - // Index of texture and subtexture where the frame's pixels are located - auto &frame_info = angle->get_frame(frame_idx); - auto tex_idx = frame_info->get_texture_idx(); - auto subtex_idx = frame_info->get_subtexture_idx(); - - auto &tex_info = animation_info->get_texture(tex_idx); - auto &tex_manager = this->asset_manager->get_texture_manager(); - auto &texture = tex_manager->request(tex_info->get_image_path().value()); - this->uniforms->update(this->tex, texture); - - // Subtexture coordinates.inside texture - auto coords = tex_info->get_subtex_info(subtex_idx).get_tile_params(); - this->uniforms->update(this->tile_params, coords); - - // Animation scale factor - // Scales the subtex up or down in the shader - auto scale = animation_info->get_scalefactor(); - this->uniforms->update(this->scale, scale); - - // Subtexture size in pixels - auto subtex_size = tex_info->get_subtex_info(subtex_idx).get_size(); - Eigen::Vector2f subtex_size_vec{ - static_cast(subtex_size[0]), - static_cast(subtex_size[1])}; - this->uniforms->update(this->subtex_size, subtex_size_vec); - - // Anchor point offset (in pixels) - // moves the subtex in the shader so that the anchor point is at the object's position - auto anchor = tex_info->get_subtex_info(subtex_idx).get_anchor_params(); - Eigen::Vector2f anchor_offset{ - static_cast(anchor[0]), - static_cast(anchor[1])}; - this->uniforms->update(this->anchor_offset, anchor_offset); } uint32_t WorldObject::get_id() { @@ -167,7 +176,11 @@ const renderer::resources::MeshData WorldObject::get_mesh() { return resources::MeshData::make_quad(); } -bool WorldObject::requires_renderable() { +const Eigen::Matrix4f WorldObject::get_model_matrix() { + return Eigen::Matrix4f::Identity(); +} + +bool WorldObject::requires_renderable() const { return this->require_renderable; } @@ -175,6 +188,30 @@ void WorldObject::clear_requires_renderable() { this->require_renderable = false; } +size_t WorldObject::get_required_layer_count(const time::time_t &time) const { + auto animation_info = this->animation_info.get(time); + if (not animation_info) { + return 0; + } + + return animation_info->get_layer_count(); +} + +std::vector WorldObject::get_layer_positions(const time::time_t &time) const { + auto animation_info = this->animation_info.get(time); + if (not animation_info) { + return {}; + } + + std::vector positions; + for (size_t i = 0; i < animation_info->get_layer_count(); ++i) { + auto layer = animation_info->get_layer(i); + positions.push_back(layer.get_position()); + } + + return positions; +} + bool WorldObject::is_changed() { return this->changed; } @@ -183,8 +220,8 @@ void WorldObject::clear_changed_flag() { this->changed = false; } -void WorldObject::set_uniforms(const std::shared_ptr &uniforms) { - this->uniforms = uniforms; +void WorldObject::set_uniforms(std::vector> &&uniforms) { + this->layer_uniforms = std::move(uniforms); } } // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/object.h b/libopenage/renderer/stages/world/object.h index a34a0c690c..c5e0aa8197 100644 --- a/libopenage/renderer/stages/world/object.h +++ b/libopenage/renderer/stages/world/object.h @@ -51,13 +51,6 @@ class WorldObject { */ void set_render_entity(const std::shared_ptr &entity); - /** - * Set the current camera of the scene. - * - * @param camera Camera object viewing the scene. - */ - void set_camera(const std::shared_ptr &camera); - /** * Fetch updates from the render entity. * @@ -82,10 +75,19 @@ class WorldObject { /** * Get the quad for creating the geometry. * + * Since the object is a bunch of sprite layers, the mesh is always a quad. + * * @return Mesh for creating a renderer geometry object. */ static const renderer::resources::MeshData get_mesh(); + /** + * Get the model matrix for the uniform input of a layer. + * + * @return Model matrix. + */ + static const Eigen::Matrix4f get_model_matrix(); + /** * Check whether a new renderable needs to be created for this mesh. * @@ -95,13 +97,22 @@ class WorldObject { * * @return true if a new renderable is required, else false. */ - bool requires_renderable(); + bool requires_renderable() const; /** * Indicate to this mesh that a new renderable has been created. */ void clear_requires_renderable(); + /** + * Get the number of layers required by this object. + * + * @return Number of layers. + */ + size_t get_required_layer_count(const time::time_t &time) const; + + std::vector get_layer_positions(const time::time_t &time) const; + /** * Check whether the object was changed by \p update(). * @@ -115,13 +126,12 @@ class WorldObject { void clear_changed_flag(); /** - * Set the reference to the uniform inputs of the renderable - * associated with this object. Relevant uniforms are updated - * when calling \p update(). + * Set the uniform inputs for the layers of this object. + * Layer uniforms are updated on every update call. * - * @param uniforms Uniform inputs of this object's renderable. + * @param uniforms Uniform inputs of this object's layers. */ - void set_uniforms(const std::shared_ptr &uniforms); + void set_uniforms(std::vector> &&uniforms); /** * Shader uniform IDs for setting uniform values. @@ -147,18 +157,14 @@ class WorldObject { */ bool changed; - /** - * Camera for model uniforms. - */ - std::shared_ptr camera; - /** * Asset manager for central accessing and loading asset resources. */ std::shared_ptr asset_manager; /** - * Source for positional and texture data. + * Entity that gets updates from the gamestate, e.g. the position and + * requested animation data. */ std::shared_ptr render_entity; @@ -179,14 +185,15 @@ class WorldObject { curve::Segmented angle; /** - * Animation information for the renderables. + * Animation information for the layers. */ curve::Discrete> animation_info; /** - * Shader uniforms for the renderable in the terrain render pass. + * Shader uniforms for the layers of the object. Each layer corresponds to a + * renderable in the render pass. */ - std::shared_ptr uniforms; + std::vector> layer_uniforms; /** * Time of the last update call. diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index d24bb5f896..05827eb645 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -4,6 +4,8 @@ #include "renderer/camera/camera.h" #include "renderer/opengl/context.h" +#include "renderer/render_pass.h" +#include "renderer/render_target.h" #include "renderer/resources/assets/asset_manager.h" #include "renderer/resources/shader_source.h" #include "renderer/resources/texture_info.h" @@ -50,7 +52,6 @@ void WorldRenderStage::add_render_entity(const std::shared_ptr(this->asset_manager); world_object->set_render_entity(entity); - world_object->set_camera(this->camera); this->render_objects.push_back(world_object); } @@ -61,31 +62,36 @@ void WorldRenderStage::update() { obj->fetch_updates(current_time); if (obj->is_changed()) { if (obj->requires_renderable()) { - Eigen::Matrix4f model_m = Eigen::Matrix4f::Identity(); - - // Set uniforms that don't change or are not changed often - auto transform_unifs = this->display_shader->new_uniform_input( - "model", - model_m, - "flip_x", - false, - "flip_y", - false, - "u_id", - obj->get_id()); - - Renderable display_obj{ - transform_unifs, - this->default_geometry, - true, - true, - }; - - this->render_pass->add_renderables(display_obj); + auto layer_positions = obj->get_layer_positions(current_time); + Eigen::Matrix4f model_m = obj->get_model_matrix(); + + std::vector> transform_unifs; + for (auto layer_pos : layer_positions) { + // Set uniforms that don't change or are not changed often + auto layer_unifs = this->display_shader->new_uniform_input( + "model", + model_m, + "flip_x", + false, + "flip_y", + false, + "u_id", + obj->get_id()); + + Renderable display_obj{ + layer_unifs, + this->default_geometry, + true, + true, + }; + this->render_pass->add_renderables(std::move(display_obj), layer_pos); + transform_unifs.push_back(layer_unifs); + } + obj->clear_requires_renderable(); // update remaining uniforms for the object - obj->set_uniforms(transform_unifs); + obj->set_uniforms(std::move(transform_unifs)); } } obj->update_uniforms(current_time); diff --git a/libopenage/renderer/vulkan/render_target.h b/libopenage/renderer/vulkan/render_target.h index f25c684fd4..32cc4784d7 100644 --- a/libopenage/renderer/vulkan/render_target.h +++ b/libopenage/renderer/vulkan/render_target.h @@ -9,7 +9,7 @@ #include "log/log.h" -#include "renderer/renderer.h" +#include "renderer/render_target.h" #include "renderer/vulkan/graphics_device.h"