mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-16 21:42:19 +00:00
ifcviewer (GL minimal): --screenshot for parity diff with wgpu
Closes the other half of task #10. The wgpu minimal already wrote PNGs via wgpuCommandEncoderCopyTextureToBuffer + mapAsync; the GL backend now has the equivalent via glReadPixels on the back buffer just before swapBuffers. - ViewportWindow::captureNextFrameToPng(path, quit_after=true) queues a one-shot capture. render() reads the default framebuffer at full pixel size (width * devicePixelRatio), flips bottom-up → top-down into a QImage::Format_RGBA8888, saves PNG, and optionally QCoreApplication::quit. Synchronous glReadPixels is fine here — pick is interactive and rare; not used per-frame. - ifcviewer-minimal --screenshot PATH wires through MinimalWindow just like --camera / --benchmark. Honoured after all loads complete (applyPendingBenchmark also drains pending_screenshot_). Lets a parity script do: IfcViewerMinimal foo.ifc --camera A,B,C,D,E,F --screenshot gl.png IfcViewerWgpuMinimal foo.ifcview --camera A,B,C,D,E,F --screenshot wgpu.png # then pixel-diff with whatever (ImageMagick, PIL, etc.) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -127,8 +127,13 @@ void MinimalWindow::setPendingBenchmark(int frames) {
|
|||||||
pending_benchmark_ = frames;
|
pending_benchmark_ = frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MinimalWindow::setPendingScreenshot(const QString& path) {
|
||||||
|
pending_screenshot_ = path;
|
||||||
|
}
|
||||||
|
|
||||||
void MinimalWindow::applyPendingBenchmark() {
|
void MinimalWindow::applyPendingBenchmark() {
|
||||||
if (pending_camera_.isEmpty() && pending_benchmark_ <= 0) return;
|
if (pending_camera_.isEmpty() && pending_benchmark_ <= 0
|
||||||
|
&& pending_screenshot_.isEmpty()) return;
|
||||||
|
|
||||||
if (!pending_camera_.isEmpty()) {
|
if (!pending_camera_.isEmpty()) {
|
||||||
QStringList parts = pending_camera_.split(',');
|
QStringList parts = pending_camera_.split(',');
|
||||||
@@ -148,4 +153,9 @@ void MinimalWindow::applyPendingBenchmark() {
|
|||||||
viewport_->setBenchmarkFrames(pending_benchmark_);
|
viewport_->setBenchmarkFrames(pending_benchmark_);
|
||||||
pending_benchmark_ = 0;
|
pending_benchmark_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!pending_screenshot_.isEmpty()) {
|
||||||
|
viewport_->captureNextFrameToPng(pending_screenshot_, /*quit_after=*/true);
|
||||||
|
pending_screenshot_.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ public:
|
|||||||
void addFiles(const QStringList& paths);
|
void addFiles(const QStringList& paths);
|
||||||
void setPendingCamera(const QString& params);
|
void setPendingCamera(const QString& params);
|
||||||
void setPendingBenchmark(int frames);
|
void setPendingBenchmark(int frames);
|
||||||
|
// One-shot framebuffer capture queued for the next render. Forwarded
|
||||||
|
// to ViewportWindow::captureNextFrameToPng; the viewport handles the
|
||||||
|
// glReadPixels + PNG save + optional QCoreApplication::quit.
|
||||||
|
void setPendingScreenshot(const QString& path);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onLoadStarted(uint32_t mid, QString display_name);
|
void onLoadStarted(uint32_t mid, QString display_name);
|
||||||
@@ -55,6 +59,7 @@ private:
|
|||||||
|
|
||||||
QString pending_camera_;
|
QString pending_camera_;
|
||||||
int pending_benchmark_ = 0;
|
int pending_benchmark_ = 0;
|
||||||
|
QString pending_screenshot_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MINIMALWINDOW_H
|
#endif // MINIMALWINDOW_H
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ int main(int argc, char* argv[]) {
|
|||||||
"Set camera: tx,ty,tz,dist,yaw,pitch", "params"});
|
"Set camera: tx,ty,tz,dist,yaw,pitch", "params"});
|
||||||
parser.addOption({{"b", "benchmark"},
|
parser.addOption({{"b", "benchmark"},
|
||||||
"Run N frames then print stats and exit", "frames"});
|
"Run N frames then print stats and exit", "frames"});
|
||||||
|
parser.addOption({{"s", "screenshot"},
|
||||||
|
"Render one frame after load, save to PATH as PNG, exit", "path"});
|
||||||
parser.process(app);
|
parser.process(app);
|
||||||
|
|
||||||
MinimalWindow window;
|
MinimalWindow window;
|
||||||
@@ -60,6 +62,9 @@ int main(int argc, char* argv[]) {
|
|||||||
if (parser.isSet("benchmark")) {
|
if (parser.isSet("benchmark")) {
|
||||||
window.setPendingBenchmark(parser.value("benchmark").toInt());
|
window.setPendingBenchmark(parser.value("benchmark").toInt());
|
||||||
}
|
}
|
||||||
|
if (parser.isSet("screenshot")) {
|
||||||
|
window.setPendingScreenshot(parser.value("screenshot"));
|
||||||
|
}
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <QWheelEvent>
|
#include <QWheelEvent>
|
||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QImage>
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QtMath>
|
#include <QtMath>
|
||||||
@@ -1638,6 +1639,12 @@ void ViewportWindow::setBenchmarkFrames(int n) {
|
|||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewportWindow::captureNextFrameToPng(const QString& path, bool quit_after) {
|
||||||
|
pending_screenshot_path_ = path;
|
||||||
|
pending_screenshot_quit_ = quit_after;
|
||||||
|
requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
QString ViewportWindow::cameraString() const {
|
QString ViewportWindow::cameraString() const {
|
||||||
return QString("%1,%2,%3,%4,%5,%6")
|
return QString("%1,%2,%3,%4,%5,%6")
|
||||||
.arg(camera_target_.x(), 0, 'f', 4)
|
.arg(camera_target_.x(), 0, 'f', 4)
|
||||||
@@ -2865,6 +2872,41 @@ void ViewportWindow::render() {
|
|||||||
buildHizPyramid();
|
buildHizPyramid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- One-shot framebuffer capture (parity-diff harness) -------------
|
||||||
|
// Read back the just-rendered colour buffer and write a PNG. Runs
|
||||||
|
// before swapBuffers so the source is the back buffer (still bound).
|
||||||
|
// glReadPixels here is synchronous and at full surface size — fine
|
||||||
|
// for a one-shot capture, never used per-frame.
|
||||||
|
if (!pending_screenshot_path_.isEmpty() && gl_) {
|
||||||
|
const int w = int(width() * devicePixelRatio());
|
||||||
|
const int h = int(height() * devicePixelRatio());
|
||||||
|
if (w > 0 && h > 0) {
|
||||||
|
std::vector<uint8_t> buf(size_t(w) * size_t(h) * 4);
|
||||||
|
gl_->glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||||
|
gl_->glReadBuffer(GL_BACK);
|
||||||
|
gl_->glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buf.data());
|
||||||
|
// glReadPixels returns bottom-up; QImage is top-down — flip rows.
|
||||||
|
QImage img(w, h, QImage::Format_RGBA8888);
|
||||||
|
for (int y = 0; y < h; ++y) {
|
||||||
|
std::memcpy(img.scanLine(h - 1 - y),
|
||||||
|
buf.data() + size_t(y) * size_t(w) * 4,
|
||||||
|
size_t(w) * 4);
|
||||||
|
}
|
||||||
|
if (img.save(pending_screenshot_path_, "PNG")) {
|
||||||
|
qInfo().noquote() << "[gl] saved screenshot:"
|
||||||
|
<< pending_screenshot_path_
|
||||||
|
<< "(" << w << "x" << h << ")";
|
||||||
|
} else {
|
||||||
|
qWarning().noquote() << "[gl] QImage::save failed for"
|
||||||
|
<< pending_screenshot_path_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const bool quit_after = pending_screenshot_quit_;
|
||||||
|
pending_screenshot_path_.clear();
|
||||||
|
pending_screenshot_quit_ = false;
|
||||||
|
if (quit_after) QCoreApplication::quit();
|
||||||
|
}
|
||||||
|
|
||||||
context_->swapBuffers(this);
|
context_->swapBuffers(this);
|
||||||
|
|
||||||
// Ensure one more frame runs after the last motion frame so the
|
// Ensure one more frame runs after the last motion frame so the
|
||||||
|
|||||||
@@ -399,6 +399,14 @@ public:
|
|||||||
|
|
||||||
void setCamera(float tx, float ty, float tz, float dist, float yaw, float pitch);
|
void setCamera(float tx, float ty, float tz, float dist, float yaw, float pitch);
|
||||||
void setBenchmarkFrames(int n);
|
void setBenchmarkFrames(int n);
|
||||||
|
|
||||||
|
// Queue a one-shot framebuffer capture: at the end of the next render()
|
||||||
|
// (just before swapBuffers), the default framebuffer is read back with
|
||||||
|
// glReadPixels, saved as PNG to `path`, and — if `quit_after` — the
|
||||||
|
// QCoreApplication is asked to quit. Used by parity-diff harnesses to
|
||||||
|
// produce a GL output PNG that's directly comparable to the wgpu
|
||||||
|
// backend's --screenshot.
|
||||||
|
void captureNextFrameToPng(const QString& path, bool quit_after = true);
|
||||||
QString cameraString() const;
|
QString cameraString() const;
|
||||||
|
|
||||||
// Move camera_target_ to the selected set's world-AABB centroid and
|
// Move camera_target_ to the selected set's world-AABB centroid and
|
||||||
@@ -716,6 +724,10 @@ private:
|
|||||||
float benchmark_yaw_speed_ = 0.5f; // degrees per frame
|
float benchmark_yaw_speed_ = 0.5f; // degrees per frame
|
||||||
std::vector<float> benchmark_frame_times_;
|
std::vector<float> benchmark_frame_times_;
|
||||||
|
|
||||||
|
// Queued one-shot framebuffer capture (see captureNextFrameToPng).
|
||||||
|
QString pending_screenshot_path_;
|
||||||
|
bool pending_screenshot_quit_ = false;
|
||||||
|
|
||||||
// Per-frame stats
|
// Per-frame stats
|
||||||
uint32_t visible_triangles_ = 0;
|
uint32_t visible_triangles_ = 0;
|
||||||
uint32_t visible_objects_ = 0;
|
uint32_t visible_objects_ = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user