mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
perf(client): improve chunk rendering performance with snapshots and threaded generation
This commit is contained in:
@@ -51,6 +51,7 @@ public:
|
||||
const std::vector<BlockType>& 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<BlockType>& blocks();
|
||||
ClientWorld& world();
|
||||
unsigned seed() const;
|
||||
const ChunkRenderSnapshot* get_render_snapshot() const;
|
||||
|
||||
private:
|
||||
struct FaceKey {
|
||||
@@ -117,6 +119,9 @@ private:
|
||||
4 - water
|
||||
*/
|
||||
std::vector<VertexData> m_vertex_data;
|
||||
|
||||
ChunkRenderSnapshot m_render_snapshot;
|
||||
|
||||
unsigned m_seed = 0;
|
||||
void clear_dirty();
|
||||
void gen_vertices(const OptionalBlockVectorArray& neighbor_block);
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
void hot_reload();
|
||||
void request_chunk();
|
||||
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;
|
||||
glm::vec3 sunlight_dir() const;
|
||||
void receive_chunk(std::vector<uint8_t> 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<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_vao;
|
||||
|
||||
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;
|
||||
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
|
||||
std::atomic<bool> m_game_running{false};
|
||||
|
||||
@@ -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<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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Cubed/gameplay/packet.hpp"
|
||||
#include "Cubed/tools/math_tools.hpp"
|
||||
|
||||
#include <absl/container/inlined_vector.h>
|
||||
#include <numbers>
|
||||
|
||||
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<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[] = {
|
||||
{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) {
|
||||
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<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) {
|
||||
std::lock_guard lk(m_delete_vbo_mutex);
|
||||
@@ -538,23 +608,49 @@ void ClientWorld::update(float delta_time) {
|
||||
}
|
||||
m_pending_delete_vao.clear();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<ClientChunk>> new_chunks;
|
||||
{
|
||||
std::unique_ptr<ClientChunk> 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();
|
||||
|
||||
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();
|
||||
|
||||
for (auto& pos : chunk_pos_set) {
|
||||
std::shared_ptr<ClientChunk> chunk;
|
||||
{
|
||||
@@ -566,43 +662,10 @@ void ClientWorld::update(float delta_time) {
|
||||
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<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_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<ChunkRenderSnapshot>& ClientWorld::render_snapshots() const {
|
||||
const std::vector<const ChunkRenderSnapshot*>&
|
||||
ClientWorld::render_snapshots() const {
|
||||
return m_render_snapshots;
|
||||
};
|
||||
const std::vector<RemotePlayerRenderData>&
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user