mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-12 14:33:28 +00:00
wgpu: input parity, fly mode, diagnostic instrumentation, chunk-priority fix
Brings the wgpu viewport's keyboard + mouse into line with GL ViewportWindow
+ Bonsai's MainWindow shortcut table, lands fly-mode, swaps in three
diagnostic env vars, and fixes a chunk-priority bug exposed by the
diagnostics.
Keyboard parity with GL + Bonsai:
P — toggle perspective / ortho projection
X / Shift+X — front / back view (eye on ±X, pitch 0)
Y / Shift+Y — right / left view (eye on ±Y, pitch 0)
Z / Shift+Z — top / bottom view (pitch ±90°)
F — focus camera on currently selected object
Home — frame entire scene
C — print --camera CLI args for current view
H — hide selected
Shift+H — isolate selected (hide everything not in selection)
Alt+H — show all (clear hidden set)
Shift+F — enter fly mode (matches BonsaiViewer)
Escape (fly) — exit fly mode
WASD/QE/Shift — fly movement (when in fly mode)
The previous H/Shift+H/I assignments were wrong vs Bonsai (Shift+H went
to show-all, I to isolate); both are fixed.
Fly mode:
- GL-style absolute m/s base speed (default 5.0), Shift = 5×, scrollwheel
adjusts ×1.25/×0.8 per notch (Blender convention). Scrollwheel does
NOT zoom in fly mode; that interfered with speed when speed was
distance-scaled (it was, briefly; replaced with absolute m/s).
- Mouse-look pins eye: yaw/pitch update first, then target is re-derived
so orbitEye(target, dist, new_yaw, new_pitch) == old eye. Result:
camera rotates in place (FPS) rather than orbiting the pivot.
- dt ceiling clamp at 100ms (matches GL fps_move_speed_) so a stall
doesn't warp the camera.
- Pitch sign matches non-inverted FPS convention (mouse-up = look up).
Mouse-nav presets (WGPU_NAV_PRESET=blender|rhino|revit, default blender):
Blender — Orbit MMB, Pan Shift+MMB
Rhino — Orbit RMB, Pan Shift+RMB
Revit — Orbit Shift+MMB, Pan MMB
LMB stays free for selection in every preset. Nav-drag kind is captured
at press time so a mid-drag Shift release doesn't flip orbit↔pan. The
pan up-vector switches to world-Y at near-vertical pitch so panning
still works in top/bottom view (would otherwise NaN at pitch=±90°).
Camera-math refactor:
buildViewProj(view, proj) centralises perspective↔ortho selection and
the near-vertical up-vector switch. Four open-coded copies of the
view/proj build (cull, debug, streaming priority, render uniforms) now
call it instead, ensuring projection mode + up-vector switch land
identically everywhere. basic.ifc pixel-diff is 0 (refactor confirmed
output-equivalent on the path with no ortho / no near-vertical pitch).
WGPU_PRESENT_MODE=fifo|fifo_relaxed|mailbox|immediate (default fifo):
Diagnostic toggle for stutter analysis. fifo_relaxed gave the tightest
per-frame dt distribution on a federated bench scene; immediate gave
uncapped throughput at the cost of tearing. Mailbox not supported on
Vulkan + NVIDIA Linux but kept as an option for other backends.
WGPU_FLY_DEBUG=1: per-frame [fly] log printing dt, render gap, key
count, speed, position delta. Confirmed render_gap == dt to four
decimal places — fpsIntegrate runs exactly once per render, no
double-tick. Cull cost (~14-20 ms) is the dominant frame variance and
the eventual fix is task #49 (sub-model parallel cull) — fly-mode
stutter on slow scenes is a downstream symptom of cull cost, not a
fly-mode bug.
WGPU_STREAM_DEBUG=1: per-frame [stream-debug] log with cands / enq /
drained / ev_lru / ev_pri / blocked / resident / cycled / max_load.
Confirms thrash / pool-bound / load-budget cases on big scenes.
Pick-and-track diagnostic: clicking an object enumerates every chunk
holding instances of that object (an IFC object can split across
representations / chunks), printing each chunk's AABB + instance AABB +
residency. If any tracked chunk's is_resident flips true → false in
driveStreamingLoads, an EVICTED dump prints with the chunk's AABB,
priority, pool state, this-frame eviction counts, and the top-5
candidates that displaced it. Surfaces exactly why an object disappeared.
chunkScreenAreaPx fix (uses diagnostic to confirm the bug):
A chunk's AABB is the union of every instance's world AABB in the
chunk. On a federated IFC the camera commonly sits INSIDE that AABB
(e.g. inside a 263×30×15 m floor-area bounding box). Previously the
8-corner projection silently dropped corners with clip.w <= 1e-3
(behind near plane), so the projected bbox of the surviving in-front
corners was a tiny fraction of the chunk's true on-screen footprint.
Result: big-AABB chunks lost every eviction fight, visible objects
popped out as the camera tilted. Fix: short-circuit to full-viewport
area when (a) eye is inside the chunk AABB (mirrors GL's
contributionPasses camera-inside short-circuit), or (b) any AABB
corner sits behind the near plane (AABB straddles → 8 corners cannot
honestly measure footprint; conservatively over-prioritise).
The fix is a workaround for the deeper chunking issue — chunks are
mesh-keyed (group of meshes), and a mesh's AABB used in chunking is
the mean of its instances' positions, which is meaningless for
heavily-deduplicated meshes scattered across the scene. The root fix
is task #55 (spatial instance bucketing, runtime prototype) + #56
(sidecar v15 instance-keyed format). chunkScreenAreaPx fix unblocks
the user-visible "missing objects" issue while those land.
Extracted chunkScreenAreaPx from a driveStreamingLoads-local lambda
to a private member so the disappear-diagnostic and any future call
sites can use it consistently.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -22,8 +22,10 @@
|
||||
|
||||
#include <QWindow>
|
||||
#include <QColor>
|
||||
#include <QElapsedTimer>
|
||||
#include <QMatrix4x4>
|
||||
#include <QPoint>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
|
||||
#include <webgpu/webgpu.h>
|
||||
@@ -98,6 +100,50 @@ public:
|
||||
void setCamera(float tx, float ty, float tz,
|
||||
float dist, float yaw_deg, float pitch_deg);
|
||||
|
||||
// GL-parity camera helpers. setStandardView snaps to an axis-aligned
|
||||
// angle without re-framing (used by X/Y/Z keys). focusOnSelectedObject
|
||||
// frames the union AABB of the current selection. toggleProjection
|
||||
// flips perspective <-> orthographic. cameraString formats the current
|
||||
// state for a --camera CLI arg.
|
||||
void setStandardView(float yaw_deg, float pitch_deg);
|
||||
void focusOnSelectedObject();
|
||||
void toggleProjection();
|
||||
QString cameraString() const;
|
||||
|
||||
// FPS / fly mode. enterFpsMode swaps the orbit camera for a WASD/QE
|
||||
// free-fly camera (hotkey: Shift+F). exitFpsMode restores the orbit
|
||||
// pivot and reveals the cursor. Mouse-look uses raw deltas (cursor is
|
||||
// hidden and recentered each frame).
|
||||
void enterFpsMode();
|
||||
void exitFpsMode();
|
||||
bool fpsMode() const { return fps_mode_; }
|
||||
|
||||
private:
|
||||
// Common camera math used by render, cull, streaming, and pick. Produces
|
||||
// the view matrix and a WebGPU-correct projection (z mapped to [0, 1]).
|
||||
// Single helper so projection_ortho_ and the up-vector switch at near-
|
||||
// vertical pitch land identically everywhere.
|
||||
void buildViewProj(QMatrix4x4& view_out, QMatrix4x4& proj_out) const;
|
||||
// Per-frame WASD integration when fps_mode_ is true. Called near the
|
||||
// top of render() so the displayed frame already reflects movement.
|
||||
void fpsIntegrate();
|
||||
// Build the camera AABB for a single object across all loaded models.
|
||||
bool computeObjectAabb(uint32_t object_id,
|
||||
float mn[3], float mx[3]) const;
|
||||
// Re-aim the orbit camera so the bounding sphere of [mn, mx] fits.
|
||||
void frameAabb(const float mn[3], const float mx[3], float padding);
|
||||
// Resolve nav_preset_ env var to orbit/pan bindings.
|
||||
void applyNavPreset(const char* name);
|
||||
|
||||
// Project the chunk's world-space AABB through `vp_mat` and return the
|
||||
// 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,
|
||||
const QMatrix4x4& vp_mat) const;
|
||||
|
||||
public:
|
||||
|
||||
// Queue a one-shot framebuffer capture: the next rendered frame is
|
||||
// copied back to host memory and saved to `path` as PNG. If
|
||||
// `quit_after` is true, QCoreApplication::quit() is called once the
|
||||
@@ -120,6 +166,7 @@ protected:
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void keyReleaseEvent(QKeyEvent* event) override;
|
||||
|
||||
private:
|
||||
bool initWgpu();
|
||||
@@ -378,6 +425,58 @@ private:
|
||||
// Mirrors the GL viewport's defaults; mouse navigation lands later.
|
||||
float camera_target_[3] = { 0.0f, 0.0f, 0.0f };
|
||||
float camera_distance_ = 50.0f;
|
||||
|
||||
// Perspective by default; toggleProjection() (P key) flips this. When
|
||||
// true, the per-frame projection builder uses an orthographic matrix
|
||||
// sized by camera_distance_ × tan(fov/2) so toggling looks like a
|
||||
// smooth swap rather than a jump in apparent size.
|
||||
bool projection_ortho_ = false;
|
||||
|
||||
// Fly / FPS-mode state. Mirrors GL ViewportWindow::CameraMode::Fps.
|
||||
// While fps_mode_ is true: cursor is hidden, mouse-look uses raw
|
||||
// deltas, fps_keys_held_ accumulates pressed W/A/S/D/Q/E/Shift, and
|
||||
// render() integrates a movement step each frame from those keys.
|
||||
// exit via Esc (also any unrelated key click) — recenter the cursor
|
||||
// back at fps_press_center_ so the orbit camera resumes cleanly.
|
||||
bool fps_mode_ = false;
|
||||
QSet<int> fps_keys_held_;
|
||||
QElapsedTimer fps_last_tick_;
|
||||
QPoint fps_press_center_;
|
||||
bool fps_ignore_next_mouse_move_ = false;
|
||||
// Fly base speed in m/s at no-modifier (Shift gives a 5× boost). Default
|
||||
// 5.0 matches GL fps_move_speed_. Scrollwheel in fly mode adjusts this
|
||||
// by ×1.25 / ×0.8 per notch, Blender-style — wheel does NOT zoom while
|
||||
// in fly mode (which would change camera_distance_ underneath us and
|
||||
// make speed jitter if speed were distance-scaled).
|
||||
float fps_move_speed_ = 5.0f;
|
||||
// Per-frame [fly] dt log when WGPU_FLY_DEBUG=1. Diagnoses stutter:
|
||||
// print dt of each fpsIntegrate call and the prior render's elapsed
|
||||
// ms. Off by default (env-gated) so the normal log stays clean.
|
||||
bool fly_debug_ = false;
|
||||
QElapsedTimer fly_render_clock_;
|
||||
|
||||
// Click-and-track diagnostic: when a pick lands, stash the chunk
|
||||
// that holds the picked object. driveStreamingLoads watches for that
|
||||
// chunk's `is_resident` flipping true→false and dumps the priority
|
||||
// / pool stats at the moment of eviction so we can see why it lost.
|
||||
uint32_t tracked_object_id_ = 0;
|
||||
uint32_t tracked_chunk_mid_ = 0;
|
||||
size_t tracked_chunk_idx_ = SIZE_MAX;
|
||||
bool tracked_was_resident_ = false;
|
||||
|
||||
// Mouse-navigation bindings — mirrors GL's NavBindings + currentNavBindings().
|
||||
// Selection stays on LMB for every preset (none of the presets steal it),
|
||||
// so the click-vs-drag distinction at mouseReleaseEvent's pick path keeps
|
||||
// working. Set at init from WGPU_NAV_PRESET=blender|rhino|revit (default
|
||||
// blender, matching GL's AppSettings::NavPreset::Blender default).
|
||||
Qt::MouseButton orbit_button_ = Qt::MiddleButton;
|
||||
Qt::KeyboardModifiers orbit_mods_ = Qt::NoModifier;
|
||||
Qt::MouseButton pan_button_ = Qt::MiddleButton;
|
||||
Qt::KeyboardModifiers pan_mods_ = Qt::ShiftModifier;
|
||||
// Set by mousePressEvent based on which binding matched; consumed by
|
||||
// mouseMoveEvent so mid-drag modifier changes don't switch axes.
|
||||
enum class NavDrag : uint8_t { Inactive, Orbit, Pan };
|
||||
NavDrag nav_drag_kind_ = NavDrag::Inactive;
|
||||
float camera_yaw_deg_ = 45.0f;
|
||||
float camera_pitch_deg_ = 30.0f;
|
||||
float camera_fov_y_deg_ = 45.0f;
|
||||
|
||||
Reference in New Issue
Block a user