From 912665885b24241c05ddc597b157a331ca0b6b61 Mon Sep 17 00:00:00 2001 From: Stanislav Fifik Date: Mon, 28 Jan 2019 21:26:08 +0100 Subject: [PATCH] :zap: canvaspool and offscreen canvas --- src/canvas/CanvasPool.js | 42 ++++++++++++++++++++++++++ src/nodes/Canvas2d/Canvas2d.js | 54 ++++++++++++++++++++++++---------- src/nodes/Canvas2d/Compose.js | 2 +- src/nodes/Canvas2d/Fill.js | 2 +- src/nodes/Canvas2d/Resize.js | 2 +- src/nodes/Canvas2d/Text.js | 2 +- src/nodes/WebGL/WebGl.js | 16 ++++++++-- src/nodes/canvas.js | 1 + src/nodes/io/AbstractIO.js | 1 + 9 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 src/canvas/CanvasPool.js diff --git a/src/canvas/CanvasPool.js b/src/canvas/CanvasPool.js new file mode 100644 index 0000000..b850d39 --- /dev/null +++ b/src/canvas/CanvasPool.js @@ -0,0 +1,42 @@ + +class CanvasPool { + + constructor(ctx) { + this.__ctx = ctx; + this.__pool = []; + this.__pointerCounter = new WeakMap(); + } + + __createNewCanvas() { + const canvas = (this.__ctx !== 'webgl') ? new OffscreenCanvas(1,1) : document.createElement('canvas'); + canvas.acquire = this.acquireCanvas.bind(this, canvas); + canvas.release = this.releaseCanvas.bind(this, canvas); + canvas.getContext(this.__ctx); + this.__pool.push(canvas); + this.__pointerCounter.set(canvas, 0); + } + + createCanvas() { + if (this.__pool.length === 0) { + this.__createNewCanvas(); + } + return this.__pool.pop(); + } + + acquireCanvas(canvas) { + const inUse = this.__pointerCounter.get(canvas) + 1; + this.__pointerCounter.set(canvas, inUse); + } + + releaseCanvas(canvas) { + const inUse = this.__pointerCounter.get(canvas) - 1; + this.__pointerCounter.set(canvas, inUse); + if (inUse === 0) { + this.__pool.push(canvas); + } + } + +} + +export const canvasPool2D = new CanvasPool('2d'); +export const canvasPoolWebGL = new CanvasPool('webgl'); diff --git a/src/nodes/Canvas2d/Canvas2d.js b/src/nodes/Canvas2d/Canvas2d.js index 7d5b770..bae1de1 100644 --- a/src/nodes/Canvas2d/Canvas2d.js +++ b/src/nodes/Canvas2d/Canvas2d.js @@ -1,5 +1,6 @@ import Node from '../Node'; import {waitForMedia} from '../../utils'; +import {canvasPool2D} from '../../canvas/CanvasPool'; export default class Canvas2d extends Node { constructor(name, inputDefinition, outputDefiniton, options) { @@ -22,11 +23,9 @@ export default class Canvas2d extends Node { }, outputDefiniton), options ); - this.__canvas = document.createElement('canvas'); } destroy() { - this.__canvas = null; super.destroy(); } @@ -35,19 +34,44 @@ export default class Canvas2d extends Node { for (let name of Object.keys(this.in.variables)) { values[name] = await waitForMedia(this.in[name].value); } - const ctx = this.__canvas.getContext('2d'); - if (this.__canvas.width > 0 && this.__canvas.height > 0) { - ctx.clearRect(0,0,this.__canvas.width, this.__canvas.height); + + const canvas = canvasPool2D.createCanvas(); + canvas.acquire(); + const ctx = canvas.getContext('2d'); + canvas.width = 1; + canvas.height = 1; + if (values.image && values.image.acquire) { + values.image.acquire(); } - const result = await this.render(values, this.__canvas, ctx); - if (result instanceof ImageBitmap) { - this.__out.image.value = result; - if (this.out.width.value !== this.out.image.value.width) { - this.out.width.value = this.out.image.value.width; - } - if (this.out.height.value !== this.out.image.value.height) { - this.out.height.value = this.out.image.value.height; - } + + const lastResult = this.__out.image.value; + try { + const result = await this.render(values, canvas, ctx); + this.out.image.value = result; + this.__updateOutputDimensions(); + } catch (err) { + console.error('Error in render', err); + canvas.release(); + } + + if (values.image && values.image.release) { + values.image.release(); + } + + if (lastResult && lastResult.release) { + lastResult.release(); + } + } + + __updateOutputDimensions() { + if (!this.out.image.value) { + return; + } + if (this.out.width.value !== this.out.image.value.width) { + this.out.width.value = this.out.image.value.width; + } + if (this.out.height.value !== this.out.image.value.height) { + this.out.height.value = this.out.image.value.height; } } @@ -57,7 +81,7 @@ export default class Canvas2d extends Node { * @param {any} values * @param {HTMLCanvasElement} canvas * @param {CanvasRenderingContext2D} ctx - * @returns {Promise} + * @returns {Promise} */ async render(values, canvas, ctx) { return null; diff --git a/src/nodes/Canvas2d/Compose.js b/src/nodes/Canvas2d/Compose.js index cee511e..28ac6ea 100644 --- a/src/nodes/Canvas2d/Compose.js +++ b/src/nodes/Canvas2d/Compose.js @@ -145,6 +145,6 @@ export default class Compose extends Canvas2d { paintToCanvas(canvas, this.fg.image, this.fg); } - return await createImageBitmap(canvas); + return canvas; } } \ No newline at end of file diff --git a/src/nodes/Canvas2d/Fill.js b/src/nodes/Canvas2d/Fill.js index 97b3cea..c9bf5e3 100644 --- a/src/nodes/Canvas2d/Fill.js +++ b/src/nodes/Canvas2d/Fill.js @@ -28,6 +28,6 @@ export default class Fill extends Canvas2d { canvas.height = height; ctx.fillStyle = color; ctx.fillRect(0, 0, width, height); - return await createImageBitmap(canvas); + return canvas; } } \ No newline at end of file diff --git a/src/nodes/Canvas2d/Resize.js b/src/nodes/Canvas2d/Resize.js index 4984e57..799a396 100644 --- a/src/nodes/Canvas2d/Resize.js +++ b/src/nodes/Canvas2d/Resize.js @@ -54,6 +54,6 @@ export default class Resize extends Canvas2d { newImage.left = (destWidth - newImage.width) / 2; paintToCanvas(canvas, media, newImage); - return await createImageBitmap(canvas); + return canvas; } } \ No newline at end of file diff --git a/src/nodes/Canvas2d/Text.js b/src/nodes/Canvas2d/Text.js index 3e3766a..7b63098 100644 --- a/src/nodes/Canvas2d/Text.js +++ b/src/nodes/Canvas2d/Text.js @@ -68,6 +68,6 @@ export default class Resize extends Canvas2d { text.split('\\n').forEach((line, index) => { ctx.fillText(line, x, fontSize * (1 + index)); }); - return await createImageBitmap(canvas); + return canvas; } } \ No newline at end of file diff --git a/src/nodes/WebGL/WebGl.js b/src/nodes/WebGL/WebGl.js index f5c1e12..ac9f076 100644 --- a/src/nodes/WebGL/WebGl.js +++ b/src/nodes/WebGL/WebGl.js @@ -1,5 +1,6 @@ import Node from '../Node'; import {createCanvas, mediaSize, paintToCanvas} from '../canvas'; +import {canvasPoolWebGL} from '../../canvas/CanvasPool'; export default class WebGL extends Node { @@ -138,8 +139,8 @@ export default class WebGL extends Node { setup() { if (!this.canvas) { + this.canvas = new OffscreenCanvas(1,1); this.program = null; - this.canvas = createCanvas(0, 0); } const gl = this.canvas.getContext('webgl'); if (!this.program) { @@ -150,8 +151,12 @@ export default class WebGL extends Node { } async _update() { + const image = this.in.image.value; if (!image) return; + if (image.acquire) { + image.acquire(); + } const {width, height} = mediaSize(image); if (width === 0 || height === 0) { return; @@ -229,7 +234,14 @@ export default class WebGL extends Node { // Draw the rectangle. gl.drawArrays(gl.TRIANGLES, 0, 6); - const result = await createImageBitmap(canvas); + const result = canvas.transferToImageBitmap(); + // this.canvas = null; + if (image.release) { + image.release(); + } + if (this.out.image.value && this.out.image.value.release) { + this.out.image.value.release(); + } this.out.image.value = result; if (this.out.width.value !== this.out.image.value.width) { this.out.width.value = this.out.image.value.width; diff --git a/src/nodes/canvas.js b/src/nodes/canvas.js index 56f8d5e..435a9d5 100644 --- a/src/nodes/canvas.js +++ b/src/nodes/canvas.js @@ -25,6 +25,7 @@ export const createCanvas = (width, height) => { * @returns {{width: Number, height: Number}} */ export const mediaSize = (media) => + (media === null) ? {width: null, height: null} : (media instanceof HTMLVideoElement) ? {width: media.videoWidth, height: media.videoHeight} : (media instanceof HTMLImageElement) ? {width: media.naturalWidth, height: media.naturalHeight} : {width: media.width, height: media.height}; diff --git a/src/nodes/io/AbstractIO.js b/src/nodes/io/AbstractIO.js index 9ae0244..313a9f7 100644 --- a/src/nodes/io/AbstractIO.js +++ b/src/nodes/io/AbstractIO.js @@ -6,6 +6,7 @@ export const defaultConstrainSatisfied = (value) => { export const imageConstrainSatisfied = (value) => { return value instanceof HTMLCanvasElement || + value instanceof OffscreenCanvas || value instanceof Image || value instanceof HTMLVideoElement || value instanceof ImageData ||