perf(client): improve chunk rendering performance with snapshots and threaded generation

This commit is contained in:
2026-07-01 12:30:02 +08:00
parent 810ce672da
commit cb3f78b1f3
5 changed files with 208 additions and 92 deletions

View File

@@ -51,6 +51,7 @@ public:
const std::vector<BlockType>& get_chunk_blocks() const; const std::vector<BlockType>& get_chunk_blocks() const;
void receive_chunk(const ChunkDataRsp& data); void receive_chunk(const ChunkDataRsp& data);
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block); void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
// Can only be called on the render thread
void upload_to_gpu(); void upload_to_gpu();
GLuint get_normal_vao() const; GLuint get_normal_vao() const;
@@ -82,6 +83,7 @@ public:
std::vector<BlockType>& blocks(); std::vector<BlockType>& blocks();
ClientWorld& world(); ClientWorld& world();
unsigned seed() const; unsigned seed() const;
const ChunkRenderSnapshot* get_render_snapshot() const;
private: private:
struct FaceKey { struct FaceKey {
@@ -117,6 +119,9 @@ private:
4 - water 4 - water
*/ */
std::vector<VertexData> m_vertex_data; std::vector<VertexData> m_vertex_data;
ChunkRenderSnapshot m_render_snapshot;
unsigned m_seed = 0; unsigned m_seed = 0;
void clear_dirty(); void clear_dirty();
void gen_vertices(const OptionalBlockVectorArray& neighbor_block); void gen_vertices(const OptionalBlockVectorArray& neighbor_block);

View File

@@ -64,7 +64,7 @@ public:
void hot_reload(); void hot_reload();
void request_chunk(); void request_chunk();
std::vector<glm::vec4>& planes(); std::vector<glm::vec4>& planes();
const std::vector<ChunkRenderSnapshot>& render_snapshots() const; const std::vector<const ChunkRenderSnapshot*>& render_snapshots() const;
const std::vector<RemotePlayerRenderData>& render_player_data() const; const std::vector<RemotePlayerRenderData>& render_player_data() const;
glm::vec3 sunlight_dir() const; glm::vec3 sunlight_dir() const;
void receive_chunk(std::vector<uint8_t> data, PacketHeader header); void receive_chunk(std::vector<uint8_t> data, PacketHeader header);
@@ -90,7 +90,7 @@ private:
using chunk_acc = ChunkHashMap::accessor; using chunk_acc = ChunkHashMap::accessor;
using chunk_cacc = ChunkHashMap::const_accessor; using chunk_cacc = ChunkHashMap::const_accessor;
static constexpr int WORLD_EXIT_TIMEOUT = 200; static constexpr int WORLD_EXIT_TIMEOUT = 200;
static constexpr int MAX_UPLOAD_CHUNK_SUM = 16;
ClientPlayer m_player; ClientPlayer m_player;
OtherPlayerHashMap m_other_players; OtherPlayerHashMap m_other_players;
ChunkHashMap m_chunks; ChunkHashMap m_chunks;
@@ -102,12 +102,13 @@ private:
std::mutex m_other_players_mutex; std::mutex m_other_players_mutex;
tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue; tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue;
tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue;
std::vector<GLuint> m_pending_delete_vbo; std::vector<GLuint> m_pending_delete_vbo;
std::vector<GLuint> m_pending_delete_vao; std::vector<GLuint> m_pending_delete_vao;
std::deque<ChunkPos> m_dirty_queue; std::deque<ChunkPos> m_dirty_queue;
std::vector<ChunkRenderSnapshot> m_render_snapshots; std::vector<const ChunkRenderSnapshot*> m_render_snapshots;
std::vector<RemotePlayerRenderData> m_render_player_data; std::vector<RemotePlayerRenderData> m_render_player_data;
tbb::concurrent_unordered_map<std::string, Timer> m_timers; tbb::concurrent_unordered_map<std::string, Timer> m_timers;
std::atomic<bool> m_game_running{false}; std::atomic<bool> m_game_running{false};

View File

@@ -233,7 +233,9 @@ size_t ClientChunk::get_water_vertices_sum() const {
void ClientChunk::upload_to_gpu() { void ClientChunk::upload_to_gpu() {
ASSERT(is_need_upload()); if (!is_need_upload()) {
return;
}
std::lock_guard lk(m_vertexs_data_mutex); std::lock_guard lk(m_vertexs_data_mutex);
@@ -243,6 +245,27 @@ void ClientChunk::upload_to_gpu() {
// after fininshed it, can use // after fininshed it, can use
clear_dirty(); 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<float>(m_chunk_pos.x * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(m_chunk_pos.z * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2)),
glm::vec3(static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(CHUNK_SIZE / 2))};
m_need_upload = false; 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) { void ClientChunk::set_chunk_block(int index, unsigned id) {
m_blocks[index] = id; m_blocks[index] = id;
mark_dirty();
} }
ChunkPos ClientChunk::chunk_pos() const { return m_chunk_pos; } ChunkPos ClientChunk::chunk_pos() const { return m_chunk_pos; }
@@ -276,6 +298,10 @@ unsigned ClientChunk::seed() const {
return m_seed; return m_seed;
} }
const ChunkRenderSnapshot* ClientChunk::get_render_snapshot() const {
return &m_render_snapshot;
}
void ClientChunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) { void ClientChunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
// SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256 // SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256

View File

@@ -5,6 +5,7 @@
#include "Cubed/gameplay/packet.hpp" #include "Cubed/gameplay/packet.hpp"
#include "Cubed/tools/math_tools.hpp" #include "Cubed/tools/math_tools.hpp"
#include <absl/container/inlined_vector.h>
#include <numbers> #include <numbers>
using namespace std::chrono; using namespace std::chrono;
@@ -149,10 +150,11 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
world_z = block_pos.z; world_z = block_pos.z;
auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z); auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z);
ChunkPos pos{chunk_x, chunk_z};
{ {
chunk_acc acc; chunk_acc acc;
if (!m_chunks.find(acc, ChunkPos{chunk_x, chunk_z})) { if (!m_chunks.find(acc, pos)) {
return; 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->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<ClientChunk> 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[] = { static const glm::ivec3 NEIGHBOR_DIRS[] = {
{1, 0, 0}, {-1, 0, 0}, {0, 0, -1}, {0, 0, 1}}; {1, 0, 0}, {-1, 0, 0}, {0, 0, -1}, {0, 0, 1}};
static constexpr int NPOS_SUM = sizeof(NEIGHBOR_DIRS);
absl::InlinedVector<ChunkPos, NPOS_SUM> nposes;
for (const auto& dir : NEIGHBOR_DIRS) { for (const auto& dir : NEIGHBOR_DIRS) {
glm::ivec3 neighbor = block_pos + dir; glm::ivec3 neighbor = block_pos + dir;
@@ -176,10 +212,44 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
{ {
chunk_acc acc; chunk_acc acc;
if (m_chunks.find(acc, {cx, cz})) { 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<ClientChunk> 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) { void ClientWorld::push_delete_vbo(GLuint vbo) {
std::lock_guard lk(m_delete_vbo_mutex); std::lock_guard lk(m_delete_vbo_mutex);
@@ -538,23 +608,49 @@ void ClientWorld::update(float delta_time) {
} }
m_pending_delete_vao.clear(); m_pending_delete_vao.clear();
} }
std::vector<std::unique_ptr<ClientChunk>> new_chunks; std::vector<std::unique_ptr<ClientChunk>> new_chunks;
{ {
std::unique_ptr<ClientChunk> chunk; std::unique_ptr<ClientChunk> chunk;
int sum = 0;
while (m_pending_upload_queue.try_pop(chunk)) { while (m_pending_upload_queue.try_pop(chunk)) {
new_chunks.emplace_back(std::move(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) { for (auto& c : new_chunks) {
c->upload_to_gpu(); c->upload_to_gpu();
} }
{
for (auto& c : new_chunks) { for (auto& c : new_chunks) {
m_chunks.emplace(c->get_chunk_pos(), std::move(c)); m_chunks.emplace(c->get_chunk_pos(), std::move(c));
} }
m_render_snapshots.clear(); m_render_snapshots.clear();
ChunkPos pos;
while (m_dirty_chunk_queue.try_pop(pos)) {
std::shared_ptr<ClientChunk> 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(); auto chunk_pos_set = m_player.get_chunk_pos_set();
for (auto& pos : chunk_pos_set) { for (auto& pos : chunk_pos_set) {
std::shared_ptr<ClientChunk> chunk; std::shared_ptr<ClientChunk> chunk;
{ {
@@ -566,43 +662,10 @@ void ClientWorld::update(float delta_time) {
if (!chunk) { if (!chunk) {
continue; continue;
} }
if (chunk->is_dirty()) {
// the curial fator influence m_render_snapshots.push_back(chunk->get_render_snapshot());
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<float>(pos.x * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(pos.z * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2)),
glm::vec3(static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(CHUNK_SIZE / 2))});
}
}
} }
m_render_player_data.clear(); m_render_player_data.clear();
{ {
std::lock_guard lock(m_other_players_mutex); 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(); } int ClientWorld::get_chunk_task_id() const { return m_chunk_task_id.load(); }
const std::vector<ChunkRenderSnapshot>& ClientWorld::render_snapshots() const { const std::vector<const ChunkRenderSnapshot*>&
ClientWorld::render_snapshots() const {
return m_render_snapshots; return m_render_snapshots;
}; };
const std::vector<RemotePlayerRenderData>& const std::vector<RemotePlayerRenderData>&

View File

@@ -676,37 +676,42 @@ void Renderer::render_world() {
glActiveTexture(GL_TEXTURE1); glActiveTexture(GL_TEXTURE1);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
for (const auto& snapshot : m_render_snapshots) { for (const auto& snapshot : m_render_snapshots) {
if (!snapshot) {
continue;
}
glBindTexture(GL_TEXTURE_2D_ARRAY, glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_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 // cross_plane and discard
for (const auto& snapshot : m_render_snapshots) { for (const auto& snapshot : m_render_snapshots) {
if (!snapshot) {
continue;
}
glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z}; glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z};
if (snapshot.cross_vertices_count != 0) { if (snapshot->cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot.center.x, snapshot.center.z}; glm::vec2 center_xz{snapshot->center.x, snapshot->center.z};
float dist2d = glm::distance(camera_pos_xz, center_xz); float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) { if (dist2d <= CROSS_PLANE_DISTANCE * 16) {
glBindTexture(GL_TEXTURE_2D_ARRAY, glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_cross_plane_array()); m_texture_manager.get_cross_plane_array());
glBindVertexArray(snapshot.cross_vao); glBindVertexArray(snapshot->cross_vao);
glDrawArrays(GL_TRIANGLES, 0, 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, glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array()); m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_discard_vao); glBindVertexArray(snapshot->normal_discard_vao);
glDrawArrays(GL_TRIANGLES, 0, 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()); glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_pbr_texture());
normal_block_shader.set_loc("enablePBR", m_pbr); normal_block_shader.set_loc("enablePBR", m_pbr);
for (const auto& snapshot : m_render_snapshots) { for (const auto& snapshot : m_render_snapshots) {
if (!snapshot) {
if (Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents, continue;
}
if (Math::is_aabb_in_frustum(snapshot->center, snapshot->half_extents,
m_planes)) { 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++; rendered_sum++;
} }
} }
// discard // discard
for (const auto& snapshot : m_render_snapshots) { 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)) { m_planes)) {
continue; continue;
} }
if (snapshot.normal_discard_vertices_count != 0) { if (snapshot->normal_discard_vertices_count != 0) {
glBindVertexArray(snapshot.normal_discard_vao); glBindVertexArray(snapshot->normal_discard_vao);
glDrawArrays(GL_TRIANGLES, 0, glDrawArrays(GL_TRIANGLES, 0,
snapshot.normal_discard_vertices_count); snapshot->normal_discard_vertices_count);
} }
} }
// cross_plane // cross_plane
@@ -792,18 +802,21 @@ void Renderer::render_world() {
m_texture_manager.get_cross_plane_array()); m_texture_manager.get_cross_plane_array());
normal_block_shader.set_loc("enablePBR", false); normal_block_shader.set_loc("enablePBR", false);
for (const auto& snapshot : m_render_snapshots) { 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)) { m_planes)) {
continue; continue;
} }
glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z}; glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z};
if (snapshot.cross_vertices_count != 0) { if (snapshot->cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot.center.x, snapshot.center.z}; glm::vec2 center_xz{snapshot->center.x, snapshot->center.z};
float dist2d = glm::distance(camera_pos_xz, center_xz); float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) { 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); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array());
for (const auto& snapshot : m_render_snapshots) { 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)) { m_planes)) {
continue; 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); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array()); glBindTexture(GL_TEXTURE_2D_ARRAY, m_texture_manager.get_texture_array());
for (const auto& snapshot : m_render_snapshots) { 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)) { m_planes)) {
continue; 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);
} }
} }