Flamechart Renderer with no culling
This commit is contained in:
+17
-18
@@ -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()}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user