[canvaskit] Expose more options for working with Surfaces and Contexts

PS1 is mostly from luigi@2dimensions.com, with slight re-naming
tweaks (thanks!).

With the exposition of these extra methods, MakeWebGLCanvasSurface
is now really just a convenience function.

Bug: skia:
Change-Id: Ie61657bc580146e3e759fae8620e4df0c0212f62
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/198720
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
Kevin Lubick 2019-03-08 10:04:28 -05:00
parent a720d76470
commit 543f352ace
6 changed files with 188 additions and 66 deletions

View File

@ -12,6 +12,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `SkCanvas.saveLayer(rect, paint)`
- `SkCanvas.restoreToCount(int)` which can be used with the output of .save() and .saveLayer().
- Optional particles library from modules/particles. `See CanvasKit.MakeParticles(json)`;
- More public APIs for working with Surfaces/Contexts `GetWebGLContext`,
`MakeGrContext`, `MakeOnScreenGLSurface`, `MakeRenderTarget`.
- `SkSurface.getSurface()` and `SkCanvas.getSurface()` for making compatible surfaces (typically
used as a workspace and then "saved" with `surface.makeImageSnapshot()`)
### Breaking
- `CanvasKit.MakeWebGLCanvasSurface` no longer takes a webgl context as a first arg, only a
canvas or an id of a canvas. If users want to manage their own GL contexts, they should build
the `SkSurface` themselves with `GetWebGLContext` -> `MakeGrContext` ->
`MakeOnScreenGLSurface`.
## [0.4.1] - 2019-03-01

View File

@ -35,13 +35,14 @@
<img id=api8 width=300 height=300>
<canvas id=api8_c width=300 height=300></canvas>
<h2> CanvasKit draws Paths to the browser</h2>
<h2> CanvasKit expands the functionality of a stock HTML canvas</h2>
<canvas id=vertex1 width=300 height=300></canvas>
<canvas id=vertex2 width=300 height=300></canvas>
<canvas id=gradient1 width=300 height=300></canvas>
<canvas id=patheffect width=300 height=300></canvas>
<canvas id=paths width=200 height=200></canvas>
<canvas id=ink width=300 height=300></canvas>
<canvas id=surfaces width=300 height=300></canvas>
<h2> CanvasKit can allow for text shaping (e.g. breaking, kerning)</h2>
<canvas id=shape1 width=600 height=600></canvas>
@ -117,6 +118,8 @@
TextShapingAPI2(CanvasKit, notoserifData);
ParticlesAPI1(CanvasKit);
SurfaceAPI1(CanvasKit);
});
fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
@ -1414,4 +1417,71 @@ const snowfall = {
]
};
function SurfaceAPI1(CanvasKit) {
const surface = CanvasKit.MakeCanvasSurface('surfaces');
if (!surface) {
console.error('Could not make surface');
return;
}
const context = CanvasKit.currentContext();
const canvas = surface.getCanvas();
// create a subsurface as a temporary workspace.
const subSurface = surface.makeSurface({
width: 50,
height: 50,
alphaType: CanvasKit.AlphaType.Premul,
colorType: CanvasKit.ColorType.RGBA_8888,
});
if (!subSurface) {
console.error('Could not make subsurface');
return;
}
// draw a small "scene"
const paint = new CanvasKit.SkPaint();
paint.setColor(CanvasKit.Color(139, 228, 135, 0.95)); // greenish
paint.setStyle(CanvasKit.PaintStyle.Fill);
paint.setAntiAlias(true);
const subCanvas = subSurface.getCanvas();
subCanvas.clear(CanvasKit.BLACK);
subCanvas.drawRect(CanvasKit.LTRBRect(5, 15, 45, 40), paint);
paint.setColor(CanvasKit.Color(214, 93, 244)); // purplish
for (let i = 0; i < 10; i++) {
const x = Math.random() * 50;
const y = Math.random() * 50;
subCanvas.drawOval(CanvasKit.XYWHRect(x, y, 6, 6), paint);
}
// Snap it off as an SkImage - this image will be in the form the
// parent surface prefers (e.g. Texture for GPU / Raster for CPU).
const img = subSurface.makeImageSnapshot();
// clean up the temporary surface
subSurface.delete();
paint.delete();
// Make it repeat a bunch with a shader
const pattern = CanvasKit.MakeImageShader(img, CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror);
const patternPaint = new CanvasKit.SkPaint();
patternPaint.setShader(pattern);
let i = 0;
function drawFrame() {
i++;
CanvasKit.setCurrentContext(context);
canvas.clear(CanvasKit.WHITE);
canvas.drawOval(CanvasKit.LTRBRect(i % 60, i % 60, 300 - (i% 60), 300 - (i % 60)), patternPaint);
surface.flush();
window.requestAnimationFrame(drawFrame);
}
window.requestAnimationFrame(drawFrame);
}
</script>

