From ae6c34fb880b60d6579c21cec55da0096751b5e8 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sun, 28 Apr 2024 15:52:12 +0200 Subject: [PATCH 01/28] renderer: Store terrain tiles in render entity. --- libopenage/renderer/stages/terrain/chunk.cpp | 43 +++++++++++----- libopenage/renderer/stages/terrain/chunk.h | 2 +- .../renderer/stages/terrain/render_entity.cpp | 49 +++++++++++++++---- .../renderer/stages/terrain/render_entity.h | 36 ++++++++++++-- 4 files changed, 105 insertions(+), 25 deletions(-) diff --git a/libopenage/renderer/stages/terrain/chunk.cpp b/libopenage/renderer/stages/terrain/chunk.cpp index 2bb836a673..70322283bb 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,11 +59,25 @@ 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 tiles = this->render_entity->get_tiles(); auto src_verts = this->render_entity->get_vertices(); + // Filter tiles by texture path + std::vector> tile_coords; + for (size_t i = 0; i < tiles.size(); ++i) { + auto tile = tiles.at(i); + if (tile.second != texture_path) { + continue; + } + + // Convert tile index to 2D coordinates + auto x = i % size[0]; + auto y = i / size[0]; + tile_coords.push_back({x, y}); + } + // dst_verts places vertices in order // (left to right, bottom to top) std::vector dst_verts{}; @@ -82,12 +103,12 @@ std::shared_ptr TerrainChunk::create_mesh() { 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 + 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 + idxs.push_back(j + size[1] + i * size[1]); // top left } } 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/render_entity.cpp b/libopenage/renderer/stages/terrain/render_entity.cpp index fdfd7e486d..9a38681b7f 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,22 @@ 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 + // 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); + // set texture path - this->terrain_path.set_last(time, terrain_path); + // this->terrain_path.set_last(time, terrain_path); this->changed = true; } @@ -83,8 +96,20 @@ void TerrainRenderEntity::update(const util::Vector2s size, } } + // 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); + } + // set texture path - this->terrain_path.set_last(time, tiles[0].second); + // this->terrain_path.set_last(time, tiles[0].second); this->changed = true; } @@ -95,10 +120,16 @@ const std::vector &TerrainRenderEntity::get_vertices() { return this->vertices; } -const curve::Discrete &TerrainRenderEntity::get_terrain_path() { +// const curve::Discrete &TerrainRenderEntity::get_terrain_path() { +// std::shared_lock lock{this->mutex}; + +// return this->terrain_path; +// } + +const TerrainRenderEntity::tiles_t &TerrainRenderEntity::get_tiles() { std::shared_lock lock{this->mutex}; - return this->terrain_path; + return this->tiles; } 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..2b80c634a0 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" @@ -69,7 +70,21 @@ class TerrainRenderEntity { * * @return Texture mapping of textures to vertex area. */ - const curve::Discrete &get_terrain_path(); + // const curve::Discrete &get_terrain_path(); + + /** + * Get the tiles of the terrain. + * + * @return Terrain tiles. + */ + const tiles_t &get_tiles(); + + /** + * Get the terrain paths used in the terrain. + * + * @return Terrain paths. + */ + const std::unordered_set &get_terrain_paths(); /** * Get the number of vertices on each side of the terrain. @@ -104,6 +119,21 @@ class TerrainRenderEntity { */ util::Vector2s size; + /** + * Terrain tile information (elevation, terrain path). + */ + tiles_t tiles; + + /** + * Time of the last update call. + */ + time::time_t last_update; + + /** + * Terrain texture paths used in \p tiles . + */ + std::unordered_set terrain_paths; + /** * Terrain vertices (ingame coordinates). */ @@ -112,9 +142,7 @@ class TerrainRenderEntity { /** * Path to the terrain definition file. */ - curve::Discrete terrain_path; - - // std::unordered_map texture_map; // texture -> vertex indices + // curve::Discrete terrain_path; /** * Mutex for protecting threaded access. From 56994af7c56fe877a882954739ef4d5709685c63 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 1 May 2024 11:07:26 +0200 Subject: [PATCH 02/28] renderer: Don't use curve for terrain mesh. --- libopenage/renderer/stages/terrain/mesh.cpp | 25 ++++++++------------- libopenage/renderer/stages/terrain/mesh.h | 7 +++--- 2 files changed, 12 insertions(+), 20 deletions(-) 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. From 86040276ce654c8dad90bb8334c84d3585011256 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 1 May 2024 11:08:35 +0200 Subject: [PATCH 03/28] renderer: Create mesh for individual terrain textures. --- libopenage/renderer/stages/terrain/chunk.cpp | 123 ++++++++++-------- .../renderer/stages/terrain/render_entity.cpp | 6 + 2 files changed, 72 insertions(+), 57 deletions(-) diff --git a/libopenage/renderer/stages/terrain/chunk.cpp b/libopenage/renderer/stages/terrain/chunk.cpp index 70322283bb..12b207bdc9 100644 --- a/libopenage/renderer/stages/terrain/chunk.cpp +++ b/libopenage/renderer/stages/terrain/chunk.cpp @@ -61,54 +61,66 @@ const std::vector> &TerrainChunk::get_meshes( std::shared_ptr TerrainChunk::create_mesh(const std::string &texture_path) { auto size = this->render_entity->get_size(); - auto tiles = this->render_entity->get_tiles(); - auto src_verts = this->render_entity->get_vertices(); - - // Filter tiles by texture path - std::vector> tile_coords; - for (size_t i = 0; i < tiles.size(); ++i) { - auto tile = tiles.at(i); - if (tile.second != texture_path) { - continue; - } + auto v_width = size[0]; + auto v_height = size[1]; - // Convert tile index to 2D coordinates - auto x = i % size[0]; - auto y = i / size[0]; - tile_coords.push_back({x, y}); - } - - // 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 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 } } @@ -118,24 +130,21 @@ std::shared_ptr TerrainChunk::create_mesh(const std::string & 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/render_entity.cpp b/libopenage/renderer/stages/terrain/render_entity.cpp index 9a38681b7f..ba6da4faaa 100644 --- a/libopenage/renderer/stages/terrain/render_entity.cpp +++ b/libopenage/renderer/stages/terrain/render_entity.cpp @@ -132,6 +132,12 @@ const TerrainRenderEntity::tiles_t &TerrainRenderEntity::get_tiles() { return this->tiles; } +const std::unordered_set &TerrainRenderEntity::get_terrain_paths() { + std::shared_lock lock{this->mutex}; + + return this->terrain_paths; +} + const util::Vector2s &TerrainRenderEntity::get_size() { std::shared_lock lock{this->mutex}; From 2233c713cc11ae1bb40925e67771e8128d8b7123 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 1 May 2024 13:47:10 +0200 Subject: [PATCH 04/28] renderer: Remove old terrain paths from render entity. --- .../renderer/stages/terrain/render_entity.cpp | 12 ------------ libopenage/renderer/stages/terrain/render_entity.h | 14 -------------- 2 files changed, 26 deletions(-) diff --git a/libopenage/renderer/stages/terrain/render_entity.cpp b/libopenage/renderer/stages/terrain/render_entity.cpp index ba6da4faaa..e5eb0ba784 100644 --- a/libopenage/renderer/stages/terrain/render_entity.cpp +++ b/libopenage/renderer/stages/terrain/render_entity.cpp @@ -49,9 +49,6 @@ void TerrainRenderEntity::update_tile(const util::Vector2s size, // update the terrain paths this->terrain_paths.insert(terrain_path); - // set texture path - // this->terrain_path.set_last(time, terrain_path); - this->changed = true; } @@ -108,9 +105,6 @@ void TerrainRenderEntity::update(const util::Vector2s size, this->terrain_paths.insert(tile.second); } - // set texture path - // this->terrain_path.set_last(time, tiles[0].second); - this->changed = true; } @@ -120,12 +114,6 @@ const std::vector &TerrainRenderEntity::get_vertices() { return this->vertices; } -// const curve::Discrete &TerrainRenderEntity::get_terrain_path() { -// std::shared_lock lock{this->mutex}; - -// return this->terrain_path; -// } - const TerrainRenderEntity::tiles_t &TerrainRenderEntity::get_tiles() { std::shared_lock lock{this->mutex}; diff --git a/libopenage/renderer/stages/terrain/render_entity.h b/libopenage/renderer/stages/terrain/render_entity.h index 2b80c634a0..4136c7c81a 100644 --- a/libopenage/renderer/stages/terrain/render_entity.h +++ b/libopenage/renderer/stages/terrain/render_entity.h @@ -63,15 +63,6 @@ class TerrainRenderEntity { */ const std::vector &get_vertices(); - /** - * Get the texture mapping for the terrain. - * - * TODO: Return the actual mapping. - * - * @return Texture mapping of textures to vertex area. - */ - // const curve::Discrete &get_terrain_path(); - /** * Get the tiles of the terrain. * @@ -139,11 +130,6 @@ class TerrainRenderEntity { */ std::vector vertices; - /** - * Path to the terrain definition file. - */ - // curve::Discrete terrain_path; - /** * Mutex for protecting threaded access. */ From fc44940f27d539cfdd2037518d7d1b7001d686bc Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 1 May 2024 14:54:27 +0200 Subject: [PATCH 05/28] assets: Add second test terrain asset. --- assets/test/textures/test_terrain2.png | Bin 0 -> 9596 bytes assets/test/textures/test_terrain2.terrain | 12 ++++++++++++ assets/test/textures/test_terrain2.texture | 12 ++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 assets/test/textures/test_terrain2.png create mode 100644 assets/test/textures/test_terrain2.terrain create mode 100644 assets/test/textures/test_terrain2.texture diff --git a/assets/test/textures/test_terrain2.png b/assets/test/textures/test_terrain2.png new file mode 100644 index 0000000000000000000000000000000000000000..100205bfa2cf5eb44c7e1aaabbeeb01c766063a1 GIT binary patch literal 9596 zcmZu%Ygkjq*0#r^2wG!3pLg9z)zdbLG*(nFTNO z=BQYT3b9~C5V2LN0)`4o6j1J1FMt>cSAjr6xFwv~vu_!n??)e>3~Sz5Yi7UeU9+Y? zZrSAYuEk7?QKLq^yUzKut)oW0qb2_vHx`VP)HeJyYSe<&>pl~?hMs#t3(_Z!)FEy6^p=|ciFCBC_=##kC;o+)egTDj#(JDM2?NEXxE9+D( zPERiu22VFG3Jb^9Iy~^-xNXR=?^N5glh_sQ?u%v_`J)My^`Sm@L4P8VniztZ@HXy|uH0Umi;(*KNIP(HuCGa8oVkBKnXRD$27pn-j_eT@d=h zL7i#W@)zU(lmc-*I;RjAA{;z{1Y~*d<34`oEf^Ofb~g3e$x4yUOf~n#9jM85 zya+|HEr}E$yIE6*okM-;?AeXNhFmubK%F})Oh6CT>&@Lc$lkT_d3~rW4bpBUaQLnjFM&X-r!RQcPHvItp+zwp=I zrlrGQ^z{onlh-;FFP^hFCHbr5GXH!_!-u)Sx6eU-s*Hi!l#a^Q1CXCdeKwidbuCJ} zT(gnXaT{nZMJao?+K=gJGMlUqAlkk^|*F z46R!=6+5o)do8bG$o5!uJz!(W1JNZ&&(^!?r#0Sf;81ChkPH8ZFzxi-**vrJXXuo60lS0D@J(a$3l5*K|$$>zHe;>J6)pd`QM<*GiRv8*A1)z4_ZQ*T*VVP!*Nn;7li`2fid!EVE}LlIN$F`f_Qbg2 zNgVj+JY%=;hSJ*MEI$8+Qpe(1v(gag@I3^{jFP|3t8G`!T)IqB;)z z!@-?ao@>~LO)gd$S(lG#5V+?Z8%{%BRf%fQhe3y&FtV8R1J{%>US*y@t?3OD8#Rr9}1D6 zOhh28U;W;PK-A%DkvM>KR-*>OwMZO5I8i6ARb0^y+aC*cZt*$7gE#Z>18fOnb z3yW6@D^mJcl*He*%Sx++Z4LhR=YP9gGtwbj6|x+w7+4DVYJ5TR)&AULNXe2iWd-ra zhM%kU^k47L*croTkUDDb^#fVwUP^|4&yvn%>3PcJ z{I7YJ^PFmjnAAgwr&WC4gD?kLQ{28FWci~Or~ZNc(#7OSmbCNq21ZDlgsX;XZ^RW# zzRG7;fHI10{fHRWGm;gr>FSf zc~_3z9R9X1t{i*LdN-w3RqK6Y^5k0aSr(3^?6;hED~K@J7+>cV#CNQB7EOX?{V&l` zJ>6^!(+NB98FsO?!Z_kK@|z-T9$P8At81}UOS>1(qvh7NG^IJA{cbKJ*fo=gdIW&t zD>orKUAYxCkSww@Ma=$5S&0}>bTvUpQ+l8Vk}D{}apg0_fMP2bM2K!W5=6}-@oUo{ z+@=u~2mnP^ZkMhLKSch1C@ijMC$D}lQ($%W$WIK^;>Ukx>M5G5@?A_l)%I^*TS7oh zWH?j$WMtqE#ELb%K^Ej{kDM5&74mnob36$Pv0?$iXJJ8s3#nSkPFkf)TdMZ#PCLlr z^6>N%k{qY|>=A+jcZK>P=4Qj3Ak)FCfbZ76s zUOMs{8;4vNImuIIJ3O|Xp9jqD(`GD30UGUFf%L`Tf3`aoNc$sP=>ox`HLxsT<9PE{ z0A`@I>74RT{y%KzpNsBN_V2SBp+^~AE!5AuUr5DT5L!AT2oIgM^>{Ru?2mOPtRzX3 zI*CW6SKnJsgy2c!A**AcE`t~G)wmRhTHg4N?$IDK)fGXdx|d$gR-ld{%LIn2juxOc zhD8UggAo%-T}@EH1@$4iP)6t?9Q7rTuqEkm?EhXhQiY3k+wZj`!Qvc2-A>AK#(iP! zY$T#=nzWoVWHF4GP$FxJ;*ZT!A`69RroZLOpJsc|pcnCqQCD(tXOhg^!VG)4?FRSL zz|xx?qB~pwwAS`;L3+IrVZGMUavSzgz!h~cA>dI;0cUi$-DOYd&DSnX+;R$S+7h*= zCM}pdyJV^B%wFqISCb86Xw01_tdLLP>AScCokgUJliFMcIPhIKpOF0Oq~oU1qB|LU zJ4CckqDR5L{O5;&>PqZ+vNQN9A{R<&bnC|?b2|<)SJIN;8DtMBHWzl3s)A>dzmg7a zPO;qkGq%4rE>o)B6rt)39w!bCg>1sY8%-wt{Gx3=!xlo@{M5R186E26)s#F{ru3s&7mPk0Y_@ zA+|nNSgMjQVlsI3Hj&%%$ar|+qUF}Hay0dumRt5WEw`W%S8 zmI;E9TXqFnZn@u-+_JxExdnNI56cjiuqUxJSi`Y$>nxSSqhRIMs`Y3w*-BvLmIYzu zmYu`MEx3+Ru&ms&lUTWBaao=+N65-86Fw`qO#C&B+_E5y+=7T}t{xkHvU73svN7SY zBRG^%FG^#pWHu!Eek<8aR>V(74v_*JtT+cNV&zDZN!N+G&!#@$7pQIT;^m$s36EIw00YI?bgXz>*L9c{n$LG~m0F&QAO$ zO%13$U5g9#DUTV$C0K)@JRz<)v_GOcht9381**j4QPZ}X^BM>#;|{`AI=@ogCaVh| zv$QcTnh0;Tf(DGz-f9MS>nJU#0A#e==FNd!S)aaZ-rP7OFn-ad@t}lt3lmMMhu5?2V{+sV~(}%6o?Pmfg;tp^)8>1 z#jtx$68Q78TpHOY`Jf>kPLv(qTH!~!UH>_XJl*3_5#cr6r7(B7xC_6AGe zNH;3Tsbm|96nQ41%J(rp)S0$U!Iy~1)*XZk6iLwBr%-YvF;KwSg{t8V*VLD%SQHu& zT{eT#g9hmYy(nZCa!MkXsXg^`$u|IMyDJ}I_XG`pnCZA#{yp;8f$jJMvQ0t-8Juk< zDZSYu%I6M+y66%XoRK!u>6oD5q*FY%>86CH&+c%Wi%;pzmo9I(^;T)qmZ@8@Ddd|7 zbrP-To$d=Z_oYd>0}T+S$4b(jnmBN&E={QZ5v$>*0kt&BA@ftw3 z5GtR6haD3_Q@J#h@RObMa}EVF6X}`X5S|gs!koVWLpk)2JK-s-Snuo(hMv(w&O}%Q zNG;A_XpkOaF1lbyQ?`|O0;SPWQaW&0ZFK>pAnlQhq)tpN5&Y4=lWt4B0+P58emnksb7Ke2;A!A=uE3+nc^GN0_SDS< zAJR2gBD9nJ1X;f|!yO&SAPz#VjmWC6jL&ztnnM=zSCdyOB+x_xdU z;OT6c0=9_HFNT-csxnvzw^D_0p96g4Ilmb`@~Fz-)wr;gx*Eqs#MS-Of9h2bczL~Y z7>t`dIS%HB=nOApXq|J&Tknq1wq%%~{cEizYw_gix1HC;&!iBl(6s**UsX}NS~ zD$E4%HTmol2LzaURwlQmDG#vvc^$g zvJ|$cyMt~wYZHdP@8%*J=>xZ3?@9IWtA<+QUH|MYXR2liP+W&Rm$yvnPP~N@;IBm& zlHmpDK)^Ot3=&arwW)zv6}f4asb}AYmj7tFDsf_>eTeBM{eD;U0bz=`Y*~xebc>Wf zjJ<<6A)XhxYcB$ERFt*UG}dvJT3=+*m!hl{h`}6fI%4pbC`*VK%+j_Y26e$JKHrux z@^nPnN#F>&42aZx4c6>sLJ`mmXU~eb~^9Hmv~RY zcf%aE#>|kC1=`W6PMza_HCsf=O8QH=jH2&@2oWh9WzU7mtQ8Fe>$4ADwmhd=$$<&HrmG7 zZzGRA* zBMztc$_X?k`-y|#5!WQ9^eQ=l#$-Rieotx8r{N;_rGW1z(XQn*GRL<_eyT`lc5m80 z>1Xfsm1gIcAu-koTJ9lp+KNP%z$usny?lbb1X(BNKzS>=3sWL@p{@*mU7itxpm Date: Wed, 1 May 2024 15:15:28 +0200 Subject: [PATCH 06/28] gamestate: Use tile's asset path when updating renderer. --- libopenage/gamestate/terrain_chunk.cpp | 24 ++------------------ libopenage/gamestate/terrain_chunk.h | 29 +++++++----------------- libopenage/gamestate/terrain_factory.cpp | 7 ++---- 3 files changed, 12 insertions(+), 48 deletions(-) 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..974cf19de6 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" @@ -131,12 +131,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; } From 7a02427a618fc141288c919080f01cc6c03a4a6a Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 1 May 2024 17:27:05 +0200 Subject: [PATCH 07/28] gamestate: Add more choices to terrain examples. --- libopenage/gamestate/terrain_factory.cpp | 40 ++++++++++++++++-------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/libopenage/gamestate/terrain_factory.cpp b/libopenage/gamestate/terrain_factory.cpp index 974cf19de6..afeb4ae52a 100644 --- a/libopenage/gamestate/terrain_factory.cpp +++ b/libopenage/gamestate/terrain_factory.cpp @@ -20,33 +20,33 @@ namespace openage::gamestate { +// TODO: Remove test terrain references +static const std::string test_terrain_path = "../test/textures/test_terrain.terrain"; +static const std::string test_terrain_path2 = "../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 +91,17 @@ void build_test_terrains(const std::shared_ptr &gstate) { } } +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 // ========== @@ -112,7 +117,16 @@ std::shared_ptr TerrainFactory::add_chunk(const std::shared_ptrget_db_view()->get_object(test_terrains[test_terrain_index]); - test_texture_path = api::APITerrain::get_terrain_path(terrain_obj.value()); + terrain_info_path = api::APITerrain::get_terrain_path(terrain_obj.value()); + + test_terrain_index += 1; + } + else { + // use a test texture + if (test_terrain_index >= test_terrains.size()) { + test_terrain_index = 0; + } + terrain_info_path = test_terrain_path; test_terrain_index += 1; } @@ -122,7 +136,7 @@ std::shared_ptr TerrainFactory::add_chunk(const std::shared_ptr 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()}); + tiles.push_back({terrain_obj, terrain_info_path, terrain_elevation_t::zero()}); } auto chunk = std::make_shared(size, offset, std::move(tiles)); From 87346675a69a36c3c017c91bb4b7d6eae3129bc6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 1 May 2024 18:00:26 +0200 Subject: [PATCH 08/28] gamestate: Add terrain test layouts to terrain factory. --- libopenage/gamestate/terrain_factory.cpp | 123 +++++++++++++++++++---- 1 file changed, 104 insertions(+), 19 deletions(-) diff --git a/libopenage/gamestate/terrain_factory.cpp b/libopenage/gamestate/terrain_factory.cpp index afeb4ae52a..17b5d452cb 100644 --- a/libopenage/gamestate/terrain_factory.cpp +++ b/libopenage/gamestate/terrain_factory.cpp @@ -21,8 +21,10 @@ namespace openage::gamestate { // TODO: Remove test terrain references -static const std::string test_terrain_path = "../test/textures/test_terrain.terrain"; -static const std::string test_terrain_path2 = "../test/textures/test_terrain2.terrain"; +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 = {}; @@ -91,6 +93,83 @@ 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(); @@ -110,34 +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]); - terrain_info_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_terrain_index >= test_terrains.size()) { - test_terrain_index = 0; + if (test_chunk_index >= test_terrain_paths.size()) { + test_chunk_index = 0; } - terrain_info_path = test_terrain_path; + terrain_info_path = test_terrain_paths.at(test_chunk_index); - test_terrain_index += 1; - } - // ========== + for (size_t i = 0; i < size[0] * size[1]; ++i) { + tiles.push_back({terrain_obj, terrain_info_path, terrain_elevation_t::zero()}); + } - // 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, terrain_info_path, terrain_elevation_t::zero()}); + test_chunk_index += 1; } + // ========== auto chunk = std::make_shared(size, offset, std::move(tiles)); From a46451e852f5744757721d2fbfde392cfd9154f7 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 1 May 2024 18:12:12 +0200 Subject: [PATCH 09/28] renderer: Fix vertex generation from tiles. Generation used column-major instead of row-major ordering. --- libopenage/renderer/stages/terrain/render_entity.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libopenage/renderer/stages/terrain/render_entity.cpp b/libopenage/renderer/stages/terrain/render_entity.cpp index e5eb0ba784..7f2e6d825e 100644 --- a/libopenage/renderer/stages/terrain/render_entity.cpp +++ b/libopenage/renderer/stages/terrain/render_entity.cpp @@ -66,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) { From 9329095014edfce577d298dcc4a1aa9fa18dae56 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 3 May 2024 18:12:55 +0200 Subject: [PATCH 10/28] renderer: Order tayers by position. --- libopenage/renderer/resources/parser/parse_sprite.cpp | 11 +++++++++-- .../renderer/resources/parser/parse_terrain.cpp | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) 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) { From f5177907102fe8b3d8012bf82d141b8d414eace6 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 3 May 2024 19:04:34 +0200 Subject: [PATCH 11/28] renderer: Remove camera from world object. --- libopenage/renderer/stages/world/object.cpp | 5 ----- libopenage/renderer/stages/world/object.h | 12 ------------ libopenage/renderer/stages/world/render_stage.cpp | 1 - 3 files changed, 18 deletions(-) diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp index c58e9faa98..95ddf21ae4 100644 --- a/libopenage/renderer/stages/world/object.cpp +++ b/libopenage/renderer/stages/world/object.cpp @@ -33,7 +33,6 @@ 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}, @@ -49,10 +48,6 @@ 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) { if (not this->render_entity->is_changed()) { // exit early because there is nothing to do diff --git a/libopenage/renderer/stages/world/object.h b/libopenage/renderer/stages/world/object.h index a34a0c690c..1516a99be0 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. * @@ -147,11 +140,6 @@ class WorldObject { */ bool changed; - /** - * Camera for model uniforms. - */ - std::shared_ptr camera; - /** * Asset manager for central accessing and loading asset resources. */ diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index d24bb5f896..0201fd598f 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -50,7 +50,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); } From fcfc5963f902995be88200ce44c01173ff80edf1 Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 3 May 2024 19:21:36 +0200 Subject: [PATCH 12/28] renderer: Store uniforms for multiple layers in world object. --- libopenage/renderer/stages/world/object.cpp | 133 ++++++++++---------- libopenage/renderer/stages/world/object.h | 14 ++- 2 files changed, 80 insertions(+), 67 deletions(-) diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp index 95ddf21ae4..3636fb99d8 100644 --- a/libopenage/renderer/stages/world/object.cpp +++ b/libopenage/renderer/stages/world/object.cpp @@ -39,7 +39,7 @@ WorldObject::WorldObject(const std::shared_ptruniforms == 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); + 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); } - 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; - } - - // 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() { @@ -179,7 +185,8 @@ void WorldObject::clear_changed_flag() { } void WorldObject::set_uniforms(const std::shared_ptr &uniforms) { - this->uniforms = uniforms; + this->layer_uniforms.clear(); // ASDF: Update instead of clear + this->layer_uniforms.push_back(uniforms); } } // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/object.h b/libopenage/renderer/stages/world/object.h index 1516a99be0..19684330f8 100644 --- a/libopenage/renderer/stages/world/object.h +++ b/libopenage/renderer/stages/world/object.h @@ -146,7 +146,8 @@ class WorldObject { 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; @@ -167,14 +168,19 @@ 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. + * + * Since all layers are sprites and share the same geometry, we can reuse the layer uniforms and + * their renderables even if the animation changes. Therefore, we only need to add/remove + * layers when the _number_ of layers in the animation changes. */ - std::shared_ptr uniforms; + std::vector> layer_uniforms; /** * Time of the last update call. From b576532121de23b47dc1cb75e70504111065d49e Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 3 May 2024 20:27:32 +0200 Subject: [PATCH 13/28] renderer: Draw multiple layers per animation. --- libopenage/renderer/stages/world/object.cpp | 30 ++++++++++-- libopenage/renderer/stages/world/object.h | 31 ++++++++---- .../renderer/stages/world/render_stage.cpp | 49 ++++++++++--------- 3 files changed, 73 insertions(+), 37 deletions(-) diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp index 3636fb99d8..949cb23395 100644 --- a/libopenage/renderer/stages/world/object.cpp +++ b/libopenage/renderer/stages/world/object.cpp @@ -49,10 +49,18 @@ void WorldObject::set_render_entity(const std::shared_ptr &en } 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()); @@ -168,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; } @@ -176,6 +188,15 @@ 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(); +} + bool WorldObject::is_changed() { return this->changed; } @@ -184,9 +205,8 @@ void WorldObject::clear_changed_flag() { this->changed = false; } -void WorldObject::set_uniforms(const std::shared_ptr &uniforms) { - this->layer_uniforms.clear(); // ASDF: Update instead of clear - this->layer_uniforms.push_back(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 19684330f8..bec405a69d 100644 --- a/libopenage/renderer/stages/world/object.h +++ b/libopenage/renderer/stages/world/object.h @@ -75,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. * @@ -88,13 +97,20 @@ 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; + /** * Check whether the object was changed by \p update(). * @@ -108,13 +124,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. @@ -175,10 +190,6 @@ class WorldObject { /** * Shader uniforms for the layers of the object. Each layer corresponds to a * renderable in the render pass. - * - * Since all layers are sprites and share the same geometry, we can reuse the layer uniforms and - * their renderables even if the animation changes. Therefore, we only need to add/remove - * layers when the _number_ of layers in the animation changes. */ std::vector> layer_uniforms; diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index 0201fd598f..147e3fbd1b 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -60,31 +60,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_count = obj->get_required_layer_count(current_time); + Eigen::Matrix4f model_m = obj->get_model_matrix(); + + std::vector> transform_unifs; + for (size_t i = 0; i < layer_count; i++) { + // 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(display_obj); + 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); From d11e9267eeec6475c6dea12b6a158c1a1e34ca9d Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 3 May 2024 21:09:31 +0200 Subject: [PATCH 14/28] renderer: Split renderer.h into more source files. --- libopenage/presenter/presenter.cpp | 3 + libopenage/renderer/CMakeLists.txt | 3 + libopenage/renderer/demo/demo_0.cpp | 3 + libopenage/renderer/demo/demo_1.cpp | 3 + libopenage/renderer/demo/demo_2.cpp | 3 + libopenage/renderer/demo/demo_3.cpp | 2 + libopenage/renderer/demo/demo_4.cpp | 3 + libopenage/renderer/demo/demo_5.cpp | 2 + libopenage/renderer/demo/stresstest_0.cpp | 2 + libopenage/renderer/gui/gui.cpp | 13 ++- libopenage/renderer/opengl/render_pass.h | 6 +- libopenage/renderer/opengl/render_target.cpp | 4 + libopenage/renderer/opengl/render_target.h | 8 ++ libopenage/renderer/render_pass.cpp | 40 +++++++ libopenage/renderer/render_pass.h | 44 ++++++++ libopenage/renderer/render_target.cpp | 8 ++ libopenage/renderer/render_target.h | 38 +++++++ libopenage/renderer/renderable.cpp | 8 ++ libopenage/renderer/renderable.h | 62 +++++++++++ libopenage/renderer/renderer.cpp | 34 +----- libopenage/renderer/renderer.h | 101 +----------------- .../renderer/stages/hud/render_stage.cpp | 4 +- .../renderer/stages/screen/render_stage.cpp | 4 +- .../renderer/stages/screen/screenshot.cpp | 5 +- .../renderer/stages/skybox/render_stage.cpp | 4 +- .../renderer/stages/terrain/render_stage.cpp | 4 +- .../renderer/stages/world/render_stage.cpp | 2 + libopenage/renderer/vulkan/render_target.h | 2 +- 28 files changed, 272 insertions(+), 143 deletions(-) create mode 100644 libopenage/renderer/render_pass.cpp create mode 100644 libopenage/renderer/render_pass.h create mode 100644 libopenage/renderer/render_target.cpp create mode 100644 libopenage/renderer/render_target.h create mode 100644 libopenage/renderer/renderable.cpp create mode 100644 libopenage/renderer/renderable.h 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/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.h b/libopenage/renderer/opengl/render_pass.h index 2994a0e7f7..b5f317f585 100644 --- a/libopenage/renderer/opengl/render_pass.h +++ b/libopenage/renderer/opengl/render_pass.h @@ -1,8 +1,10 @@ -// 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 { 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/render_pass.cpp b/libopenage/renderer/render_pass.cpp new file mode 100644 index 0000000000..f326fdf0bb --- /dev/null +++ b/libopenage/renderer/render_pass.cpp @@ -0,0 +1,40 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "render_pass.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/render_pass.h b/libopenage/renderer/render_pass.h new file mode 100644 index 0000000000..5849c97a26 --- /dev/null +++ b/libopenage/renderer/render_pass.h @@ -0,0 +1,44 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "renderer/renderable.h" + + +namespace openage { +namespace renderer { +class RenderTarget; + +/// 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; +}; + +} // 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/stages/hud/render_stage.cpp b/libopenage/renderer/stages/hud/render_stage.cpp index b7ecaef31e..ad115bd2f6 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" diff --git a/libopenage/renderer/stages/screen/render_stage.cpp b/libopenage/renderer/stages/screen/render_stage.cpp index 786c547f62..a55419e0e1 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" 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/render_stage.cpp b/libopenage/renderer/stages/terrain/render_stage.cpp index 518ebd7c9b..332b80dfd9 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" diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index 147e3fbd1b..203b974d4d 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" 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" From 253f94b70574a16a0e5fe422377535faa51980eb Mon Sep 17 00:00:00 2001 From: heinezen Date: Fri, 3 May 2024 21:33:31 +0200 Subject: [PATCH 15/28] renderer: Remove unused variable. --- libopenage/renderer/opengl/shader_program.cpp | 1 - 1 file changed, 1 deletion(-) 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); } From 85c4787195e46766296efbc860696cca5018f1f4 Mon Sep 17 00:00:00 2001 From: heinezen Date: Sat, 4 May 2024 00:03:07 +0200 Subject: [PATCH 16/28] renderer: Priority sorting in render pass. --- libopenage/renderer/opengl/render_pass.cpp | 2 +- libopenage/renderer/render_pass.cpp | 67 +++++++++++++- libopenage/renderer/render_pass.h | 87 ++++++++++++++++--- libopenage/renderer/stages/world/object.cpp | 14 +++ libopenage/renderer/stages/world/object.h | 2 + .../renderer/stages/world/render_stage.cpp | 6 +- 6 files changed, 159 insertions(+), 19 deletions(-) diff --git a/libopenage/renderer/opengl/render_pass.cpp b/libopenage/renderer/opengl/render_pass.cpp index 526663d324..f0904d9bb2 100644 --- a/libopenage/renderer/opengl/render_pass.cpp +++ b/libopenage/renderer/opengl/render_pass.cpp @@ -19,7 +19,7 @@ const std::vector &GlRenderPass::get_renderables() const { } void GlRenderPass::set_renderables(std::vector renderables) { - this->renderables = renderables; + RenderPass::set_renderables(renderables); this->is_optimised = false; } diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp index f326fdf0bb..0be3bdc941 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -2,6 +2,8 @@ #include "render_pass.h" +#include + namespace openage::renderer { @@ -23,14 +25,75 @@ void RenderPass::set_target(const std::shared_ptr &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); + this->renderables.insert(this->renderables.end(), renderables.begin(), renderables.end()); + + if (this->priority_slices.empty() or this->priority_slices.back().priority != std::numeric_limits::max()) { + this->priority_slices.push_back(priority_slice{std::numeric_limits::max(), renderables.size()}); + return; } + + this->priority_slices.back().length += renderables.size(); } void RenderPass::add_renderables(Renderable renderable) { this->renderables.push_back(std::move(renderable)); + + if (this->priority_slices.empty() or this->priority_slices.back().priority != std::numeric_limits::max()) { + this->priority_slices.push_back(priority_slice{std::numeric_limits::max(), 1}); + return; + } + + this->priority_slices.back().length += 1; +} + +void RenderPass::add_renderables(std::vector renderables, int64_t priority) { + size_t renderables_index = 0; + size_t slice_index = 0; + int64_t current_priority = std::numeric_limits::min(); + for (auto i = 0; i < this->priority_slices.size(); i++) { + auto &slice = this->priority_slices.at(i); + if (slice.priority > priority) { + slice_index = i; + break; + } + renderables_index += slice.length; + current_priority = slice.priority; + } + + this->renderables.insert(this->renderables.begin() + renderables_index, renderables.begin(), renderables.end()); + + if (current_priority == priority) { + this->priority_slices[slice_index].length += renderables.size(); + } + else { + this->priority_slices.insert(this->priority_slices.begin() + slice_index, priority_slice{priority, renderables.size()}); + } +} + +void RenderPass::add_renderables(Renderable renderable, int64_t priority) { + size_t renderables_index = 0; + size_t slice_index = 0; + int64_t current_priority = std::numeric_limits::min(); + for (auto i = 0; i < this->priority_slices.size(); i++) { + auto &slice = this->priority_slices.at(i); + if (slice.priority > priority) { + slice_index = i; + break; + } + renderables_index += slice.length; + current_priority = slice.priority; + } + + this->renderables.insert(this->renderables.begin() + renderables_index, std::move(renderable)); + + if (current_priority == priority) { + this->priority_slices[slice_index].length += 1; + } + else { + this->priority_slices.insert(this->priority_slices.begin() + slice_index, priority_slice{priority, 1}); + } } void RenderPass::clear_renderables() { diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h index 5849c97a26..8f4e304cfd 100644 --- a/libopenage/renderer/render_pass.h +++ b/libopenage/renderer/render_pass.h @@ -2,6 +2,7 @@ #pragma once +#include #include #include @@ -12,32 +13,92 @@ namespace openage { namespace renderer { class RenderTarget; -/// 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; +/** + * A slice of renderables with the same priority. + */ +struct priority_slice { + /// The priority of the renderables in this slice. + int64_t priority; + /// The number of renderables in this slice. + size_t length; +}; + +/** + * 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; + + /** + * Set the render target to write to. + */ void set_target(const std::shared_ptr &); + + /** + * Get the render target of the render pass. + */ const std::shared_ptr &get_target() const; - // Replace the current renderables + /** + * Replace the current renderables with the given list of renderables. + */ void set_renderables(std::vector); - // Append renderables to the end of the list of renderables + + /** + * Append renderables to the render pass. + */ void add_renderables(std::vector); - // Append a single renderable to the end of the list of renderables + + /** + * Append a single renderable to the render pass. + */ void add_renderables(Renderable); - // Clear the list of renderables + + /** + * Append renderables to the render pass with a given priority. + */ + void add_renderables(std::vector, int64_t priority); + + /** + * Append a single renderable to the render pass with a given priority. + */ + void add_renderables(Renderable, int64_t priority); + + /** + * Clear the list of renderables + */ void clear_renderables(); +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; + private: - /// The render target to write into. + /** + * Render target to write to. + */ std::shared_ptr target; + + /** + * Stores the number of renderables in the \p renderables member that + * have the same priority. + * + * The vector is sorted by priority, so the index of the first renderable + * with a given priority can be retrieved by adding up the lengths of all + * priority slices with a lower priority. + */ + std::vector priority_slices; }; } // namespace renderer diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp index 949cb23395..7131d42601 100644 --- a/libopenage/renderer/stages/world/object.cpp +++ b/libopenage/renderer/stages/world/object.cpp @@ -197,6 +197,20 @@ size_t WorldObject::get_required_layer_count(const time::time_t &time) const { 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) { + positions.push_back(i); + } + + return positions; +} + bool WorldObject::is_changed() { return this->changed; } diff --git a/libopenage/renderer/stages/world/object.h b/libopenage/renderer/stages/world/object.h index bec405a69d..c5e0aa8197 100644 --- a/libopenage/renderer/stages/world/object.h +++ b/libopenage/renderer/stages/world/object.h @@ -111,6 +111,8 @@ class WorldObject { */ 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(). * diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index 203b974d4d..083b2461db 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -62,11 +62,11 @@ void WorldRenderStage::update() { obj->fetch_updates(current_time); if (obj->is_changed()) { if (obj->requires_renderable()) { - auto layer_count = obj->get_required_layer_count(current_time); + auto layer_positions = obj->get_layer_positions(current_time); Eigen::Matrix4f model_m = obj->get_model_matrix(); std::vector> transform_unifs; - for (size_t i = 0; i < layer_count; i++) { + 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", @@ -84,7 +84,7 @@ void WorldRenderStage::update() { true, true, }; - this->render_pass->add_renderables(display_obj); + this->render_pass->add_renderables(display_obj, layer_pos); transform_unifs.push_back(layer_unifs); } From e33d4815ebcdee80a3fab3bd1649ae4a37547e78 Mon Sep 17 00:00:00 2001 From: heinezen Date: Tue, 7 May 2024 22:56:45 +0200 Subject: [PATCH 17/28] renderer: Remove code duplication in render pass operations. --- libopenage/renderer/render_pass.cpp | 35 +++------------------ libopenage/renderer/stages/world/object.cpp | 3 +- 2 files changed, 6 insertions(+), 32 deletions(-) diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp index 0be3bdc941..eaa218536f 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -38,28 +38,21 @@ void RenderPass::add_renderables(std::vector renderables) { } void RenderPass::add_renderables(Renderable renderable) { - this->renderables.push_back(std::move(renderable)); - - if (this->priority_slices.empty() or this->priority_slices.back().priority != std::numeric_limits::max()) { - this->priority_slices.push_back(priority_slice{std::numeric_limits::max(), 1}); - return; - } - - this->priority_slices.back().length += 1; + this->add_renderables(std::vector{std::move(renderable)}); } void RenderPass::add_renderables(std::vector renderables, int64_t priority) { size_t renderables_index = 0; size_t slice_index = 0; int64_t current_priority = std::numeric_limits::min(); - for (auto i = 0; i < this->priority_slices.size(); i++) { + for (size_t i = 0; i < this->priority_slices.size(); i++) { auto &slice = this->priority_slices.at(i); if (slice.priority > priority) { - slice_index = i; break; } renderables_index += slice.length; current_priority = slice.priority; + slice_index = i; } this->renderables.insert(this->renderables.begin() + renderables_index, renderables.begin(), renderables.end()); @@ -73,27 +66,7 @@ void RenderPass::add_renderables(std::vector renderables, int64_t pr } void RenderPass::add_renderables(Renderable renderable, int64_t priority) { - size_t renderables_index = 0; - size_t slice_index = 0; - int64_t current_priority = std::numeric_limits::min(); - for (auto i = 0; i < this->priority_slices.size(); i++) { - auto &slice = this->priority_slices.at(i); - if (slice.priority > priority) { - slice_index = i; - break; - } - renderables_index += slice.length; - current_priority = slice.priority; - } - - this->renderables.insert(this->renderables.begin() + renderables_index, std::move(renderable)); - - if (current_priority == priority) { - this->priority_slices[slice_index].length += 1; - } - else { - this->priority_slices.insert(this->priority_slices.begin() + slice_index, priority_slice{priority, 1}); - } + this->add_renderables(std::vector{std::move(renderable)}, priority); } void RenderPass::clear_renderables() { diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp index 7131d42601..3b9edd6d3d 100644 --- a/libopenage/renderer/stages/world/object.cpp +++ b/libopenage/renderer/stages/world/object.cpp @@ -205,7 +205,8 @@ std::vector WorldObject::get_layer_positions(const time::time_t &time) c std::vector positions; for (size_t i = 0; i < animation_info->get_layer_count(); ++i) { - positions.push_back(i); + auto layer = animation_info->get_layer(i); + positions.push_back(layer.get_position()); } return positions; From 2b3262d674522e74c1bf1195433b373a547ca541 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 8 May 2024 14:59:22 +0200 Subject: [PATCH 18/28] renderer: Autosort renderables into layers on insert. --- libopenage/renderer/opengl/render_pass.cpp | 4 -- libopenage/renderer/opengl/render_pass.h | 1 - libopenage/renderer/render_pass.cpp | 63 ++++++++++++++-------- libopenage/renderer/render_pass.h | 47 ++++++++++++---- 4 files changed, 79 insertions(+), 36 deletions(-) diff --git a/libopenage/renderer/opengl/render_pass.cpp b/libopenage/renderer/opengl/render_pass.cpp index f0904d9bb2..ad8340a8b3 100644 --- a/libopenage/renderer/opengl/render_pass.cpp +++ b/libopenage/renderer/opengl/render_pass.cpp @@ -14,10 +14,6 @@ GlRenderPass::GlRenderPass(std::vector renderables, log::log(MSG(dbg) << "Created OpenGL render pass"); } -const std::vector &GlRenderPass::get_renderables() const { - return this->renderables; -} - void GlRenderPass::set_renderables(std::vector renderables) { RenderPass::set_renderables(renderables); this->is_optimised = false; diff --git a/libopenage/renderer/opengl/render_pass.h b/libopenage/renderer/opengl/render_pass.h index b5f317f585..4977c8e2e9 100644 --- a/libopenage/renderer/opengl/render_pass.h +++ b/libopenage/renderer/opengl/render_pass.h @@ -14,7 +14,6 @@ class GlRenderPass final : public RenderPass { const std::shared_ptr &); void set_renderables(std::vector); - const std::vector &get_renderables() const; void set_is_optimised(bool); bool get_is_optimised() const; diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp index eaa218536f..5101a03b06 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -10,14 +10,16 @@ namespace openage::renderer { RenderPass::RenderPass(std::vector renderables, const std::shared_ptr &target) : renderables(std::move(renderables)), - target{target} {} - + target{target}, + layers{} { + // Add a default layer with the lowest priority + this->add_layer(0, std::numeric_limits::max()); +} const std::shared_ptr &RenderPass::get_target() const { return this->target; } - void RenderPass::set_target(const std::shared_ptr &target) { this->target = target; } @@ -29,12 +31,7 @@ void RenderPass::set_renderables(std::vector renderables) { void RenderPass::add_renderables(std::vector renderables) { this->renderables.insert(this->renderables.end(), renderables.begin(), renderables.end()); - if (this->priority_slices.empty() or this->priority_slices.back().priority != std::numeric_limits::max()) { - this->priority_slices.push_back(priority_slice{std::numeric_limits::max(), renderables.size()}); - return; - } - - this->priority_slices.back().length += renderables.size(); + this->layers.front().length += renderables.size(); } void RenderPass::add_renderables(Renderable renderable) { @@ -43,32 +40,56 @@ void RenderPass::add_renderables(Renderable renderable) { void RenderPass::add_renderables(std::vector renderables, int64_t priority) { size_t renderables_index = 0; - size_t slice_index = 0; + size_t layer_index = 0; int64_t current_priority = std::numeric_limits::min(); - for (size_t i = 0; i < this->priority_slices.size(); i++) { - auto &slice = this->priority_slices.at(i); - if (slice.priority > priority) { + for (size_t i = 0; i < this->layers.size(); i++) { + auto &layer = this->layers.at(i); + if (layer.priority < priority) { break; } - renderables_index += slice.length; - current_priority = slice.priority; - slice_index = i; + renderables_index += layer.length; + current_priority = layer.priority; + layer_index = i; } this->renderables.insert(this->renderables.begin() + renderables_index, renderables.begin(), renderables.end()); - if (current_priority == priority) { - this->priority_slices[slice_index].length += renderables.size(); - } - else { - this->priority_slices.insert(this->priority_slices.begin() + slice_index, priority_slice{priority, renderables.size()}); + if (current_priority != priority) { + layer_index += 1; + this->add_layer(layer_index, priority); } + + this->layers.at(layer_index).length += renderables.size(); } 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) { + size_t layer_index = 0; + for (const auto &layer : this->layers) { + if (layer.priority < priority) { + break; + } + layer_index++; + } + + this->add_layer(layer_index, priority); +} + +const std::vector &RenderPass::get_renderables() const { + return this->renderables; +} + +const std::vector &RenderPass::get_layers() const { + return this->layers; +} + +void RenderPass::add_layer(size_t index, int64_t priority) { + this->layers.insert(this->layers.begin() + index, Layer{priority, 0}); +} + void RenderPass::clear_renderables() { this->renderables.clear(); } diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h index 8f4e304cfd..82581d824a 100644 --- a/libopenage/renderer/render_pass.h +++ b/libopenage/renderer/render_pass.h @@ -14,12 +14,13 @@ namespace renderer { class RenderTarget; /** - * A slice of renderables with the same priority. + * 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. */ -struct priority_slice { - /// The priority of the renderables in this slice. +struct Layer { + /// Priority of the renderables in this slice. int64_t priority; - /// The number of renderables in this slice. + /// Number of renderables in this slice. size_t length; }; @@ -32,6 +33,16 @@ class RenderPass { public: virtual ~RenderPass() = default; + /** + * Get the renderables of the render pass. + */ + const std::vector &get_renderables() const; + + /** + * Get the layers of the render pass. + */ + const std::vector &get_layers() const; + /** * Set the render target to write to. */ @@ -67,6 +78,14 @@ class RenderPass { */ void add_renderables(Renderable, int64_t priority); + /** + * Add a new layer to the render pass. + * + * @param priority Priority of the layer. Layers with higher priority are drawn first. + * @param clear_depth Whether to clear the depth buffer before rendering this layer. + */ + void add_layer(int64_t priority); + /** * Clear the list of renderables */ @@ -85,20 +104,28 @@ class RenderPass { 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. + */ + void add_layer(size_t index, int64_t priority); + /** * Render target to write to. */ std::shared_ptr target; /** - * Stores the number of renderables in the \p renderables member that - * have the same priority. + * 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. * - * The vector is sorted by priority, so the index of the first renderable - * with a given priority can be retrieved by adding up the lengths of all - * priority slices with a lower priority. + * Sorted from lowest to highest priority. */ - std::vector priority_slices; + std::vector layers; }; } // namespace renderer From 10140c8102886af928d7562b34a016604a8e200d Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 8 May 2024 16:18:26 +0200 Subject: [PATCH 19/28] renderer: Make inserion by priority the default. --- libopenage/renderer/definitions.h | 10 ++++- libopenage/renderer/opengl/renderer.cpp | 2 +- libopenage/renderer/render_pass.cpp | 26 ++++++------- libopenage/renderer/render_pass.h | 49 ++++++++++++++++--------- 4 files changed, 53 insertions(+), 34 deletions(-) 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/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index d26ba7b96c..bf4740f935 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -178,7 +178,7 @@ void GlRenderer::render(const std::shared_ptr &pass) { // glEnable(GL_CULL_FACE); auto gl_pass = std::dynamic_pointer_cast(pass); - GlRenderer::optimise(gl_pass); + // GlRenderer::optimise(gl_pass); for (auto const &obj : gl_pass->get_renderables()) { if (obj.alpha_blending) { diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp index 5101a03b06..e2eade4ded 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -2,18 +2,19 @@ #include "render_pass.h" -#include - namespace openage::renderer { RenderPass::RenderPass(std::vector renderables, const std::shared_ptr &target) : - renderables(std::move(renderables)), + renderables{}, target{target}, layers{} { // Add a default layer with the lowest priority this->add_layer(0, std::numeric_limits::max()); + + // Add the renderables to the pass + this->add_renderables(renderables); } const std::shared_ptr &RenderPass::get_target() const { @@ -25,17 +26,8 @@ void RenderPass::set_target(const std::shared_ptr &target) { } void RenderPass::set_renderables(std::vector renderables) { - this->renderables = std::move(renderables); -} - -void RenderPass::add_renderables(std::vector renderables) { - this->renderables.insert(this->renderables.end(), renderables.begin(), renderables.end()); - - this->layers.front().length += renderables.size(); -} - -void RenderPass::add_renderables(Renderable renderable) { - this->add_renderables(std::vector{std::move(renderable)}); + this->clear_renderables(); + this->add_renderables(renderables); } void RenderPass::add_renderables(std::vector renderables, int64_t priority) { @@ -91,7 +83,13 @@ void RenderPass::add_layer(size_t index, int64_t priority) { } void RenderPass::clear_renderables() { + // Erase the renderables this->renderables.clear(); + + // Keep layers, but reset the length of each layer + for (auto &layer : this->layers) { + layer.length = 0; + } } } // namespace openage::renderer diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h index 82581d824a..6eb99963ac 100644 --- a/libopenage/renderer/render_pass.h +++ b/libopenage/renderer/render_pass.h @@ -6,6 +6,7 @@ #include #include +#include "renderer/definitions.h" #include "renderer/renderable.h" @@ -24,7 +25,6 @@ struct Layer { size_t length; }; - /** * A render pass is a series of draw calls represented by renderables that output * into the given render target. @@ -35,48 +35,56 @@ class RenderPass { /** * 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 &); + 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); - - /** - * Append renderables to the render pass. - */ - void add_renderables(std::vector); - - /** - * Append a single renderable to the render pass. - */ - void add_renderables(Renderable); + 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 first. */ - void add_renderables(std::vector, int64_t priority); + 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 first. */ - void add_renderables(Renderable, int64_t priority); + void add_renderables(Renderable renderable, + int64_t priority = LAYER_PRIORITY_MAX); /** * Add a new layer to the render pass. @@ -95,11 +103,16 @@ class RenderPass { /** * 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, const std::shared_ptr &); + RenderPass(std::vector renderables, const std::shared_ptr &target); /** - * The renderables to parse and possibly execute. + * The renderables to draw. + * + * Kept sorted by layer priorities (highest to lowest priority). */ std::vector renderables; @@ -123,7 +136,7 @@ class RenderPass { * 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. + * Sorted from highest to lowest priority. */ std::vector layers; }; From bf9b2f2f2834df0a6a318f8d89e300ae2dba771e Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 8 May 2024 16:39:57 +0200 Subject: [PATCH 20/28] renderer: Move sorting of renderables to render pass. --- libopenage/renderer/opengl/render_pass.cpp | 13 ++++++------- libopenage/renderer/opengl/render_pass.h | 6 +++--- libopenage/renderer/opengl/renderer.cpp | 12 +++++------- libopenage/renderer/opengl/renderer.h | 2 +- libopenage/renderer/render_pass.cpp | 14 ++++++++++++++ libopenage/renderer/render_pass.h | 11 +++++++++++ 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/libopenage/renderer/opengl/render_pass.cpp b/libopenage/renderer/opengl/render_pass.cpp index ad8340a8b3..79a1773b1a 100644 --- a/libopenage/renderer/opengl/render_pass.cpp +++ b/libopenage/renderer/opengl/render_pass.cpp @@ -10,20 +10,19 @@ namespace openage::renderer::opengl { GlRenderPass::GlRenderPass(std::vector renderables, const std::shared_ptr &target) : RenderPass(renderables, target), - is_optimised(false) { - log::log(MSG(dbg) << "Created OpenGL render pass"); + is_optimized(false) { } void GlRenderPass::set_renderables(std::vector renderables) { RenderPass::set_renderables(renderables); - this->is_optimised = false; + this->is_optimized = false; } -bool GlRenderPass::get_is_optimised() const { - return this->is_optimised; +bool GlRenderPass::get_is_optimized() const { + return this->is_optimized; } -void GlRenderPass::set_is_optimised(bool flag) { - this->is_optimised = flag; +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 4977c8e2e9..d29ad6e227 100644 --- a/libopenage/renderer/opengl/render_pass.h +++ b/libopenage/renderer/opengl/render_pass.h @@ -14,12 +14,12 @@ class GlRenderPass final : public RenderPass { const std::shared_ptr &); void set_renderables(std::vector); - 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/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index bf4740f935..6e8c865194 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -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,7 +176,7 @@ void GlRenderer::render(const std::shared_ptr &pass) { // glEnable(GL_CULL_FACE); auto gl_pass = std::dynamic_pointer_cast(pass); - // GlRenderer::optimise(gl_pass); + GlRenderer::optimize(gl_pass); for (auto const &obj : gl_pass->get_renderables()) { if (obj.alpha_blending) { diff --git a/libopenage/renderer/opengl/renderer.h b/libopenage/renderer/opengl/renderer.h index 4d9038808a..aa3d3769a3 100644 --- a/libopenage/renderer/opengl/renderer.h +++ b/libopenage/renderer/opengl/renderer.h @@ -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/render_pass.cpp b/libopenage/renderer/render_pass.cpp index e2eade4ded..b9a638cd76 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -2,6 +2,8 @@ #include "render_pass.h" +#include "log/log.h" + namespace openage::renderer { @@ -15,6 +17,8 @@ RenderPass::RenderPass(std::vector renderables, // Add the renderables to the pass this->add_renderables(renderables); + + log::log(MSG(dbg) << "Created render pass"); } const std::shared_ptr &RenderPass::get_target() const { @@ -92,4 +96,14 @@ void RenderPass::clear_renderables() { } } +void RenderPass::sort(const compare_func &compare) { + size_t offset = 0; + for (auto &layer : this->layers) { + std::stable_sort(this->renderables.begin() + offset, + this->renderables.begin() + offset + layer.length, + compare); + offset += layer.length; + } +} + } // namespace openage::renderer diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h index 6eb99963ac..d424c413c7 100644 --- a/libopenage/renderer/render_pass.h +++ b/libopenage/renderer/render_pass.h @@ -99,6 +99,17 @@ class RenderPass { */ 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, From b328def2f657952f9e9e5655b9ea24885326d71f Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 8 May 2024 16:50:37 +0200 Subject: [PATCH 21/28] renderer: Add more functions for optimization to GLRenderPass. --- libopenage/renderer/opengl/render_pass.cpp | 10 ++++++++++ libopenage/renderer/opengl/render_pass.h | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/libopenage/renderer/opengl/render_pass.cpp b/libopenage/renderer/opengl/render_pass.cpp index 79a1773b1a..e97fd922c1 100644 --- a/libopenage/renderer/opengl/render_pass.cpp +++ b/libopenage/renderer/opengl/render_pass.cpp @@ -18,6 +18,16 @@ void GlRenderPass::set_renderables(std::vector renderables) { this->is_optimized = false; } +void GlRenderPass::add_renderables(std::vector renderables, int64_t priority) { + RenderPass::add_renderables(renderables, priority); + this->is_optimized = false; +} + +void GlRenderPass::add_renderables(Renderable renderable, int64_t priority) { + RenderPass::add_renderables(renderable, priority); + this->is_optimized = false; +} + bool GlRenderPass::get_is_optimized() const { return this->is_optimized; } diff --git a/libopenage/renderer/opengl/render_pass.h b/libopenage/renderer/opengl/render_pass.h index d29ad6e227..4ac5e31c44 100644 --- a/libopenage/renderer/opengl/render_pass.h +++ b/libopenage/renderer/opengl/render_pass.h @@ -13,7 +13,10 @@ class GlRenderPass final : public RenderPass { GlRenderPass(std::vector, const std::shared_ptr &); - void set_renderables(std::vector); + 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_is_optimized(bool); bool get_is_optimized() const; From 017c3bec44dc4483f926a63808c36f89b0c5d879 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 8 May 2024 16:52:35 +0200 Subject: [PATCH 22/28] renderer: Turn off optimization for OpenGL. Because it could be very expensive. --- libopenage/renderer/opengl/renderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index 6e8c865194..41496db679 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -176,7 +176,8 @@ void GlRenderer::render(const std::shared_ptr &pass) { // glEnable(GL_CULL_FACE); auto gl_pass = std::dynamic_pointer_cast(pass); - GlRenderer::optimize(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) { From 5746a5bc5c0784146718e2e8ed5675cfc976e8f3 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 8 May 2024 17:21:21 +0200 Subject: [PATCH 23/28] renderer: Use move semantics for adding renderables to pass. --- libopenage/renderer/opengl/render_pass.cpp | 16 ++++++++-------- libopenage/renderer/opengl/render_pass.h | 10 +++++----- libopenage/renderer/opengl/renderer.h | 2 +- libopenage/renderer/render_pass.cpp | 16 +++++++++------- libopenage/renderer/render_pass.h | 8 ++++---- libopenage/renderer/stages/hud/render_stage.cpp | 2 +- .../renderer/stages/screen/render_stage.cpp | 2 +- .../renderer/stages/terrain/render_stage.cpp | 2 +- .../renderer/stages/world/render_stage.cpp | 2 +- 9 files changed, 31 insertions(+), 29 deletions(-) diff --git a/libopenage/renderer/opengl/render_pass.cpp b/libopenage/renderer/opengl/render_pass.cpp index e97fd922c1..6d2ab052c1 100644 --- a/libopenage/renderer/opengl/render_pass.cpp +++ b/libopenage/renderer/opengl/render_pass.cpp @@ -7,24 +7,24 @@ namespace openage::renderer::opengl { -GlRenderPass::GlRenderPass(std::vector renderables, +GlRenderPass::GlRenderPass(std::vector &&renderables, const std::shared_ptr &target) : - RenderPass(renderables, target), + RenderPass(std::move(renderables), target), is_optimized(false) { } -void GlRenderPass::set_renderables(std::vector renderables) { - RenderPass::set_renderables(renderables); +void GlRenderPass::set_renderables(std::vector &&renderables) { + RenderPass::set_renderables(std::move(renderables)); this->is_optimized = false; } -void GlRenderPass::add_renderables(std::vector renderables, int64_t priority) { - RenderPass::add_renderables(renderables, priority); +void GlRenderPass::add_renderables(std::vector &&renderables, int64_t priority) { + RenderPass::add_renderables(std::move(renderables), priority); this->is_optimized = false; } -void GlRenderPass::add_renderables(Renderable renderable, int64_t priority) { - RenderPass::add_renderables(renderable, priority); +void GlRenderPass::add_renderables(Renderable &&renderable, int64_t priority) { + RenderPass::add_renderables(std::move(renderable), priority); this->is_optimized = false; } diff --git a/libopenage/renderer/opengl/render_pass.h b/libopenage/renderer/opengl/render_pass.h index 4ac5e31c44..87ea8448c9 100644 --- a/libopenage/renderer/opengl/render_pass.h +++ b/libopenage/renderer/opengl/render_pass.h @@ -10,12 +10,12 @@ 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 &&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_is_optimized(bool); bool get_is_optimized() const; diff --git a/libopenage/renderer/opengl/renderer.h b/libopenage/renderer/opengl/renderer.h index aa3d3769a3..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 diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp index b9a638cd76..931f5519ac 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -7,7 +7,7 @@ namespace openage::renderer { -RenderPass::RenderPass(std::vector renderables, +RenderPass::RenderPass(std::vector &&renderables, const std::shared_ptr &target) : renderables{}, target{target}, @@ -16,7 +16,7 @@ RenderPass::RenderPass(std::vector renderables, this->add_layer(0, std::numeric_limits::max()); // Add the renderables to the pass - this->add_renderables(renderables); + this->add_renderables(std::move(renderables)); log::log(MSG(dbg) << "Created render pass"); } @@ -29,12 +29,12 @@ void RenderPass::set_target(const std::shared_ptr &target) { this->target = target; } -void RenderPass::set_renderables(std::vector renderables) { +void RenderPass::set_renderables(std::vector &&renderables) { this->clear_renderables(); - this->add_renderables(renderables); + this->add_renderables(std::move(renderables)); } -void RenderPass::add_renderables(std::vector renderables, int64_t priority) { +void RenderPass::add_renderables(std::vector &&renderables, int64_t priority) { size_t renderables_index = 0; size_t layer_index = 0; int64_t current_priority = std::numeric_limits::min(); @@ -48,7 +48,9 @@ void RenderPass::add_renderables(std::vector renderables, int64_t pr layer_index = i; } - this->renderables.insert(this->renderables.begin() + renderables_index, renderables.begin(), renderables.end()); + this->renderables.insert(this->renderables.begin() + renderables_index, + std::make_move_iterator(renderables.begin()), + std::make_move_iterator(renderables.end())); if (current_priority != priority) { layer_index += 1; @@ -58,7 +60,7 @@ void RenderPass::add_renderables(std::vector renderables, int64_t pr this->layers.at(layer_index).length += renderables.size(); } -void RenderPass::add_renderables(Renderable renderable, int64_t priority) { +void RenderPass::add_renderables(Renderable &&renderable, int64_t priority) { this->add_renderables(std::vector{std::move(renderable)}, priority); } diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h index d424c413c7..33526e7f7b 100644 --- a/libopenage/renderer/render_pass.h +++ b/libopenage/renderer/render_pass.h @@ -66,7 +66,7 @@ class RenderPass { * * @param renderables New renderables. */ - void set_renderables(std::vector renderables); + void set_renderables(std::vector &&renderables); /** * Append renderables to the render pass with a given priority. @@ -74,7 +74,7 @@ class RenderPass { * @param renderables New renderables. * @param priority Priority of the renderables. Layers with higher priority are drawn first. */ - void add_renderables(std::vector renderables, + void add_renderables(std::vector &&renderables, int64_t priority = LAYER_PRIORITY_MAX); /** @@ -83,7 +83,7 @@ class RenderPass { * @param renderable New renderable. * @param priority Priority of the renderable. Layers with higher priority are drawn first. */ - void add_renderables(Renderable renderable, + void add_renderables(Renderable &&renderable, int64_t priority = LAYER_PRIORITY_MAX); /** @@ -118,7 +118,7 @@ class RenderPass { * @param renderables The renderables to draw. * @param target Render target to write to. */ - RenderPass(std::vector renderables, const std::shared_ptr &target); + RenderPass(std::vector &&renderables, const std::shared_ptr &target); /** * The renderables to draw. diff --git a/libopenage/renderer/stages/hud/render_stage.cpp b/libopenage/renderer/stages/hud/render_stage.cpp index ad115bd2f6..a55751ec5f 100644 --- a/libopenage/renderer/stages/hud/render_stage.cpp +++ b/libopenage/renderer/stages/hud/render_stage.cpp @@ -80,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 a55419e0e1..3d07e4ed3e 100644 --- a/libopenage/renderer/stages/screen/render_stage.cpp +++ b/libopenage/renderer/stages/screen/render_stage.cpp @@ -89,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/terrain/render_stage.cpp b/libopenage/renderer/stages/terrain/render_stage.cpp index 332b80dfd9..740fe260f6 100644 --- a/libopenage/renderer/stages/terrain/render_stage.cpp +++ b/libopenage/renderer/stages/terrain/render_stage.cpp @@ -77,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/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index 083b2461db..05827eb645 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -84,7 +84,7 @@ void WorldRenderStage::update() { true, true, }; - this->render_pass->add_renderables(display_obj, layer_pos); + this->render_pass->add_renderables(std::move(display_obj), layer_pos); transform_unifs.push_back(layer_unifs); } From fb93335cce9c21f0f4b7cdd1949e2bf9086a8400 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 8 May 2024 17:28:36 +0200 Subject: [PATCH 24/28] renderer: Add more comments to explain render pass logic. --- libopenage/renderer/render_pass.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp index 931f5519ac..11bad2493e 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -35,12 +35,21 @@ void RenderPass::set_renderables(std::vector &&renderables) { } void RenderPass::add_renderables(std::vector &&renderables, int64_t priority) { + // Insertion index for the renderables size_t renderables_index = 0; + + // 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 = std::numeric_limits::min(); + + // 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; } renderables_index += layer.length; @@ -53,10 +62,12 @@ void RenderPass::add_renderables(std::vector &&renderables, int64_t std::make_move_iterator(renderables.end())); if (current_priority != priority) { + // Lazily add a new layer with the desired priority layer_index += 1; this->add_layer(layer_index, priority); } + // Update the length of the layer with the number of added renderables this->layers.at(layer_index).length += renderables.size(); } @@ -89,10 +100,10 @@ void RenderPass::add_layer(size_t index, int64_t priority) { } void RenderPass::clear_renderables() { - // Erase the renderables + // Erase all renderables this->renderables.clear(); - // Keep layers, but reset the length of each layer + // Keep layer definitions, but reset the length of each layer to 0 for (auto &layer : this->layers) { layer.length = 0; } From 82356fd28d8834079ade65d832df93f58a8c2104 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 8 May 2024 19:26:45 +0200 Subject: [PATCH 25/28] renderer: Revere ordering of layers in pass. --- libopenage/renderer/opengl/renderer.cpp | 96 ++++++++++++++++++------- libopenage/renderer/render_pass.cpp | 18 +++-- libopenage/renderer/render_pass.h | 10 +-- 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index 41496db679..a7546cc404 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 ); @@ -179,34 +179,80 @@ void GlRenderer::render(const std::shared_ptr &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); - } + // render all objects in the pass + auto &layers = gl_pass->get_layers(); + auto &renderables = gl_pass->get_renderables(); - if (obj.depth_test) { - glEnable(GL_DEPTH_TEST); - } - else { - glDisable(GL_DEPTH_TEST); + // Draw by layers + size_t offset = 0; + size_t next_offset = 0; + for (auto const &layer : layers) { + if (layer.clear_depth) { + glClear(GL_DEPTH_BUFFER_BIT); } - 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(); + next_offset = offset + layer.length; + for (size_t i = offset; i < next_offset; i++) { + const auto &obj = renderables[i]; + 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(); + } } + + offset = next_offset; } + + // 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); + // } + + // 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(); + // } + // } } } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp index 11bad2493e..18e15b8c68 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -13,7 +13,7 @@ RenderPass::RenderPass(std::vector &&renderables, target{target}, layers{} { // Add a default layer with the lowest priority - this->add_layer(0, std::numeric_limits::max()); + this->add_layer(0, LAYER_PRIORITY_MAX); // Add the renderables to the pass this->add_renderables(std::move(renderables)); @@ -42,12 +42,21 @@ void RenderPass::add_renderables(std::vector &&renderables, int64_t size_t layer_index = 0; // Priority of the last observed layer - int64_t current_priority = std::numeric_limits::min(); + int64_t current_priority = LAYER_PRIORITY_MAX; + + if (priority == LAYER_PRIORITY_MAX) { + // Add the renderables to the last (default) layer + this->renderables.insert(this->renderables.end(), + std::make_move_iterator(renderables.begin()), + std::make_move_iterator(renderables.end())); + this->layers.back().length += renderables.size(); + return; + } // 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) { + if (layer.priority > priority) { // Priority of the next layer is lower than the desired priority // Insert the renderables directly before this layer break; @@ -63,7 +72,6 @@ void RenderPass::add_renderables(std::vector &&renderables, int64_t if (current_priority != priority) { // Lazily add a new layer with the desired priority - layer_index += 1; this->add_layer(layer_index, priority); } @@ -78,7 +86,7 @@ void RenderPass::add_renderables(Renderable &&renderable, int64_t priority) { void RenderPass::add_layer(int64_t priority) { size_t layer_index = 0; for (const auto &layer : this->layers) { - if (layer.priority < priority) { + if (layer.priority > priority) { break; } layer_index++; diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h index 33526e7f7b..95eb4a47ea 100644 --- a/libopenage/renderer/render_pass.h +++ b/libopenage/renderer/render_pass.h @@ -23,6 +23,8 @@ struct Layer { int64_t priority; /// Number of renderables in this slice. size_t length; + /// Whether to clear the depth buffer before rendering this layer. + bool clear_depth = true; }; /** @@ -72,7 +74,7 @@ class RenderPass { * 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 first. + * @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); @@ -81,7 +83,7 @@ class RenderPass { * 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 first. + * @param priority Priority of the renderable. Layers with higher priority are drawn later. */ void add_renderables(Renderable &&renderable, int64_t priority = LAYER_PRIORITY_MAX); @@ -123,7 +125,7 @@ class RenderPass { /** * The renderables to draw. * - * Kept sorted by layer priorities (highest to lowest priority). + * Kept sorted by layer priorities (lowest to highest priority). */ std::vector renderables; @@ -147,7 +149,7 @@ class RenderPass { * Layers are slices of the renderables that have the same priority. * They can assign different settings to the renderables in the slice. * - * Sorted from highest to lowest priority. + * Sorted from lowest to highest priority. */ std::vector layers; }; From 5f74b7212051b673b7ba6a53b645bb3747e9b640 Mon Sep 17 00:00:00 2001 From: heinezen Date: Wed, 8 May 2024 23:08:08 +0200 Subject: [PATCH 26/28] renderer: Allow configuring depth of added layers. --- libopenage/renderer/render_pass.cpp | 24 ++++++++++++------------ libopenage/renderer/render_pass.h | 7 ++++--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp index 18e15b8c68..efabcbf445 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -21,6 +21,14 @@ RenderPass::RenderPass(std::vector &&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; } @@ -83,7 +91,7 @@ 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) { +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) { @@ -92,19 +100,11 @@ void RenderPass::add_layer(int64_t priority) { layer_index++; } - this->add_layer(layer_index, priority); -} - -const std::vector &RenderPass::get_renderables() const { - return this->renderables; -} - -const std::vector &RenderPass::get_layers() const { - return this->layers; + this->add_layer(layer_index, priority, clear_depth); } -void RenderPass::add_layer(size_t index, int64_t priority) { - this->layers.insert(this->layers.begin() + index, Layer{priority, 0}); +void RenderPass::add_layer(size_t index, int64_t priority, bool clear_depth) { + this->layers.insert(this->layers.begin() + index, Layer{priority, 0, clear_depth}); } void RenderPass::clear_renderables() { diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h index 95eb4a47ea..56473d9952 100644 --- a/libopenage/renderer/render_pass.h +++ b/libopenage/renderer/render_pass.h @@ -92,9 +92,9 @@ class RenderPass { * Add a new layer to the render pass. * * @param priority Priority of the layer. Layers with higher priority are drawn first. - * @param clear_depth Whether to clear the depth buffer before rendering this layer. + * @param clear_depth If true clears the depth buffer before rendering this layer. */ - void add_layer(int64_t priority); + void add_layer(int64_t priority, bool clear_depth = true); /** * Clear the list of renderables @@ -135,8 +135,9 @@ class RenderPass { * * @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); + void add_layer(size_t index, int64_t priority, bool clear_depth = true); /** * Render target to write to. From ac1be0fb72e374c2aab79ec453b60ee1e2b99862 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 9 May 2024 00:51:32 +0200 Subject: [PATCH 27/28] doc: Document layer usage in level 1 renderer. --- doc/code/renderer/level1.md | 74 +++++++++++++++++++++++++++++-- libopenage/renderer/render_pass.h | 4 ++ 2 files changed, 74 insertions(+), 4 deletions(-) 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/renderer/render_pass.h b/libopenage/renderer/render_pass.h index 56473d9952..393cfd4f6a 100644 --- a/libopenage/renderer/render_pass.h +++ b/libopenage/renderer/render_pass.h @@ -17,6 +17,10 @@ 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 in this slice. From 52663d60c5c3bec6bfad2d34d48df38b0359f686 Mon Sep 17 00:00:00 2001 From: heinezen Date: Thu, 9 May 2024 01:05:10 +0200 Subject: [PATCH 28/28] renderer: Use vector of vectors for storing renderables. --- libopenage/renderer/opengl/renderer.cpp | 46 ++++-------------------- libopenage/renderer/render_pass.cpp | 48 ++++++++++--------------- libopenage/renderer/render_pass.h | 8 ++--- 3 files changed, 28 insertions(+), 74 deletions(-) diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index a7546cc404..48639dc368 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -180,20 +180,19 @@ void GlRenderer::render(const std::shared_ptr &pass) { // GlRenderer::optimize(gl_pass); // render all objects in the pass - auto &layers = gl_pass->get_layers(); - auto &renderables = gl_pass->get_renderables(); + const auto &layers = gl_pass->get_layers(); + const auto &renderables = gl_pass->get_renderables(); // Draw by layers - size_t offset = 0; - size_t next_offset = 0; - for (auto const &layer : layers) { + for (size_t i = 0; i < layers.size(); i++) { + const auto &layer = layers[i]; + const auto &objects = renderables[i]; + if (layer.clear_depth) { glClear(GL_DEPTH_BUFFER_BIT); } - next_offset = offset + layer.length; - for (size_t i = offset; i < next_offset; i++) { - const auto &obj = renderables[i]; + for (auto const &obj : objects) { if (obj.alpha_blending) { glEnable(GL_BLEND); } @@ -221,38 +220,7 @@ void GlRenderer::render(const std::shared_ptr &pass) { geom->draw(); } } - - offset = next_offset; } - - // 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); - // } - - // 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(); - // } - // } } } // namespace openage::renderer::opengl diff --git a/libopenage/renderer/render_pass.cpp b/libopenage/renderer/render_pass.cpp index efabcbf445..c925fc0a89 100644 --- a/libopenage/renderer/render_pass.cpp +++ b/libopenage/renderer/render_pass.cpp @@ -21,7 +21,7 @@ RenderPass::RenderPass(std::vector &&renderables, log::log(MSG(dbg) << "Created render pass"); } -const std::vector &RenderPass::get_renderables() const { +const std::vector> &RenderPass::get_renderables() const { return this->renderables; } @@ -43,8 +43,13 @@ void RenderPass::set_renderables(std::vector &&renderables) { } void RenderPass::add_renderables(std::vector &&renderables, int64_t priority) { - // Insertion index for the renderables - size_t renderables_index = 0; + 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; @@ -52,15 +57,6 @@ void RenderPass::add_renderables(std::vector &&renderables, int64_t // Priority of the last observed layer int64_t current_priority = LAYER_PRIORITY_MAX; - if (priority == LAYER_PRIORITY_MAX) { - // Add the renderables to the last (default) layer - this->renderables.insert(this->renderables.end(), - std::make_move_iterator(renderables.begin()), - std::make_move_iterator(renderables.end())); - this->layers.back().length += renderables.size(); - return; - } - // 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); @@ -69,22 +65,18 @@ void RenderPass::add_renderables(std::vector &&renderables, int64_t // Insert the renderables directly before this layer break; } - renderables_index += layer.length; current_priority = layer.priority; layer_index = i; } - this->renderables.insert(this->renderables.begin() + renderables_index, - std::make_move_iterator(renderables.begin()), - std::make_move_iterator(renderables.end())); - if (current_priority != priority) { // Lazily add a new layer with the desired priority this->add_layer(layer_index, priority); } - // Update the length of the layer with the number of added renderables - this->layers.at(layer_index).length += renderables.size(); + 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) { @@ -104,26 +96,22 @@ void RenderPass::add_layer(int64_t priority, bool clear_depth) { } void RenderPass::add_layer(size_t index, int64_t priority, bool clear_depth) { - this->layers.insert(this->layers.begin() + index, Layer{priority, 0, 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() { - // Erase all renderables - this->renderables.clear(); - // Keep layer definitions, but reset the length of each layer to 0 - for (auto &layer : this->layers) { - layer.length = 0; + for (size_t i = 0; i < this->layers.size(); i++) { + this->renderables[i].clear(); } } void RenderPass::sort(const compare_func &compare) { - size_t offset = 0; - for (auto &layer : this->layers) { - std::stable_sort(this->renderables.begin() + offset, - this->renderables.begin() + offset + layer.length, + for (size_t i = 0; i < this->layers.size(); i++) { + std::stable_sort(this->renderables[i].begin(), + this->renderables[i].end(), compare); - offset += layer.length; } } diff --git a/libopenage/renderer/render_pass.h b/libopenage/renderer/render_pass.h index 393cfd4f6a..43c99bd659 100644 --- a/libopenage/renderer/render_pass.h +++ b/libopenage/renderer/render_pass.h @@ -23,10 +23,8 @@ class RenderTarget; * // not need layers at all. */ struct Layer { - /// Priority of the renderables in this slice. + /// Priority of the renderables. int64_t priority; - /// Number of renderables in this slice. - size_t length; /// Whether to clear the depth buffer before rendering this layer. bool clear_depth = true; }; @@ -44,7 +42,7 @@ class RenderPass { * * @return Renderables of the render pass. */ - const std::vector &get_renderables() const; + const std::vector> &get_renderables() const; /** * Get the layers of the render pass. @@ -131,7 +129,7 @@ class RenderPass { * * Kept sorted by layer priorities (lowest to highest priority). */ - std::vector renderables; + std::vector> renderables; private: /**