Files
IfcOpenShell/src/ifcviewer/OverlayRenderer.h
T
Dion Moult 8ab5c31e75 refactor: merge ifcviewer-wgpu into ifcviewer, drop Wgpu prefix
The GL backend is gone (task #53). The wgpu/non-wgpu folder split and
the Wgpu* class prefix were both disambiguation artefacts from the
overlap period — now pure dead weight.

## Folder + library merge

* `src/ifcviewer-wgpu/`  → folded into `src/ifcviewer/` (git mv tracks
  every file as a rename so blame/log history survives).
* `src/ifcviewer-wgpu-minimal/` → `src/ifcviewer-minimal/` (the exe was
  already named `IfcViewerMinimal`; this just brings the folder + CMake
  target name into line).
* `src/ifcviewer-wgpu/tests/test_wgpu_{selection,visibility}.cpp` →
  `src/ifcviewer/tests/test_{selection,visibility}.cpp`, folded into
  the existing `add_ifcviewer_unit_test(...)` helper.
* The `IfcViewerWgpu` static library is dissolved — its sources become
  part of the unified `IfcViewer` static library, which now bundles
  scene/loader + renderer in one target. The pre-merge circular
  dependency (IfcViewer linking IfcViewerWgpu just to get the
  ViewportWindow.h include path that SceneLoader.h needs) goes away.
* The wgpu-native FetchContent block, the Cocoa/QuartzCore link on
  Apple, the OBJCXX-enabled `.mm` source, and the wgpu-native runtime
  install all move into `src/ifcviewer/CMakeLists.txt` unchanged.

## Type renames (Wgpu prefix dropped from every Wgpu* identifier)

  WgpuAreaMeasurement   → AreaMeasurement
  WgpuBufferPool        → BufferPool
  WgpuLengthMeasurement → LengthMeasurement
  WgpuMetalSurface      → MetalSurface
  WgpuModelGpuData      → ModelGpuData
  WgpuOverlayFrame      → OverlayFrame
  WgpuOverlayRenderer   → OverlayRenderer
  WgpuSectionPlane      → SectionPlane
  WgpuSelectionState    → SelectionState
  WgpuStreamingLoader   → StreamingLoader
  WgpuStreamingThread   → StreamingThread
  WgpuViewportWindow    → ViewportWindow
  WgpuVisibilityState   → VisibilityState

  CMake target IfcViewerWgpuMinimal → IfcViewerMinimal (exe name was
  already this since wgpu shipped as default).

Deliberately kept: `onWgpuLog` (wgpu-native log callback — names a
binding to an external API, not one of *our* types), and the WGPU*
enum/struct prefixes from wgpu-native's own headers. `WgpuMemProbe`
lives in the separate `src/wgpu-mem-probe/` standalone diagnostic
project and isn't touched.

## Include-path updates

Every `#include "../ifcviewer-wgpu/Wgpu<X>.h"` → `"../ifcviewer/<X>.h"`,
every in-directory `#include "Wgpu<X>.h"` → `"<X>.h"`. Includes from
sibling subdirectories (modules/, etc.) are updated to point at
`../../../ifcviewer/` instead of `../../../ifcviewer-wgpu/`.

## cmake/CMakeLists.txt simplification

The redundant `add_subdirectory(ifcviewer-wgpu)` blocks (one inside
the BUILD_BONSAIVIEWER fan-in, one in the BONSAIVIEWER-less standalone
block) collapse into a single unconditional
`add_subdirectory(../src/ifcviewer ifcviewer)`. The standalone block
keeps only `wgpu-mem-probe` (the diagnostic tool, unrelated to the
viewer lib).

## Verification

* Full build green: `IfcViewer` static lib, `IfcViewerMinimal` exe,
  `BonsaiViewer` exe, all four pre-existing ifcviewer unit tests, and
  the two new-location tests (`test_selection`, `test_visibility`).
* No stray `Wgpu<X>` identifier remains across `src/ifcviewer/`,
  `src/bonsaiviewer/`, `src/ifcviewer-minimal/` (verified by grep).
* Renames tracked by git as `R` entries — `git log --follow` on
  ViewportWindow.cpp etc. continues to show history through the move.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 11:11:14 +10:00

340 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#ifndef WGPUOVERLAYRENDERER_H
#define WGPUOVERLAYRENDERER_H
#include <QHash>
#include <QMatrix4x4>
#include <QPoint>
#include <QString>
#include <QVector3D>
#include <webgpu/webgpu.h>
#include <cstdint>
#include <vector>
// Per-frame snapshot of viewport state that every overlay needs. Built once
// at the top of render() and passed by const-ref to each encodeX() call so
// the overlay renderer never reaches back into the viewport.
struct OverlayFrame {
QMatrix4x4 view_proj;
QVector3D camera_target;
float camera_distance = 5.0f;
float camera_yaw_deg = 0.0f;
float camera_pitch_deg = 0.0f;
float camera_fov_y_deg = 45.0f;
int viewport_w_px = 0;
int viewport_h_px = 0;
int device_pixel_ratio = 1;
};
// One section plane as the visualizer consumes it. The viewport owns the
// authoritative state vector (the section tool mutates it); the overlay
// reads from a non-owning span every frame. Held by value because the
// struct is small and copies happen at most six times per frame.
struct SectionPlane {
QVector3D n; // unit normal (camera-facing after auto-flip)
float d; // -dot(n, origin)
QVector3D origin; // surface point at the moment the plane was added
float visual_radius; // unused by the visualizer (kept here so the
// section tool's state struct round-trips
// through this overlay-facing definition).
};
// All viewport overlays in one place: axis indicator (corner + pivot),
// section plane gizmos, and the marquee drag rect. Mirrors GL's
// OverlayRenderer split so ViewportWindow.cpp doesn't have to
// carry ~1.5k lines of pipeline plumbing.
//
// Lifecycle: init() once after the device is up, destroy() before the
// device dies. Pipelines are immutable after init; only per-frame
// uniforms get re-written.
//
// Threading: all calls are main-thread only — they touch the wgpu queue.
class OverlayRenderer {
public:
OverlayRenderer() = default;
~OverlayRenderer();
OverlayRenderer(const OverlayRenderer&) = delete;
OverlayRenderer& operator=(const OverlayRenderer&) = delete;
bool init(WGPUInstance instance, WGPUDevice device, WGPUQueue queue,
WGPUTextureFormat surface_format, int sample_count);
void destroy();
// ---- Inside the main MSAA pass, after geometry ----
// Both share depth with the scene so they're correctly occluded.
// Orbit pivot indicator. `visible` is the viewport's UI gate (orbit
// drag / wheel-zoom afterglow). When false this is a cheap no-op.
void encodePivot(WGPURenderPassEncoder pass,
const OverlayFrame& f,
bool visible);
// Per-plane wireframe gizmo (2 × 2 m quad outline + arrow shaft +
// arrow head). Drawn at each plane's origin in its local basis;
// colour comes from Bonsai's decorator_color_error.
void encodeSectionGizmos(WGPURenderPassEncoder pass,
const OverlayFrame& f,
const std::vector<SectionPlane>& planes);
// Replace the highlight-triangle list. `world_xyz` is 3 floats per
// vertex, 3 vertices per triangle, in world space (post-composed-
// transform). Empty disables the overlay. Color is RGBA in [0, 1] —
// alpha < 1 gives the translucent patch shading the Area tool uses.
// Drawn inside the main MSAA pass so depth-test hides patches
// behind closer geometry; depth-write stays off so later overlays
// can still draw over the highlight.
void setHighlightTriangles(const std::vector<float>& world_xyz,
float r, float g, float b, float a);
void encodeHighlightTriangles(WGPURenderPassEncoder pass,
const OverlayFrame& f);
// One stylistic group of world-space line segments. Mirrors GL
// OverlayRenderer::LineGroup so callers can target either backend
// with one struct.
struct LineGroup {
std::vector<float> world_xyz; // 6 floats per segment (a, b)
float color[4] = {1, 1, 1, 1}; // inner color
float stroke_color[4] = {0, 0, 0, 1}; // halo (alpha 0 = no stroke)
float line_width = 1.5f; // inner full-width (px)
float stroke_extra = 0.5f; // halo per side (px)
float dash_period_px = 0.0f; // 0 = solid
float dash_on_ratio = 0.6f; // [0..1], only when period > 0
};
// Replace the overlay-line set. Each call CPU-expands every segment
// into six vertices (two triangles), uploads the concatenated
// expanded buffer once, and writes one uniform slot per group; the
// next encodeOverlayLines() draws them in order. Empty `groups`
// clears the set so subsequent encodes are no-ops.
void setOverlayLines(const std::vector<LineGroup>& groups);
// Encode the most-recently set line groups. One draw per group with
// a dynamic uniform offset; the shader handles stroke + dash from
// per-group uniforms. Drawn inside the main MSAA pass so the lines
// are depth-tested against geometry.
void encodeOverlayLines(WGPURenderPassEncoder pass,
const OverlayFrame& f);
// Replace the overlay-point set. World-space positions are CPU-
// expanded into screen-space quads at encode time. `pixel_size` is
// the inner-disc diameter (px); when `stroke_a > 0` each quad picks
// up a `stroke_extra`-pixel halo per side. Empty `world_xyz` clears
// the set. Mirrors GL OverlayRenderer::setOverlayPoints.
void setOverlayPoints(const std::vector<float>& world_xyz,
float r, float g, float b, float a,
float pixel_size,
float stroke_r, float stroke_g,
float stroke_b, float stroke_a,
float stroke_extra);
// Encode the most-recently set point list. Single draw covering all
// points; the shader does the sprite-distance pick + AA. Drawn
// inside the main MSAA pass so depth-test correctly hides points
// behind closer geometry.
void encodeOverlayPoints(WGPURenderPassEncoder pass,
const OverlayFrame& f);
// World-anchored text label. Mirrors GL OverlayRenderer::Label so
// measure-tool readouts can target either backend.
struct Label {
float world_pos[3];
QString text;
};
void setOverlayLabels(const std::vector<Label>& labels);
// Top-left HUD text (tool prompts, length / area readouts). Empty
// string hides it. Each newline starts a new line in the same rect.
void setHudText(const QString& text);
// Encode all currently-set labels + the HUD. Per-string textures are
// rasterised via QPainter into a small QImage on first sight and
// cached by content; per-frame work is just projection, vertex
// assembly, and one draw per visible label. Drawn on the resolved
// surface (no depth test, no MSAA), so labels stack on top of every
// overlay above.
void encodeLabels(WGPUCommandEncoder enc,
WGPUTextureView surface_view,
const OverlayFrame& f);
// ---- After the edge silhouette pass, on the resolved surface ----
// Corner axis gizmo (bottom-left, 110×110 px). Independent ortho
// projection — only the camera direction matters.
void encodeCornerAxis(WGPUCommandEncoder enc,
WGPUTextureView surface_view,
const OverlayFrame& f);
// Marquee box-select drag rect (translucent fill + thick outline).
// No-op when `active` is false.
void encodeMarquee(WGPUCommandEncoder enc,
WGPUTextureView surface_view,
const OverlayFrame& f,
QPoint start_logical_px,
QPoint current_logical_px,
bool active);
// Shared with the viewport's main FrameUniforms: same cap so the
// viewport's clip-plane array and the gizmo visualiser agree on
// how many planes can ever be active.
static constexpr int kMaxSectionPlanes = 6;
private:
bool buildAxisIndicator();
bool buildSectionVisualizer();
bool buildMarquee();
bool buildOverlayLines();
bool buildOverlayPoints();
bool buildHighlightTriangles();
bool buildLabels();
// Rasterise a single string at `font_pt` with dark-grey padded
// background + white text, upload as an RGBA8 texture, build the
// matching bind group. Width/height are the texture's physical-pixel
// dimensions and are also what encodeLabels uses to size the quad.
struct LabelTexture {
WGPUTexture texture = nullptr;
WGPUTextureView view = nullptr;
WGPUBindGroup bind_group = nullptr;
int width_px = 0;
int height_px = 0;
};
LabelTexture* getOrCreateLabelTexture(const QString& cache_key,
const QString& text,
int font_pt,
int dpr);
void releaseLabelTextures();
WGPUInstance instance_ = nullptr;
WGPUDevice device_ = nullptr;
WGPUQueue queue_ = nullptr;
WGPUTextureFormat surface_format_ = WGPUTextureFormat_Undefined;
int sample_count_ = 1;
// ---- Axis indicator (shared shape, three pipelines) ----
// Slot 0 = corner gizmo. Slots 1/2 = pivot visible/x-ray.
WGPUShaderModule axis_shader_module_ = nullptr;
WGPUBindGroupLayout axis_bgl_ = nullptr;
WGPUPipelineLayout axis_pipeline_layout_ = nullptr;
WGPURenderPipeline axis_pivot_pipeline_ = nullptr;
WGPURenderPipeline axis_pivot_xray_pipeline_ = nullptr;
WGPURenderPipeline axis_corner_pipeline_ = nullptr;
WGPUBuffer axis_vertex_buffer_ = nullptr;
WGPUBuffer axis_uniform_buffer_ = nullptr;
WGPUBindGroup axis_bind_group_ = nullptr;
static constexpr uint32_t kAxisUniformSlotSize = 256;
// ---- Section plane gizmos (1 pipeline, dynamic offset per plane) ----
WGPUShaderModule section_shader_module_ = nullptr;
WGPUBindGroupLayout section_bgl_ = nullptr;
WGPUPipelineLayout section_pipeline_layout_ = nullptr;
WGPURenderPipeline section_pipeline_ = nullptr;
WGPUBuffer section_vertex_buffer_ = nullptr;
WGPUBuffer section_uniform_buffer_ = nullptr;
WGPUBindGroup section_bind_group_ = nullptr;
static constexpr uint32_t kSectionUniformSlotSize = 256;
// ---- Marquee (fill + outline pipelines, one uniform buffer) ----
WGPUShaderModule marquee_shader_module_ = nullptr;
WGPUBindGroupLayout marquee_bgl_ = nullptr;
WGPUPipelineLayout marquee_pipeline_layout_ = nullptr;
WGPURenderPipeline marquee_pipeline_ = nullptr;
WGPURenderPipeline marquee_fill_pipeline_ = nullptr;
WGPUBuffer marquee_vertex_buffer_ = nullptr;
WGPUBuffer marquee_fill_vertex_buffer_ = nullptr;
WGPUBuffer marquee_uniform_buffer_ = nullptr;
WGPUBindGroup marquee_bind_group_ = nullptr;
// ---- Overlay lines (per-group dynamic offset, resizable buffers) ----
// Vertex buffer holds the concatenated expansion of every group's
// segments (8 floats × 6 verts per segment). Uniform buffer holds
// one 256-byte slot per group; the bind group binds a single 128-byte
// window that the encoder rebinds via dynamic offset.
WGPUShaderModule overlay_line_shader_module_ = nullptr;
WGPUBindGroupLayout overlay_line_bgl_ = nullptr;
WGPUPipelineLayout overlay_line_pipeline_layout_ = nullptr;
WGPURenderPipeline overlay_line_pipeline_ = nullptr;
WGPUBuffer overlay_line_vertex_buffer_ = nullptr;
uint64_t overlay_line_vertex_capacity_ = 0;
WGPUBuffer overlay_line_uniform_buffer_ = nullptr;
uint32_t overlay_line_uniform_slots_ = 0;
WGPUBindGroup overlay_line_bind_group_ = nullptr;
static constexpr uint32_t kOverlayLineUniformSlotSize = 256;
// Per-group draw record. setOverlayLines() populates one per
// LineGroup; encodeOverlayLines() iterates and issues one draw each.
struct OverlayLineDraw {
uint32_t first_vertex = 0;
uint32_t vertex_count = 0;
};
std::vector<OverlayLineDraw> overlay_line_draws_;
// ---- Overlay points (sprite-style, quad-expanded per point) ----
// Single draw per encode covering every point in the set. Vertex
// buffer holds 6 verts × 8 bytes per point (vec3 world + vec2 corner).
// Uniforms are global to the set (one inner + one stroke color).
WGPUShaderModule overlay_point_shader_module_ = nullptr;
WGPUBindGroupLayout overlay_point_bgl_ = nullptr;
WGPUPipelineLayout overlay_point_pipeline_layout_ = nullptr;
WGPURenderPipeline overlay_point_pipeline_ = nullptr;
WGPUBuffer overlay_point_vertex_buffer_ = nullptr;
uint64_t overlay_point_vertex_capacity_ = 0;
WGPUBuffer overlay_point_uniform_buffer_ = nullptr;
WGPUBindGroup overlay_point_bind_group_ = nullptr;
uint32_t overlay_point_vertex_count_ = 0;
// ---- Highlight triangles (translucent world-space triangle list) ----
// One pipeline + one uniform buffer (view_proj + RGBA tint). Vertex
// buffer grows on demand to fit the current set; empty set ⇒ encode
// is a no-op.
WGPUShaderModule highlight_shader_module_ = nullptr;
WGPUBindGroupLayout highlight_bgl_ = nullptr;
WGPUPipelineLayout highlight_pipeline_layout_ = nullptr;
WGPURenderPipeline highlight_pipeline_ = nullptr;
WGPUBuffer highlight_vertex_buffer_ = nullptr;
uint64_t highlight_vertex_capacity_ = 0;
WGPUBuffer highlight_uniform_buffer_ = nullptr;
WGPUBindGroup highlight_bind_group_ = nullptr;
uint32_t highlight_vertex_count_ = 0;
float highlight_color_[4] = {0, 0, 0, 0};
// ---- Labels + HUD text (textured quads, cached by content) ----
// One QPainter-rasterised QImage per unique text string, uploaded as
// an RGBA8 texture and re-used across frames. Per-frame work is
// projection + vertex assembly + draws; no allocation in the steady
// state. Bind groups are layout-shared across all label textures so
// every cache entry holds its own bind_group ready to bind.
WGPUShaderModule label_shader_module_ = nullptr;
WGPUBindGroupLayout label_bgl_ = nullptr;
WGPUPipelineLayout label_pipeline_layout_ = nullptr;
WGPURenderPipeline label_pipeline_ = nullptr;
WGPUSampler label_sampler_ = nullptr;
WGPUBuffer label_vertex_buffer_ = nullptr;
uint64_t label_vertex_capacity_ = 0;
QHash<QString, LabelTexture> label_tex_cache_;
std::vector<Label> labels_;
QString hud_text_;
};
#endif // WGPUOVERLAYRENDERER_H