[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:
parent
a720d76470
commit
543f352ace
@ -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
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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() {},
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user