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
|
2026-06-09 17:16:14 +10:00
|
|
|
// wgpu via emdawnwebgpu (the spec-compatible WebGPU header set that
|
|
|
|
|
// shipped with Dawn), loads the embedded sample sidecar, and drives
|
|
|
|
|
// render() per requestAnimationFrame from JS (shell.html).
|
|
|
|
|
//
|
|
|
|
|
// The RAF loop lives in shell.html — NOT here — because any call into
|
|
|
|
|
// Emscripten's main-loop / RAF helpers (or even raw
|
|
|
|
|
// requestAnimationFrame via EM_ASM) made from inside Dawn-web's wgpu
|
|
|
|
|
// promise-resolution chain stalls the device callback. Having JS drive
|
|
|
|
|
// the tick keeps the wasm init path callback-only.
|
2026-06-06 21:09:35 +10:00
|
|
|
|
|
|
|
|
#include "ViewportCore.h"
|
|
|
|
|
#include "WebViewportHost.h"
|
|
|
|
|
#include "Log.h"
|
2026-06-04 18:58:34 +10:00
|
|
|
|
|
|
|
|
#include <emscripten/emscripten.h>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2026-06-06 21:09:35 +10:00
|
|
|
struct AppState {
|
|
|
|
|
WebViewportHost host{ "#viewer-canvas" };
|
|
|
|
|
ViewportCore core{ &host };
|
|
|
|
|
int last_w = 0;
|
|
|
|
|
int last_h = 0;
|
2026-06-09 17:16:14 +10:00
|
|
|
// Set true by the init callback once the device + pipelines are up.
|
|
|
|
|
// raf_tick_c skips render() until then; before that point the wgpu
|
|
|
|
|
// state pointers inside core are still null and any draw would crash.
|
|
|
|
|
bool ready = false;
|
2026-06-06 21:09:35 +10:00
|
|
|
};
|
|
|
|
|
|
2026-06-09 17:16:14 +10:00
|
|
|
// One global so the JS-side RAF loop can recover state through a
|
|
|
|
|
// pointer round-trip (set into Module._app_ptr from on_complete).
|
|
|
|
|
AppState* g_app = nullptr;
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
// Called from shell.html's RAF tick (via Module._raf_tick_c). Exported
|
|
|
|
|
// to JS by EXPORTED_FUNCTIONS in CMakeLists.txt; EMSCRIPTEN_KEEPALIVE
|
|
|
|
|
// also keeps the symbol alive under -O*.
|
|
|
|
|
extern "C" EMSCRIPTEN_KEEPALIVE void raf_tick_c(void* user) {
|
2026-06-06 21:09:35 +10:00
|
|
|
auto* app = static_cast<AppState*>(user);
|
2026-06-09 17:16:14 +10:00
|
|
|
if (!app->ready) return;
|
2026-06-06 21:09:35 +10:00
|
|
|
|
|
|
|
|
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: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
|
|
|
int main(int /*argc*/, char** /*argv*/) {
|
|
|
|
|
Log::info() << "ifcviewer-web: starting";
|
2026-06-09 17:16:14 +10:00
|
|
|
g_app = new AppState();
|
|
|
|
|
g_app->core.initWgpuAsyncWeb([](bool ok) {
|
|
|
|
|
if (!ok) {
|
|
|
|
|
Log::warn() << "ifcviewer-web: wgpu init failed";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!g_app->core.buildPipelines()) {
|
|
|
|
|
Log::warn() << "ifcviewer-web: buildPipelines failed";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// HiZ + edge + pick pipelines: built up-front to match the
|
|
|
|
|
// desktop path. ViewportCore::shutdown expects each resource
|
|
|
|
|
// to be either constructed or null, so building them all here
|
|
|
|
|
// keeps teardown symmetric.
|
|
|
|
|
g_app->core.buildHizPipeline();
|
|
|
|
|
g_app->core.buildEdgePipeline();
|
|
|
|
|
g_app->core.buildPickPipeline();
|
2026-06-04 18:58:34 +10:00
|
|
|
|
2026-06-09 17:16:14 +10:00
|
|
|
// Load the embedded sample sidecar (mounted into MEMFS via
|
|
|
|
|
// --embed-file in CMakeLists.txt). Replaced by an
|
|
|
|
|
// emscripten_fetch + Range backend in #88.
|
|
|
|
|
if (!g_app->core.loadSidecarFromPath("/sample.ifcview")) {
|
|
|
|
|
Log::warn() << "ifcviewer-web: sample sidecar load failed";
|
|
|
|
|
}
|
2026-06-04 18:58:34 +10:00
|
|
|
|
2026-06-09 17:16:14 +10:00
|
|
|
g_app->ready = true;
|
2026-06-06 21:09:35 +10:00
|
|
|
|
2026-06-09 17:16:14 +10:00
|
|
|
// Hand the app pointer to the JS-side RAF loop (set up in
|
|
|
|
|
// shell.html's onRuntimeInitialized). The loop polls for
|
|
|
|
|
// Module._app_ptr before invoking _raf_tick_c.
|
|
|
|
|
EM_ASM({ Module._app_ptr = $0; }, (void*)g_app);
|
|
|
|
|
});
|
2026-06-04 18:58:34 +10:00
|
|
|
return 0;
|
|
|
|
|
}
|