blob: 12a9e7cdc21be2aa0f60633442908852806ba0e6 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.6
part of engine;
class SurfaceSceneBuilder implements ui.SceneBuilder {
SurfaceSceneBuilder() {
_surfaceStack.add(PersistedScene(_lastFrameScene));
}
final List<PersistedContainerSurface> _surfaceStack =
<PersistedContainerSurface>[];
/// The scene built by this scene builder.
///
/// This getter should only be called after all surfaces are built.
PersistedScene get _persistedScene {
return _surfaceStack.first;
}
/// The surface currently being built.
PersistedContainerSurface get _currentSurface => _surfaceStack.last;
ui.EngineLayer _pushSurface(PersistedContainerSurface surface) {
// Only attempt to update if the update is requested and the surface is in
// the live tree.
if (surface.oldLayer != null) {
assert(surface.oldLayer.runtimeType == surface.runtimeType);
assert(debugAssertSurfaceState(
surface.oldLayer, PersistedSurfaceState.active));
surface.oldLayer.state = PersistedSurfaceState.pendingUpdate;
}
_adoptSurface(surface);
_surfaceStack.add(surface);
return surface;
}
/// Adds [surface] to the surface tree.
///
/// This is used by tests.
void debugAddSurface(PersistedSurface surface) {
if (assertionsEnabled) {
_addSurface(surface);
}
}
void _addSurface(PersistedSurface surface) {
_adoptSurface(surface);
}
void _adoptSurface(PersistedSurface surface) {
_currentSurface.appendChild(surface);
}
/// Pushes an offset operation onto the operation stack.
///
/// This is equivalent to [pushTransform] with a matrix with only translation.
///
/// See [pop] for details about the operation stack.
@override
ui.OffsetEngineLayer pushOffset(
double dx,
double dy, {
ui.OffsetEngineLayer oldLayer,
}) {
return _pushSurface(PersistedOffset(oldLayer, dx, dy));
}
/// Pushes a transform operation onto the operation stack.
///
/// The objects are transformed by the given matrix before rasterization.
///
/// See [pop] for details about the operation stack.
@override
ui.TransformEngineLayer pushTransform(
Float64List matrix4, {
ui.TransformEngineLayer oldLayer,
}) {
if (matrix4 == null) {
throw ArgumentError('"matrix4" argument cannot be null');
}
if (matrix4.length != 16) {
throw ArgumentError('"matrix4" must have 16 entries.');
}
// TODO(yjbanov): make this final after NNBD ships definite assignment.
/*final*/ Float32List matrix;
if (_surfaceStack.length == 1) {
// Top level transform contains view configuration to scale
// scene to devicepixelratio. Use identity instead since CSS uses
// logical device pixels.
if (!ui.debugEmulateFlutterTesterEnvironment) {
assert(matrix4[0] == window.devicePixelRatio &&
matrix4[5] == window.devicePixelRatio);
}
matrix = Matrix4.identity().storage;
} else {
matrix = toMatrix32(matrix4);
}
return _pushSurface(PersistedTransform(oldLayer, matrix));
}
/// Pushes a rectangular clip operation onto the operation stack.
///
/// Rasterization outside the given rectangle is discarded.
///
/// See [pop] for details about the operation stack, and [Clip] for different clip modes.
/// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]).
@override
ui.ClipRectEngineLayer pushClipRect(
ui.Rect rect, {
ui.Clip clipBehavior = ui.Clip.antiAlias,
ui.ClipRectEngineLayer oldLayer,
}) {
assert(clipBehavior != null);
assert(clipBehavior != ui.Clip.none);
return _pushSurface(PersistedClipRect(oldLayer, rect));
}
/// Pushes a rounded-rectangular clip operation onto the operation stack.
///
/// Rasterization outside the given rounded rectangle is discarded.
///
/// See [pop] for details about the operation stack.
@override
ui.ClipRRectEngineLayer pushClipRRect(
ui.RRect rrect, {
ui.Clip clipBehavior,
ui.ClipRRectEngineLayer oldLayer,
}) {
return _pushSurface(PersistedClipRRect(oldLayer, rrect, clipBehavior));
}
/// Pushes a path clip operation onto the operation stack.
///
/// Rasterization outside the given path is discarded.
///
/// See [pop] for details about the operation stack.
@override
ui.ClipPathEngineLayer pushClipPath(
ui.Path path, {
ui.Clip clipBehavior = ui.Clip.antiAlias,
ui.ClipPathEngineLayer oldLayer,
}) {
assert(clipBehavior != null);
assert(clipBehavior != ui.Clip.none);
return _pushSurface(PersistedClipPath(oldLayer, path, clipBehavior));
}
/// Pushes an opacity operation onto the operation stack.
///
/// The given alpha value is blended into the alpha value of the objects'
/// rasterization. An alpha value of 0 makes the objects entirely invisible.
/// An alpha value of 255 has no effect (i.e., the objects retain the current
/// opacity).
///
/// See [pop] for details about the operation stack.
@override
ui.OpacityEngineLayer pushOpacity(
int alpha, {
ui.Offset offset = ui.Offset.zero,
ui.OpacityEngineLayer oldLayer,
}) {
return _pushSurface(PersistedOpacity(oldLayer, alpha, offset));
}
/// Pushes a color filter operation onto the operation stack.
///
/// The given color is applied to the objects' rasterization using the given
/// blend mode.
///
/// {@macro dart.ui.sceneBuilder.oldLayer}
///
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
///
/// See [pop] for details about the operation stack.
@override
ui.ColorFilterEngineLayer pushColorFilter(
ui.ColorFilter filter, {
ui.ColorFilterEngineLayer oldLayer,
}) {
assert(filter != null);
throw UnimplementedError();
}
/// Pushes an image filter operation onto the operation stack.
///
/// The given filter is applied to the children's rasterization before compositing them into
/// the scene.
///
/// {@macro dart.ui.sceneBuilder.oldLayer}
///
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
///
/// See [pop] for details about the operation stack.
@override
ui.ImageFilterEngineLayer pushImageFilter(
ui.ImageFilter filter, {
ui.ImageFilterEngineLayer oldLayer,
}) {
assert(filter != null);
return _pushSurface(PersistedImageFilter(oldLayer, filter));
}
/// Pushes a backdrop filter operation onto the operation stack.
///
/// The given filter is applied to the current contents of the scene prior to
/// rasterizing the given objects.
///
/// See [pop] for details about the operation stack.
@override
ui.BackdropFilterEngineLayer pushBackdropFilter(
ui.ImageFilter filter, {
ui.BackdropFilterEngineLayer oldLayer,
}) {
return _pushSurface(PersistedBackdropFilter(oldLayer, filter));
}
/// Pushes a shader mask operation onto the operation stack.
///
/// The given shader is applied to the object's rasterization in the given
/// rectangle using the given blend mode.
///
/// See [pop] for details about the operation stack.
@override
ui.ShaderMaskEngineLayer pushShaderMask(
ui.Shader shader,
ui.Rect maskRect,
ui.BlendMode blendMode, {
ui.ShaderMaskEngineLayer oldLayer,
}) {
throw UnimplementedError();
}
/// Pushes a physical layer operation for an arbitrary shape onto the
/// operation stack.
///
/// By default, the layer's content will not be clipped (clip = [Clip.none]).
/// If clip equals [Clip.hardEdge], [Clip.antiAlias], or [Clip.antiAliasWithSaveLayer],
/// then the content is clipped to the given shape defined by [path].
///
/// If [elevation] is greater than 0.0, then a shadow is drawn around the layer.
/// [shadowColor] defines the color of the shadow if present and [color] defines the
/// color of the layer background.
///
/// See [pop] for details about the operation stack, and [Clip] for different clip modes.
@override
ui.PhysicalShapeEngineLayer pushPhysicalShape({
ui.Path path,
double elevation,
ui.Color color,
ui.Color shadowColor,
ui.Clip clipBehavior = ui.Clip.none,
ui.PhysicalShapeEngineLayer oldLayer,
}) {
assert(color != null, 'color must not be null');
return _pushSurface(PersistedPhysicalShape(
oldLayer,
path,
elevation,
color.value,
shadowColor?.value ?? 0xFF000000,
clipBehavior,
));
}
/// Add a retained engine layer subtree from previous frames.
///
/// All the engine layers that are in the subtree of the retained layer will
/// be automatically appended to the current engine layer tree.
///
/// Therefore, when implementing a subclass of the [Layer] concept defined in
/// the rendering layer of Flutter's framework, once this is called, there's
/// no need to call [addToScene] for its children layers.
@override
void addRetained(ui.EngineLayer retainedLayer) {
final PersistedContainerSurface retainedSurface = retainedLayer;
if (assertionsEnabled) {
assert(debugAssertSurfaceState(retainedSurface,
PersistedSurfaceState.active, PersistedSurfaceState.released));
}
retainedSurface.tryRetain();
_adoptSurface(retainedSurface);
}
/// Ends the effect of the most recently pushed operation.
///
/// Internally the scene builder maintains a stack of operations. Each of the
/// operations in the stack applies to each of the objects added to the scene.
/// Calling this function removes the most recently added operation from the
/// stack.
@override
void pop() {
assert(_surfaceStack.isNotEmpty);
_surfaceStack.removeLast();
}
/// Adds an object to the scene that displays performance statistics.
///
/// Useful during development to assess the performance of the application.
/// The enabledOptions controls which statistics are displayed. The bounds
/// controls where the statistics are displayed.
///
/// enabledOptions is a bit field with the following bits defined:
/// - 0x01: displayRasterizerStatistics - show raster thread frame time
/// - 0x02: visualizeRasterizerStatistics - graph raster thread frame times
/// - 0x04: displayEngineStatistics - show UI thread frame time
/// - 0x08: visualizeEngineStatistics - graph UI thread frame times
/// Set enabledOptions to 0x0F to enable all the currently defined features.
///
/// The "UI thread" is the thread that includes all the execution of
/// the main Dart isolate (the isolate that can call
/// [Window.render]). The UI thread frame time is the total time
/// spent executing the [Window.onBeginFrame] callback. The "raster
/// thread" is the thread (running on the CPU) that subsequently
/// processes the [Scene] provided by the Dart code to turn it into
/// GPU commands and send it to the GPU.
///
/// See also the [PerformanceOverlayOption] enum in the rendering library.
/// for more details.
@override
void addPerformanceOverlay(int enabledOptions, ui.Rect bounds) {
_addPerformanceOverlay(
enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom);
}
/// Whether we've already warned the user about the lack of the performance
/// overlay or not.
///
/// We use this to avoid spamming the console with redundant warning messages.
static bool _webOnlyDidWarnAboutPerformanceOverlay = false;
void _addPerformanceOverlay(
int enabledOptions,
double left,
double right,
double top,
double bottom,
) {
if (!_webOnlyDidWarnAboutPerformanceOverlay) {
_webOnlyDidWarnAboutPerformanceOverlay = true;
html.window.console
.warn('The performance overlay isn\'t supported on the web');
}
}
/// Adds a [Picture] to the scene.
///
/// The picture is rasterized at the given offset.
@override
void addPicture(
ui.Offset offset,
ui.Picture picture, {
bool isComplexHint = false,
bool willChangeHint = false,
}) {
int hints = 0;
if (isComplexHint) {
hints |= 1;
}
if (willChangeHint) {
hints |= 2;
}
_addSurface(persistedPictureFactory(offset.dx, offset.dy, picture, hints));
}
/// Adds a backend texture to the scene.
///
/// The texture is scaled to the given size and rasterized at the given
/// offset.
@override
void addTexture(
int textureId, {
ui.Offset offset = ui.Offset.zero,
double width = 0.0,
double height = 0.0,
bool freeze = false,
}) {
assert(offset != null, 'Offset argument was null');
_addTexture(offset.dx, offset.dy, width, height, textureId);
}
void _addTexture(
double dx, double dy, double width, double height, int textureId) {
// In test mode, allow this to be a no-op.
if (!ui.debugEmulateFlutterTesterEnvironment) {
throw UnimplementedError('Textures are not supported in Flutter Web');
}
}
/// Adds a platform view (e.g an iOS UIView) to the scene.
///
/// Only supported on iOS, this is currently a no-op on other platforms.
///
/// On iOS this layer splits the current output surface into two surfaces, one for the scene nodes
/// preceding the platform view, and one for the scene nodes following the platform view.
///
/// ## Performance impact
///
/// Adding an additional surface doubles the amount of graphics memory directly used by Flutter
/// for output buffers. Quartz might allocated extra buffers for compositing the Flutter surfaces
/// and the platform view.
///
/// With a platform view in the scene, Quartz has to composite the two Flutter surfaces and the
/// embedded UIView. In addition to that, on iOS versions greater than 9, the Flutter frames are
/// synchronized with the UIView frames adding additional performance overhead.
@override
void addPlatformView(
int viewId, {
ui.Offset offset = ui.Offset.zero,
double width = 0.0,
double height = 0.0,
}) {
assert(offset != null, 'Offset argument was null');
_addPlatformView(offset.dx, offset.dy, width, height, viewId);
}
void _addPlatformView(
double dx,
double dy,
double width,
double height,
int viewId,
) {
_addSurface(PersistedPlatformView(viewId, dx, dy, width, height));
}
/// (Fuchsia-only) Adds a scene rendered by another application to the scene
/// for this application.
@override
void addChildScene({
ui.Offset offset = ui.Offset.zero,
double width = 0.0,
double height = 0.0,
ui.SceneHost sceneHost,
bool hitTestable = true,
}) {
_addChildScene(offset.dx, offset.dy, width, height, sceneHost, hitTestable);
}
void _addChildScene(
double dx,
double dy,
double width,
double height,
ui.SceneHost sceneHost,
bool hitTestable,
) {
throw UnimplementedError();
}
/// Sets a threshold after which additional debugging information should be
/// recorded.
///
/// Currently this interface is difficult to use by end-developers. If you're
/// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev).
/// We'll hopefully be able to figure out how to make this feature more useful
/// to you.
@override
void setRasterizerTracingThreshold(int frameInterval) {}
/// Sets whether the raster cache should checkerboard cached entries. This is
/// only useful for debugging purposes.
///
/// The compositor can sometimes decide to cache certain portions of the
/// widget hierarchy. Such portions typically don't change often from frame to
/// frame and are expensive to render. This can speed up overall rendering.
/// However, there is certain upfront cost to constructing these cache
/// entries. And, if the cache entries are not used very often, this cost may
/// not be worth the speedup in rendering of subsequent frames. If the
/// developer wants to be certain that populating the raster cache is not
/// causing stutters, this option can be set. Depending on the observations
/// made, hints can be provided to the compositor that aid it in making better
/// decisions about caching.
///
/// Currently this interface is difficult to use by end-developers. If you're
/// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev).
@override
void setCheckerboardRasterCacheImages(bool checkerboard) {}
/// Sets whether the compositor should checkerboard layers that are rendered
/// to offscreen bitmaps.
///
/// This is only useful for debugging purposes.
@override
void setCheckerboardOffscreenLayers(bool checkerboard) {}
/// The scene recorded in the last frame.
///
/// This is a surface tree that holds onto the DOM elements that can be reused
/// on the next frame.
static PersistedScene _lastFrameScene;
/// Returns the computed persisted scene graph recorded in the last frame.
///
/// This is only available in debug mode. It returns `null` in profile and
/// release modes.
static PersistedScene get debugLastFrameScene {
PersistedScene result;
assert(() {
result = _lastFrameScene;
return true;
}());
return result;
}
/// Discards information about previously rendered frames, including DOM
/// elements and cached canvases.
///
/// After calling this function new canvases will be created for the
/// subsequent scene. This is useful when tests need predictable canvas
/// sizes. If the cache is not cleared, then canvases allocated in one test
/// may be reused in another test.
static void debugForgetFrameScene() {
_lastFrameScene?.rootElement?.remove();
_lastFrameScene = null;
_clipIdCounter = 0;
_recycledCanvases.clear();
}
/// Finishes building the scene.
///
/// Returns a [Scene] containing the objects that have been added to
/// this scene builder. The [Scene] can then be displayed on the
/// screen with [Window.render].
///
/// After calling this function, the scene builder object is invalid and
/// cannot be used further.
@override
SurfaceScene build() {
timeAction<void>(kProfilePrerollFrame, () {
while (_surfaceStack.length > 1) {
// Auto-pop layers that were pushed without a corresponding pop.
pop();
}
_persistedScene.preroll();
});
return timeAction<SurfaceScene>(kProfileApplyFrame, () {
if (_lastFrameScene == null) {
_persistedScene.build();
} else {
_persistedScene.update(_lastFrameScene);
}
commitScene(_persistedScene);
_lastFrameScene = _persistedScene;
return SurfaceScene(_persistedScene.rootElement);
});
}
/// Set properties on the linked scene. These properties include its bounds,
/// as well as whether it can be the target of focus events or not.
@override
void setProperties(
double width,
double height,
double insetTop,
double insetRight,
double insetBottom,
double insetLeft,
bool focusable,
) {
throw UnimplementedError();
}
}
// TODO(yjbanov): in HTML the blur looks too aggressive. The current
// implementation was copied from the existing backdrop-filter
// but probably needs a revision.
String _imageFilterToCss(EngineImageFilter filter) {
return 'blur(${math.max(filter.sigmaX, filter.sigmaY) * 2}px)';
}