///|
extern "js" fn webgpu_measure_text(
text : String,
family : String,
size : Double,
) -> Array[Double] =
#| (text, family, size) => {
#| const cvs = document.createElement('canvas');
#| const ctx = cvs.getContext('2d');
#| const fontSize = Number.isFinite(size) && size > 0 ? size : 16;
#| const fontFamily = family && family.length > 0 ? family : 'Arial';
#| ctx.font = `${fontSize}px ${fontFamily}`;
#| const m = ctx.measureText(text ?? '');
#| const width = Math.max(0, Number(m.width) || 0);
#| const ascent = Math.max(0, Number(m.actualBoundingBoxAscent) || fontSize * 0.8);
#| const descent = Math.max(0, Number(m.actualBoundingBoxDescent) || fontSize * 0.2);
#| const height = Math.max(1, Math.ceil(ascent + descent));
#| return [width, height];
#| }
///|
extern "js" fn webgpu_begin_frame(
clear_r : Double,
clear_g : Double,
clear_b : Double,
clear_a : Double,
) -> Unit =
#| (r, g, b, a) => {
#| const rt = globalThis.__selene_webgpu_runtime;
#| if (rt) {
#| rt.beginFrame(r, g, b, a);
#| }
#| }
///|
extern "js" fn webgpu_end_frame() -> Unit =
#| () => {
#| const rt = globalThis.__selene_webgpu_runtime;
#| if (rt) {
#| rt.endFrame();
#| }
#| }
///|
extern "js" fn webgpu_capture_frame_png_bytes() -> Array[Int] =
#| () => {
#| try {
#| const rt = globalThis.__selene_webgpu_runtime;
#| const canvas = rt?.canvas;
#| if (!canvas || !canvas.toDataURL) return [];
#| const dataUrl = canvas.toDataURL("image/png");
#| const comma = dataUrl.indexOf(",");
#| if (comma < 0) return [];
#| const base64 = dataUrl.slice(comma + 1);
#| const binary = atob(base64);
#| const out = new Array(binary.length);
#| for (let i = 0; i < binary.length; i += 1) {
#| out[i] = binary.charCodeAt(i) & 0xff;
#| }
#| return out;
#| } catch (_err) {
#| return [];
#| }
#| }
///|
extern "js" fn webgpu_preload_img(path : String) -> Unit =
#| (path) => {
#| const g = globalThis;
#| if (!g.__selene_webgpu_preload_images) {
#| g.__selene_webgpu_preload_images = new Set();
#| }
#| g.__selene_webgpu_preload_images.add(String(path));
#| const rt = g.__selene_webgpu_runtime;
#| if (rt) {
#| rt.ensureImage(path);
#| }
#| }
///|
extern "js" fn webgpu_render_runtime_ready() -> Bool =
#| () => {
#| const rt = globalThis.__selene_webgpu_runtime;
#| return !!(rt?.ready && rt?.device && rt?.context && rt?.canvas);
#| }
///|
extern "js" fn webgpu_resize_capture_surface(width : Int, height : Int) -> Unit =
#| (width, height) => {
#| const rt = globalThis.__selene_webgpu_runtime;
#| if (!rt?.canvas) return;
#| const w = Math.max(1, Math.floor(Number(width) || 1));
#| const h = Math.max(1, Math.floor(Number(height) || 1));
#| rt.canvas.width = w;
#| rt.canvas.height = h;
#| if (rt.ready && rt.context && rt.device && rt.format) {
#| rt.context.configure({
#| device: rt.device,
#| format: rt.format,
#| alphaMode: 'premultiplied',
#| });
#| rt.ensureDepth?.();
#| }
#| }
///|
extern "js" fn webgpu_render_pending_resource_count() -> Int =
#| () => {
#| const g = globalThis;
#| const rt = g.__selene_webgpu_runtime;
#| let pending = 0;
#| const preloads = g.__selene_webgpu_preload_images;
#| if (!rt && preloads?.size) {
#| pending += preloads.size;
#| }
#| if (rt?.imageCache) {
#| for (const rec of rt.imageCache.values()) {
#| if (rec?.state === 'loading') pending += 1;
#| }
#| for (const path of preloads ?? []) {
#| if (!rt.imageCache.has(path)) pending += 1;
#| }
#| }
#| const fontStates = g.__selene_font_bytes_states;
#| for (const rec of fontStates?.values?.() ?? []) {
#| if (rec?.status === 0) pending += 1;
#| }
#| return pending | 0;
#| }
///|
extern "js" fn webgpu_render_resource_errors() -> Array[String] =
#| () => {
#| const g = globalThis;
#| const rt = g.__selene_webgpu_runtime;
#| const errors = [];
#| if (rt?.imageCache) {
#| for (const [path, rec] of rt.imageCache.entries()) {
#| if (rec?.state === 'error') {
#| errors.push(`image:${String(path)}`);
#| }
#| }
#| }
#| const fontStates = g.__selene_font_bytes_states;
#| for (const [family, rec] of fontStates?.entries?.() ?? []) {
#| if (rec?.status === 2) {
#| errors.push(`font:${String(family)}`);
#| }
#| }
#| return errors;
#| }
///|
extern "js" fn webgpu_begin_2d_pass(
x : Double,
y : Double,
width : Double,
height : Double,
load_op : Int,
clear_r : Double,
clear_g : Double,
clear_b : Double,
clear_a : Double,
target_name : String,
) -> Unit =
#| (x, y, width, height, loadOp, clearR, clearG, clearB, clearA, targetName) => {
#| const rt = globalThis.__selene_webgpu_runtime;
#| if (rt) {
#| rt.beginPass2d(x, y, width, height, loadOp, [clearR, clearG, clearB, clearA], targetName);
#| }
#| }
///|
extern "js" fn webgpu_end_2d_pass() -> Unit =
#| () => {
#| const rt = globalThis.__selene_webgpu_runtime;
#| if (rt) {
#| rt.endPass2d();
#| }
#| }
///|
extern "js" fn webgpu_push_clip_rect(
x : Double,
y : Double,
width : Double,
height : Double,
) -> Unit =
#| (x, y, width, height) => {
#| const rt = globalThis.__selene_webgpu_runtime;
#| if (rt) {
#| rt.pushClip2d(x, y, width, height);
#| }
#| }