ifcviewer: move buildPipelines + selection-flags wiring into ViewportCore (#84-k)

Move the main render pipeline construction + the selection flags
buffer/bind group lifecycle. Both buildPipelines and the selection
flags methods produce/consume state ViewportCore already owns
(main_pipeline_, frame_bgl_, etc.) plus a handful of "frame
infrastructure" fields this commit also brings across.

State moved (7 fields):
  WGPUBuffer        frame_uniform_buffer_
  WGPUBindGroup     frame_bind_group_
  WGPUBuffer        selection_flags_buffer_
  uint32_t          selection_flags_capacity_
  std::vector<u32>  selection_flags_scratch_
  SelectionState    selection_
  VisibilityState   visibility_

Methods moved:
  buildPipelines              (~150 lines + 320-line MAIN_WGSL string)
  ensureSelectionFlagsBuffer  (~60 lines)
  uploadSelectionFlagsIfDirty (~10 lines)

Plus the MAIN_WGSL constant + the svFromCStr helper into
ViewportCore.cpp's anonymous namespace. ViewportWindow.cpp keeps its
own svFromCStr copy (still used by 50+ label fields in the not-yet-
moved pipeline builders + render encoders).

Shared constants extracted to ViewportCore.h:
  kMaxSectionPlanes (was OverlayRenderer::kMaxSectionPlanes — assert
                     in VW.cpp keeps them in sync)
  kViewportSampleCount (was SAMPLE_COUNT in VW; VW keeps a static
                        constexpr alias for the existing callsites)
  struct FrameUniforms (canonical layout for the per-frame UBO,
                        consumed by both core's buildPipelines and
                        VW's still-in-flight updateFrameUniforms)

Builds: desktop / bonsai / web all green. Tests 100/100.
This commit is contained in:
Dion Moult
2026-06-05 16:39:01 +10:00
parent 8cf7d4346d
commit 66a21923b8
4 changed files with 674 additions and 593 deletions
+74
View File
@@ -47,8 +47,43 @@
#include "InstanceCompose.h"
#include "InstancedGeometry.h"
#include "ModelGpuData.h"
#include "SelectionState.h"
#include "StreamingThread.h"
#include "ViewportHost.h"
#include "VisibilityState.h"
// Render-loop constants shared between ViewportCore and ViewportWindow.
// Kept here (not in OverlayRenderer.h) so IfcViewerCore stays Qt-free.
// ViewportWindow.cpp asserts the section-plane cap matches
// OverlayRenderer's so the WGSL clip-plane array and the section-tool
// state vector agree by construction.
constexpr int kMaxSectionPlanes = 6;
constexpr uint32_t kViewportSampleCount = 4;
// Per-frame uniform layout. Matches the WGSL struct the main pipeline
// declares (see ViewportCore.cpp MAIN_WGSL). Both buildPipelines (in
// ViewportCore) and updateFrameUniforms (currently in ViewportWindow)
// allocate / write this; keeping the type here makes the layout the
// single source of truth.
struct FrameUniforms {
float view_proj[16];
float light_dir[4]; // xyz = unit dir toward light, w unused
float fill_dir[4]; // xyz = secondary fill dir
float sky_color[4]; // xyz = sky-tint ambient, w unused
float ground_color[4]; // xyz = ground-tint ambient, w unused
int clip_count; // active section-plane count (≤ kMaxSectionPlanes)
int _pad_clip[3]; // pad to 16-byte alignment for the array below
float clip_planes[kMaxSectionPlanes][4]; // xyz = world-space unit normal, w = plane offset
float xray_alpha_cap; // X-ray mode: fragment alpha clamped to min(in.color.a, cap)
float _pad_xray[3]; // pad to 16-byte alignment so the struct stays vec4-aligned
};
static_assert(sizeof(FrameUniforms)
== 16 * sizeof(float)
+ 4 * 4 * sizeof(float)
+ 4 * sizeof(int)
+ kMaxSectionPlanes * 4 * sizeof(float)
+ 4 * sizeof(float),
"FrameUniforms must match WGSL layout");
class ViewportCore {
public:
@@ -171,6 +206,23 @@ public:
std::vector<std::pair<uint32_t, double>>
volumesPerObject(const std::vector<uint32_t>& object_ids) const;
// ---- Pipeline construction --------------------------------------------
//
// buildPipelines creates the main render pipelines (opaque +
// transparent variants), bind group layouts, the per-frame UBO,
// and the WGSL shader module. Called once after the device + queue
// come up + the surface format is picked.
//
// ensureSelectionFlagsBuffer (re)allocates the selection flags
// storage buffer geometrically as next_object_id_ grows, and
// (re)builds the frame bind group when its referenced buffers
// change. uploadSelectionFlagsIfDirty flushes
// selection_.fillFlagsArray() into the GPU when selection_'s dirty
// flag is set — called once per frame at render time.
bool buildPipelines();
void ensureSelectionFlagsBuffer();
void uploadSelectionFlagsIfDirty();
// Friend access for ViewportWindow's reference proxies. As each
// render method moves into ViewportCore it stops needing these
// (it touches the fields directly); once everything has migrated
@@ -227,6 +279,28 @@ private:
// main pass since the pick fragment also vertex-pulls instance data.
WGPURenderPipeline pick_pipeline_ = nullptr;
// ---- Frame uniforms + selection bind ----------------------------------
//
// The per-frame UBO (view-proj + lighting + section planes + xray
// params) and the frame bind group it lives in alongside the
// selection flags storage buffer at group=0 binding=1.
// ensureSelectionFlagsBuffer is the only writer for the buffer +
// bind group; uploadSelectionFlagsIfDirty repopulates the flags
// from selection_ when it changes.
WGPUBuffer frame_uniform_buffer_ = nullptr;
WGPUBindGroup frame_bind_group_ = nullptr;
WGPUBuffer selection_flags_buffer_ = nullptr;
uint32_t selection_flags_capacity_ = 0; // u32 entries
std::vector<uint32_t> selection_flags_scratch_;
// Selection + per-element visibility state machines. Pure CPU
// bookkeeping today (no GPU touch beyond the readback uploaded via
// selection_flags_buffer_). Mutated on the main thread between
// renders; cull workers read concurrently which is safe as long
// as no concurrent writes.
SelectionState selection_;
VisibilityState visibility_;
// ---- Scene state ---------------------------------------------------------
//
// Sub-allocator for chunk vertex + index buffers. All per-chunk