blob: 74c0ff7d0d17eb142f2a53a438fa9a05f590acbb [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:meta/meta.dart';
import '../charts/flame_chart_canvas.dart';
import '../ui/colors.dart';
import '../ui/fake_flutter/dart_ui/dart_ui.dart';
import '../ui/theme.dart';
import 'timeline_model.dart';
class TimelineFlameChartCanvas extends FlameChartCanvas<TimelineFrame> {
TimelineFlameChartCanvas({
@required TimelineFrame data,
@required double width,
@required double height,
}) : super(
data: data,
duration: data.time.duration,
width: width,
height: height,
);
static const double sectionSpacing = 15.0;
int get gpuSectionStartRow => data.uiEventFlow.depth;
@override
void initRows() {
rows = List.generate(
data.uiEventFlow.depth + data.gpuEventFlow.depth,
(i) => FlameChartRow(nodes: [], index: i),
);
final totalWidth = width - 2 * sideInset;
final int frameStartOffset = data.time.start.inMicroseconds;
double getTopForRow(int row) {
// This accounts for the section spacing between the UI events and the GPU
// events.
final additionalPadding =
row >= gpuSectionStartRow ? sectionSpacing : 0.0;
return (row * rowHeightWithPadding + topOffset + additionalPadding)
.toDouble();
}
// Add UI section label.
const uiLabelWidth = 22.0 + rowPadding;
final uiLabelTop = getTopForRow(0);
final uiLabelBottom = uiLabelTop + rowHeight;
final uiSectionLabel = FlameChartNode<TimelineEvent>(
Rect.fromLTRB(rowPadding, uiLabelTop, uiLabelWidth, uiLabelBottom),
mainUiColor,
Colors.black,
Colors.black,
null,
(_) => 'UI',
);
rows[0].nodes.add(uiSectionLabel);
// Add GPU section label.
const gpuLabelWidth = 40.0 + rowPadding;
final gpuLabelTop = getTopForRow(gpuSectionStartRow);
final gpuLabelBottom = gpuLabelTop + rowHeight;
final gpuSectionLabel = FlameChartNode<TimelineEvent>(
Rect.fromLTRB(rowPadding, gpuLabelTop, gpuLabelWidth, gpuLabelBottom),
mainGpuColor,
Colors.white,
Colors.white,
null,
(_) => 'GPU',
);
rows[gpuSectionStartRow].nodes.add(gpuSectionLabel);
void createChartNodes(TimelineEvent event, int row) {
// Pixels per microsecond in order to fit the entire frame in view.
final double pxPerMicro = totalWidth / data.time.duration.inMicroseconds;
// Do not round these values. Rounding the left could cause us to have
// inaccurately placed events on the chart. Rounding the width could cause
// us to lose very small events if the width rounds to zero.
final double left =
(event.time.start.inMicroseconds - frameStartOffset) * pxPerMicro +
sideInset;
final double right =
(event.time.end.inMicroseconds - frameStartOffset) * pxPerMicro +
sideInset;
final top = getTopForRow(row);
final backgroundColor =
event.isUiEvent ? _nextUiColor() : _nextGpuColor();
final node = FlameChartNode<TimelineEvent>(
Rect.fromLTRB(left, top, right, top + rowHeight),
backgroundColor,
event.isUiEvent
? ThemedColor.fromSingleColor(Colors.black)
: ThemedColor.fromSingleColor(contrastForegroundWhite),
Colors.black,
event,
(_) => event.name,
);
rows[row].nodes.add(node);
for (TimelineEvent child in event.children) {
createChartNodes(
child,
row + 1,
);
}
}
createChartNodes(data.uiEventFlow, 0);
createChartNodes(data.gpuEventFlow, gpuSectionStartRow);
}
@override
double get calculatedWidth {
// The farthest right node in the graph will either be the root UI event or
// the root GPU event.
return math.max(rows[gpuSectionStartRow].nodes.last.rect.right,
rows[gpuSectionStartRow].nodes.last.rect.right) -
sideInset;
}
@override
double relativeYPosition(double absoluteY) {
final row = (absoluteY - topOffset) ~/ rowHeightWithPadding;
if (row >= gpuSectionStartRow) {
return absoluteY - topOffset - sectionSpacing;
}
return absoluteY - topOffset;
}
int _uiColorOffset = 0;
int _gpuColorOffset = 0;
Color _nextUiColor() {
final color = uiColorPalette[_uiColorOffset % uiColorPalette.length];
_uiColorOffset++;
return color;
}
Color _nextGpuColor() {
final color = gpuColorPalette[_gpuColorOffset % gpuColorPalette.length];
_gpuColorOffset++;
return color;
}
}