refactor(render): build entity instance buffers once per frame

Move instance matrix upload into build_vertices and use a precomputed
instance data map for both shadow and color passes, avoiding duplicate
GPU buffer updates.
This commit is contained in:
2026-08-06 15:11:51 +08:00
parent 2e9df71b31
commit 3534acd90f
4 changed files with 50 additions and 24 deletions

View File

@@ -6,6 +6,8 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <span> #include <span>
#include <unordered_map>
#include <vector>
namespace Cubed { namespace Cubed {
class Renderer; class Renderer;
class Camera; class Camera;
@@ -32,8 +34,9 @@ public:
ModelRender(Renderer& renderer); ModelRender(Renderer& renderer);
void render_instance(ModelID id, std::span<const InstanceData> instances, void render_instance(ModelID id, size_t sum, const Camera& camera,
const Camera& camera, bool shadow); bool shadow);
void build_vertices(ModelID id, std::span<const InstanceData> instances);
private: private:
Renderer& m_renderer; Renderer& m_renderer;

View File

@@ -1,10 +1,14 @@
#pragma once #pragma once
#include "Cubed/gameplay/model.hpp"
#include "Cubed/render/frame_buffer.hpp" #include "Cubed/render/frame_buffer.hpp"
#include "Cubed/render/model_renderer.hpp"
#include "Cubed/render/player_renderer.hpp" #include "Cubed/render/player_renderer.hpp"
#include "Cubed/render/texture.hpp" #include "Cubed/render/texture.hpp"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <memory> #include <memory>
#include <unordered_map>
#include <vector>
namespace Cubed { namespace Cubed {
class Renderer; class Renderer;
class ClientWorld; class ClientWorld;
@@ -12,6 +16,8 @@ class TextureManager;
class Camera; class Camera;
class WorldRenderer { class WorldRenderer {
public: public:
using InstanceDataMap =
std::unordered_map<ModelID, std::vector<ModelRender::InstanceData>>;
struct ParallelLight { struct ParallelLight {
glm::vec3 sundir; // direction from sun to vertex glm::vec3 sundir; // direction from sun to vertex
glm::vec3 lightdir; glm::vec3 lightdir;
@@ -128,12 +134,13 @@ private:
void render_world(ClientWorld& world); void render_world(ClientWorld& world);
void shadow_map_generate(ClientWorld& world); void shadow_map_generate(ClientWorld& world, const InstanceDataMap& map);
void render_underwater(ClientWorld& world); void render_underwater(ClientWorld& world);
void render_outline(ClientWorld& world); void render_outline(ClientWorld& world);
void shadow_entity(ClientWorld& world, const glm::mat4& light_matrix); void shadow_entity(ClientWorld& world, const glm::mat4& light_matrix,
void render_entity(ClientWorld& world); const InstanceDataMap& map);
void render_entity(ClientWorld& world, const InstanceDataMap& map);
void render_normal_block(const glm::mat4& model_mat, void render_normal_block(const glm::mat4& model_mat,
const glm::mat4& mv_mat, const glm::mat4& norm_mat, const glm::mat4& mv_mat, const glm::mat4& norm_mat,
@@ -147,5 +154,6 @@ private:
float angle_step_deg) const; float angle_step_deg) const;
glm::vec3 get_smoothed_shadow_lightdir(const glm::vec3& raw_shadow_sundir, glm::vec3 get_smoothed_shadow_lightdir(const glm::vec3& raw_shadow_sundir,
float dt); float dt);
InstanceDataMap entity_build(ClientWorld& world);
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -17,10 +17,8 @@ float swing_angle(const WalkPose& pose, float speed, float amp_deg) {
} // namespace } // namespace
ModelRender::ModelRender(Renderer& renderer) : m_renderer(renderer) {} ModelRender::ModelRender(Renderer& renderer) : m_renderer(renderer) {}
void ModelRender::build_vertices(ModelID id,
void ModelRender::render_instance(ModelID id, std::span<const InstanceData> instances) {
std::span<const InstanceData> instances,
const Camera& camera, bool shadow) {
if (instances.empty()) { if (instances.empty()) {
return; return;
} }
@@ -68,6 +66,12 @@ void ModelRender::render_instance(ModelID id,
batch.instance_vbo->buffer_sub_data( batch.instance_vbo->buffer_sub_data(
batch.instance_matrices.data(), batch.instance_matrices.data(),
instances.size() * batch.node_count * sizeof(glm::mat4), 0); instances.size() * batch.node_count * sizeof(glm::mat4), 0);
}
void ModelRender::render_instance(ModelID id, size_t sum, const Camera& camera,
bool shadow) {
auto& root = ModelManager::model(id).node;
auto& batch = get_batch(id, root);
auto& shader = shadow ? m_renderer.get_shader("depth_model_instance") auto& shader = shadow ? m_renderer.get_shader("depth_model_instance")
: m_renderer.get_shader("model_instance"); : m_renderer.get_shader("model_instance");
@@ -83,7 +87,7 @@ void ModelRender::render_instance(ModelID id,
} }
entry.mesh->vao->bind(); entry.mesh->vao->bind();
glDrawElementsInstanced(GL_TRIANGLES, entry.mesh->indices.size(), glDrawElementsInstanced(GL_TRIANGLES, entry.mesh->indices.size(),
GL_UNSIGNED_INT, 0, instances.size()); GL_UNSIGNED_INT, 0, sum);
} }
} }

View File

@@ -15,8 +15,7 @@
namespace Cubed { namespace Cubed {
namespace { namespace {
std::unordered_map<ModelID, std::vector<ModelRender::InstanceData>> WorldRenderer::InstanceDataMap get_instances_data_map(ClientWorld& world) {
get_instances_data_map(ClientWorld& world) {
std::unordered_map<ModelID, std::vector<ModelRender::InstanceData>> std::unordered_map<ModelID, std::vector<ModelRender::InstanceData>>
instances_data_map; instances_data_map;
auto& registry = world.entity_manager().get_registry(); auto& registry = world.entity_manager().get_registry();
@@ -40,6 +39,7 @@ get_instances_data_map(ClientWorld& world) {
} }
return instances_data_map; return instances_data_map;
}; };
} // namespace } // namespace
WorldRenderer::WorldRenderer(Renderer& renderer) WorldRenderer::WorldRenderer(Renderer& renderer)
@@ -74,13 +74,15 @@ void WorldRenderer::render(ClientWorld& world) {
day_night_calculation(world); day_night_calculation(world);
auto map = entity_build(world);
render_sky(world); render_sky(world);
if (m_shader_on) { if (m_shader_on) {
shadow_map_generate(world); shadow_map_generate(world, map);
} }
render_world(world); render_world(world);
render_outline(world); render_outline(world);
render_entity(world); render_entity(world, map);
FrameBuffer::unbind(); FrameBuffer::unbind();
@@ -128,6 +130,14 @@ void WorldRenderer::day_night_calculation(ClientWorld& world) {
m_ambient_strength = glm::mix(0.45f, 0.25f, day_factor); m_ambient_strength = glm::mix(0.45f, 0.25f, day_factor);
} }
WorldRenderer::InstanceDataMap WorldRenderer::entity_build(ClientWorld& world) {
auto instances_data_map = get_instances_data_map(world);
for (auto& [id, data] : instances_data_map) {
m_renderer.model_renderer().build_vertices(id, data);
}
return instances_data_map;
}
void WorldRenderer::render_sky(ClientWorld& world) { void WorldRenderer::render_sky(ClientWorld& world) {
auto& camera = world.world_scene().camera(); auto& camera = world.world_scene().camera();
@@ -321,16 +331,16 @@ void WorldRenderer::render_outline(ClientWorld& world) {
} }
void WorldRenderer::shadow_entity(ClientWorld& world, void WorldRenderer::shadow_entity(ClientWorld& world,
const glm::mat4& light_matrix) { const glm::mat4& light_matrix,
const InstanceDataMap& map) {
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
{ {
auto& shader = m_renderer.get_shader("depth_model_instance"); auto& shader = m_renderer.get_shader("depth_model_instance");
shader.use(); shader.use();
shader.set_loc("lightSpaceMatrix", light_matrix); shader.set_loc("lightSpaceMatrix", light_matrix);
auto instances_data_map = get_instances_data_map(world); for (auto& [id, data] : map) {
for (auto& [id, data] : instances_data_map) {
m_renderer.model_renderer().render_instance( m_renderer.model_renderer().render_instance(
id, data, world.world_scene().camera(), true); id, data.size(), world.world_scene().camera(), true);
} }
} }
@@ -342,7 +352,8 @@ void WorldRenderer::shadow_entity(ClientWorld& world,
} }
} }
void WorldRenderer::shadow_map_generate(ClientWorld& world) { void WorldRenderer::shadow_map_generate(ClientWorld& world,
const InstanceDataMap& map) {
float texels_per_unit = 0.0f; float texels_per_unit = 0.0f;
const auto& lightdir = m_parallel_light.lightdir; const auto& lightdir = m_parallel_light.lightdir;
@@ -445,7 +456,7 @@ void WorldRenderer::shadow_map_generate(ClientWorld& world) {
} }
} }
shadow_entity(world, light_space_matrix); shadow_entity(world, light_space_matrix, map);
} }
void WorldRenderer::render_underwater(ClientWorld& world) { void WorldRenderer::render_underwater(ClientWorld& world) {
@@ -727,7 +738,8 @@ void WorldRenderer::render_transparent_block(const glm::mat4& mv_mat,
glBindVertexArray(0); glBindVertexArray(0);
} }
void WorldRenderer::render_entity(ClientWorld& world) { void WorldRenderer::render_entity(ClientWorld& world,
const InstanceDataMap& map) {
// shader.set_loc("renderDistance", m_world.rendering_distance()); // shader.set_loc("renderDistance", m_world.rendering_distance());
// shader.set_loc("skyColor", m_sky_uniform.sky_top); // shader.set_loc("skyColor", m_sky_uniform.sky_top);
@@ -752,10 +764,9 @@ void WorldRenderer::render_entity(ClientWorld& world) {
model.set_loc("samples", m_samples); model.set_loc("samples", m_samples);
m_depth_map_texture->bind(0); m_depth_map_texture->bind(0);
auto instances_data_map = get_instances_data_map(world); for (auto& [id, data] : map) {
for (auto& [id, data] : instances_data_map) {
m_renderer.model_renderer().render_instance( m_renderer.model_renderer().render_instance(
id, data, world.world_scene().camera(), false); id, data.size(), world.world_scene().camera(), false);
} }
} }