[canvaskit] Add API to re-use Image textures and reduce flickering
This creates a new SkImage with the same texture and changes it out without the user noticing (or needing to delete the old Image). Change-Id: I3a1ce6d4a335873f2b7670d56dadfccdc7881c38 Bug: skia:12723 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/495556 Reviewed-by: Nathaniel Nifong <nifong@google.com>
This commit is contained in:
parent
6e1afd3bfe
commit
994c946bd3
1
demos.skia.org/.gitignore
vendored
Normal file
1
demos.skia.org/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build
|
@ -1,4 +1,6 @@
|
||||
.PHONY: local
|
||||
local:
|
||||
echo "Go check out http://localhost:8123/demos/hello_world/index.html"
|
||||
rm -f build
|
||||
ln -s ../modules/canvaskit/build build
|
||||
python2 -m SimpleHTTPServer 8123
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
<canvas id=draw width=500 height=500></canvas>
|
||||
|
||||
<img id="sourceImage" src="/demo/textures/testimg.png">
|
||||
<img id="sourceImage" src="/demos/textures/testimg.png">
|
||||
<video id="sourceVideo" autoplay muted></video>
|
||||
</body>
|
||||
|
||||
@ -62,21 +62,22 @@
|
||||
throw 'Could not make surface';
|
||||
}
|
||||
const paint = new CanvasKit.Paint();
|
||||
// This image will have its underlying texture re-used once per frame.
|
||||
const img = surface.makeImageFromTextureSource(srcEle);
|
||||
|
||||
// This example creates a new texture, loads it into an image, and then deletes the image
|
||||
// (which should delete the texture automatically).
|
||||
let lastTS = Date.now();
|
||||
function drawFrame(canvas) {
|
||||
const now = Date.now();
|
||||
canvas.rotate(10 * (now - lastTS) / 1000, 250, 250);
|
||||
lastTS = now;
|
||||
const img = surface.makeImageFromTextureSource(srcEle);
|
||||
// Re-use the image's underlying texture, but replace the contents of the old texture
|
||||
// with the contents of srcEle
|
||||
surface.updateTextureFromSource(img, srcEle);
|
||||
canvas.clear(CanvasKit.Color(200, 200, 200));
|
||||
canvas.drawImage(img, 5, 5, paint);
|
||||
img && img.delete();
|
||||
surface.requestAnimationFrame(drawFrame);
|
||||
}
|
||||
surface.requestAnimationFrame(drawFrame);
|
||||
});
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
@ -6,13 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- `Surface.updateTextureFromSource` prevents flickering on some platforms by re-using the texture
|
||||
for a given `Image` instead of needing to always create a new one via
|
||||
`Surface.makeImageFromTextureSource`. (skbug.com/12723)
|
||||
|
||||
### Changed
|
||||
- Surface factories always produce a surface with an attached color space. Specifying `null` to
|
||||
`CanvasKit.MakeWebGLCanvasSurface` or calling any factory that does not take a color space
|
||||
will now create a surface with a color space of `CanvasKit.ColorSpace.SRGB`.
|
||||
|
||||
### Fixed
|
||||
- Supplying textures via `CanvasKit.makeImageFromTextureSource` should not cause issues with
|
||||
- Supplying textures via `Surface.makeImageFromTextureSource` should not cause issues with
|
||||
Mipmaps or other places where Skia needs to create textures (skbug.com/12797)
|
||||
|
||||
## [0.32.0] - 2021-12-15
|
||||
|
@ -1922,6 +1922,9 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
auto backendRT = self.getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
|
||||
return (backendRT.isValid()) ? backendRT.sampleCnt() : 0;
|
||||
}))
|
||||
.function("_resetContext",optional_override([](SkSurface& self)->void {
|
||||
GrAsDirectContext(self.recordingContext())->resetContext(kTextureBinding_GrGLBackendState);
|
||||
}))
|
||||
#else
|
||||
.function("reportBackendTypeIsGPU", optional_override([](SkSurface& self) -> bool {
|
||||
return false;
|
||||
|
@ -674,6 +674,7 @@ var CanvasKit = {
|
||||
/** @return {CanvasKit.Image} */
|
||||
makeImageSnapshot: function() {},
|
||||
makeSurface: function() {},
|
||||
updateTextureFromSource: function() {},
|
||||
},
|
||||
|
||||
// private API
|
||||
@ -683,6 +684,7 @@ var CanvasKit = {
|
||||
_makeImageSnapshot: function() {},
|
||||
_makeSurface: function() {},
|
||||
_makeRasterDirect: function() {},
|
||||
_resetContext: function() {},
|
||||
delete: function() {},
|
||||
},
|
||||
|
||||
|
@ -167,7 +167,11 @@
|
||||
CanvasKit.Surface.prototype.makeImageFromTexture = function(tex, info) {
|
||||
CanvasKit.setCurrentContext(this._context);
|
||||
var texHandle = pushTexture(tex);
|
||||
return this._makeImageFromTexture(this._context, texHandle, info);
|
||||
var img = this._makeImageFromTexture(this._context, texHandle, info);
|
||||
if (img) {
|
||||
img._tex = texHandle;
|
||||
}
|
||||
return img;
|
||||
};
|
||||
|
||||
// We try to find the natural media type (for <img> and <video>), display* for
|
||||
@ -211,6 +215,54 @@
|
||||
return this.makeImageFromTexture(newTex, info);
|
||||
};
|
||||
|
||||
CanvasKit.Surface.prototype.updateTextureFromSource = function(img, src) {
|
||||
if (!img._tex) {
|
||||
Debug('Image is not backed by a user-provided texture');
|
||||
return;
|
||||
}
|
||||
CanvasKit.setCurrentContext(this._context);
|
||||
var glCtx = GL.currentContext.GLctx;
|
||||
// Copy the contents of src over the texture associated with this image.
|
||||
var tex = GL.textures[img._tex];
|
||||
glCtx.bindTexture(glCtx.TEXTURE_2D, tex);
|
||||
if (GL.currentContext.version === 2) {
|
||||
glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, getWidth(src), getHeight(src), 0, glCtx.RGBA, glCtx.UNSIGNED_BYTE, src);
|
||||
} else {
|
||||
glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, glCtx.RGBA, glCtx.UNSIGNED_BYTE, src);
|
||||
}
|
||||
glCtx.bindTexture(glCtx.TEXTURE_2D, null);
|
||||
// Tell Skia we messed with the currently bound texture.
|
||||
this._resetContext();
|
||||
// Create a new texture entry and put null into the old slot. This keeps our texture alive,
|
||||
// otherwise it will be deleted when we delete the old Image.
|
||||
GL.textures[img._tex] = null;
|
||||
img._tex = pushTexture(tex);
|
||||
var ii = img.getImageInfo();
|
||||
ii['colorSpace'] = img.getColorSpace();
|
||||
// Skia may cache parts of the image, and some places assume images are immutable. In order
|
||||
// to make things work, we create a new SkImage based on the same texture as the old image.
|
||||
var newImg = this._makeImageFromTexture(this._context, img._tex, ii);
|
||||
// To make things more ergonomic for the user, we change passed in img object to refer
|
||||
// to the new image and clean up the old SkImage object. This has the effect of updating
|
||||
// the Image (from the user's side of things), because they shouldn't be caring about what
|
||||
// part of WASM memory we are pointing to.
|
||||
// The $$ part is provided by emscripten's embind, so this could break if they change
|
||||
// things on us.
|
||||
// https://github.com/emscripten-core/emscripten/blob/a65d70c809f077542649c60097787e1c7460ced6/src/embind/embind.js
|
||||
// They do not do anything special to keep closure from minifying things and neither do we.
|
||||
var oldPtr = img.$$.ptr;
|
||||
var oldSmartPtr = img.$$.smartPtr;
|
||||
img.$$.ptr = newImg.$$.ptr;
|
||||
img.$$.smartPtr = newImg.$$.smartPtr;
|
||||
// We want to clean up the previous image, so we swap out the pointers and call delete on it
|
||||
// which should have that effect.
|
||||
newImg.$$.ptr = oldPtr;
|
||||
newImg.$$.smartPtr = oldSmartPtr;
|
||||
newImg.delete();
|
||||
// Clean up the colorspace that we used.
|
||||
ii['colorSpace'].delete();
|
||||
}
|
||||
|
||||
CanvasKit.MakeLazyImageFromTextureSource = function(src, info) {
|
||||
if (!info) {
|
||||
info = {
|
||||
|
@ -895,6 +895,8 @@ function surfaceTests(CK: CanvasKit, gl?: WebGLRenderingContext) {
|
||||
};
|
||||
surfaceFour.requestAnimationFrame(drawFrame);
|
||||
surfaceFour.drawOnce(drawFrame);
|
||||
|
||||
surfaceFour.updateTextureFromSource(img5!, videoEle);
|
||||
}
|
||||
|
||||
function textBlobTests(CK: CanvasKit, font?: Font, path?: Path) {
|
||||
|
13
modules/canvaskit/npm_build/types/index.d.ts
vendored
13
modules/canvaskit/npm_build/types/index.d.ts
vendored
@ -2674,6 +2674,19 @@ export interface Surface extends EmbindObject<Surface> {
|
||||
*/
|
||||
sampleCnt(): number;
|
||||
|
||||
/**
|
||||
* Updates the underlying GPU texture of the image to be the contents of the provided
|
||||
* TextureSource. Has no effect on CPU backend or if img was not created with either
|
||||
* makeImageFromTextureSource or makeImageFromTexture.
|
||||
* If the provided TextureSource is of different dimensions than the Image, the contents
|
||||
* will be deformed (e.g. squished). The ColorType, AlphaType, and ColorSpace of src should
|
||||
* match the original settings used to create the Image or it may draw strange.
|
||||
*
|
||||
* @param img - A texture-backed Image.
|
||||
* @param src - A valid texture source of any dimensions.
|
||||
*/
|
||||
updateTextureFromSource(img: Image, src: TextureSource): void;
|
||||
|
||||
/**
|
||||
* Returns the width of this surface in pixels.
|
||||
*/
|
||||
|
@ -1390,6 +1390,10 @@ describe('Core canvas behavior', () => {
|
||||
// measure, we also wait for it to be decoded.
|
||||
return imageEle.decode().then(() => {
|
||||
const img = surface.makeImageFromTextureSource(imageEle);
|
||||
// Make sure the texture is properly written to and Skia does not draw over it by
|
||||
// by accident.
|
||||
canvas.clear(CanvasKit.RED);
|
||||
surface.updateTextureFromSource(img, imageEle);
|
||||
canvas.drawImage(img, 0, 0, null);
|
||||
|
||||
const info = img.getImageInfo();
|
||||
|
Loading…
Reference in New Issue
Block a user