Commit Graph

17 Commits

Author SHA1 Message Date
Dion Moult 346e6db217 ifcviewer: move pick + raycast subsystem into ViewportCore (#84-t)
The whole pick pipeline (R32UInt + RGBA16F MRT, depth attachment,
ping-pong staging, single-pixel + rect readback) plus the public
pickObjectAt / pickSurfaceAt / picksInRect / pickMeshLocalAt / raycast
API and the rayAabbSlab / rayTriMT / rayAABBHit helpers all move to
ViewportCore. ViewportWindow keeps tiny forwarder methods so the
bonsai input + tool callers (mouseRelease, marquee, section tool,
Length/Area refinement) stay compiling.

MeshLocalPick + RaycastHit follow as nested types on ViewportCore;
ViewportWindow re-exports them as using-aliases to preserve the
ViewportWindow::MeshLocalPick / ViewportWindow::RaycastHit names
existing callers (and a couple of bonsai tests) reach for.

State migrated: pick_color_texture_/_view_, pick_normal_texture_/_view_,
pick_depth_texture_/_view_, pick_staging_buffer_, pick_normal_staging_buffer_,
pick_w_/_h_, box_pick_staging_buffer_/_capacity_. The pick_pipeline_
itself was already aliased.

The pick path no longer reaches into VW for any GPU state, so the
render() / shutdown() callers become core_.X() forwards and the pick
infrastructure can be exercised by the future web build without going
through Qt.
2026-06-06 18:48:09 +10:00
Dion Moult fa3b0f90d8 ifcviewer: move edge silhouette subsystem into ViewportCore (#84-s)
buildEdgePipeline, encodeEdgePass, releaseEdgeResources + the EDGE_WGSL
shader source all move to ViewportCore. The edge_bind_group_ + the
edges_enabled_ flag come along too (the latter aliased on VW so the
edge-toggle keybind keeps compiling).

The pass binds the now-core-side depth_view_ directly, so there's no
remaining cross-side state dependency for edge rendering. render()
still calls core_.encodeEdgePass(enc, surface_view) — once render()
itself moves, the call collapses to a sibling method invocation.
2026-06-06 18:26:57 +10:00
Dion Moult 138973830b ifcviewer: move HiZ subsystem + depth/MSAA attachments into ViewportCore (#84-r)
The whole HiZ occlusion-cull pipeline (resolve pass, ping-pong async
readback, CPU mip pyramid, per-instance AABB lookup, WGPU_HIZ_TRACE
diagnostic) moves to ViewportCore. The main render-pass depth
attachment and MSAA color attachment come along too — they're shared
between render() (still VW) and the HiZ resolve pass (now core).

Methods migrated: buildHizPipeline, ensureHizTextures,
releaseHizResources, encodeHizResolve, startHizMap, drainHizReadbacks,
aabbOccludedByHiz, ensureDepthTexture, releaseDepthTexture,
ensureMsaaColorTexture, releaseMsaaColorTexture. HIZ_WGSL moves with
them into ViewportCore.cpp's anon namespace.

State migrated: hiz_enabled_, hiz_valid_, hiz_vp_, hiz_pyramid_,
hiz_mip_offset_/_w_/_h_, hiz_reject_count_, hiz_trace_budget_,
hiz_uniform_buffer_, hiz_bind_group_, hiz_resolve_texture_/_view_/_w_/_h_,
hiz_padded_bpr_, hiz_staging_buffers_[2], hiz_slot_vp_[2],
hiz_slot_state_[2], hiz_write_idx_, depth_texture_/_view_/_w_/_h_,
msaa_color_texture_/_view_/_w_/_h_, plus the HizSlotState enum +
HIZ_SLOTS + HIZ_BASE_W constants. ViewportWindow keeps reference
aliases on every field VW.cpp still touches so the render path
compiles unchanged.

The HizOccludedFn shim in render() now wraps core_.aabbOccludedByHiz
directly. Once the render path itself moves into core, that shim
disappears and cull can call aabbOccludedByHiz as a sibling method.
2026-06-06 18:20:15 +10:00
Dion Moult 4782f54e3b ifcviewer: move sidecar / direct-load helpers into ViewportCore (#84-q)
applyCachedModel, uploadMeshChunk, uploadInstanceChunk, finalizeModel
all live in ViewportCore now. The bonsai-facing public entry points on
ViewportWindow are one-line forwarders that keep
SceneLoader → ViewportWindow* binding intact.

State + helpers that came along:
- pending_direct_loads_ (the SidecarData staging map keyed by model_id)
- initial_view_applied_ (auto-viewAll suppression; aliased on VW so
  setCamera can still flip it)
- getOrCreateDirectStaging + createBufferWithData (anon namespace
  helpers on the core side)

The Qt-bound isExposed() / requestUpdate() pair on the
applyCachedModel tail becomes host_->requestFrame() — the
QtViewportHost forwards to requestUpdate(); a WebViewportHost will
forward to requestAnimationFrame.

The sidecar load path is now fully core-side. ViewportWindow no
longer owns any of the model-creation machinery; everything from
"here's a parsed sidecar" to "fully-built models_gpu_ entry with
empty pool slices waiting on streaming" runs through ViewportCore.
2026-06-06 17:57:31 +10:00
Dion Moult d92121a62d ifcviewer: move cullModelCpuCompute + cullModelCpuUpload into ViewportCore (#84-p)
CPU cull (frustum + contribution + LOD + opaque/transparent partition)
and its companion GPU-upload step now live in ViewportCore. The HiZ
occlusion test stays in VW — the pyramid + async readback machinery
hasn't migrated yet — and is plumbed through a
ViewportCore::HizOccludedFn callback the render path binds when HiZ
is enabled-and-fresh. Null callback means "no occlusion test", which
keeps the cull path host-agnostic.

extractFrustumPlanes + aabbInFrustum moved up into CameraMath.h so
both VW's render() (where the planes are extracted) and core's cull
(where they're tested) can share without one #including the other.

LOD-debug counters (lod1_dbg_count_, lod0_dbg_eligible_count_,
lod0_dbg_no_lod1_count_, lod1_dbg_tris_saved_) moved to core too —
they're written by cull and read/reset by VW's still-here per-frame
[frame] heartbeat through reference aliases.
2026-06-06 17:21:29 +10:00
Dion Moult d86a5af662 ifcviewer: move driveStreamingLoads into ViewportCore (#84-o)
The per-frame streaming residency driver — LRU/priority eviction,
worker-result drain, candidate selection, click-and-track diagnostic,
sync-fallback for screenshot capture — now lives in ViewportCore.
ViewportWindow::driveStreamingLoads is a one-line forwarder.

Streaming-related state moves to core with reference aliases on VW:
streaming_{loads,more_pending,candidates,evictions_{lru,pri},drained,
blocked_oom}_this_frame_, streaming_debug_, tracked_{object_id,
chunk_mid,chunk_idx,was_resident}_, and pending_screenshot_path_. The
pick handler and bench-warm gate (still VW) read/write through the
aliases unchanged.

Qt-isms in the body were replaced en route:
- QFileInfo(...).completeBaseName() → std::filesystem::path::stem()
- requestUpdate() → host_->requestFrame()
- QString::number(x, 'f', N) in numeric logs → raw double / int (we lose
  fixed-precision in a couple of diag lines; acceptable tradeoff).

host_->requestFrame() means the streaming loop is now host-agnostic:
the WebViewportHost will provide its own requestAnimationFrame
equivalent when it lands.
2026-06-06 17:12:15 +10:00
Dion Moult 077080b318 ifcviewer: move chunk residency helpers (buildChunkBindGroup + applyStreamedChunk + loadChunkBytesAndUploadGpu + unloadChunk + makeChunkRequest) into ViewportCore (#84-n)
The chunk-state machine that mediates between the streaming pool and the
per-chunk WGPU bind groups now lives in ViewportCore. ViewportWindow's
remaining streaming code (driveStreamingLoads, finalizeModel) calls
through to core_.applyStreamedChunk / core_.unloadChunk /
core_.loadChunkBytesAndUploadGpu, and the chunk request builder is a
static helper on ViewportCore so VW's still-here driveStreamingLoads can
enqueue requests against streaming_thread_ without reimplementing it.

streaming_frame_idx_ moved to core (alongside the residency clock),
aliased on VW so the inline streaming logic stays compiling. The
mesh-volume side effect inside applyStreamedChunk now fires a
std::function<void()> callback (core_.on_volume_dirty_) instead of
reaching into ViewportWindow::updateVolumeReadout — VW wires the
callback in its ctor, non-Qt hosts leave it null and pay nothing.

computeMeshLocalVolumeQuantised moved to ViewportCore.cpp's anonymous
namespace; it was only called by applyStreamedChunk.
2026-06-06 16:33:31 +10:00
Dion Moult ea24851a51 ifcviewer: move section_planes_ + xray_alpha_cap_ + updateFrameUniforms into ViewportCore (#84-m)
Per-frame uniform packing now lives in ViewportCore::updateFrameUniforms,
which reads the camera (via the already-migrated buildViewProj), the
section_planes_ vector, and the xray_alpha_cap_ scalar — all of which
have moved into ViewportCore alongside frame_uniform_buffer_.
ViewportWindow keeps reference-aliases on section_planes_ and
xray_alpha_cap_ so the section-tool and X-ray toggle (still Qt-input-
bound, still living in VW) keep compiling unchanged. The render-path
caller in VW::render now does core_.updateFrameUniforms().

Extracted SectionPlane into its own Qt-free header (SectionPlane.h)
so ViewportCore doesn't have to include OverlayRenderer.h's QString /
QHash. OverlayRenderer.h re-exports it.
2026-06-05 20:56:24 +10:00
Dion Moult 2b43e6f7e0 ifcviewer: move initWgpu + probeAndCreatePool + shutdown into ViewportCore (#84-l)
The instance/adapter/device/queue/pool/surface-format wgpu lifecycle now
lives in ViewportCore — including the OOM-scoped pool size probe and
the worker-thread startup. ViewportWindow::initWgpu becomes a Qt shell
that handles env-var tuning + nav-button preset wiring, then delegates
to core_.initWgpu(); the VW-only pipeline builders (HiZ, edge, overlays,
pick) still run after. ViewportWindow::shutdown drops the VW-only
resources (depth, msaa, hiz, edge, overlays, pick) and lets
core_.shutdown() release the shared wgpu handles it now owns.

The wgpu-native log callback (wgpuSetLogCallback / WGPULogLevel) is
gated on !__EMSCRIPTEN__: it's not part of the W3C spec header, and
the emdawnwebgpu port doesn't ship wgpu.h — validation errors there
land in the browser console regardless.

Drive-by: update test_federation to compare HomeView::target as
Eigen::Vector3f (left stale by #79 when QVector3D was retired).
2026-06-05 18:06:25 +10:00
Dion Moult 66a21923b8 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.
2026-06-05 16:39:01 +10:00
Dion Moult 8cf7d4346d ifcviewer: move volume readout helpers into ViewportCore (#84-j)
Tiny followup to #84-i — move the const-lookup volume helpers used by
bonsai's measurement HUD:

  double volumeOfObjects(const std::vector<uint32_t>&) const
  vector<pair<uint32_t, double>> volumesPerObject(
                              const std::vector<uint32_t>&) const

The det3OfPlacement static helper moves with them into ViewportCore.cpp's
anonymous namespace (the original kept its mirror in
ViewportWindow.cpp; ViewportWindow's own internal callers are gone now
since these methods moved).

Pure read of models_gpu_ + mesh_local_volumes — all in core already.
Trivial transplant.

Builds: desktop / bonsai / web all green. Tests 100/100.
2026-06-05 15:37:47 +10:00
Dion Moult 707bb8f5d4 ifcviewer: move camera mutators + AABB helpers into ViewportCore (#84-i)
Move the cluster of camera-state mutators + per-object AABB helpers
now that the camera fields all live in ViewportCore. CameraState
struct is canonical in core; ViewportWindow keeps a `using` alias
so bonsai's HomeView round-trip (Commands.cpp setHome / restoreHome)
compiles unchanged.

Moved:
  void viewAll()
  void setCamera(...) — pitch + distance clamping included
  void setStandardView(yaw, pitch) — bypasses clamp for ±90°
  void toggleProjection()
  std::string cameraString() const
  CameraState cameraState() const
  void frameAabb(mn, mx, padding)
  bool computeObjectAabb(id, float[3], float[3]) const
  bool computeObjectAabb(id, Eigen::Vector3f&, Eigen::Vector3f&) const

ViewportWindow keeps thin forwarders for the public ones (bonsai
calls them). setCamera additionally flips initial_view_applied_
on the VW side — the auto-viewAll suppression flag isn't in core
yet because the trigger for auto-viewAll lives in the still-in-VW
applyCachedModel path.

The isExposed()+requestUpdate() Qt pattern inside the moved bodies
becomes host_->requestFrame(); two viewAll/toggleProjection diagnostic
prints become fprintf since Log::info() doesn't reach into core.cpp
through the Qt logging surface.

Builds: desktop / bonsai / web all green. Tests 100/100.
2026-06-05 15:27:39 +10:00
Dion Moult 14e7c9fc42 ifcviewer: move camera math into ViewportCore (#84-h)
Move the three camera-math methods that compute view/projection
matrices, scene bounds, and per-chunk screen footprint for the
streaming priority signal:

  void  buildViewProj(Eigen::Matrix4f&, Eigen::Matrix4f&) const
  bool  computeSceneAabb(float[3], float[3]) const
  float chunkScreenAreaPx(const ModelGpuData::Chunk&,
                          const Eigen::Matrix4f&) const

Plus the orbitEye helper (anonymous namespace in ViewportCore.cpp;
the qDegreesToRadians dep got swapped for an inline M_PI/180 constant).

ViewportWindow.cpp's 9 internal callers (cull, streaming, pick,
render, debug) updated to use core_.buildViewProj() etc. The
buildViewProj forwarder stays out of ViewportWindow.h since no
external caller needs it — bonsai/minimal both go through
public API methods like viewAll which still wrap core_ access
on the VW side.

Builds: desktop / bonsai / web all green. Tests 100/100.
2026-06-05 14:43:25 +10:00
Dion Moult 8da0993457 ifcviewer: move scene mutators + releaseWgpuModelGpuData into ViewportCore (#84-f)
Move the eight scene-mutation methods that drive bonsai's load/unload
and georeference setters, plus the per-model GPU teardown helper.
All are mechanical transplants — no logic change — so behaviour stays
identical; only the owner has changed.

Methods moved (ViewportWindow public-API methods stay as forwarders
to keep the bonsai-side callers compiling):
  removeModel / resetScene / hideModel / showModel
  setFederatedFalseOrigin
  setModelCoordinateOperation
  setModelTransformation
  recomposeAndUploadModel

State moved:
  bool wgpu_initialized_   (storage → core_, alias kept in VW for
                            the initWgpu call site that still flips
                            it; goes when initWgpu moves)

Free function moved:
  releaseWgpuModelGpuData(ModelGpuData&, BufferPool&) → ViewportCore.cpp
  (must live in IfcViewerCore now that ViewportCore.cpp's
   removeModel / resetScene call it; ViewportWindow.cpp's remaining
   two call sites continue to resolve through ModelGpuData.h's
   declaration — same linker view, different definition TU)

The `if (isExposed()) requestUpdate()` Qt pattern inside the moved
bodies became `host_->requestFrame()` since ViewportCore can't see
QWindow; the desktop ViewportHost override at the bottom of
ViewportWindow.cpp continues to translate that into requestUpdate().

Builds: desktop / bonsai / web all green. Tests 100/100.
2026-06-05 14:04:57 +10:00
Dion Moult b2fe9c4a71 ifcviewer: move const-lookup methods into ViewportCore (#84-e)
Move two pure-read methods (no GPU touch, no Qt) that the bonsai
measurement / federation-origin paths use:

  bool findInstance(uint32_t, InstanceLookup&)        const
  bool firstGeometryPointWorldM(uint32_t, Vector3d&)  const

ViewportWindow keeps both public-API method names — they now forward
to core_ for the implementation so existing callers in
bonsaiviewer/Measurement.cpp + Federation hooks don't have to change.
The InstanceLookup type also stays a `using` alias in ViewportWindow
(was added in #74).

Both methods were already de-Qt'd (`findInstance` delegates to
InstanceCompose; `firstGeometryPointWorldM` is pure Eigen). The move
is a straight transplant — no behaviour change.

Builds: desktop / bonsai / web all green. Tests 100/100.
2026-06-05 13:54:28 +10:00
Dion Moult ad6822ac85 ifcviewer: move composeInstanceFromPlacement into ViewportCore (#84-d)
First method-body migration. composeInstanceFromPlacement composes the
federated-false-origin × model-transformation × coordinate-operation ×
placement chain and re-derives the world AABB; it's a small,
self-contained method that only reads scene state and one matrix.

Moved:
  Eigen::Matrix4d federated_false_origin_meters_   (storage → core_)
  void composeInstanceFromPlacement(InstanceCpu&, ...) (body → core_)

ViewportWindow keeps:
  - alias reference to federated_false_origin_meters_ (existing
    setFederatedFalseOrigin call site still writes through it)
  - no method declaration — internal callers route through core_

Internal caller (recomposeAndUploadModel) now invokes
core_.composeInstanceFromPlacement; once recomposeAndUploadModel
itself moves into ViewportCore the call shortens back.

Pattern for the rest of #84: state moves, then method body moves,
then internal callers update. Each commit leaves desktop / bonsai /
web green and tests 100/100. This is one of many such steps.
2026-06-05 13:32:19 +10:00
Dion Moult c314dd3ca8 ifcviewer: scaffold ViewportHost + ViewportCore (Path A step 1)
Define the boundary the Path-A web-bring-up refactor will move things
across:

- ViewportHost.h is the embedder interface — surface creation,
  framebuffer geometry, frame scheduling, quit, and notification
  callbacks (onObjectPicked, onToolModeChanged, …). Desktop hosts
  forward notifications to Q_SIGNALS; the future web host pushes
  them to JS callbacks.

- ViewportCore.{h,cpp} is the platform-agnostic render-core target.
  Empty today — the body fills in across the #78-#86 sequence as
  each Qt subsystem (matrices, vectors, strings, timers, render
  path, input) gets de-Qt'd and moved over.

- ViewportWindow now multiply-inherits ViewportHost alongside QWindow
  and implements the host overrides as thin forwarders: createSurface
  returns the cached surface_, requestFrame -> requestUpdate, quit ->
  QCoreApplication::quit, onObjectPicked -> emit objectPicked.
  Renamed the DPR accessor `dpr()` (vs `devicePixelRatio`) to avoid
  the inherited-virtual clash with QWindow's qreal-returning version.

No method movement yet — this is purely the architectural scaffold so
subsequent commits have a destination.
2026-06-04 19:34:51 +10:00