From cb3f78b1f3b05cdcad61d1b7f79bea7c2fd0b576 Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Wed, 1 Jul 2026 12:30:02 +0800 Subject: [PATCH] perf(client): improve chunk rendering performance with snapshots and threaded generation --- include/Cubed/gameplay/client_chunk.hpp | 5 + include/Cubed/gameplay/client_world.hpp | 7 +- src/gameplay/client_chunk.cpp | 30 +++- src/gameplay/client_world.cpp | 176 ++++++++++++++++-------- src/renderer.cpp | 82 ++++++----- 5 files changed, 208 insertions(+), 92 deletions(-) diff --git a/include/Cubed/gameplay/client_chunk.hpp b/include/Cubed/gameplay/client_chunk.hpp index 619631c..bc283cf 100644 --- a/include/Cubed/gameplay/client_chunk.hpp +++ b/include/Cubed/gameplay/client_chunk.hpp @@ -51,6 +51,7 @@ public: const std::vector& get_chunk_blocks() const; void receive_chunk(const ChunkDataRsp& data); void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block); + // Can only be called on the render thread void upload_to_gpu(); GLuint get_normal_vao() const; @@ -82,6 +83,7 @@ public: std::vector& blocks(); ClientWorld& world(); unsigned seed() const; + const ChunkRenderSnapshot* get_render_snapshot() const; private: struct FaceKey { @@ -117,6 +119,9 @@ private: 4 - water */ std::vector m_vertex_data; + + ChunkRenderSnapshot m_render_snapshot; + unsigned m_seed = 0; void clear_dirty(); void gen_vertices(const OptionalBlockVectorArray& neighbor_block); diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index d26dc81..efcf8f2 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -64,7 +64,7 @@ public: void hot_reload(); void request_chunk(); std::vector& planes(); - const std::vector& render_snapshots() const; + const std::vector& render_snapshots() const; const std::vector& render_player_data() const; glm::vec3 sunlight_dir() const; void receive_chunk(std::vector data, PacketHeader header); @@ -90,7 +90,7 @@ private: using chunk_acc = ChunkHashMap::accessor; using chunk_cacc = ChunkHashMap::const_accessor; static constexpr int WORLD_EXIT_TIMEOUT = 200; - + static constexpr int MAX_UPLOAD_CHUNK_SUM = 16; ClientPlayer m_player; OtherPlayerHashMap m_other_players; ChunkHashMap m_chunks; @@ -102,12 +102,13 @@ private: std::mutex m_other_players_mutex; tbb::concurrent_queue> m_pending_upload_queue; + tbb::concurrent_queue m_dirty_chunk_queue; std::vector m_pending_delete_vbo; std::vector m_pending_delete_vao; std::deque m_dirty_queue; - std::vector m_render_snapshots; + std::vector m_render_snapshots; std::vector m_render_player_data; tbb::concurrent_unordered_map m_timers; std::atomic m_game_running{false}; diff --git a/src/gameplay/client_chunk.cpp b/src/gameplay/client_chunk.cpp index 58ff06b..79ad810 100644 --- a/src/gameplay/client_chunk.cpp +++ b/src/gameplay/client_chunk.cpp @@ -233,7 +233,9 @@ size_t ClientChunk::get_water_vertices_sum() const { void ClientChunk::upload_to_gpu() { - ASSERT(is_need_upload()); + if (!is_need_upload()) { + return; + } std::lock_guard lk(m_vertexs_data_mutex); @@ -243,6 +245,27 @@ void ClientChunk::upload_to_gpu() { // after fininshed it, can use clear_dirty(); + + m_render_snapshot = { + get_normal_vao(), + get_normal_vertices_sum(), + get_cross_vao(), + get_cross_vertices_sum(), + get_normal_discard_vao(), + get_normal_discard_vertices_sum(), + get_normal_blend_vao(), + get_normal_blend_vertices_sum(), + get_water_vao(), + get_water_vertices_sum(), + glm::vec3(static_cast(m_chunk_pos.x * CHUNK_SIZE) + + static_cast(CHUNK_SIZE / 2), + static_cast(WORLD_SIZE_Y / 2), + static_cast(m_chunk_pos.z * CHUNK_SIZE) + + static_cast(CHUNK_SIZE / 2)), + glm::vec3(static_cast(CHUNK_SIZE / 2), + static_cast(WORLD_SIZE_Y / 2), + static_cast(CHUNK_SIZE / 2))}; + m_need_upload = false; } @@ -258,7 +281,6 @@ void ClientChunk::need_upload() { m_need_upload = true; } void ClientChunk::set_chunk_block(int index, unsigned id) { m_blocks[index] = id; - mark_dirty(); } ChunkPos ClientChunk::chunk_pos() const { return m_chunk_pos; } @@ -276,6 +298,10 @@ unsigned ClientChunk::seed() const { return m_seed; } +const ChunkRenderSnapshot* ClientChunk::get_render_snapshot() const { + return &m_render_snapshot; +} + void ClientChunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) { // SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256 diff --git a/src/gameplay/client_world.cpp b/src/gameplay/client_world.cpp index ff75314..4f62815 100644 --- a/src/gameplay/client_world.cpp +++ b/src/gameplay/client_world.cpp @@ -5,6 +5,7 @@ #include "Cubed/gameplay/packet.hpp" #include "Cubed/tools/math_tools.hpp" +#include #include using namespace std::chrono; @@ -149,10 +150,11 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) { world_z = block_pos.z; auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z); + ChunkPos pos{chunk_x, chunk_z}; { chunk_acc acc; - if (!m_chunks.find(acc, ChunkPos{chunk_x, chunk_z})) { + if (!m_chunks.find(acc, pos)) { return; } @@ -164,10 +166,44 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) { } acc->second->set_chunk_block(ClientChunk::index(x, y, z), id); + acc->second->mark_dirty(); } + auto pool = m_thread_pool.load(); + + pool->enqueue([this, pos]() { + std::shared_ptr chunk; + + { + chunk_acc acc; + if (m_chunks.find(acc, pos)) { + chunk = acc->second; + } + } + + if (!chunk) { + return; + } + + OptionalBlockVectorArray neighbor_block; + for (int i = 0; i < 4; i++) { + chunk_cacc cacc; + if (m_chunks.find(cacc, pos + CHUNK_DIR[i])) { + neighbor_block[i] = (cacc->second->get_chunk_blocks()); + } else { + neighbor_block[i] = std::nullopt; + } + } + + chunk->gen_vertex_data(neighbor_block); + m_dirty_chunk_queue.emplace(pos); + }); + static const glm::ivec3 NEIGHBOR_DIRS[] = { {1, 0, 0}, {-1, 0, 0}, {0, 0, -1}, {0, 0, 1}}; + static constexpr int NPOS_SUM = sizeof(NEIGHBOR_DIRS); + + absl::InlinedVector nposes; for (const auto& dir : NEIGHBOR_DIRS) { glm::ivec3 neighbor = block_pos + dir; @@ -176,10 +212,44 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) { { chunk_acc acc; if (m_chunks.find(acc, {cx, cz})) { - acc->second->mark_dirty(); + if (acc->second->is_dirty()) { + continue; + } + nposes.emplace_back(acc->first); } } } + + for (auto& npos : nposes) { + pool->enqueue([this, npos]() { + std::shared_ptr chunk; + + { + chunk_acc acc; + if (m_chunks.find(acc, npos)) { + chunk = acc->second; + } + } + + if (!chunk) { + return; + } + + OptionalBlockVectorArray neighbor_block; + for (int i = 0; i < 4; i++) { + chunk_cacc cacc; + if (m_chunks.find(cacc, npos + CHUNK_DIR[i])) { + neighbor_block[i] = (cacc->second->get_chunk_blocks()); + } else { + neighbor_block[i] = std::nullopt; + } + } + + chunk->gen_vertex_data(neighbor_block); + + m_dirty_chunk_queue.emplace(npos); + }); + } } void ClientWorld::push_delete_vbo(GLuint vbo) { std::lock_guard lk(m_delete_vbo_mutex); @@ -538,71 +608,64 @@ void ClientWorld::update(float delta_time) { } m_pending_delete_vao.clear(); } + std::vector> new_chunks; { std::unique_ptr chunk; + int sum = 0; while (m_pending_upload_queue.try_pop(chunk)) { new_chunks.emplace_back(std::move(chunk)); + ++sum; + if (sum >= MAX_UPLOAD_CHUNK_SUM) { + break; // Limit the maximum number of uploads per frame to + // improve frame rate performance + } } } + for (auto& c : new_chunks) { c->upload_to_gpu(); } - { - for (auto& c : new_chunks) { - m_chunks.emplace(c->get_chunk_pos(), std::move(c)); - } - m_render_snapshots.clear(); - auto chunk_pos_set = m_player.get_chunk_pos_set(); - for (auto& pos : chunk_pos_set) { - std::shared_ptr chunk; - { - chunk_acc acc; - if (m_chunks.find(acc, pos)) { - chunk = acc->second; - } - } - if (!chunk) { - continue; - } - if (chunk->is_dirty()) { - // the curial fator influence - OptionalBlockVectorArray neighbor_block; - for (int i = 0; i < 4; i++) { - chunk_cacc cacc; - if (m_chunks.find(cacc, pos + CHUNK_DIR[i])) { - neighbor_block[i] = (cacc->second->get_chunk_blocks()); - } else { - neighbor_block[i] = std::nullopt; - } - } - chunk->gen_vertex_data(neighbor_block); - chunk->upload_to_gpu(); - } - if (!chunk->is_dirty()) { - if (chunk->is_need_upload()) { - chunk->upload_to_gpu(); - } - m_render_snapshots.push_back( - {chunk->get_normal_vao(), chunk->get_normal_vertices_sum(), - chunk->get_cross_vao(), chunk->get_cross_vertices_sum(), - chunk->get_normal_discard_vao(), - chunk->get_normal_discard_vertices_sum(), - chunk->get_normal_blend_vao(), - chunk->get_normal_blend_vertices_sum(), - chunk->get_water_vao(), chunk->get_water_vertices_sum(), - glm::vec3(static_cast(pos.x * CHUNK_SIZE) + - static_cast(CHUNK_SIZE / 2), - static_cast(WORLD_SIZE_Y / 2), - static_cast(pos.z * CHUNK_SIZE) + - static_cast(CHUNK_SIZE / 2)), - glm::vec3(static_cast(CHUNK_SIZE / 2), - static_cast(WORLD_SIZE_Y / 2), - static_cast(CHUNK_SIZE / 2))}); - } - } + for (auto& c : new_chunks) { + m_chunks.emplace(c->get_chunk_pos(), std::move(c)); } + m_render_snapshots.clear(); + + ChunkPos pos; + + while (m_dirty_chunk_queue.try_pop(pos)) { + std::shared_ptr chunk; + { + chunk_acc acc; + if (m_chunks.find(acc, pos)) { + chunk = acc->second; + } + } + if (!chunk) { + continue; + } + + chunk->upload_to_gpu(); + } + + auto chunk_pos_set = m_player.get_chunk_pos_set(); + + for (auto& pos : chunk_pos_set) { + std::shared_ptr chunk; + { + chunk_acc acc; + if (m_chunks.find(acc, pos)) { + chunk = acc->second; + } + } + if (!chunk) { + continue; + } + + m_render_snapshots.push_back(chunk->get_render_snapshot()); + } + m_render_player_data.clear(); { std::lock_guard lock(m_other_players_mutex); @@ -649,7 +712,8 @@ void ClientWorld::rendering_distance(int rendering_distance) { int ClientWorld::get_chunk_task_id() const { return m_chunk_task_id.load(); } -const std::vector& ClientWorld::render_snapshots() const { +const std::vector& +ClientWorld::render_snapshots() const { return m_render_snapshots; }; const std::vector& diff --git a/src/renderer.cpp b/src/renderer.cpp index 26dffd1..4cfaf09 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -676,37 +676,42 @@ void Renderer::render_world() { glActiveTexture(GL_TEXTURE1); glEnable(GL_DEPTH_TEST); for (const auto& snapshot : m_render_snapshots) { + if (!snapshot) { + continue; + } glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); - glBindVertexArray(snapshot.normal_vao); + glBindVertexArray(snapshot->normal_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, snapshot->normal_vertices_count); } // cross_plane and discard for (const auto& snapshot : m_render_snapshots) { - + if (!snapshot) { + continue; + } glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z}; - if (snapshot.cross_vertices_count != 0) { - glm::vec2 center_xz{snapshot.center.x, snapshot.center.z}; + if (snapshot->cross_vertices_count != 0) { + glm::vec2 center_xz{snapshot->center.x, snapshot->center.z}; float dist2d = glm::distance(camera_pos_xz, center_xz); if (dist2d <= CROSS_PLANE_DISTANCE * 16) { glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_cross_plane_array()); - glBindVertexArray(snapshot.cross_vao); + glBindVertexArray(snapshot->cross_vao); glDrawArrays(GL_TRIANGLES, 0, - snapshot.cross_vertices_count); + snapshot->cross_vertices_count); } } - if (snapshot.normal_discard_vertices_count != 0) { + if (snapshot->normal_discard_vertices_count != 0) { glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); - glBindVertexArray(snapshot.normal_discard_vao); + glBindVertexArray(snapshot->normal_discard_vao); glDrawArrays(GL_TRIANGLES, 0, - snapshot.normal_discard_vertices_count); + snapshot->normal_discard_vertices_count); } } } @@ -762,28 +767,33 @@ void Renderer::render_world() { glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_pbr_texture()); normal_block_shader.set_loc("enablePBR", m_pbr); for (const auto& snapshot : m_render_snapshots) { - - if (Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { - glBindVertexArray(snapshot.normal_vao); + glBindVertexArray(snapshot->normal_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, snapshot->normal_vertices_count); rendered_sum++; } } // discard for (const auto& snapshot : m_render_snapshots) { - if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { continue; } - if (snapshot.normal_discard_vertices_count != 0) { - glBindVertexArray(snapshot.normal_discard_vao); + if (snapshot->normal_discard_vertices_count != 0) { + glBindVertexArray(snapshot->normal_discard_vao); glDrawArrays(GL_TRIANGLES, 0, - snapshot.normal_discard_vertices_count); + snapshot->normal_discard_vertices_count); } } // cross_plane @@ -792,18 +802,21 @@ void Renderer::render_world() { m_texture_manager.get_cross_plane_array()); normal_block_shader.set_loc("enablePBR", false); for (const auto& snapshot : m_render_snapshots) { - if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { continue; } glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z}; - if (snapshot.cross_vertices_count != 0) { - glm::vec2 center_xz{snapshot.center.x, snapshot.center.z}; + if (snapshot->cross_vertices_count != 0) { + glm::vec2 center_xz{snapshot->center.x, snapshot->center.z}; float dist2d = glm::distance(camera_pos_xz, center_xz); if (dist2d <= CROSS_PLANE_DISTANCE * 16) { - glBindVertexArray(snapshot.cross_vao); + glBindVertexArray(snapshot->cross_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.cross_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, snapshot->cross_vertices_count); } } } @@ -853,16 +866,20 @@ void Renderer::render_world() { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); for (const auto& snapshot : m_render_snapshots) { - if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { continue; } - if (snapshot.normal_blend_vertices_count != 0) { + if (snapshot->normal_blend_vertices_count != 0) { - glBindVertexArray(snapshot.normal_blend_vao); + glBindVertexArray(snapshot->normal_blend_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_blend_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, + snapshot->normal_blend_vertices_count); } } @@ -902,16 +919,19 @@ void Renderer::render_world() { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); for (const auto& snapshot : m_render_snapshots) { - if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, + if (!snapshot) { + continue; + } + if (!Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents, m_planes)) { continue; } - if (snapshot.water_vertices_count != 0) { + if (snapshot->water_vertices_count != 0) { - glBindVertexArray(snapshot.water_vao); + glBindVertexArray(snapshot->water_vao); - glDrawArrays(GL_TRIANGLES, 0, snapshot.water_vertices_count); + glDrawArrays(GL_TRIANGLES, 0, snapshot->water_vertices_count); } }