mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 17:58:20 +00:00
wgpu backend: --benchmark N parity with the GL minimal
Stage 11 of the wgpu port. WgpuViewportWindow gains setBenchmarkFrames(N); the minimal driver wires it to a --benchmark N flag. Renders N frames after a 5-frame warmup, yaw-sweeping the camera at 0.5°/frame, captures per-frame wall time with QElapsedTimer (cull + encode + present), and prints avg/median/p1/p99 + last-frame stats in the same line format as IfcViewerMinimal so a script can diff them line for line. Per-frame stats (visible_objects, visible_triangles, sub_draws) are now summed in render() from m.mesh_draws. hiz_rej reports 0 until stage 7 adds HiZ occlusion. Verified on basic.ifc (3 instances): wgpu 11.68 ms avg vs GL 11.75 ms avg — same scene, same camera sweep, same window size. Noise-level delta as expected on a tiny scene; the interesting comparison is on real BIM corpora once you bake them to v13 sidecars. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,8 @@ int main(int argc, char* argv[]) {
|
||||
"[files...]");
|
||||
parser.addOption({{"s", "screenshot"},
|
||||
"Render one frame, save to PATH as PNG, exit.", "path"});
|
||||
parser.addOption({{"b", "benchmark"},
|
||||
"Render N frames (yaw-sweeping the camera), print stats, exit.", "frames"});
|
||||
parser.process(app);
|
||||
|
||||
auto* viewport = new WgpuViewportWindow;
|
||||
@@ -67,6 +69,9 @@ int main(int argc, char* argv[]) {
|
||||
viewport->captureNextFrameToPng(parser.value("screenshot"),
|
||||
/*quit_after=*/true);
|
||||
}
|
||||
if (parser.isSet("benchmark")) {
|
||||
viewport->setBenchmarkFrames(parser.value("benchmark").toInt());
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QResizeEvent>
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFileInfo>
|
||||
#include <QMatrix4x4>
|
||||
#include <QVector3D>
|
||||
@@ -765,6 +766,15 @@ static bool aabbInFrustum(const float mn[3], const float mx[3],
|
||||
return true;
|
||||
}
|
||||
|
||||
void WgpuViewportWindow::setBenchmarkFrames(int frames) {
|
||||
bench_total_ = std::max(0, frames);
|
||||
bench_count_ = 0;
|
||||
bench_yaw_start_ = camera_yaw_deg_;
|
||||
bench_frame_ms_.clear();
|
||||
bench_frame_ms_.reserve(size_t(bench_total_));
|
||||
if (isExposed() && bench_total_ > 0) requestUpdate();
|
||||
}
|
||||
|
||||
void WgpuViewportWindow::cullModelCpu(WgpuModelGpuData& m, const float planes[6][4]) {
|
||||
if (m.instances.empty() || m.meshes.empty() || !m.visible_buffer) {
|
||||
for (auto& d : m.mesh_draws) d.instance_count = 0;
|
||||
@@ -813,6 +823,11 @@ void WgpuViewportWindow::cullModelCpu(WgpuModelGpuData& m, const float planes[6]
|
||||
}
|
||||
|
||||
void WgpuViewportWindow::render() {
|
||||
// Time the whole render() body (cull + encode + present) for the
|
||||
// benchmark stats. Started before any wgpu work so cull is included.
|
||||
QElapsedTimer frame_timer;
|
||||
if (bench_total_ > 0) frame_timer.start();
|
||||
|
||||
WGPUSurfaceTexture surf_tex = {};
|
||||
wgpuSurfaceGetCurrentTexture(surface_, &surf_tex);
|
||||
|
||||
@@ -844,6 +859,9 @@ void WgpuViewportWindow::render() {
|
||||
// cull writes its results directly into each model's visible_buffer via
|
||||
// wgpuQueueWriteBuffer — these writes are sequenced before the draw
|
||||
// commands we encode next.
|
||||
last_visible_objects_ = 0;
|
||||
last_visible_triangles_ = 0;
|
||||
last_sub_draws_ = 0;
|
||||
{
|
||||
const QVector3D target(camera_target_[0], camera_target_[1], camera_target_[2]);
|
||||
const QVector3D eye = orbitEye(camera_target_, camera_distance_,
|
||||
@@ -859,6 +877,12 @@ void WgpuViewportWindow::render() {
|
||||
for (auto& [mid, m] : models_gpu_) {
|
||||
if (m.hidden) continue;
|
||||
cullModelCpu(m, planes);
|
||||
for (const auto& d : m.mesh_draws) {
|
||||
if (d.instance_count == 0 || d.index_count == 0) continue;
|
||||
last_visible_objects_ += d.instance_count;
|
||||
last_visible_triangles_ += (d.index_count / 3u) * d.instance_count;
|
||||
last_sub_draws_ += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1036,6 +1060,61 @@ void WgpuViewportWindow::render() {
|
||||
|
||||
wgpuSurfacePresent(surface_);
|
||||
wgpuTextureRelease(surf_tex.texture);
|
||||
|
||||
// ---- Benchmark integration + auto-quit -------------------------------
|
||||
if (bench_total_ > 0) {
|
||||
const float ms = float(frame_timer.nsecsElapsed()) / 1e6f;
|
||||
|
||||
// Warm-up frames are dropped from the sample. The yaw advance starts
|
||||
// immediately so the warmup frames already exercise different views.
|
||||
if (bench_count_ >= bench_warmup_) {
|
||||
bench_frame_ms_.push_back(ms);
|
||||
}
|
||||
camera_yaw_deg_ = bench_yaw_start_
|
||||
+ bench_yaw_speed_ * float(bench_count_ + 1);
|
||||
++bench_count_;
|
||||
|
||||
if (bench_count_ >= bench_warmup_ + bench_total_) {
|
||||
// Final frame — assemble stats and emit. Format mirrors the GL
|
||||
// minimal so output is line-diffable across backends.
|
||||
std::vector<float> times = bench_frame_ms_;
|
||||
std::sort(times.begin(), times.end());
|
||||
auto pct = [×](double p) -> float {
|
||||
if (times.empty()) return 0.0f;
|
||||
const size_t idx = std::min(times.size() - 1,
|
||||
size_t(p * double(times.size() - 1)));
|
||||
return times[idx];
|
||||
};
|
||||
float sum = 0.0f;
|
||||
for (float f : times) sum += f;
|
||||
const float avg = times.empty() ? 0.0f : sum / float(times.size());
|
||||
const float median = pct(0.5);
|
||||
const float p1 = pct(0.01);
|
||||
const float p99 = pct(0.99);
|
||||
|
||||
const float total_sweep = bench_yaw_speed_ * float(bench_total_);
|
||||
qInfo().noquote().nospace()
|
||||
<< "\n=== BENCHMARK (" << bench_total_ << " frames, orbit "
|
||||
<< total_sweep << "° at " << bench_yaw_speed_ << "°/frame) ===";
|
||||
qInfo().noquote().nospace()
|
||||
<< " avg: " << avg << " ms (" << (avg > 0 ? 1000.0f/avg : 0.0f) << " fps)";
|
||||
qInfo().noquote().nospace()
|
||||
<< " median: " << median << " ms (" << (median > 0 ? 1000.0f/median : 0.0f) << " fps)";
|
||||
qInfo().noquote().nospace()
|
||||
<< " p1: " << p1 << " ms p99: " << p99 << " ms";
|
||||
qInfo().noquote().nospace()
|
||||
<< " last frame: obj " << last_visible_objects_
|
||||
<< " tri " << last_visible_triangles_
|
||||
<< " sub_draws " << last_sub_draws_
|
||||
<< " hiz_rej 0"; // HiZ lands in stage 7
|
||||
qInfo().noquote() << "=== END BENCHMARK ===\n";
|
||||
|
||||
bench_total_ = 0;
|
||||
QCoreApplication::quit();
|
||||
} else {
|
||||
requestUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -83,6 +83,12 @@ public:
|
||||
// parity testing against the GL backend.
|
||||
void captureNextFrameToPng(const QString& path, bool quit_after = true);
|
||||
|
||||
// Benchmark mode: render N timed frames (after a small warmup), yaw-
|
||||
// sweeping the camera at 0.5°/frame, then print a stats block on
|
||||
// stderr and QCoreApplication::quit(). Mirrors the GL minimal's
|
||||
// --benchmark output format so a script can diff them line for line.
|
||||
void setBenchmarkFrames(int frames);
|
||||
|
||||
protected:
|
||||
void exposeEvent(QExposeEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
@@ -176,6 +182,23 @@ private:
|
||||
// safe to dedicate to orbit for now.
|
||||
Qt::MouseButton nav_active_button_ = Qt::NoButton;
|
||||
QPoint nav_last_pos_;
|
||||
|
||||
// Benchmark mode. setBenchmarkFrames(N) arms it; render() integrates the
|
||||
// yaw, captures per-frame ms after warmup, and prints + quits when the
|
||||
// target frame count is hit.
|
||||
int bench_total_ = 0;
|
||||
int bench_count_ = 0;
|
||||
int bench_warmup_ = 5;
|
||||
float bench_yaw_start_ = 0.0f;
|
||||
float bench_yaw_speed_ = 0.5f; // degrees per frame
|
||||
std::vector<float> bench_frame_ms_;
|
||||
|
||||
// Per-frame stat snapshot from the last cull. Sum of m.mesh_draws across
|
||||
// visible models. Exposed via the benchmark summary; will grow into a
|
||||
// proper FrameStats signal when stage 11's host integration arrives.
|
||||
uint32_t last_visible_objects_ = 0;
|
||||
uint32_t last_visible_triangles_ = 0;
|
||||
uint32_t last_sub_draws_ = 0;
|
||||
};
|
||||
|
||||
#endif // WGPUVIEWPORTWINDOW_H
|
||||
|
||||
Reference in New Issue
Block a user