From 2cf78c55837b25ae07da36e33d76d14c32985a64 Mon Sep 17 00:00:00 2001 From: flutteractionsbot <154381524+flutteractionsbot@users.noreply.github.com> Date: Tue, 15 Apr 2025 18:09:26 -0700 Subject: [PATCH] [CP-beta][skwasm] Use `queueMicrotask` instead of `postMessage` when single-threaded (#167154) This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) ### Issue Link: https://github.com/flutter/flutter/issues/166905 ### Changelog Description: * [flutter/166905](https://github.com/flutter/flutter/issues/166905) Fixes a performance regression in skwasm when running in single-threaded mode. ### Impact Description: This fixes a significant regression in the skwasm renderer when running single-thraaded (i.e. in a non-`crossOriginIsolated` browsing context) ### Workaround: Is there a workaround for this issue? The only workaround is to run skwasm in a multi-threaded context or to disable skwasm. ### Risk: What is the risk level of this cherry-pick? - [ x ] Low This essentially returns the single-threaded renderer to the previous message passing strategy. ### Test Coverage: Are you confident that your fix is well-tested by automated tests? - [ x ] Yes ### Validation Steps: What are the steps to validate that this fix works? Built the Wonderous app and take a Chrome profile. --- .../web_ui/skwasm/library_skwasm_support.js | 101 +++++++++++------- 1 file changed, 61 insertions(+), 40 deletions(-) diff --git a/engine/src/flutter/lib/web_ui/skwasm/library_skwasm_support.js b/engine/src/flutter/lib/web_ui/skwasm/library_skwasm_support.js index 702724e71d..ac98d599ca 100644 --- a/engine/src/flutter/lib/web_ui/skwasm/library_skwasm_support.js +++ b/engine/src/flutter/lib/web_ui/skwasm/library_skwasm_support.js @@ -8,51 +8,72 @@ mergeInto(LibraryManager.library, { $skwasm_support_setup__postset: 'skwasm_support_setup();', $skwasm_support_setup: function() { - // This value represents the difference between the time origin of the main - // thread and whichever web worker this code is running on. This is so that - // when we report frame timings, that they are in the same time domain - // regardless of whether they are captured on the main thread or the web - // worker. - let timeOriginDelta = 0; - skwasm_registerMessageListener = function(threadId, listener) { - const eventListener = function({data}) { - const skwasmMessage = data.skwasmMessage; - if (!skwasmMessage) { - return; - } - if (skwasmMessage == 'syncTimeOrigin') { - timeOriginDelta = performance.timeOrigin - data.timeOrigin; - return; - } - listener(data); + if (Module["skwasmSingleThreaded"]) { + _skwasm_isSingleThreaded = function() { + return true; }; - if (!threadId) { - addEventListener("message", eventListener); - } else { - _wasmWorkers[threadId].addEventListener("message", eventListener); - _wasmWorkers[threadId].postMessage({ - skwasmMessage: 'syncTimeOrigin', - timeOrigin: performance.timeOrigin, - }); + + let messageListener; + // In single threaded mode, we simply invoke the message listener as a + // microtask, as it's much cheaper than doing a full postMessage + skwasm_registerMessageListener = function(threadId, listener) { + messageListener = listener; } - }; - skwasm_getCurrentTimestamp = function() { - return performance.now() + timeOriginDelta; - }; - skwasm_postMessage = function(message, transfers, threadId) { - if (threadId) { - _wasmWorkers[threadId].postMessage(message, transfers); - } else { - postMessage(message, transfers); - } - }; + skwasm_getCurrentTimestamp = function() { + return performance.now(); + }; + skwasm_postMessage = function(message, transfers, threadId) { + // If we're in single-threaded mode, we shouldn't use postMessage, as + // it ends up being quite expensive. Instead, just queue a microtask. + queueMicrotask(() => messageListener(message)); + }; + } else { + _skwasm_isSingleThreaded = function() { + return false; + }; + + // This value represents the difference between the time origin of the main + // thread and whichever web worker this code is running on. This is so that + // when we report frame timings, that they are in the same time domain + // regardless of whether they are captured on the main thread or the web + // worker. + let timeOriginDelta = 0; + skwasm_registerMessageListener = function(threadId, listener) { + const eventListener = function({data}) { + const skwasmMessage = data.skwasmMessage; + if (!skwasmMessage) { + return; + } + if (skwasmMessage == 'syncTimeOrigin') { + timeOriginDelta = performance.timeOrigin - data.timeOrigin; + return; + } + listener(data); + }; + if (!threadId) { + addEventListener("message", eventListener); + } else { + _wasmWorkers[threadId].addEventListener("message", eventListener); + _wasmWorkers[threadId].postMessage({ + skwasmMessage: 'syncTimeOrigin', + timeOrigin: performance.timeOrigin, + }); + } + }; + skwasm_getCurrentTimestamp = function() { + return performance.now() + timeOriginDelta; + }; + skwasm_postMessage = function(message, transfers, threadId) { + if (threadId) { + _wasmWorkers[threadId].postMessage(message, { transfer: transfers } ); + } else { + postMessage(message, { transfer: transfers }); + } + }; + } const handleToCanvasMap = new Map(); const associatedObjectsMap = new Map(); - - _skwasm_isSingleThreaded = function() { - return Module["skwasmSingleThreaded"]; - }; _skwasm_setAssociatedObjectOnThread = function(threadId, pointer, object) { skwasm_postMessage({ skwasmMessage: 'setAssociatedObject',