Add debug json mechanism for EngineSceneBuilder. (#165821)
I added some debugging mechanisms that can help create a minimal test case more easily: * In debug mode, you can press `Alt+F10` and the engine will generate a JSON representation of the most recently rendered scene and download it. * Added a script called `generate_scene_test.dart` which can take that JSON and generate a dart file which renders a scene with the identical hierarchy and layout. All the pictures are rendered as squares with random-ish colors.
This commit is contained in:
parent
838515b967
commit
355129961c
259
engine/src/flutter/lib/web_ui/dev/generate_scene_test.dart
Normal file
259
engine/src/flutter/lib/web_ui/dev/generate_scene_test.dart
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
// This script takes a json file that comes from the `debugJsonOutput` of an
|
||||||
|
// `EngineScene` object and generates a function that builds a scene with the
|
||||||
|
// same dimensions and layering. This makes it easier to take a complex scene
|
||||||
|
// rendered by an app and whittle it down to a minimal test case.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
// A bunch of visually unique colors to cycle through.
|
||||||
|
const List<String> colorStrings = [
|
||||||
|
'0xFF7090da',
|
||||||
|
'0xFF9cb835',
|
||||||
|
'0xFF7c64d4',
|
||||||
|
'0xFF5fc250',
|
||||||
|
'0xFFc853be',
|
||||||
|
'0xFF4e8d2b',
|
||||||
|
'0xFFdb3f80',
|
||||||
|
'0xFF54bd7b',
|
||||||
|
'0xFFd2404e',
|
||||||
|
'0xFF53c4ad',
|
||||||
|
'0xFFd34a2a',
|
||||||
|
'0xFF46aed7',
|
||||||
|
'0xFFdc7a2d',
|
||||||
|
'0xFF6561a8',
|
||||||
|
'0xFFc0ab39',
|
||||||
|
'0xFFa44c8c',
|
||||||
|
'0xFF408147',
|
||||||
|
'0xFFd18dce',
|
||||||
|
'0xFF707822',
|
||||||
|
'0xFFae4462',
|
||||||
|
'0xFF308870',
|
||||||
|
'0xFFd99a37',
|
||||||
|
'0xFF935168',
|
||||||
|
'0xFF9eb46b',
|
||||||
|
'0xFFe1808a',
|
||||||
|
'0xFF627037',
|
||||||
|
'0xFFda815c',
|
||||||
|
'0xFF8e6c2b',
|
||||||
|
'0xFF9f4f30',
|
||||||
|
'0xFFd4a66f',
|
||||||
|
];
|
||||||
|
int colorIndex = 0;
|
||||||
|
|
||||||
|
String getNextColor() {
|
||||||
|
final colorString = colorStrings[colorIndex];
|
||||||
|
colorIndex++;
|
||||||
|
colorIndex %= colorStrings.length;
|
||||||
|
return colorString;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getColorAsString() {
|
||||||
|
return 'const ui.Color(${getNextColor()})';
|
||||||
|
}
|
||||||
|
|
||||||
|
String offsetAsString(Object? offset) {
|
||||||
|
offset as Map<String, Object?>?;
|
||||||
|
offset!;
|
||||||
|
final num x = offset['x']! as num;
|
||||||
|
final num y = offset['y']! as num;
|
||||||
|
if (x == 0 && y == 0) {
|
||||||
|
return 'ui.Offset.zero';
|
||||||
|
}
|
||||||
|
return 'const ui.Offset($x, $y)';
|
||||||
|
}
|
||||||
|
|
||||||
|
String rRectAsString(Object? rRect) {
|
||||||
|
rRect as Map<String, Object?>?;
|
||||||
|
rRect!;
|
||||||
|
return 'ui.RRect.fromLTRBAndCorners('
|
||||||
|
'${rRect['left']}, '
|
||||||
|
'${rRect['top']}, '
|
||||||
|
'${rRect['right']}, '
|
||||||
|
'${rRect['bottom']}, '
|
||||||
|
'topLeft: const ui.Radius.elliptical(${rRect['tlRadiusX']}, ${rRect['tlRadiusY']}), '
|
||||||
|
'topRight: const ui.Radius.elliptical(${rRect['trRadiusX']}, ${rRect['trRadiusY']}), '
|
||||||
|
'bottomRight: const ui.Radius.elliptical(${rRect['brRadiusX']}, ${rRect['brRadiusY']}), '
|
||||||
|
'bottomLeft: const ui.Radius.elliptical(${rRect['blRadiusX']}, ${rRect['blRadiusY']}))';
|
||||||
|
}
|
||||||
|
|
||||||
|
String rectAsString(Object? rect) {
|
||||||
|
rect as Map<String, Object?>?;
|
||||||
|
rect!;
|
||||||
|
final num left = rect['left']! as num;
|
||||||
|
final num top = rect['top']! as num;
|
||||||
|
final num right = rect['right']! as num;
|
||||||
|
final num bottom = rect['bottom']! as num;
|
||||||
|
|
||||||
|
if (left == 0 && top == 0 && right == 0 && bottom == 0) {
|
||||||
|
return 'ui.Rect.zero';
|
||||||
|
}
|
||||||
|
return 'const ui.Rect.fromLTRB($left, $top, $right, $bottom)';
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitShaderMaskOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
// TODO(jacksongardner): implement
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitTransformOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
final matrixValues = operation['matrix']! as List<Object?>;
|
||||||
|
print('${indent}builder.pushTransform(Float64List.fromList([${matrixValues.join(', ')}]));');
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitOpacityOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
final offset = operation['offset']! as Map<String, Object?>;
|
||||||
|
print('${indent}builder.pushOpacity(${operation['alpha']}, offset: ${offsetAsString(offset)});');
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitOffsetOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
final offset = operation['offset']! as Map<String, Object?>;
|
||||||
|
print('${indent}builder.pushOffset(${offset['x']}, ${offset['y']});');
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitImageFilterOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
// TODO(jacksongardner): implement
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitColorFilterOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
// TODO(jacksongardner): implement
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitClipRRectOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
print(
|
||||||
|
'${indent}builder.pushClipRRect(${rRectAsString(operation['rrect'])}, clipBehavior: ui.Clip.${operation['clip']});',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitClipRectOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
print(
|
||||||
|
'${indent}builder.pushClipRect(${rectAsString(operation['rect'])}, clipBehavior: ui.Clip.${operation['clip']});',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitClipPathOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
print(
|
||||||
|
'${indent}builder.pushClipPath(ui.Path()..addRect(${rectAsString(operation['pathBounds'])}), clipBehavior: ui.Clip.${operation['clip']});',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitBackdropFilterOperation(Map<String, Object?> operation, String indent) {
|
||||||
|
// TODO(jacksongardner): implement
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitOperation(Object? operation, String indent) {
|
||||||
|
operation as Map<String, Object?>?;
|
||||||
|
operation!;
|
||||||
|
switch (operation['type']) {
|
||||||
|
case 'backdropFilter':
|
||||||
|
emitBackdropFilterOperation(operation, indent);
|
||||||
|
case 'clipPath':
|
||||||
|
emitClipPathOperation(operation, indent);
|
||||||
|
case 'clipRect':
|
||||||
|
emitClipRectOperation(operation, indent);
|
||||||
|
case 'clipRRect':
|
||||||
|
emitClipRRectOperation(operation, indent);
|
||||||
|
case 'colorFilter':
|
||||||
|
emitColorFilterOperation(operation, indent);
|
||||||
|
case 'imageFilter':
|
||||||
|
emitImageFilterOperation(operation, indent);
|
||||||
|
case 'offset':
|
||||||
|
emitOffsetOperation(operation, indent);
|
||||||
|
case 'opacity':
|
||||||
|
emitOpacityOperation(operation, indent);
|
||||||
|
case 'transform':
|
||||||
|
emitTransformOperation(operation, indent);
|
||||||
|
case 'shaderMask':
|
||||||
|
emitShaderMaskOperation(operation, indent);
|
||||||
|
default:
|
||||||
|
throw ArgumentError('invalid operation type: ${operation['type']}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitLayer(Map<String, Object?> command, String indent) {
|
||||||
|
final layer = command['layer']! as Map<String, Object?>;
|
||||||
|
print('$indent{');
|
||||||
|
final String innerIndent = ' $indent';
|
||||||
|
emitOperation(layer['operation'], innerIndent);
|
||||||
|
emitCommands(layer['commands'], innerIndent);
|
||||||
|
print('${innerIndent}builder.pop();');
|
||||||
|
print('$indent}');
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitPlatformView(Map<String, Object?> command, String indent) {
|
||||||
|
final localBounds = command['localBounds']! as Map<String, Object?>;
|
||||||
|
final left = localBounds['left']! as double;
|
||||||
|
final top = localBounds['top']! as double;
|
||||||
|
final right = localBounds['right']! as double;
|
||||||
|
final bottom = localBounds['bottom']! as double;
|
||||||
|
print(
|
||||||
|
'${indent}builder.addPlatformView(1, offset: const ui.Offset($left, $top), width: ${right - left}, height: ${bottom - top});',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitPicture(Map<String, Object?> command, String indent) {
|
||||||
|
print(
|
||||||
|
'${indent}builder.addPicture(${offsetAsString(command['offset'])}, drawPicture(${rectAsString(command['localBounds'])}, ${getColorAsString()}));',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitCommands(Object? commands, String indent) {
|
||||||
|
commands as List<Object?>?;
|
||||||
|
commands!;
|
||||||
|
for (final Object? command in commands) {
|
||||||
|
command as Map<String, Object?>?;
|
||||||
|
command!;
|
||||||
|
switch (command['type']) {
|
||||||
|
case 'picture':
|
||||||
|
emitPicture(command, indent);
|
||||||
|
case 'platformView':
|
||||||
|
emitPlatformView(command, indent);
|
||||||
|
case 'layer':
|
||||||
|
emitLayer(command, indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(List<String> args) {
|
||||||
|
if (args.length != 1) {
|
||||||
|
stderr.write('Usage: dart generate_scene_test.dart <path_to_json>.\n');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final jsonFile = File(args[0]);
|
||||||
|
if (!jsonFile.existsSync()) {
|
||||||
|
stderr.write('Json file at path ${jsonFile.path} not found.\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
final fileString = jsonFile.readAsStringSync();
|
||||||
|
final sceneJson = jsonDecode(fileString) as Map<String, Object?>;
|
||||||
|
final rootLayer = sceneJson['rootLayer']! as Map<String, Object?>;
|
||||||
|
print('''
|
||||||
|
// This file was generated from a JSON file using the `web_ui/dev/generate_scene_test.dart`.
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:ui/ui.dart' as ui;
|
||||||
|
|
||||||
|
ui.Picture drawPicture(ui.Rect bounds, ui.Color color) {
|
||||||
|
final recorder = ui.PictureRecorder();
|
||||||
|
final canvas = ui.Canvas(recorder);
|
||||||
|
canvas.drawRect(bounds, ui.Paint()..color = color);
|
||||||
|
return recorder.endRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Scene buildScene() {
|
||||||
|
final ui.SceneBuilder builder = ui.SceneBuilder();''');
|
||||||
|
emitCommands(rootLayer['commands'], ' ');
|
||||||
|
print('''
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}''');
|
||||||
|
return 0;
|
||||||
|
}
|
@ -573,4 +573,7 @@ class CanvasKitRenderer implements Renderer {
|
|||||||
baseline: baseline,
|
baseline: baseline,
|
||||||
lineNumber: lineNumber,
|
lineNumber: lineNumber,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dumpDebugInfo() {}
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,12 @@ import 'package:ui/ui.dart' as ui;
|
|||||||
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||||
import 'package:web_locale_keymap/web_locale_keymap.dart' as locale_keymap;
|
import 'package:web_locale_keymap/web_locale_keymap.dart' as locale_keymap;
|
||||||
|
|
||||||
import '../engine.dart' show registerHotRestartListener;
|
|
||||||
import 'dom.dart';
|
import 'dom.dart';
|
||||||
|
import 'initialization.dart';
|
||||||
import 'key_map.g.dart';
|
import 'key_map.g.dart';
|
||||||
import 'platform_dispatcher.dart';
|
import 'platform_dispatcher.dart';
|
||||||
import 'raw_keyboard.dart';
|
import 'raw_keyboard.dart';
|
||||||
|
import 'renderer.dart';
|
||||||
import 'semantics.dart';
|
import 'semantics.dart';
|
||||||
|
|
||||||
typedef _VoidCallback = void Function();
|
typedef _VoidCallback = void Function();
|
||||||
@ -600,6 +601,14 @@ class KeyboardConverter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (kDebugMode &&
|
||||||
|
event.key == 'F10' &&
|
||||||
|
event.altKey &&
|
||||||
|
event.type == 'keydown' &&
|
||||||
|
!(event.repeat ?? false)) {
|
||||||
|
renderer.dumpDebugInfo();
|
||||||
|
}
|
||||||
|
|
||||||
assert(_dispatchKeyData == null);
|
assert(_dispatchKeyData == null);
|
||||||
bool sentAnyEvents = false;
|
bool sentAnyEvents = false;
|
||||||
_dispatchKeyData = (ui.KeyData data) {
|
_dispatchKeyData = (ui.KeyData data) {
|
||||||
|
@ -41,6 +41,11 @@ class NoopOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'NoopOperation()';
|
String toString() => 'NoopOperation()';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{'type': 'noop'};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackdropFilterLayer with PictureEngineLayer implements ui.BackdropFilterEngineLayer {
|
class BackdropFilterLayer with PictureEngineLayer implements ui.BackdropFilterEngineLayer {
|
||||||
@ -80,6 +85,15 @@ class BackdropFilterOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'BackdropFilterOperation(filter: $filter, mode: $mode)';
|
String toString() => 'BackdropFilterOperation(filter: $filter, mode: $mode)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'backdropFilter',
|
||||||
|
'filter': filter.toString(),
|
||||||
|
'mode': mode.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClipPathLayer with PictureEngineLayer implements ui.ClipPathEngineLayer {
|
class ClipPathLayer with PictureEngineLayer implements ui.ClipPathEngineLayer {
|
||||||
@ -128,6 +142,21 @@ class ClipPathOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ClipPathOperation(path: $path, clip: $clip)';
|
String toString() => 'ClipPathOperation(path: $path, clip: $clip)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
final ui.Rect bounds = path.getBounds();
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'clipPath',
|
||||||
|
'pathBounds': {
|
||||||
|
'left': bounds.left,
|
||||||
|
'top': bounds.top,
|
||||||
|
'right': bounds.right,
|
||||||
|
'bottom': bounds.bottom,
|
||||||
|
},
|
||||||
|
'clip': clip.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClipRectLayer with PictureEngineLayer implements ui.ClipRectEngineLayer {
|
class ClipRectLayer with PictureEngineLayer implements ui.ClipRectEngineLayer {
|
||||||
@ -176,6 +205,15 @@ class ClipRectOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ClipRectOperation(rect: $rect, clip: $clip)';
|
String toString() => 'ClipRectOperation(rect: $rect, clip: $clip)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'clipRect',
|
||||||
|
'rect': {'left': rect.left, 'top': rect.top, 'right': rect.right, 'bottom': rect.bottom},
|
||||||
|
'clip': clip.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClipRRectLayer with PictureEngineLayer implements ui.ClipRRectEngineLayer {
|
class ClipRRectLayer with PictureEngineLayer implements ui.ClipRRectEngineLayer {
|
||||||
@ -224,6 +262,28 @@ class ClipRRectOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ClipRRectOperation(rrect: $rrect, clip: $clip)';
|
String toString() => 'ClipRRectOperation(rrect: $rrect, clip: $clip)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'clipRRect',
|
||||||
|
'rrect': {
|
||||||
|
'left': rrect.left,
|
||||||
|
'top': rrect.top,
|
||||||
|
'right': rrect.right,
|
||||||
|
'bottom': rrect.bottom,
|
||||||
|
'tlRadiusX': rrect.tlRadiusX,
|
||||||
|
'tlRadiusY': rrect.tlRadiusY,
|
||||||
|
'trRadiusX': rrect.trRadiusX,
|
||||||
|
'trRadiusY': rrect.trRadiusY,
|
||||||
|
'brRadiusX': rrect.brRadiusX,
|
||||||
|
'brRadiusY': rrect.brRadiusY,
|
||||||
|
'blRadiusX': rrect.blRadiusX,
|
||||||
|
'blRadiusY': rrect.blRadiusY,
|
||||||
|
},
|
||||||
|
'clip': clip.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClipRSuperellipseLayer with PictureEngineLayer implements ui.ClipRSuperellipseEngineLayer {
|
class ClipRSuperellipseLayer with PictureEngineLayer implements ui.ClipRSuperellipseEngineLayer {
|
||||||
@ -274,6 +334,28 @@ class ClipRSuperellipseOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ClipRSuperellipseOperation(rse: $rse, clip: $clip)';
|
String toString() => 'ClipRSuperellipseOperation(rse: $rse, clip: $clip)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'clipRSuperEllipse',
|
||||||
|
'rse': {
|
||||||
|
'left': rse.left,
|
||||||
|
'top': rse.top,
|
||||||
|
'right': rse.right,
|
||||||
|
'bottom': rse.bottom,
|
||||||
|
'tlRadiusX': rse.tlRadiusX,
|
||||||
|
'tlRadiusY': rse.tlRadiusY,
|
||||||
|
'trRadiusX': rse.trRadiusX,
|
||||||
|
'trRadiusY': rse.trRadiusY,
|
||||||
|
'brRadiusX': rse.brRadiusX,
|
||||||
|
'brRadiusY': rse.brRadiusY,
|
||||||
|
'blRadiusX': rse.blRadiusX,
|
||||||
|
'blRadiusY': rse.blRadiusY,
|
||||||
|
},
|
||||||
|
'clip': clip.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColorFilterLayer with PictureEngineLayer implements ui.ColorFilterEngineLayer {
|
class ColorFilterLayer with PictureEngineLayer implements ui.ColorFilterEngineLayer {
|
||||||
@ -312,6 +394,11 @@ class ColorFilterOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ColorFilterOperation(filter: $filter)';
|
String toString() => 'ColorFilterOperation(filter: $filter)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{'type': 'colorFilter', 'filter': filter.toString()};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImageFilterLayer with PictureEngineLayer implements ui.ImageFilterEngineLayer {
|
class ImageFilterLayer with PictureEngineLayer implements ui.ImageFilterEngineLayer {
|
||||||
@ -371,6 +458,15 @@ class ImageFilterOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ImageFilterOperation(filter: $filter)';
|
String toString() => 'ImageFilterOperation(filter: $filter)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'imageFilter',
|
||||||
|
'filter': filter.toString(),
|
||||||
|
'offset': {'x': offset.dx, 'y': offset.dy},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OffsetLayer with PictureEngineLayer implements ui.OffsetEngineLayer {
|
class OffsetLayer with PictureEngineLayer implements ui.OffsetEngineLayer {
|
||||||
@ -412,6 +508,14 @@ class OffsetOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'OffsetOperation(dx: $dx, dy: $dy)';
|
String toString() => 'OffsetOperation(dx: $dx, dy: $dy)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'offset',
|
||||||
|
'offset': {'x': dx, 'y': dy},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OpacityLayer with PictureEngineLayer implements ui.OpacityEngineLayer {
|
class OpacityLayer with PictureEngineLayer implements ui.OpacityEngineLayer {
|
||||||
@ -464,6 +568,15 @@ class OpacityOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'OpacityOperation(offset: $offset, alpha: $alpha)';
|
String toString() => 'OpacityOperation(offset: $offset, alpha: $alpha)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'opacity',
|
||||||
|
'alpha': alpha,
|
||||||
|
'offset': {'x': offset.dx, 'y': offset.dy},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransformLayer with PictureEngineLayer implements ui.TransformEngineLayer {
|
class TransformLayer with PictureEngineLayer implements ui.TransformEngineLayer {
|
||||||
@ -508,6 +621,11 @@ class TransformOperation implements LayerOperation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'TransformOperation(matrix: $matrix)';
|
String toString() => 'TransformOperation(matrix: $matrix)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{'type': 'transform', 'matrix': transform.toList()};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShaderMaskLayer with PictureEngineLayer implements ui.ShaderMaskEngineLayer {
|
class ShaderMaskLayer with PictureEngineLayer implements ui.ShaderMaskEngineLayer {
|
||||||
@ -558,6 +676,20 @@ class ShaderMaskOperation implements LayerOperation {
|
|||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'ShaderMaskOperation(shader: $shader, maskRect: $maskRect, blendMode: $blendMode)';
|
'ShaderMaskOperation(shader: $shader, maskRect: $maskRect, blendMode: $blendMode)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'shaderMask',
|
||||||
|
'shader': shader.toString(),
|
||||||
|
'maskRect': {
|
||||||
|
'left': maskRect.left,
|
||||||
|
'top': maskRect.top,
|
||||||
|
'right': maskRect.right,
|
||||||
|
'bottom': maskRect.bottom,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformView {
|
class PlatformView {
|
||||||
@ -614,6 +746,13 @@ mixin PictureEngineLayer implements ui.EngineLayer {
|
|||||||
return 'PictureEngineLayer($operation)';
|
return 'PictureEngineLayer($operation)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'operation': operation.debugJsonDescription,
|
||||||
|
'commands': drawCommands.map((c) => c.debugJsonDescription).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
bool get isSimple {
|
bool get isSimple {
|
||||||
if (slices.length > 1) {
|
if (slices.length > 1) {
|
||||||
return false;
|
return false;
|
||||||
@ -643,9 +782,13 @@ abstract class LayerOperation {
|
|||||||
/// invoked even if it contains no pictures. (Most operations don't need to
|
/// invoked even if it contains no pictures. (Most operations don't need to
|
||||||
/// actually be performed at all if they don't contain any pictures.)
|
/// actually be performed at all if they don't contain any pictures.)
|
||||||
bool get affectsBackdrop;
|
bool get affectsBackdrop;
|
||||||
|
|
||||||
|
Map<String, Object> get debugJsonDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class LayerDrawCommand {}
|
sealed class LayerDrawCommand {
|
||||||
|
Map<String, Object> get debugJsonDescription;
|
||||||
|
}
|
||||||
|
|
||||||
class PictureDrawCommand extends LayerDrawCommand {
|
class PictureDrawCommand extends LayerDrawCommand {
|
||||||
PictureDrawCommand(this.offset, this.picture, this.sliceIndex);
|
PictureDrawCommand(this.offset, this.picture, this.sliceIndex);
|
||||||
@ -653,6 +796,22 @@ class PictureDrawCommand extends LayerDrawCommand {
|
|||||||
final int sliceIndex;
|
final int sliceIndex;
|
||||||
final ui.Offset offset;
|
final ui.Offset offset;
|
||||||
final ScenePicture picture;
|
final ScenePicture picture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
final bounds = picture.cullRect;
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'picture',
|
||||||
|
'sliceIndex': sliceIndex,
|
||||||
|
'offset': <String, Object>{'x': offset.dx, 'y': offset.dy},
|
||||||
|
'localBounds': <String, Object>{
|
||||||
|
'left': bounds.left,
|
||||||
|
'top': bounds.top,
|
||||||
|
'right': bounds.right,
|
||||||
|
'bottom': bounds.bottom,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformViewDrawCommand extends LayerDrawCommand {
|
class PlatformViewDrawCommand extends LayerDrawCommand {
|
||||||
@ -661,12 +820,32 @@ class PlatformViewDrawCommand extends LayerDrawCommand {
|
|||||||
final int sliceIndex;
|
final int sliceIndex;
|
||||||
final int viewId;
|
final int viewId;
|
||||||
final ui.Rect bounds;
|
final ui.Rect bounds;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{
|
||||||
|
'type': 'platformView',
|
||||||
|
'sliceIndex': sliceIndex,
|
||||||
|
'viewId': viewId,
|
||||||
|
'localBounds': <String, Object>{
|
||||||
|
'left': bounds.left,
|
||||||
|
'top': bounds.top,
|
||||||
|
'right': bounds.right,
|
||||||
|
'bottom': bounds.bottom,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RetainedLayerDrawCommand extends LayerDrawCommand {
|
class RetainedLayerDrawCommand extends LayerDrawCommand {
|
||||||
RetainedLayerDrawCommand(this.layer);
|
RetainedLayerDrawCommand(this.layer);
|
||||||
|
|
||||||
final PictureEngineLayer layer;
|
final PictureEngineLayer layer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return <String, Object>{'type': 'layer', 'layer': layer.debugJsonDescription};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents how a platform view should be positioned in the scene.
|
// Represents how a platform view should be positioned in the scene.
|
||||||
@ -883,6 +1062,11 @@ class PlatformViewNoClip implements PlatformViewClip {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
ui.Rect get outerRect => ui.Rect.largest;
|
ui.Rect get outerRect => ui.Rect.largest;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PlatformViewClip(none)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformViewRectClip implements PlatformViewClip {
|
class PlatformViewRectClip implements PlatformViewClip {
|
||||||
@ -922,10 +1106,15 @@ class PlatformViewRectClip implements PlatformViewClip {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
ui.Rect get outerRect => rect;
|
ui.Rect get outerRect => rect;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PlatformViewRectClip($rect)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformViewRRectClip implements PlatformViewClip {
|
class PlatformViewRRectClip implements PlatformViewClip {
|
||||||
PlatformViewRRectClip(this.rrect);
|
const PlatformViewRRectClip(this.rrect);
|
||||||
|
|
||||||
final ui.RRect rrect;
|
final ui.RRect rrect;
|
||||||
|
|
||||||
@ -961,6 +1150,11 @@ class PlatformViewRRectClip implements PlatformViewClip {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
ui.Rect get outerRect => rrect.outerRect;
|
ui.Rect get outerRect => rrect.outerRect;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PlatformViewRRectClip($rrect)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformViewPathClip implements PlatformViewClip {
|
class PlatformViewPathClip implements PlatformViewClip {
|
||||||
@ -1001,6 +1195,11 @@ class PlatformViewPathClip implements PlatformViewClip {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
ui.Rect get outerRect => path.getBounds();
|
ui.Rect get outerRect => path.getBounds();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PlatformViewPathClip($path)';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LayerSliceBuilder {
|
class LayerSliceBuilder {
|
||||||
|
@ -227,4 +227,6 @@ abstract class Renderer {
|
|||||||
ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style);
|
ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style);
|
||||||
|
|
||||||
Future<void> renderScene(ui.Scene scene, EngineFlutterView view);
|
Future<void> renderScene(ui.Scene scene, EngineFlutterView view);
|
||||||
|
|
||||||
|
void dumpDebugInfo();
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,10 @@ class EngineScene implements ui.Scene {
|
|||||||
}
|
}
|
||||||
return recorder.endRecording().toImageSync(width, height);
|
return recorder.endRecording().toImageSync(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, Object> get debugJsonDescription {
|
||||||
|
return {'rootLayer': rootLayer.debugJsonDescription};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class OcclusionMapNode {
|
sealed class OcclusionMapNode {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:ui/src/engine.dart';
|
import 'package:ui/src/engine.dart';
|
||||||
import 'package:ui/ui.dart' as ui;
|
import 'package:ui/ui.dart' as ui;
|
||||||
@ -53,6 +54,9 @@ class EngineSceneView {
|
|||||||
_SceneRender? _currentRender;
|
_SceneRender? _currentRender;
|
||||||
_SceneRender? _nextRender;
|
_SceneRender? _nextRender;
|
||||||
|
|
||||||
|
// Only populated in debug mode.
|
||||||
|
_SceneRender? _previousRender;
|
||||||
|
|
||||||
Future<void> renderScene(EngineScene scene, FrameTimingRecorder? recorder) {
|
Future<void> renderScene(EngineScene scene, FrameTimingRecorder? recorder) {
|
||||||
if (_currentRender != null) {
|
if (_currentRender != null) {
|
||||||
// If a scene is already queued up, drop it and queue this one up instead
|
// If a scene is already queued up, drop it and queue this one up instead
|
||||||
@ -71,7 +75,7 @@ class EngineSceneView {
|
|||||||
Future<void> _kickRenderLoop() async {
|
Future<void> _kickRenderLoop() async {
|
||||||
final _SceneRender current = _currentRender!;
|
final _SceneRender current = _currentRender!;
|
||||||
await _renderScene(current.scene, current.recorder);
|
await _renderScene(current.scene, current.recorder);
|
||||||
current.done();
|
_renderComplete(current);
|
||||||
_currentRender = _nextRender;
|
_currentRender = _nextRender;
|
||||||
_nextRender = null;
|
_nextRender = null;
|
||||||
if (_currentRender == null) {
|
if (_currentRender == null) {
|
||||||
@ -194,6 +198,37 @@ class EngineSceneView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _renderComplete(_SceneRender render) {
|
||||||
|
render.done();
|
||||||
|
if (kDebugMode) {
|
||||||
|
_previousRender = render;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _generateDebugFilename() {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final String y = now.year.toString().padLeft(4, '0');
|
||||||
|
final String mo = now.month.toString().padLeft(2, '0');
|
||||||
|
final String d = now.day.toString().padLeft(2, '0');
|
||||||
|
final String h = now.hour.toString().padLeft(2, '0');
|
||||||
|
final String mi = now.minute.toString().padLeft(2, '0');
|
||||||
|
final String s = now.second.toString().padLeft(2, '0');
|
||||||
|
return 'flutter-scene-$y-$mo-$d-$h-$mi-$s.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
void dumpDebugInfo() {
|
||||||
|
if (kDebugMode && _previousRender != null) {
|
||||||
|
final Map<String, Object?> debugJson = _previousRender!.scene.debugJsonDescription;
|
||||||
|
final String jsonString = jsonEncode(debugJson);
|
||||||
|
final blob = createDomBlob([jsonString], {'type': 'application/json'});
|
||||||
|
final url = domWindow.URL.createObjectURL(blob);
|
||||||
|
final element = domDocument.createElement('a');
|
||||||
|
element.setAttribute('href', url);
|
||||||
|
element.setAttribute('download', _generateDebugFilename());
|
||||||
|
element.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class SliceContainer {
|
sealed class SliceContainer {
|
||||||
|
@ -463,6 +463,13 @@ class SkwasmRenderer implements Renderer {
|
|||||||
imageCreateFromTextureSource(textureSource as JSObject, width, height, surface.handle),
|
imageCreateFromTextureSource(textureSource as JSObject, width, height, surface.handle),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dumpDebugInfo() {
|
||||||
|
for (final view in _sceneViews.values) {
|
||||||
|
view.dumpDebugInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SkwasmPictureRenderer implements PictureRenderer {
|
class SkwasmPictureRenderer implements PictureRenderer {
|
||||||
|
@ -324,6 +324,11 @@ class SkwasmRenderer implements Renderer {
|
|||||||
required int height,
|
required int height,
|
||||||
required bool transferOwnership,
|
required bool transferOwnership,
|
||||||
}) {
|
}) {
|
||||||
throw Exception('Skwasm not implemented on this platform.');
|
throw UnimplementedError('Skwasm not implemented on this platform.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dumpDebugInfo() {
|
||||||
|
throw UnimplementedError('Skwasm not implemented on this platform.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user