UNPKG

@soapbox.pub/wasmboy

Version:

Soapbox fork of Wasmboy.

150 lines (122 loc) 4.93 kB
// Handles rendering graphics using the HTML5 Canvas import { WasmBoyPlugins } from '../plugins/plugins'; import { GAMEBOY_CAMERA_WIDTH, GAMEBOY_CAMERA_HEIGHT } from './constants'; import { WORKER_MESSAGE_TYPE } from '../worker/constants'; import { getEventData } from '../worker/util'; // Performance tips with canvas: // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas class WasmBoyGraphicsService { constructor() { this.worker = undefined; this.updateGraphicsCallback = undefined; this.frameQueue = undefined; this.frameQueueRenderPromise = undefined; this.canvasElement = undefined; this.canvasContext = undefined; this.canvasImageData = undefined; this.imageDataArray = undefined; this.imageDataArrayChanged = false; } initialize(canvasElement, updateGraphicsCallback) { this.updateGraphicsCallback = updateGraphicsCallback; // Initialiuze our cached wasm constants // WASMBOY_CURRENT_FRAME_OUTPUT_LOCATION = this.wasmInstance.exports.frameInProgressGRAPHICS_OUTPUT_LOCATION.valueOf(); // Reset our frame queue and render promises this.frameQueue = []; const initializeTask = async () => { // Prepare our canvas this.canvasElement = canvasElement; this.canvasContext = this.canvasElement.getContext('2d'); this.canvasElement.width = GAMEBOY_CAMERA_WIDTH; this.canvasElement.height = GAMEBOY_CAMERA_HEIGHT; this.canvasImageData = this.canvasContext.createImageData(this.canvasElement.width, this.canvasElement.height); // Add some css for smooth 8-bit canvas scaling // https://stackoverflow.com/questions/7615009/disable-interpolation-when-scaling-a-canvas // https://caniuse.com/#feat=css-crisp-edges this.canvasElement.style = ` image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -webkit-optimize-contrast; image-rendering: -o-crisp-edges; image-rendering: pixelated; -ms-interpolation-mode: nearest-neighbor; `; // Fill the canvas with a blank screen // using client width since we are not requiring a width and height oin the canvas // https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth // TODO: Mention respopnsive canvas scaling in the docs this.canvasContext.clearRect(0, 0, this.canvasElement.width, this.canvasElement.height); // Doing set canvas here, as multiple sources can re-initialize the graphics // TODO: Move setCanvas out of initialize :p WasmBoyPlugins.runHook({ key: 'canvas', params: [this.canvasElement, this.canvasContext, this.canvasImageData], callback: response => { if (!response) { return; } if (response.canvasElement) { this.canvasElement = response.canvasElement; } if (response.canvasContext) { this.canvasContext = response.canvasContext; } if (response.canvasImageData) { this.canvasImageData = response.canvasImageData; } } }); // Finally make sure we set our constants for our worker if (this.worker) { await this.worker.postMessage({ type: WORKER_MESSAGE_TYPE.GET_CONSTANTS }); } }; return initializeTask(); } // Function to set our worker setWorker(worker) { this.worker = worker; this.worker.addMessageListener(event => { const eventData = getEventData(event); switch (eventData.message.type) { case WORKER_MESSAGE_TYPE.UPDATED: { this.imageDataArray = new Uint8ClampedArray(eventData.message.imageDataArrayBuffer); this.imageDataArrayChanged = true; return; } } }); } // Function to render a frame // Will add the frame to the frame queue to be rendered // Returns the promise from this.drawFrameQueue // Which resolves once all frames are rendered renderFrame() { // Check if we have new graphics to show if (!this.imageDataArrayChanged) { return; } this.imageDataArrayChanged = false; // Check for a callback for accessing image data if (this.updateGraphicsCallback) { this.updateGraphicsCallback(this.imageDataArray); } // Set the imageDataArray to our plugins WasmBoyPlugins.runHook({ key: 'graphics', params: [this.imageDataArray], callback: response => { if (response) { this.imageDataArray = response; } } }); // Add our new imageData this.canvasImageData.data.set(this.imageDataArray); this.canvasContext.clearRect(0, 0, this.canvasElement.width, this.canvasElement.height); this.canvasContext.putImageData(this.canvasImageData, 0, 0); } } export const WasmBoyGraphics = new WasmBoyGraphicsService();