I think it works!!!!!

This commit is contained in:
Jamie Wong 2018-01-30 01:51:02 -08:00
parent 838cc06ff5
commit 53656fef43
11 changed files with 250 additions and 203 deletions

View File

@ -12,7 +12,6 @@ import {Profile, Frame} from './profile'
import {Flamechart} from './flamechart'
import { FlamechartView } from './flamechart-view'
import { FontFamily, FontSize, Colors } from './style'
import { FrameColorGenerator } from './color'
const enum SortOrder {
CHRONO,
@ -224,13 +223,26 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
const frames: Frame[] = []
profile.forEachFrame(f => frames.push(f))
const colorGenerator = new FrameColorGenerator(frames)
function key(f: Frame) {
return (f.file || '') + f.name
}
function compare(a: Frame, b: Frame) {
return key(a) > key(b) ? 1 : -1
}
frames.sort(compare)
const frameToColorBucket = new Map<Frame, number>()
for (let i = 0; i < frames.length; i++) {
frameToColorBucket.set(frames[i], Math.floor(255 * i / frames.length))
}
function getColorBucketForFrame(frame: Frame) {
return frameToColorBucket.get(frame) || 0
}
const flamechart = new Flamechart({
getTotalWeight: profile.getTotalWeight.bind(profile),
forEachCall: profile.forEachCall.bind(profile),
formatValue: profile.formatValue.bind(profile),
getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator)
getColorBucketForFrame
})
const flamechartRenderer = new FlamechartRenderer(this.canvasContext, flamechart)
@ -238,7 +250,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
getTotalWeight: profile.getTotalNonIdleWeight.bind(profile),
forEachCall: profile.forEachCallGrouped.bind(profile),
formatValue: profile.formatValue.bind(profile),
getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator)
getColorBucketForFrame
})
const sortedFlamechartRenderer = new FlamechartRenderer(this.canvasContext, sortedFlamechart)

View File

