Batch culling
This commit is contained in:
+13
-7
@@ -81,25 +81,31 @@ export class CanvasContext {
|
||||
this.tick = this.gl.frame(this.onBeforeFrame)
|
||||
}
|
||||
}
|
||||
private onBeforeFrame = () => {
|
||||
|
||||
private onBeforeFrame = (context: regl.Context) => {
|
||||
this.gl.clear({ color: [0, 0, 0, 0] })
|
||||
this.tickNeeded = false
|
||||
|
||||
let beforeHandlers = performance.now()
|
||||
for (const handler of this.beforeFrameHandlers) {
|
||||
handler()
|
||||
}
|
||||
let cpuTimeElapsed = performance.now() - beforeHandlers;
|
||||
|
||||
if (this.tick && !this.tickNeeded) {
|
||||
this.tick.cancel()
|
||||
this.tick = null
|
||||
}
|
||||
|
||||
// TODO(jlfwong): It would be really nice to have GPU
|
||||
// stats here, but I can't figure out how to interpret
|
||||
// the gpuTime. I suspect this is caused by executing the same
|
||||
// program multiple times without flushing.
|
||||
// I'll investigate this at some point and report a bug to regl.
|
||||
|
||||
console.group('Frame')
|
||||
console.log('Rectangle Batch Renderer: ', this.rectangleBatchRenderer.stats().gpuTime.toFixed(3), 'in', this.rectangleBatchRenderer.stats().count, 'calls')
|
||||
console.log('Texture Renderer: ', this.textureRenderer.stats().gpuTime.toFixed(3), 'in', this.textureRenderer.stats().count, 'calls')
|
||||
console.log('Viewport Rect Renderer: ', this.viewportRectangleRenderer.stats().gpuTime.toFixed(3), 'in', this.viewportRectangleRenderer.stats().count, 'calls')
|
||||
console.log('CPU Frame Generation Time (ms)', cpuTimeElapsed.toFixed(2))
|
||||
console.groupEnd()
|
||||
this.rectangleBatchRenderer.resetStats()
|
||||
this.textureRenderer.resetStats()
|
||||
this.viewportRectangleRenderer.resetStats()
|
||||
}
|
||||
|
||||
drawRectangleBatch(props: RectangleBatchRendererProps) {
|
||||
|
||||
+41
-11
@@ -1,16 +1,16 @@
|
||||
import { Flamechart, FlamechartFrame } from './flamechart'
|
||||
import { Flamechart } 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
|
||||
const MAX_BATCH_SIZE = 10000
|
||||
|
||||
interface RangeTreeNode {
|
||||
getMinLeft(): number
|
||||
getMaxRight(): number
|
||||
getRectCount(): number
|
||||
getChildren(): RangeTreeNode[]
|
||||
forEachBatch(cb: (batch: RectangleBatch) => void): void
|
||||
forEachBatchInViewport(configSpaceViewport: Rect, cb: (batch: RectangleBatch) => void): void
|
||||
}
|
||||
|
||||
class RangeTreeLeafNode implements RangeTreeNode {
|
||||
@@ -20,13 +20,17 @@ class RangeTreeLeafNode implements RangeTreeNode {
|
||||
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) }
|
||||
forEachBatchInViewport(configSpaceViewport: Rect, cb: (batch: RectangleBatch) => void) {
|
||||
if (this.maxRight < configSpaceViewport.left()) return
|
||||
if (this.minLeft > configSpaceViewport.right()) return
|
||||
cb(this.batch)
|
||||
}
|
||||
}
|
||||
|
||||
class RangeTreeInteriorNode implements RangeTreeNode {
|
||||
@@ -44,9 +48,12 @@ class RangeTreeInteriorNode implements RangeTreeNode {
|
||||
getMaxRight() { return this.children[this.children.length - 1].getMaxRight() }
|
||||
getRectCount() { return this.rectCount }
|
||||
getChildren() { return this.children }
|
||||
forEachBatch(cb: (batch: RectangleBatch) => void) {
|
||||
forEachBatchInViewport(configSpaceViewport: Rect, cb: (batch: RectangleBatch) => void) {
|
||||
// if (this.getMaxRight() < configSpaceViewport.left()) return
|
||||
// if (this.getMinLeft() > configSpaceViewport.right()) return
|
||||
|
||||
for (let child of this.children) {
|
||||
child.forEachBatch(cb)
|
||||
child.forEachBatchInViewport(configSpaceViewport, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +65,11 @@ export interface FlamechartRendererProps {
|
||||
|
||||
class BoundedLayer {
|
||||
private rootNode: RangeTreeNode
|
||||
constructor(private canvasContext: CanvasContext, flamechart: Flamechart, stackDepth: number) {
|
||||
constructor(
|
||||
private canvasContext: CanvasContext,
|
||||
flamechart: Flamechart,
|
||||
private stackDepth: number
|
||||
) {
|
||||
const leafNodes: RangeTreeLeafNode[] = []
|
||||
|
||||
let minLeft = Infinity
|
||||
@@ -76,6 +87,8 @@ class BoundedLayer {
|
||||
new Vec2(frame.start, stackDepth + 1),
|
||||
new Vec2(frame.end - frame.start, 1)
|
||||
)
|
||||
minLeft = Math.min(minLeft, configSpaceBounds.left())
|
||||
maxRight = Math.max(maxRight, configSpaceBounds.right())
|
||||
const color = flamechart.getColorForFrame(frame.node.frame)
|
||||
batch.addRect(configSpaceBounds, color)
|
||||
}
|
||||
@@ -89,8 +102,26 @@ class BoundedLayer {
|
||||
}
|
||||
|
||||
render(props: FlamechartRendererProps) {
|
||||
// TODO(jlfwong): Cull batches!
|
||||
this.rootNode.forEachBatch(batch => {
|
||||
const configSpaceTop = this.stackDepth + 1
|
||||
const configSpaceBottom = configSpaceTop + 1
|
||||
|
||||
const ndcToConfigSpace = props.configSpaceToNDC.inverted()
|
||||
if (!ndcToConfigSpace) return
|
||||
const configSpaceViewportRect = ndcToConfigSpace.transformRect(new Rect(
|
||||
new Vec2(-1, -1), new Vec2(2, 2)
|
||||
))
|
||||
|
||||
if (configSpaceTop > configSpaceViewportRect.bottom()) {
|
||||
// Entire layer is below the viewport
|
||||
return
|
||||
}
|
||||
|
||||
if (configSpaceBottom < configSpaceViewportRect.top()) {
|
||||
// Entire layer is above the viewport
|
||||
return
|
||||
}
|
||||
|
||||
this.rootNode.forEachBatchInViewport(configSpaceViewportRect, batch => {
|
||||
this.canvasContext.drawRectangleBatch({ ...props, batch })
|
||||
})
|
||||
}
|
||||
@@ -106,7 +137,6 @@ export class FlamechartRenderer {
|
||||
}
|
||||
|
||||
render(props: FlamechartRendererProps) {
|
||||
// TODO(jlfwong): Cull layers outside the viewport!
|
||||
for (let layer of this.layers) {
|
||||
layer.render(props)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export class Vec2 {
|
||||
equals(other: Vec2) { return this.x === other.x && this.y === other.y }
|
||||
length2() { return this.dot(this) }
|
||||
length() { return Math.sqrt(this.length2()) }
|
||||
abs() { return new Vec2(Math.abs(this.x), Math.abs(this.y)) }
|
||||
|
||||
static min(a: Vec2, b: Vec2) {
|
||||
return new Vec2(Math.min(a.x, b.x), Math.min(a.y, b.y))
|
||||
@@ -171,10 +172,18 @@ export class AffineTransform {
|
||||
}
|
||||
|
||||
transformRect(r: Rect) {
|
||||
return new Rect(
|
||||
this.transformPosition(r.origin),
|
||||
this.transformVector(r.size)
|
||||
)
|
||||
const size = this.transformVector(r.size)
|
||||
const origin = this.transformPosition(r.origin)
|
||||
|
||||
if (size.x < 0 && size.y < 0) {
|
||||
return new Rect(origin.plus(size), size.abs())
|
||||
} else if (size.x < 0) {
|
||||
return new Rect(origin.withX(origin.x + size.x), size.abs())
|
||||
} else if (size.y < 0) {
|
||||
return new Rect(origin.withY(origin.y + size.y), size.abs())
|
||||
}
|
||||
|
||||
return new Rect(origin, size)
|
||||
}
|
||||
|
||||
flatten(): [number, number, number, number, number, number, number, number, number] {
|
||||
|
||||
Reference in New Issue
Block a user