Revert "ifcviewer: GPU cull fwd/rev reflection bucketing (step 3b)"

This reverts commit 7defbe982464536e34e80aa85d2cd7eaafbb62ee.
This commit is contained in:
Dion Moult
2026-04-17 18:34:01 +10:00
parent 643a2e1c1f
commit 175efcfffe
2 changed files with 41 additions and 95 deletions
+34 -81
View File
@@ -278,8 +278,7 @@ static const char* CULL_COMPACT_COMPUTE_SHADER = R"(
#version 450 core
layout(local_size_x = 64) in;
// Each instance contributes two vec4 entries: (min.xyz, mesh_id_as_float),
// (max.xyz, flags_as_float). mesh_id is packed via floatBitsToUint;
// flags bit 0 = reflected (winding-bucket selector).
// (max.xyz, flags_as_float). mesh_id is packed via floatBitsToUint.
layout(std430, binding = 0) readonly buffer AabbBuf { vec4 entries[]; };
layout(std430, binding = 1) coherent buffer IndirectBuf { uint ind[]; };
layout(std430, binding = 2) writeonly buffer VisibleBuf { uint visible[]; };
@@ -287,7 +286,6 @@ layout(std430, binding = 3) readonly buffer MeshBaseBuf { uint mesh_base[]; };
uniform vec4 u_planes[6];
uniform uint u_count; // num instances
uniform uint u_fwd_mesh_count; // M; reflected bucket is mesh_id + M
uniform vec3 u_camera_eye;
uniform float u_focal_px;
uniform float u_min_pixel_radius;
@@ -324,12 +322,9 @@ void main() {
vec3 mx = hi.xyz;
if (!frustum(mn, mx)) return;
if (!contribution(mn, mx)) return;
uint mesh_id = floatBitsToUint(lo.w);
uint flags = floatBitsToUint(hi.w);
uint bucket = ((flags & 1u) != 0u) ? (mesh_id + u_fwd_mesh_count)
: mesh_id;
uint local = atomicAdd(ind[bucket * 5u + 1u], 1u);
visible[mesh_base[bucket] + local] = gid;
uint mesh_id = floatBitsToUint(lo.w);
uint local = atomicAdd(ind[mesh_id * 5u + 1u], 1u);
visible[mesh_base[mesh_id] + local] = gid;
}
)";
@@ -918,27 +913,13 @@ void ViewportWindow::uploadInstanceAabbs(ModelGpuData& m) {
void ViewportWindow::uploadGpuCullStaticBuffers(ModelGpuData& m) {
const uint32_t M = static_cast<uint32_t>(m.meshes.size());
m.gpu_mesh_command_count = 2u * M;
m.gpu_forward_command_count = M;
m.gpu_mesh_command_count = M;
// Count fwd / rev instances per mesh so each bucket gets a tight
// per-mesh slot range. (Sum of fwd + rev = total_instances, so the
// visible buffer is no bigger than the single-bucket version.)
std::vector<uint32_t> fwd_n(M, 0), rev_n(M, 0);
for (size_t i = 0; i < m.instances.size(); ++i) {
const uint32_t mid = m.instances[i].mesh_id;
if (mid >= M) continue;
const bool reflected = i < m.instance_reflected.size()
&& m.instance_reflected[i];
(reflected ? rev_n[mid] : fwd_n[mid]) += 1u;
}
// Prefix sums. mesh_base[0..M) for fwd, mesh_base[M..2M) for rev.
// Same layout for the indirect commands. baseInstance of each
// command points at its visible[] slot so the vertex shader's
// gl_BaseInstanceARB + gl_InstanceID indexes directly into it.
std::vector<uint32_t> mesh_base(2u * M, 0);
std::vector<DrawElementsIndirectCommand> indir(2u * M);
// Prefix-sum instance_count to get per-mesh base offsets. Also build a
// DrawElementsIndirectCommand template per mesh (count / firstIndex /
// baseVertex / baseInstance static; instanceCount starts at 0).
std::vector<uint32_t> mesh_base(M, 0);
std::vector<DrawElementsIndirectCommand> indir(M);
uint32_t running = 0;
for (uint32_t i = 0; i < M; ++i) {
const MeshInfo& mesh = m.meshes[i];
@@ -949,23 +930,12 @@ void ViewportWindow::uploadGpuCullStaticBuffers(ModelGpuData& m) {
cmd.firstIndex = mesh.ebo_byte_offset / sizeof(uint32_t);
cmd.baseVertex = mesh.vbo_byte_offset / INSTANCED_VERTEX_STRIDE_BYTES;
cmd.baseInstance = running;
running += fwd_n[i];
}
for (uint32_t i = 0; i < M; ++i) {
const MeshInfo& mesh = m.meshes[i];
mesh_base[M + i] = running;
DrawElementsIndirectCommand& cmd = indir[M + i];
cmd.count = mesh.index_count;
cmd.instanceCount = 0;
cmd.firstIndex = mesh.ebo_byte_offset / sizeof(uint32_t);
cmd.baseVertex = mesh.vbo_byte_offset / INSTANCED_VERTEX_STRIDE_BYTES;
cmd.baseInstance = running;
running += rev_n[i];
running += mesh.instance_count;
}
const uint32_t total_instances = running;
// Indirect buffer — 2M commands (fwd bucket then rev bucket).
const size_t ind_bytes = std::max<size_t>(2u * M * sizeof(DrawElementsIndirectCommand),
// Indirect buffer.
const size_t ind_bytes = std::max<size_t>(M * sizeof(DrawElementsIndirectCommand),
sizeof(DrawElementsIndirectCommand));
if (m.gpu_indirect_buffer && m.gpu_indirect_capacity < ind_bytes) {
gl_->glDeleteBuffers(1, &m.gpu_indirect_buffer);
@@ -980,10 +950,10 @@ void ViewportWindow::uploadGpuCullStaticBuffers(ModelGpuData& m) {
}
if (M > 0) {
gl_->glNamedBufferSubData(m.gpu_indirect_buffer, 0,
2u * M * sizeof(DrawElementsIndirectCommand), indir.data());
M * sizeof(DrawElementsIndirectCommand), indir.data());
}
// Visible list — exact: fwd + rev per-mesh counts sum to total_instances.
// Visible list — sized to worst case (every instance survives).
const size_t vis_bytes = std::max<size_t>(total_instances * sizeof(uint32_t),
sizeof(uint32_t));
if (m.gpu_visible_ssbo && m.gpu_visible_capacity < vis_bytes) {
@@ -998,8 +968,8 @@ void ViewportWindow::uploadGpuCullStaticBuffers(ModelGpuData& m) {
m.gpu_visible_capacity = vis_bytes;
}
// Mesh-base SSBO — 2M entries (one per bucket).
const size_t mb_bytes = std::max<size_t>(2u * M * sizeof(uint32_t), sizeof(uint32_t));
// Mesh-base SSBO.
const size_t mb_bytes = std::max<size_t>(M * sizeof(uint32_t), sizeof(uint32_t));
if (m.gpu_mesh_base_ssbo && m.gpu_mesh_base_capacity < mb_bytes) {
gl_->glDeleteBuffers(1, &m.gpu_mesh_base_ssbo);
m.gpu_mesh_base_ssbo = 0;
@@ -1013,7 +983,7 @@ void ViewportWindow::uploadGpuCullStaticBuffers(ModelGpuData& m) {
}
if (M > 0) {
gl_->glNamedBufferSubData(m.gpu_mesh_base_ssbo, 0,
2u * M * sizeof(uint32_t), mesh_base.data());
M * sizeof(uint32_t), mesh_base.data());
}
}
@@ -1962,10 +1932,10 @@ void ViewportWindow::render() {
// Phase 3E: the GPU-cull path. When IFC_GPU_CULL=1 we dispatch two
// tiny compute shaders per model (reset + compact), then let the draw
// loop below issue MDI from gpu_indirect_buffer. Commands are laid
// out as two buckets of M entries each — fwd (CCW) then rev (CW) —
// so reflected instances render with correct winding. LOD and HiZ
// still live only on the CPU path.
// loop below issue MDI from gpu_indirect_buffer. Single-bucket-per-
// mesh for now — LOD selection, reflection winding split, and HiZ
// still live only on the CPU path. Reflected instances therefore
// render with wrong winding under this gate; that's the next commit.
if (gpu_cull_enabled && cull_this_frame && cull_compact_program_) {
QElapsedTimer t; t.start();
float planes_flat[24];
@@ -2000,8 +1970,6 @@ void ViewportWindow::render() {
gl_->glUniform4fv(gl_->glGetUniformLocation(cull_compact_program_, "u_planes"),
6, planes_flat);
gl_->glUniform1ui(gl_->glGetUniformLocation(cull_compact_program_, "u_count"), n);
gl_->glUniform1ui(gl_->glGetUniformLocation(cull_compact_program_, "u_fwd_mesh_count"),
m.gpu_forward_command_count);
gl_->glUniform3f (gl_->glGetUniformLocation(cull_compact_program_, "u_camera_eye"),
camera_eye_.x(), camera_eye_.y(), camera_eye_.z());
gl_->glUniform1f (gl_->glGetUniformLocation(cull_compact_program_, "u_focal_px"),
@@ -2036,10 +2004,10 @@ void ViewportWindow::render() {
if (m.hidden || !m.ssbo || m.ssbo_instance_count == 0) continue;
if (gpu_cull_enabled) {
// GPU path: compact shader routed survivors into fwd/rev
// buckets (commands [0..M) and [M..2M)). Two MDIs: CCW then
// CW. LOD and HiZ still CPU-only; reflected winding is now
// correct.
// GPU path: compact shader already wrote visible indices into
// gpu_visible_ssbo at [mesh_base[i], mesh_base[i]+count) and
// set each command's instanceCount. One MDI per model, no
// fwd/rev split yet — reflected winding is wrong; step 3b.
if (!m.gpu_indirect_buffer || !m.gpu_visible_ssbo ||
m.gpu_mesh_command_count == 0) continue;
@@ -2049,29 +2017,18 @@ void ViewportWindow::render() {
gl_->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, m.mesh_info_ssbo);
gl_->glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m.gpu_indirect_buffer);
uint32_t fwd = m.gpu_forward_command_count;
uint32_t rev = m.gpu_mesh_command_count - fwd;
if (max_subdraws < m.gpu_mesh_command_count) {
const uint32_t total = m.gpu_mesh_command_count;
fwd = static_cast<uint32_t>((uint64_t)fwd * max_subdraws / total);
rev = max_subdraws - fwd;
}
if (fwd > 0 && !skip_mdi) {
uint32_t count = m.gpu_mesh_command_count;
if (max_subdraws < count) count = max_subdraws;
if (count > 0 && !skip_mdi) {
gl_->glFrontFace(GL_CCW);
gl_->glMultiDrawElementsIndirect(
GL_TRIANGLES, GL_UNSIGNED_INT, nullptr,
static_cast<GLsizei>(fwd), 0);
static_cast<GLsizei>(count), 0);
++gl_draw_calls_;
}
if (rev > 0 && !skip_mdi) {
gl_->glFrontFace(GL_CW);
gl_->glMultiDrawElementsIndirect(
GL_TRIANGLES, GL_UNSIGNED_INT,
reinterpret_cast<const void*>(m.gpu_forward_command_count * sizeof(DrawElementsIndirectCommand)),
static_cast<GLsizei>(rev), 0);
++gl_draw_calls_;
gl_->glFrontFace(GL_CCW);
}
// Stats: we don't have visible_objects / visible_triangles
// from the GPU yet (would need a readback). Report command
// count as a proxy for indirect_sub_draws_.
indirect_sub_draws_ += m.gpu_mesh_command_count;
continue;
}
@@ -2183,15 +2140,11 @@ void ViewportWindow::render() {
gl_->glGetNamedBufferSubData(mm.gpu_indirect_buffer, 0,
mm.gpu_mesh_command_count * sizeof(DrawElementsIndirectCommand),
readback.data());
// Commands [0..M) are fwd, [M..2M) are rev for the same
// mesh — index meshes[] modulo forward_command_count.
const uint32_t M = mm.gpu_forward_command_count;
for (uint32_t i = 0; i < mm.gpu_mesh_command_count; ++i) {
const uint32_t ic = readback[i].instanceCount;
const uint32_t mesh_i = (M > 0) ? (i % M) : 0;
gpu_surv += ic;
gpu_obj += ic;
gpu_tri += ic * (mm.meshes[mesh_i].index_count / 3u);
gpu_tri += ic * (mm.meshes[i].index_count / 3u);
}
}
gpu_cull_last_survivors_ = gpu_surv;