webgpu support

This commit is contained in:
Jorijn van der Graaf 2026-05-18 04:58:52 +02:00
commit dedf6b0467
22 changed files with 1656 additions and 324 deletions

View file

@ -69,6 +69,10 @@ import :Gamepad;
import :VulkanTransition;
import :DescriptorHeapVulkan;
import :RenderPass;
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
import :WebGPU;
import :DescriptorHeapWebGPU;
#endif
import std;
using namespace Crafter;
@ -1323,23 +1327,22 @@ Window::Window(std::uint32_t w, std::uint32_t h, const std::string_view title)
Window::Window(std::uint32_t w, std::uint32_t h) : width(w), height(h) {
if (g_domWindow != nullptr) {
// Only one Window per page in V1. Subsequent constructions are
// a programming error — log loudly and clobber the previous
// pointer so the new Window's events at least fire.
// (stderr isn't reachable via `import std;` on wasi-sdk yet; just log
// to cout. The browser console pipes both to the same place.)
std::println("Crafter::Window: only one DOM Window per page; "
"overwriting the previous instance.");
}
g_domWindow = this;
// Use the browser-reported viewport size as the initial dimensions
// unless the caller asked for something specific. Browser owns the
// real size; w/h passed in are advisory.
if (w == 0 || h == 0) {
width = static_cast<std::uint32_t>(Crafter::DomEnv::domGetInnerWidth());
height = static_cast<std::uint32_t>(Crafter::DomEnv::domGetInnerHeight());
}
// Browser owns the real surface size. The width/height passed in are
// advisory only — useful as a native-side hint, ignored on DOM. We
// always sync to innerWidth/innerHeight so:
// - window.width/.height match the canvas's CSS pixel size,
// - MouseEvent.clientX/.clientY (CSS pixels) compare correctly
// against any layout done with window.width/.height,
// - the dispatch group count from window.width/8 covers the
// canvas exactly.
(void)w; (void)h;
width = static_cast<std::uint32_t>(Crafter::DomEnv::domGetInnerWidth());
height = static_cast<std::uint32_t>(Crafter::DomEnv::domGetInnerHeight());
// The handle passed to attach is just a non-zero token the JS side
// includes back in every dispatcher call. We don't use it on the
@ -1394,10 +1397,18 @@ void Window::SetDefaultCursor() {
}
void Window::StartSync() {
// Hand the loop to rAF. Returns immediately; the wasm `_start`
// (main) finishes, and the runtime keeps the module alive while
// the JS-side rAF chain ticks `__crafterDom_frame`.
// Hand the loop to rAF, then exit the wasm via _Exit so wasi-libc
// skips __wasm_call_dtors. If we let main return normally, _start
// calls __wasm_call_dtors → static destructors fire (including
// Window's own), then __wasi_proc_exit → wasm trap. Subsequent rAF
// calls into the wasm would then trap too, killing rendering.
// _Exit jumps straight to __wasi_proc_exit, which our runtime.js
// catches via a thrown sentinel so the instance stays alive while
// every static-allocated object (Window, UIRenderer, GPU buffers,
// event listeners) remains untouched. Callers' code after
// StartSync() never runs — match that contract on native too.
Crafter::DomEnv::domStartFrameLoop();
std::_Exit(0);
}
void Window::StartUpdate() {
@ -1421,10 +1432,25 @@ void Window::Update() {
}
void Window::Render() {
// V1: no rendering in DOM mode. Kept as a callable no-op so
// existing cross-platform code paths (e.g. main loops calling
// window.Render() before window.StartSync()) compile. V2 will
// hang the WebGPU command-submit here.
if (!open) return;
Crafter::WebGPU::wgpuFrameBegin();
for (RenderPass* p : passes) {
if (p) p->Record(/*cmd*/ 0u, currentBuffer, *this);
}
Crafter::WebGPU::wgpuFrameEnd();
}
WebGPUCommandEncoderRef Window::StartInit() {
// DOM init: no command buffer needed — texture / buffer creation goes
// through synchronous wgpu* imports. Return 0 as a placeholder; the
// value is opaque to user code (auto-typed in HelloUI).
Crafter::WebGPU::wgpuInit();
return 0;
}
void Window::FinishInit() {
// Nothing to submit in DOM mode; all init writes are queued at call
// time via queue.writeBuffer / writeTexture.
}
// ─── C exports the JS bridge calls back into ──────────────────────────
@ -1437,6 +1463,7 @@ extern "C" {
g_domWindow->onBeforeUpdate.Invoke();
if (g_domWindow->updating) {
g_domWindow->Update();
g_domWindow->Render();
}
}