diff --git a/canvas-context.ts b/canvas-context.ts index 036f40b..a643027 100644 --- a/canvas-context.ts +++ b/canvas-context.ts @@ -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) { diff --git a/flamechart-renderer.ts b/flamechart-renderer.ts index 4b647f6..ad8cd05 100644 --- a/flamechart-renderer.ts +++ b/flamechart-renderer.ts @@ -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) } diff --git a/math.ts b/math.ts index e0706fa..acd7886 100644 --- a/math.ts +++ b/math.ts @@ -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] { diff --git a/regl.d.ts b/regl.d.ts index 23c6d2b..83072c4 100644 --- a/regl.d.ts +++ b/regl.d.ts @@ -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 |