View File

@ -68,47 +68,6 @@ using BoneIndices = SkVertices::BoneIndices;
using BoneWeights = SkVertices::BoneWeights;
using Bone = SkVertices::Bone;
#if SK_SUPPORT_GPU
// Wraps the WebGL context in an SkSurface and returns it.
// This function based on the work of
// https://github.com/Zubnix/skia-wasm-port/, used under the terms of the MIT license.
sk_sp<SkSurface> getWebGLSurface(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context, int width, int height) {
EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
if (r < 0) {
printf("failed to make webgl context current %d\n", r);
return nullptr;
}
glClearColor(0, 0, 0, 0);
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// setup GrContext
auto interface = GrGLMakeNativeInterface();
// setup contexts
sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
// Wrap the frame buffer object attached to the screen in a Skia render target so Skia can
// render to it
GrGLint buffer;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
GrGLFramebufferInfo info;
info.fFBOID = (GrGLuint) buffer;
SkColorType colorType;
info.fFormat = GL_RGBA8;
colorType = kRGBA_8888_SkColorType;
GrBackendRenderTarget target(width, height, 0, 8, info);
sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
kBottomLeft_GrSurfaceOrigin,
colorType, nullptr, nullptr));
return surface;
}
#endif
struct SimpleMatrix {
SkScalar scaleX, skewX, transX;
SkScalar skewY, scaleY, transY;
@ -140,6 +99,68 @@ SkImageInfo toSkImageInfo(const SimpleImageInfo& sii) {
return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType);
}
#if SK_SUPPORT_GPU
sk_sp<GrContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
{
EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
if (r < 0) {
printf("failed to make webgl context current %d\n", r);
return nullptr;
}
// setup GrContext
auto interface = GrGLMakeNativeInterface();
// setup contexts
sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
return grContext;
}
sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrContext> grContext, int width, int height) {
glClearColor(0, 0, 0, 0);
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// Wrap the frame buffer object attached to the screen in a Skia render
// target so Skia can render to it
GrGLint buffer;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
GrGLFramebufferInfo info;
info.fFBOID = (GrGLuint) buffer;
SkColorType colorType;
info.fFormat = GL_RGBA8;
colorType = kRGBA_8888_SkColorType;
GrBackendRenderTarget target(width, height, 0, 8, info);
sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
kBottomLeft_GrSurfaceOrigin,
colorType, nullptr, nullptr));
return surface;
}
sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, int width, int height) {
SkImageInfo info = SkImageInfo::MakeN32(width, height, SkAlphaType::kPremul_SkAlphaType);
sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
SkBudgeted::kYes,
info, 0,
kBottomLeft_GrSurfaceOrigin,
nullptr, true));
return surface;
}
sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, SimpleImageInfo sii) {
sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
SkBudgeted::kYes,
toSkImageInfo(sii), 0,
kBottomLeft_GrSurfaceOrigin,
nullptr, true));
return surface;
}
#endif
//========================================================================================
// Path things
//========================================================================================
@ -553,9 +574,13 @@ namespace emscripten {
// the compiler is happy.
EMSCRIPTEN_BINDINGS(Skia) {
#if SK_SUPPORT_GPU
function("_getWebGLSurface", &getWebGLSurface, allow_raw_pointers());
function("currentContext", &emscripten_webgl_get_current_context);
function("setCurrentContext", &emscripten_webgl_make_context_current);
function("MakeGrContext", &MakeGrContext);
function("MakeOnScreenGLSurface", &MakeOnScreenGLSurface);
function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(sk_sp<GrContext>, int, int)>(&MakeRenderTarget));
function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(sk_sp<GrContext>, SimpleImageInfo)>(&MakeRenderTarget));
constant("gpu", true);
#endif
function("_decodeImage", optional_override([](uintptr_t /* uint8_t* */ iptr,
@ -709,6 +734,11 @@ EMSCRIPTEN_BINDINGS(Skia) {
boneIndices, boneWeights, indexCount, indices, isVolatile);
}), allow_raw_pointers());
#if SK_SUPPORT_GPU
class_<GrContext>("GrContext")
.smart_ptr<sk_sp<GrContext>>("sk_sp<GrContext>");
#endif
class_<SkCanvas>("SkCanvas")
.constructor<>()
.function("clear", optional_override([](SkCanvas& self, JSColor color)->void {
@ -760,6 +790,9 @@ EMSCRIPTEN_BINDINGS(Skia) {
SkMatrix m = self.getTotalMatrix();
return toSimpleSkMatrix(m);
}))
.function("makeSurface", optional_override([](SkCanvas& self, SimpleImageInfo sii)->sk_sp<SkSurface> {
return self.makeSurface(toSkImageInfo(sii), nullptr);
}), allow_raw_pointers())
.function("_readPixels", optional_override([](SkCanvas& self, SimpleImageInfo di,
uintptr_t /* uint8_t* */ pPtr,
size_t dstRowBytes, int srcX, int srcY) {
@ -945,18 +978,21 @@ EMSCRIPTEN_BINDINGS(Skia) {
class_<SkSurface>("SkSurface")
.smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
.function("width", &SkSurface::width)
.function("height", &SkSurface::height)
.function("_flush", select_overload<void()>(&SkSurface::flush))
.function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers())
.function("height", &SkSurface::height)
.function("makeImageSnapshot", select_overload<sk_sp<SkImage>()>(&SkSurface::makeImageSnapshot))
.function("makeImageSnapshot", select_overload<sk_sp<SkImage>(const SkIRect& bounds)>(&SkSurface::makeImageSnapshot))
.function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
.function("makeSurface", optional_override([](SkSurface& self, SimpleImageInfo sii)->sk_sp<SkSurface> {
return self.makeSurface(toSkImageInfo(sii));
}), allow_raw_pointers())
.function("width", &SkSurface::width);
class_<SkTextBlob>("SkTextBlob")
.smart_ptr<sk_sp<SkTextBlob>>("sk_sp<SkTextBlob>>")
.class_function("_MakeFromText",optional_override([](uintptr_t /* char* */ sptr,
size_t len, const SkFont& font,
SkTextEncoding encoding)->sk_sp<SkTextBlob> {
.class_function("_MakeFromText", optional_override([](uintptr_t /* char* */ sptr,
size_t len, const SkFont& font,
SkTextEncoding encoding)->sk_sp<SkTextBlob> {
// See comment above for uintptr_t explanation
const char* str = reinterpret_cast<const char*>(sptr);
return SkTextBlob::MakeFromText(str, len, font, encoding);

View File

@ -31,18 +31,23 @@ var CanvasKit = {
XYWHRect: function() {},
/** @return {ImageData} */
ImageData: function() {},
GetWebGLContext: function() {},
MakeBlurMaskFilter: function() {},
MakeCanvas: function() {},
MakeCanvasSurface: function() {},
MakeGrContext: function() {},
MakeImageShader: function() {},
/** @return {CanvasKit.SkImage} */
MakeImageFromEncoded: function() {},
/** @return {LinearCanvasGradient} */
MakeLinearGradientShader: function() {},
MakeOnScreenGLSurface: function() {},
MakePathFromCmds: function() {},
MakePathFromOp: function() {},
MakePathFromSVGString: function() {},
MakeRadialGradientShader: function() {},
MakeRenderTarget: function() {},
MakeSWCanvasSurface: function() {},
MakeManagedAnimation: function() {},
MakeSkDashPathEffect: function() {},
@ -72,7 +77,6 @@ var CanvasKit = {
_drawShapedText: function() {},
_getRasterDirectSurface: function() {},
_getRasterN32PremulSurface: function() {},
_getWebGLSurface: function() {},
// The testing object is meant to expose internal functions
// for more fine-grained testing, e.g. parseColor
@ -106,6 +110,7 @@ var CanvasKit = {
drawVertices: function() {},
flush: function() {},
getTotalMatrix: function() {},
makeSurface: function() {},
restore: function() {},
restoreToCount: function() {},
rotate: function() {},
@ -248,6 +253,7 @@ var CanvasKit = {
getCanvas: function() {},
/** @return {CanvasKit.SkImage} */
makeImageSnapshot: function() {},
makeSurface: function() {},
// private API
_flush: function() {},

View File

@ -41,29 +41,27 @@
return GL.createContext(canvas, contextAttributes);
}
CanvasKit.GetWebGLContext = function(canvas, attrs) {
return makeWebGLContext(canvas, attrs);
};
// arg can be of types:
// - String - in which case it is interpreted as an id of a
// canvas element.
// - HTMLCanvasElement - in which the provided canvas element will
// be used directly.
// - int - in which case it will be used as a WebGLContext. Only 1.0
// contexts are known to work for now.
// Width and height can be provided to override those on the canvas
// element, or specify a height for when a context is provided.
CanvasKit.MakeWebGLCanvasSurface = function(arg, width, height) {
var ctx = arg;
// ctx is only > 0 if it's an int, and thus a valid context
if (!(ctx > 0)) {
var canvas = arg;
if (canvas.tagName !== 'CANVAS') {
canvas = document.getElementById(arg);
if (!canvas) {
throw 'Canvas with id ' + arg + ' was not found';
}
var canvas = arg;
if (canvas.tagName !== 'CANVAS') {
canvas = document.getElementById(arg);
if (!canvas) {
throw 'Canvas with id ' + arg + ' was not found';
}
// we are ok with all the defaults
ctx = makeWebGLContext(canvas);
}
// we are ok with all the defaults
var ctx = CanvasKit.GetWebGLContext(canvas);
if (!ctx || ctx < 0) {
throw 'failed to create webgl context: err ' + ctx;
@ -73,10 +71,12 @@
throw 'height and width must be provided with context';
}
var grcontext = this.MakeGrContext(ctx);
// Maybe better to use clientWidth/height. See:
// https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
var surface = this._getWebGLSurface(ctx, width || canvas.width,
height || canvas.height);
var surface = this.MakeOnScreenGLSurface(grcontext,
width || canvas.width,
height || canvas.height);
if (!surface) {
SkDebug('falling back from GPU implementation to a SW based one');
return CanvasKit.MakeSWCanvasSurface(arg);

View File

@ -551,7 +551,7 @@ CanvasKit.MakeImageFromEncoded = function(data) {
return img;
}
// imgData is an Encoded SkImage, e.g. from MakeImageFromEncoded
// imgData is an SkImage, e.g. from MakeImageFromEncoded or SkSurface.makeImageSnapshot
CanvasKit.MakeImageShader = function(img, xTileMode, yTileMode, clampUnpremul, localMatrix) {
if (!img) {
return null;