skia2/modules/canvaskit/cpu.js
Kevin Lubick f611404816 [canvaskit] Remove the need for users to keep track of contexts.
We'll switch to the correct context when necessary (e.g. before
calls that talk to the GPU). This is achieved by adding in
calls at the JS layer to switch the context before making a call
that is known to talk to the GPU (e.g. draw calls on SkCanvas).

Another implementation that was considered was to add a C++
shim in GrGLInterface that would switch the context before
every call in the GPU - however, that seemed too difficult
and would add extra overhead if a single draw* call talks
to the GPU multiple times.

Bug: skia:12255
Change-Id: I96e4c6b41a5bfcc9913aeaca7ccb125358048ad3
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/432136
Reviewed-by: Brian Salomon <bsalomon@google.com>
2021-08-25 18:23:06 +00:00

109 lines
4.6 KiB
JavaScript

// Adds compile-time JS functions to augment the CanvasKit interface.
// Implementations in this file are considerate of GPU builds, i.e. some
// behavior is predicated on whether or not this is being compiled alongside
// gpu.js.
(function(CanvasKit){
CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
CanvasKit._extraInitializations.push(function() {
// Takes in an html id or a canvas element
CanvasKit.MakeSWCanvasSurface = function(idOrElement) {
var canvas = idOrElement;
if (canvas.tagName !== 'CANVAS') {
// TODO(nifong): unit test
canvas = document.getElementById(idOrElement);
if (!canvas) {
throw 'Canvas with id ' + idOrElement + ' was not found';
}
}
// Maybe better to use clientWidth/height. See:
// https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
var surface = CanvasKit.MakeSurface(canvas.width, canvas.height);
if (surface) {
surface._canvas = canvas;
}
return surface;
};
// Don't over-write the MakeCanvasSurface set by gpu.js if it exists.
if (!CanvasKit.MakeCanvasSurface) {
CanvasKit.MakeCanvasSurface = CanvasKit.MakeSWCanvasSurface;
}
// Note that color spaces are currently not supported in CPU surfaces. due to the limitation
// canvas.getContext('2d').putImageData imposes a limitation of using an RGBA_8888 color type.
// TODO(nifong): support WGC color spaces while still using an RGBA_8888 color type when
// on a cpu backend.
CanvasKit.MakeSurface = function(width, height) {
var imageInfo = {
'width': width,
'height': height,
'colorType': CanvasKit.ColorType.RGBA_8888,
// Since we are sending these pixels directly into the HTML canvas,
// (and those pixels are un-premultiplied, i.e. straight r,g,b,a)
'alphaType': CanvasKit.AlphaType.Unpremul,
'colorSpace': CanvasKit.ColorSpace.SRGB,
};
var pixelLen = width * height * 4; // it's 8888, so 4 bytes per pixel
// Allocate the buffer of pixels to be drawn into.
var pixelPtr = CanvasKit._malloc(pixelLen);
// Experiments with using RasterDirect vs Raster showed a 10% slowdown
// over the traditional Surface::MakeRaster approach. This was exacerbated when
// the surface was drawing to Premul and we had to convert to Unpremul each frame
// (up to a 10x further slowdown).
var surface = CanvasKit.Surface._makeRasterDirect(imageInfo, pixelPtr, width*4);
if (surface) {
surface._canvas = null;
surface._width = width;
surface._height = height;
surface._pixelLen = pixelLen;
surface._pixelPtr = pixelPtr;
// rasterDirectSurface does not initialize the pixels, so we clear them
// to transparent black.
surface.getCanvas().clear(CanvasKit.TRANSPARENT);
}
return surface;
};
CanvasKit.MakeRasterDirectSurface = function(imageInfo, mallocObj, bytesPerRow) {
return CanvasKit.Surface._makeRasterDirect(imageInfo, mallocObj['byteOffset'], bytesPerRow);
};
// For GPU builds, simply proxies to native code flush. For CPU builds,
// also updates the underlying HTML canvas, optionally with dirtyRect.
CanvasKit.Surface.prototype.flush = function(dirtyRect) {
CanvasKit.setCurrentContext(this._context);
this._flush();
// Do we have an HTML canvas to write the pixels to?
// We will not have a canvas if this a GPU build, for example.
if (this._canvas) {
var pixels = new Uint8ClampedArray(CanvasKit.HEAPU8.buffer, this._pixelPtr, this._pixelLen);
var imageData = new ImageData(pixels, this._width, this._height);
if (!dirtyRect) {
this._canvas.getContext('2d').putImageData(imageData, 0, 0);
} else {
this._canvas.getContext('2d').putImageData(imageData, 0, 0,
dirtyRect[0], dirtyRect[1],
dirtyRect[2] - dirtyRect[0],
dirtyRect[3] - dirtyRect[1]);
}
}
};
// Call dispose() instead of delete to clean up the underlying memory.
// TODO(kjlubick) get rid of this and just wrap around delete().
CanvasKit.Surface.prototype.dispose = function() {
if (this._pixelPtr) {
CanvasKit._free(this._pixelPtr);
}
this.delete();
};
CanvasKit.setCurrentContext = CanvasKit.setCurrentContext || function() {
// no op if this is a cpu-only build.
};
});
}(Module)); // When this file is loaded in, the high level object is "Module";