mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 14:23:53 +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...]");
|
"[files...]");
|
||||||
parser.addOption({{"s", "screenshot"},
|
parser.addOption({{"s", "screenshot"},
|
||||||
"Render one frame, save to PATH as PNG, exit.", "path"});
|
"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);
|
parser.process(app);
|
||||||
|
|
||||||
auto* viewport = new WgpuViewportWindow;
|
auto* viewport = new WgpuViewportWindow;
|
||||||
@@ -67,6 +69,9 @@ int main(int argc, char* argv[]) {
|
|||||||
viewport->captureNextFrameToPng(parser.value("screenshot"),
|
viewport->captureNextFrameToPng(parser.value("screenshot"),
|
||||||
/*quit_after=*/true);
|
/*quit_after=*/true);
|
||||||
}
|
}
|
||||||
|
if (parser.isSet("benchmark")) {
|
||||||
|
viewport->setBenchmarkFrames(parser.value("benchmark").toInt());
|
||||||
|
}
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QMatrix4x4>
|
#include <QMatrix4x4>
|
||||||
#include <QVector3D>
|
#include <QVector3D>
|
||||||
@@ -765,6 +766,15 @@ static bool aabbInFrustum(const float mn[3], const float mx[3],
|
|||||||
return true;
|
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]) {
|
void WgpuViewportWindow::cullModelCpu(WgpuModelGpuData& m, const float planes[6][4]) {
|
||||||
if (m.instances.empty() || m.meshes.empty() || !m.visible_buffer) {
|
if (m.instances.empty() || m.meshes.empty() || !m.visible_buffer) {
|
||||||
for (auto& d : m.mesh_draws) d.instance_count = 0;
|
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() {
|
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 = {};
|
WGPUSurfaceTexture surf_tex = {};
|
||||||
wgpuSurfaceGetCurrentTexture(surface_, &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
|
// cull writes its results directly into each model's visible_buffer via
|
||||||
// wgpuQueueWriteBuffer — these writes are sequenced before the draw
|
// wgpuQueueWriteBuffer — these writes are sequenced before the draw
|
||||||
// commands we encode next.
|
// 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 target(camera_target_[0], camera_target_[1], camera_target_[2]);
|
||||||
const QVector3D eye = orbitEye(camera_target_, camera_distance_,
|
const QVector3D eye = orbitEye(camera_target_, camera_distance_,
|
||||||
@@ -859,6 +877,12 @@ void WgpuViewportWindow::render() {
|
|||||||
for (auto& [mid, m] : models_gpu_) {
|
for (auto& [mid, m] : models_gpu_) {
|
||||||
if (m.hidden) continue;
|
if (m.hidden) continue;
|
||||||
cullModelCpu(m, planes);
|
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_);
|
wgpuSurfacePresent(surface_);
|
||||||
wgpuTextureRelease(surf_tex.texture);
|
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.
|
// parity testing against the GL backend.
|
||||||
void captureNextFrameToPng(const QString& path, bool quit_after = true);
|
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:
|
protected:
|
||||||
void exposeEvent(QExposeEvent* event) override;
|
void exposeEvent(QExposeEvent* event) override;
|
||||||
void resizeEvent(QResizeEvent* event) override;
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
@@ -176,6 +182,23 @@ private:
|
|||||||
// safe to dedicate to orbit for now.
|
// safe to dedicate to orbit for now.
|
||||||
Qt::MouseButton nav_active_button_ = Qt::NoButton;
|
Qt::MouseButton nav_active_button_ = Qt::NoButton;
|
||||||
QPoint nav_last_pos_;
|
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
|
#endif // WGPUVIEWPORTWINDOW_H
|
||||||
|
|||||||
Reference in New Issue
Block a user