Skip to content

Commit

Permalink
[canvaskit] Add configuration for maximum canvases (#51735)
Browse files Browse the repository at this point in the history
Allows developers to customize the maximum amount of overlay canvases.

Fixes flutter/flutter#144589

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide] and the [C++,
Objective-C, Java style guides].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making or feature I am
adding, or the PR is [test-exempt]. See [testing the engine] for
instructions on writing and running engine tests.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I signed the [CLA].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
  • Loading branch information
harryterkelsen authored Apr 22, 2024
1 parent 35b6c7b commit 6d54e22
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 6 deletions.
6 changes: 3 additions & 3 deletions lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'dart:math' as math;

import 'package:ui/ui.dart' as ui;

import '../../engine.dart' show PlatformViewManager, longestIncreasingSubsequence;
import '../../engine.dart' show PlatformViewManager, configuration, longestIncreasingSubsequence;
import '../display.dart';
import '../dom.dart';
import '../html/path_to_svg_clip.dart';
Expand Down Expand Up @@ -49,7 +49,7 @@ class HtmlViewEmbedder {

/// The maximum number of render canvases to create. Too many canvases can
/// cause a performance burden.
static const int maximumCanvases = 8;
static int get maximumCanvases => configuration.canvasKitMaximumSurfaces;

/// The views that need to be recomposited into the scene on the next frame.
final Set<int> _viewsToRecomposite = <int>{};
Expand Down Expand Up @@ -478,7 +478,7 @@ class HtmlViewEmbedder {
final List<RenderingEntity> modifiedEntities =
List<RenderingEntity>.from(rendering.entities);
bool sawLastCanvas = false;
for (int i = rendering.entities.length - 1; i > 0; i--) {
for (int i = rendering.entities.length - 1; i >= 0; i--) {
final RenderingEntity entity = modifiedEntities[i];
if (entity is RenderingRenderCanvas) {
if (!sawLastCanvas) {
Expand Down
17 changes: 17 additions & 0 deletions lib/web_ui/lib/src/engine/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,18 @@ class FlutterConfiguration {
'FLUTTER_WEB_CANVASKIT_FORCE_CPU_ONLY',
);

/// The maximum number of canvases to use when rendering in CanvasKit.
///
/// Limits the amount of overlays that can be created.
int get canvasKitMaximumSurfaces {
final int maxSurfaces =
_configuration?.canvasKitMaximumSurfaces?.toInt() ?? 8;
if (maxSurfaces < 1) {
return 1;
}
return maxSurfaces;
}

/// Set this flag to `true` to cause the engine to visualize the semantics tree
/// on the screen for debugging.
///
Expand Down Expand Up @@ -361,6 +373,11 @@ extension JsFlutterConfigurationExtension on JsFlutterConfiguration {
external JSBoolean? get _canvasKitForceCpuOnly;
bool? get canvasKitForceCpuOnly => _canvasKitForceCpuOnly?.toDart;

@JS('canvasKitMaximumSurfaces')
external JSNumber? get _canvasKitMaximumSurfaces;
double? get canvasKitMaximumSurfaces =>
_canvasKitMaximumSurfaces?.toDartDouble;

@JS('debugShowSemanticsNodes')
external JSBoolean? get _debugShowSemanticsNodes;
bool? get debugShowSemanticsNodes => _debugShowSemanticsNodes?.toDart;
Expand Down
70 changes: 69 additions & 1 deletion lib/web_ui/test/canvaskit/embedded_views_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:js_interop';

import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
Expand Down Expand Up @@ -1119,7 +1120,7 @@ void testMain() {
]);
});

test('optimized overlays correctly with transforms and clips', () async {
test('optimizes overlays correctly with transforms and clips', () async {
ui_web.platformViewRegistry.registerViewFactory(
'test-view',
(int viewId) => createDomHTMLDivElement()..className = 'platform-view',
Expand Down Expand Up @@ -1152,6 +1153,73 @@ void testMain() {
_platformView,
]);
});

test('can customize amount of overlays', () async {
final CkPicture testPicture =
paintPicture(const ui.Rect.fromLTRB(0, 0, 10, 10), (CkCanvas canvas) {
canvas.drawCircle(const ui.Offset(5, 5), 5, CkPaint());
});

// Initialize all platform views to be used in the test.
final List<int> platformViewIds = <int>[];
for (int i = 0; i < 16; i++) {
ui_web.platformViewRegistry.registerViewFactory(
'test-platform-view',
(int viewId) => createDomHTMLDivElement()..id = 'view-$i',
);
await createPlatformView(i, 'test-platform-view');
platformViewIds.add(i);
}

Future<void> renderTestScene({required int viewCount}) async {
final LayerSceneBuilder sb = LayerSceneBuilder();
sb.pushOffset(0, 0);
for (int i = 0; i < viewCount; i++) {
sb.addPicture(ui.Offset.zero, testPicture);
sb.addPlatformView(i, width: 10, height: 10);
}
await renderScene(sb.build());
}

// Set maximum overlays to 4.
debugOverrideJsConfiguration(<String, Object?>{
'canvasKitMaximumSurfaces': 4,
}.jsify() as JsFlutterConfiguration?);

await renderTestScene(viewCount: 8);
_expectSceneMatches(<_EmbeddedViewMarker>[
_overlay,
_platformView,
_overlay,
_platformView,
_overlay,
_platformView,
_platformView,
_platformView,
_platformView,
_platformView,
_overlay,
_platformView,
]);

// Set maximum overlays to -1. Should default to 1.
debugOverrideJsConfiguration(<String, Object?>{
'canvasKitMaximumSurfaces': -1,
}.jsify() as JsFlutterConfiguration?);

await renderTestScene(viewCount: 8);
_expectSceneMatches(<_EmbeddedViewMarker>[
_platformView,
_platformView,
_platformView,
_platformView,
_platformView,
_platformView,
_platformView,
_overlay,
_platformView,
]);
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void testMain() {
// A property under test, that we'll try to read later.
js_util.setProperty(config, 'nonce', 'some_nonce');
// A non-existing property to verify our js-interop doesn't crash.
js_util.setProperty(config, 'canvasKitMaximumSurfaces', 32.0);
js_util.setProperty(config, 'nonexistentProperty', 32.0);

// Remove window.flutterConfiguration (if it's there)
js_util.setProperty(domWindow, 'flutterConfiguration', null);
Expand Down
2 changes: 1 addition & 1 deletion lib/web_ui/test/engine/configuration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ void testMain() {
expect(() {
config.setUserConfiguration(
js_util.jsify(<String, Object?>{
'canvasKitMaximumSurfaces': 32.0,
'nonexistentProperty': 32.0,
}) as JsFlutterConfiguration);
}, returnsNormally);
});
Expand Down

0 comments on commit 6d54e22

Please sign in to comment.