ifcviewer: replace qInfo/qWarning with a Qt-free logger seam

Add Log.h (in IfcViewerCore) — a tiny stream-style logger that backs
fprintf(stderr,...), with overloads for the common primitives + char
strings. Mimics qInfo()/qWarning()'s syntax surface enough that
mass-replacing qInfo()→Log::info() and qWarning()→Log::warn() keeps
existing call sites parsing unchanged; .noquote() / .nospace() exist
as compat no-ops so chained qInfo().noquote()<<x<<y patterns survive.

QString streaming is a transitional concern — the QString → std::string
sweep (#80) hasn't landed yet, so ViewportWindow and friends still
construct QStrings for log payloads. LogQt.h (in IfcViewer, not Core)
adds the QString / QStringView operator<< overloads so those streaming
sites work without source changes during the in-flight Qt removal.
When #80 retires QString, LogQt.h drops out.

ViewportWindow.cpp: 132 qInfo/qWarning callsites converted. The two
printf-style qInfo("fmt %s", ...) callsites get fprintf with explicit
[info]/[warn] prefixes to keep the output discoverable.

Also de-Qt'd:
  AreaMeasurement.cpp  — 1 qInfo("fmt", …) → fprintf
  SceneLoader.cpp      — 4 qDebug + 1 qWarning printf-style → fprintf
  GeometryStreamer.cpp — 2 qDebug printf-style → fprintf
  ifcviewer-minimal/main.cpp — 2 qWarning << → Log::warn

Drops <QDebug> from each. Closes #82.

Builds: desktop / bonsai / web all green. Tests 100/100 pass.
This commit is contained in:
Dion Moult
2026-06-05 08:58:45 +10:00
parent 77cf535b45
commit 6dd3558db9
8 changed files with 348 additions and 161 deletions
+6 -3
View File
@@ -23,6 +23,8 @@
#include <QWidget>
#include <QVBoxLayout>
#include "Log.h"
#include "LogQt.h"
#include "ViewportWindow.h"
// Stage-1 driver: opens a single window with the wgpu viewport embedded,
@@ -85,11 +87,12 @@ int main(int argc, char* argv[]) {
if (ok) {
viewport->setCamera(v[0], v[1], v[2], v[3], v[4], v[5]);
} else {
qWarning() << "--camera: failed to parse" << parser.value("camera");
Log::warn() << "--camera: failed to parse "
<< parser.value("camera");
}
} else {
qWarning() << "--camera: expected 6 comma-separated floats, got"
<< parts.size();
Log::warn() << "--camera: expected 6 comma-separated floats, got "
<< parts.size();
}
}
+5 -4
View File
@@ -22,7 +22,7 @@
#include "OverlayRenderer.h"
#include "ViewportWindow.h"
#include <QDebug>
#include <cstdio>
#include <QString>
#include <algorithm>
@@ -280,9 +280,10 @@ void AreaMeasurement::onPick(ViewportWindow& vp,
rebuildHighlightAndLabels(vp);
qInfo("[wgpu area] %s%.6f m^2 (total: %.6f m^2, %zu tris)",
delta >= 0.0 ? "+" : "", delta,
total_area_m2_, selected_.size());
std::fprintf(stderr,
"[info] [wgpu area] %s%.6f m^2 (total: %.6f m^2, %zu tris)\n",
delta >= 0.0 ? "+" : "", delta,
total_area_m2_, selected_.size());
}
void AreaMeasurement::rebuildHighlightAndLabels(ViewportWindow& vp) {
+2
View File
@@ -149,9 +149,11 @@ set(IFCVIEWER_CORE_SOURCES
)
set(IFCVIEWER_CORE_HEADERS
BufferPool.h
CameraMath.h
ChunkPlanner.h
InstanceCompose.h
InstancedGeometry.h
Log.h
LodBuilder.h
ModelGpuData.h
SelectionState.h
+9 -7
View File
@@ -34,7 +34,7 @@
#include <limits>
#include <set>
#include <QDebug>
#include <cstdio>
#include <QElapsedTimer>
struct MaterialInfo {
@@ -453,9 +453,10 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
return;
}
if (!gross_ids.empty()) {
qDebug("Excessive voids: %zu element(s) will be loaded without "
"opening subtractions",
gross_ids.size());
std::fprintf(stderr,
"[info] Excessive voids: %zu element(s) will be loaded without "
"opening subtractions\n",
gross_ids.size());
}
// Shared dedup + AABB state across passes — same geom.id() across
@@ -696,8 +697,9 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
double dedup_ratio = total_meshes > 0
? static_cast<double>(total_shapes) / static_cast<double>(total_meshes) : 1.0;
qDebug("Streamer done: %s %.2fs shapes=%u unique_meshes=%u dedup=%.2fx",
path.c_str(), stream_timer.elapsed() / 1000.0,
total_shapes, total_meshes, dedup_ratio);
std::fprintf(stderr,
"[info] Streamer done: %s %.2fs shapes=%u unique_meshes=%u dedup=%.2fx\n",
path.c_str(), stream_timer.elapsed() / 1000.0,
total_shapes, total_meshes, dedup_ratio);
succeeded_ = !cancel_requested_.load();
}
+106
View File
@@ -0,0 +1,106 @@
/********************************************************************************
* *
* 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 IFCVIEWER_LOG_H
#define IFCVIEWER_LOG_H
// Tiny portable logging shim. Replaces Qt's qInfo()/qWarning()/qDebug()
// stream syntax with a no-Qt equivalent so IfcViewerCore can run under
// Emscripten + so ViewportCore (post-extraction) doesn't link Qt for
// the sake of a few diagnostic prints.
//
// Surface mimics QDebug enough for mass `qInfo()`→`Log::info()` /
// `qWarning()`→`Log::warn()` replacement to compile unchanged:
//
// - operator<< overloads for the common primitives + char strings
// - .noquote() / .nospace() as compat no-ops (Qt formatting controls
// that don't have meaningful equivalents here — Qt added a quoted
// wrapper around QStrings + a space separator between args; we just
// never do those)
// - a temp-destructor newline so call-site syntax stays terse
//
// QString streaming sites have to convert at the boundary (".toUtf8()
// .constData()") because Log.h lives in IfcViewerCore and can't pull in
// Qt. Once the QString → std::string sweep lands the manual conversions
// go away.
//
// Both info() and warn() write to stderr; the only difference is the
// prefix tag. Add a colourised stream sink later if we ever want
// terminal-friendly output.
#include <cstdio>
#include <string>
#include <string_view>
namespace Log {
class Stream {
public:
Stream() = default;
~Stream() { std::fputc('\n', stderr); std::fflush(stderr); }
// Move-only — copies would double the trailing newline.
Stream(const Stream&) = delete;
Stream& operator=(const Stream&) = delete;
Stream(Stream&&) = default;
// Qt-style formatting toggles. Both are no-ops here; declared so
// existing qInfo().noquote().nospace() chains keep parsing.
Stream& noquote() { return *this; }
Stream& nospace() { return *this; }
Stream& quote() { return *this; }
Stream& space() { return *this; }
Stream& operator<<(const char* s) {
if (s) std::fputs(s, stderr);
return *this;
}
Stream& operator<<(const std::string& s) {
std::fwrite(s.data(), 1, s.size(), stderr);
return *this;
}
Stream& operator<<(std::string_view s) {
std::fwrite(s.data(), 1, s.size(), stderr);
return *this;
}
Stream& operator<<(char c) { std::fputc(c, stderr); return *this; }
Stream& operator<<(bool b) { std::fputs(b ? "true" : "false", stderr); return *this; }
Stream& operator<<(int x) { std::fprintf(stderr, "%d", x); return *this; }
Stream& operator<<(unsigned int x) { std::fprintf(stderr, "%u", x); return *this; }
Stream& operator<<(long x) { std::fprintf(stderr, "%ld", x); return *this; }
Stream& operator<<(unsigned long x) { std::fprintf(stderr, "%lu", x); return *this; }
Stream& operator<<(long long x) { std::fprintf(stderr, "%lld", x); return *this; }
Stream& operator<<(unsigned long long x) { std::fprintf(stderr, "%llu", x); return *this; }
Stream& operator<<(float x) { std::fprintf(stderr, "%g", double(x)); return *this; }
Stream& operator<<(double x) { std::fprintf(stderr, "%g", x); return *this; }
Stream& operator<<(const void* p) { std::fprintf(stderr, "%p", p); return *this; }
};
inline Stream info() {
std::fputs("[info] ", stderr);
return Stream{};
}
inline Stream warn() {
std::fputs("[warn] ", stderr);
return Stream{};
}
} // namespace Log
#endif // IFCVIEWER_LOG_H
+67
View File
@@ -0,0 +1,67 @@
/********************************************************************************
* *
* 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 IFCVIEWER_LOG_QT_H
#define IFCVIEWER_LOG_QT_H
// Qt-only adjunct to Log.h. Lets the Qt-using TUs (ViewportWindow,
// OverlayRenderer, Federation, SceneLoader …) keep streaming QString
// values through Log::info()/warn() during the in-flight Qt removal —
// once the QString → std::string sweep (#80) lands these TUs stop
// constructing QStrings in log statements and this header goes away.
//
// Lives in src/ifcviewer/ (NOT in IfcViewerCore), so the web build /
// any future Qt-free ViewportCore TU never includes it and never pulls
// in QString.
#include "Log.h"
#include <QByteArray>
#include <QString>
namespace Log {
// The chain `Log::info() << "tag" << qstr` produces a prvalue Stream
// that's then chained through Stream& returned by the member
// operator<<. So both lvalue-ref and rvalue-ref overloads are needed
// for QString to bind regardless of where it appears in the chain.
inline Stream& operator<<(Stream& s, const QString& qs) {
const QByteArray utf8 = qs.toUtf8();
std::fwrite(utf8.constData(), 1, std::size_t(utf8.size()), stderr);
return s;
}
inline Stream&& operator<<(Stream&& s, const QString& qs) {
s << qs;
return std::move(s);
}
inline Stream& operator<<(Stream& s, QStringView qsv) {
const QByteArray utf8 = qsv.toUtf8();
std::fwrite(utf8.constData(), 1, std::size_t(utf8.size()), stderr);
return s;
}
inline Stream&& operator<<(Stream&& s, QStringView qsv) {
s << qsv;
return std::move(s);
}
} // namespace Log
#endif // IFCVIEWER_LOG_QT_H
+18 -14
View File
@@ -22,7 +22,7 @@
#include <QFileInfo>
#include <QTimer>
#include <QDebug>
#include <cstdio>
#include <QElapsedTimer>
#include <memory>
@@ -184,7 +184,8 @@ void SceneLoader::startNextLoad() {
sidecar_read_thread_ = std::thread([this, ifc_path, mid, is_sidecar_source]() {
QElapsedTimer rt; rt.start();
auto cached = readSidecarMetadataOnly(ifc_path);
qDebug(" Sidecar metadata read: %lld ms (%s)", rt.elapsed(), ifc_path.c_str());
std::fprintf(stderr, "[info] Sidecar metadata read: %lld ms (%s)\n",
(long long)rt.elapsed(), ifc_path.c_str());
auto result = std::make_shared<std::optional<StreamingSidecar>>(std::move(cached));
QMetaObject::invokeMethod(this, [this, mid, result, is_sidecar_source]() {
if (*result && !(*result)->meta.instances.empty()) {
@@ -233,13 +234,14 @@ void SceneLoader::applySidecarData(uint32_t mid, StreamingSidecar metadata) {
auto& model = it->second;
SidecarData& d = metadata.meta;
qDebug("Sidecar hit: %s (%zu metadata bytes, %zu indices, %zu meshes, %zu instances, %zu elements)",
model.file_path.toStdString().c_str(),
size_t(metadata.vertex_total_bytes),
size_t(metadata.index_total_count),
d.meshes.size(),
d.instances.size(),
d.elements.size());
std::fprintf(stderr,
"[info] Sidecar hit: %s (%zu metadata bytes, %zu indices, %zu meshes, %zu instances, %zu elements)\n",
model.file_path.toStdString().c_str(),
size_t(metadata.vertex_total_bytes),
size_t(metadata.index_total_count),
d.meshes.size(),
d.instances.size(),
d.elements.size());
// Rebase object/model IDs onto the current session's ID space. Two
// cached models both starting at object_id=1 would collide otherwise.
@@ -303,11 +305,12 @@ void SceneLoader::startDataSourceLoad(uint32_t mid) {
file = std::make_unique<ifcopenshell::file>(
data_path_std, ifcopenshell::FT_AUTODETECT, /*read_only=*/true);
} catch (const std::exception& e) {
qWarning(" Data source load failed: %s (%s)",
data_path_std.c_str(), e.what());
std::fprintf(stderr, "[warn] Data source load failed: %s (%s)\n",
data_path_std.c_str(), e.what());
return;
}
qDebug(" Data source load: %lld ms (%s)", t.elapsed(), data_path_std.c_str());
std::fprintf(stderr, "[info] Data source load: %lld ms (%s)\n",
(long long)t.elapsed(), data_path_std.c_str());
auto shared = std::make_shared<std::unique_ptr<ifcopenshell::file>>(std::move(file));
QMetaObject::invokeMethod(this, [this, mid, shared]() {
@@ -390,8 +393,9 @@ void SceneLoader::onStreamerFinished() {
QElapsedTimer wt; wt.start();
SidecarData data = m.sidecar_builder->finalize(georef, m.streamed_elements);
const bool ok = writeSidecar(m.file_path.toStdString(), data);
qDebug(" Sidecar finalize + write: %lld ms (%s)",
wt.elapsed(), ok ? "ok" : "FAILED");
std::fprintf(stderr,
"[info] Sidecar finalize + write: %lld ms (%s)\n",
(long long)wt.elapsed(), ok ? "ok" : "FAILED");
m.sidecar_builder.reset();
m.streamed_elements.clear();
m.streamed_elements.shrink_to_fit();
+135 -133
View File
@@ -23,13 +23,14 @@
#include "ChunkPlanner.h"
#include "InstanceCompose.h"
#include "LengthMeasurement.h"
#include "Log.h"
#include "LogQt.h"
#include "StreamingLoader.h"
#include "VertexQuantization.h"
#include <QCoreApplication>
#include <QGuiApplication>
#include <QResizeEvent>
#include <QDebug>
#include <QDir>
#include <QElapsedTimer>
#include <QFile>
@@ -175,11 +176,11 @@ static QString sv(WGPUStringView s) {
static void onWgpuLog(WGPULogLevel level, WGPUStringView message, void* /*userdata*/) {
const QString m = sv(message);
switch (level) {
case WGPULogLevel_Error: qWarning().noquote() << "[wgpu err]" << m; break;
case WGPULogLevel_Warn: qWarning().noquote() << "[wgpu warn]" << m; break;
case WGPULogLevel_Info: qInfo ().noquote() << "[wgpu info]" << m; break;
case WGPULogLevel_Debug: qDebug ().noquote() << "[wgpu dbg]" << m; break;
case WGPULogLevel_Trace: qDebug ().noquote() << "[wgpu trace]" << m; break;
case WGPULogLevel_Error: Log::warn().noquote() << "[wgpu err]" << m; break;
case WGPULogLevel_Warn: Log::warn().noquote() << "[wgpu warn]" << m; break;
case WGPULogLevel_Info: Log::info() << "[wgpu info] " << m; break;
case WGPULogLevel_Debug: Log::info() << "[wgpu dbg] " << m; break;
case WGPULogLevel_Trace: Log::info() << "[wgpu trace] " << m; break;
default: break;
}
}
@@ -187,7 +188,7 @@ static void onWgpuLog(WGPULogLevel level, WGPUStringView message, void* /*userda
static void onUncapturedError(WGPUDevice const* /*device*/,
WGPUErrorType type, WGPUStringView message,
void* /*ud1*/, void* /*ud2*/) {
qWarning().noquote() << "[wgpu device error" << int(type) << "]" << sv(message);
Log::warn().noquote() << "[wgpu device error" << int(type) << "]" << sv(message);
}
// Allocate a wgpu buffer of `size_bytes` with the given usage, and upload
@@ -698,7 +699,7 @@ void ViewportWindow::queueLoadSidecar(const QString& path) {
uint32_t ViewportWindow::loadSidecar(const QString& path) {
if (!wgpu_initialized_) {
qWarning().noquote() << "loadSidecar called before wgpu init:" << path;
Log::warn().noquote() << "loadSidecar called before wgpu init:" << path;
return 0;
}
@@ -719,33 +720,33 @@ uint32_t ViewportWindow::loadSidecar(const QString& path) {
// peeking the header ourselves, so users know which to fix.
QFile f(resolved);
if (!f.exists()) {
qWarning().noquote() << "Sidecar not found:" << resolved;
Log::warn().noquote() << "Sidecar not found:" << resolved;
} else if (!f.open(QIODevice::ReadOnly)) {
qWarning().noquote() << "Sidecar unreadable:" << resolved
Log::warn().noquote() << "Sidecar unreadable:" << resolved
<< "(" << f.errorString() << ")";
} else {
uint32_t header[3] = { 0, 0, 0 };
const qint64 got = f.read(reinterpret_cast<char*>(header), sizeof(header));
if (got < qint64(sizeof(header))) {
qWarning().noquote() << "Sidecar truncated:" << resolved
Log::warn().noquote() << "Sidecar truncated:" << resolved
<< "(only" << got << "bytes — expected ≥ 12)";
} else if (header[0] != SIDECAR_MAGIC) {
qWarning().noquote().nospace()
Log::warn().noquote().nospace()
<< "Sidecar magic mismatch: " << resolved
<< " — got 0x" << QString::number(header[0], 16)
<< ", expected 0x" << QString::number(SIDECAR_MAGIC, 16)
<< " (\"IFVW\")";
} else if (header[1] != SIDECAR_VERSION) {
qWarning().noquote().nospace()
Log::warn().noquote().nospace()
<< "Sidecar schema mismatch: " << resolved
<< " — file is v" << header[1]
<< ", this build expects v" << SIDECAR_VERSION
<< ". Re-bake the .ifc with a viewer at the matching schema.";
} else if (header[2] != SIDECAR_ENDIAN) {
qWarning().noquote() << "Sidecar endianness mismatch:" << resolved
Log::warn().noquote() << "Sidecar endianness mismatch:" << resolved
<< "(cross-platform load not supported)";
} else {
qWarning().noquote() << "Sidecar metadata read failed past the header:" << resolved;
Log::warn().noquote() << "Sidecar metadata read failed past the header:" << resolved;
}
}
return 0;
@@ -759,7 +760,7 @@ uint32_t ViewportWindow::loadSidecar(const QString& path) {
void ViewportWindow::applyCachedModel(uint32_t model_id,
StreamingSidecar metadata) {
if (!device_ || !queue_) {
qWarning() << "applyCachedModel without an initialised device";
Log::warn() << "applyCachedModel without an initialised device";
return;
}
@@ -1056,7 +1057,7 @@ void ViewportWindow::applyCachedModel(uint32_t model_id,
// chunk's load. The per-frame loader (commit 4) will buildModelBindGroup
// after a chunk becomes resident.
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu stream] applyCachedModel mid=" << model_id
<< " verts=" << mref.vertex_bytes << "B (deferred)"
<< " idx=" << mref.index_count
@@ -1174,7 +1175,7 @@ void ViewportWindow::uploadInstanceChunk(const InstanceChunk& chunk) {
void ViewportWindow::finalizeModel(uint32_t model_id) {
auto it = pending_direct_loads_.find(model_id);
if (it == pending_direct_loads_.end()) {
qWarning().nospace()
Log::warn().nospace()
<< "[wgpu direct] finalizeModel(" << model_id
<< ") with no staged data; skipping";
return;
@@ -1186,11 +1187,11 @@ void ViewportWindow::finalizeModel(uint32_t model_id) {
SidecarData& s = *staging_ptr;
if (!device_ || !queue_) {
qWarning() << "[wgpu direct] finalizeModel without an initialised device";
Log::warn() << "[wgpu direct] finalizeModel without an initialised device";
return;
}
if (s.meshes.empty() || s.instances.empty()) {
qInfo().nospace() << "[wgpu direct] finalizeModel(" << model_id
Log::info().nospace() << "[wgpu direct] finalizeModel(" << model_id
<< "): empty staging (meshes=" << s.meshes.size()
<< " instances=" << s.instances.size() << ")";
return;
@@ -1219,7 +1220,7 @@ void ViewportWindow::finalizeModel(uint32_t model_id) {
auto model_it = models_gpu_.find(model_id);
if (model_it == models_gpu_.end()) {
qWarning().nospace()
Log::warn().nospace()
<< "[wgpu direct] finalizeModel(" << model_id
<< "): applyCachedModel produced no model entry";
return;
@@ -1261,7 +1262,7 @@ void ViewportWindow::finalizeModel(uint32_t model_id) {
// sidecar gather.
if (!applyStreamedChunk(m, ci, vbytes, idx)) {
qWarning().nospace()
Log::warn().nospace()
<< "[wgpu direct] finalizeModel(" << model_id
<< "): applyStreamedChunk failed on chunk " << ci
<< " (pool OOM?)";
@@ -1270,7 +1271,7 @@ void ViewportWindow::finalizeModel(uint32_t model_id) {
++chunks_uploaded;
}
qInfo().nospace()
Log::info().nospace()
<< "[wgpu direct] finalizeModel mid=" << model_id
<< " meshes=" << m.meshes.size()
<< " instances=" << m.instances.size()
@@ -1491,7 +1492,7 @@ void ViewportWindow::frameOnFederatedOrigin(uint32_t model_id,
}
}
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu] frameOnFederatedOrigin model=" << model_id
<< " distance=" << camera_distance_
<< " (cap=" << max_distance_m << "m, model radius=" << radius << ")";
@@ -1516,7 +1517,7 @@ void ViewportWindow::exposeEvent(QExposeEvent* /*event*/) {
if (!wgpu_initialized_) {
if (!initWgpu()) {
qWarning() << "wgpu init failed; viewport will not render";
Log::warn() << "wgpu init failed; viewport will not render";
return;
}
wgpu_initialized_ = true;
@@ -1572,26 +1573,26 @@ bool ViewportWindow::initWgpu() {
if (const char* s = std::getenv("WGPU_MIN_PX")) {
const float v = float(std::atof(s));
if (v >= 0.0f) min_pixel_radius_ = v;
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu cull] WGPU_MIN_PX=" << min_pixel_radius_;
}
if (const char* s = std::getenv("WGPU_MIN_PX_MOTION")) {
const float v = float(std::atof(s));
if (v >= 0.0f) motion_min_pixel_radius_ = v;
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu cull] WGPU_MIN_PX_MOTION=" << motion_min_pixel_radius_;
}
if (const char* s = std::getenv("WGPU_STREAM_DEBUG")) {
streaming_debug_ = (s[0] == '1');
if (streaming_debug_) {
qInfo().noquote() << "[wgpu stream] WGPU_STREAM_DEBUG=1 — per-frame "
Log::info().noquote() << "[wgpu stream] WGPU_STREAM_DEBUG=1 — per-frame "
"[stream-debug] log enabled";
}
}
if (const char* s = std::getenv("WGPU_HIZ")) {
if (s[0] == '1') {
hiz_enabled_ = true;
qInfo() << "[wgpu] WGPU_HIZ=1 — HiZ occlusion culling enabled "
Log::info() << "[wgpu] WGPU_HIZ=1 — HiZ occlusion culling enabled "
"(disabled by default; see task #58)";
}
}
@@ -1600,21 +1601,21 @@ bool ViewportWindow::initWgpu() {
// main thread, sequentially. Used to measure speedup vs the
// parallel-per-model path. Any non-"0" value keeps parallelism on.
cull_threads_enabled_ = (s[0] != '0');
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu cull] WGPU_CULL_THREADS=" << s
<< " (parallelism " << (cull_threads_enabled_ ? "ON" : "OFF") << ")";
}
if (const char* s = std::getenv("WGPU_FLY_DEBUG")) {
fly_debug_ = (s[0] == '1');
if (fly_debug_) {
qInfo() << "[wgpu fly] WGPU_FLY_DEBUG=1 — per-frame [fly] dt log enabled";
Log::info() << "[wgpu fly] WGPU_FLY_DEBUG=1 — per-frame [fly] dt log enabled";
}
}
// Mouse-nav preset (matches GL AppSettings::NavPreset). blender default,
// rhino or revit as alternatives. Selection always stays on LMB.
const char* nav_env = std::getenv("WGPU_NAV_PRESET");
applyNavPreset(nav_env ? nav_env : "blender");
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu nav] preset=" << (nav_env ? nav_env : "blender")
<< " (orbit "
<< (orbit_button_ == Qt::RightButton ? "RMB" : "MMB")
@@ -1626,7 +1627,7 @@ bool ViewportWindow::initWgpu() {
instance_ = wgpuCreateInstance(nullptr);
if (!instance_) {
qWarning() << "wgpuCreateInstance returned null";
Log::warn() << "wgpuCreateInstance returned null";
return false;
}
@@ -1650,7 +1651,7 @@ bool ViewportWindow::initWgpu() {
r->adapter = adapter;
r->ok = true;
} else {
qWarning().noquote() << "RequestAdapter failed:" << sv(message);
Log::warn().noquote() << "RequestAdapter failed:" << sv(message);
}
};
acb.userdata1 = &areq;
@@ -1684,10 +1685,10 @@ bool ViewportWindow::initWgpu() {
WGPUDeviceDescriptor dev_desc = {};
dev_desc.requiredLimits = web_limits_ ? &web_floor_limits : &adapter_limits;
if (web_limits_) {
qInfo() << "wgpu --web-limits: requesting browser-floor limits"
Log::info() << "wgpu --web-limits: requesting browser-floor limits"
<< "(maxStorageBufferBindingSize=128MB, maxBufferSize=256MB)";
}
// Surface uncaptured errors (validation failures etc.) into qWarning so
// Surface uncaptured errors (validation failures etc.) into Log::warn so
// they're attributable rather than silently swallowed.
dev_desc.uncapturedErrorCallbackInfo.callback = onUncapturedError;
@@ -1701,7 +1702,7 @@ bool ViewportWindow::initWgpu() {
r->device = device;
r->ok = true;
} else {
qWarning().noquote() << "RequestDevice failed:" << sv(message);
Log::warn().noquote() << "RequestDevice failed:" << sv(message);
}
};
dcb.userdata1 = &dreq;
@@ -1718,7 +1719,7 @@ bool ViewportWindow::initWgpu() {
// runtime answers the question. Failure here is fatal — without any
// pool we can't load chunks.
if (!probeAndCreatePool()) {
qWarning() << "wgpu: streaming pool probe failed; cannot start";
Log::warn() << "wgpu: streaming pool probe failed; cannot start";
return false;
}
@@ -1731,7 +1732,7 @@ bool ViewportWindow::initWgpu() {
WGPUSurfaceCapabilities caps = {};
if (wgpuSurfaceGetCapabilities(surface_, adapter_, &caps) != WGPUStatus_Success
|| caps.formatCount == 0) {
qWarning() << "wgpuSurfaceGetCapabilities returned no formats";
Log::warn() << "wgpuSurfaceGetCapabilities returned no formats";
return false;
}
surface_format_ = caps.formats[0]; // preferred format per wgpu docs
@@ -1741,12 +1742,12 @@ bool ViewportWindow::initWgpu() {
if (!buildHizPipeline()) return false;
if (!buildEdgePipeline()) return false;
if (!overlays_.init(instance_, device_, queue_, surface_format_, SAMPLE_COUNT)) {
qWarning() << "OverlayRenderer init failed";
Log::warn() << "OverlayRenderer init failed";
return false;
}
if (!buildPickPipeline()) return false;
qInfo() << "wgpu init OK; surface format =" << int(surface_format_);
Log::info() << "wgpu init OK; surface format =" << int(surface_format_);
return true;
}
@@ -1824,7 +1825,7 @@ bool ViewportWindow::probeAndCreatePool() {
// size on demand (up to whatever the driver lets us total).
pool_.configure(instance_, device_, pool_usage, try_size,
"ifcviewer-wgpu.pool");
qInfo().noquote()
Log::info().noquote()
<< "wgpu: pool per-sub-buffer capacity ="
<< (try_size / (1024 * 1024)) << "MB"
<< "(device maxBufferSize ="
@@ -1835,7 +1836,7 @@ bool ViewportWindow::probeAndCreatePool() {
try_size /= 2;
}
qWarning() << "wgpu: pool probe found no allocatable size >="
Log::warn() << "wgpu: pool probe found no allocatable size >="
<< (MIN_POOL_CAPACITY / (1024 * 1024)) << "MB";
return false;
}
@@ -1882,7 +1883,7 @@ bool ViewportWindow::createSurface() {
# if __has_include(<X11/Xlib.h>)
auto* x11 = qApp->nativeInterface<QNativeInterface::QX11Application>();
if (!x11 || !x11->display()) {
qWarning() << "Could not get X11 Display* from Qt";
Log::warn() << "Could not get X11 Display* from Qt";
return false;
}
WGPUSurfaceSourceXlibWindow xlib = {};
@@ -1892,27 +1893,27 @@ bool ViewportWindow::createSurface() {
surface_desc.nextInChain = &xlib.chain;
surface_ = wgpuInstanceCreateSurface(instance_, &surface_desc);
# else
qWarning() << "Built without Xlib headers; cannot create X11 surface";
Log::warn() << "Built without Xlib headers; cannot create X11 surface";
return false;
# endif
} else if (platform == "wayland") {
# if __has_include(<wayland-client-core.h>)
auto* wl = qApp->nativeInterface<QNativeInterface::QWaylandApplication>();
if (!wl || !wl->display()) {
qWarning() << "Could not get Wayland wl_display* from Qt";
Log::warn() << "Could not get Wayland wl_display* from Qt";
return false;
}
// The wl_surface for a window is exposed via the QPA window-handle
// accessor on the native interface (not the application-wide one).
// For stage 1 we fail loud; stage-1.5 fills this in.
qWarning() << "Wayland wgpu surface creation not yet wired (stage 1.5)";
Log::warn() << "Wayland wgpu surface creation not yet wired (stage 1.5)";
return false;
# else
qWarning() << "Built without Wayland headers; cannot create Wayland surface";
Log::warn() << "Built without Wayland headers; cannot create Wayland surface";
return false;
# endif
} else {
qWarning().noquote() << "Unsupported Qt platform for wgpu surface:" << platform;
Log::warn().noquote() << "Unsupported Qt platform for wgpu surface:" << platform;
return false;
}
#elif defined(Q_OS_WIN)
@@ -1934,7 +1935,7 @@ bool ViewportWindow::createSurface() {
void* nsview = reinterpret_cast<void*>(static_cast<uintptr_t>(winId()));
void* layer = wgpu_macos_attach_metal_layer(nsview);
if (!layer) {
qWarning() << "Could not attach CAMetalLayer to the Qt NSView";
Log::warn() << "Could not attach CAMetalLayer to the Qt NSView";
return false;
}
WGPUSurfaceSourceMetalLayer metalsrc = {};
@@ -1943,12 +1944,12 @@ bool ViewportWindow::createSurface() {
surface_desc.nextInChain = &metalsrc.chain;
surface_ = wgpuInstanceCreateSurface(instance_, &surface_desc);
#else
qWarning() << "wgpu surface creation not yet wired for this platform";
Log::warn() << "wgpu surface creation not yet wired for this platform";
return false;
#endif
if (!surface_) {
qWarning() << "wgpuInstanceCreateSurface returned null";
Log::warn() << "wgpuInstanceCreateSurface returned null";
return false;
}
return true;
@@ -2024,7 +2025,7 @@ void ViewportWindow::configureSurface(int width_px, int height_px) {
else if (std::strcmp(s, "immediate") == 0) { override_pm = WGPUPresentMode_Immediate; pm_name = "immediate"; }
else {
known = false;
qWarning().noquote().nospace()
Log::warn().noquote().nospace()
<< "[wgpu] unknown WGPU_PRESENT_MODE=" << s
<< " (expected fifo|fifo_relaxed|mailbox|immediate); falling back to preference order";
}
@@ -2064,7 +2065,7 @@ void ViewportWindow::configureSurface(int width_px, int height_px) {
if (i > 0) advertised += ", ";
advertised += QString::fromLatin1(name);
}
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu] surface advertises present modes: " << advertised;
}
WGPUPresentMode pm = WGPUPresentMode_Fifo; // spec-guaranteed fallback
@@ -2093,7 +2094,7 @@ void ViewportWindow::configureSurface(int width_px, int height_px) {
note = " (vsync OFF — framerate uncapped, may tear)"; break;
default: break;
}
qInfo().noquote().nospace() << "[wgpu] present mode = " << pm_name << note;
Log::info().noquote().nospace() << "[wgpu] present mode = " << pm_name << note;
}
cfg.alphaMode = WGPUCompositeAlphaMode_Auto;
@@ -2376,7 +2377,7 @@ bool ViewportWindow::buildEdgePipeline() {
edge_pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &rp_desc);
if (!edge_pipeline_) {
qWarning() << "wgpu edge pipeline creation failed";
Log::warn() << "wgpu edge pipeline creation failed";
return false;
}
return true;
@@ -2495,7 +2496,7 @@ bool ViewportWindow::buildPickPipeline() {
pick_pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &rp_desc);
if (!pick_pipeline_) {
qWarning() << "wgpu pick pipeline creation failed";
Log::warn() << "wgpu pick pipeline creation failed";
return false;
}
return true;
@@ -3033,7 +3034,7 @@ bool ViewportWindow::pickSurfaceAt(int x_pixels, int y_pixels,
void ViewportWindow::toggleSectionTool() {
section_tool_active_ = !section_tool_active_;
qInfo().noquote() << "[wgpu section] tool"
Log::info().noquote() << "[wgpu section] tool"
<< (section_tool_active_ ? "active" : "off");
if (isExposed()) requestUpdate();
}
@@ -3042,7 +3043,8 @@ bool ViewportWindow::addSectionPlaneAtSurface(const Eigen::Vector3f& point,
const Eigen::Vector3f& normal,
float visual_radius) {
if (int(section_planes_.size()) >= kMaxSectionPlanes) {
qWarning("[wgpu section] cap reached (%d planes)", kMaxSectionPlanes);
std::fprintf(stderr, "[warn] [wgpu section] cap reached (%d planes)\n",
kMaxSectionPlanes);
return false;
}
Eigen::Vector3f n = normal;
@@ -3061,7 +3063,7 @@ bool ViewportWindow::addSectionPlaneAtSurface(const Eigen::Vector3f& point,
p.d = -n.dot(point);
p.visual_radius = (visual_radius > 0.0f) ? visual_radius : 1.0f;
section_planes_.push_back(p);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu section] added plane #" << section_planes_.size() - 1
<< " origin=(" << point.x() << "," << point.y() << "," << point.z() << ")"
<< " normal=(" << n.x() << "," << n.y() << "," << n.z() << ")";
@@ -3072,14 +3074,14 @@ bool ViewportWindow::addSectionPlaneAtSurface(const Eigen::Vector3f& point,
void ViewportWindow::removeSectionPlane(int index) {
if (index < 0 || index >= int(section_planes_.size())) return;
section_planes_.erase(section_planes_.begin() + index);
qInfo().noquote() << "[wgpu section] removed plane" << index;
Log::info().noquote() << "[wgpu section] removed plane" << index;
if (isExposed()) requestUpdate();
}
void ViewportWindow::clearSectionPlanes() {
if (section_planes_.empty()) return;
section_planes_.clear();
qInfo() << "[wgpu section] cleared all planes";
Log::info() << "[wgpu section] cleared all planes";
if (isExposed()) requestUpdate();
}
@@ -3628,21 +3630,21 @@ void ViewportWindow::setToolMode(ToolMode m) {
switch (tool_mode_) {
case ToolMode::NoTool:
qInfo() << "[wgpu measure] tool off";
Log::info() << "[wgpu measure] tool off";
break;
case ToolMode::Volume:
qInfo() << "[wgpu measure] volume tool — pick / marquee objects, Esc to exit";
Log::info() << "[wgpu measure] volume tool — pick / marquee objects, Esc to exit";
overlays_.setHudText(QStringLiteral("Volume: 0.0000 m³ (0 objects)"));
updateVolumeReadout();
break;
case ToolMode::Area:
if (!area_tool_) area_tool_ = std::make_unique<AreaMeasurement>();
qInfo() << "[wgpu measure] area tool — LMB pick coplanar patch, Alt+LMB single tri, click again to remove, Esc exits";
Log::info() << "[wgpu measure] area tool — LMB pick coplanar patch, Alt+LMB single tri, click again to remove, Esc exits";
overlays_.setHudText(QStringLiteral("Area: 0.0000 m² (0 tris)"));
break;
case ToolMode::Length:
if (!length_tool_) length_tool_ = std::make_unique<LengthMeasurement>();
qInfo() << "[wgpu measure] length tool — LMB add point, Backspace remove last, Esc exits";
Log::info() << "[wgpu measure] length tool — LMB add point, Backspace remove last, Esc exits";
overlays_.setHudText(QStringLiteral("Length tool: click first point"));
break;
}
@@ -3901,7 +3903,7 @@ bool ViewportWindow::buildHizPipeline() {
hiz_pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &rp_desc);
if (!hiz_pipeline_) {
qWarning() << "wgpu hiz pipeline creation failed";
Log::warn() << "wgpu hiz pipeline creation failed";
return false;
}
@@ -4300,7 +4302,7 @@ bool ViewportWindow::aabbOccludedByHiz(const float mn[3], const float mx[3]) con
if (rejected && hiz_trace_budget_.load(std::memory_order_relaxed) > 0) {
int prev = hiz_trace_budget_.fetch_sub(1, std::memory_order_relaxed);
if (prev > 0) {
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[hiz reject] aabb_min=(" << mn[0] << "," << mn[1] << "," << mn[2] << ")"
<< " aabb_max=(" << mx[0] << "," << mx[1] << "," << mx[2] << ")"
<< " ndc_x=[" << nx_lo << "," << nx_hi << "]"
@@ -4648,7 +4650,7 @@ void ViewportWindow::render() {
return;
}
default:
qWarning() << "GetCurrentTexture status" << int(surf_tex.status);
Log::warn() << "GetCurrentTexture status" << int(surf_tex.status);
return;
}
@@ -4749,7 +4751,7 @@ void ViewportWindow::render() {
hiz_trace_budget_.store(kHizTracePerFrame, std::memory_order_relaxed);
// One-shot per-frame log so the user can correlate rejections
// with what they were looking at.
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[hiz trace] frame: vp_match="
<< (hiz_vp_ == vp_this_frame ? "exact" : "loose")
<< " pyramid_mip0=" << hiz_mip_w_[0] << "x" << hiz_mip_h_[0]
@@ -4767,7 +4769,7 @@ void ViewportWindow::render() {
const uint32_t x = (s * (W0 - 1)) / 7;
row += QString::asprintf("%.4f ", L0[y * W0 + x]);
}
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[hiz trace] pyramid row " << y << " (8 samples): " << row;
}
} else if (hiz_trace_on) {
@@ -5060,7 +5062,7 @@ void ViewportWindow::render() {
r->done = true;
r->ok = (status == WGPUMapAsyncStatus_Success);
if (!r->ok) {
qWarning().noquote() << "wgpu MapAsync failed:" << sv(message);
Log::warn().noquote() << "wgpu MapAsync failed:" << sv(message);
}
};
mcb.userdata1 = &req;
@@ -5099,10 +5101,10 @@ void ViewportWindow::render() {
wgpuBufferUnmap(capture_buffer);
if (img.save(pending_screenshot_path_, "PNG")) {
qInfo().noquote() << "[wgpu] saved screenshot:"
Log::info().noquote() << "[wgpu] saved screenshot:"
<< pending_screenshot_path_ << "(" << w << "x" << h << ")";
} else {
qWarning().noquote() << "[wgpu] QImage::save failed for"
Log::warn().noquote() << "[wgpu] QImage::save failed for"
<< pending_screenshot_path_;
}
}
@@ -5219,7 +5221,7 @@ void ViewportWindow::render() {
}
}
const double mb = 1.0 / (1024.0 * 1024.0);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[frame] " << QString::number(ms > 0 ? 1000.0f / ms : 0.0f, 'f', 1) << " fps"
<< " " << QString::number(ms, 'f', 2) << " ms"
<< " obj " << last_visible_objects_ << "/" << total_instances
@@ -5261,7 +5263,7 @@ void ViewportWindow::render() {
: (max_load > 5)
? "few chunks cycling (hysteresis boundary)"
: "loading";
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[stream] " << chunks_resident << " resident, "
<< chunks_missing << " missing, " << cycled
<< " cycled (max load=" << max_load << ")"
@@ -5362,7 +5364,7 @@ void ViewportWindow::render() {
const float hb = std::max(b.history, 0.05f);
return a.priority * ha < b.priority * hb;
});
qInfo().noquote() << " [missing per model — top 8 by missing-count]";
Log::info().noquote() << " [missing per model — top 8 by missing-count]";
struct Row {
QString name;
size_t resident = 0;
@@ -5391,27 +5393,27 @@ void ViewportWindow::render() {
const size_t cap = std::min<size_t>(rows.size(), 8);
for (size_t i = 0; i < cap; ++i) {
const Row& r = rows[i];
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " " << r.name
<< " resident=" << r.resident
<< " frustum=" << r.frustum
<< " missing=" << r.missing;
}
qInfo().noquote() << " [top 20 MISSING chunks by priority (px², want these loaded)]";
Log::info().noquote() << " [top 20 MISSING chunks by priority (px², want these loaded)]";
for (size_t i = 0; i < std::min<size_t>(20, missing_set.size()); ++i) {
const Probe& p = missing_set[i];
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " pri=" << QString::number(p.priority, 'f', 0)
<< " aabb=" << QString::number(p.ex, 'f', 1) << "x"
<< QString::number(p.ey, 'f', 1) << "x"
<< QString::number(p.ez, 'f', 1) << "m"
<< " in " << p.name;
}
qInfo().noquote() << " [bottom 5 RESIDENT chunks by effective priority (must beat with 2× hysteresis)]";
Log::info().noquote() << " [bottom 5 RESIDENT chunks by effective priority (must beat with 2× hysteresis)]";
for (size_t i = 0; i < std::min<size_t>(5, resident_set.size()); ++i) {
const Probe& p = resident_set[i];
const float eff = p.priority * std::max(p.history, 0.05f);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " pri=" << QString::number(p.priority, 'f', 0)
<< " hist=" << QString::number(p.history, 'f', 2)
<< " eff=" << QString::number(eff, 'f', 0)
@@ -5448,7 +5450,7 @@ void ViewportWindow::render() {
const bool converged = bench_warm_streak_ >= CONVERGE_FRAMES_REQUIRED;
const bool timed_out = bench_warm_frames_total_ >= MAX_WARM_FRAMES;
if (converged) {
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[bench warm] converged after "
<< bench_warm_frames_total_ << " frames";
bench_warm_done_ = true;
@@ -5510,7 +5512,7 @@ void ViewportWindow::render() {
} else {
diag = "converged, just below the gate's 5-frame streak";
}
qWarning().noquote().nospace()
Log::warn().noquote().nospace()
<< "[bench warm] timed out after " << bench_warm_frames_total_
<< " frames without convergence (last loads="
<< streaming_loads_this_frame_ << ")\n"
@@ -5564,7 +5566,7 @@ void ViewportWindow::render() {
const double avg_n = double(std::max(1, bench_count_ - bench_warmup_ + 1));
const double cull_ms = bench_cull_ms_total_ / avg_n;
const double stream_ms = bench_stream_ms_total_ / avg_n;
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[frame] " << QString::number(ms > 0 ? 1000.0f / ms : 0.0f, 'f', 1) << " fps"
<< " " << QString::number(ms, 'f', 2) << " ms"
<< " obj " << last_visible_objects_ << "/" << total_instances
@@ -5612,27 +5614,27 @@ void ViewportWindow::render() {
const float p99 = pct(0.99);
const float total_sweep = bench_yaw_speed_ * float(bench_total_);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "\n=== BENCHMARK (" << bench_total_ << " frames, orbit "
<< total_sweep << "° at " << bench_yaw_speed_ << "°/frame) ===";
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " avg: " << avg << " ms (" << (avg > 0 ? 1000.0f/avg : 0.0f) << " fps)";
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " median: " << median << " ms (" << (median > 0 ? 1000.0f/median : 0.0f) << " fps)";
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " p1: " << p1 << " ms p99: " << p99 << " ms";
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " last frame: obj " << last_visible_objects_
<< " tri " << last_visible_triangles_
<< " sub_draws " << last_sub_draws_
<< " hiz_rej " << hiz_reject_count_;
const double n = double(std::max(1, bench_total_));
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " per-frame avg ms: cull=" << bench_cull_ms_total_ / n
<< " stream=" << bench_stream_ms_total_ / n
<< " hiz_readback=" << bench_hiz_readback_ms_total_ / n
<< " hiz=" << (hiz_enabled_ ? "on" : "off");
qInfo().noquote() << "=== END BENCHMARK ===\n";
Log::info().noquote() << "=== END BENCHMARK ===\n";
bench_total_ = 0;
QCoreApplication::quit();
@@ -5735,7 +5737,7 @@ bool ViewportWindow::buildPipelines() {
main_pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &rp_desc);
if (!main_pipeline_) {
qWarning() << "wgpu main render pipeline creation failed";
Log::warn() << "wgpu main render pipeline creation failed";
return false;
}
@@ -5787,7 +5789,7 @@ bool ViewportWindow::buildPipelines() {
main_pipeline_transparent_ =
wgpuDeviceCreateRenderPipeline(device_, &rp_desc_t);
if (!main_pipeline_transparent_) {
qWarning() << "wgpu main transparent render pipeline creation failed";
Log::warn() << "wgpu main transparent render pipeline creation failed";
return false;
}
@@ -6110,7 +6112,7 @@ bool ViewportWindow::loadChunkBytesAndUploadGpu(ModelGpuData& m, size_t chunk_id
if (!readSidecarVertexRanges(req.file_path,
req.vertex_section_offset,
req.v_ranges, vbytes)) {
qWarning().noquote().nospace()
Log::warn().noquote().nospace()
<< "[wgpu stream] failed to read vertex chunk " << chunk_idx
<< " (" << req.v_ranges.size() << " ranges, total "
<< c.vertex_byte_size << " B)";
@@ -6121,7 +6123,7 @@ bool ViewportWindow::loadChunkBytesAndUploadGpu(ModelGpuData& m, size_t chunk_id
if (!readSidecarIndexRanges(req.file_path,
req.index_section_offset,
req.i_ranges, idx)) {
qWarning().noquote().nospace()
Log::warn().noquote().nospace()
<< "[wgpu stream] failed to read index chunk " << chunk_idx
<< " (" << req.i_ranges.size() << " ranges, total "
<< c.index_count << " indices)";
@@ -6339,7 +6341,7 @@ void ViewportWindow::driveStreamingLoads() {
victim.last_evicted_by_model_id == cand_mid
&& victim.last_evicted_by_chunk_idx == cand_ci
&& victim.load_count > 1;
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< (is_2_cycle ? "[evict 2-cycle] " : "[evict] ")
<< "kicked chunk " << victim_ci
<< " of " << vic_fi.completeBaseName()
@@ -6389,7 +6391,7 @@ void ViewportWindow::driveStreamingLoads() {
// been hidden). Clear the loading flag regardless.
c.is_loading = false;
if (!res.success) {
qWarning().noquote().nospace()
Log::warn().noquote().nospace()
<< "[wgpu stream] worker read failed for model "
<< res.model_id << " chunk " << res.chunk_idx;
continue;
@@ -6405,7 +6407,7 @@ void ViewportWindow::driveStreamingLoads() {
streaming_frame_idx_ + BLOCKED_COOLDOWN_FRAMES;
if (evict_log) {
QFileInfo fi(QString::fromStdString(m.streaming_file_path));
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[blocked-apply] chunk " << res.chunk_idx
<< " of " << fi.completeBaseName()
<< " — pool OOM at apply, fetched bytes discarded"
@@ -6426,7 +6428,7 @@ void ViewportWindow::driveStreamingLoads() {
if (lc == 3 || lc == 10 || lc == 30 || lc == 100
|| (lc > 100 && (lc % 100) == 0)) {
QFileInfo fi(QString::fromStdString(m.streaming_file_path));
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[stream thrash] chunk " << res.chunk_idx
<< " of " << fi.completeBaseName()
<< " loaded " << lc << "× — pool saturated?";
@@ -6501,7 +6503,7 @@ void ViewportWindow::driveStreamingLoads() {
const uint64_t i_bytes = c.index_count * sizeof(uint32_t);
const double mb = 1.0 / (1024.0 * 1024.0);
QFileInfo fi(QString::fromStdString(cand.m->streaming_file_path));
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[blocked] chunk " << cand.ci
<< " of " << fi.completeBaseName()
<< " (pri=" << QString::number(cand.priority, 'f', 0)
@@ -6567,18 +6569,18 @@ void ViewportWindow::driveStreamingLoads() {
const float my_area = chunkScreenAreaPx(c, vp_mat);
const uint64_t my_bytes = c.vertex_byte_size
+ c.index_count * sizeof(uint32_t);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[track] chunk " << tracked_chunk_idx_
<< " (object " << tracked_object_id_
<< ", model " << tracked_chunk_mid_
<< ") EVICTED this frame";
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " area=" << QString::number(my_area, 'f', 0) << "px²"
<< " frustum_vis=" << c.frustum_visible_count
<< " hist=" << QString::number(c.visibility_history, 'f', 2)
<< " load_count=" << c.load_count
<< " size=" << QString::number(double(my_bytes) * mb, 'f', 1) << "MB";
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " chunk aabb "
<< QString::number(c.aabb_max[0] - c.aabb_min[0], 'f', 1) << "×"
<< QString::number(c.aabb_max[1] - c.aabb_min[1], 'f', 1) << "×"
@@ -6587,14 +6589,14 @@ void ViewportWindow::driveStreamingLoads() {
<< QString::number(0.5f * (c.aabb_min[0] + c.aabb_max[0]), 'f', 1) << ","
<< QString::number(0.5f * (c.aabb_min[1] + c.aabb_max[1]), 'f', 1) << ","
<< QString::number(0.5f * (c.aabb_min[2] + c.aabb_max[2]), 'f', 1) << ")";
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " pool used="
<< QString::number(double(pool_.total_used_bytes()) * mb, 'f', 0)
<< "/"
<< QString::number(double(pool_.total_capacity_bytes()) * mb, 'f', 0)
<< "MB largest_free="
<< QString::number(double(pool_.largest_free_run_bytes()) * mb, 'f', 1) << "MB";
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " this-frame: cands=" << streaming_candidates_this_frame_
<< " enq=" << enqueued
<< " ev_lru=" << streaming_evictions_lru_this_frame_
@@ -6617,7 +6619,7 @@ void ViewportWindow::driveStreamingLoads() {
[](const Stat& a, const Stat& b){ return a.area > b.area; });
const size_t n = std::min<size_t>(5, all.size());
for (size_t i = 0; i < n; ++i) {
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " top cand #" << i << ": model " << all[i].mid
<< " chunk " << all[i].ci
<< " area=" << QString::number(all[i].area, 'f', 0) << "px²";
@@ -6641,7 +6643,7 @@ void ViewportWindow::driveStreamingLoads() {
if (c.load_count > 1) ++cycled;
}
}
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[stream-debug] f" << streaming_frame_idx_
<< " cands=" << streaming_candidates_this_frame_
<< " enq=" << enqueued
@@ -6892,7 +6894,7 @@ void ViewportWindow::viewAll() {
}
}
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu] viewAll target=(" << cx << ", " << cy << ", " << cz << ")"
<< " distance=" << camera_distance_
<< " (scene radius=" << radius << ")";
@@ -6959,7 +6961,7 @@ bool ViewportWindow::computeObjectAabb(uint32_t object_id,
void ViewportWindow::focusOnSelectedObject() {
if (fps_mode_) return;
if (selection_.count() == 0) {
qInfo() << "[wgpu] focus: no object selected";
Log::info() << "[wgpu] focus: no object selected";
return;
}
float lo[3] = { std::numeric_limits<float>::infinity(),
@@ -6979,7 +6981,7 @@ void ViewportWindow::focusOnSelectedObject() {
any = true;
}
if (!any) {
qInfo() << "[wgpu] focus: no AABB available";
Log::info() << "[wgpu] focus: no AABB available";
return;
}
frameAabb(lo, hi, 1.30f);
@@ -6996,7 +6998,7 @@ void ViewportWindow::setStandardView(float yaw_deg, float pitch_deg) {
void ViewportWindow::toggleProjection() {
projection_ortho_ = !projection_ortho_;
qInfo() << "[wgpu] projection:" << (projection_ortho_ ? "ortho" : "perspective");
Log::info() << "[wgpu] projection:" << (projection_ortho_ ? "ortho" : "perspective");
if (isExposed()) requestUpdate();
}
@@ -7019,7 +7021,7 @@ void ViewportWindow::enterFpsMode() {
fps_last_tick_.start();
setCursor(Qt::BlankCursor);
QCursor::setPos(mapToGlobal(fps_press_center_));
qInfo() << "[wgpu] fly mode active — WASD/QE to move, Shift to boost, Esc to exit";
Log::info() << "[wgpu] fly mode active — WASD/QE to move, Shift to boost, Esc to exit";
if (isExposed()) requestUpdate();
}
@@ -7028,7 +7030,7 @@ void ViewportWindow::exitFpsMode() {
fps_mode_ = false;
fps_keys_held_.clear();
setCursor(Qt::ArrowCursor);
qInfo() << "[wgpu] fly mode off";
Log::info() << "[wgpu] fly mode off";
if (isExposed()) requestUpdate();
}
@@ -7088,7 +7090,7 @@ void ViewportWindow::fpsIntegrate() {
const qint64 since_render_ns = fly_render_clock_.isValid()
? fly_render_clock_.nsecsElapsed() : 0;
fly_render_clock_.restart();
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[fly] dt=" << QString::number(dt * 1000.0f, 'f', 2) << "ms"
<< " render_gap=" << QString::number(double(since_render_ns) / 1e6, 'f', 2) << "ms"
<< " keys=" << fps_keys_held_.size()
@@ -7247,7 +7249,7 @@ void ViewportWindow::mousePressEvent(QMouseEvent* event) {
section_drag_start_mouse_ = lp;
section_drag_start_origin_ = section_planes_[hit].origin;
nav_drag_kind_ = NavDrag::Inactive;
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu section] drag start: plane=" << hit;
return;
}
@@ -7312,16 +7314,16 @@ void ViewportWindow::mouseReleaseEvent(QMouseEvent* event) {
const auto mods = box_select_press_mods_;
if (mods & Qt::ShiftModifier) {
for (uint32_t id : ids) selection_.add(id);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu marquee] +add " << ids.size() << " object_ids";
} else if (mods & Qt::ControlModifier) {
for (uint32_t id : ids) selection_.remove(id);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu marquee] -remove " << ids.size() << " object_ids";
} else {
selection_.clear();
for (uint32_t id : ids) selection_.add(id);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu marquee] replace " << ids.size() << " object_ids";
}
nav_active_button_ = Qt::NoButton;
@@ -7357,7 +7359,7 @@ void ViewportWindow::mouseReleaseEvent(QMouseEvent* event) {
addSectionPlaneAtSurface(hit_pos, hit_normal,
hit_radius * 1.5f);
} else {
qInfo().noquote() << "[wgpu section] click missed (no surface)";
Log::info().noquote() << "[wgpu section] click missed (no surface)";
}
nav_active_button_ = Qt::NoButton;
nav_drag_kind_ = NavDrag::Inactive;
@@ -7403,18 +7405,18 @@ void ViewportWindow::mouseReleaseEvent(QMouseEvent* event) {
if (!(mods & (Qt::ShiftModifier | Qt::ControlModifier))) {
selection_.clear();
}
qInfo().noquote() << "[wgpu pick] miss";
Log::info().noquote() << "[wgpu pick] miss";
} else if (mods & Qt::ControlModifier) {
selection_.remove(id);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu pick] -remove object_id=" << id;
} else if (mods & Qt::ShiftModifier) {
selection_.add(id);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu pick] +add object_id=" << id;
} else {
selection_.replace(id);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu pick] replace object_id=" << id;
}
// Notify external listeners (bonsai mirrors picks into
@@ -7433,7 +7435,7 @@ void ViewportWindow::mouseReleaseEvent(QMouseEvent* event) {
tracked_chunk_idx_ = SIZE_MAX; // legacy "primary" slot
tracked_chunk_mid_ = 0;
std::set<std::pair<uint32_t, size_t>> seen;
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[track] object " << id << " — enumerating chunks:";
for (auto& [mid, m] : models_gpu_) {
for (const auto& inst : m.instances) {
@@ -7442,7 +7444,7 @@ void ViewportWindow::mouseReleaseEvent(QMouseEvent* event) {
const size_t ci = m.mesh_chunk_idx[inst.mesh_id];
if (!seen.insert({mid, ci}).second) continue;
const auto& c = m.chunks[ci];
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< " model " << mid << " chunk " << ci
<< " inst_aabb "
<< QString::number(inst.world_aabb_max[0] - inst.world_aabb_min[0], 'f', 1)
@@ -7466,7 +7468,7 @@ void ViewportWindow::mouseReleaseEvent(QMouseEvent* event) {
}
}
if (tracked_chunk_idx_ == SIZE_MAX) {
qInfo() << " (object_id not matched to any instance)";
Log::info() << " (object_id not matched to any instance)";
}
} else {
tracked_object_id_ = 0;
@@ -7653,7 +7655,7 @@ void ViewportWindow::keyPressEvent(QKeyEvent* event) {
if (key == Qt::Key_H && mods == Qt::AltModifier) {
if (visibility_.hiddenCount() == 0) return;
visibility_.clear();
qInfo() << "[wgpu] show all";
Log::info() << "[wgpu] show all";
requestUpdate();
return;
}
@@ -7664,7 +7666,7 @@ void ViewportWindow::keyPressEvent(QKeyEvent* event) {
if (key == Qt::Key_X && mods == Qt::AltModifier && !event->isAutoRepeat()) {
constexpr float kXrayOnCap = 0.3f;
xray_alpha_cap_ = (xray_alpha_cap_ < 1.0f) ? 1.0f : kXrayOnCap;
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu] x-ray "
<< (xray_alpha_cap_ < 1.0f ? "ON" : "OFF")
<< " (cap=" << xray_alpha_cap_ << ")";
@@ -7683,7 +7685,7 @@ void ViewportWindow::keyPressEvent(QKeyEvent* event) {
}
}
}
qInfo().noquote().nospace() << "[wgpu] isolated " << selection_.count()
Log::info().noquote().nospace() << "[wgpu] isolated " << selection_.count()
<< " (hid " << hidden_now << " others)";
requestUpdate();
return;
@@ -7693,7 +7695,7 @@ void ViewportWindow::keyPressEvent(QKeyEvent* event) {
for (uint32_t id : selection_.selectionIds()) visibility_.hide(id);
const size_t n = selection_.count();
selection_.clear(); // hiding deselects, matching GL behaviour
qInfo().noquote().nospace() << "[wgpu] hid " << n << " selected";
Log::info().noquote().nospace() << "[wgpu] hid " << n << " selected";
requestUpdate();
return;
}
@@ -7771,7 +7773,7 @@ void ViewportWindow::keyPressEvent(QKeyEvent* event) {
return;
}
if (key == Qt::Key_C && !(mods & Qt::ControlModifier)) {
qInfo("--camera %s", qPrintable(cameraString()));
Log::info() << "--camera " << cameraString().toUtf8().constData();
return;
}
// Standard axis-aligned views: X/Y/Z look from +axis, Shift+X/Y/Z from
@@ -7808,7 +7810,7 @@ void ViewportWindow::wheelEvent(QWheelEvent* event) {
if (fps_mode_) {
const float factor = std::pow(1.25f, notches);
fps_move_speed_ = std::clamp(fps_move_speed_ * factor, 0.05f, 1000.0f);
qInfo().noquote().nospace()
Log::info().noquote().nospace()
<< "[wgpu] fly speed: " << QString::number(fps_move_speed_, 'f', 2) << " m/s";
return;
}