mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
61726e00a4
Stage 6 of the wgpu port. Replaces the one-draw-per-(mesh, instance) loop
with a CPU cull pass that survives one drawIndexed per non-empty mesh
with packed instanceCount.
Adds to WgpuModelGpuData:
- visible_buffer: u32[] storage SSBO, pre-sized to instance_count at
applyCachedModel so the bind group reference never invalidates.
Re-uploaded each frame via wgpuQueueWriteBuffer.
- mesh_draws: per-mesh schedule (first_instance, instance_count,
first_index, base_vertex, index_count). instance_count==0 means the
mesh contributed nothing this frame and the draw is elided entirely.
cullModelCpu per-frame:
- Extract 6 frustum planes from the same VP we write into the uniform.
WebGPU clip-space z is [0, 1], so near plane = matrix row 2 (not
row 3 + row 2 as in GL); rest of the derivation is standard.
- Per-instance AABB-vs-frustum test using the p-vertex shortcut
(cheapest correct early-out for AABBs).
- Bucket survivors by mesh_id; flatten into a contiguous u32 list;
upload via wgpuQueueWriteBuffer. Per-mesh slice is [first_instance,
first_instance + instance_count).
WGSL adds @group(1) @binding(3) var<storage, read> visible: array<u32>
and an extra indirection: instance_idx = visible[iid]; the rest of the
shader is unchanged. firstInstance on each drawIndexed offsets into
visible[], so each mesh reads its own slice.
Verified two ways:
1. basic.ifc (3 instances, all on-screen) renders pixel-identically
to pre-stage-6 — proves cull keeps everything it should.
2. basic.ifc + a synthetic instance placed at (100, 100, 100) is
culled cleanly: only the cube renders, the far quad is rejected
by the frustum test. Proves cull actually rejects out-of-frustum
geometry rather than passing everything through.
Contribution culling, HiZ, and LOD selection arrive in stages 7 and 8;
they all hook into the same cullModelCpu seam.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
170 lines
7.1 KiB
C++
170 lines
7.1 KiB
C++
/********************************************************************************
|
|
* *
|
|
* This file is part of IfcOpenShell. *
|
|
* *
|
|
* IfcOpenShell is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the Lesser GNU General Public License as published by *
|
|
* the Free Software Foundation, either version 3.0 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* IfcOpenShell is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* Lesser GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the Lesser GNU General Public License *
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
|
* *
|
|
********************************************************************************/
|
|
|
|
#ifndef WGPUVIEWPORTWINDOW_H
|
|
#define WGPUVIEWPORTWINDOW_H
|
|
|
|
#include <QWindow>
|
|
#include <QColor>
|
|
#include <QString>
|
|
|
|
#include <webgpu/webgpu.h>
|
|
|
|
#include <cstdint>
|
|
#include <deque>
|
|
#include <unordered_map>
|
|
|
|
#include "SidecarCache.h"
|
|
#include "WgpuModelGpuData.h"
|
|
|
|
// Stage-2 wgpu viewport: opens a native QWindow, brings up a wgpu instance/
|
|
// adapter/device, configures a surface against the platform-native window
|
|
// handle, and clears to background_color_ on every UpdateRequest. Models
|
|
// loaded from `.ifcview` sidecars are uploaded as wgpu buffers (no draw
|
|
// path yet — that's stage 3).
|
|
//
|
|
// 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 {
|
|
Q_OBJECT
|
|
public:
|
|
explicit WgpuViewportWindow(QWindow* parent = nullptr);
|
|
~WgpuViewportWindow();
|
|
|
|
void setBackgroundColor(const QColor& color);
|
|
|
|
// Queue a sidecar path to be loaded after wgpu init completes. Safe to
|
|
// call before the window is exposed. The path is resolved against the
|
|
// working directory and read via SidecarCache::readSidecar (which
|
|
// normalises stem → .ifcview).
|
|
void queueLoadSidecar(const QString& path);
|
|
|
|
// Synchronous load + GPU upload. Requires wgpu init to have completed
|
|
// (i.e. the window has been exposed at least once). Returns the
|
|
// assigned model_id, or 0 on failure.
|
|
uint32_t loadSidecar(const QString& path);
|
|
|
|
// Restore a finalised model from a SidecarData struct: allocate wgpu
|
|
// buffers, upload vertex/index/mesh/instance bytes, register in
|
|
// models_gpu_. Replaces any existing state for model_id.
|
|
void applyCachedModel(uint32_t model_id, SidecarData data);
|
|
|
|
void removeModel(uint32_t model_id);
|
|
void resetScene();
|
|
|
|
size_t modelCount() const { return models_gpu_.size(); }
|
|
|
|
// Frame the union of all loaded models' world AABBs. No-op on empty
|
|
// scenes. Called automatically after the first model loads; clients
|
|
// can re-invoke to re-frame.
|
|
void viewAll();
|
|
|
|
// 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
|
|
// PNG is written. Use this for headless verification and pixel-diff
|
|
// parity testing against the GL backend.
|
|
void captureNextFrameToPng(const QString& path, bool quit_after = true);
|
|
|
|
protected:
|
|
void exposeEvent(QExposeEvent* event) override;
|
|
void resizeEvent(QResizeEvent* event) override;
|
|
bool event(QEvent* event) override;
|
|
|
|
private:
|
|
bool initWgpu();
|
|
bool createSurface();
|
|
void configureSurface(int width_px, int height_px);
|
|
void render();
|
|
void shutdown();
|
|
|
|
bool buildPipelines();
|
|
void buildModelBindGroup(WgpuModelGpuData& m);
|
|
void ensureDepthTexture(int w, int h);
|
|
void releaseDepthTexture();
|
|
void updateFrameUniforms();
|
|
void flushPendingSidecarQueue();
|
|
bool computeSceneAabb(float mn[3], float mx[3]) const;
|
|
|
|
// Cull `m`'s instances against the supplied frustum planes (world-space,
|
|
// ax+by+cz+d >= 0 means inside), bucket survivors by mesh_id, and write
|
|
// the flat visible-index list into m.visible_buffer via wgpuQueueWriteBuffer.
|
|
// After return, m.mesh_draws is the per-mesh draw schedule for the frame.
|
|
void cullModelCpu(WgpuModelGpuData& m, const float planes[6][4]);
|
|
|
|
bool wgpu_initialized_ = false;
|
|
bool surface_configured_ = false;
|
|
int configured_w_ = 0;
|
|
int configured_h_ = 0;
|
|
|
|
WGPUInstance instance_ = nullptr;
|
|
WGPUAdapter adapter_ = nullptr;
|
|
WGPUDevice device_ = nullptr;
|
|
WGPUQueue queue_ = nullptr;
|
|
WGPUSurface surface_ = nullptr;
|
|
WGPUTextureFormat surface_format_ = WGPUTextureFormat_Undefined;
|
|
|
|
// Render pipeline + bind group layouts (built once after init).
|
|
WGPUShaderModule main_shader_module_ = nullptr;
|
|
WGPUBindGroupLayout frame_bgl_ = nullptr; // group 0
|
|
WGPUBindGroupLayout model_bgl_ = nullptr; // group 1
|
|
WGPUPipelineLayout pipeline_layout_ = nullptr;
|
|
WGPURenderPipeline main_pipeline_ = nullptr;
|
|
|
|
// Per-frame uniform (view-proj + lighting), bound at group 0.
|
|
WGPUBuffer frame_uniform_buffer_ = nullptr;
|
|
WGPUBindGroup frame_bind_group_ = nullptr;
|
|
|
|
// Depth attachment, recreated on surface resize.
|
|
WGPUTexture depth_texture_ = nullptr;
|
|
WGPUTextureView depth_view_ = nullptr;
|
|
int depth_w_ = 0;
|
|
int depth_h_ = 0;
|
|
|
|
QColor background_color_ = QColor("#202329");
|
|
|
|
// Camera (orbit, right-handed Y-up world → wait, BIM is +Z up).
|
|
// 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;
|
|
float camera_yaw_deg_ = 45.0f;
|
|
float camera_pitch_deg_ = 30.0f;
|
|
float camera_fov_y_deg_ = 45.0f;
|
|
float camera_near_ = 0.1f;
|
|
float camera_far_ = 10000.0f;
|
|
|
|
// Per-model state, keyed by viewport-assigned model_id.
|
|
std::unordered_map<uint32_t, WgpuModelGpuData> models_gpu_;
|
|
uint32_t next_model_id_ = 1;
|
|
|
|
// Sidecar paths queued before init completes.
|
|
std::deque<QString> pending_sidecars_;
|
|
|
|
// Set after the first model load triggers a viewAll(); prevents
|
|
// subsequent loads from snapping the camera away from where the
|
|
// user pointed it.
|
|
bool initial_view_applied_ = false;
|
|
|
|
// Pending one-shot screenshot, captured at the end of the next render().
|
|
QString pending_screenshot_path_;
|
|
bool pending_screenshot_quit_ = false;
|
|
};
|
|
|
|
#endif // WGPUVIEWPORTWINDOW_H
|