1// Adds compile-time JS functions to augment the CanvasKit interface. 2// Specifically, anything that should only be on the WebGL version of canvaskit. 3// Functions in this file are supplemented by cpu.js. 4(function(CanvasKit){ 5 CanvasKit._extraInitializations = CanvasKit._extraInitializations || []; 6 CanvasKit._extraInitializations.push(function() { 7 function get(obj, attr, defaultValue) { 8 if (obj && obj.hasOwnProperty(attr)) { 9 return obj[attr]; 10 } 11 return defaultValue; 12 } 13 14 CanvasKit.GetWebGLContext = function(canvas, attrs) { 15 if (!canvas) { 16 throw 'null canvas passed into makeWebGLContext'; 17 } 18 var contextAttributes = { 19 'alpha': get(attrs, 'alpha', 1), 20 'depth': get(attrs, 'depth', 1), 21 'stencil': get(attrs, 'stencil', 8), 22 'antialias': get(attrs, 'antialias', 0), 23 'premultipliedAlpha': get(attrs, 'premultipliedAlpha', 1), 24 'preserveDrawingBuffer': get(attrs, 'preserveDrawingBuffer', 0), 25 'preferLowPowerToHighPerformance': get(attrs, 'preferLowPowerToHighPerformance', 0), 26 'failIfMajorPerformanceCaveat': get(attrs, 'failIfMajorPerformanceCaveat', 0), 27 'enableExtensionsByDefault': get(attrs, 'enableExtensionsByDefault', 1), 28 'explicitSwapControl': get(attrs, 'explicitSwapControl', 0), 29 'renderViaOffscreenBackBuffer': get(attrs, 'renderViaOffscreenBackBuffer', 0), 30 }; 31 32 if (attrs && attrs['majorVersion']) { 33 contextAttributes['majorVersion'] = attrs['majorVersion'] 34 } else { 35 // Default to WebGL 2 if available and not specified. 36 contextAttributes['majorVersion'] = (typeof WebGL2RenderingContext !== 'undefined') ? 2 : 1; 37 } 38 39 // This check is from the emscripten version 40 if (contextAttributes['explicitSwapControl']) { 41 throw 'explicitSwapControl is not supported'; 42 } 43 // Creates a WebGL context and sets it to be the current context. 44 // These functions are defined in emscripten's library_webgl.js 45 var handle = GL.createContext(canvas, contextAttributes); 46 if (!handle) { 47 return 0; 48 } 49 GL.makeContextCurrent(handle); 50 // Emscripten does not enable this by default and Skia needs this to handle certain GPU 51 // corner cases. 52 GL.currentContext.GLctx.getExtension('WEBGL_debug_renderer_info'); 53 return handle; 54 }; 55 56 CanvasKit.deleteContext = function(handle) { 57 GL.deleteContext(handle); 58 }; 59 60 CanvasKit._setTextureCleanup({ 61 'deleteTexture': function(webglHandle, texHandle) { 62 var tex = GL.textures[texHandle]; 63 if (tex) { 64 GL.getContext(webglHandle).GLctx.deleteTexture(tex); 65 } 66 GL.textures[texHandle] = null; 67 }, 68 }); 69 70 CanvasKit.MakeWebGLContext = function(ctx) { 71 // Make sure we are pointing at the right WebGL context. 72 if (!this.setCurrentContext(ctx)) { 73 return null; 74 } 75 var grCtx = this._MakeGrContext(); 76 if (!grCtx) { 77 return null; 78 } 79 // This context is an index into the emscripten-provided GL wrapper. 80 grCtx._context = ctx; 81 var oldDelete = grCtx.delete.bind(grCtx); 82 // We need to make sure we are focusing on the correct webgl context 83 // when Skia cleans up the context. 84 grCtx['delete'] = function() { 85 CanvasKit.setCurrentContext(this._context); 86 oldDelete(); 87 }.bind(grCtx); 88 // Save this so it is easy to access (e.g. Image.readPixels) 89 GL.currentContext.grDirectContext = grCtx; 90 return grCtx; 91 }; 92 93 CanvasKit.MakeGrContext = CanvasKit.MakeWebGLContext; 94 95 CanvasKit.GrDirectContext.prototype.getResourceCacheLimitBytes = function() { 96 CanvasKit.setCurrentContext(this._context); 97 this._getResourceCacheLimitBytes(); 98 }; 99 100 CanvasKit.GrDirectContext.prototype.getResourceCacheUsageBytes = function() { 101 CanvasKit.setCurrentContext(this._context); 102 this._getResourceCacheUsageBytes(); 103 }; 104 105 CanvasKit.GrDirectContext.prototype.releaseResourcesAndAbandonContext = function() { 106 CanvasKit.setCurrentContext(this._context); 107 this._releaseResourcesAndAbandonContext(); 108 }; 109 110 CanvasKit.GrDirectContext.prototype.setResourceCacheLimitBytes = function(maxResourceBytes) { 111 CanvasKit.setCurrentContext(this._context); 112 this._setResourceCacheLimitBytes(maxResourceBytes); 113 }; 114 115 CanvasKit.MakeOnScreenGLSurface = function(grCtx, w, h, colorspace, sc, st) { 116 if (!this.setCurrentContext(grCtx._context)) { 117 return null; 118 } 119 var surface; 120 // zero is a valid value for sample count or stencil bits. 121 if (sc === undefined || st === undefined) { 122 surface = this._MakeOnScreenGLSurface(grCtx, w, h, colorspace); 123 } else { 124 surface = this._MakeOnScreenGLSurface(grCtx, w, h, colorspace, sc, st); 125 } 126 if (!surface) { 127 return null; 128 } 129 surface._context = grCtx._context; 130 return surface; 131 } 132 133 CanvasKit.MakeRenderTarget = function() { 134 var grCtx = arguments[0]; 135 if (!this.setCurrentContext(grCtx._context)) { 136 return null; 137 } 138 var surface; 139 if (arguments.length === 3) { 140 surface = this._MakeRenderTargetWH(grCtx, arguments[1], arguments[2]); 141 if (!surface) { 142 return null; 143 } 144 } else if (arguments.length === 2) { 145 surface = this._MakeRenderTargetII(grCtx, arguments[1]); 146 if (!surface) { 147 return null; 148 } 149 } else { 150 Debug('Expected 2 or 3 params'); 151 return null; 152 } 153 surface._context = grCtx._context; 154 return surface; 155 } 156 157 // idOrElement can be of types: 158 // - String - in which case it is interpreted as an id of a 159 // canvas element. 160 // - HTMLCanvasElement - in which the provided canvas element will 161 // be used directly. 162 // colorSpace - sk_sp<ColorSpace> - one of the supported color spaces: 163 // CanvasKit.ColorSpace.SRGB 164 // CanvasKit.ColorSpace.DISPLAY_P3 165 // CanvasKit.ColorSpace.ADOBE_RGB 166 CanvasKit.MakeWebGLCanvasSurface = function(idOrElement, colorSpace, attrs) { 167 colorSpace = colorSpace || null; 168 var canvas = idOrElement; 169 var isHTMLCanvas = typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement; 170 var isOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas; 171 if (!isHTMLCanvas && !isOffscreenCanvas) { 172 canvas = document.getElementById(idOrElement); 173 if (!canvas) { 174 throw 'Canvas with id ' + idOrElement + ' was not found'; 175 } 176 } 177 178 var ctx = this.GetWebGLContext(canvas, attrs); 179 if (!ctx || ctx < 0) { 180 throw 'failed to create webgl context: err ' + ctx; 181 } 182 183 var grcontext = this.MakeWebGLContext(ctx); 184 185 // Note that canvas.width/height here is used because it gives the size of the buffer we're 186 // rendering into. This may not be the same size the element is displayed on the page, which 187 // controlled by css, and available in canvas.clientWidth/height. 188 var surface = this.MakeOnScreenGLSurface(grcontext, canvas.width, canvas.height, colorSpace); 189 if (!surface) { 190 Debug('falling back from GPU implementation to a SW based one'); 191 // we need to throw away the old canvas (which was locked to 192 // a webGL context) and create a new one so we can 193 var newCanvas = canvas.cloneNode(true); 194 var parent = canvas.parentNode; 195 parent.replaceChild(newCanvas, canvas); 196 // add a class so the user can detect that it was replaced. 197 newCanvas.classList.add('ck-replaced'); 198 199 return CanvasKit.MakeSWCanvasSurface(newCanvas); 200 } 201 return surface; 202 }; 203 // Default to trying WebGL first. 204 CanvasKit.MakeCanvasSurface = CanvasKit.MakeWebGLCanvasSurface; 205 206 function pushTexture(tex) { 207 // GL is an emscripten object that holds onto WebGL state. One item in that state is 208 // an array of textures, of which the index is the handle/id. We must call getNewId so 209 // the GL's tracking of textures is up to date and we do not accidentally use the same 210 // texture in two different places if Skia creates a texture. (e.g. skbug.com/12797) 211 var texHandle = GL.getNewId(GL.textures); 212 GL.textures[texHandle] = tex; 213 return texHandle 214 } 215 216 CanvasKit.Surface.prototype.makeImageFromTexture = function(tex, info) { 217 CanvasKit.setCurrentContext(this._context); 218 var texHandle = pushTexture(tex); 219 var img = this._makeImageFromTexture(this._context, texHandle, info); 220 if (img) { 221 img._tex = texHandle; 222 } 223 return img; 224 }; 225 226 // We try to find the natural media type (for <img> and <video>), display* for 227 // https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame and then fall back to 228 // the height and width (to cover <canvas>, ImageBitmap or ImageData). 229 function getHeight(src) { 230 return src['naturalHeight'] || src['videoHeight'] || src['displayHeight'] || src['height']; 231 } 232 233 function getWidth(src) { 234 return src['naturalWidth'] || src['videoWidth'] || src['displayWidth'] || src['width']; 235 } 236 237 function setupTexture(glCtx, newTex, imageInfo, srcIsPremul) { 238 glCtx.bindTexture(glCtx.TEXTURE_2D, newTex); 239 // See https://github.com/flutter/flutter/issues/106433#issuecomment-1169102945 240 // for an example of what can happen if we do not set this. 241 if (!srcIsPremul && imageInfo['alphaType'] === CanvasKit.AlphaType.Premul) { 242 glCtx.pixelStorei(glCtx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); 243 } 244 return newTex; 245 } 246 247 function resetTexture(glCtx, imageInfo, srcIsPremul) { 248 // If we set this earlier, we want to unset it now. 249 if (!srcIsPremul && imageInfo['alphaType'] === CanvasKit.AlphaType.Premul) { 250 glCtx.pixelStorei(glCtx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); 251 } 252 glCtx.bindTexture(glCtx.TEXTURE_2D, null); 253 } 254 255 CanvasKit.Surface.prototype.makeImageFromTextureSource = function(src, info, srcIsPremul) { 256 if (!info) { 257 // If the user didn't specify the image info, use some sensible defaults. 258 info = { 259 'height': getHeight(src), 260 'width': getWidth(src), 261 'colorType': CanvasKit.ColorType.RGBA_8888, 262 'alphaType': srcIsPremul ? CanvasKit.AlphaType.Premul: CanvasKit.AlphaType.Unpremul, 263 }; 264 } 265 if (!info['colorSpace']) { 266 info['colorSpace'] = CanvasKit.ColorSpace.SRGB; 267 } 268 if (info['colorType'] !== CanvasKit.ColorType.RGBA_8888) { 269 Debug('colorType currently has no impact on makeImageFromTextureSource'); 270 } 271 272 // We want to be pointing at the context associated with this surface. 273 CanvasKit.setCurrentContext(this._context); 274 var glCtx = GL.currentContext.GLctx; 275 var newTex = setupTexture(glCtx, glCtx.createTexture(), info, srcIsPremul); 276 if (GL.currentContext.version === 2) { 277 glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, info['width'], info['height'], 0, glCtx.RGBA, glCtx.UNSIGNED_BYTE, src); 278 } else { 279 glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, glCtx.RGBA, glCtx.UNSIGNED_BYTE, src); 280 } 281 resetTexture(glCtx, info); 282 this._resetContext(); 283 return this.makeImageFromTexture(newTex, info); 284 }; 285 286 CanvasKit.Surface.prototype.updateTextureFromSource = function(img, src, srcIsPremul) { 287 if (!img._tex) { 288 Debug('Image is not backed by a user-provided texture'); 289 return; 290 } 291 CanvasKit.setCurrentContext(this._context); 292 var ii = img.getImageInfo(); 293 var glCtx = GL.currentContext.GLctx; 294 // Copy the contents of src over the texture associated with this image. 295 var tex = setupTexture(glCtx, GL.textures[img._tex], ii, srcIsPremul); 296 if (GL.currentContext.version === 2) { 297 glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, getWidth(src), getHeight(src), 0, glCtx.RGBA, glCtx.UNSIGNED_BYTE, src); 298 } else { 299 glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, glCtx.RGBA, glCtx.UNSIGNED_BYTE, src); 300 } 301 resetTexture(glCtx, ii, srcIsPremul); 302 // Tell Skia we messed with the currently bound texture. 303 this._resetContext(); 304 // Create a new texture entry and put null into the old slot. This keeps our texture alive, 305 // otherwise it will be deleted when we delete the old Image. 306 GL.textures[img._tex] = null; 307 img._tex = pushTexture(tex); 308 ii['colorSpace'] = img.getColorSpace(); 309 // Skia may cache parts of the image, and some places assume images are immutable. In order 310 // to make things work, we create a new SkImage based on the same texture as the old image. 311 var newImg = this._makeImageFromTexture(this._context, img._tex, ii); 312 // To make things more ergonomic for the user, we change passed in img object to refer 313 // to the new image and clean up the old SkImage object. This has the effect of updating 314 // the Image (from the user's side of things), because they shouldn't be caring about what 315 // part of WASM memory we are pointing to. 316 // The $$ part is provided by emscripten's embind, so this could break if they change 317 // things on us. 318 // https://github.com/emscripten-core/emscripten/blob/a65d70c809f077542649c60097787e1c7460ced6/src/embind/embind.js 319 // They do not do anything special to keep closure from minifying things and neither do we. 320 var oldPtr = img.$$.ptr; 321 var oldSmartPtr = img.$$.smartPtr; 322 img.$$.ptr = newImg.$$.ptr; 323 img.$$.smartPtr = newImg.$$.smartPtr; 324 // We want to clean up the previous image, so we swap out the pointers and call delete on it 325 // which should have that effect. 326 newImg.$$.ptr = oldPtr; 327 newImg.$$.smartPtr = oldSmartPtr; 328 newImg.delete(); 329 // Clean up the colorspace that we used. 330 ii['colorSpace'].delete(); 331 } 332 333 CanvasKit.MakeLazyImageFromTextureSource = function(src, info, srcIsPremul) { 334 if (!info) { 335 info = { 336 'height': getHeight(src), 337 'width': getWidth(src), 338 'colorType': CanvasKit.ColorType.RGBA_8888, 339 'alphaType': srcIsPremul ? CanvasKit.AlphaType.Premul : CanvasKit.AlphaType.Unpremul, 340 }; 341 } 342 if (!info['colorSpace']) { 343 info['colorSpace'] = CanvasKit.ColorSpace.SRGB; 344 } 345 if (info['colorType'] !== CanvasKit.ColorType.RGBA_8888) { 346 Debug('colorType currently has no impact on MakeLazyImageFromTextureSource'); 347 } 348 349 var callbackObj = { 350 'makeTexture': function() { 351 // This callback function will make a texture on the current drawing surface (i.e. 352 // the current WebGL context). It assumes that Skia is just about to draw the texture 353 // to the desired surface, and thus the currentContext is the correct one. 354 // This is a lot easier than needing to pass the surface handle from the C++ side here. 355 var ctx = GL.currentContext; 356 var glCtx = ctx.GLctx; 357 var newTex = setupTexture(glCtx, glCtx.createTexture(), info, srcIsPremul); 358 if (ctx.version === 2) { 359 glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, info['width'], info['height'], 0, glCtx.RGBA, glCtx.UNSIGNED_BYTE, src); 360 } else { 361 glCtx.texImage2D(glCtx.TEXTURE_2D, 0, glCtx.RGBA, glCtx.RGBA, glCtx.UNSIGNED_BYTE, src); 362 } 363 resetTexture(glCtx, info, srcIsPremul); 364 return pushTexture(newTex); 365 }, 366 'freeSrc': function() { 367 // This callback will be executed whenever the returned image is deleted. This gives 368 // us a chance to free up the src (which we now own). Generally, there's nothing 369 // we need to do (we can let JS garbage collection do its thing). The one exception 370 // is for https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame, which we should 371 // close when we are done. 372 }, 373 } 374 if (src.constructor.name === 'VideoFrame') { 375 callbackObj['freeSrc'] = function() { 376 src.close(); 377 } 378 } 379 return CanvasKit.Image._makeFromGenerator(info, callbackObj); 380 } 381 382 CanvasKit.setCurrentContext = function(ctx) { 383 if (!ctx) { 384 return false; 385 } 386 return GL.makeContextCurrent(ctx); 387 }; 388 389 CanvasKit.getCurrentGrDirectContext = function() { 390 if (GL.currentContext && GL.currentContext.grDirectContext && 391 !GL.currentContext.grDirectContext['isDeleted']()) { 392 return GL.currentContext.grDirectContext; 393 } 394 return null; 395 }; 396 397 }); 398}(Module)); // When this file is loaded in, the high level object is "Module"; 399