Row atlas seems like it is working!

This commit is contained in:
Jamie Wong 2018-01-28 12:29:41 -08:00
parent 4fcd40da98
commit 31829c4aa0
6 changed files with 50 additions and 23 deletions

View File

@ -149,6 +149,10 @@ export class CanvasContext {
this.setViewportScope({ physicalBounds }, cb) this.setViewportScope({ physicalBounds }, cb)
} }
setViewport(physicalBounds: Rect, cb: (context: regl.Context) => void) {
this.setViewportScope({ physicalBounds }, cb)
}
getMaxTextureSize() { getMaxTextureSize() {
return this.gl.limits.maxTextureSize return this.gl.limits.maxTextureSize
} }

View File

@ -4,14 +4,16 @@ import { RectangleBatch } from './rectangle-batch-renderer'
import { CanvasContext } from './canvas-context'; import { CanvasContext } from './canvas-context';
import { Vec2, Rect, AffineTransform } from './math' import { Vec2, Rect, AffineTransform } from './math'
import { LRUCache } from './lru-cache' import { LRUCache } from './lru-cache'
import { Color } from './color'
const MAX_BATCH_SIZE = 10000 const MAX_BATCH_SIZE = 10000
class RowAtlas<K> { class RowAtlas<K> {
private texture: regl.Texture texture: regl.Texture
private framebuffer: regl.Framebuffer private framebuffer: regl.Framebuffer
private renderToFramebuffer: regl.Command<{}> private renderToFramebuffer: regl.Command<{}>
private rowCache: LRUCache<K, number> private rowCache: LRUCache<K, number>
private clearLineBatch: RectangleBatch
constructor(private canvasContext: CanvasContext) { constructor(private canvasContext: CanvasContext) {
this.texture = canvasContext.gl.texture({ this.texture = canvasContext.gl.texture({
@ -25,6 +27,8 @@ class RowAtlas<K> {
this.renderToFramebuffer = canvasContext.gl({ this.renderToFramebuffer = canvasContext.gl({
framebuffer: this.framebuffer framebuffer: this.framebuffer
}) })
this.clearLineBatch = canvasContext.createRectangleBatch()
this.clearLineBatch.addRect(Rect.unit, new Color(1, 1, 1, 1))
} }
has(key: K) { return this.rowCache.has(key) } has(key: K) { return this.rowCache.has(key) }
@ -53,20 +57,23 @@ class RowAtlas<K> {
let row = this.rowCache.get(key) let row = this.rowCache.get(key)
if (row != null) { if (row != null) {
// Already cached! // Already cached!
return continue
} }
// Not cached -- we'll have to actually render // Not cached -- we'll have to actually render
row = this.allocateLine(key) row = this.allocateLine(key)
const textureRect = new Rect( const textureRect = new Rect(
new Vec2(0, row), new Vec2(0, row),
new Vec2(this.texture.width, 1) new Vec2(this.texture.width, 1)
) )
this.canvasContext.drawRectangleBatch({
batch: this.clearLineBatch,
configSpaceSrcRect: Rect.unit,
physicalSpaceDstRect: textureRect
})
render(textureRect, key) render(textureRect, key)
} }
}) })
return
} }
renderViaAtlas(key: K, dstRect: Rect): boolean { renderViaAtlas(key: K, dstRect: Rect): boolean {
@ -165,7 +172,7 @@ export class FlamechartRenderer {
private root: RangeTreeNode private root: RangeTreeNode
private rowAtlas: RowAtlas<RangeTreeLeafNode> private rowAtlas: RowAtlas<RangeTreeLeafNode>
constructor(private canvasContext: CanvasContext, flamechart: Flamechart) { constructor(private canvasContext: CanvasContext, private flamechart: Flamechart) {
const nLayers = flamechart.getLayers().length const nLayers = flamechart.getLayers().length
this.rowAtlas = new RowAtlas(canvasContext) this.rowAtlas = new RowAtlas(canvasContext)
@ -173,7 +180,7 @@ export class FlamechartRenderer {
for (let stackDepth = 0; stackDepth < nLayers; stackDepth++) { for (let stackDepth = 0; stackDepth < nLayers; stackDepth++) {
const leafNodes: RangeTreeLeafNode[] = [] const leafNodes: RangeTreeLeafNode[] = []
const y = stackDepth + 1 const y = stackDepth
let minLeft = Infinity let minLeft = Infinity
let maxRight = -Infinity let maxRight = -Infinity
@ -229,9 +236,7 @@ export class FlamechartRenderer {
// which is even more expensive than not using a cache at all! Instead, // which is even more expensive than not using a cache at all! Instead,
// we'll cache the first 2 entries in that case, and re-use that cache each time, // we'll cache the first 2 entries in that case, and re-use that cache each time,
// while rendering the final 2 items without use of the cache. // while rendering the final 2 items without use of the cache.
// An exception here is if the node is already in the cache! let useCache = renderedBatchCount++ < cacheCapacity
let useCache = renderedBatchCount++ < cacheCapacity || this.rowAtlas.has(leaf)
if (useCache) { if (useCache) {
cachedLeaves.push(leaf) cachedLeaves.push(leaf)
} else { } else {
@ -240,17 +245,24 @@ export class FlamechartRenderer {
}) })
this.rowAtlas.writeToAtlasIfNeeded(cachedLeaves, (textureDstRect, leaf) => { this.rowAtlas.writeToAtlasIfNeeded(cachedLeaves, (textureDstRect, leaf) => {
const configSpaceBounds = new Rect(
new Vec2(0, leaf.getBounds().top()),
new Vec2(this.flamechart.getTotalWeight(), 1)
)
this.canvasContext.drawRectangleBatch({ this.canvasContext.drawRectangleBatch({
batch: leaf.getBatch(), batch: leaf.getBatch(),
configSpaceSrcRect: leaf.getBounds(), configSpaceSrcRect: configSpaceBounds,
physicalSpaceDstRect: textureDstRect physicalSpaceDstRect: textureDstRect
}) })
}) })
const configToPhysical = AffineTransform.betweenRects(configSpaceSrcRect, physicalSpaceDstRect) const configToPhysical = AffineTransform.betweenRects(configSpaceSrcRect, physicalSpaceDstRect)
for (let leaf of cachedLeaves) { for (let leaf of cachedLeaves) {
const configSpaceLeafBounds = leaf.getBounds() const configSpaceBounds = new Rect(
const physicalLeafBounds = configToPhysical.transformRect(configSpaceLeafBounds) new Vec2(0, leaf.getBounds().top()),
new Vec2(this.flamechart.getTotalWeight(), 1)
)
const physicalLeafBounds = configToPhysical.transformRect(configSpaceBounds)
if (!this.rowAtlas.renderViaAtlas(leaf, physicalLeafBounds)) { if (!this.rowAtlas.renderViaAtlas(leaf, physicalLeafBounds)) {
console.error('Failed to render from cache') console.error('Failed to render from cache')
} }
@ -263,5 +275,14 @@ export class FlamechartRenderer {
physicalSpaceDstRect physicalSpaceDstRect
}) })
} }
// Overlay the atlas on top of the canvas for debugging
/*
this.canvasContext.drawTexture({
texture: this.rowAtlas.texture,
srcRect: new Rect(Vec2.zero, new Vec2(this.rowAtlas.texture.width, this.rowAtlas.texture.height)),
dstRect: new Rect(Vec2.zero, new Vec2(800, 800))
})
*/
} }
} }

View File

@ -195,7 +195,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
const renderFrameLabelAndChildren = (frame: FlamechartFrame, depth = 0) => { const renderFrameLabelAndChildren = (frame: FlamechartFrame, depth = 0) => {
const width = frame.end - frame.start const width = frame.end - frame.start
const configSpaceBounds = new Rect( const configSpaceBounds = new Rect(
new Vec2(frame.start, depth + 1), new Vec2(frame.start, depth),
new Vec2(width, 1) new Vec2(width, 1)
) )
@ -281,7 +281,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
if (this.lastBounds == null) { if (this.lastBounds == null) {
this.setConfigSpaceViewportRect(new Rect( this.setConfigSpaceViewportRect(new Rect(
new Vec2(0, 0), new Vec2(0, -1),
new Vec2(this.configSpaceSize().x, height / this.LOGICAL_VIEW_SPACE_FRAME_HEIGHT) new Vec2(this.configSpaceSize().x, height / this.LOGICAL_VIEW_SPACE_FRAME_HEIGHT)
)) ))
} else if (windowResized) { } else if (windowResized) {
@ -308,7 +308,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
if (this.props.configSpaceViewportRect.isEmpty()) return if (this.props.configSpaceViewportRect.isEmpty()) return
this.props.canvasContext.renderInto(this.container, () => { this.props.canvasContext.renderInto(this.container, (context) => {
this.props.flamechartRenderer.render({ this.props.flamechartRenderer.render({
physicalSpaceDstRect: new Rect(Vec2.zero, this.physicalViewSize()), physicalSpaceDstRect: new Rect(Vec2.zero, this.physicalViewSize()),
configSpaceSrcRect: this.props.configSpaceViewportRect configSpaceSrcRect: this.props.configSpaceViewportRect
@ -430,7 +430,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
const setHoveredLabel = (frame: FlamechartFrame, depth = 0) => { const setHoveredLabel = (frame: FlamechartFrame, depth = 0) => {
const width = frame.end - frame.start const width = frame.end - frame.start
const configSpaceBounds = new Rect( const configSpaceBounds = new Rect(
new Vec2(frame.start, depth + 1), new Vec2(frame.start, depth),
new Vec2(width, 1) new Vec2(width, 1)
) )
if (configSpaceMouse.x < configSpaceBounds.left()) return null if (configSpaceMouse.x < configSpaceBounds.left()) return null
@ -585,11 +585,13 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
) )
} }
private minConfigSpaceViewportRectWidth() { return 3 * this.props.flamechart.getMinFrameWidth(); } private minConfigSpaceViewportRectWidth() {
return Math.min(this.props.flamechart.getTotalWeight(), 3 * this.props.flamechart.getMinFrameWidth());
}
private setConfigSpaceViewportRect = (viewportRect: Rect): void => { private setConfigSpaceViewportRect = (viewportRect: Rect): void => {
const configSpaceOriginBounds = new Rect( const configSpaceOriginBounds = new Rect(
new Vec2(0, 0), new Vec2(0, -1),
Vec2.max(new Vec2(0, 0), this.configSpaceSize().minus(viewportRect.size)) Vec2.max(new Vec2(0, 0), this.configSpaceSize().minus(viewportRect.size))
) )

View File

@ -83,9 +83,9 @@ export class List<V> {
node.prev.next = node.next node.prev.next = node.next
node.next = null node.next = null
node.prev = null node.prev = null
}
this.size-- this.size--
} }
}
} }
interface LRUCacheNode<K, V> { interface LRUCacheNode<K, V> {

View File

@ -157,9 +157,8 @@ export class RectangleBatchRenderer {
const viewportSize = new Vec2(context.viewportWidth, context.viewportHeight) const viewportSize = new Vec2(context.viewportWidth, context.viewportHeight)
const physicalToNDC = AffineTransform const physicalToNDC = AffineTransform.withTranslation(new Vec2(-1, 1))
.withTranslation(new Vec2(-1, 1)) .times(AffineTransform.withScale(new Vec2(2, -2).dividedByPointwise(viewportSize)))
.withScale(new Vec2(2, -2).dividedByPointwise(viewportSize))
return physicalToNDC.times(configToPhysical).flatten() return physicalToNDC.times(configToPhysical).flatten()
} }

1
sample/tall.txt Normal file
View File

@ -0,0 +1 @@
a;b;c;d;e;f;g;h;i;j;k;l;m;n;o;p;q;r;s;t;u;v;w;x;y;z 1