mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-16 21:42:19 +00:00
Per-object frustum culling with glMultiDrawElements
Track per-object AABB and index range during upload. Each frame, extract frustum planes from the view-projection matrix and cull objects whose AABB is entirely outside any plane. Draw only visible objects via glMultiDrawElements. Document the three-phase rendering performance strategy in README.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,9 @@
|
|||||||
#include <QtOpenGL/QOpenGLVersionFunctionsFactory>
|
#include <QtOpenGL/QOpenGLVersionFunctionsFactory>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
static const size_t INITIAL_VBO_SIZE = 64 * 1024 * 1024; // 64 MB
|
static const size_t INITIAL_VBO_SIZE = 64 * 1024 * 1024; // 64 MB
|
||||||
static const size_t INITIAL_EBO_SIZE = 32 * 1024 * 1024; // 32 MB
|
static const size_t INITIAL_EBO_SIZE = 32 * 1024 * 1024; // 32 MB
|
||||||
@@ -421,9 +423,31 @@ void ViewportWindow::uploadChunk(const UploadChunk& chunk) {
|
|||||||
}
|
}
|
||||||
gl_->glNamedBufferSubData(ebo_, ebo_used_, ib_size, global_indices.data());
|
gl_->glNamedBufferSubData(ebo_, ebo_used_, ib_size, global_indices.data());
|
||||||
|
|
||||||
|
// Compute AABB from vertex positions in this chunk.
|
||||||
|
ObjectDrawInfo info;
|
||||||
|
info.index_offset = static_cast<uint32_t>(ebo_used_);
|
||||||
|
info.index_count = static_cast<uint32_t>(chunk.indices.size());
|
||||||
|
|
||||||
|
const size_t num_verts = chunk.vertices.size() / VERTEX_STRIDE;
|
||||||
|
if (num_verts > 0) {
|
||||||
|
info.aabb_min[0] = info.aabb_min[1] = info.aabb_min[2] = std::numeric_limits<float>::max();
|
||||||
|
info.aabb_max[0] = info.aabb_max[1] = info.aabb_max[2] = -std::numeric_limits<float>::max();
|
||||||
|
for (size_t v = 0; v < num_verts; ++v) {
|
||||||
|
const float* pos = &chunk.vertices[v * VERTEX_STRIDE];
|
||||||
|
for (int a = 0; a < 3; ++a) {
|
||||||
|
if (pos[a] < info.aabb_min[a]) info.aabb_min[a] = pos[a];
|
||||||
|
if (pos[a] > info.aabb_max[a]) info.aabb_max[a] = pos[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info.aabb_min[0] = info.aabb_min[1] = info.aabb_min[2] = 0.0f;
|
||||||
|
info.aabb_max[0] = info.aabb_max[1] = info.aabb_max[2] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(upload_mutex_);
|
std::lock_guard<std::mutex> lock(upload_mutex_);
|
||||||
total_index_count_ += static_cast<uint32_t>(chunk.indices.size());
|
total_index_count_ += static_cast<uint32_t>(chunk.indices.size());
|
||||||
|
object_draw_info_.push_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
vbo_used_ += vb_size;
|
vbo_used_ += vb_size;
|
||||||
@@ -442,6 +466,7 @@ void ViewportWindow::resetScene() {
|
|||||||
vertex_count_ = 0;
|
vertex_count_ = 0;
|
||||||
total_triangles_ = 0;
|
total_triangles_ = 0;
|
||||||
selected_object_id_ = 0;
|
selected_object_id_ = 0;
|
||||||
|
object_draw_info_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportWindow::setSelectedObjectId(uint32_t id) {
|
void ViewportWindow::setSelectedObjectId(uint32_t id) {
|
||||||
@@ -504,6 +529,63 @@ void ViewportWindow::updateCamera() {
|
|||||||
proj_matrix_.perspective(45.0f, aspect, 0.1f, camera_distance_ * 10.0f);
|
proj_matrix_.perspective(45.0f, aspect, 0.1f, camera_distance_ * 10.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewportWindow::buildVisibleList(const QMatrix4x4& vp) {
|
||||||
|
visible_counts_.clear();
|
||||||
|
visible_offsets_.clear();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(upload_mutex_);
|
||||||
|
if (object_draw_info_.empty()) return;
|
||||||
|
|
||||||
|
// Extract 6 frustum planes from the view-projection matrix.
|
||||||
|
// Each plane is (a, b, c, d) where ax + by + cz + d >= 0 is inside.
|
||||||
|
// QMatrix4x4 is stored column-major; operator(row, col) gives element.
|
||||||
|
float planes[6][4];
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
planes[0][i] = vp(3, i) + vp(0, i); // left
|
||||||
|
planes[1][i] = vp(3, i) - vp(0, i); // right
|
||||||
|
planes[2][i] = vp(3, i) + vp(1, i); // bottom
|
||||||
|
planes[3][i] = vp(3, i) - vp(1, i); // top
|
||||||
|
planes[4][i] = vp(3, i) + vp(2, i); // near
|
||||||
|
planes[5][i] = vp(3, i) - vp(2, i); // far
|
||||||
|
}
|
||||||
|
// Normalize planes.
|
||||||
|
for (int p = 0; p < 6; ++p) {
|
||||||
|
float len = std::sqrt(planes[p][0] * planes[p][0] +
|
||||||
|
planes[p][1] * planes[p][1] +
|
||||||
|
planes[p][2] * planes[p][2]);
|
||||||
|
if (len > 0.0f) {
|
||||||
|
float inv = 1.0f / len;
|
||||||
|
planes[p][0] *= inv;
|
||||||
|
planes[p][1] *= inv;
|
||||||
|
planes[p][2] *= inv;
|
||||||
|
planes[p][3] *= inv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visible_counts_.reserve(object_draw_info_.size());
|
||||||
|
visible_offsets_.reserve(object_draw_info_.size());
|
||||||
|
|
||||||
|
for (const auto& obj : object_draw_info_) {
|
||||||
|
bool visible = true;
|
||||||
|
for (int p = 0; p < 6; ++p) {
|
||||||
|
// p-vertex: the AABB corner most in the direction of the plane normal.
|
||||||
|
float px = planes[p][0] >= 0.0f ? obj.aabb_max[0] : obj.aabb_min[0];
|
||||||
|
float py = planes[p][1] >= 0.0f ? obj.aabb_max[1] : obj.aabb_min[1];
|
||||||
|
float pz = planes[p][2] >= 0.0f ? obj.aabb_max[2] : obj.aabb_min[2];
|
||||||
|
float dist = planes[p][0] * px + planes[p][1] * py + planes[p][2] * pz + planes[p][3];
|
||||||
|
if (dist < 0.0f) {
|
||||||
|
visible = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (visible) {
|
||||||
|
visible_counts_.push_back(static_cast<GLsizei>(obj.index_count));
|
||||||
|
visible_offsets_.push_back(reinterpret_cast<const void*>(
|
||||||
|
static_cast<uintptr_t>(obj.index_offset)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ViewportWindow::render() {
|
void ViewportWindow::render() {
|
||||||
if (!gl_initialized_ || !isExposed()) return;
|
if (!gl_initialized_ || !isExposed()) return;
|
||||||
|
|
||||||
@@ -524,11 +606,12 @@ void ViewportWindow::render() {
|
|||||||
|
|
||||||
gl_->glBindVertexArray(vao_);
|
gl_->glBindVertexArray(vao_);
|
||||||
|
|
||||||
{
|
buildVisibleList(vp);
|
||||||
std::lock_guard<std::mutex> lock(upload_mutex_);
|
if (!visible_counts_.empty()) {
|
||||||
if (total_index_count_ > 0) {
|
gl_->glMultiDrawElements(GL_TRIANGLES,
|
||||||
gl_->glDrawElements(GL_TRIANGLES, total_index_count_, GL_UNSIGNED_INT, nullptr);
|
visible_counts_.data(), GL_UNSIGNED_INT,
|
||||||
}
|
visible_offsets_.data(),
|
||||||
|
static_cast<GLsizei>(visible_counts_.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAxisGizmo();
|
renderAxisGizmo();
|
||||||
@@ -588,11 +671,12 @@ void ViewportWindow::renderPickPass() {
|
|||||||
|
|
||||||
gl_->glBindVertexArray(vao_);
|
gl_->glBindVertexArray(vao_);
|
||||||
|
|
||||||
{
|
// Reuse the visible list from the most recent render() call.
|
||||||
std::lock_guard<std::mutex> lock(upload_mutex_);
|
if (!visible_counts_.empty()) {
|
||||||
if (total_index_count_ > 0) {
|
gl_->glMultiDrawElements(GL_TRIANGLES,
|
||||||
gl_->glDrawElements(GL_TRIANGLES, total_index_count_, GL_UNSIGNED_INT, nullptr);
|
visible_counts_.data(), GL_UNSIGNED_INT,
|
||||||
}
|
visible_offsets_.data(),
|
||||||
|
static_cast<GLsizei>(visible_counts_.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
gl_->glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
gl_->glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ struct MaterialInfo {
|
|||||||
float r = 0.75f, g = 0.75f, b = 0.78f, a = 1.0f;
|
float r = 0.75f, g = 0.75f, b = 0.78f, a = 1.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ObjectDrawInfo {
|
||||||
|
uint32_t index_offset; // byte offset into EBO
|
||||||
|
uint32_t index_count; // number of indices
|
||||||
|
float aabb_min[3]; // world-space AABB
|
||||||
|
float aabb_max[3];
|
||||||
|
};
|
||||||
|
|
||||||
struct UploadChunk {
|
struct UploadChunk {
|
||||||
// Interleaved per-vertex layout (8 floats / 32 bytes per vertex):
|
// Interleaved per-vertex layout (8 floats / 32 bytes per vertex):
|
||||||
// pos(3 float) + normal(3 float) + object_id(1 float bitcast from uint)
|
// pos(3 float) + normal(3 float) + object_id(1 float bitcast from uint)
|
||||||
@@ -77,6 +84,7 @@ private:
|
|||||||
void buildAxisGizmo();
|
void buildAxisGizmo();
|
||||||
bool growVbo(size_t needed_total);
|
bool growVbo(size_t needed_total);
|
||||||
bool growEbo(size_t needed_total);
|
bool growEbo(size_t needed_total);
|
||||||
|
void buildVisibleList(const QMatrix4x4& vp);
|
||||||
|
|
||||||
// Mouse interaction
|
// Mouse interaction
|
||||||
void handleMousePress(QMouseEvent* event);
|
void handleMousePress(QMouseEvent* event);
|
||||||
@@ -116,12 +124,15 @@ private:
|
|||||||
int pick_width_ = 0;
|
int pick_width_ = 0;
|
||||||
int pick_height_ = 0;
|
int pick_height_ = 0;
|
||||||
|
|
||||||
// The entire scene is a single mega-batch: per-vertex color removes the
|
// Per-object draw metadata for frustum culling.
|
||||||
// need to switch materials between draw calls. Indices are written into
|
std::vector<ObjectDrawInfo> object_draw_info_;
|
||||||
// the EBO already offset by base_vertex so one glDrawElements covers all.
|
|
||||||
uint32_t total_index_count_ = 0;
|
uint32_t total_index_count_ = 0;
|
||||||
std::mutex upload_mutex_;
|
std::mutex upload_mutex_;
|
||||||
|
|
||||||
|
// Scratch buffers reused each frame to avoid allocation.
|
||||||
|
std::vector<GLsizei> visible_counts_;
|
||||||
|
std::vector<const void*> visible_offsets_;
|
||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
QVector3D camera_target_{0, 0, 0};
|
QVector3D camera_target_{0, 0, 0};
|
||||||
float camera_distance_ = 50.0f;
|
float camera_distance_ = 50.0f;
|
||||||
|
|||||||
Reference in New Issue
Block a user