⚡ canvaspool and offscreen canvas
This commit is contained in:
42
src/canvas/CanvasPool.js
Normal file
42
src/canvas/CanvasPool.js
Normal file
@@ -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');
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import Node from '../Node';
|
import Node from '../Node';
|
||||||
import {waitForMedia} from '../../utils';
|
import {waitForMedia} from '../../utils';
|
||||||
|
import {canvasPool2D} from '../../canvas/CanvasPool';
|
||||||
|
|
||||||
export default class Canvas2d extends Node {
|
export default class Canvas2d extends Node {
|
||||||
constructor(name, inputDefinition, outputDefiniton, options) {
|
constructor(name, inputDefinition, outputDefiniton, options) {
|
||||||
@@ -22,11 +23,9 @@ export default class Canvas2d extends Node {
|
|||||||
}, outputDefiniton),
|
}, outputDefiniton),
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
this.__canvas = document.createElement('canvas');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.__canvas = null;
|
|
||||||
super.destroy();
|
super.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,13 +34,39 @@ export default class Canvas2d extends Node {
|
|||||||
for (let name of Object.keys(this.in.variables)) {
|
for (let name of Object.keys(this.in.variables)) {
|
||||||
values[name] = await waitForMedia(this.in[name].value);
|
values[name] = await waitForMedia(this.in[name].value);
|
||||||
}
|
}
|
||||||
const ctx = this.__canvas.getContext('2d');
|
|
||||||
if (this.__canvas.width > 0 && this.__canvas.height > 0) {
|
const canvas = canvasPool2D.createCanvas();
|
||||||
ctx.clearRect(0,0,this.__canvas.width, this.__canvas.height);
|
canvas.acquire();
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = 1;
|
||||||
|
canvas.height = 1;
|
||||||
|
if (values.image && values.image.acquire) {
|
||||||
|
values.image.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
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) {
|
if (this.out.width.value !== this.out.image.value.width) {
|
||||||
this.out.width.value = this.out.image.value.width;
|
this.out.width.value = this.out.image.value.width;
|
||||||
}
|
}
|
||||||
@@ -49,7 +74,6 @@ export default class Canvas2d extends Node {
|
|||||||
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 {any} values
|
||||||
* @param {HTMLCanvasElement} canvas
|
* @param {HTMLCanvasElement} canvas
|
||||||
* @param {CanvasRenderingContext2D} ctx
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
* @returns {Promise<ImageBitmap|null>}
|
* @returns {Promise<HTMLCanvasElement|null>}
|
||||||
*/
|
*/
|
||||||
async render(values, canvas, ctx) {
|
async render(values, canvas, ctx) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -145,6 +145,6 @@ export default class Compose extends Canvas2d {
|
|||||||
paintToCanvas(canvas, this.fg.image, this.fg);
|
paintToCanvas(canvas, this.fg.image, this.fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await createImageBitmap(canvas);
|
return canvas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,6 @@ export default class Fill extends Canvas2d {
|
|||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
ctx.fillStyle = color;
|
ctx.fillStyle = color;
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
return await createImageBitmap(canvas);
|
return canvas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,6 @@ export default class Resize extends Canvas2d {
|
|||||||
newImage.left = (destWidth - newImage.width) / 2;
|
newImage.left = (destWidth - newImage.width) / 2;
|
||||||
|
|
||||||
paintToCanvas(canvas, media, newImage);
|
paintToCanvas(canvas, media, newImage);
|
||||||
return await createImageBitmap(canvas);
|
return canvas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,6 +68,6 @@ export default class Resize extends Canvas2d {
|
|||||||
text.split('\\n').forEach((line, index) => {
|
text.split('\\n').forEach((line, index) => {
|
||||||
ctx.fillText(line, x, fontSize * (1 + index));
|
ctx.fillText(line, x, fontSize * (1 + index));
|
||||||
});
|
});
|
||||||
return await createImageBitmap(canvas);
|
return canvas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import Node from '../Node';
|
import Node from '../Node';
|
||||||
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
import {createCanvas, mediaSize, paintToCanvas} from '../canvas';
|
||||||
|
import {canvasPoolWebGL} from '../../canvas/CanvasPool';
|
||||||
|
|
||||||
export default class WebGL extends Node {
|
export default class WebGL extends Node {
|
||||||
|
|
||||||
@@ -138,8 +139,8 @@ export default class WebGL extends Node {
|
|||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
if (!this.canvas) {
|
if (!this.canvas) {
|
||||||
|
this.canvas = new OffscreenCanvas(1,1);
|
||||||
this.program = null;
|
this.program = null;
|
||||||
this.canvas = createCanvas(0, 0);
|
|
||||||
}
|
}
|
||||||
const gl = this.canvas.getContext('webgl');
|
const gl = this.canvas.getContext('webgl');
|
||||||
if (!this.program) {
|
if (!this.program) {
|
||||||
@@ -150,8 +151,12 @@ export default class WebGL extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _update() {
|
async _update() {
|
||||||
|
|
||||||
const image = this.in.image.value;
|
const image = this.in.image.value;
|
||||||
if (!image) return;
|
if (!image) return;
|
||||||
|
if (image.acquire) {
|
||||||
|
image.acquire();
|
||||||
|
}
|
||||||
const {width, height} = mediaSize(image);
|
const {width, height} = mediaSize(image);
|
||||||
if (width === 0 || height === 0) {
|
if (width === 0 || height === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -229,7 +234,14 @@ export default class WebGL extends Node {
|
|||||||
// Draw the rectangle.
|
// Draw the rectangle.
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
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;
|
this.out.image.value = result;
|
||||||
if (this.out.width.value !== this.out.image.value.width) {
|
if (this.out.width.value !== this.out.image.value.width) {
|
||||||
this.out.width.value = this.out.image.value.width;
|
this.out.width.value = this.out.image.value.width;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const createCanvas = (width, height) => {
|
|||||||
* @returns {{width: Number, height: Number}}
|
* @returns {{width: Number, height: Number}}
|
||||||
*/
|
*/
|
||||||
export const mediaSize = (media) =>
|
export const mediaSize = (media) =>
|
||||||
|
(media === null) ? {width: null, height: null} :
|
||||||
(media instanceof HTMLVideoElement) ? {width: media.videoWidth, height: media.videoHeight} :
|
(media instanceof HTMLVideoElement) ? {width: media.videoWidth, height: media.videoHeight} :
|
||||||
(media instanceof HTMLImageElement) ? {width: media.naturalWidth, height: media.naturalHeight} :
|
(media instanceof HTMLImageElement) ? {width: media.naturalWidth, height: media.naturalHeight} :
|
||||||
{width: media.width, height: media.height};
|
{width: media.width, height: media.height};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const defaultConstrainSatisfied = (value) => {
|
|||||||
|
|
||||||
export const imageConstrainSatisfied = (value) => {
|
export const imageConstrainSatisfied = (value) => {
|
||||||
return value instanceof HTMLCanvasElement ||
|
return value instanceof HTMLCanvasElement ||
|
||||||
|
value instanceof OffscreenCanvas ||
|
||||||
value instanceof Image ||
|
value instanceof Image ||
|
||||||
value instanceof HTMLVideoElement ||
|
value instanceof HTMLVideoElement ||
|
||||||
value instanceof ImageData ||
|
value instanceof ImageData ||
|
||||||
|
|||||||
Reference in New Issue
Block a user