mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 06:39:13 +00:00
wgpu backend: --camera flag + request adapter's max buffer limits
Two pieces that block proper side-by-side parity with the GL minimal: 1. --camera tx,ty,tz,dist,yaw,pitch. Same format string as the GL minimal so a pasted camera arg lands the same view on both backends. setCamera() also flips initial_view_applied_ = true so the auto- viewAll-on-first-load doesn't snap away from the script-set position when the model finishes uploading. 2. Real BIM models exceed the conservative WebGPU defaults at device create time. A 114k-instance / 19M-index sidecar's vertex storage is 139 MB, which trips wgpu's default 128 MB max_storage_buffer_binding_ size and bind-group creation fails. Now wgpuAdapterGetLimits is called first and the device is requested at the adapter's full ceiling — every desktop driver supports multi-GB. Trade-off worth flagging: web parity will fail here because browsers cap at the defaults. The eventual fix is to split a model's vertex/ instance storage into ≤128 MB chunks with a small per-frame routing table, which is a real chunk of work. For now this unblocks all the native benchmarking the user is actually doing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,9 @@ int main(int argc, char* argv[]) {
|
|||||||
"Render one frame, save to PATH as PNG, exit.", "path"});
|
"Render one frame, save to PATH as PNG, exit.", "path"});
|
||||||
parser.addOption({{"b", "benchmark"},
|
parser.addOption({{"b", "benchmark"},
|
||||||
"Render N frames (yaw-sweeping the camera), print stats, exit.", "frames"});
|
"Render N frames (yaw-sweeping the camera), print stats, exit.", "frames"});
|
||||||
|
parser.addOption({{"c", "camera"},
|
||||||
|
"Set camera as tx,ty,tz,dist,yaw,pitch (same format as IfcViewerMinimal).",
|
||||||
|
"params"});
|
||||||
parser.process(app);
|
parser.process(app);
|
||||||
|
|
||||||
auto* viewport = new WgpuViewportWindow;
|
auto* viewport = new WgpuViewportWindow;
|
||||||
@@ -65,6 +68,23 @@ int main(int argc, char* argv[]) {
|
|||||||
viewport->queueLoadSidecar(path);
|
viewport->queueLoadSidecar(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parser.isSet("camera")) {
|
||||||
|
const QStringList parts = parser.value("camera").split(',');
|
||||||
|
if (parts.size() == 6) {
|
||||||
|
bool ok = true;
|
||||||
|
float v[6];
|
||||||
|
for (int i = 0; i < 6 && ok; ++i) v[i] = parts[i].toFloat(&ok);
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning() << "--camera: expected 6 comma-separated floats, got"
|
||||||
|
<< parts.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (parser.isSet("screenshot")) {
|
if (parser.isSet("screenshot")) {
|
||||||
viewport->captureNextFrameToPng(parser.value("screenshot"),
|
viewport->captureNextFrameToPng(parser.value("screenshot"),
|
||||||
/*quit_after=*/true);
|
/*quit_after=*/true);
|
||||||
|
|||||||
@@ -631,7 +631,19 @@ bool WgpuViewportWindow::initWgpu() {
|
|||||||
struct DeviceReq { WGPUDevice device = nullptr; bool done = false; bool ok = false; };
|
struct DeviceReq { WGPUDevice device = nullptr; bool done = false; bool ok = false; };
|
||||||
DeviceReq dreq;
|
DeviceReq dreq;
|
||||||
|
|
||||||
|
// Query what the adapter can do, and request the same headroom on the
|
||||||
|
// device so a single large model's vertex/instance storage buffer doesn't
|
||||||
|
// hit the conservative defaults (128 MB binding, 256 MB buffer). Real
|
||||||
|
// BIM models routinely cross 100 MB of vertex bytes; without this the
|
||||||
|
// first applyCachedModel fails with a bind-group validation error.
|
||||||
|
//
|
||||||
|
// For eventual web parity this needs revisiting — browsers cap at the
|
||||||
|
// defaults — but native targets always support the requested ceilings.
|
||||||
|
WGPULimits adapter_limits = {};
|
||||||
|
wgpuAdapterGetLimits(adapter_, &adapter_limits);
|
||||||
|
|
||||||
WGPUDeviceDescriptor dev_desc = {};
|
WGPUDeviceDescriptor dev_desc = {};
|
||||||
|
dev_desc.requiredLimits = &adapter_limits;
|
||||||
// Surface uncaptured errors (validation failures etc.) into qWarning so
|
// Surface uncaptured errors (validation failures etc.) into qWarning so
|
||||||
// they're attributable rather than silently swallowed.
|
// they're attributable rather than silently swallowed.
|
||||||
dev_desc.uncapturedErrorCallbackInfo.callback = onUncapturedError;
|
dev_desc.uncapturedErrorCallbackInfo.callback = onUncapturedError;
|
||||||
@@ -1543,6 +1555,20 @@ bool WgpuViewportWindow::computeSceneAabb(float mn[3], float mx[3]) const {
|
|||||||
return any;
|
return any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WgpuViewportWindow::setCamera(float tx, float ty, float tz,
|
||||||
|
float dist, float yaw_deg, float pitch_deg) {
|
||||||
|
camera_target_[0] = tx;
|
||||||
|
camera_target_[1] = ty;
|
||||||
|
camera_target_[2] = tz;
|
||||||
|
camera_distance_ = std::max(0.01f, dist);
|
||||||
|
camera_yaw_deg_ = yaw_deg;
|
||||||
|
camera_pitch_deg_ = std::clamp(pitch_deg, -89.9f, 89.9f);
|
||||||
|
// Suppress the auto-viewAll on the first model load so the script-set
|
||||||
|
// camera survives. Manual viewAll() calls after this still work.
|
||||||
|
initial_view_applied_ = true;
|
||||||
|
if (isExposed()) requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
void WgpuViewportWindow::viewAll() {
|
void WgpuViewportWindow::viewAll() {
|
||||||
float mn[3], mx[3];
|
float mn[3], mx[3];
|
||||||
if (!computeSceneAabb(mn, mx)) return;
|
if (!computeSceneAabb(mn, mx)) return;
|
||||||
|
|||||||
@@ -72,10 +72,17 @@ public:
|
|||||||
size_t modelCount() const { return models_gpu_.size(); }
|
size_t modelCount() const { return models_gpu_.size(); }
|
||||||
|
|
||||||
// Frame the union of all loaded models' world AABBs. No-op on empty
|
// Frame the union of all loaded models' world AABBs. No-op on empty
|
||||||
// scenes. Called automatically after the first model loads; clients
|
// scenes. Called automatically after the first model loads (unless
|
||||||
// can re-invoke to re-frame.
|
// setCamera was already invoked); clients can re-invoke to re-frame.
|
||||||
void viewAll();
|
void viewAll();
|
||||||
|
|
||||||
|
// Explicit camera state, mirroring the GL ViewportWindow API. Suppresses
|
||||||
|
// the auto-viewAll on first load so a script-driven camera survives
|
||||||
|
// model loading. Parameters match the GL --camera tx,ty,tz,dist,yaw,pitch
|
||||||
|
// order so a pasted camera string lands the same view in both backends.
|
||||||
|
void setCamera(float tx, float ty, float tz,
|
||||||
|
float dist, float yaw_deg, float pitch_deg);
|
||||||
|
|
||||||
// Queue a one-shot framebuffer capture: the next rendered frame is
|
// Queue a one-shot framebuffer capture: the next rendered frame is
|
||||||
// copied back to host memory and saved to `path` as PNG. If
|
// copied back to host memory and saved to `path` as PNG. If
|
||||||
// `quit_after` is true, QCoreApplication::quit() is called once the
|
// `quit_after` is true, QCoreApplication::quit() is called once the
|
||||||
|
|||||||
Reference in New Issue
Block a user