@ -5,7 +5,7 @@ import { TextureCachedRenderer, TextureRenderer, TextureRendererProps } from './
import { StatsPanel } from './stats'
import { Vec2, Rect } from './math';
import { OutlineRenderer, OutlineRendererProps } from './outline-renderer';
import { FlamechartColorPassRenderer, FlamechartColorPassRenderProps } from './flamechart-color-pass-renderer';
type FrameCallback = () => void
@ -18,7 +18,7 @@ export class CanvasContext {
private rectangleBatchRenderer: RectangleBatchRenderer
private viewportRectangleRenderer: ViewportRectangleRenderer
private textureRenderer: TextureRenderer
private outlineRenderer: OutlineRenderer
private flamechartColorPassRenderer: FlamechartColorPassRenderer
private setViewportScope: regl.Command<{ physicalBounds: Rect }>
private setScissor: regl.Command<{}>
@ -36,7 +36,7 @@ export class CanvasContext {
this.rectangleBatchRenderer = new RectangleBatchRenderer(this.gl)
this.viewportRectangleRenderer = new ViewportRectangleRenderer(this.gl)
this.textureRenderer = new TextureRenderer(this.gl)
this.outlineRenderer = new OutlineRenderer(this.gl)
this.flamechartColorPassRenderer = new FlamechartColorPassRenderer(this.gl)
this.setScissor = this.gl({ scissor: { enable: true } })
this.setViewportScope = this.gl<SetViewportScopeProps>({
context: {
@ -125,8 +125,8 @@ export class CanvasContext {
this.textureRenderer.render(props)
}
drawOutlines(props: OutlineRendererProps) {
this.outlineRenderer.render(props)
drawFlamechartColorPass(props: FlamechartColorPassRenderProps) {
this.flamechartColorPassRenderer.render(props)
}
createRectangleBatch(): RectangleBatch {

View File

@ -27,6 +27,7 @@ function fract(x: number) {
return x - Math.floor(x)
}
// TODO(jlfwong): Can probably delete this?
export class FrameColorGenerator {
private frameToColor = new Map<Frame, Color>()

View File

@ -0,0 +1,172 @@
import * as regl from 'regl'
import { Vec2, Rect, AffineTransform } from './math'
export class FlamechartColorPassRenderProps {
rectInfoTexture: regl.Texture
renderOutlines: boolean
srcRect: Rect
dstRect: Rect
}
export class FlamechartColorPassRenderer {
private command: regl.Command<FlamechartColorPassRenderProps>
constructor(gl: regl.Instance) {
this.command = gl({
vert: `
uniform mat3 uvTransform;
uniform mat3 positionTransform;
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = (uvTransform * vec3(uv, 1)).xy;
gl_Position = vec4((positionTransform * vec3(position, 1)).xy, 0, 1);
}
`,
frag: `
precision mediump float;
uniform vec2 uvSpacePixelSize;
uniform float renderOutlines;
varying vec2 vUv;
uniform sampler2D colorTexture;
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_luma/chroma/hue
vec3 lch2rgb(float L, float C, float H) {
float hPrime = H / 60.0;
float X = C * (1.0 - abs(mod(hPrime, 2.0) - 1.0));
vec3 RGB =
hPrime < 1.0 ? vec3(C, X, 0) :
hPrime < 2.0 ? vec3(X, C, 0) :
hPrime < 3.0 ? vec3(0, C, X) :
hPrime < 4.0 ? vec3(0, X, C) :
hPrime < 5.0 ? vec3(X, 0, C) :
vec3(C, 0, X);
float m = L - dot(RGB, vec3(0.30, 0.59, 0.11));
return RGB + vec3(m, m, m);
}
vec3 colorForBucket(float bucket) {
float x = 2.0 * fract(100.0 * bucket) - 1.0;
float L = 0.85 - 0.1 * x;
float C = 0.20 + 0.1 * x;
float H = 360.0 * bucket;
return lch2rgb(L, C, H);
}
void main() {
vec4 here = texture2D(colorTexture, vUv);
if (here.z == 0.0) {
// Background color
gl_FragColor = vec4(0, 0, 0, 0);
return;
}
// Sample the 4 surrounding pixels in the depth texture to determine
// if we should draw a boundary here or not.
vec4 N = texture2D(colorTexture, vUv + vec2(0, uvSpacePixelSize.y));
vec4 E = texture2D(colorTexture, vUv + vec2(uvSpacePixelSize.x, 0));
vec4 S = texture2D(colorTexture, vUv + vec2(0, -uvSpacePixelSize.y));
vec4 W = texture2D(colorTexture, vUv + vec2(-uvSpacePixelSize.x, 0));
// NOTE: For outline checks, we intentionally check both the right
// and the left to determine if we're an edge. If a rectangle is a single
// pixel wide, we don't want to render it as an outline, so this method
// of checking ensures that we don't outline single physical-space
// pixel width rectangles.
if (
renderOutlines > 0.0 &&
(
here.y == N.y && here.y != S.y || // Top edge
here.y == S.y && here.y != N.y || // Bottom edge
here.x == E.x && here.x != W.x || // Left edge
here.x == W.x && here.x != E.x
)
) {
// We're on an edge! Draw white.
gl_FragColor = vec4(1, 1, 1, 1);
} else {
// Not on an edge. Draw the appropriate color;
gl_FragColor = vec4(colorForBucket(here.z), here.a);
}
}
`,
depth: {
enable: false
},
attributes: {
// Cover full canvas with a rectangle
// with 2 triangles using a triangle
// strip.
//
// 0 +--+ 1
// | /|
// |/ |
// 2 +--+ 3
position: gl.buffer([
[-1, 1],
[1, 1],
[-1, -1],
[1, -1]
]),
uv: gl.buffer([
[0, 1],
[1, 1],
[0, 0],
[1, 0]
])
},
count: 4,
primitive: 'triangle strip',
uniforms: {
colorTexture: (context, props) => props.rectInfoTexture,
uvTransform: (context, props) => {
const { srcRect, rectInfoTexture } = props
const physicalToUV = AffineTransform.withTranslation(new Vec2(0, 1))
.times(AffineTransform.withScale(new Vec2(1, -1)))
.times(AffineTransform.betweenRects(
new Rect(Vec2.zero, new Vec2(rectInfoTexture.width, rectInfoTexture.height)),
Rect.unit
))
const uvRect = physicalToUV.transformRect(srcRect)
return AffineTransform.betweenRects(
Rect.unit,
uvRect,
).flatten()
},
renderOutlines: (context, props) => {
return props.renderOutlines ? 1.0 : 0.0
},
uvSpacePixelSize: (context, props) => {
return Vec2.unit.dividedByPointwise(new Vec2(props.rectInfoTexture.width, props.rectInfoTexture.height)).flatten()
},
positionTransform: (context, props) => {
const { dstRect } = props
const viewportSize = new Vec2(context.viewportWidth, context.viewportHeight)
const physicalToNDC = AffineTransform.withScale(new Vec2(1, -1))
.times(AffineTransform.betweenRects(
new Rect(Vec2.zero, viewportSize),
Rect.NDC)
)
const ndcRect = physicalToNDC.transformRect(dstRect)
return AffineTransform.betweenRects(Rect.NDC, ndcRect).flatten()
}
},
})
}
render(props: FlamechartColorPassRenderProps) {
this.command(props)
}
}

View File

@ -78,6 +78,10 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
}> | null = null
private renderRects() {
if (!this.container) return
// Hasn't resized yet -- no point in rendering yet
if (this.physicalViewSize().x < 2) return
if (!this.cachedRenderer) {
this.cachedRenderer = this.props.canvasContext.createTextureCachedRenderer({
shouldUpdate(oldProps, newProps) {
@ -93,15 +97,29 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
this.physicalViewSize().minus(this.minimapOrigin())
),
configSpaceSrcRect: new Rect(new Vec2(0, 0), this.configSpaceSize()),
renderOutlines: false
})
}
})
}
this.props.canvasContext.renderInto(this.container, (context) => {
// TODO(jlfwong): Switch back to the texture cached renderer once I figure out
// how to resize a framebuffer while another framebuffer is active. It seems
// to crash regl. I should submit a reduced repro case and hopefully get it fixed?
/*
this.cachedRenderer!.render({
physicalSize: this.physicalViewSize()
})
*/
this.props.flamechartRenderer.render({
configSpaceSrcRect: new Rect(new Vec2(0, 0), this.configSpaceSize()),
physicalSpaceDstRect: new Rect(
this.minimapOrigin(),
this.physicalViewSize().minus(this.minimapOrigin())
),
renderOutlines: false
})
this.props.canvasContext.drawViewportRectangle({
configSpaceViewportRect: this.props.configSpaceViewportRect,
configSpaceToPhysicalViewSpace: this.configSpaceToPhysicalViewSpace(),

View File

@ -29,7 +29,7 @@ class RowAtlas<K> {
framebuffer: this.framebuffer
})
this.clearLineBatch = canvasContext.createRectangleBatch()
this.clearLineBatch.addRect(Rect.unit, new Color(1, 1, 1, 1))
this.clearLineBatch.addRect(Rect.unit, new Color(0, 0, 0, 0))
}
has(key: K) { return this.rowCache.has(key) }
@ -169,6 +169,7 @@ class RangeTreeInteriorNode implements RangeTreeNode {
export interface FlamechartRendererProps {
configSpaceSrcRect: Rect
physicalSpaceDstRect: Rect
renderOutlines: boolean
}
interface FlamechartRowAtlasKey {
@ -180,8 +181,7 @@ interface FlamechartRowAtlasKey {
export class FlamechartRenderer {
private layers: RangeTreeNode[] = []
private rowAtlas: RowAtlas<FlamechartRowAtlasKey>
private colorTexture: regl.Texture
private depthTexture: regl.Texture
private rectInfoTexture: regl.Texture
private framebuffer: regl.Framebuffer
private renderToFramebuffer: regl.Command<{}>
private withContext: regl.Command<{}>
@ -199,7 +199,10 @@ export class FlamechartRenderer {
let rectCount = 0
for (let frame of flamechart.getLayers()[stackDepth]) {
const layer = flamechart.getLayers()[stackDepth]
for (let i = 0; i < layer.length; i++) {
const frame = layer[i]
if (batch.getRectCount() >= MAX_BATCH_SIZE) {
leafNodes.push(new RangeTreeLeafNode(batch, new Rect(
new Vec2(minLeft, stackDepth),
@ -215,7 +218,17 @@ export class FlamechartRenderer {
)
minLeft = Math.min(minLeft, configSpaceBounds.left())
maxRight = Math.max(maxRight, configSpaceBounds.right())
const color = flamechart.getColorForFrame(frame.node.frame)
// We'll use the red channel to indicate the index to allow
// us to separate adjacent rectangles within a row from one another,
// the green channel to indicate the row,
// and the blue channel to indicate the color bucket to render.
// We add one to each so we have zero reserved for the background color.
const color = new Color(
(1 + i % 255) / 256,
(1 + stackDepth % 255) / 256,
(1 + this.flamechart.getColorBucketForFrame(frame.node.frame)) / 256
)
batch.addRect(configSpaceBounds, color)
rectCount++
}
@ -234,11 +247,9 @@ export class FlamechartRenderer {
// TODO(jlfwong): Extract this to CanvasContext
this.withContext = canvasContext.gl({})
this.colorTexture = this.canvasContext.gl.texture({ width: 1, height: 1 })
this.depthTexture = this.canvasContext.gl.texture({ width: 1, height: 1, format: 'depth', type: 'uint16' })
this.rectInfoTexture = this.canvasContext.gl.texture({ width: 1, height: 1 })
this.framebuffer = this.canvasContext.gl.framebuffer({
color: [this.colorTexture],
depth: this.depthTexture
color: [this.rectInfoTexture],
})
this.renderToFramebuffer = canvasContext.gl({
@ -352,22 +363,13 @@ export class FlamechartRenderer {
}
})
this.canvasContext.drawOutlines({
colorTexture: this.colorTexture,
depthTexture: this.depthTexture,
srcRect: new Rect(Vec2.zero, new Vec2(this.colorTexture.width, this.colorTexture.height)),
dstRect: physicalSpaceDstRect
this.canvasContext.drawFlamechartColorPass({
rectInfoTexture: this.rectInfoTexture,
srcRect: new Rect(Vec2.zero, new Vec2(this.rectInfoTexture.width, this.rectInfoTexture.height)),
dstRect: physicalSpaceDstRect,
renderOutlines: props.renderOutlines
})
// Paint the color texture for debugging
/*
this.canvasContext.drawTexture({
texture: this.colorTexture,
srcRect: new Rect(Vec2.zero, new Vec2(this.colorTexture.width, this.colorTexture.height)),
dstRect: physicalSpaceDstRect
})
*/
// Overlay the atlas on top of the canvas for debugging
/*
this.canvasContext.drawTexture({

View File

@ -311,7 +311,8 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
this.props.canvasContext.renderInto(this.container, (context) => {
this.props.flamechartRenderer.render({
physicalSpaceDstRect: new Rect(Vec2.zero, this.physicalViewSize()),
configSpaceSrcRect: this.props.configSpaceViewportRect
configSpaceSrcRect: this.props.configSpaceViewportRect,
renderOutlines: true
})
})
}

View File

@ -1,5 +1,4 @@
import {Frame, CallTreeNode} from './profile'
import { Color } from './color'
import { lastOf } from './utils'
@ -23,7 +22,7 @@ interface FlamechartDataSource {
closeFrame: (value: number) => void
): void
getColorForFrame(f: Frame): Color
getColorBucketForFrame(f: Frame): number
}
export class Flamechart {
@ -34,7 +33,7 @@ export class Flamechart {
getTotalWeight() { return this.totalWeight }
getLayers() { return this.layers }
getColorForFrame(f: Frame) { return this.source.getColorForFrame(f) }
getColorBucketForFrame(frame: Frame) { return this.source.getColorBucketForFrame(frame) }
getMinFrameWidth() { return this.minFrameWidth }
formatValue(v: number) { return this.source.formatValue(v) }

View File

@ -1,130 +0,0 @@
import * as regl from 'regl'
import { Vec2, Rect, AffineTransform } from './math'
export class OutlineRendererProps {
colorTexture: regl.Texture
depthTexture: regl.Texture
srcRect: Rect
dstRect: Rect
}
export class OutlineRenderer {
private command: regl.Command<OutlineRendererProps>
constructor(gl: regl.Instance) {
this.command = gl({
vert: `
uniform mat3 uvTransform;
uniform mat3 positionTransform;
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = (uvTransform * vec3(uv, 1)).xy;
gl_Position = vec4((positionTransform * vec3(position, 1)).xy, 0, 1);
}
`,
frag: `
precision mediump float;
uniform vec2 uvSpacePixelSize;
varying vec2 vUv;
uniform sampler2D colorTexture;
uniform sampler2D depthTexture;
void main() {
// Sample the 4 surrounding pixels in the depth texture to determine
// if we should draw a boundary here or not.
float N = texture2D(depthTexture, vUv + vec2(0, uvSpacePixelSize.y)).r;
float E = texture2D(depthTexture, vUv + vec2(uvSpacePixelSize.x, 0)).r;
float S = texture2D(depthTexture, vUv + vec2(0, -uvSpacePixelSize.y)).r;
float W = texture2D(depthTexture, vUv + vec2(-uvSpacePixelSize.x, 0)).r;
float here = texture2D(depthTexture, vUv).x;
if (
here == N && here != S || // Top edge
here == S && here != N || // Bottom edge
here == E && here != W || // Left edge
here == W && here != E
) {
// We're on an edge! Draw white.
gl_FragColor = vec4(1, 1, 1, 1);
} else {
gl_FragColor = texture2D(colorTexture, vUv);
}
}
`,
depth: {
enable: false
},
attributes: {
// Cover full canvas with a rectangle
// with 2 triangles using a triangle
// strip.
//
// 0 +--+ 1
// | /|
// |/ |
// 2 +--+ 3
position: gl.buffer([
[-1, 1],
[1, 1],
[-1, -1],
[1, -1]
]),
uv: gl.buffer([
[0, 1],
[1, 1],
[0, 0],
[1, 0]
])
},
count: 4,
primitive: 'triangle strip',
uniforms: {
colorTexture: (context, props) => props.colorTexture,
depthTexture: (context, props) => props.depthTexture,
uvTransform: (context, props) => {
const { srcRect, colorTexture } = props
const physicalToUV = AffineTransform.withTranslation(new Vec2(0, 1))
.times(AffineTransform.withScale(new Vec2(1, -1)))
.times(AffineTransform.betweenRects(
new Rect(Vec2.zero, new Vec2(colorTexture.width, colorTexture.height)),
Rect.unit
))
const uvRect = physicalToUV.transformRect(srcRect)
return AffineTransform.betweenRects(
Rect.unit,
uvRect,
).flatten()
},
uvSpacePixelSize: (context, props) => {
return Vec2.unit.dividedByPointwise(new Vec2(props.colorTexture.width, props.colorTexture.height)).flatten()
},
positionTransform: (context, props) => {
const { dstRect } = props
const viewportSize = new Vec2(context.viewportWidth, context.viewportHeight)
const physicalToNDC = AffineTransform.withScale(new Vec2(1, -1))
.times(AffineTransform.betweenRects(
new Rect(Vec2.zero, viewportSize),
Rect.NDC)
)
const ndcRect = physicalToNDC.transformRect(dstRect)
return AffineTransform.betweenRects(Rect.NDC, ndcRect).flatten()
}
},
})
}
render(props: OutlineRendererProps) {
this.command(props)
}
}

View File

@ -32,21 +32,10 @@ export class RectangleBatch {
return this.colorBuffer
}
private indexBuffer: regl.Buffer | null = null
getIndexBuffer() {
if (!this.indexBuffer) {
const indices = new Float32Array(this.rectCount)
for (let i = 0; i < this.rectCount; i++) indices[i] = i
this.indexBuffer = this.gl.buffer(indices)
}
return this.indexBuffer
}
uploadToGPU() {
this.getConfigSpaceOffsetBuffer()
this.getConfigSpaceSizeBuffer()
this.getColorBuffer()
this.getIndexBuffer()
}
addRect(rect: Rect, color: Color) {
@ -89,16 +78,13 @@ export interface RectangleBatchRendererProps {
export class RectangleBatchRenderer {
private command: regl.Command<RectangleBatchRendererProps>
constructor(gl: regl.Instance) {
// We draw the parity / 4 into the depth channel so it
// can be used in a post-processing step to draw boundaries
// between rectangles. We use 4 different values (2 per row)
// so we can distingish both between adjacent rectangles on a row
// and between rows!
// We draw the parity / 5 into the depth channel so it can be used in a
// post-processing step to draw boundaries between rectangles. We use 5
// different values (2 per row) + one for the background, so we can
// distingish both between adjacent rectangles on a row and between rows!
this.command = gl({
vert: `
uniform mat3 configSpaceToNDC;
uniform float parityMin;
uniform float parityOffset;
// Non-instanced
attribute vec2 corner;
@ -113,10 +99,9 @@ export class RectangleBatchRenderer {
void main() {
vColor = color;
float depth = parityMin + mod(parityOffset + index, 2.0);
vec2 configSpacePos = configSpaceOffset + corner * configSpaceSize;
vec2 position = (configSpaceToNDC * vec3(configSpacePos, 1)).xy;
gl_Position = vec4(position, depth / 4.0, 1);
gl_Position = vec4(position, 1, 1);
}
`,
@ -128,8 +113,9 @@ export class RectangleBatchRenderer {
precision mediump float;
varying vec3 vColor;
varying float vParity;
void main() {
gl_FragColor = vec4(vColor, 1);
gl_FragColor = vec4(vColor.rgb, 1);
}
`,
@ -169,15 +155,6 @@ export class RectangleBatchRenderer {
size: 3,
divisor: 1
}
},
index: (context, props) => {
return {
buffer: props.batch.getIndexBuffer(),
offset: 0,
stride: 4,
size: 1,
divisor: 1
}
}
},
@ -197,11 +174,11 @@ export class RectangleBatchRenderer {
},
parityOffset: (context, props) => {
return props.parityOffset || 0
return props.parityOffset == null ? 0 : props.parityOffset
},
parityMin: (context, props) => {
return props.parityMin || 0
return props.parityMin == null ? 0 : 1 + props.parityMin
}
},

View File

@ -155,10 +155,6 @@ export class TextureCachedRenderer<T> {
}
if (needsRender) {
// Render to texture
// TODO(jlfwong): Re-enable this when I figure out how to
// resize a framebuffer while another framebuffer is active.
/*
this.gl({
viewport: (context, props) => {
return {
@ -173,7 +169,6 @@ export class TextureCachedRenderer<T> {
this.gl.clear({color: [0, 0, 0, 0]})
this.renderUncached(props)
})
*/
}
const glViewportRect = new Rect(Vec2.zero, new Vec2(context.viewportWidth, context.viewportHeight))