Batch culling

This commit is contained in:
Jamie Wong
2018-01-25 11:50:40 -08:00
parent 24c58d5e34
commit 62eb215226
4 changed files with 68 additions and 23 deletions
+13 -7
View File
@@ -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
View File
@@ -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)
}
+13 -4
View File
@@ -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] {
Vendored
+1 -1
View File
@@ -124,7 +124,7 @@ declare module "regl" {
destroy(): void
frame(callback: (context?: Context) => void): Tick
frame(callback: (context: Context) => void): Tick
}
type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array |