Flamechart Renderer with no culling

This commit is contained in:
Jamie Wong
2018-01-25 10:17:17 -08:00
parent 282aaf8597
commit 24c58d5e34
5 changed files with 143 additions and 32 deletions
+17 -18
View File
@@ -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 <div onDrop={this.onDrop} onDragOver={this.onDragOver} className={css(style.root)}>
<GLCanvas setCanvasContext={this.setCanvasContext} />
<Toolbar setSortOrder={this.setSortOrder} {...this.state} />
{loading ?
this.renderLoadingBar() :
this.canvasContext && flamechartToView && rectangleBatch ?
this.canvasContext && flamechartToView && flamechartRendererToUse ?
<FlamechartView
canvasContext={this.canvasContext}
rectangles={rectangleBatch}
flamechartRenderer={flamechartRendererToUse}
ref={this.flamechartRef}
flamechart={flamechartToView} /> :
this.renderLanding()}
+3 -4
View File
@@ -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<FlamechartMinimapViewProps,
return false
},
render: (props) => {
this.props.canvasContext.drawRectangleBatch({
this.props.flamechartRenderer.render({
physicalSize: props.physicalSize,
configSpaceToNDC: props.configSpaceToNDC,
batch: this.props.rectangles
})
}
})
+114
View File
@@ -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)
}
}
}
+8 -9
View File
@@ -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<FlamechartPanZoom
// console.trace('main view render')
this.props.canvasContext.renderInto(this.container, () => {
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<FlamechartPanZoom
interface FlamechartViewProps {
flamechart: Flamechart
canvasContext: CanvasContext
rectangles: RectangleBatch
flamechartRenderer: FlamechartRenderer
}
interface FlamechartViewState {
@@ -703,15 +702,15 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
<FlamechartMinimapView
configSpaceViewportRect={this.state.configSpaceViewportRect}
transformViewport={this.transformViewport}
flamechart={this.props.flamechart}
flamechartRenderer={this.props.flamechartRenderer}
canvasContext={this.props.canvasContext}
rectangles={this.props.rectangles}
setConfigSpaceViewportRect={this.setConfigSpaceViewportRect}
flamechart={this.props.flamechart} />
setConfigSpaceViewportRect={this.setConfigSpaceViewportRect} />
<FlamechartPanZoomView
ref={this.panZoomRef}
canvasContext={this.props.canvasContext}
rectangles={this.props.rectangles}
flamechart={this.props.flamechart}
flamechartRenderer={this.props.flamechartRenderer}
setNodeHover={this.onNodeHover}
transformViewport={this.transformViewport}
configSpaceViewportRect={this.state.configSpaceViewportRect}
+1 -1
View File
@@ -3,7 +3,7 @@ import { Rect, Vec2, AffineTransform } from './math'
import { Color } from './color'
export class RectangleBatch {
private rectCapacity = 10
private rectCapacity = 1000
private rectCount = 0
private configSpaceOffsets = new Float32Array(this.rectCapacity * 2)