2026-06-04 18:58:34 +10:00
|
|
|
/********************************************************************************
|
|
|
|
|
* *
|
|
|
|
|
* 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/>. *
|
|
|
|
|
* *
|
|
|
|
|
********************************************************************************/
|
|
|
|
|
|
2026-06-06 21:09:35 +10:00
|
|
|
// Web entry point. Wires a WebViewportHost to a ViewportCore, brings up
|
|
|
|
|
// wgpu through emdawnwebgpu (the spec-compatible WebGPU header set that
|
|
|
|
|
// shipped with Dawn), then drives a render() per requestAnimationFrame
|
|
|
|
|
// tick. No sidecar load yet — that lands with the emscripten_fetch
|
|
|
|
|
// streaming backend (#88). For this scaffold we render an empty scene
|
|
|
|
|
// with the configured background so init + present is verified
|
|
|
|
|
// end-to-end through the same ViewportCore code path the desktop build
|
|
|
|
|
// uses.
|
|
|
|
|
|
|
|
|
|
#include "ViewportCore.h"
|
|
|
|
|
#include "WebViewportHost.h"
|
|
|
|
|
#include "Log.h"
|
2026-06-04 18:58:34 +10:00
|
|
|
|
|
|
|
|
#include <emscripten/emscripten.h>
|
|
|
|
|
#include <emscripten/html5.h>
|
|
|
|
|
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2026-06-06 21:09:35 +10:00
|
|
|
// Shared by the main_loop trampoline + the cleanup path. Allocated on
|
|
|
|
|
// the heap so Emscripten's set_main_loop callback (which is C-style)
|
|
|
|
|
// can recover state through a void*.
|
|
|
|
|
struct AppState {
|
|
|
|
|
WebViewportHost host{ "#viewer-canvas" };
|
|
|
|
|
ViewportCore core{ &host };
|
|
|
|
|
int last_w = 0;
|
|
|
|
|
int last_h = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// The main_loop is a free function (Emscripten signature em_callback_func)
|
|
|
|
|
// so we can hand it directly to emscripten_set_main_loop_arg.
|
|
|
|
|
void main_loop(void* user) {
|
|
|
|
|
auto* app = static_cast<AppState*>(user);
|
|
|
|
|
|
|
|
|
|
// Reconfigure when the canvas resizes. The first tick also lands
|
|
|
|
|
// here because last_w / last_h start at 0.
|
|
|
|
|
int w = 0, h = 0;
|
|
|
|
|
app->host.framebufferSize(w, h);
|
|
|
|
|
if (w != app->last_w || h != app->last_h) {
|
|
|
|
|
app->core.configureSurface(w, h);
|
|
|
|
|
app->last_w = w;
|
|
|
|
|
app->last_h = h;
|
2026-06-04 18:58:34 +10:00
|
|
|
}
|
|
|
|
|
|
2026-06-06 21:28:27 +10:00
|
|
|
// Only render when something has requested a frame — CPU stays
|
|
|
|
|
// idle while the scene is static. WebViewportHost's ctor arms one
|
|
|
|
|
// initial frame request so the canvas always paints at startup;
|
|
|
|
|
// subsequent paints come from core_.render() rearming itself
|
|
|
|
|
// (motion settle, streaming in flight, bench mode) and, once
|
|
|
|
|
// input is wired (#85), from mouse / key events flagging the host.
|
2026-06-06 21:09:35 +10:00
|
|
|
if (app->host.consumeFrameRequest()) {
|
|
|
|
|
app->core.render();
|
2026-06-04 18:58:34 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 21:09:35 +10:00
|
|
|
} // namespace
|
2026-06-04 18:58:34 +10:00
|
|
|
|
2026-06-06 21:09:35 +10:00
|
|
|
int main(int /*argc*/, char** /*argv*/) {
|
|
|
|
|
Log::info() << "ifcviewer-web: starting";
|
2026-06-04 18:58:34 +10:00
|
|
|
|
2026-06-06 21:09:35 +10:00
|
|
|
auto* app = new AppState();
|
2026-06-04 18:58:34 +10:00
|
|
|
|
2026-06-06 21:09:35 +10:00
|
|
|
// Web limits floor: requestDevice the WebGPU spec's mandatory floor
|
|
|
|
|
// (maxStorageBufferBindingSize=128MB, maxBufferSize=256MB) so the
|
|
|
|
|
// chunking + pool probe see the same constraints they would hit in
|
|
|
|
|
// any browser. Desktop --web-limits did this opt-in; on web it's
|
|
|
|
|
// the only sensible default.
|
|
|
|
|
if (!app->core.initWgpu(/*web_limits=*/true)) {
|
|
|
|
|
std::fprintf(stderr, "[viewer-web] initWgpu failed\n");
|
|
|
|
|
delete app;
|
|
|
|
|
return 1;
|
2026-06-04 18:58:34 +10:00
|
|
|
}
|
2026-06-06 21:09:35 +10:00
|
|
|
if (!app->core.buildPipelines()) {
|
|
|
|
|
std::fprintf(stderr, "[viewer-web] buildPipelines failed\n");
|
|
|
|
|
delete app;
|
2026-06-04 18:58:34 +10:00
|
|
|
return 1;
|
|
|
|
|
}
|
2026-06-06 21:09:35 +10:00
|
|
|
// HiZ + edge + pick pipelines: built up-front to match the desktop
|
|
|
|
|
// path's lifetime. shutdown() in ViewportCore expects each
|
|
|
|
|
// resource to be either constructed or null, so building them all
|
|
|
|
|
// here keeps teardown symmetric.
|
|
|
|
|
app->core.buildHizPipeline();
|
|
|
|
|
app->core.buildEdgePipeline();
|
|
|
|
|
app->core.buildPickPipeline();
|
|
|
|
|
|
|
|
|
|
// fps = 0 means "use the browser's natural rate (RAF)" — Emscripten
|
|
|
|
|
// schedules the callback once per requestAnimationFrame tick.
|
|
|
|
|
// simulate_infinite_loop = false because we want main() to return
|
|
|
|
|
// so the runtime + JS event loop keep ticking. The AppState leaks
|
|
|
|
|
// on tab close, which is fine — emscripten_force_exit (called
|
|
|
|
|
// from host_->quit()) is the only formal shutdown path on web.
|
|
|
|
|
emscripten_set_main_loop_arg(main_loop, app, /*fps=*/0,
|
|
|
|
|
/*simulate_infinite_loop=*/0);
|
2026-06-04 18:58:34 +10:00
|
|
|
return 0;
|
|
|
|
|
}
|