import { Application, Container } from 'pixi.js'; import { initPixelGrid, initRenderLoop, initUI, loadEditorFonts, Theme, tieBreakFor, VStack } from './ui'; // `desynchronized` bypasses the compositor queue (headless click-to-paint 72 → 16 ms) but may // tear; `?vsync` opts out. Pixi has no option for it, hence our own context. function lowLatencyContext(): { canvas?: HTMLCanvasElement; context?: WebGL2RenderingContext } { const canvas = document.createElement('canvas'); const context = canvas.getContext('webgl2', { alpha: false, antialias: true, premultipliedAlpha: true, stencil: true, desynchronized: true, }); return context ? { canvas, context } : {}; } /** * The one canvas a page is: the editor's and a released game's alike (#266). `root` fills the * window and follows it when it is resized. */ export async function createPage(params: URLSearchParams): Promise<{ app: Application; root: VStack }> { const fonts = loadEditorFonts(); const app = new Application(); await app.init({ resizeTo: window, background: Theme.colors.background, antialias: true, resolution: Math.min(2, window.devicePixelRatio || 1), autoDensity: true, preference: 'webgl', ...(params.has('vsync') ? {} : lowLatencyContext()), }); initPixelGrid(app.renderer.resolution); document.body.appendChild(app.canvas); await fonts; const root = new VStack(); const overlay = new Container(); app.stage.addChild(root, overlay); app.stage.position.set(tieBreakFor(app.renderer.resolution)); app.stage.eventMode = 'static'; app.stage.hitArea = app.screen; initUI(app, root, overlay); // From here on the picture is drawn when something changes, not sixty times a second. initRenderLoop(app); app.renderer.on('resize', () => { app.stage.hitArea = app.screen; root.resize(app.screen.width, app.screen.height); }); return { app, root }; } export function showFailure(error: unknown): void { console.error(error); document.body.innerHTML = `
${String(error)}`;
}