Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit cdac5ca

Browse files
eyebrowsoffireharryterkelsen
authored andcommitted
Fix async image loading issues in skwasm. (#47117)
This fixes flutter/flutter#134045 There were a few different issues here: * We need to do our own message passing for rendering pictures. The async methods provided by emscripten have their own queue that can drain synchronously, so basically it's not guaranteed to be FIFO with other messages sent to the web worker or main thread. * When we drop frames, we should only drop intermediate frames, so that when the rendering flurry stops that the frame that is displayed is the last one that was actually requested. * We need to reset the GL context after lazy image creation, otherwise skia's renderer gets into a bad state for that frame.
1 parent f4fa7db commit cdac5ca

File tree

7 files changed

+107
-26
lines changed

7 files changed

+107
-26
lines changed

lib/web_ui/lib/src/engine/scene_view.dart

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@ abstract class PictureRenderer {
1616
FutureOr<DomImageBitmap> renderPicture(ScenePicture picture);
1717
}
1818

19+
class _SceneRender {
20+
_SceneRender(this.scene, this._completer) {
21+
scene.beginRender();
22+
}
23+
24+
final EngineScene scene;
25+
final Completer<void> _completer;
26+
27+
void done() {
28+
scene.endRender();
29+
_completer.complete();
30+
}
31+
}
32+
1933
// This class builds a DOM tree that composites an `EngineScene`.
2034
class EngineSceneView {
2135
factory EngineSceneView(PictureRenderer pictureRenderer) {
@@ -30,16 +44,38 @@ class EngineSceneView {
3044

3145
List<SliceContainer> containers = <SliceContainer>[];
3246

33-
int queuedRenders = 0;
34-
static const int kMaxQueuedRenders = 3;
47+
_SceneRender? _currentRender;
48+
_SceneRender? _nextRender;
49+
50+
Future<void> renderScene(EngineScene scene) {
51+
if (_currentRender != null) {
52+
// If a scene is already queued up, drop it and queue this one up instead
53+
// so that the scene view always displays the most recently requested scene.
54+
_nextRender?.done();
55+
final Completer<void> completer = Completer<void>();
56+
_nextRender = _SceneRender(scene, completer);
57+
return completer.future;
58+
}
59+
final Completer<void> completer = Completer<void>();
60+
_currentRender = _SceneRender(scene, completer);
61+
_kickRenderLoop();
62+
return completer.future;
63+
}
3564

36-
Future<void> renderScene(EngineScene scene) async {
37-
if (queuedRenders >= kMaxQueuedRenders) {
65+
Future<void> _kickRenderLoop() async {
66+
final _SceneRender current = _currentRender!;
67+
await _renderScene(current.scene);
68+
current.done();
69+
_currentRender = _nextRender;
70+
_nextRender = null;
71+
if (_currentRender == null) {
3872
return;
73+
} else {
74+
return _kickRenderLoop();
3975
}
40-
queuedRenders += 1;
76+
}
4177

42-
scene.beginRender();
78+
Future<void> _renderScene(EngineScene scene) async {
4379
final List<LayerSlice> slices = scene.rootLayer.slices;
4480
final Iterable<Future<DomImageBitmap?>> renderFutures = slices.map(
4581
(LayerSlice slice) async => switch (slice) {
@@ -113,9 +149,6 @@ class EngineSceneView {
113149
sceneElement.removeChild(currentElement);
114150
currentElement = sibling;
115151
}
116-
scene.endRender();
117-
118-
queuedRenders -= 1;
119152
}
120153
}
121154

lib/web_ui/skwasm/image.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ class TextureSourceImageGenerator : public GrExternalTextureGenerator {
101101

102102
auto backendTexture = GrBackendTextures::MakeGL(
103103
fInfo.width(), fInfo.height(), mipmapped, glInfo);
104+
105+
// In order to bind the image source to the texture, makeTexture has changed
106+
// which texture is "in focus" for the WebGL context.
107+
GrAsDirectContext(context)->resetContext(kTextureBinding_GrGLBackendState);
104108
return std::make_unique<ExternalWebGLTexture>(
105109
backendTexture, glInfo.fID, emscripten_webgl_get_current_context());
106110
}

lib/web_ui/skwasm/library_skwasm_support.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ mergeInto(LibraryManager.library, {
2727
return;
2828
}
2929
switch (skwasmMessage) {
30+
case 'renderPicture':
31+
_surface_renderPictureOnWorker(data.surface, data.picture, data.callbackId);
32+
return;
3033
case 'onRenderComplete':
3134
_surface_onRenderComplete(data.surface, data.callbackId, data.imageBitmap);
3235
return;
@@ -51,6 +54,14 @@ mergeInto(LibraryManager.library, {
5154
PThread.pthreads[threadId].addEventListener("message", eventListener);
5255
}
5356
};
57+
_skwasm_dispatchRenderPicture = function(threadId, surfaceHandle, pictureHandle, callbackId) {
58+
PThread.pthreads[threadId].postMessage({
59+
skwasmMessage: 'renderPicture',
60+
surface: surfaceHandle,
61+
picture: pictureHandle,
62+
callbackId,
63+
});
64+
};
5465
_skwasm_createOffscreenCanvas = function(width, height) {
5566
const canvas = new OffscreenCanvas(width, height);
5667
var contextAttributes = {
@@ -114,6 +125,8 @@ mergeInto(LibraryManager.library, {
114125
skwasm_disposeAssociatedObjectOnThread__deps: ['$skwasm_support_setup'],
115126
skwasm_registerMessageListener: function() {},
116127
skwasm_registerMessageListener__deps: ['$skwasm_support_setup'],
128+
skwasm_dispatchRenderPicture: function() {},
129+
skwasm_dispatchRenderPicture__deps: ['$skwasm_support_setup'],
117130
skwasm_createOffscreenCanvas: function () {},
118131
skwasm_createOffscreenCanvas__deps: ['$skwasm_support_setup'],
119132
skwasm_resizeCanvas: function () {},

lib/web_ui/skwasm/skwasm_support.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <emscripten/threading.h>
66
#include <cinttypes>
7+
#include "third_party/skia/include/core/SkPicture.h"
78

89
namespace Skwasm {
910
class Surface;
@@ -19,6 +20,10 @@ extern SkwasmObject skwasm_getAssociatedObject(void* pointer);
1920
extern void skwasm_disposeAssociatedObjectOnThread(unsigned long threadId,
2021
void* pointer);
2122
extern void skwasm_registerMessageListener(pthread_t threadId);
23+
extern void skwasm_dispatchRenderPicture(unsigned long threadId,
24+
Skwasm::Surface* surface,
25+
SkPicture* picture,
26+
uint32_t callbackId);
2227
extern uint32_t skwasm_createOffscreenCanvas(int width, int height);
2328
extern void skwasm_resizeCanvas(uint32_t contextHandle, int width, int height);
2429
extern void skwasm_captureImageBitmap(Skwasm::Surface* surfaceHandle,

lib/web_ui/skwasm/surface.cpp

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ uint32_t Surface::renderPicture(SkPicture* picture) {
4343
assert(emscripten_is_main_browser_thread());
4444
uint32_t callbackId = ++_currentCallbackId;
4545
picture->ref();
46-
emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VIII,
47-
reinterpret_cast<void*>(fRenderPicture),
48-
nullptr, this, picture, callbackId);
46+
skwasm_dispatchRenderPicture(_thread, this, picture, callbackId);
4947
return callbackId;
5048
}
5149

@@ -138,7 +136,7 @@ void Surface::_recreateSurface() {
138136
}
139137

140138
// Worker thread only
141-
void Surface::_renderPicture(const SkPicture* picture, uint32_t callbackId) {
139+
void Surface::renderPictureOnWorker(SkPicture* picture, uint32_t callbackId) {
142140
SkRect pictureRect = picture->cullRect();
143141
SkIRect roundedOutRect;
144142
pictureRect.roundOut(&roundedOutRect);
@@ -195,13 +193,6 @@ void Surface::fDispose(Surface* surface) {
195193
surface->_dispose();
196194
}
197195

198-
void Surface::fRenderPicture(Surface* surface,
199-
SkPicture* picture,
200-
uint32_t callbackId) {
201-
surface->_renderPicture(picture, callbackId);
202-
picture->unref();
203-
}
204-
205196
void Surface::fOnRasterizeComplete(Surface* surface,
206197
SkData* imageData,
207198
uint32_t callbackId) {
@@ -239,6 +230,13 @@ SKWASM_EXPORT uint32_t surface_renderPicture(Surface* surface,
239230
return surface->renderPicture(picture);
240231
}
241232

233+
SKWASM_EXPORT void surface_renderPictureOnWorker(Surface* surface,
234+
SkPicture* picture,
235+
uint32_t callbackId) {
236+
surface->renderPictureOnWorker(picture, callbackId);
237+
picture->unref();
238+
}
239+
242240
SKWASM_EXPORT uint32_t surface_rasterizeImage(Surface* surface,
243241
SkImage* image,
244242
ImageByteFormat format) {

lib/web_ui/skwasm/surface.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,15 @@ class Surface {
7070
std::unique_ptr<TextureSourceWrapper> createTextureSourceWrapper(
7171
SkwasmObject textureSource);
7272

73+
// Worker thread
74+
void renderPictureOnWorker(SkPicture* picture, uint32_t callbackId);
75+
7376
private:
7477
void _runWorker();
7578
void _init();
7679
void _dispose();
7780
void _resizeCanvasToFit(int width, int height);
7881
void _recreateSurface();
79-
void _renderPicture(const SkPicture* picture, uint32_t callbackId);
8082
void _rasterizeImage(SkImage* image,
8183
ImageByteFormat format,
8284
uint32_t callbackId);
@@ -99,9 +101,6 @@ class Surface {
99101
pthread_t _thread;
100102

101103
static void fDispose(Surface* surface);
102-
static void fRenderPicture(Surface* surface,
103-
SkPicture* picture,
104-
uint32_t callbackId);
105104
static void fOnRenderComplete(Surface* surface,
106105
uint32_t callbackId,
107106
SkwasmObject imageBitmap);

lib/web_ui/test/engine/scene_view_test.dart

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class StubPictureRenderer implements PictureRenderer {
2323

2424
@override
2525
Future<DomImageBitmap> renderPicture(ScenePicture picture) async {
26+
renderedPictures.add(picture);
2627
final ui.Rect cullRect = picture.cullRect;
2728
final DomImageBitmap bitmap = (await createSizedImageBitmap(
2829
scratchCanvasElement,
@@ -32,12 +33,16 @@ class StubPictureRenderer implements PictureRenderer {
3233
cullRect.height.toInt()))!;
3334
return bitmap;
3435
}
36+
37+
List<ScenePicture> renderedPictures = <ScenePicture>[];
3538
}
3639

3740
void testMain() {
3841
late EngineSceneView sceneView;
42+
late StubPictureRenderer stubPictureRenderer;
3943
setUp(() {
40-
sceneView = EngineSceneView(StubPictureRenderer());
44+
stubPictureRenderer = StubPictureRenderer();
45+
sceneView = EngineSceneView(stubPictureRenderer);
4146
});
4247

4348
test('SceneView places canvas according to device-pixel ratio', () async {
@@ -74,7 +79,7 @@ void testMain() {
7479
debugOverrideDevicePixelRatio(null);
7580
});
7681

77-
test('SceneView places canvas according to device-pixel ratio', () async {
82+
test('SceneView places platform view according to device-pixel ratio', () async {
7883
debugOverrideDevicePixelRatio(2.0);
7984

8085
final PlatformView platformView = PlatformView(
@@ -103,4 +108,28 @@ void testMain() {
103108

104109
debugOverrideDevicePixelRatio(null);
105110
});
111+
112+
test('SceneView always renders most recent picture and skips intermediate pictures', () async {
113+
final List<StubPicture> pictures = <StubPicture>[];
114+
final List<Future<void>> renderFutures = <Future<void>>[];
115+
for (int i = 1; i < 20; i++) {
116+
final StubPicture picture = StubPicture(const ui.Rect.fromLTWH(
117+
50,
118+
80,
119+
100,
120+
120,
121+
));
122+
pictures.add(picture);
123+
final EngineRootLayer rootLayer = EngineRootLayer();
124+
rootLayer.slices.add(PictureSlice(picture));
125+
final EngineScene scene = EngineScene(rootLayer);
126+
renderFutures.add(sceneView.renderScene(scene));
127+
}
128+
await Future.wait(renderFutures);
129+
130+
// Should just render the first and last pictures and skip the one inbetween.
131+
expect(stubPictureRenderer.renderedPictures.length, 2);
132+
expect(stubPictureRenderer.renderedPictures.first, pictures.first);
133+
expect(stubPictureRenderer.renderedPictures.last, pictures.last);
134+
});
106135
}

0 commit comments

Comments
 (0)