From 24c58d5e3475f48cb5173bce721b78672183fc1f Mon Sep 17 00:00:00 2001 From: Jamie Wong Date: Thu, 25 Jan 2018 10:17:17 -0800 Subject: [PATCH] Flamechart Renderer with no culling --- application.tsx | 35 ++++++----- flamechart-minimap-view.tsx | 7 +-- flamechart-renderer.ts | 114 ++++++++++++++++++++++++++++++++++++ flamechart-view.tsx | 17 +++--- rectangle-batch-renderer.ts | 2 +- 5 files changed, 143 insertions(+), 32 deletions(-) create mode 100644 flamechart-renderer.ts diff --git a/application.tsx b/application.tsx index 786715f..a5ccf7b 100644 --- a/application.tsx +++ b/application.tsx @@ -5,7 +5,7 @@ import {ReloadableComponent, SerializedComponent} from './reloadable' import {importFromBGFlameGraph} from './import/bg-flamegraph' import {importFromStackprof} from './import/stackprof' import {importFromChromeTimeline, importFromChromeCPUProfile} from './import/chrome' -import { RectangleBatch } from './rectangle-batch-renderer' +import { FlamechartRenderer } from './flamechart-renderer' import { CanvasContext } from './canvas-context' import {Profile, Frame} from './profile' @@ -23,9 +23,9 @@ const enum SortOrder { interface ApplicationState { profile: Profile | null flamechart: Flamechart | null - flamechartRectBatch: RectangleBatch | null + flamechartRenderer: FlamechartRenderer | null sortedFlamechart: Flamechart | null - sortedFlamechartRectBatch: RectangleBatch | null + sortedFlamechartRenderer: FlamechartRenderer | null sortOrder: SortOrder loading: boolean } @@ -202,7 +202,6 @@ function rectangleBatchForFlamechart(canvasContext: CanvasContext, flamechart: F return batch } - export class Application extends ReloadableComponent<{}, ApplicationState> { constructor() { super() @@ -210,17 +209,17 @@ export class Application extends ReloadableComponent<{}, ApplicationState> { loading: false, profile: null, flamechart: null, - flamechartRectBatch: null, + flamechartRenderer: null, sortedFlamechart: null, - sortedFlamechartRectBatch: null, + sortedFlamechartRenderer: null, sortOrder: SortOrder.CHRONO } } serialize() { const result = super.serialize() - delete result.state.flamechartRectBatch - delete result.state.sortedFlamechartRectBatch + delete result.state.flamechartRenderer + delete result.state.sortedFlamechartRenderer return result } @@ -229,8 +228,8 @@ export class Application extends ReloadableComponent<{}, ApplicationState> { const { flamechart, sortedFlamechart } = serialized.state if (this.canvasContext && flamechart && sortedFlamechart) { this.setState({ - flamechartRectBatch: rectangleBatchForFlamechart(this.canvasContext, flamechart), - sortedFlamechartRectBatch: rectangleBatchForFlamechart(this.canvasContext, sortedFlamechart) + flamechartRenderer: new FlamechartRenderer(this.canvasContext, flamechart), + sortedFlamechartRenderer: new FlamechartRenderer(this.canvasContext, sortedFlamechart) }) } } @@ -260,7 +259,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> { formatValue: profile.formatValue.bind(profile), getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator) }) - const flamechartRectBatch = rectangleBatchForFlamechart(this.canvasContext, flamechart) + const flamechartRenderer = new FlamechartRenderer(this.canvasContext, flamechart) const sortedFlamechart = new Flamechart({ getTotalWeight: profile.getTotalNonIdleWeight.bind(profile), @@ -268,7 +267,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> { formatValue: profile.formatValue.bind(profile), getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator) }) - const sortedFlamechartRectBatch = rectangleBatchForFlamechart(this.canvasContext, sortedFlamechart) + const sortedFlamechartRenderer = new FlamechartRenderer(this.canvasContext, sortedFlamechart) console.timeEnd('import') @@ -276,9 +275,9 @@ export class Application extends ReloadableComponent<{}, ApplicationState> { this.setState({ profile, flamechart, - flamechartRectBatch, + flamechartRenderer, sortedFlamechart, - sortedFlamechartRectBatch, + sortedFlamechartRenderer, loading: false }, () => { console.timeEnd('first setState') @@ -386,19 +385,19 @@ export class Application extends ReloadableComponent<{}, ApplicationState> { } render() { - const {flamechart, flamechartRectBatch, sortedFlamechart, sortedFlamechartRectBatch, sortOrder, loading} = this.state + const {flamechart, flamechartRenderer, sortedFlamechart, sortedFlamechartRenderer, sortOrder, loading} = this.state const flamechartToView = sortOrder == SortOrder.CHRONO ? flamechart : sortedFlamechart - const rectangleBatch = sortOrder == SortOrder.CHRONO ? flamechartRectBatch : sortedFlamechartRectBatch + const flamechartRendererToUse = sortOrder == SortOrder.CHRONO ? flamechartRenderer : sortedFlamechartRenderer return
{loading ? this.renderLoadingBar() : - this.canvasContext && flamechartToView && rectangleBatch ? + this.canvasContext && flamechartToView && flamechartRendererToUse ? : this.renderLanding()} diff --git a/flamechart-minimap-view.tsx b/flamechart-minimap-view.tsx index ed07c6e..5b7cee3 100644 --- a/flamechart-minimap-view.tsx +++ b/flamechart-minimap-view.tsx @@ -2,7 +2,7 @@ import { h, Component } from 'preact' import { css } from 'aphrodite' import { Flamechart } from './flamechart' import { Rect, Vec2, AffineTransform, clamp } from './math' -import { RectangleBatch } from "./rectangle-batch-renderer" +import { FlamechartRenderer } from "./flamechart-renderer" import { cachedMeasureTextWidth } from "./utils"; import { style, Sizes } from "./flamechart-style"; import { FontFamily, FontSize, Colors } from "./style" @@ -16,7 +16,7 @@ interface FlamechartMinimapViewProps { configSpaceViewportRect: Rect canvasContext: CanvasContext - rectangles: RectangleBatch + flamechartRenderer: FlamechartRenderer transformViewport: (transform: AffineTransform) => void setConfigSpaceViewportRect: (rect: Rect) => void @@ -95,10 +95,9 @@ export class FlamechartMinimapView extends Component { - this.props.canvasContext.drawRectangleBatch({ + this.props.flamechartRenderer.render({ physicalSize: props.physicalSize, configSpaceToNDC: props.configSpaceToNDC, - batch: this.props.rectangles }) } }) diff --git a/flamechart-renderer.ts b/flamechart-renderer.ts new file mode 100644 index 0000000..4b647f6 --- /dev/null +++ b/flamechart-renderer.ts @@ -0,0 +1,114 @@ +import { Flamechart, FlamechartFrame } from './flamechart' +import { RectangleBatch } from './rectangle-batch-renderer' +import { CanvasContext } from './canvas-context'; +import { Vec2, Rect, AffineTransform } from './math' + +const MAX_BATCH_SIZE = 10000 // TODO(jlfwong): Bump this to 10000 + +interface RangeTreeNode { + getMinLeft(): number + getMaxRight(): number + getRectCount(): number + getChildren(): RangeTreeNode[] + forEachBatch(cb: (batch: RectangleBatch) => void): void +} + +class RangeTreeLeafNode implements RangeTreeNode { + private children: RangeTreeNode[] = [] + + constructor( + private batch: RectangleBatch, + private minLeft: number, + private maxRight: number + ) { } + + getMinLeft() { return this.minLeft } + getMaxRight() { return this.maxRight } + getRectCount() { return this.batch.getRectCount() } + getChildren() { return this.children } + forEachBatch(cb: (batch: RectangleBatch) => void) { cb(this.batch) } +} + +class RangeTreeInteriorNode implements RangeTreeNode { + private rectCount: number = 0 + constructor(private children: RangeTreeNode[]) { + if (children.length === 0) { + throw new Error("Empty interior node") + } + for (let child of children) { + this.rectCount += child.getRectCount() + } + } + + getMinLeft() { return this.children[0].getMinLeft() } + getMaxRight() { return this.children[this.children.length - 1].getMaxRight() } + getRectCount() { return this.rectCount } + getChildren() { return this.children } + forEachBatch(cb: (batch: RectangleBatch) => void) { + for (let child of this.children) { + child.forEachBatch(cb) + } + } +} + +export interface FlamechartRendererProps { + configSpaceToNDC: AffineTransform + physicalSize: Vec2 +} + +class BoundedLayer { + private rootNode: RangeTreeNode + constructor(private canvasContext: CanvasContext, flamechart: Flamechart, stackDepth: number) { + const leafNodes: RangeTreeLeafNode[] = [] + + let minLeft = Infinity + let maxRight = -Infinity + let batch = canvasContext.createRectangleBatch() + + for (let frame of flamechart.getLayers()[stackDepth]) { + if (batch.getRectCount() >= MAX_BATCH_SIZE) { + leafNodes.push(new RangeTreeLeafNode(batch, minLeft, maxRight)) + minLeft = Infinity + maxRight = -Infinity + batch = canvasContext.createRectangleBatch() + } + const configSpaceBounds = new Rect( + new Vec2(frame.start, stackDepth + 1), + new Vec2(frame.end - frame.start, 1) + ) + const color = flamechart.getColorForFrame(frame.node.frame) + batch.addRect(configSpaceBounds, color) + } + + if (batch.getRectCount() > 0) { + leafNodes.push(new RangeTreeLeafNode(batch, minLeft, maxRight)) + } + + // TODO(jlfwong): Probably want this to be a binary tree + this.rootNode = new RangeTreeInteriorNode(leafNodes) + } + + render(props: FlamechartRendererProps) { + // TODO(jlfwong): Cull batches! + this.rootNode.forEachBatch(batch => { + this.canvasContext.drawRectangleBatch({ ...props, batch }) + }) + } +} + +export class FlamechartRenderer { + private layers: BoundedLayer[] = [] + + constructor(canvasContext: CanvasContext, flamechart: Flamechart) { + for (let i = 0; i < flamechart.getLayers().length; i++) { + this.layers.push(new BoundedLayer(canvasContext, flamechart, i)) + } + } + + render(props: FlamechartRendererProps) { + // TODO(jlfwong): Cull layers outside the viewport! + for (let layer of this.layers) { + layer.render(props) + } + } +} \ No newline at end of file diff --git a/flamechart-view.tsx b/flamechart-view.tsx index 89a3ce0..963defe 100644 --- a/flamechart-view.tsx +++ b/flamechart-view.tsx @@ -7,12 +7,12 @@ import { Flamechart, FlamechartFrame } from './flamechart' import { Rect, Vec2, AffineTransform, clamp } from './math' import { cachedMeasureTextWidth } from "./utils"; -import { RectangleBatch } from "./rectangle-batch-renderer" import { FlamechartMinimapView } from "./flamechart-minimap-view" import { style, Sizes } from './flamechart-style' import { FontSize, FontFamily, Colors } from './style' import { CanvasContext } from './canvas-context' +import { FlamechartRenderer } from './flamechart-renderer' interface FlamechartFrameLabel { configSpaceBounds: Rect @@ -74,7 +74,7 @@ interface FlamechartPanZoomViewProps { flamechart: Flamechart canvasContext: CanvasContext - rectangles: RectangleBatch + flamechartRenderer: FlamechartRenderer setNodeHover: (node: CallTreeNode | null, logicalViewSpaceMouse: Vec2) => void configSpaceViewportRect: Rect @@ -324,10 +324,9 @@ export class FlamechartPanZoomView extends ReloadableComponent { - this.props.canvasContext.drawRectangleBatch({ + this.props.flamechartRenderer.render({ configSpaceToNDC: configSpaceToNDC, physicalSize: this.physicalViewSize(), - batch: this.props.rectangles }) }) } @@ -573,7 +572,7 @@ export class FlamechartPanZoomView extends ReloadableComponent + setConfigSpaceViewportRect={this.setConfigSpaceViewportRect} />