mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-29 08:13:14 +00:00
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>
This commit is contained in:
@@ -134,11 +134,10 @@ if(APPLE)
|
||||
endif()
|
||||
|
||||
target_link_libraries(BonsaiViewer PRIVATE
|
||||
# IfcViewer still ships SceneLoader / Federation / GeometryStreamer /
|
||||
# SidecarBuilder until the GL ViewportWindow is deleted. IfcViewerWgpu
|
||||
# ships the live render path. Both link cleanly side-by-side.
|
||||
# IfcViewer is the unified scene + render lib (SceneLoader, Federation,
|
||||
# GeometryStreamer, SidecarBuilder, ViewportWindow, OverlayRenderer,
|
||||
# BufferPool, …) since the wgpu/ifcviewer merge.
|
||||
IfcViewer
|
||||
IfcViewerWgpu
|
||||
Qt${QT_VERSION}::Core
|
||||
Qt${QT_VERSION}::CorePrivate
|
||||
Qt${QT_VERSION}::Gui
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "../ifcviewer/AppSettings.h"
|
||||
#include "../ifcviewer/Federation.h"
|
||||
#include "../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
#include "../ifcviewer/ViewportWindow.h"
|
||||
#include "SessionState.h"
|
||||
#include "components/Buttons.h"
|
||||
#include "components/Panel.h"
|
||||
@@ -529,8 +529,8 @@ void MainWindow::setupLoader() {
|
||||
QMessageBox::warning(this, "Bonsai Viewer", message);
|
||||
});
|
||||
|
||||
connect(viewport_widget_->viewport(), &WgpuViewportWindow::frameStatsUpdated, this,
|
||||
[this](const WgpuViewportWindow::FrameStats& s) {
|
||||
connect(viewport_widget_->viewport(), &ViewportWindow::frameStatsUpdated, this,
|
||||
[this](const ViewportWindow::FrameStats& s) {
|
||||
if (!status_perf_label_->isVisible()) return;
|
||||
status_perf_label_->setText(
|
||||
QString("%1 fps | %2 ms | %3/%4 obj | %5/%6 tri | %7 draws")
|
||||
@@ -542,7 +542,7 @@ void MainWindow::setupLoader() {
|
||||
.arg(s.total_triangles)
|
||||
.arg(s.gl_draw_calls));
|
||||
});
|
||||
connect(viewport_widget_->viewport(), &WgpuViewportWindow::objectPicked,
|
||||
connect(viewport_widget_->viewport(), &ViewportWindow::objectPicked,
|
||||
this, [this](uint32_t object_id) {
|
||||
session_state_->setSelectedObjectId(object_id);
|
||||
session_state_->notifySelectionChanged();
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include "Measurement.h"
|
||||
|
||||
#include "WgpuViewportWindow.h"
|
||||
#include "ViewportWindow.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <Eigen/Dense>
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
namespace {
|
||||
|
||||
double meshLocalVolume(const WgpuViewportWindow::MeshTriangles& tris) {
|
||||
double meshLocalVolume(const ViewportWindow::MeshTriangles& tris) {
|
||||
// Signed tetrahedra from the origin: V = sum( a · (b × c) ) / 6.
|
||||
// Absolute value at the end so winding convention doesn't matter.
|
||||
double sum = 0.0;
|
||||
@@ -67,7 +67,7 @@ double det3(const double M[16]) {
|
||||
|
||||
} // namespace
|
||||
|
||||
double volumeOfObjects(WgpuViewportWindow& vp,
|
||||
double volumeOfObjects(ViewportWindow& vp,
|
||||
const std::vector<uint32_t>& object_ids) {
|
||||
if (object_ids.empty()) return 0.0;
|
||||
|
||||
@@ -77,14 +77,14 @@ double volumeOfObjects(WgpuViewportWindow& vp,
|
||||
std::unordered_map<uint64_t, std::vector<double>> by_mesh;
|
||||
by_mesh.reserve(object_ids.size());
|
||||
for (uint32_t oid : object_ids) {
|
||||
WgpuViewportWindow::InstanceLookup lk;
|
||||
ViewportWindow::InstanceLookup lk;
|
||||
if (!vp.findInstance(oid, lk)) continue;
|
||||
const uint64_t key = (uint64_t(lk.model_id) << 32) | lk.mesh_id;
|
||||
by_mesh[key].push_back(std::abs(det3(lk.placement_transformation)));
|
||||
}
|
||||
|
||||
double total = 0.0;
|
||||
WgpuViewportWindow::MeshTriangles tris;
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
for (const auto& [key, dets] : by_mesh) {
|
||||
const uint32_t model_id = uint32_t(key >> 32);
|
||||
const uint32_t mesh_id = uint32_t(key & 0xffffffffu);
|
||||
@@ -96,7 +96,7 @@ double volumeOfObjects(WgpuViewportWindow& vp,
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint32_t, double>>
|
||||
volumesPerObject(WgpuViewportWindow& vp,
|
||||
volumesPerObject(ViewportWindow& vp,
|
||||
const std::vector<uint32_t>& object_ids) {
|
||||
std::vector<std::pair<uint32_t, double>> out;
|
||||
if (object_ids.empty()) return out;
|
||||
@@ -108,9 +108,9 @@ volumesPerObject(WgpuViewportWindow& vp,
|
||||
std::unordered_map<uint64_t, double> mesh_vol_local;
|
||||
mesh_vol_local.reserve(object_ids.size());
|
||||
|
||||
WgpuViewportWindow::MeshTriangles tris;
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
for (uint32_t oid : object_ids) {
|
||||
WgpuViewportWindow::InstanceLookup lk;
|
||||
ViewportWindow::InstanceLookup lk;
|
||||
if (!vp.findInstance(oid, lk)) continue;
|
||||
|
||||
const uint64_t key = (uint64_t(lk.model_id) << 32) | lk.mesh_id;
|
||||
@@ -238,7 +238,7 @@ constexpr double kCoplanarDot = 0.9999; // ~0.81° tolerance
|
||||
|
||||
AreaMeasurement::AreaMeasurement() = default;
|
||||
|
||||
void AreaMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
void AreaMeasurement::clear(ViewportWindow& vp) {
|
||||
mesh_cache_.clear();
|
||||
selected_.clear();
|
||||
total_area_m2_ = 0.0;
|
||||
@@ -246,7 +246,7 @@ void AreaMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
vp.setOverlayLabels({});
|
||||
}
|
||||
|
||||
void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) {
|
||||
void AreaMeasurement::rebuildHighlight(ViewportWindow& vp) {
|
||||
// Push every selected triangle's three world-space vertices to the
|
||||
// overlay. Mesh-local positions × per-instance composed transform.
|
||||
std::vector<float> world_xyz;
|
||||
@@ -287,7 +287,7 @@ void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) {
|
||||
by_object[object_id].push_back(&sel);
|
||||
}
|
||||
|
||||
std::vector<WgpuOverlayRenderer::Label> labels;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
for (const auto& [obj_id, sels] : by_object) {
|
||||
if (sels.empty()) continue;
|
||||
// All tris belonging to one object share its mesh + transform.
|
||||
@@ -350,7 +350,7 @@ void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) {
|
||||
|
||||
// Centroid → world via the instance's composed transform.
|
||||
const float* M = any.composed_transform;
|
||||
WgpuOverlayRenderer::Label lbl;
|
||||
OverlayRenderer::Label lbl;
|
||||
lbl.world_pos[0] = float(M[0]*cx + M[4]*cy + M[8]*cz + M[12]);
|
||||
lbl.world_pos[1] = float(M[1]*cx + M[5]*cy + M[9]*cz + M[13]);
|
||||
lbl.world_pos[2] = float(M[2]*cx + M[6]*cy + M[10]*cz + M[14]);
|
||||
@@ -361,14 +361,14 @@ void AreaMeasurement::rebuildHighlight(WgpuViewportWindow& vp) {
|
||||
vp.setOverlayLabels(labels);
|
||||
}
|
||||
|
||||
AreaMeasurement::MeshCache* AreaMeasurement::meshCache(WgpuViewportWindow& vp,
|
||||
AreaMeasurement::MeshCache* AreaMeasurement::meshCache(ViewportWindow& vp,
|
||||
uint32_t model_id,
|
||||
uint32_t mesh_id) {
|
||||
const uint64_t key = (uint64_t(model_id) << 32) | uint64_t(mesh_id);
|
||||
auto it = mesh_cache_.find(key);
|
||||
if (it != mesh_cache_.end()) return &it->second;
|
||||
|
||||
WgpuViewportWindow::MeshTriangles tris;
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
if (!vp.readbackMeshTriangles(model_id, mesh_id, tris)) return nullptr;
|
||||
|
||||
MeshCache c;
|
||||
@@ -397,8 +397,8 @@ AreaMeasurement::MeshCache* AreaMeasurement::meshCache(WgpuViewportWindow& vp,
|
||||
return &mesh_cache_.emplace(key, std::move(c)).first->second;
|
||||
}
|
||||
|
||||
void AreaMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool alt) {
|
||||
WgpuViewportWindow::MeshLocalPick pick;
|
||||
void AreaMeasurement::onPick(ViewportWindow& vp, int x, int y, bool alt) {
|
||||
ViewportWindow::MeshLocalPick pick;
|
||||
if (!vp.pickMeshLocalAt(x, y, pick)) return;
|
||||
|
||||
MeshCache* cache = meshCache(vp, pick.model_id, pick.mesh_id);
|
||||
@@ -607,10 +607,10 @@ constexpr float DOT_HALO = 1.0f;
|
||||
constexpr float DASH_PERIOD = 9.0f; // px
|
||||
constexpr float DASH_ON_RATIO = 0.55f; // 5 on, 4 off
|
||||
|
||||
WgpuOverlayRenderer::LineGroup makeGroup(std::vector<float> xyz,
|
||||
OverlayRenderer::LineGroup makeGroup(std::vector<float> xyz,
|
||||
float r, float g, float b,
|
||||
bool dashed = false) {
|
||||
WgpuOverlayRenderer::LineGroup gp;
|
||||
OverlayRenderer::LineGroup gp;
|
||||
gp.world_xyz = std::move(xyz);
|
||||
gp.color[0] = r; gp.color[1] = g; gp.color[2] = b; gp.color[3] = 1.0f;
|
||||
gp.stroke_color[0] = 0.0f; gp.stroke_color[1] = 0.0f;
|
||||
@@ -635,10 +635,10 @@ void pushSeg(std::vector<float>& xyz,
|
||||
xyz.insert(xyz.end(), b.begin(), b.end());
|
||||
}
|
||||
|
||||
WgpuOverlayRenderer::Label makeLabel(const std::array<float, 3>& a,
|
||||
OverlayRenderer::Label makeLabel(const std::array<float, 3>& a,
|
||||
const std::array<float, 3>& b,
|
||||
const QString& text) {
|
||||
WgpuOverlayRenderer::Label lbl;
|
||||
OverlayRenderer::Label lbl;
|
||||
lbl.world_pos[0] = 0.5f * (a[0] + b[0]);
|
||||
lbl.world_pos[1] = 0.5f * (a[1] + b[1]);
|
||||
lbl.world_pos[2] = 0.5f * (a[2] + b[2]);
|
||||
@@ -646,7 +646,7 @@ WgpuOverlayRenderer::Label makeLabel(const std::array<float, 3>& a,
|
||||
return lbl;
|
||||
}
|
||||
|
||||
void pushDots(WgpuViewportWindow& vp, const std::vector<float>& xyz) {
|
||||
void pushDots(ViewportWindow& vp, const std::vector<float>& xyz) {
|
||||
vp.setOverlayPoints(xyz,
|
||||
/*inner*/ 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
/*size*/ DOT_SIZE,
|
||||
@@ -656,7 +656,7 @@ void pushDots(WgpuViewportWindow& vp, const std::vector<float>& xyz) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void LengthMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
void LengthMeasurement::clear(ViewportWindow& vp) {
|
||||
points_.clear();
|
||||
normals_.clear();
|
||||
vp.setOverlayPoints({}, 0,0,0,0, 0, 0,0,0,0, 0);
|
||||
@@ -665,8 +665,8 @@ void LengthMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
vp.setHudText(QString());
|
||||
}
|
||||
|
||||
void LengthMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool /*alt*/) {
|
||||
WgpuViewportWindow::MeshLocalPick pick;
|
||||
void LengthMeasurement::onPick(ViewportWindow& vp, int x, int y, bool /*alt*/) {
|
||||
ViewportWindow::MeshLocalPick pick;
|
||||
if (!vp.pickMeshLocalAt(x, y, pick)) return;
|
||||
points_.push_back({pick.world_pos[0], pick.world_pos[1], pick.world_pos[2]});
|
||||
normals_.push_back({pick.world_normal[0], pick.world_normal[1], pick.world_normal[2]});
|
||||
@@ -676,14 +676,14 @@ void LengthMeasurement::onPick(WgpuViewportWindow& vp, int x, int y, bool /*alt*
|
||||
rebuildOverlay(vp);
|
||||
}
|
||||
|
||||
void LengthMeasurement::removeLastPoint(WgpuViewportWindow& vp) {
|
||||
void LengthMeasurement::removeLastPoint(ViewportWindow& vp) {
|
||||
if (points_.empty()) return;
|
||||
points_.pop_back();
|
||||
if (!normals_.empty()) normals_.pop_back();
|
||||
rebuildOverlay(vp);
|
||||
}
|
||||
|
||||
void LengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) {
|
||||
void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) {
|
||||
if (points_.size() == 1 && normals_.size() == 1) {
|
||||
rebuildLaserOverlay(vp);
|
||||
return;
|
||||
@@ -694,8 +694,8 @@ void LengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) {
|
||||
for (const auto& p : points_) pushDot(pts_xyz, p);
|
||||
pushDots(vp, pts_xyz);
|
||||
|
||||
std::vector<WgpuOverlayRenderer::LineGroup> groups;
|
||||
std::vector<WgpuOverlayRenderer::Label> labels;
|
||||
std::vector<OverlayRenderer::LineGroup> groups;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
const size_t n = points_.size();
|
||||
|
||||
if (n == 2) {
|
||||
@@ -823,7 +823,7 @@ const char* dominantAxisLabel(const float v[3]) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) {
|
||||
void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) {
|
||||
const auto& wp = first_pick_.world_pos; // float[3] world click
|
||||
const auto& n = first_pick_.world_normal; // float[3] world normal
|
||||
|
||||
@@ -855,8 +855,8 @@ void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) {
|
||||
n[0]*t1[1] - n[1]*t1[0],
|
||||
};
|
||||
|
||||
std::vector<WgpuOverlayRenderer::LineGroup> groups;
|
||||
std::vector<WgpuOverlayRenderer::Label> labels;
|
||||
std::vector<OverlayRenderer::LineGroup> groups;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
QStringList hud_lines;
|
||||
hud_lines << QStringLiteral("Laser measure (click another point for distance)");
|
||||
double enh[3] = {0.0, 0.0, 0.0};
|
||||
@@ -873,7 +873,7 @@ void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) {
|
||||
// co-normal neighbours, then project each patch vertex into the
|
||||
// (t1, t2) basis to get the bounding extent of the face. Stops
|
||||
// exactly at the face edge (no overshoot into adjacent geometry).
|
||||
WgpuViewportWindow::MeshTriangles tris;
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
bool have_extent = false;
|
||||
double min_t1 = 0.0, max_t1 = 0.0, min_t2 = 0.0, max_t2 = 0.0;
|
||||
if (vp.readbackMeshTriangles(first_pick_.model_id, first_pick_.mesh_id, tris)) {
|
||||
@@ -1008,7 +1008,7 @@ void LengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) {
|
||||
wp[1] + NUDGE * n[1],
|
||||
wp[2] + NUDGE * n[2],
|
||||
};
|
||||
WgpuViewportWindow::RaycastHit hit;
|
||||
ViewportWindow::RaycastHit hit;
|
||||
if (vp.raycast(ro, n, hit)) {
|
||||
const double dist = double(hit.distance) + double(NUDGE);
|
||||
const std::array<float, 3> a = {wp[0], wp[1], wp[2]};
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#define IFCINTERFACE_MEASUREMENT_H
|
||||
|
||||
#include <cstddef>
|
||||
#include "WgpuViewportWindow.h"
|
||||
#include "ViewportWindow.h"
|
||||
#include <QString>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
@@ -36,7 +36,7 @@
|
||||
// taken as the absolute value of the signed-tetrahedra sum, so winding
|
||||
// convention does not matter. Returns 0.0 for empty input or when nothing
|
||||
// resolves. Recomputes from scratch on every call — no cache.
|
||||
double volumeOfObjects(WgpuViewportWindow& vp,
|
||||
double volumeOfObjects(ViewportWindow& vp,
|
||||
const std::vector<uint32_t>& object_ids);
|
||||
|
||||
// Per-object volumes (m³). Same algorithm as volumeOfObjects but
|
||||
@@ -45,11 +45,11 @@ double volumeOfObjects(WgpuViewportWindow& vp,
|
||||
// Used by MainWindow's volume readout to drive both the total HUD and
|
||||
// the per-object overlay labels.
|
||||
std::vector<std::pair<uint32_t, double>>
|
||||
volumesPerObject(WgpuViewportWindow& vp,
|
||||
volumesPerObject(ViewportWindow& vp,
|
||||
const std::vector<uint32_t>& object_ids);
|
||||
|
||||
// Click-to-accumulate area measurement. Each pick resolves the screen
|
||||
// click to a (instance, triangle) using WgpuViewportWindow's primitives,
|
||||
// click to a (instance, triangle) using ViewportWindow's primitives,
|
||||
// expands it into the connected coplanar patch (BFS over shared edges,
|
||||
// dot(normal, seed_normal) > 0.9999), then either adds or removes that
|
||||
// patch from the running set depending on whether the seed triangle was
|
||||
@@ -58,7 +58,7 @@ volumesPerObject(WgpuViewportWindow& vp,
|
||||
// separate patches and their areas are summed.
|
||||
//
|
||||
// On every pick the world-space triangles of the running set are pushed
|
||||
// to WgpuViewportWindow::setHighlightTriangles for in-viewport shading.
|
||||
// to ViewportWindow::setHighlightTriangles for in-viewport shading.
|
||||
// State is cleared on construction, on clear(), and is expected to be
|
||||
// reset by the host (e.g. when the viewport's area tool toggles off).
|
||||
class AreaMeasurement {
|
||||
@@ -68,11 +68,11 @@ public:
|
||||
// Main entry point: handle one click in area-tool mode. alt = true
|
||||
// suppresses BFS expansion. Logs the per-click delta and running total
|
||||
// via qInfo. Misses are silent.
|
||||
void onPick(WgpuViewportWindow& vp, int x, int y, bool alt);
|
||||
void onPick(ViewportWindow& vp, int x, int y, bool alt);
|
||||
|
||||
// Wipe all accumulated triangles, per-mesh adjacency caches, and the
|
||||
// viewport overlay.
|
||||
void clear(WgpuViewportWindow& vp);
|
||||
void clear(ViewportWindow& vp);
|
||||
|
||||
double totalArea() const { return total_area_m2_; }
|
||||
size_t triangleCount() const { return selected_.size(); }
|
||||
@@ -89,7 +89,7 @@ private:
|
||||
// edge_key (min<<32 | max) → list of triangle indices touching it.
|
||||
std::unordered_map<uint64_t, std::vector<uint32_t>> edges;
|
||||
};
|
||||
MeshCache* meshCache(WgpuViewportWindow& vp, uint32_t model_id, uint32_t mesh_id);
|
||||
MeshCache* meshCache(ViewportWindow& vp, uint32_t model_id, uint32_t mesh_id);
|
||||
|
||||
// Per-selected-triangle record. The composed transform is captured at
|
||||
// pick time so the overlay rebuild doesn't have to re-query the
|
||||
@@ -109,7 +109,7 @@ private:
|
||||
return (uint64_t(object_id) << 32) | uint64_t(tri);
|
||||
}
|
||||
|
||||
void rebuildHighlight(WgpuViewportWindow& vp);
|
||||
void rebuildHighlight(ViewportWindow& vp);
|
||||
|
||||
std::unordered_map<uint64_t, MeshCache> mesh_cache_;
|
||||
std::unordered_map<uint64_t, SelectedTri> selected_;
|
||||
@@ -138,15 +138,15 @@ class LengthMeasurement {
|
||||
public:
|
||||
LengthMeasurement();
|
||||
|
||||
void onPick(WgpuViewportWindow& vp, int x, int y, bool alt);
|
||||
void removeLastPoint(WgpuViewportWindow& vp);
|
||||
void clear(WgpuViewportWindow& vp);
|
||||
void onPick(ViewportWindow& vp, int x, int y, bool alt);
|
||||
void removeLastPoint(ViewportWindow& vp);
|
||||
void clear(ViewportWindow& vp);
|
||||
|
||||
size_t pointCount() const { return points_.size(); }
|
||||
|
||||
private:
|
||||
void rebuildOverlay(WgpuViewportWindow& vp);
|
||||
void rebuildLaserOverlay(WgpuViewportWindow& vp);
|
||||
void rebuildOverlay(ViewportWindow& vp);
|
||||
void rebuildLaserOverlay(ViewportWindow& vp);
|
||||
QString formatReadout() const;
|
||||
|
||||
std::vector<std::array<float, 3>> points_;
|
||||
@@ -156,7 +156,7 @@ private:
|
||||
// updated afterwards. Used by the 1-pt laser BFS to re-locate the
|
||||
// mesh-local position of points_[0] without re-picking. Stays valid
|
||||
// while points_[0] does (pop_back never touches the first element).
|
||||
WgpuViewportWindow::MeshLocalPick first_pick_{};
|
||||
ViewportWindow::MeshLocalPick first_pick_{};
|
||||
};
|
||||
|
||||
#endif // IFCINTERFACE_MEASUREMENT_H
|
||||
|
||||
@@ -42,7 +42,7 @@ SessionState::SessionState(QObject* parent)
|
||||
this, &SessionState::notifyFederationChanged);
|
||||
}
|
||||
|
||||
void SessionState::createLoader(WgpuViewportWindow* viewport) {
|
||||
void SessionState::createLoader(ViewportWindow* viewport) {
|
||||
Q_ASSERT(!loader_);
|
||||
loader_ = new SceneLoader(viewport, this);
|
||||
loader_->setShouldReadSidecar(true);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
class Federation;
|
||||
class SceneLoader;
|
||||
class WgpuViewportWindow;
|
||||
class ViewportWindow;
|
||||
|
||||
namespace bonsaiviewer {
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
|
||||
// Owned by SessionState once it can be tied to a viewport. Wires
|
||||
// loader → element registry signals internally. Call exactly once.
|
||||
void createLoader(WgpuViewportWindow* viewport);
|
||||
void createLoader(ViewportWindow* viewport);
|
||||
|
||||
Federation* federation() const { return federation_; }
|
||||
SceneLoader* loader() const { return loader_; }
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include "../../../ifcviewer/Federation.h"
|
||||
#include "../../../ifcviewer/SceneLoader.h"
|
||||
#include "../../../ifcviewer/SidecarBuilder.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
#include "../../../ifcgeom/Serializer.h"
|
||||
#include "../../../serializers/document_serializer_plugin.h"
|
||||
|
||||
@@ -166,7 +166,7 @@ void removeGroup(SessionState& s, QWidget& host, const QString& group_id) {
|
||||
s.setStatusMessage("Models", "Group removed");
|
||||
}
|
||||
|
||||
void removeModel(SessionState& s, WgpuViewportWindow& vp, QWidget& host, const QString& fed_id) {
|
||||
void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id) {
|
||||
const Federation::Model* model = s.federation()->findById(fed_id);
|
||||
const QString label = model ? model->display_name : fed_id;
|
||||
const auto choice = QMessageBox::question(
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
class QWidget;
|
||||
class WgpuViewportWindow;
|
||||
class ViewportWindow;
|
||||
namespace bonsaiviewer { class SessionState; }
|
||||
|
||||
namespace bonsaiviewer::modules::models {
|
||||
@@ -58,7 +58,7 @@ void renameGroup(SessionState& s, QWidget& host, const QString& group_id);
|
||||
void moveGroup(SessionState& s, const QString& id, const QString& parent_group_id);
|
||||
void moveModels(SessionState& s, const QStringList& ids, const QString& parent_group_id);
|
||||
void removeGroup(SessionState& s, QWidget& host, const QString& group_id);
|
||||
void removeModel(SessionState& s, WgpuViewportWindow& vp, QWidget& host, const QString& fed_id);
|
||||
void removeModel(SessionState& s, ViewportWindow& vp, QWidget& host, const QString& fed_id);
|
||||
void addModel(SessionState& s, QWidget& host);
|
||||
// Connector picker → pull_models_interactive → addCloudModel + load.
|
||||
// Reachable from AddModelDialog's CloudModel button; the underlying call
|
||||
|
||||
@@ -220,7 +220,7 @@ private:
|
||||
} // namespace
|
||||
|
||||
ModelsPanel::ModelsPanel(bonsaiviewer::SessionState* session_state,
|
||||
WgpuViewportWindow* viewport,
|
||||
ViewportWindow* viewport,
|
||||
QWidget* parent)
|
||||
: components::Panel("Models", nullptr, parent, true)
|
||||
, session_state_(session_state)
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "../../components/Panel.h"
|
||||
|
||||
class QTreeView;
|
||||
class WgpuViewportWindow;
|
||||
class ViewportWindow;
|
||||
namespace bonsaiviewer { class SessionState; }
|
||||
|
||||
namespace bonsaiviewer::modules::models {
|
||||
@@ -41,7 +41,7 @@ class ModelsPanel : public components::Panel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ModelsPanel(bonsaiviewer::SessionState* session_state,
|
||||
WgpuViewportWindow* viewport,
|
||||
ViewportWindow* viewport,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
// Owned externally (the View constructs and owns the model). The panel
|
||||
@@ -52,7 +52,7 @@ private:
|
||||
void applyColumnLayout();
|
||||
|
||||
bonsaiviewer::SessionState* session_state_ = nullptr;
|
||||
WgpuViewportWindow* viewport_ = nullptr;
|
||||
ViewportWindow* viewport_ = nullptr;
|
||||
QTreeView* tree_ = nullptr;
|
||||
FederationItemModel* model_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include "../models/Commands.h"
|
||||
#include "../../../ifcviewer/Federation.h"
|
||||
#include "../../../ifcviewer/SceneLoader.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -53,7 +53,7 @@ namespace {
|
||||
// Pure helper — clears the loaded scene without emitting any signals. The
|
||||
// caller (newProject / openProject) emits projectReset / projectOpened once
|
||||
// the whole flow finishes.
|
||||
void clearScene(SessionState& s, WgpuViewportWindow& vp) {
|
||||
void clearScene(SessionState& s, ViewportWindow& vp) {
|
||||
vp.setSelectedObjectId(0);
|
||||
s.setSelectedObjectId(0);
|
||||
for (uint32_t mid : s.modelIds()) {
|
||||
@@ -88,7 +88,7 @@ bool confirmDiscardIfDirty(SessionState& s, QWidget& host) {
|
||||
// - if no scene entry exists yet (initial open), queue a load.
|
||||
// Per spec, connector errors are not surfaced to the user; the connector
|
||||
// has already shown its own UI.
|
||||
void resolveCloudModels(SessionState& s, WgpuViewportWindow& vp) {
|
||||
void resolveCloudModels(SessionState& s, ViewportWindow& vp) {
|
||||
auto* fed = s.federation();
|
||||
QHash<QString, QStringList> connector_to_fed_ids;
|
||||
for (const auto& m : fed->models()) {
|
||||
@@ -99,7 +99,7 @@ void resolveCloudModels(SessionState& s, WgpuViewportWindow& vp) {
|
||||
|
||||
auto* registry = s.connectorRegistry();
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<WgpuViewportWindow> vguard(&vp);
|
||||
QPointer<ViewportWindow> vguard(&vp);
|
||||
|
||||
for (auto it = connector_to_fed_ids.constBegin();
|
||||
it != connector_to_fed_ids.constEnd(); ++it) {
|
||||
@@ -203,7 +203,7 @@ bool isIfcfedUnchanged(const QString& current_path, const QString& candidate_pat
|
||||
return a.readAll() == b.readAll();
|
||||
}
|
||||
|
||||
bool openProjectAt(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path) {
|
||||
bool openProjectAt(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) {
|
||||
SceneLoader* loader = s.loader();
|
||||
if (loader && loader->isLoading()) {
|
||||
QMessageBox::information(
|
||||
@@ -268,7 +268,7 @@ bool saveProjectTo(SessionState& s, QWidget& host, const QString& path) {
|
||||
|
||||
} // namespace
|
||||
|
||||
bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
SceneLoader* loader = s.loader();
|
||||
if (loader && loader->isLoading()) {
|
||||
QMessageBox::information(
|
||||
@@ -285,7 +285,7 @@ bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
QFileDialog file_dialog(&host, "Open Project");
|
||||
file_dialog.setFileMode(QFileDialog::ExistingFile);
|
||||
file_dialog.setNameFilter("IFC Federation (*.ifcfed);;All Files (*)");
|
||||
@@ -297,12 +297,12 @@ bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
return openProjectAt(s, host, vp, path);
|
||||
}
|
||||
|
||||
bool openProjectPath(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path) {
|
||||
bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path) {
|
||||
if (path.isEmpty()) return false;
|
||||
return openProjectAt(s, host, vp, path);
|
||||
}
|
||||
|
||||
bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
SceneLoader* loader = s.loader();
|
||||
if (loader && loader->isLoading()) {
|
||||
QMessageBox::information(
|
||||
@@ -341,7 +341,7 @@ bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<QWidget> hguard(&host);
|
||||
QPointer<WgpuViewportWindow> vguard(&vp);
|
||||
QPointer<ViewportWindow> vguard(&vp);
|
||||
|
||||
proc->call("pull_ifcfed_interactive", QJsonValue(),
|
||||
[sguard, hguard, vguard, connector_id](const QJsonValue& result) {
|
||||
@@ -371,7 +371,7 @@ bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp) {
|
||||
auto* fed = s.federation();
|
||||
|
||||
// Per spec, sync has two independent phases — refreshing the .ifcfed
|
||||
@@ -429,7 +429,7 @@ bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp) {
|
||||
|
||||
QPointer<SessionState> sguard(&s);
|
||||
QPointer<QWidget> hguard(&host);
|
||||
QPointer<WgpuViewportWindow> vguard(&vp);
|
||||
QPointer<ViewportWindow> vguard(&vp);
|
||||
const QString current_path = fed->filePath();
|
||||
|
||||
proc->call("pull_ifcfed", fed->manifest(),
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <QString>
|
||||
|
||||
class QWidget;
|
||||
class WgpuViewportWindow;
|
||||
class ViewportWindow;
|
||||
namespace bonsaiviewer { class SessionState; }
|
||||
|
||||
namespace bonsaiviewer::modules::project::commands {
|
||||
@@ -32,21 +32,21 @@ namespace bonsaiviewer::modules::project::commands {
|
||||
// User-facing commands. Each owns its own dialogs and confirmations; each
|
||||
// emits exactly one notify() at the end (projectReset / projectOpened /
|
||||
// projectSaved) so views refresh once per command.
|
||||
bool newProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp);
|
||||
bool openProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp);
|
||||
bool newProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
||||
bool openProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
||||
// Open a specific .ifcfed by path, bypassing the file dialog. Used by the
|
||||
// "Open Recent" menu. Same dirty-check / load / cloud-resolve flow as
|
||||
// openProject; returns false if the load failed or was cancelled.
|
||||
bool openProjectPath(SessionState& s, QWidget& host, WgpuViewportWindow& vp, const QString& path);
|
||||
bool openProjectPath(SessionState& s, QWidget& host, ViewportWindow& vp, const QString& path);
|
||||
// Pick a connector, then call pull_ifcfed_interactive and open the resulting
|
||||
// .ifcfed as a fresh project. Non-local models in the loaded federation are
|
||||
// resolved asynchronously via pull_models.
|
||||
bool openCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp);
|
||||
bool openCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
||||
// pull_ifcfed using the current project's .ifcfed.manifest. Re-downloads
|
||||
// the .ifcfed from the same cloud target it came from (typically without
|
||||
// user interaction), then opens it like a fresh project — discarding any
|
||||
// local edits after the usual dirty-check prompt.
|
||||
bool syncCloudProject(SessionState& s, QWidget& host, WgpuViewportWindow& vp);
|
||||
bool syncCloudProject(SessionState& s, QWidget& host, ViewportWindow& vp);
|
||||
bool saveProject(SessionState& s, QWidget& host);
|
||||
bool saveProjectAs(SessionState& s, QWidget& host);
|
||||
// Push the current federation to the cloud target named in its manifest
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
|
||||
#include "../../SessionState.h"
|
||||
#include "../../../ifcviewer/Federation.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
|
||||
namespace bonsaiviewer::modules::viewport::commands {
|
||||
|
||||
void setHome(SessionState& session, WgpuViewportWindow& vp) {
|
||||
void setHome(SessionState& session, ViewportWindow& vp) {
|
||||
auto camera = vp.cameraState();
|
||||
Federation::HomeView home_view;
|
||||
home_view.target = camera.target;
|
||||
@@ -37,7 +37,7 @@ void setHome(SessionState& session, WgpuViewportWindow& vp) {
|
||||
session.setStatusMessage("Camera", "Home view updated");
|
||||
}
|
||||
|
||||
void goHome(SessionState& session, WgpuViewportWindow& vp) {
|
||||
void goHome(SessionState& session, ViewportWindow& vp) {
|
||||
Federation* federation = session.federation();
|
||||
if (!federation->hasHomeView()) {
|
||||
session.setStatusMessage("Camera", "No home view set for this project");
|
||||
@@ -50,52 +50,52 @@ void goHome(SessionState& session, WgpuViewportWindow& vp) {
|
||||
session.setStatusMessage("Camera", "Home view restored");
|
||||
}
|
||||
|
||||
void viewSelected(WgpuViewportWindow& vp) {
|
||||
void viewSelected(ViewportWindow& vp) {
|
||||
vp.focusOnSelectedObject();
|
||||
}
|
||||
|
||||
void fly(SessionState& session, WgpuViewportWindow& vp) {
|
||||
void fly(SessionState& session, ViewportWindow& vp) {
|
||||
vp.requestActivate();
|
||||
vp.enterFpsMode();
|
||||
session.setStatusMessage("Mode", "Fly mode active");
|
||||
}
|
||||
|
||||
void toggleSection(SessionState& session, WgpuViewportWindow& vp) {
|
||||
void toggleSection(SessionState& session, ViewportWindow& vp) {
|
||||
vp.toggleSectionTool();
|
||||
session.setStatusMessage("Section",
|
||||
vp.sectionToolActive() ? "Section tool active" : "Section tool off");
|
||||
}
|
||||
|
||||
void clearSection(SessionState& session, WgpuViewportWindow& vp) {
|
||||
void clearSection(SessionState& session, ViewportWindow& vp) {
|
||||
vp.clearSectionPlanes();
|
||||
session.setStatusMessage("Section", "Section planes cleared");
|
||||
}
|
||||
|
||||
void toggleDistance(WgpuViewportWindow& vp) {
|
||||
void toggleDistance(ViewportWindow& vp) {
|
||||
vp.toggleLengthTool();
|
||||
}
|
||||
|
||||
void toggleArea(WgpuViewportWindow& vp) {
|
||||
void toggleArea(ViewportWindow& vp) {
|
||||
vp.toggleAreaTool();
|
||||
}
|
||||
|
||||
void toggleVolume(WgpuViewportWindow& vp) {
|
||||
void toggleVolume(ViewportWindow& vp) {
|
||||
vp.toggleVolumeTool();
|
||||
}
|
||||
|
||||
void hideSelected(WgpuViewportWindow& vp) {
|
||||
void hideSelected(ViewportWindow& vp) {
|
||||
vp.hideSelectedElements();
|
||||
}
|
||||
|
||||
void isolateSelected(WgpuViewportWindow& vp) {
|
||||
void isolateSelected(ViewportWindow& vp) {
|
||||
vp.isolateSelectedElements();
|
||||
}
|
||||
|
||||
void showAll(WgpuViewportWindow& vp) {
|
||||
void showAll(ViewportWindow& vp) {
|
||||
vp.showAllElements();
|
||||
}
|
||||
|
||||
void invertVisibility(WgpuViewportWindow& vp) {
|
||||
void invertVisibility(ViewportWindow& vp) {
|
||||
vp.invertElementVisibility();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,27 +21,27 @@
|
||||
#ifndef IFCINTERFACE_MODULES_VIEWPORT_COMMANDS_H
|
||||
#define IFCINTERFACE_MODULES_VIEWPORT_COMMANDS_H
|
||||
|
||||
class WgpuViewportWindow;
|
||||
class ViewportWindow;
|
||||
namespace bonsaiviewer { class SessionState; }
|
||||
|
||||
namespace bonsaiviewer::modules::viewport::commands {
|
||||
|
||||
void setHome(SessionState& session, WgpuViewportWindow& vp);
|
||||
void goHome(SessionState& session, WgpuViewportWindow& vp);
|
||||
void viewSelected(WgpuViewportWindow& vp);
|
||||
void setHome(SessionState& session, ViewportWindow& vp);
|
||||
void goHome(SessionState& session, ViewportWindow& vp);
|
||||
void viewSelected(ViewportWindow& vp);
|
||||
|
||||
void fly(SessionState& session, WgpuViewportWindow& vp);
|
||||
void toggleSection(SessionState& session, WgpuViewportWindow& vp);
|
||||
void clearSection(SessionState& session, WgpuViewportWindow& vp);
|
||||
void fly(SessionState& session, ViewportWindow& vp);
|
||||
void toggleSection(SessionState& session, ViewportWindow& vp);
|
||||
void clearSection(SessionState& session, ViewportWindow& vp);
|
||||
|
||||
void toggleDistance(WgpuViewportWindow& vp);
|
||||
void toggleArea(WgpuViewportWindow& vp);
|
||||
void toggleVolume(WgpuViewportWindow& vp);
|
||||
void toggleDistance(ViewportWindow& vp);
|
||||
void toggleArea(ViewportWindow& vp);
|
||||
void toggleVolume(ViewportWindow& vp);
|
||||
|
||||
void hideSelected(WgpuViewportWindow& vp);
|
||||
void isolateSelected(WgpuViewportWindow& vp);
|
||||
void showAll(WgpuViewportWindow& vp);
|
||||
void invertVisibility(WgpuViewportWindow& vp);
|
||||
void hideSelected(ViewportWindow& vp);
|
||||
void isolateSelected(ViewportWindow& vp);
|
||||
void showAll(ViewportWindow& vp);
|
||||
void invertVisibility(ViewportWindow& vp);
|
||||
|
||||
} // namespace bonsaiviewer::modules::viewport::commands
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
|
||||
#include <QFrame>
|
||||
#include <QVBoxLayout>
|
||||
@@ -47,7 +47,7 @@ ViewportPanel::ViewportPanel(QWidget* parent)
|
||||
frame_layout->setContentsMargins(0, 0, 0, 0);
|
||||
frame_layout->setSpacing(0);
|
||||
|
||||
viewport_ = new WgpuViewportWindow();
|
||||
viewport_ = new ViewportWindow();
|
||||
viewport_container_ = QWidget::createWindowContainer(viewport_, frame);
|
||||
viewport_container_->setMinimumSize(400, 300);
|
||||
viewport_container_->setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class WgpuViewportWindow;
|
||||
class ViewportWindow;
|
||||
|
||||
namespace bonsaiviewer::modules::viewport {
|
||||
|
||||
@@ -33,10 +33,10 @@ class ViewportPanel : public QWidget {
|
||||
public:
|
||||
explicit ViewportPanel(QWidget* parent = nullptr);
|
||||
|
||||
WgpuViewportWindow* viewport() const { return viewport_; }
|
||||
ViewportWindow* viewport() const { return viewport_; }
|
||||
|
||||
private:
|
||||
WgpuViewportWindow* viewport_ = nullptr;
|
||||
ViewportWindow* viewport_ = nullptr;
|
||||
QWidget* viewport_container_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include "../models/Commands.h"
|
||||
#include "../../../ifcviewer/Federation.h"
|
||||
#include "../../../ifcviewer/SceneLoader.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
#include "../../../ifcviewer-wgpu/WgpuOverlayRenderer.h"
|
||||
#include "../../../ifcviewer/ViewportWindow.h"
|
||||
#include "../../../ifcviewer/OverlayRenderer.h"
|
||||
#include "../../Measurement.h"
|
||||
|
||||
#include <Eigen/Dense>
|
||||
@@ -37,7 +37,7 @@
|
||||
namespace bonsaiviewer::modules::viewport {
|
||||
|
||||
ViewportView::ViewportView(bonsaiviewer::SessionState* session_state,
|
||||
WgpuViewportWindow* viewport,
|
||||
ViewportWindow* viewport,
|
||||
QObject* parent)
|
||||
: QObject(parent)
|
||||
, session_state_(session_state)
|
||||
@@ -73,54 +73,54 @@ ViewportView::ViewportView(bonsaiviewer::SessionState* session_state,
|
||||
});
|
||||
|
||||
// Measurement tools — input-driven, share the View's lifetime.
|
||||
connect(viewport_, &WgpuViewportWindow::surfacePickedInTool, this,
|
||||
connect(viewport_, &ViewportWindow::surfacePickedInTool, this,
|
||||
[this](int x, int y, int modifiers) {
|
||||
const bool alt = (modifiers & Qt::AltModifier) != 0;
|
||||
switch (viewport_->toolMode()) {
|
||||
case WgpuViewportWindow::ToolMode::Area:
|
||||
case ViewportWindow::ToolMode::Area:
|
||||
area_measurement_->onPick(*viewport_, x, y, alt);
|
||||
viewport_->setHudText(QString("Area: %1 m² (%2 tris)")
|
||||
.arg(area_measurement_->totalArea(), 0, 'f', 4)
|
||||
.arg(area_measurement_->triangleCount()));
|
||||
break;
|
||||
case WgpuViewportWindow::ToolMode::Length:
|
||||
case ViewportWindow::ToolMode::Length:
|
||||
length_measurement_->onPick(*viewport_, x, y, alt);
|
||||
break;
|
||||
case WgpuViewportWindow::ToolMode::NoTool:
|
||||
case WgpuViewportWindow::ToolMode::Volume:
|
||||
case ViewportWindow::ToolMode::NoTool:
|
||||
case ViewportWindow::ToolMode::Volume:
|
||||
break;
|
||||
}
|
||||
});
|
||||
connect(viewport_, &WgpuViewportWindow::toolModeChanged, this,
|
||||
[this](WgpuViewportWindow::ToolMode mode) {
|
||||
connect(viewport_, &ViewportWindow::toolModeChanged, this,
|
||||
[this](ViewportWindow::ToolMode mode) {
|
||||
area_measurement_->clear(*viewport_);
|
||||
length_measurement_->clear(*viewport_);
|
||||
switch (mode) {
|
||||
case WgpuViewportWindow::ToolMode::NoTool:
|
||||
case ViewportWindow::ToolMode::NoTool:
|
||||
viewport_->setHudText(QString());
|
||||
viewport_->setOverlayLabels({});
|
||||
session_state_->setStatusMessage("Measure", "Measurement tool off");
|
||||
break;
|
||||
case WgpuViewportWindow::ToolMode::Length:
|
||||
case ViewportWindow::ToolMode::Length:
|
||||
viewport_->setHudText("Length tool: click first point");
|
||||
session_state_->setStatusMessage("Measure", "Length tool: LMB add point, Backspace remove last, Esc exits");
|
||||
break;
|
||||
case WgpuViewportWindow::ToolMode::Area:
|
||||
case ViewportWindow::ToolMode::Area:
|
||||
viewport_->setHudText("Area: 0.0000 m² (0 tris)");
|
||||
session_state_->setStatusMessage("Measure", "Area tool: LMB add, Alt+LMB single tri, click again to remove, Esc exits");
|
||||
break;
|
||||
case WgpuViewportWindow::ToolMode::Volume:
|
||||
case ViewportWindow::ToolMode::Volume:
|
||||
session_state_->setStatusMessage("Measure", "Volume tool: click / box-select objects, Esc exits");
|
||||
updateVolumeReadout();
|
||||
break;
|
||||
}
|
||||
});
|
||||
connect(viewport_, &WgpuViewportWindow::toolBackspacePressed, this, [this]() {
|
||||
if (viewport_->toolMode() == WgpuViewportWindow::ToolMode::Length) {
|
||||
connect(viewport_, &ViewportWindow::toolBackspacePressed, this, [this]() {
|
||||
if (viewport_->toolMode() == ViewportWindow::ToolMode::Length) {
|
||||
length_measurement_->removeLastPoint(*viewport_);
|
||||
}
|
||||
});
|
||||
connect(viewport_, &WgpuViewportWindow::objectPicked, this, [this](uint32_t) {
|
||||
connect(viewport_, &ViewportWindow::objectPicked, this, [this](uint32_t) {
|
||||
updateVolumeReadout();
|
||||
});
|
||||
|
||||
@@ -238,7 +238,7 @@ void ViewportView::guessFederatedFalseOriginFromFirstModel(uint32_t mid) {
|
||||
}
|
||||
|
||||
void ViewportView::updateVolumeReadout() {
|
||||
if (viewport_->toolMode() != WgpuViewportWindow::ToolMode::Volume) return;
|
||||
if (viewport_->toolMode() != ViewportWindow::ToolMode::Volume) return;
|
||||
|
||||
const auto& sel = viewport_->selection().selectionIds();
|
||||
if (sel.empty()) {
|
||||
@@ -251,13 +251,13 @@ void ViewportView::updateVolumeReadout() {
|
||||
const auto per_obj = volumesPerObject(*viewport_, ids);
|
||||
|
||||
double total = 0.0;
|
||||
std::vector<WgpuOverlayRenderer::Label> labels;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
labels.reserve(per_obj.size());
|
||||
for (const auto& [oid, v] : per_obj) {
|
||||
total += v;
|
||||
QVector3D mn, mx;
|
||||
if (!viewport_->computeObjectAabb(oid, mn, mx)) continue;
|
||||
WgpuOverlayRenderer::Label lbl;
|
||||
OverlayRenderer::Label lbl;
|
||||
const QVector3D c = (mn + mx) * 0.5f;
|
||||
lbl.world_pos[0] = c.x();
|
||||
lbl.world_pos[1] = c.y();
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <memory>
|
||||
|
||||
namespace bonsaiviewer { class SessionState; }
|
||||
class WgpuViewportWindow;
|
||||
class ViewportWindow;
|
||||
class AreaMeasurement;
|
||||
class LengthMeasurement;
|
||||
|
||||
@@ -44,7 +44,7 @@ class ViewportView : public QObject {
|
||||
|
||||
public:
|
||||
explicit ViewportView(bonsaiviewer::SessionState* session_state,
|
||||
WgpuViewportWindow* viewport,
|
||||
ViewportWindow* viewport,
|
||||
QObject* parent = nullptr);
|
||||
~ViewportView() override;
|
||||
|
||||
@@ -57,7 +57,7 @@ private:
|
||||
void updateVolumeReadout();
|
||||
|
||||
bonsaiviewer::SessionState* session_state_ = nullptr;
|
||||
WgpuViewportWindow* viewport_ = nullptr;
|
||||
ViewportWindow* viewport_ = nullptr;
|
||||
std::unique_ptr<AreaMeasurement> area_measurement_;
|
||||
std::unique_ptr<LengthMeasurement> length_measurement_;
|
||||
};
|
||||
|
||||
@@ -17,22 +17,22 @@
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
message("Running CMakeLists.txt in /src/ifcviewer-wgpu-minimal")
|
||||
message("Running CMakeLists.txt in /src/ifcviewer-minimal")
|
||||
|
||||
find_package(Qt${QT_VERSION} COMPONENTS Widgets REQUIRED PATHS ${QT_DIR})
|
||||
|
||||
file(GLOB IFCVIEWER_WGPU_MIN_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||
file(GLOB IFCVIEWER_MIN_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||
|
||||
add_executable(IfcViewerWgpuMinimal ${IFCVIEWER_WGPU_MIN_CPP_FILES})
|
||||
add_executable(IfcViewerMinimal ${IFCVIEWER_MIN_CPP_FILES})
|
||||
|
||||
set_target_properties(IfcViewerWgpuMinimal PROPERTIES
|
||||
set_target_properties(IfcViewerMinimal PROPERTIES
|
||||
AUTOMOC ON
|
||||
WIN32_EXECUTABLE ON
|
||||
MACOSX_BUNDLE ON
|
||||
)
|
||||
|
||||
target_link_libraries(IfcViewerWgpuMinimal PRIVATE
|
||||
IfcViewerWgpu
|
||||
target_link_libraries(IfcViewerMinimal PRIVATE
|
||||
IfcViewer
|
||||
Qt${QT_VERSION}::Widgets
|
||||
)
|
||||
|
||||
@@ -40,19 +40,19 @@ target_link_libraries(IfcViewerWgpuMinimal PRIVATE
|
||||
# run directly out of build-viewer-wgpu/. The patched SONAME means DT_NEEDED
|
||||
# is just the basename, so a single rpath entry suffices.
|
||||
if(UNIX AND NOT APPLE AND WGPU_NATIVE_LIB_DIR)
|
||||
set_target_properties(IfcViewerWgpuMinimal PROPERTIES
|
||||
set_target_properties(IfcViewerMinimal PROPERTIES
|
||||
BUILD_RPATH "${WGPU_NATIVE_LIB_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
# BUNDLE DESTINATION must be "." (install-prefix root) on macOS — Qt's
|
||||
# deploy generator emits `macdeployqt IfcViewerWgpuMinimal.app` (no
|
||||
# deploy generator emits `macdeployqt IfcViewerMinimal.app` (no
|
||||
# path prefix), which resolves only when the .app sits at the install
|
||||
# root. With `bin/` it can't find the bundle and the install/strip
|
||||
# step fails.
|
||||
install(TARGETS IfcViewerWgpuMinimal
|
||||
install(TARGETS IfcViewerMinimal
|
||||
EXPORT ${IFCOPENSHELL_EXPORT_TARGETS}
|
||||
RUNTIME DESTINATION bin
|
||||
BUNDLE DESTINATION .
|
||||
)
|
||||
ifcopenshell_deploy_qt_runtime(IfcViewerWgpuMinimal)
|
||||
ifcopenshell_deploy_qt_runtime(IfcViewerMinimal)
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <QWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "WgpuViewportWindow.h"
|
||||
#include "ViewportWindow.h"
|
||||
|
||||
// Stage-1 driver: opens a single window with the wgpu viewport embedded,
|
||||
// clears to background colour, and exits on close. The shape mirrors
|
||||
@@ -31,7 +31,7 @@
|
||||
// benchmark-comparable binary.
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
app.setApplicationName("IfcViewerWgpuMinimal");
|
||||
app.setApplicationName("IfcViewerMinimal");
|
||||
app.setOrganizationName("IfcOpenShell");
|
||||
|
||||
QCommandLineParser parser;
|
||||
@@ -56,7 +56,7 @@ int main(int argc, char* argv[]) {
|
||||
"browser constraints."});
|
||||
parser.process(app);
|
||||
|
||||
auto* viewport = new WgpuViewportWindow;
|
||||
auto* viewport = new ViewportWindow;
|
||||
viewport->resize(1280, 800);
|
||||
if (parser.isSet("no-hiz")) viewport->hiz_enabled_ = false;
|
||||
if (parser.isSet("web-limits")) viewport->web_limits_ = true;
|
||||
@@ -1,194 +0,0 @@
|
||||
################################################################################
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
message("Running CMakeLists.txt in /src/ifcviewer-wgpu")
|
||||
|
||||
set(QT_VERSION 6 CACHE STRING "Qt version")
|
||||
find_package(Qt${QT_VERSION} COMPONENTS Core Gui REQUIRED PATHS ${QT_DIR})
|
||||
|
||||
# Eigen3 for the federation hooks (setModelTransformation etc.) and the
|
||||
# per-model coordinate-operation matrices stored on WgpuModelGpuData.
|
||||
find_package(Eigen3 REQUIRED)
|
||||
|
||||
# wgpu-native — fetched as a pre-built binary release from upstream.
|
||||
# Pin the version with WGPU_NATIVE_VERSION; bump to pull a newer release.
|
||||
set(WGPU_NATIVE_VERSION "v29.0.0.0" CACHE STRING "wgpu-native release tag")
|
||||
|
||||
# Pick the right release archive for the host platform.
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64")
|
||||
set(_wgpu_archive "wgpu-windows-aarch64-msvc-release.zip")
|
||||
else()
|
||||
set(_wgpu_archive "wgpu-windows-x86_64-msvc-release.zip")
|
||||
endif()
|
||||
set(_wgpu_lib "wgpu_native.dll.lib")
|
||||
set(_wgpu_runtime "wgpu_native.dll")
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
|
||||
set(_wgpu_archive "wgpu-macos-aarch64-release.zip")
|
||||
else()
|
||||
set(_wgpu_archive "wgpu-macos-x86_64-release.zip")
|
||||
endif()
|
||||
set(_wgpu_lib "libwgpu_native.dylib")
|
||||
else() # Linux + BSDs
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
|
||||
set(_wgpu_archive "wgpu-linux-aarch64-release.zip")
|
||||
else()
|
||||
set(_wgpu_archive "wgpu-linux-x86_64-release.zip")
|
||||
endif()
|
||||
set(_wgpu_lib "libwgpu_native.so")
|
||||
endif()
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
wgpu_native
|
||||
URL https://github.com/gfx-rs/wgpu-native/releases/download/${WGPU_NATIVE_VERSION}/${_wgpu_archive}
|
||||
DOWNLOAD_NO_PROGRESS FALSE
|
||||
)
|
||||
FetchContent_MakeAvailable(wgpu_native)
|
||||
|
||||
# Release archive layout: include/webgpu/*.h and lib/<libname>.
|
||||
#
|
||||
# The Linux .so shipped in the v29 release has no DT_SONAME, which causes
|
||||
# CMake to bake the relative IMPORTED_LOCATION path into DT_NEEDED. We patch
|
||||
# the SONAME in once at configure time so dependents get a clean
|
||||
# libwgpu_native.so reference, and pin the executable's rpath to the lib dir.
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_program(PATCHELF_EXECUTABLE patchelf)
|
||||
if(PATCHELF_EXECUTABLE)
|
||||
execute_process(
|
||||
COMMAND ${PATCHELF_EXECUTABLE} --set-soname "${_wgpu_lib}"
|
||||
"${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}"
|
||||
RESULT_VARIABLE _patchelf_rc
|
||||
)
|
||||
if(NOT _patchelf_rc EQUAL 0)
|
||||
message(WARNING "patchelf --set-soname failed on libwgpu_native.so")
|
||||
endif()
|
||||
else()
|
||||
message(WARNING
|
||||
"patchelf not found; libwgpu_native.so will be linked with a "
|
||||
"relative DT_NEEDED. Install patchelf to fix.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(wgpu_native SHARED IMPORTED GLOBAL)
|
||||
set_target_properties(wgpu_native PROPERTIES
|
||||
IMPORTED_LOCATION "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${wgpu_native_SOURCE_DIR}/include"
|
||||
)
|
||||
if(WIN32)
|
||||
# On Windows the .lib is the import library; the .dll is the runtime.
|
||||
set_target_properties(wgpu_native PROPERTIES
|
||||
IMPORTED_IMPLIB "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}"
|
||||
IMPORTED_LOCATION "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_runtime}"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Expose the lib dir so dependents can put it on their rpath.
|
||||
set(WGPU_NATIVE_LIB_DIR "${wgpu_native_SOURCE_DIR}/lib" CACHE INTERNAL
|
||||
"Directory containing the wgpu-native shared library")
|
||||
|
||||
file(GLOB IFCVIEWER_WGPU_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||
file(GLOB IFCVIEWER_WGPU_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
||||
set(IFCVIEWER_WGPU_FILES ${IFCVIEWER_WGPU_CPP_FILES} ${IFCVIEWER_WGPU_H_FILES})
|
||||
|
||||
# Cocoa bridge for the CAMetalLayer surface attach — Objective-C++.
|
||||
# Only compiled into the target on Apple platforms; CMake handles `.mm`
|
||||
# natively once OBJCXX is enabled.
|
||||
if(APPLE)
|
||||
enable_language(OBJCXX)
|
||||
list(APPEND IFCVIEWER_WGPU_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WgpuMetalSurface_mac.mm
|
||||
)
|
||||
endif()
|
||||
|
||||
# Intentional source-level borrowing from the GL backend until ifcviewer-core
|
||||
# is extracted (task #12). SidecarCache + InstancedGeometry have zero Qt /
|
||||
# OCCT / IFC-parse deps, so compiling them directly into IfcViewerWgpu is
|
||||
# cheaper than dragging in the IfcViewer static lib (which would pull all
|
||||
# of IfcGeom + IfcParse + OCCT + Qt OpenGL).
|
||||
set(IFCVIEWER_SHARED_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../ifcviewer)
|
||||
list(APPEND IFCVIEWER_WGPU_FILES
|
||||
${IFCVIEWER_SHARED_DIR}/SidecarCache.cpp
|
||||
)
|
||||
|
||||
add_library(IfcViewerWgpu STATIC ${IFCVIEWER_WGPU_FILES})
|
||||
|
||||
set_target_properties(IfcViewerWgpu PROPERTIES
|
||||
AUTOMOC ON
|
||||
VERSION "${PROJECT_VERSION}"
|
||||
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
|
||||
)
|
||||
|
||||
target_include_directories(IfcViewerWgpu
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PUBLIC ${IFCVIEWER_SHARED_DIR} # SidecarCache.h is reachable via WgpuViewportWindow.h
|
||||
)
|
||||
|
||||
target_link_libraries(IfcViewerWgpu PUBLIC
|
||||
Qt${QT_VERSION}::Core
|
||||
Qt${QT_VERSION}::Gui
|
||||
wgpu_native
|
||||
Eigen3::Eigen
|
||||
)
|
||||
|
||||
# Qt platform-handle access (QNativeInterface::QX11Application etc.) is in
|
||||
# the public Gui headers in Qt 6.2+, no PRIVATE_INCLUDE_DIRS needed.
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(IfcViewerWgpu PUBLIC Threads::Threads)
|
||||
endif()
|
||||
|
||||
# Cocoa + QuartzCore for WgpuMetalSurface_mac.mm (NSView, CAMetalLayer).
|
||||
if(APPLE)
|
||||
target_link_libraries(IfcViewerWgpu PUBLIC
|
||||
"-framework Cocoa"
|
||||
"-framework QuartzCore"
|
||||
)
|
||||
endif()
|
||||
|
||||
install(TARGETS IfcViewerWgpu EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
||||
|
||||
install(FILES ${IFCVIEWER_WGPU_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcviewer-wgpu
|
||||
)
|
||||
|
||||
# Install the wgpu_native shared library so the deployed runtime can find it.
|
||||
# At build/run-from-build-tree time CMake adds wgpu_native_SOURCE_DIR to the
|
||||
# binary's rpath automatically (IMPORTED_LOCATION dirname).
|
||||
#
|
||||
# macOS: drop libwgpu_native.dylib straight into BonsaiViewer.app's
|
||||
# Frameworks/. The exe has `LC_LOAD_DYLIB @rpath/libwgpu_native.dylib`
|
||||
# (baked from the dylib's install_name), and BonsaiViewer's
|
||||
# INSTALL_RPATH is set to @executable_path/../Frameworks — together
|
||||
# they resolve at launch without depending on macdeployqt to follow
|
||||
# non-Qt @rpath references.
|
||||
if(WIN32)
|
||||
install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_runtime}" DESTINATION bin)
|
||||
elseif(APPLE AND BUILD_BONSAIVIEWER)
|
||||
install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}"
|
||||
DESTINATION "BonsaiViewer.app/Contents/Frameworks")
|
||||
else()
|
||||
install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" DESTINATION lib)
|
||||
endif()
|
||||
|
||||
if(BUILD_BONSAIVIEWER_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
@@ -1,35 +0,0 @@
|
||||
################################################################################
|
||||
# #
|
||||
# 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/>. #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
# Tier-1 unit tests for the wgpu backend's pure-CPU state machines (selection,
|
||||
# visibility). Header-only subjects — the test binaries compile against the
|
||||
# class definitions directly, no link to IfcViewerWgpu needed and no
|
||||
# wgpu-native runtime involvement.
|
||||
|
||||
set(IFCVIEWER_WGPU_SRC ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
|
||||
function(add_ifcviewer_wgpu_unit_test name)
|
||||
add_executable(${name} ${name}.cpp)
|
||||
target_include_directories(${name} PRIVATE ${IFCVIEWER_WGPU_SRC})
|
||||
target_link_libraries(${name} PRIVATE Catch2::Catch2WithMain)
|
||||
catch_discover_tests(${name})
|
||||
endfunction()
|
||||
|
||||
add_ifcviewer_wgpu_unit_test(test_wgpu_selection)
|
||||
add_ifcviewer_wgpu_unit_test(test_wgpu_visibility)
|
||||
@@ -17,10 +17,10 @@
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "WgpuAreaMeasurement.h"
|
||||
#include "AreaMeasurement.h"
|
||||
|
||||
#include "WgpuOverlayRenderer.h"
|
||||
#include "WgpuViewportWindow.h"
|
||||
#include "OverlayRenderer.h"
|
||||
#include "ViewportWindow.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
@@ -138,9 +138,9 @@ constexpr double kCoplanarDot = 0.9999; // ~0.81° tolerance, matches GL
|
||||
|
||||
} // namespace
|
||||
|
||||
WgpuAreaMeasurement::WgpuAreaMeasurement() = default;
|
||||
AreaMeasurement::AreaMeasurement() = default;
|
||||
|
||||
void WgpuAreaMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
void AreaMeasurement::clear(ViewportWindow& vp) {
|
||||
mesh_cache_.clear();
|
||||
selected_.clear();
|
||||
total_area_m2_ = 0.0;
|
||||
@@ -148,8 +148,8 @@ void WgpuAreaMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
vp.setOverlayLabels({});
|
||||
}
|
||||
|
||||
WgpuAreaMeasurement::MeshAdj*
|
||||
WgpuAreaMeasurement::meshAdj(WgpuViewportWindow& vp,
|
||||
AreaMeasurement::MeshAdj*
|
||||
AreaMeasurement::meshAdj(ViewportWindow& vp,
|
||||
uint32_t model_id, uint32_t mesh_id) {
|
||||
const uint64_t key = (uint64_t(model_id) << 32) | uint64_t(mesh_id);
|
||||
auto it = mesh_cache_.find(key);
|
||||
@@ -159,7 +159,7 @@ WgpuAreaMeasurement::meshAdj(WgpuViewportWindow& vp,
|
||||
// them in the per-mesh cache (positions can be hundreds of KB each
|
||||
// and live in the viewport already), so just look them up freshly
|
||||
// each time the user picks a brand-new mesh.
|
||||
WgpuViewportWindow::MeshTriangles tris;
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
if (!vp.readbackMeshTriangles(model_id, mesh_id, tris)) return nullptr;
|
||||
if (tris.indices.size() < 3) return nullptr;
|
||||
|
||||
@@ -190,12 +190,12 @@ WgpuAreaMeasurement::meshAdj(WgpuViewportWindow& vp,
|
||||
return &mesh_cache_.emplace(key, std::move(a)).first->second;
|
||||
}
|
||||
|
||||
void WgpuAreaMeasurement::onPick(WgpuViewportWindow& vp,
|
||||
void AreaMeasurement::onPick(ViewportWindow& vp,
|
||||
int x_phys, int y_phys, bool alt) {
|
||||
WgpuViewportWindow::MeshLocalPick pick;
|
||||
ViewportWindow::MeshLocalPick pick;
|
||||
if (!vp.pickMeshLocalAt(x_phys, y_phys, pick)) return;
|
||||
|
||||
WgpuViewportWindow::MeshTriangles tris;
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
if (!vp.readbackMeshTriangles(pick.model_id, pick.mesh_id, tris)) return;
|
||||
const size_t n_tris = tris.indices.size() / 3;
|
||||
if (n_tris == 0) return;
|
||||
@@ -285,7 +285,7 @@ void WgpuAreaMeasurement::onPick(WgpuViewportWindow& vp,
|
||||
total_area_m2_, selected_.size());
|
||||
}
|
||||
|
||||
void WgpuAreaMeasurement::rebuildHighlightAndLabels(WgpuViewportWindow& vp) {
|
||||
void AreaMeasurement::rebuildHighlightAndLabels(ViewportWindow& vp) {
|
||||
// 1) Highlight triangle list — each selected tri's three vertices
|
||||
// transformed by its captured composed_transform. Push as a
|
||||
// flat world-space tri list; the overlay tints them translucent
|
||||
@@ -295,20 +295,20 @@ void WgpuAreaMeasurement::rebuildHighlightAndLabels(WgpuViewportWindow& vp) {
|
||||
|
||||
// Cache the latest MeshTriangles per (model,mesh) for this rebuild
|
||||
// to avoid repeated viewport lookups when many tris share a mesh.
|
||||
std::unordered_map<uint64_t, WgpuViewportWindow::MeshTriangles> tris_cache;
|
||||
std::unordered_map<uint64_t, ViewportWindow::MeshTriangles> tris_cache;
|
||||
|
||||
auto get_tris = [&](uint32_t model_id, uint32_t mesh_id)
|
||||
-> WgpuViewportWindow::MeshTriangles* {
|
||||
-> ViewportWindow::MeshTriangles* {
|
||||
const uint64_t k = (uint64_t(model_id) << 32) | uint64_t(mesh_id);
|
||||
auto it = tris_cache.find(k);
|
||||
if (it != tris_cache.end()) return &it->second;
|
||||
WgpuViewportWindow::MeshTriangles t;
|
||||
ViewportWindow::MeshTriangles t;
|
||||
if (!vp.readbackMeshTriangles(model_id, mesh_id, t)) return nullptr;
|
||||
return &tris_cache.emplace(k, std::move(t)).first->second;
|
||||
};
|
||||
|
||||
for (const auto& [key, sel] : selected_) {
|
||||
WgpuViewportWindow::MeshTriangles* t = get_tris(sel.model_id, sel.mesh_id);
|
||||
ViewportWindow::MeshTriangles* t = get_tris(sel.model_id, sel.mesh_id);
|
||||
if (!t) continue;
|
||||
if (size_t(sel.tri) * 3 + 2 >= t->indices.size()) continue;
|
||||
const float* M = sel.composed_transform; // column-major
|
||||
@@ -337,11 +337,11 @@ void WgpuAreaMeasurement::rebuildHighlightAndLabels(WgpuViewportWindow& vp) {
|
||||
by_object[object_id].push_back(&sel);
|
||||
}
|
||||
|
||||
std::vector<WgpuOverlayRenderer::Label> labels;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
for (const auto& [obj_id, sels] : by_object) {
|
||||
if (sels.empty()) continue;
|
||||
const SelectedTri& any = *sels[0];
|
||||
WgpuViewportWindow::MeshTriangles* t = get_tris(any.model_id, any.mesh_id);
|
||||
ViewportWindow::MeshTriangles* t = get_tris(any.model_id, any.mesh_id);
|
||||
if (!t) continue;
|
||||
MeshAdj* adj = meshAdj(vp, any.model_id, any.mesh_id);
|
||||
if (!adj) continue;
|
||||
@@ -393,7 +393,7 @@ void WgpuAreaMeasurement::rebuildHighlightAndLabels(WgpuViewportWindow& vp) {
|
||||
cx /= area; cy /= area; cz /= area;
|
||||
|
||||
const float* M = any.composed_transform;
|
||||
WgpuOverlayRenderer::Label lbl;
|
||||
OverlayRenderer::Label lbl;
|
||||
lbl.world_pos[0] = float(M[0]*cx + M[4]*cy + M[8]*cz + M[12]);
|
||||
lbl.world_pos[1] = float(M[1]*cx + M[5]*cy + M[9]*cz + M[13]);
|
||||
lbl.world_pos[2] = float(M[2]*cx + M[6]*cy + M[10]*cz + M[14]);
|
||||
@@ -25,11 +25,11 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class WgpuViewportWindow;
|
||||
class ViewportWindow;
|
||||
|
||||
// Click-to-accumulate area measurement for the wgpu viewport. Mirrors
|
||||
// src/bonsaiviewer/Measurement.h's AreaMeasurement: each pick resolves
|
||||
// to (instance, triangle) via WgpuViewportWindow::pickMeshLocalAt, then
|
||||
// to (instance, triangle) via ViewportWindow::pickMeshLocalAt, then
|
||||
// either adds or removes the connected coplanar patch (BFS over shared
|
||||
// edges, dot(normal, seed_normal) > 0.9999) depending on whether the
|
||||
// seed triangle was already in the running set. Alt-click skips the BFS.
|
||||
@@ -37,18 +37,18 @@ class WgpuViewportWindow;
|
||||
// separate patches.
|
||||
//
|
||||
// On every mutation the world-space triangles of the running set are
|
||||
// pushed to WgpuViewportWindow::setHighlightTriangles for the
|
||||
// pushed to ViewportWindow::setHighlightTriangles for the
|
||||
// translucent cyan patch shading, and per-component "X.XXXX m²" labels
|
||||
// are pushed to setOverlayLabels at each connected component's
|
||||
// area-weighted centroid.
|
||||
class WgpuAreaMeasurement {
|
||||
class AreaMeasurement {
|
||||
public:
|
||||
WgpuAreaMeasurement();
|
||||
AreaMeasurement();
|
||||
|
||||
// Pixel coords are physical (post-DPR), to match
|
||||
// WgpuViewportWindow::pickMeshLocalAt's convention.
|
||||
void onPick(WgpuViewportWindow& vp, int x_phys, int y_phys, bool alt);
|
||||
void clear(WgpuViewportWindow& vp);
|
||||
// ViewportWindow::pickMeshLocalAt's convention.
|
||||
void onPick(ViewportWindow& vp, int x_phys, int y_phys, bool alt);
|
||||
void clear(ViewportWindow& vp);
|
||||
|
||||
double totalArea() const { return total_area_m2_; }
|
||||
size_t triangleCount() const { return selected_.size(); }
|
||||
@@ -57,7 +57,7 @@ private:
|
||||
// Cached per-mesh derived data: triangle normals + areas + edge
|
||||
// adjacency. Computed once per (model, mesh) on first pick; the
|
||||
// raw positions + indices live in
|
||||
// WgpuViewportWindow::readbackMeshTriangles' CPU shadow.
|
||||
// ViewportWindow::readbackMeshTriangles' CPU shadow.
|
||||
struct MeshAdj {
|
||||
std::vector<float> tri_normals; // 3 floats per tri (unit, mesh-local)
|
||||
std::vector<float> tri_areas; // mesh-local area per tri
|
||||
@@ -65,7 +65,7 @@ private:
|
||||
std::unordered_map<uint64_t, std::vector<uint32_t>> edges;
|
||||
};
|
||||
// Keyed by (model_id << 32) | mesh_id.
|
||||
MeshAdj* meshAdj(WgpuViewportWindow& vp,
|
||||
MeshAdj* meshAdj(ViewportWindow& vp,
|
||||
uint32_t model_id, uint32_t mesh_id);
|
||||
|
||||
// Per-selected-triangle record. The composed transform is captured
|
||||
@@ -86,7 +86,7 @@ private:
|
||||
return (uint64_t(object_id) << 32) | uint64_t(tri);
|
||||
}
|
||||
|
||||
void rebuildHighlightAndLabels(WgpuViewportWindow& vp);
|
||||
void rebuildHighlightAndLabels(ViewportWindow& vp);
|
||||
|
||||
std::unordered_map<uint64_t, MeshAdj> mesh_cache_;
|
||||
std::unordered_map<uint64_t, SelectedTri> selected_;
|
||||
@@ -17,18 +17,18 @@
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "WgpuBufferPool.h"
|
||||
#include "BufferPool.h"
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
WgpuBufferPool::~WgpuBufferPool() {
|
||||
BufferPool::~BufferPool() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
void WgpuBufferPool::configure(WGPUInstance instance, WGPUDevice device,
|
||||
void BufferPool::configure(WGPUInstance instance, WGPUDevice device,
|
||||
WGPUBufferUsage usage,
|
||||
uint64_t per_sub_buffer_capacity,
|
||||
const char* label_prefix) {
|
||||
@@ -41,7 +41,7 @@ void WgpuBufferPool::configure(WGPUInstance instance, WGPUDevice device,
|
||||
label_prefix_ = label_prefix ? label_prefix : "";
|
||||
}
|
||||
|
||||
void WgpuBufferPool::destroy() {
|
||||
void BufferPool::destroy() {
|
||||
for (auto& sp : sub_pools_) {
|
||||
if (sp.buffer) wgpuBufferRelease(sp.buffer);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ void WgpuBufferPool::destroy() {
|
||||
label_prefix_.clear();
|
||||
}
|
||||
|
||||
bool WgpuBufferPool::addSubBuffer() {
|
||||
bool BufferPool::addSubBuffer() {
|
||||
if (!device_ || per_sub_buffer_capacity_ == 0) return false;
|
||||
if (growth_disabled_) return false;
|
||||
|
||||
@@ -132,7 +132,7 @@ bool WgpuBufferPool::addSubBuffer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
WgpuBufferPool::Slice WgpuBufferPool::alloc(uint64_t size, uint64_t align) {
|
||||
BufferPool::Slice BufferPool::alloc(uint64_t size, uint64_t align) {
|
||||
Slice out;
|
||||
if (size == 0 || align == 0) return out;
|
||||
|
||||
@@ -179,7 +179,7 @@ WgpuBufferPool::Slice WgpuBufferPool::alloc(uint64_t size, uint64_t align) {
|
||||
return out;
|
||||
}
|
||||
|
||||
void WgpuBufferPool::free(const Slice& s) {
|
||||
void BufferPool::free(const Slice& s) {
|
||||
if (!s.valid()) return;
|
||||
if (s.sub_idx < 0 || size_t(s.sub_idx) >= sub_pools_.size()) return;
|
||||
SubPool& sp = sub_pools_[size_t(s.sub_idx)];
|
||||
@@ -202,19 +202,19 @@ void WgpuBufferPool::free(const Slice& s) {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t WgpuBufferPool::total_capacity_bytes() const {
|
||||
uint64_t BufferPool::total_capacity_bytes() const {
|
||||
uint64_t s = 0;
|
||||
for (const auto& sp : sub_pools_) s += sp.capacity;
|
||||
return s;
|
||||
}
|
||||
|
||||
uint64_t WgpuBufferPool::total_used_bytes() const {
|
||||
uint64_t BufferPool::total_used_bytes() const {
|
||||
uint64_t s = 0;
|
||||
for (const auto& sp : sub_pools_) s += sp.used;
|
||||
return s;
|
||||
}
|
||||
|
||||
uint64_t WgpuBufferPool::largest_free_run_bytes() const {
|
||||
uint64_t BufferPool::largest_free_run_bytes() const {
|
||||
uint64_t m = 0;
|
||||
for (const auto& sp : sub_pools_) {
|
||||
for (const auto& r : sp.free_ranges) {
|
||||
@@ -46,7 +46,7 @@
|
||||
// Allocator: per-sub-buffer sorted free list with adjacent-range
|
||||
// coalescing, first-fit across sub-buffers. Adequate for the chunk
|
||||
// workload (a few hundred allocations of broadly similar size).
|
||||
class WgpuBufferPool {
|
||||
class BufferPool {
|
||||
public:
|
||||
// A handle to a previously-allocated range. Includes the underlying
|
||||
// sub-buffer so callers (bind-group builders, queueWriteBuffer) can
|
||||
@@ -60,11 +60,11 @@ public:
|
||||
bool valid() const { return size > 0 && buffer != nullptr; }
|
||||
};
|
||||
|
||||
WgpuBufferPool() = default;
|
||||
~WgpuBufferPool();
|
||||
BufferPool() = default;
|
||||
~BufferPool();
|
||||
|
||||
WgpuBufferPool(const WgpuBufferPool&) = delete;
|
||||
WgpuBufferPool& operator=(const WgpuBufferPool&) = delete;
|
||||
BufferPool(const BufferPool&) = delete;
|
||||
BufferPool& operator=(const BufferPool&) = delete;
|
||||
|
||||
// Record the device + usage + sub-buffer size. Does NOT allocate any
|
||||
// sub-buffer here — that happens lazily on first alloc(). `instance`
|
||||
+129
-10
@@ -20,15 +20,105 @@
|
||||
message("Running CMakeLists.txt in /src/ifcviewer")
|
||||
|
||||
set(QT_VERSION 6 CACHE STRING "Qt version")
|
||||
# IfcViewerLib always needs OpenGL in addition to Core/Gui/Widgets. We don't
|
||||
# use the CACHE'd QT_COMPONENTS here because it may have been set by another
|
||||
# target (e.g. qtviewer) without the OpenGL component.
|
||||
find_package(Qt${QT_VERSION} COMPONENTS Core Gui REQUIRED PATHS ${QT_DIR})
|
||||
|
||||
# Eigen3 — used by Federation matrices, ViewportWindow's instance compose,
|
||||
# the per-model coordinate-operation matrices stored on ModelGpuData, and
|
||||
# everywhere a 4x4 transform shows up.
|
||||
find_package(Eigen3 REQUIRED)
|
||||
|
||||
# wgpu-native — fetched as a pre-built binary release from upstream.
|
||||
# Pin the version with WGPU_NATIVE_VERSION; bump to pull a newer release.
|
||||
set(WGPU_NATIVE_VERSION "v29.0.0.0" CACHE STRING "wgpu-native release tag")
|
||||
|
||||
# Pick the right release archive for the host platform.
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64")
|
||||
set(_wgpu_archive "wgpu-windows-aarch64-msvc-release.zip")
|
||||
else()
|
||||
set(_wgpu_archive "wgpu-windows-x86_64-msvc-release.zip")
|
||||
endif()
|
||||
set(_wgpu_lib "wgpu_native.dll.lib")
|
||||
set(_wgpu_runtime "wgpu_native.dll")
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
|
||||
set(_wgpu_archive "wgpu-macos-aarch64-release.zip")
|
||||
else()
|
||||
set(_wgpu_archive "wgpu-macos-x86_64-release.zip")
|
||||
endif()
|
||||
set(_wgpu_lib "libwgpu_native.dylib")
|
||||
else() # Linux + BSDs
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
|
||||
set(_wgpu_archive "wgpu-linux-aarch64-release.zip")
|
||||
else()
|
||||
set(_wgpu_archive "wgpu-linux-x86_64-release.zip")
|
||||
endif()
|
||||
set(_wgpu_lib "libwgpu_native.so")
|
||||
endif()
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
wgpu_native
|
||||
URL https://github.com/gfx-rs/wgpu-native/releases/download/${WGPU_NATIVE_VERSION}/${_wgpu_archive}
|
||||
DOWNLOAD_NO_PROGRESS FALSE
|
||||
)
|
||||
FetchContent_MakeAvailable(wgpu_native)
|
||||
|
||||
# Release archive layout: include/webgpu/*.h and lib/<libname>.
|
||||
#
|
||||
# The Linux .so shipped in the v29 release has no DT_SONAME, which causes
|
||||
# CMake to bake the relative IMPORTED_LOCATION path into DT_NEEDED. We patch
|
||||
# the SONAME in once at configure time so dependents get a clean
|
||||
# libwgpu_native.so reference, and pin the executable's rpath to the lib dir.
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_program(PATCHELF_EXECUTABLE patchelf)
|
||||
if(PATCHELF_EXECUTABLE)
|
||||
execute_process(
|
||||
COMMAND ${PATCHELF_EXECUTABLE} --set-soname "${_wgpu_lib}"
|
||||
"${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}"
|
||||
RESULT_VARIABLE _patchelf_rc
|
||||
)
|
||||
if(NOT _patchelf_rc EQUAL 0)
|
||||
message(WARNING "patchelf --set-soname failed on libwgpu_native.so")
|
||||
endif()
|
||||
else()
|
||||
message(WARNING
|
||||
"patchelf not found; libwgpu_native.so will be linked with a "
|
||||
"relative DT_NEEDED. Install patchelf to fix.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(wgpu_native SHARED IMPORTED GLOBAL)
|
||||
set_target_properties(wgpu_native PROPERTIES
|
||||
IMPORTED_LOCATION "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${wgpu_native_SOURCE_DIR}/include"
|
||||
)
|
||||
if(WIN32)
|
||||
# On Windows the .lib is the import library; the .dll is the runtime.
|
||||
set_target_properties(wgpu_native PROPERTIES
|
||||
IMPORTED_IMPLIB "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}"
|
||||
IMPORTED_LOCATION "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_runtime}"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Expose the lib dir so dependents can put it on their rpath.
|
||||
set(WGPU_NATIVE_LIB_DIR "${wgpu_native_SOURCE_DIR}/lib" CACHE INTERNAL
|
||||
"Directory containing the wgpu-native shared library")
|
||||
|
||||
file(GLOB IFCVIEWER_CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||
file(GLOB IFCVIEWER_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
||||
file(GLOB IFCVIEWER_H_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
|
||||
set(IFCVIEWER_FILES ${IFCVIEWER_CPP_FILES} ${IFCVIEWER_H_FILES})
|
||||
|
||||
# Cocoa bridge for the CAMetalLayer surface attach — Objective-C++.
|
||||
# Only compiled into the target on Apple platforms; CMake handles `.mm`
|
||||
# natively once OBJCXX is enabled.
|
||||
if(APPLE)
|
||||
enable_language(OBJCXX)
|
||||
list(APPEND IFCVIEWER_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/MetalSurface_mac.mm
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(IfcViewer STATIC ${IFCVIEWER_FILES})
|
||||
|
||||
set_target_properties(IfcViewer PROPERTIES
|
||||
@@ -38,9 +128,9 @@ set_target_properties(IfcViewer PROPERTIES
|
||||
)
|
||||
|
||||
if (WITH_MESH_OPTIMIZER)
|
||||
find_package(meshoptimizer REQUIRED)
|
||||
set(MESH_OPTIMIZER_LIB meshoptimizer::meshoptimizer)
|
||||
target_compile_definitions(IfcViewer PUBLIC -DWITH_MESH_OPTIMIZER)
|
||||
find_package(meshoptimizer REQUIRED)
|
||||
set(MESH_OPTIMIZER_LIB meshoptimizer::meshoptimizer)
|
||||
target_compile_definitions(IfcViewer PUBLIC -DWITH_MESH_OPTIMIZER)
|
||||
endif()
|
||||
|
||||
# Consumers include headers as `#include "ViewportWindow.h"`, so expose this
|
||||
@@ -57,23 +147,52 @@ target_link_libraries(IfcViewer PUBLIC
|
||||
${CGAL_LIBRARIES}
|
||||
Qt${QT_VERSION}::Core
|
||||
Qt${QT_VERSION}::Gui
|
||||
Eigen3::Eigen
|
||||
wgpu_native
|
||||
${MESH_OPTIMIZER_LIB}
|
||||
# SceneLoader drives WgpuViewportWindow; the wgpu lib also provides
|
||||
# the include path for WgpuViewportWindow.h that SceneLoader.h pulls in.
|
||||
IfcViewerWgpu
|
||||
)
|
||||
|
||||
# Qt platform-handle access (QNativeInterface::QX11Application etc.) is in
|
||||
# the public Gui headers in Qt 6.2+, no PRIVATE_INCLUDE_DIRS needed.
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(IfcViewer PUBLIC Threads::Threads)
|
||||
endif()
|
||||
|
||||
# Cocoa + QuartzCore for MetalSurface_mac.mm (NSView, CAMetalLayer).
|
||||
if(APPLE)
|
||||
target_link_libraries(IfcViewer PUBLIC
|
||||
"-framework Cocoa"
|
||||
"-framework QuartzCore"
|
||||
)
|
||||
endif()
|
||||
|
||||
install(TARGETS IfcViewer EXPORT ${IFCOPENSHELL_EXPORT_TARGETS})
|
||||
|
||||
install(FILES ${IFCVIEWER_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcviewer
|
||||
)
|
||||
|
||||
# Install the wgpu_native shared library so the deployed runtime can find it.
|
||||
# At build/run-from-build-tree time CMake adds wgpu_native_SOURCE_DIR to the
|
||||
# binary's rpath automatically (IMPORTED_LOCATION dirname).
|
||||
#
|
||||
# macOS: drop libwgpu_native.dylib straight into BonsaiViewer.app's
|
||||
# Frameworks/. The exe has `LC_LOAD_DYLIB @rpath/libwgpu_native.dylib`
|
||||
# (baked from the dylib's install_name), and BonsaiViewer's
|
||||
# INSTALL_RPATH is set to @executable_path/../Frameworks — together
|
||||
# they resolve at launch without depending on macdeployqt to follow
|
||||
# non-Qt @rpath references.
|
||||
if(WIN32)
|
||||
install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_runtime}" DESTINATION bin)
|
||||
elseif(APPLE AND BUILD_BONSAIVIEWER)
|
||||
install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}"
|
||||
DESTINATION "BonsaiViewer.app/Contents/Frameworks")
|
||||
else()
|
||||
install(FILES "${wgpu_native_SOURCE_DIR}/lib/${_wgpu_lib}" DESTINATION lib)
|
||||
endif()
|
||||
|
||||
if(BUILD_BONSAIVIEWER_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
+21
-21
@@ -17,9 +17,9 @@
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "WgpuLengthMeasurement.h"
|
||||
#include "LengthMeasurement.h"
|
||||
|
||||
#include "WgpuOverlayRenderer.h"
|
||||
#include "OverlayRenderer.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStringList>
|
||||
@@ -292,10 +292,10 @@ constexpr float DOT_HALO = 1.0f;
|
||||
constexpr float DASH_PERIOD = 9.0f; // px
|
||||
constexpr float DASH_ON_RATIO = 0.55f; // 5 on, 4 off
|
||||
|
||||
WgpuOverlayRenderer::LineGroup makeGroup(std::vector<float> xyz,
|
||||
OverlayRenderer::LineGroup makeGroup(std::vector<float> xyz,
|
||||
float r, float g, float b,
|
||||
bool dashed = false) {
|
||||
WgpuOverlayRenderer::LineGroup gp;
|
||||
OverlayRenderer::LineGroup gp;
|
||||
gp.world_xyz = std::move(xyz);
|
||||
gp.color[0] = r; gp.color[1] = g; gp.color[2] = b; gp.color[3] = 1.0f;
|
||||
gp.stroke_color[0] = 0.0f; gp.stroke_color[1] = 0.0f;
|
||||
@@ -318,10 +318,10 @@ void pushSeg(std::vector<float>& xyz,
|
||||
xyz.insert(xyz.end(), b.begin(), b.end());
|
||||
}
|
||||
|
||||
WgpuOverlayRenderer::Label makeLabel(const std::array<float, 3>& a,
|
||||
OverlayRenderer::Label makeLabel(const std::array<float, 3>& a,
|
||||
const std::array<float, 3>& b,
|
||||
const QString& text) {
|
||||
WgpuOverlayRenderer::Label lbl;
|
||||
OverlayRenderer::Label lbl;
|
||||
lbl.world_pos[0] = 0.5f * (a[0] + b[0]);
|
||||
lbl.world_pos[1] = 0.5f * (a[1] + b[1]);
|
||||
lbl.world_pos[2] = 0.5f * (a[2] + b[2]);
|
||||
@@ -329,7 +329,7 @@ WgpuOverlayRenderer::Label makeLabel(const std::array<float, 3>& a,
|
||||
return lbl;
|
||||
}
|
||||
|
||||
void pushDots(WgpuViewportWindow& vp, const std::vector<float>& xyz) {
|
||||
void pushDots(ViewportWindow& vp, const std::vector<float>& xyz) {
|
||||
vp.setOverlayPoints(xyz,
|
||||
/*inner*/ 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
/*size*/ DOT_SIZE,
|
||||
@@ -348,9 +348,9 @@ const char* dominantAxisLabel(const float v[3]) {
|
||||
|
||||
} // namespace
|
||||
|
||||
WgpuLengthMeasurement::WgpuLengthMeasurement() = default;
|
||||
LengthMeasurement::LengthMeasurement() = default;
|
||||
|
||||
void WgpuLengthMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
void LengthMeasurement::clear(ViewportWindow& vp) {
|
||||
points_.clear();
|
||||
normals_.clear();
|
||||
vp.setOverlayPoints({}, 0,0,0,0, 0, 0,0,0,0, 0);
|
||||
@@ -359,9 +359,9 @@ void WgpuLengthMeasurement::clear(WgpuViewportWindow& vp) {
|
||||
vp.setHudText(QString());
|
||||
}
|
||||
|
||||
void WgpuLengthMeasurement::onPick(WgpuViewportWindow& vp,
|
||||
void LengthMeasurement::onPick(ViewportWindow& vp,
|
||||
int x_phys, int y_phys, bool /*alt*/) {
|
||||
WgpuViewportWindow::MeshLocalPick pick;
|
||||
ViewportWindow::MeshLocalPick pick;
|
||||
if (!vp.pickMeshLocalAt(x_phys, y_phys, pick)) return;
|
||||
points_.push_back({pick.world_pos[0], pick.world_pos[1], pick.world_pos[2]});
|
||||
normals_.push_back({pick.world_normal[0], pick.world_normal[1], pick.world_normal[2]});
|
||||
@@ -371,14 +371,14 @@ void WgpuLengthMeasurement::onPick(WgpuViewportWindow& vp,
|
||||
rebuildOverlay(vp);
|
||||
}
|
||||
|
||||
void WgpuLengthMeasurement::removeLastPoint(WgpuViewportWindow& vp) {
|
||||
void LengthMeasurement::removeLastPoint(ViewportWindow& vp) {
|
||||
if (points_.empty()) return;
|
||||
points_.pop_back();
|
||||
if (!normals_.empty()) normals_.pop_back();
|
||||
rebuildOverlay(vp);
|
||||
}
|
||||
|
||||
void WgpuLengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) {
|
||||
void LengthMeasurement::rebuildOverlay(ViewportWindow& vp) {
|
||||
if (points_.size() == 1 && normals_.size() == 1) {
|
||||
rebuildLaserOverlay(vp);
|
||||
return;
|
||||
@@ -389,8 +389,8 @@ void WgpuLengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) {
|
||||
for (const auto& p : points_) pushDot(pts_xyz, p);
|
||||
pushDots(vp, pts_xyz);
|
||||
|
||||
std::vector<WgpuOverlayRenderer::LineGroup> groups;
|
||||
std::vector<WgpuOverlayRenderer::Label> labels;
|
||||
std::vector<OverlayRenderer::LineGroup> groups;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
const size_t n = points_.size();
|
||||
|
||||
if (n == 2) {
|
||||
@@ -489,7 +489,7 @@ void WgpuLengthMeasurement::rebuildOverlay(WgpuViewportWindow& vp) {
|
||||
vp.setHudText(formatReadout());
|
||||
}
|
||||
|
||||
void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) {
|
||||
void LengthMeasurement::rebuildLaserOverlay(ViewportWindow& vp) {
|
||||
const auto& wp = first_pick_.world_pos;
|
||||
const auto& n = first_pick_.world_normal;
|
||||
|
||||
@@ -519,8 +519,8 @@ void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) {
|
||||
n[0]*t1[1] - n[1]*t1[0],
|
||||
};
|
||||
|
||||
std::vector<WgpuOverlayRenderer::LineGroup> groups;
|
||||
std::vector<WgpuOverlayRenderer::Label> labels;
|
||||
std::vector<OverlayRenderer::LineGroup> groups;
|
||||
std::vector<OverlayRenderer::Label> labels;
|
||||
QStringList hud_lines;
|
||||
hud_lines << QStringLiteral("Laser measure (click another point for distance)");
|
||||
double enh[3] = {0.0, 0.0, 0.0};
|
||||
@@ -532,7 +532,7 @@ void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) {
|
||||
}
|
||||
|
||||
// Coplanar-patch BFS for face extent.
|
||||
WgpuViewportWindow::MeshTriangles tris;
|
||||
ViewportWindow::MeshTriangles tris;
|
||||
bool have_extent = false;
|
||||
double min_t1 = 0.0, max_t1 = 0.0, min_t2 = 0.0, max_t2 = 0.0;
|
||||
if (vp.readbackMeshTriangles(first_pick_.model_id, first_pick_.mesh_id, tris)) {
|
||||
@@ -658,7 +658,7 @@ void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) {
|
||||
wp[1] + NUDGE * n[1],
|
||||
wp[2] + NUDGE * n[2],
|
||||
};
|
||||
WgpuViewportWindow::RaycastHit hit;
|
||||
ViewportWindow::RaycastHit hit;
|
||||
if (vp.raycast(ro, n, hit)) {
|
||||
const double dist = double(hit.distance) + double(NUDGE);
|
||||
const std::array<float, 3> a = {wp[0], wp[1], wp[2]};
|
||||
@@ -682,7 +682,7 @@ void WgpuLengthMeasurement::rebuildLaserOverlay(WgpuViewportWindow& vp) {
|
||||
vp.setHudText(hud_lines.join('\n'));
|
||||
}
|
||||
|
||||
QString WgpuLengthMeasurement::formatReadout() const {
|
||||
QString LengthMeasurement::formatReadout() const {
|
||||
const size_t n = points_.size();
|
||||
if (n == 0) return QStringLiteral("Length tool: click first point");
|
||||
if (n == 1) return QStringLiteral("1 point (click another)");
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef WGPULENGTHMEASUREMENT_H
|
||||
#define WGPULENGTHMEASUREMENT_H
|
||||
|
||||
#include "WgpuViewportWindow.h"
|
||||
#include "ViewportWindow.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
@@ -45,21 +45,21 @@
|
||||
// Pushes the running set as overlay points + the connecting polyline +
|
||||
// per-segment labels to the viewport; the multi-line HUD carries the
|
||||
// adaptive readout.
|
||||
class WgpuLengthMeasurement {
|
||||
class LengthMeasurement {
|
||||
public:
|
||||
WgpuLengthMeasurement();
|
||||
LengthMeasurement();
|
||||
|
||||
// Pixel coords are physical (post-DPR). `alt` is currently unused
|
||||
// (kept for API symmetry with the Area tool).
|
||||
void onPick(WgpuViewportWindow& vp, int x_phys, int y_phys, bool alt);
|
||||
void removeLastPoint(WgpuViewportWindow& vp);
|
||||
void clear(WgpuViewportWindow& vp);
|
||||
void onPick(ViewportWindow& vp, int x_phys, int y_phys, bool alt);
|
||||
void removeLastPoint(ViewportWindow& vp);
|
||||
void clear(ViewportWindow& vp);
|
||||
|
||||
size_t pointCount() const { return points_.size(); }
|
||||
|
||||
private:
|
||||
void rebuildOverlay(WgpuViewportWindow& vp);
|
||||
void rebuildLaserOverlay(WgpuViewportWindow& vp);
|
||||
void rebuildOverlay(ViewportWindow& vp);
|
||||
void rebuildLaserOverlay(ViewportWindow& vp);
|
||||
QString formatReadout() const;
|
||||
|
||||
std::vector<std::array<float, 3>> points_;
|
||||
@@ -69,7 +69,7 @@ private:
|
||||
// updated afterwards. Used by the 1-pt laser BFS to locate the
|
||||
// mesh-local position of points_[0] without re-picking. Stays valid
|
||||
// while points_[0] does (pop_back never touches the first element).
|
||||
WgpuViewportWindow::MeshLocalPick first_pick_{};
|
||||
ViewportWindow::MeshLocalPick first_pick_{};
|
||||
};
|
||||
|
||||
#endif // WGPULENGTHMEASUREMENT_H
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Objective-C++ bridge between WgpuViewportWindow (pure C++) and Cocoa /
|
||||
* Objective-C++ bridge between ViewportWindow (pure C++) and Cocoa /
|
||||
* QuartzCore (Objective-C). Compiled only on macOS — see CMakeLists.txt.
|
||||
*
|
||||
* Qt's QWindow::winId() returns the backing NSView* (as a WId) on macOS;
|
||||
* we need a CAMetalLayer attached to that view to hand to wgpu-native
|
||||
* via WGPUSurfaceSourceMetalLayer. Doing that requires Objective-C, so
|
||||
* the actual layer attach lives in WgpuMetalSurface_mac.mm.
|
||||
* the actual layer attach lives in MetalSurface_mac.mm.
|
||||
*/
|
||||
|
||||
#ifndef WGPU_METAL_SURFACE_MAC_H
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Objective-C++ implementation of the Cocoa bridge declared in
|
||||
* WgpuMetalSurface_mac.h. Compiled only on macOS.
|
||||
* MetalSurface_mac.h. Compiled only on macOS.
|
||||
*/
|
||||
|
||||
#include "WgpuMetalSurface_mac.h"
|
||||
#include "MetalSurface_mac.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "InstancedGeometry.h"
|
||||
#include "WgpuBufferPool.h"
|
||||
#include "BufferPool.h"
|
||||
|
||||
// Per-model wgpu state. Mirrors the GL backend's ModelGpuData but with
|
||||
// wgpu handles. Stage 2 only allocates and uploads the four core buffers;
|
||||
@@ -49,7 +49,7 @@
|
||||
//
|
||||
// At INSTANCED_VERTEX_STRIDE_BYTES = 12 B/vertex this caps a chunk at
|
||||
// ~1.4 M vertices. 16 MB is the sweet spot once background-thread I/O
|
||||
// (WgpuStreamingThread) is in place: scatter-gather per-mesh seeks
|
||||
// (StreamingThread) is in place: scatter-gather per-mesh seeks
|
||||
// happen on the worker, not the render thread, so smaller chunks
|
||||
// (and thus more per-frame loads as orbit shifts) no longer stall
|
||||
// rendering. The win is much finer pool-allocation granularity —
|
||||
@@ -64,7 +64,7 @@
|
||||
// and 16 MB.
|
||||
static constexpr uint64_t WGPU_CHUNK_VERTEX_BYTES_LIMIT = 16ull * 1024 * 1024;
|
||||
|
||||
struct WgpuModelGpuData {
|
||||
struct ModelGpuData {
|
||||
// std430 layout: 16 bytes per entry, naturally aligned. base_vertex is
|
||||
// CHUNK-LOCAL — the bound vertex_storage on that chunk's bind group
|
||||
// gives the right slice when the shader indexes vertices[].
|
||||
@@ -77,7 +77,7 @@ struct WgpuModelGpuData {
|
||||
static_assert(sizeof(VisibleDrawGpu) == 16, "VisibleDrawGpu must be 16 bytes");
|
||||
|
||||
// Per-chunk state. Each chunk references a vertex range and an
|
||||
// index range inside WgpuViewportWindow::pool_, plus a small set of
|
||||
// index range inside ViewportWindow::pool_, plus a small set of
|
||||
// per-frame buffers (visible_draws, prefix_sums, uniform) and a bind
|
||||
// group that binds the pool ranges alongside the model-shared
|
||||
// mesh/instance storage. Rendering issues one drawcall per non-empty
|
||||
@@ -91,12 +91,12 @@ struct WgpuModelGpuData {
|
||||
// populates pool ranges at applyCachedModel time.
|
||||
struct Chunk {
|
||||
// Pool-allocated vertex + index bytes. Both slices land in the
|
||||
// shared WgpuViewportWindow::pool_; the slice tells us which
|
||||
// shared ViewportWindow::pool_; the slice tells us which
|
||||
// sub-buffer they live in (the pool may span multiple sub-buffers
|
||||
// when scenes exceed wgpu's single-buffer cap). When non-resident,
|
||||
// both .size are 0.
|
||||
WgpuBufferPool::Slice vertex_slice;
|
||||
WgpuBufferPool::Slice index_slice;
|
||||
BufferPool::Slice vertex_slice;
|
||||
BufferPool::Slice index_slice;
|
||||
|
||||
WGPUBuffer visible_draws_buffer = nullptr;
|
||||
WGPUBuffer prefix_sums_buffer = nullptr;
|
||||
@@ -357,6 +357,6 @@ struct WgpuModelGpuData {
|
||||
// Release every wgpu handle in `m` (including per-chunk and per-model pool
|
||||
// ranges via `pool.free()`) and clear its size mirrors. Safe to call
|
||||
// repeatedly; idempotent on already-released entries.
|
||||
void releaseWgpuModelGpuData(WgpuModelGpuData& m, WgpuBufferPool& pool);
|
||||
void releaseWgpuModelGpuData(ModelGpuData& m, BufferPool& pool);
|
||||
|
||||
#endif // WGPUMODELGPUDATA_H
|
||||
@@ -17,7 +17,7 @@
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "WgpuOverlayRenderer.h"
|
||||
#include "OverlayRenderer.h"
|
||||
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
@@ -439,11 +439,11 @@ fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
|
||||
// Construction / destruction
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
WgpuOverlayRenderer::~WgpuOverlayRenderer() {
|
||||
OverlayRenderer::~OverlayRenderer() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool WgpuOverlayRenderer::init(WGPUInstance instance, WGPUDevice device,
|
||||
bool OverlayRenderer::init(WGPUInstance instance, WGPUDevice device,
|
||||
WGPUQueue queue, WGPUTextureFormat surface_format,
|
||||
int sample_count) {
|
||||
instance_ = instance;
|
||||
@@ -461,7 +461,7 @@ bool WgpuOverlayRenderer::init(WGPUInstance instance, WGPUDevice device,
|
||||
return true;
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::destroy() {
|
||||
void OverlayRenderer::destroy() {
|
||||
// Axis indicator
|
||||
if (axis_bind_group_) { wgpuBindGroupRelease(axis_bind_group_); axis_bind_group_ = nullptr; }
|
||||
if (axis_pivot_pipeline_) { wgpuRenderPipelineRelease(axis_pivot_pipeline_); axis_pivot_pipeline_ = nullptr; }
|
||||
@@ -545,7 +545,7 @@ void WgpuOverlayRenderer::destroy() {
|
||||
// Axis indicator
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool WgpuOverlayRenderer::buildAxisIndicator() {
|
||||
bool OverlayRenderer::buildAxisIndicator() {
|
||||
// Bonsai decorator palette (src/bonsai/bonsai/bim/ui.py:593+):
|
||||
// decorator_color_error = (1.000, 0.200, 0.322) — red → +X
|
||||
// decorator_color_selected = (0.545, 0.863, 0.000) — green → +Y
|
||||
@@ -719,8 +719,8 @@ bool WgpuOverlayRenderer::buildAxisIndicator() {
|
||||
&& axis_corner_pipeline_;
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::encodePivot(WGPURenderPassEncoder pass,
|
||||
const WgpuOverlayFrame& f,
|
||||
void OverlayRenderer::encodePivot(WGPURenderPassEncoder pass,
|
||||
const OverlayFrame& f,
|
||||
bool visible) {
|
||||
if (!visible || !axis_pivot_pipeline_ || !axis_pivot_xray_pipeline_) return;
|
||||
if (f.viewport_h_px <= 0) return;
|
||||
@@ -760,9 +760,9 @@ void WgpuOverlayRenderer::encodePivot(WGPURenderPassEncoder pass,
|
||||
wgpuRenderPassEncoderDraw(pass, 18, 1, 0, 0);
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::encodeCornerAxis(WGPUCommandEncoder enc,
|
||||
void OverlayRenderer::encodeCornerAxis(WGPUCommandEncoder enc,
|
||||
WGPUTextureView surface_view,
|
||||
const WgpuOverlayFrame& f) {
|
||||
const OverlayFrame& f) {
|
||||
if (!axis_corner_pipeline_ || !surface_view) return;
|
||||
const int dpr = std::max(1, f.device_pixel_ratio);
|
||||
const uint32_t gizmo_size = uint32_t(110 * dpr);
|
||||
@@ -829,7 +829,7 @@ void WgpuOverlayRenderer::encodeCornerAxis(WGPUCommandEncoder enc,
|
||||
// Section plane visualizer
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool WgpuOverlayRenderer::buildSectionVisualizer() {
|
||||
bool OverlayRenderer::buildSectionVisualizer() {
|
||||
struct Seg {
|
||||
std::array<float, 3> s, e;
|
||||
std::array<float, 3> c;
|
||||
@@ -967,9 +967,9 @@ bool WgpuOverlayRenderer::buildSectionVisualizer() {
|
||||
return section_pipeline_ != nullptr;
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::encodeSectionGizmos(WGPURenderPassEncoder pass,
|
||||
const WgpuOverlayFrame& f,
|
||||
const std::vector<WgpuSectionPlane>& planes) {
|
||||
void OverlayRenderer::encodeSectionGizmos(WGPURenderPassEncoder pass,
|
||||
const OverlayFrame& f,
|
||||
const std::vector<SectionPlane>& planes) {
|
||||
if (!section_pipeline_ || planes.empty()) return;
|
||||
|
||||
wgpuRenderPassEncoderSetPipeline(pass, section_pipeline_);
|
||||
@@ -978,7 +978,7 @@ void WgpuOverlayRenderer::encodeSectionGizmos(WGPURenderPassEncoder pass,
|
||||
|
||||
const int n = std::min<int>(int(planes.size()), kMaxSectionPlanes);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
const WgpuSectionPlane& p = planes[i];
|
||||
const SectionPlane& p = planes[i];
|
||||
|
||||
// Stable in-plane basis: pick the world axis least parallel to n
|
||||
// so the cross-product stays well-conditioned at any orientation.
|
||||
@@ -1021,7 +1021,7 @@ void WgpuOverlayRenderer::encodeSectionGizmos(WGPURenderPassEncoder pass,
|
||||
// Marquee
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool WgpuOverlayRenderer::buildMarquee() {
|
||||
bool OverlayRenderer::buildMarquee() {
|
||||
struct Seg { std::array<float, 2> s, e; };
|
||||
static const Seg segs[] = {
|
||||
{ {0, 0}, {1, 0} },
|
||||
@@ -1183,9 +1183,9 @@ bool WgpuOverlayRenderer::buildMarquee() {
|
||||
return marquee_pipeline_ != nullptr && marquee_fill_pipeline_ != nullptr;
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::encodeMarquee(WGPUCommandEncoder enc,
|
||||
void OverlayRenderer::encodeMarquee(WGPUCommandEncoder enc,
|
||||
WGPUTextureView surface_view,
|
||||
const WgpuOverlayFrame& f,
|
||||
const OverlayFrame& f,
|
||||
QPoint start_logical_px,
|
||||
QPoint current_logical_px,
|
||||
bool active) {
|
||||
@@ -1258,7 +1258,7 @@ void WgpuOverlayRenderer::encodeMarquee(WGPUCommandEncoder enc,
|
||||
// Overlay lines
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool WgpuOverlayRenderer::buildOverlayLines() {
|
||||
bool OverlayRenderer::buildOverlayLines() {
|
||||
// Empty initial buffers — both grow on demand inside setOverlayLines.
|
||||
// Use a tiny starter capacity so the very first set call doesn't have
|
||||
// to special-case "buffer is null."
|
||||
@@ -1379,7 +1379,7 @@ bool WgpuOverlayRenderer::buildOverlayLines() {
|
||||
return overlay_line_pipeline_ != nullptr;
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::setOverlayLines(const std::vector<LineGroup>& groups) {
|
||||
void OverlayRenderer::setOverlayLines(const std::vector<LineGroup>& groups) {
|
||||
overlay_line_draws_.clear();
|
||||
if (groups.empty()) return;
|
||||
|
||||
@@ -1493,8 +1493,8 @@ void WgpuOverlayRenderer::setOverlayLines(const std::vector<LineGroup>& groups)
|
||||
}
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::encodeOverlayLines(WGPURenderPassEncoder pass,
|
||||
const WgpuOverlayFrame& f) {
|
||||
void OverlayRenderer::encodeOverlayLines(WGPURenderPassEncoder pass,
|
||||
const OverlayFrame& f) {
|
||||
if (!overlay_line_pipeline_ || overlay_line_draws_.empty()) return;
|
||||
if (f.viewport_w_px <= 0 || f.viewport_h_px <= 0) return;
|
||||
|
||||
@@ -1529,7 +1529,7 @@ void WgpuOverlayRenderer::encodeOverlayLines(WGPURenderPassEncoder pass,
|
||||
// Overlay points
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool WgpuOverlayRenderer::buildOverlayPoints() {
|
||||
bool OverlayRenderer::buildOverlayPoints() {
|
||||
{
|
||||
WGPUBufferDescriptor bdesc = {};
|
||||
bdesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst;
|
||||
@@ -1642,7 +1642,7 @@ bool WgpuOverlayRenderer::buildOverlayPoints() {
|
||||
return overlay_point_pipeline_ != nullptr;
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::setOverlayPoints(const std::vector<float>& world_xyz,
|
||||
void OverlayRenderer::setOverlayPoints(const std::vector<float>& world_xyz,
|
||||
float r, float g, float b, float a,
|
||||
float pixel_size,
|
||||
float stroke_r, float stroke_g,
|
||||
@@ -1715,8 +1715,8 @@ void WgpuOverlayRenderer::setOverlayPoints(const std::vector<float>& world_xyz,
|
||||
&inner_norm, sizeof(inner_norm));
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::encodeOverlayPoints(WGPURenderPassEncoder pass,
|
||||
const WgpuOverlayFrame& f) {
|
||||
void OverlayRenderer::encodeOverlayPoints(WGPURenderPassEncoder pass,
|
||||
const OverlayFrame& f) {
|
||||
if (!overlay_point_pipeline_ || overlay_point_vertex_count_ == 0) return;
|
||||
if (f.viewport_w_px <= 0 || f.viewport_h_px <= 0) return;
|
||||
|
||||
@@ -1738,7 +1738,7 @@ void WgpuOverlayRenderer::encodeOverlayPoints(WGPURenderPassEncoder pass,
|
||||
// Highlight triangles (translucent world-space triangle list)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool WgpuOverlayRenderer::buildHighlightTriangles() {
|
||||
bool OverlayRenderer::buildHighlightTriangles() {
|
||||
{
|
||||
WGPUBufferDescriptor bdesc = {};
|
||||
bdesc.usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst;
|
||||
@@ -1853,7 +1853,7 @@ bool WgpuOverlayRenderer::buildHighlightTriangles() {
|
||||
return highlight_pipeline_ != nullptr;
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::setHighlightTriangles(
|
||||
void OverlayRenderer::setHighlightTriangles(
|
||||
const std::vector<float>& world_xyz,
|
||||
float r, float g, float b, float a) {
|
||||
highlight_color_[0] = r;
|
||||
@@ -1881,8 +1881,8 @@ void WgpuOverlayRenderer::setHighlightTriangles(
|
||||
highlight_vertex_count_ = uint32_t(n_floats / 3);
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::encodeHighlightTriangles(WGPURenderPassEncoder pass,
|
||||
const WgpuOverlayFrame& f) {
|
||||
void OverlayRenderer::encodeHighlightTriangles(WGPURenderPassEncoder pass,
|
||||
const OverlayFrame& f) {
|
||||
if (!highlight_pipeline_ || highlight_vertex_count_ == 0) return;
|
||||
// Pack mat4 + vec4 into the slot. mat4 is column-major 16 floats.
|
||||
uint8_t slot[80] = {};
|
||||
@@ -1901,7 +1901,7 @@ void WgpuOverlayRenderer::encodeHighlightTriangles(WGPURenderPassEncoder pass,
|
||||
// Labels + HUD text (textured quads, content-cached)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool WgpuOverlayRenderer::buildLabels() {
|
||||
bool OverlayRenderer::buildLabels() {
|
||||
{
|
||||
WGPUSamplerDescriptor sd = {};
|
||||
sd.minFilter = WGPUFilterMode_Linear;
|
||||
@@ -2001,7 +2001,7 @@ bool WgpuOverlayRenderer::buildLabels() {
|
||||
return label_pipeline_ != nullptr;
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::releaseLabelTextures() {
|
||||
void OverlayRenderer::releaseLabelTextures() {
|
||||
for (auto it = label_tex_cache_.begin(); it != label_tex_cache_.end(); ++it) {
|
||||
if (it.value().bind_group) wgpuBindGroupRelease(it.value().bind_group);
|
||||
if (it.value().view) wgpuTextureViewRelease(it.value().view);
|
||||
@@ -2010,16 +2010,16 @@ void WgpuOverlayRenderer::releaseLabelTextures() {
|
||||
label_tex_cache_.clear();
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::setOverlayLabels(const std::vector<Label>& labels) {
|
||||
void OverlayRenderer::setOverlayLabels(const std::vector<Label>& labels) {
|
||||
labels_ = labels;
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::setHudText(const QString& text) {
|
||||
void OverlayRenderer::setHudText(const QString& text) {
|
||||
hud_text_ = text;
|
||||
}
|
||||
|
||||
WgpuOverlayRenderer::LabelTexture*
|
||||
WgpuOverlayRenderer::getOrCreateLabelTexture(const QString& cache_key,
|
||||
OverlayRenderer::LabelTexture*
|
||||
OverlayRenderer::getOrCreateLabelTexture(const QString& cache_key,
|
||||
const QString& text,
|
||||
int font_pt,
|
||||
int dpr) {
|
||||
@@ -2129,9 +2129,9 @@ WgpuOverlayRenderer::getOrCreateLabelTexture(const QString& cache_key,
|
||||
return &inserted.value();
|
||||
}
|
||||
|
||||
void WgpuOverlayRenderer::encodeLabels(WGPUCommandEncoder enc,
|
||||
void OverlayRenderer::encodeLabels(WGPUCommandEncoder enc,
|
||||
WGPUTextureView surface_view,
|
||||
const WgpuOverlayFrame& f) {
|
||||
const OverlayFrame& f) {
|
||||
if (!label_pipeline_ || !surface_view) return;
|
||||
if (labels_.empty() && hud_text_.isEmpty()) {
|
||||
// Selection cleared / tool exited — drop the cache so we don't
|
||||
@@ -34,7 +34,7 @@
|
||||
// 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 WgpuOverlayFrame {
|
||||
struct OverlayFrame {
|
||||
QMatrix4x4 view_proj;
|
||||
QVector3D camera_target;
|
||||
float camera_distance = 5.0f;
|
||||
@@ -50,7 +50,7 @@ struct WgpuOverlayFrame {
|
||||
// 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 WgpuSectionPlane {
|
||||
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
|
||||
@@ -61,7 +61,7 @@ struct WgpuSectionPlane {
|
||||
|
||||
// All viewport overlays in one place: axis indicator (corner + pivot),
|
||||
// section plane gizmos, and the marquee drag rect. Mirrors GL's
|
||||
// OverlayRenderer split so WgpuViewportWindow.cpp doesn't have to
|
||||
// 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
|
||||
@@ -69,13 +69,13 @@ struct WgpuSectionPlane {
|
||||
// uniforms get re-written.
|
||||
//
|
||||
// Threading: all calls are main-thread only — they touch the wgpu queue.
|
||||
class WgpuOverlayRenderer {
|
||||
class OverlayRenderer {
|
||||
public:
|
||||
WgpuOverlayRenderer() = default;
|
||||
~WgpuOverlayRenderer();
|
||||
OverlayRenderer() = default;
|
||||
~OverlayRenderer();
|
||||
|
||||
WgpuOverlayRenderer(const WgpuOverlayRenderer&) = delete;
|
||||
WgpuOverlayRenderer& operator=(const WgpuOverlayRenderer&) = delete;
|
||||
OverlayRenderer(const OverlayRenderer&) = delete;
|
||||
OverlayRenderer& operator=(const OverlayRenderer&) = delete;
|
||||
|
||||
bool init(WGPUInstance instance, WGPUDevice device, WGPUQueue queue,
|
||||
WGPUTextureFormat surface_format, int sample_count);
|
||||
@@ -87,15 +87,15 @@ public:
|
||||
// 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 WgpuOverlayFrame& f,
|
||||
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 WgpuOverlayFrame& f,
|
||||
const std::vector<WgpuSectionPlane>& planes);
|
||||
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-
|
||||
@@ -107,7 +107,7 @@ public:
|
||||
void setHighlightTriangles(const std::vector<float>& world_xyz,
|
||||
float r, float g, float b, float a);
|
||||
void encodeHighlightTriangles(WGPURenderPassEncoder pass,
|
||||
const WgpuOverlayFrame& f);
|
||||
const OverlayFrame& f);
|
||||
|
||||
// One stylistic group of world-space line segments. Mirrors GL
|
||||
// OverlayRenderer::LineGroup so callers can target either backend
|
||||
@@ -134,7 +134,7 @@ public:
|
||||
// per-group uniforms. Drawn inside the main MSAA pass so the lines
|
||||
// are depth-tested against geometry.
|
||||
void encodeOverlayLines(WGPURenderPassEncoder pass,
|
||||
const WgpuOverlayFrame& f);
|
||||
const OverlayFrame& f);
|
||||
|
||||
// Replace the overlay-point set. World-space positions are CPU-
|
||||
// expanded into screen-space quads at encode time. `pixel_size` is
|
||||
@@ -153,7 +153,7 @@ public:
|
||||
// inside the main MSAA pass so depth-test correctly hides points
|
||||
// behind closer geometry.
|
||||
void encodeOverlayPoints(WGPURenderPassEncoder pass,
|
||||
const WgpuOverlayFrame& f);
|
||||
const OverlayFrame& f);
|
||||
|
||||
// World-anchored text label. Mirrors GL OverlayRenderer::Label so
|
||||
// measure-tool readouts can target either backend.
|
||||
@@ -175,7 +175,7 @@ public:
|
||||
// overlay above.
|
||||
void encodeLabels(WGPUCommandEncoder enc,
|
||||
WGPUTextureView surface_view,
|
||||
const WgpuOverlayFrame& f);
|
||||
const OverlayFrame& f);
|
||||
|
||||
// ---- After the edge silhouette pass, on the resolved surface ----
|
||||
|
||||
@@ -183,13 +183,13 @@ public:
|
||||
// projection — only the camera direction matters.
|
||||
void encodeCornerAxis(WGPUCommandEncoder enc,
|
||||
WGPUTextureView surface_view,
|
||||
const WgpuOverlayFrame& f);
|
||||
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 WgpuOverlayFrame& f,
|
||||
const OverlayFrame& f,
|
||||
QPoint start_logical_px,
|
||||
QPoint current_logical_px,
|
||||
bool active);
|
||||
@@ -37,7 +37,7 @@ void SceneLoader::setShouldWriteSidecar(bool enabled) {
|
||||
should_write_sidecar_ = enabled;
|
||||
}
|
||||
|
||||
SceneLoader::SceneLoader(WgpuViewportWindow* viewport, QObject* parent)
|
||||
SceneLoader::SceneLoader(ViewportWindow* viewport, QObject* parent)
|
||||
: QObject(parent), viewport_(viewport)
|
||||
{
|
||||
connect(&element_poll_timer_, &QTimer::timeout,
|
||||
|
||||
@@ -35,13 +35,13 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Federation.h"
|
||||
#include "../ifcviewer-wgpu/WgpuViewportWindow.h"
|
||||
#include "../ifcviewer-wgpu/WgpuStreamingLoader.h"
|
||||
#include "../ifcviewer/ViewportWindow.h"
|
||||
#include "../ifcviewer/StreamingLoader.h"
|
||||
#include "GeometryStreamer.h"
|
||||
#include "SidecarBuilder.h"
|
||||
#include "SidecarCache.h"
|
||||
|
||||
// Drives IFC file loading into a WgpuViewportWindow. Owns the per-model
|
||||
// Drives IFC file loading into a ViewportWindow. Owns the per-model
|
||||
// GeometryStreamer, the load queue, the sidecar read thread, and the
|
||||
// next-free object_id counter used to rebase cached models onto the
|
||||
// current session's ID space.
|
||||
@@ -55,7 +55,7 @@
|
||||
class SceneLoader : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SceneLoader(WgpuViewportWindow* viewport, QObject* parent = nullptr);
|
||||
explicit SceneLoader(ViewportWindow* viewport, QObject* parent = nullptr);
|
||||
~SceneLoader();
|
||||
|
||||
// Sidecar cache use is opt-in per direction. Embedders that don't care
|
||||
@@ -165,7 +165,7 @@ private:
|
||||
void applySidecarData(uint32_t mid, StreamingSidecar metadata);
|
||||
void startDataSourceLoad(uint32_t mid);
|
||||
|
||||
WgpuViewportWindow* viewport_ = nullptr;
|
||||
ViewportWindow* viewport_ = nullptr;
|
||||
bool should_read_sidecar_ = false;
|
||||
bool should_write_sidecar_ = false;
|
||||
std::map<uint32_t, Model> models_;
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
//
|
||||
// The GPU consumes a flat u32 array indexed by object_id: bit 0 = selected,
|
||||
// bit 1 = active. Sized to (max_object_id + 1) by the caller.
|
||||
class WgpuSelectionState {
|
||||
class SelectionState {
|
||||
public:
|
||||
void clear() {
|
||||
if (ids_.empty() && active_ == 0) return;
|
||||
@@ -33,7 +33,7 @@
|
||||
// Streaming reader returns offsets to the two skipped sections so chunks
|
||||
// can be range-read on demand. File handle is closed before return.
|
||||
|
||||
#include "WgpuStreamingLoader.h"
|
||||
#include "StreamingLoader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
@@ -17,24 +17,24 @@
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include "WgpuStreamingThread.h"
|
||||
#include "StreamingThread.h"
|
||||
|
||||
#include "WgpuStreamingLoader.h"
|
||||
#include "StreamingLoader.h"
|
||||
|
||||
WgpuStreamingThread::~WgpuStreamingThread() {
|
||||
StreamingThread::~StreamingThread() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void WgpuStreamingThread::start() {
|
||||
void StreamingThread::start() {
|
||||
std::unique_lock lk(mu_);
|
||||
if (running_) return;
|
||||
shutdown_ = false;
|
||||
running_ = true;
|
||||
lk.unlock();
|
||||
worker_ = std::thread(&WgpuStreamingThread::workerLoop, this);
|
||||
worker_ = std::thread(&StreamingThread::workerLoop, this);
|
||||
}
|
||||
|
||||
void WgpuStreamingThread::stop() {
|
||||
void StreamingThread::stop() {
|
||||
{
|
||||
std::unique_lock lk(mu_);
|
||||
if (!running_) return;
|
||||
@@ -48,7 +48,7 @@ void WgpuStreamingThread::stop() {
|
||||
results_.clear();
|
||||
}
|
||||
|
||||
bool WgpuStreamingThread::enqueue(Request req) {
|
||||
bool StreamingThread::enqueue(Request req) {
|
||||
{
|
||||
std::unique_lock lk(mu_);
|
||||
if (!running_ || shutdown_) return false;
|
||||
@@ -58,7 +58,7 @@ bool WgpuStreamingThread::enqueue(Request req) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<WgpuStreamingThread::Result> WgpuStreamingThread::drainResults() {
|
||||
std::vector<StreamingThread::Result> StreamingThread::drainResults() {
|
||||
std::vector<Result> out;
|
||||
{
|
||||
std::unique_lock lk(mu_);
|
||||
@@ -71,12 +71,12 @@ std::vector<WgpuStreamingThread::Result> WgpuStreamingThread::drainResults() {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::size_t WgpuStreamingThread::inFlightApprox() const {
|
||||
std::size_t StreamingThread::inFlightApprox() const {
|
||||
std::unique_lock lk(mu_);
|
||||
return requests_.size() + (in_progress_ ? 1u : 0u);
|
||||
}
|
||||
|
||||
void WgpuStreamingThread::workerLoop() {
|
||||
void StreamingThread::workerLoop() {
|
||||
for (;;) {
|
||||
Request req;
|
||||
{
|
||||
@@ -41,7 +41,7 @@
|
||||
// joins. The Result destructor releases its byte vectors back to the
|
||||
// heap, so dropping unclaimed Results (e.g. when their model was
|
||||
// unloaded mid-flight) is a free operation.
|
||||
class WgpuStreamingThread {
|
||||
class StreamingThread {
|
||||
public:
|
||||
struct Request {
|
||||
uint32_t model_id;
|
||||
@@ -63,13 +63,13 @@ public:
|
||||
std::vector<uint32_t> idx;
|
||||
};
|
||||
|
||||
~WgpuStreamingThread();
|
||||
~StreamingThread();
|
||||
|
||||
// Spawn the worker thread. Safe to call once; subsequent calls are
|
||||
// no-ops while the worker is alive.
|
||||
void start();
|
||||
// Signal shutdown, wake the worker, join. Idempotent. Must be
|
||||
// called before the WgpuBufferPool the results would upload into
|
||||
// called before the BufferPool the results would upload into
|
||||
// is destroyed.
|
||||
void stop();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,12 +41,12 @@
|
||||
#include <unordered_set>
|
||||
|
||||
#include "SidecarCache.h"
|
||||
#include "WgpuBufferPool.h"
|
||||
#include "WgpuModelGpuData.h"
|
||||
#include "WgpuOverlayRenderer.h"
|
||||
#include "WgpuSelectionState.h"
|
||||
#include "WgpuStreamingThread.h"
|
||||
#include "WgpuVisibilityState.h"
|
||||
#include "BufferPool.h"
|
||||
#include "ModelGpuData.h"
|
||||
#include "OverlayRenderer.h"
|
||||
#include "SelectionState.h"
|
||||
#include "StreamingThread.h"
|
||||
#include "VisibilityState.h"
|
||||
|
||||
// Stage-2 wgpu viewport: opens a native QWindow, brings up a wgpu instance/
|
||||
// adapter/device, configures a surface against the platform-native window
|
||||
@@ -56,11 +56,11 @@
|
||||
//
|
||||
// Mirrors the lifecycle shape of the GL ViewportWindow so subsequent stages
|
||||
// can grow this into a full IFC renderer without restructuring the host.
|
||||
class WgpuViewportWindow : public QWindow {
|
||||
class ViewportWindow : public QWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WgpuViewportWindow(QWindow* parent = nullptr);
|
||||
~WgpuViewportWindow();
|
||||
explicit ViewportWindow(QWindow* parent = nullptr);
|
||||
~ViewportWindow();
|
||||
|
||||
void setBackgroundColor(const QColor& color);
|
||||
|
||||
@@ -103,7 +103,7 @@ public:
|
||||
void resetScene();
|
||||
|
||||
// Model-level visibility. Mirrors the GL ViewportWindow API — flips
|
||||
// WgpuModelGpuData::hidden, which every render/pick/cull pass already
|
||||
// ModelGpuData::hidden, which every render/pick/cull pass already
|
||||
// consults. requestUpdate() so the change is visible immediately.
|
||||
void hideModel(uint32_t model_id);
|
||||
void showModel(uint32_t model_id);
|
||||
@@ -213,7 +213,7 @@ private:
|
||||
// 2D pixel area covered on screen. This is the streaming loader's
|
||||
// chunk-priority metric — extracted from driveStreamingLoads as a
|
||||
// member so the click-and-track diagnostic can compare scores.
|
||||
float chunkScreenAreaPx(const WgpuModelGpuData::Chunk& c,
|
||||
float chunkScreenAreaPx(const ModelGpuData::Chunk& c,
|
||||
const QMatrix4x4& vp_mat) const;
|
||||
|
||||
public:
|
||||
@@ -250,27 +250,27 @@ private:
|
||||
void shutdown();
|
||||
|
||||
bool buildPipelines();
|
||||
void buildModelBindGroup(WgpuModelGpuData& m);
|
||||
void buildChunkBindGroup(WgpuModelGpuData& m, size_t chunk_idx);
|
||||
void buildModelBindGroup(ModelGpuData& m);
|
||||
void buildChunkBindGroup(ModelGpuData& m, size_t chunk_idx);
|
||||
// Streaming: read the chunk's vertex + index bytes from disk,
|
||||
// sub-allocate ranges in pool_, queueWriteBuffer them in, build the
|
||||
// chunk's bind group, flip is_resident=true. Returns true on success;
|
||||
// false if either the disk read or a pool alloc fails (caller is
|
||||
// expected to have already evicted enough). No-op (returns true)
|
||||
// when already resident.
|
||||
bool loadChunkBytesAndUploadGpu(WgpuModelGpuData& m, size_t chunk_idx);
|
||||
bool loadChunkBytesAndUploadGpu(ModelGpuData& m, size_t chunk_idx);
|
||||
// Pool-allocate + queueWriteBuffer + build bind group for a chunk
|
||||
// whose vbytes/idx have already been read (by either the worker
|
||||
// thread's drained result or the sync fallback). Returns false on
|
||||
// pool OOM. Toggles is_resident=true / is_loading=false on success.
|
||||
bool applyStreamedChunk(WgpuModelGpuData& m, size_t chunk_idx,
|
||||
bool applyStreamedChunk(ModelGpuData& m, size_t chunk_idx,
|
||||
const std::vector<uint8_t>& vbytes,
|
||||
const std::vector<uint32_t>& idx);
|
||||
// Release a resident chunk's pool ranges + bind group; flip
|
||||
// is_resident=false. The chunk's CPU metadata (offsets, AABB,
|
||||
// visible-draw scratch) is retained so a subsequent
|
||||
// loadChunkBytesAndUploadGpu can bring it back without re-planning.
|
||||
void unloadChunk(WgpuModelGpuData& m, size_t chunk_idx);
|
||||
void unloadChunk(ModelGpuData& m, size_t chunk_idx);
|
||||
// Called from render() after cull: find non-resident chunks with
|
||||
// current visible draw counts > 0 and bring them resident. When the
|
||||
// pool is full, evicts LRU non-visible chunks first, then falls back
|
||||
@@ -294,7 +294,7 @@ private:
|
||||
// Show/hide the pivot indicator. hide_after_ms > 0 starts the
|
||||
// single-shot auto-hide timer used by the wheel-zoom afterglow;
|
||||
// drag callers pass 0 and toggle manually on press/release. The
|
||||
// actual gizmo rendering lives in WgpuOverlayRenderer — this just
|
||||
// actual gizmo rendering lives in OverlayRenderer — this just
|
||||
// manages the UI-side visibility timer.
|
||||
void setPivotIndicatorVisible(bool visible, int hide_after_ms = 0);
|
||||
void releaseEdgeResources();
|
||||
@@ -355,14 +355,14 @@ public:
|
||||
// Overlay primitives. Mirror GL ViewportWindow so the Measurement +
|
||||
// dimension tools can target either backend through one API.
|
||||
// Empty inputs clears the corresponding set.
|
||||
void setOverlayLines(const std::vector<WgpuOverlayRenderer::LineGroup>& groups);
|
||||
void setOverlayLines(const std::vector<OverlayRenderer::LineGroup>& groups);
|
||||
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);
|
||||
void setOverlayLabels(const std::vector<WgpuOverlayRenderer::Label>& labels);
|
||||
void setOverlayLabels(const std::vector<OverlayRenderer::Label>& labels);
|
||||
void setHudText(const QString& text);
|
||||
// Translucent world-space triangle overlay (Area-tool patch shading).
|
||||
// Empty list disables; color is RGBA in [0, 1].
|
||||
@@ -375,7 +375,7 @@ public:
|
||||
// (model_id, mesh_id) pair doesn't resolve. Matches the GL
|
||||
// ViewportWindow::MeshTriangles + readbackMeshTriangles shape so
|
||||
// the measure tools port verbatim.
|
||||
using MeshTriangles = WgpuModelGpuData::MeshTriangles;
|
||||
using MeshTriangles = ModelGpuData::MeshTriangles;
|
||||
bool readbackMeshTriangles(uint32_t model_id, uint32_t mesh_id,
|
||||
MeshTriangles& out) const;
|
||||
|
||||
@@ -423,8 +423,8 @@ public:
|
||||
// Selection accessor. Exposed for callers (bonsai's volume readout)
|
||||
// that need to read selectionIds() / activeObjectId(). Mutation goes
|
||||
// through the existing setSelection / pick paths.
|
||||
WgpuSelectionState& selection() { return selection_; }
|
||||
const WgpuSelectionState& selection() const { return selection_; }
|
||||
SelectionState& selection() { return selection_; }
|
||||
const SelectionState& selection() const { return selection_; }
|
||||
|
||||
// Pick + resolve to mesh-local space. Runs pickSurfaceAt to get the
|
||||
// world-space hit, then inverts the instance's composed transform
|
||||
@@ -499,14 +499,14 @@ signals:
|
||||
// Selection moved by a pick / marquee. Emitted with the active id
|
||||
// (0 = miss). Bonsai mirrors this into SessionState.
|
||||
void objectPicked(uint32_t object_id);
|
||||
void frameStatsUpdated(const WgpuViewportWindow::FrameStats& stats);
|
||||
void frameStatsUpdated(const ViewportWindow::FrameStats& stats);
|
||||
// Emitted instead of objectPicked when an Area / Length tool is
|
||||
// active. The host branches on toolMode() and calls
|
||||
// pickMeshLocalAt(x, y, ...) for hit details. Coordinates are in
|
||||
// physical pixels (post-DPR).
|
||||
void surfacePickedInTool(int x, int y, int modifiers);
|
||||
// Emitted whenever the active tool changes (incl. on→off).
|
||||
void toolModeChanged(WgpuViewportWindow::ToolMode mode);
|
||||
void toolModeChanged(ViewportWindow::ToolMode mode);
|
||||
// Backspace/Delete pressed while a tool is active. Length tool's
|
||||
// remove-last-point; other tools may ignore.
|
||||
void toolBackspacePressed();
|
||||
@@ -578,7 +578,7 @@ private:
|
||||
// is overwhelmingly thin-in-one-axis (pipes, columns, slabs,
|
||||
// windows). Sphere projection is kept for contribution / LOD picks
|
||||
// because conservative-over is the right failure mode there.
|
||||
uint32_t cullModelCpuCompute(WgpuModelGpuData& m,
|
||||
uint32_t cullModelCpuCompute(ModelGpuData& m,
|
||||
const float planes[6][4],
|
||||
const float eye[3],
|
||||
const float forward[3],
|
||||
@@ -591,7 +591,7 @@ private:
|
||||
// Upload phase: wgpuQueueWriteBuffer for visible_draws / prefix_sums /
|
||||
// per-model uniform. Main-thread only (wgpu queue ops are not all
|
||||
// thread-safe).
|
||||
void cullModelCpuUpload(WgpuModelGpuData& m);
|
||||
void cullModelCpuUpload(ModelGpuData& m);
|
||||
|
||||
// Compose one instance's `transform` (float[16] column-major) from
|
||||
// FederatedFalseOrigin · ModelTransformation · CoordinateOperation
|
||||
@@ -601,7 +601,7 @@ private:
|
||||
// get cancelled by the federation false origin before precision is
|
||||
// narrowed. Mirrors GL ViewportWindow::composeInstanceFromPlacement.
|
||||
void composeInstanceFromPlacement(InstanceCpu& inst,
|
||||
const WgpuModelGpuData& m) const;
|
||||
const ModelGpuData& m) const;
|
||||
|
||||
// Walk every instance of `model_id`, recompose its transform from the
|
||||
// current federation matrices, refresh per-chunk world AABBs, and
|
||||
@@ -638,7 +638,7 @@ private:
|
||||
// frame bind group because object_ids are globally unique across models.
|
||||
WGPUBuffer selection_flags_buffer_ = nullptr;
|
||||
uint32_t selection_flags_capacity_ = 0; // number of u32 entries
|
||||
WgpuSelectionState selection_;
|
||||
SelectionState selection_;
|
||||
std::vector<uint32_t> selection_flags_scratch_;
|
||||
|
||||
// Per-element visibility. Consulted in cullModelCpuCompute to drop
|
||||
@@ -646,7 +646,7 @@ private:
|
||||
// hidden geometry out of cost on every axis (no draw, no depth, no
|
||||
// pick). Mutated on the main thread between renders; cull workers
|
||||
// read concurrently which is safe as long as no concurrent writes.
|
||||
WgpuVisibilityState visibility_;
|
||||
VisibilityState visibility_;
|
||||
|
||||
// Depth attachment (4× MSAA), recreated on surface resize.
|
||||
WGPUTexture depth_texture_ = nullptr;
|
||||
@@ -711,9 +711,9 @@ private:
|
||||
|
||||
// All viewport overlays (axis indicator, section gizmos, marquee
|
||||
// rect) — pipelines + shaders + buffers + encoders. The viewport
|
||||
// builds a WgpuOverlayFrame each frame and asks the renderer to
|
||||
// encode each overlay; see WgpuOverlayRenderer.h.
|
||||
WgpuOverlayRenderer overlays_;
|
||||
// builds a OverlayFrame each frame and asks the renderer to
|
||||
// encode each overlay; see OverlayRenderer.h.
|
||||
OverlayRenderer overlays_;
|
||||
|
||||
// Active measurement tool. setToolMode() / setSelection mutations
|
||||
// both funnel into updateVolumeReadout() which pushes the HUD +
|
||||
@@ -724,18 +724,18 @@ private:
|
||||
// after entering Volume mode this primes the overlay.
|
||||
void updateVolumeReadout();
|
||||
|
||||
// Area tool state lives in WgpuAreaMeasurement (header below). The
|
||||
// Area tool state lives in AreaMeasurement (header below). The
|
||||
// viewport owns it for the session and routes LMB picks in Area
|
||||
// mode through onAreaPick.
|
||||
std::unique_ptr<class WgpuAreaMeasurement> area_tool_;
|
||||
std::unique_ptr<class AreaMeasurement> area_tool_;
|
||||
void onAreaPick(int x_phys, int y_phys, bool alt);
|
||||
void updateAreaHud();
|
||||
|
||||
// Length tool state lives in WgpuLengthMeasurement. Same lifecycle
|
||||
// Length tool state lives in LengthMeasurement. Same lifecycle
|
||||
// pattern: lazily constructed on first L press, cleared on tool
|
||||
// exit, click handler routes LMB through onLengthPick + Backspace
|
||||
// through onLengthBackspace.
|
||||
std::unique_ptr<class WgpuLengthMeasurement> length_tool_;
|
||||
std::unique_ptr<class LengthMeasurement> length_tool_;
|
||||
void onLengthPick(int x_phys, int y_phys, bool alt);
|
||||
void onLengthBackspace();
|
||||
|
||||
@@ -759,10 +759,10 @@ private:
|
||||
int pick_w_ = 0;
|
||||
int pick_h_ = 0;
|
||||
|
||||
// Section-cutting state. WgpuSectionPlane lives in WgpuOverlayRenderer.h
|
||||
// Section-cutting state. SectionPlane lives in OverlayRenderer.h
|
||||
// because the visualiser reads it; the viewport owns the authoritative
|
||||
// vector that the section tool mutates.
|
||||
std::vector<WgpuSectionPlane> section_planes_;
|
||||
std::vector<SectionPlane> section_planes_;
|
||||
bool section_tool_active_ = false;
|
||||
|
||||
// Marquee box-select. Armed on LMB press (when no other tool consumes
|
||||
@@ -934,14 +934,14 @@ public:
|
||||
// ceiling that one-WGPUBuffer-per-chunk would otherwise hit. All
|
||||
// chunk allocations land here; nothing else uses the pool. Replaces
|
||||
// the old hand-picked streaming_vram_budget_bytes_ knob entirely.
|
||||
WgpuBufferPool pool_;
|
||||
BufferPool pool_;
|
||||
|
||||
// Background worker that does scatter-gather chunk reads off the
|
||||
// render thread. driveStreamingLoads enqueues requests for visible
|
||||
// non-resident chunks and drains completed results into the pool
|
||||
// on subsequent frames. Kills the 100-300 ms per-frame stutters
|
||||
// that synchronous disk reads caused during orbit.
|
||||
WgpuStreamingThread streaming_thread_;
|
||||
StreamingThread streaming_thread_;
|
||||
|
||||
// Per-frame streaming activity, written by driveStreamingLoads,
|
||||
// consumed by the benchmark harness to delay the orbit sweep until
|
||||
@@ -976,7 +976,7 @@ private:
|
||||
float lod1_pixel_threshold_ = 30.0f;
|
||||
|
||||
// Per-model state, keyed by viewport-assigned model_id.
|
||||
std::unordered_map<uint32_t, WgpuModelGpuData> models_gpu_;
|
||||
std::unordered_map<uint32_t, ModelGpuData> models_gpu_;
|
||||
uint32_t next_model_id_ = 1;
|
||||
// Globally-unique object_id allocator. Each applyCachedModel rebases
|
||||
// the sidecar's local object_ids by base_object_id_so_far so picks
|
||||
@@ -32,7 +32,7 @@
|
||||
// from multiple cull worker threads are safe as long as no mutations
|
||||
// happen during render — which is the case here (input handlers
|
||||
// requestUpdate after mutating, render then reads).
|
||||
class WgpuVisibilityState {
|
||||
class VisibilityState {
|
||||
public:
|
||||
bool isHidden(uint32_t object_id) const {
|
||||
return hidden_ids_.count(object_id) > 0;
|
||||
@@ -52,6 +52,11 @@ add_ifcviewer_unit_test(test_sidecar_cache
|
||||
|
||||
add_ifcviewer_unit_test(test_instanced_geometry)
|
||||
|
||||
# Header-only state-machine tests for the renderer subsystems (selection,
|
||||
# visibility). Subjects are inline in their .h files, so no SOURCES needed.
|
||||
add_ifcviewer_unit_test(test_selection)
|
||||
add_ifcviewer_unit_test(test_visibility)
|
||||
|
||||
# Federation is Qt-derived (QObject + signals). It has to pull Qt6 in
|
||||
# directly and enable AUTOMOC for the Q_OBJECT moc-generation.
|
||||
find_package(Qt${QT_VERSION} COMPONENTS Core Gui Test REQUIRED PATHS ${QT_DIR})
|
||||
|
||||
+20
-20
@@ -17,19 +17,19 @@
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
// Tier-1 coverage of WgpuSelectionState — the CPU-side selection set + active
|
||||
// Tier-1 coverage of SelectionState — the CPU-side selection set + active
|
||||
// id used by the wgpu viewport. The class is pure stdlib (no Qt, no QObject),
|
||||
// so the test exercises the state machine directly. The GPU-side flags SSBO
|
||||
// is filled via fillFlagsArray; that pure-function path is also covered.
|
||||
|
||||
#include "WgpuSelectionState.h"
|
||||
#include "SelectionState.h"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("WgpuSelectionState starts empty with no active id", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
TEST_CASE("SelectionState starts empty with no active id", "[wgpu-selection]") {
|
||||
SelectionState sel;
|
||||
REQUIRE(sel.count() == 0);
|
||||
REQUIRE(sel.activeId() == 0);
|
||||
REQUIRE_FALSE(sel.contains(1));
|
||||
@@ -38,7 +38,7 @@ TEST_CASE("WgpuSelectionState starts empty with no active id", "[wgpu-selection]
|
||||
|
||||
TEST_CASE("replace(id) selects a single id and makes it active",
|
||||
"[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
|
||||
sel.replace(5);
|
||||
REQUIRE(sel.count() == 1);
|
||||
@@ -48,7 +48,7 @@ TEST_CASE("replace(id) selects a single id and makes it active",
|
||||
}
|
||||
|
||||
TEST_CASE("replace(0) clears the selection", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.replace(5);
|
||||
sel.markClean();
|
||||
|
||||
@@ -59,7 +59,7 @@ TEST_CASE("replace(0) clears the selection", "[wgpu-selection]") {
|
||||
}
|
||||
|
||||
TEST_CASE("add(id) appends to the set and steals active", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.replace(1);
|
||||
sel.markClean();
|
||||
|
||||
@@ -75,7 +75,7 @@ TEST_CASE("add(id) appends to the set and steals active", "[wgpu-selection]") {
|
||||
}
|
||||
|
||||
TEST_CASE("add(0) is ignored", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.replace(1);
|
||||
sel.markClean();
|
||||
|
||||
@@ -86,7 +86,7 @@ TEST_CASE("add(0) is ignored", "[wgpu-selection]") {
|
||||
}
|
||||
|
||||
TEST_CASE("remove(non-active) keeps active", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.replace(1);
|
||||
sel.add(2);
|
||||
sel.add(3);
|
||||
@@ -101,7 +101,7 @@ TEST_CASE("remove(non-active) keeps active", "[wgpu-selection]") {
|
||||
}
|
||||
|
||||
TEST_CASE("remove(active) falls back to some remaining id", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.replace(1);
|
||||
sel.add(2);
|
||||
sel.add(3);
|
||||
@@ -119,7 +119,7 @@ TEST_CASE("remove(active) falls back to some remaining id", "[wgpu-selection]")
|
||||
}
|
||||
|
||||
TEST_CASE("remove(last id) clears active", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.replace(7);
|
||||
REQUIRE(sel.activeId() == 7);
|
||||
|
||||
@@ -130,7 +130,7 @@ TEST_CASE("remove(last id) clears active", "[wgpu-selection]") {
|
||||
|
||||
TEST_CASE("remove(non-existent) is a no-op for state, no dirty flag",
|
||||
"[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.replace(1);
|
||||
sel.markClean();
|
||||
|
||||
@@ -141,7 +141,7 @@ TEST_CASE("remove(non-existent) is a no-op for state, no dirty flag",
|
||||
}
|
||||
|
||||
TEST_CASE("remove(0) is ignored", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.replace(1);
|
||||
sel.markClean();
|
||||
|
||||
@@ -151,7 +151,7 @@ TEST_CASE("remove(0) is ignored", "[wgpu-selection]") {
|
||||
}
|
||||
|
||||
TEST_CASE("toggle adds when absent, removes when present", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
|
||||
sel.toggle(5);
|
||||
REQUIRE(sel.contains(5));
|
||||
@@ -171,7 +171,7 @@ TEST_CASE("toggle adds when absent, removes when present", "[wgpu-selection]") {
|
||||
}
|
||||
|
||||
TEST_CASE("toggle(0) is ignored", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.markClean();
|
||||
|
||||
sel.toggle(0);
|
||||
@@ -180,7 +180,7 @@ TEST_CASE("toggle(0) is ignored", "[wgpu-selection]") {
|
||||
}
|
||||
|
||||
TEST_CASE("clear empties; no-op when already empty", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
|
||||
sel.clear(); // already empty
|
||||
REQUIRE_FALSE(sel.dirty());
|
||||
@@ -194,7 +194,7 @@ TEST_CASE("clear empties; no-op when already empty", "[wgpu-selection]") {
|
||||
}
|
||||
|
||||
TEST_CASE("markClean clears the dirty flag", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.replace(5);
|
||||
REQUIRE(sel.dirty());
|
||||
|
||||
@@ -207,7 +207,7 @@ TEST_CASE("markClean clears the dirty flag", "[wgpu-selection]") {
|
||||
}
|
||||
|
||||
TEST_CASE("selectionIds returns the live set", "[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.add(1);
|
||||
sel.add(2);
|
||||
sel.add(3);
|
||||
@@ -221,7 +221,7 @@ TEST_CASE("selectionIds returns the live set", "[wgpu-selection]") {
|
||||
|
||||
TEST_CASE("fillFlagsArray packs selected/active bits per object_id",
|
||||
"[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.add(1);
|
||||
sel.add(3); // active is now 3
|
||||
|
||||
@@ -241,7 +241,7 @@ TEST_CASE("fillFlagsArray packs selected/active bits per object_id",
|
||||
|
||||
TEST_CASE("fillFlagsArray drops ids past the entries cap",
|
||||
"[wgpu-selection]") {
|
||||
WgpuSelectionState sel;
|
||||
SelectionState sel;
|
||||
sel.add(1);
|
||||
sel.add(100); // active is 100
|
||||
|
||||
+12
-12
@@ -17,18 +17,18 @@
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
// Tier-1 coverage of WgpuVisibilityState — the per-element hidden-id set
|
||||
// Tier-1 coverage of VisibilityState — the per-element hidden-id set
|
||||
// consulted in cull. The class is pure stdlib; this test exercises its
|
||||
// primitives directly. Bulk hide/isolate/show-all semantics live in
|
||||
// WgpuViewportWindow (which composes WgpuVisibilityState + the model
|
||||
// ViewportWindow (which composes VisibilityState + the model
|
||||
// instance lists) and would need an integration test, not a Tier-1 unit.
|
||||
|
||||
#include "WgpuVisibilityState.h"
|
||||
#include "VisibilityState.h"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
TEST_CASE("WgpuVisibilityState starts empty", "[wgpu-visibility]") {
|
||||
WgpuVisibilityState vis;
|
||||
TEST_CASE("VisibilityState starts empty", "[wgpu-visibility]") {
|
||||
VisibilityState vis;
|
||||
REQUIRE(vis.hiddenCount() == 0);
|
||||
REQUIRE_FALSE(vis.isHidden(0)); // 0 is the "no object" sentinel
|
||||
REQUIRE_FALSE(vis.isHidden(1));
|
||||
@@ -37,7 +37,7 @@ TEST_CASE("WgpuVisibilityState starts empty", "[wgpu-visibility]") {
|
||||
|
||||
TEST_CASE("hide(id) records the id; isHidden reflects it",
|
||||
"[wgpu-visibility]") {
|
||||
WgpuVisibilityState vis;
|
||||
VisibilityState vis;
|
||||
|
||||
vis.hide(1);
|
||||
vis.hide(2);
|
||||
@@ -51,7 +51,7 @@ TEST_CASE("hide(id) records the id; isHidden reflects it",
|
||||
}
|
||||
|
||||
TEST_CASE("hide is idempotent", "[wgpu-visibility]") {
|
||||
WgpuVisibilityState vis;
|
||||
VisibilityState vis;
|
||||
|
||||
vis.hide(5);
|
||||
vis.hide(5);
|
||||
@@ -62,7 +62,7 @@ TEST_CASE("hide is idempotent", "[wgpu-visibility]") {
|
||||
}
|
||||
|
||||
TEST_CASE("hide(0) is ignored", "[wgpu-visibility]") {
|
||||
WgpuVisibilityState vis;
|
||||
VisibilityState vis;
|
||||
|
||||
vis.hide(0);
|
||||
REQUIRE(vis.hiddenCount() == 0);
|
||||
@@ -71,7 +71,7 @@ TEST_CASE("hide(0) is ignored", "[wgpu-visibility]") {
|
||||
|
||||
TEST_CASE("show(id) removes a previously hidden id",
|
||||
"[wgpu-visibility]") {
|
||||
WgpuVisibilityState vis;
|
||||
VisibilityState vis;
|
||||
vis.hide(1);
|
||||
vis.hide(2);
|
||||
|
||||
@@ -82,7 +82,7 @@ TEST_CASE("show(id) removes a previously hidden id",
|
||||
}
|
||||
|
||||
TEST_CASE("show(non-hidden) is a no-op", "[wgpu-visibility]") {
|
||||
WgpuVisibilityState vis;
|
||||
VisibilityState vis;
|
||||
vis.hide(1);
|
||||
|
||||
vis.show(99); // never hidden
|
||||
@@ -91,7 +91,7 @@ TEST_CASE("show(non-hidden) is a no-op", "[wgpu-visibility]") {
|
||||
}
|
||||
|
||||
TEST_CASE("clear() drops every hidden id", "[wgpu-visibility]") {
|
||||
WgpuVisibilityState vis;
|
||||
VisibilityState vis;
|
||||
vis.hide(1);
|
||||
vis.hide(2);
|
||||
vis.hide(3);
|
||||
@@ -104,7 +104,7 @@ TEST_CASE("clear() drops every hidden id", "[wgpu-visibility]") {
|
||||
}
|
||||
|
||||
TEST_CASE("hiddenIds returns the live set", "[wgpu-visibility]") {
|
||||
WgpuVisibilityState vis;
|
||||
VisibilityState vis;
|
||||
vis.hide(10);
|
||||
vis.hide(20);
|
||||
vis.hide(30);
|
||||
Reference in New Issue
Block a user