From 125f20b9cefefc526d7deaf48ffba01f9dc22039 Mon Sep 17 00:00:00 2001 From: GarboMuffin Date: Sat, 30 Jul 2022 02:56:56 -0500 Subject: [PATCH] Revert "Don't use createImageBitmap to load bitmap costumes" Reverts 7abbcdbb108be9f3f2e513608f874ebd83fe877d I am unable to reproduce the claim that ImageBitmap uses more memory than . Instead I've observed the following: uses a base64 data: URI which is slower than creating a Blob directly from the binary data. I also experimented using a blob: URI with and did find it to perform better. A future commit may use this when createImageBitmap() is not available. can't be reliably decoded in parallel. Chrome in particular will very often wait until texImage2d() to actually decode the image, which turns image decoding into a single-threaded process. Calling decode() does not affect this. createImageBitmap is reliably parallelized. 's only benefit is that projects with a lot of copies of the same exact costume will usually have their image decoding happen only once whereas createImageBitmap() will decode each one individually (does not apply when using blob: URIs). Chrome: with data: 48.5MB 4160ms with blob: 48.4MB 2162ms createImageBitmap() 48.4MB 1261ms Equivalent SVGs 60.8MB 3857ms Firefox: with data: 88.3MB 3704ms with blob: 88.9MB 2459ms createImageBitmap() 92.5MB 2380ms Equivalent SVGs 483.3MB 5429ms Using a test project with 100 480x360 unique costumes. Equivalent SVGs is the same project with each costume converted to SVG, just for curiosity. Note that SVGs may not actually be rendered or decoded until the project starts. --- src/import/load-costume.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/import/load-costume.js b/src/import/load-costume.js index c1e90e41..acc42eb5 100644 --- a/src/import/load-costume.js +++ b/src/import/load-costume.js @@ -84,7 +84,11 @@ const canvasPool = (function () { return new CanvasPool(); }()); -const readImage = src => new Promise((resolve, reject) => { +/** + * @param {string} src URL of image + * @returns {Promise} + */ +const readAsImageElement = src => new Promise((resolve, reject) => { const image = new Image(); image.onload = function () { resolve(image); @@ -99,14 +103,23 @@ const readImage = src => new Promise((resolve, reject) => { image.src = src; }); -const persistentReadImage = async src => { +/** + * @param {Asset} asset scratch-storage asset + * @returns {Promise} + */ +const persistentReadImage = async asset => { // Sometimes, when a lot of images are loaded at once, especially in Chrome, reading an image // can throw an error even on valid images. To mitigate this, we'll retry image reading a few // time with delays. let firstError; for (let i = 0; i < 3; i++) { try { - return await readImage(src); + if (typeof createImageBitmap === 'function') { + return await createImageBitmap( + new Blob([asset.data.buffer], {type: asset.assetType.contentType}) + ); + } + return await readAsImageElement(asset.encodeDataURI()); } catch (e) { if (!firstError) { firstError = e; @@ -145,7 +158,7 @@ const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) { return null; } - return persistentReadImage(asset.encodeDataURI()); + return persistentReadImage(asset); })) .then(([baseImageElement, textImageElement]) => { if (!baseImageElement) {