From 6dd3558db9d547316a5e64f50ab63ba6be7b0702 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 5 Jun 2026 08:58:45 +1000 Subject: [PATCH] ifcviewer: replace qInfo/qWarning with a Qt-free logger seam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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()< from each. Closes #82. Builds: desktop / bonsai / web all green. Tests 100/100 pass. --- src/ifcviewer-minimal/main.cpp | 9 +- src/ifcviewer/AreaMeasurement.cpp | 9 +- src/ifcviewer/CMakeLists.txt | 2 + src/ifcviewer/GeometryStreamer.cpp | 16 +- src/ifcviewer/Log.h | 106 ++++++++++++ src/ifcviewer/LogQt.h | 67 ++++++++ src/ifcviewer/SceneLoader.cpp | 32 ++-- src/ifcviewer/ViewportWindow.cpp | 268 +++++++++++++++-------------- 8 files changed, 348 insertions(+), 161 deletions(-) create mode 100644 src/ifcviewer/Log.h create mode 100644 src/ifcviewer/LogQt.h diff --git a/src/ifcviewer-minimal/main.cpp b/src/ifcviewer-minimal/main.cpp index 24fd316e8b..19d5d31dce 100644 --- a/src/ifcviewer-minimal/main.cpp +++ b/src/ifcviewer-minimal/main.cpp @@ -23,6 +23,8 @@ #include #include +#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(); } } diff --git a/src/ifcviewer/AreaMeasurement.cpp b/src/ifcviewer/AreaMeasurement.cpp index 623578725b..747499fc43 100644 --- a/src/ifcviewer/AreaMeasurement.cpp +++ b/src/ifcviewer/AreaMeasurement.cpp @@ -22,7 +22,7 @@ #include "OverlayRenderer.h" #include "ViewportWindow.h" -#include +#include #include #include @@ -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) { diff --git a/src/ifcviewer/CMakeLists.txt b/src/ifcviewer/CMakeLists.txt index fe84002203..71b8436771 100644 --- a/src/ifcviewer/CMakeLists.txt +++ b/src/ifcviewer/CMakeLists.txt @@ -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 diff --git a/src/ifcviewer/GeometryStreamer.cpp b/src/ifcviewer/GeometryStreamer.cpp index 20216d2187..1acd88023d 100644 --- a/src/ifcviewer/GeometryStreamer.cpp +++ b/src/ifcviewer/GeometryStreamer.cpp @@ -34,7 +34,7 @@ #include #include -#include +#include #include 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(total_shapes) / static_cast(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(); } diff --git a/src/ifcviewer/Log.h b/src/ifcviewer/Log.h new file mode 100644 index 0000000000..d04fa47982 --- /dev/null +++ b/src/ifcviewer/Log.h @@ -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 . * + * * + ********************************************************************************/ + +#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 +#include +#include + +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 diff --git a/src/ifcviewer/LogQt.h b/src/ifcviewer/LogQt.h new file mode 100644 index 0000000000..7031b3653a --- /dev/null +++ b/src/ifcviewer/LogQt.h @@ -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 . * + * * + ********************************************************************************/ + +#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 +#include + +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 diff --git a/src/ifcviewer/SceneLoader.cpp b/src/ifcviewer/SceneLoader.cpp index b02cbb3808..4b96aae495 100644 --- a/src/ifcviewer/SceneLoader.cpp +++ b/src/ifcviewer/SceneLoader.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include @@ -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::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( 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::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(); diff --git a/src/ifcviewer/ViewportWindow.cpp b/src/ifcviewer/ViewportWindow.cpp index 92167517b8..1aa3f5fbad 100644 --- a/src/ifcviewer/ViewportWindow.cpp +++ b/src/ifcviewer/ViewportWindow.cpp @@ -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 #include #include -#include #include #include #include @@ -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(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() auto* x11 = qApp->nativeInterface(); 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() auto* wl = qApp->nativeInterface(); 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(static_cast(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(); - 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(); - 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(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(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(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(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::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> 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; }