Ugly flamechart rendering working

This commit is contained in:
Jamie Wong
2017-11-23 01:25:25 -08:00
parent af9783c838
commit 141fd52395
5 changed files with 242 additions and 175 deletions
-128
View File
@@ -1,128 +0,0 @@
import {Profile, Frame} from './profile'
import regl, {ReglCommandConstructor} from 'regl'
interface FlamechartFrame {
frame: Frame
start: number
end: number
}
type StackLayer = FlamechartFrame[]
export class Flamechart {
// Bottom to top
private layers: StackLayer[] = []
private duration: number = 0
private getLayers() { return this.layers }
private appendFrame(layerIndex: number, frame: Frame, timeDelta: number) {
while (layerIndex <= this.layers.length) this.layers.push([])
this.layers[layerIndex].push({
frame: frame,
start: this.duration,
end: this.duration + timeDelta
})
}
private appendSample(stack: Frame[], timeDelta: number) {
for (let i = 0; i < stack.length; i++) {
this.appendFrame(i, stack[i], timeDelta)
}
this.duration += timeDelta
}
private static mergeAdjacentFrames(layer: StackLayer): StackLayer {
const ret: StackLayer = []
for (let flamechartFrame of layer) {
const prev = ret.length > 0 ? ret[ret.length - 1] : null
if (prev && prev.frame === flamechartFrame.frame && prev.end === flamechartFrame.start) {
prev.end = flamechartFrame.end
} else {
ret.push(flamechartFrame)
}
}
return ret
}
constructor(private profile: Profile) {
profile.forEachSample(this.appendSample.bind(this))
this.layers = this.layers.map(Flamechart.mergeAdjacentFrames)
}
}
interface Rect {
left: number
top: number
width: number
height: number
}
type vec2 = [number, number]
type vec3 = [number, number, number]
type mat3 = [number, number, number, number, number, number, number, number, number]
export const rectangleBatchRenderer = (ctx: WebGLRenderingContext, rects: Rect[], colors: vec3[]) => {
const gl = regl(ctx)
const positions: vec2[] = []
const vertexColors: vec3[] = []
const addRectangle = (r: Rect, color: [number, number, number]) => {
const NW: [number, number] = [r.left, r.top]
const NE: [number, number] = [r.left + r.width, r.top]
const SW: [number, number] = [r.left, r.top + r.height]
const SE: [number, number] = [r.left + r.width, r.top + r.height]
positions.push(NW)
positions.push(SW)
positions.push(NE)
positions.push(SW)
positions.push(NE)
positions.push(SE)
for (let i = 0; i < 6; i++) vertexColors.push(color)
}
for (let i = 0; i < rects.length; i++) addRectangle(rects[i], colors[i])
return gl<{
configSpaceToNDC: mat3
}>({
vert: `
uniform mat3 configSpaceToNDC;
attribute vec2 position;
attribute vec3 color;
varying vec3 vColor;
void main() {
vColor = color;
gl_Position = vec4((configSpaceToNDC * vec3(position, 1)).xy, 0, 1);
}
`,
frag: `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1);
}
`,
attributes: {
position: positions,
color: vertexColors
},
uniforms: {
configSpaceToNDC: (context, props) => {
return props.configSpaceToNDC
}
},
primitive: 'triangles',
count: colors.length
})
}
+194
View File
@@ -0,0 +1,194 @@
import {h, render, Component} from 'preact'
import {StyleSheet, css} from 'aphrodite'
import {Profile, Frame} from './profile'
import regl, {vec2, vec3, mat3, ReglCommand, ReglCommandConstructor} from 'regl'
import { Rect, Vec2 } from './math'
interface FlamechartFrame {
frame: Frame
start: number
end: number
}
type StackLayer = FlamechartFrame[]
export class Flamechart {
// Bottom to top
private layers: StackLayer[] = []
private duration: number = 0
getDuration() { return this.duration }
getLayers() { return this.layers }
private appendFrame(layerIndex: number, frame: Frame, timeDelta: number) {
while (layerIndex >= this.layers.length) this.layers.push([])
this.layers[layerIndex].push({
frame: frame,
start: this.duration,
end: this.duration + timeDelta
})
}
private appendSample(stack: Frame[], timeDelta: number) {
for (let i = 0; i < stack.length; i++) {
this.appendFrame(i, stack[i], timeDelta)
}
this.duration += timeDelta
}
private static mergeAdjacentFrames(layer: StackLayer): StackLayer {
const ret: StackLayer = []
for (let flamechartFrame of layer) {
const prev = ret.length > 0 ? ret[ret.length - 1] : null
if (prev && prev.frame === flamechartFrame.frame && prev.end === flamechartFrame.start) {
prev.end = flamechartFrame.end
} else {
ret.push(flamechartFrame)
}
}
return ret
}
constructor(private profile: Profile) {
profile.forEachSample(this.appendSample.bind(this))
this.layers = this.layers.map(Flamechart.mergeAdjacentFrames)
}
}
interface FlamechartViewProps {
flamechart: Flamechart
}
export class FlamechartView extends Component<FlamechartViewProps, void> {
renderer: ReglCommand<RectangleBatchRendererProps> | null = null
canvasRef = (element?: Element) => {
if (element) {
const {flamechart} = this.props
const rects: Rect[] = []
const colors: vec3[] = []
const layers = flamechart.getLayers()
const duration = flamechart.getDuration()
const maxStackHeight = layers.length
for (let i = 0; i < layers.length; i++) {
const layer = layers[i]
for (let frame of layer) {
rects.push(new Rect(
new Vec2(frame.start, i),
new Vec2(frame.end - frame.start, 1)
))
colors.push([Math.random(), Math.random(), 0])
}
}
const canvas = element as HTMLCanvasElement
const ctx = canvas.getContext('webgl')!
this.renderer = rectangleBatchRenderer(ctx, rects, colors)
this.renderGL()
}
}
renderGL() {
if (this.renderer) {
const {flamechart} = this.props
const layers = flamechart.getLayers()
const duration = flamechart.getDuration()
const maxStackHeight = layers.length
const sx = 2 / duration
const sy = -2 / maxStackHeight
const tx = -1
const ty = 1
this.renderer({
configSpaceToNDC: [
sx, 0, 0,
0, sy, 0,
tx, ty, 1
]
})
}
}
render() {
const width = window.innerWidth
const height = window.innerHeight
return <canvas width={width} height={height} ref={this.canvasRef} className={css(style.fullscreen)}></canvas>
}
}
const style = StyleSheet.create({
fullscreen: {
width: '100vw',
height: '100vh',
}
})
interface RectangleBatchRendererProps {
configSpaceToNDC: mat3
}
export const rectangleBatchRenderer = (ctx: WebGLRenderingContext, rects: Rect[], colors: vec3[]) => {
const positions: vec2[] = []
const vertexColors: vec3[] = []
const addRectangle = (r: Rect, color: vec3) => {
function addVertex(v: Vec2) {
positions.push(v.flatten())
vertexColors.push(color)
}
addVertex(r.topLeft())
addVertex(r.bottomLeft())
addVertex(r.topRight())
addVertex(r.bottomLeft())
addVertex(r.topRight())
addVertex(r.bottomRight())
}
for (let i = 0; i < rects.length; i++) {
addRectangle(rects[i], colors[i])
}
return regl(ctx)<RectangleBatchRendererProps>({
vert: `
uniform mat3 configSpaceToNDC;
attribute vec2 position;
attribute vec3 color;
varying vec3 vColor;
void main() {
vColor = color;
gl_Position = vec4((configSpaceToNDC * vec3(position, 1)).xy, 0, 1);
}
`,
frag: `
precision mediump float;
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1);
}
`,
attributes: {
position: positions,
color: vertexColors
},
uniforms: {
configSpaceToNDC: (context, props) => {
return props.configSpaceToNDC
}
},
primitive: 'triangles',
count: vertexColors.length
})
}
+11 -46
View File
@@ -2,59 +2,23 @@ import {h, render, Component} from 'preact'
import {StyleSheet, css} from 'aphrodite'
import {importFromStackprof} from './stackprof'
import {Flamechart, rectangleBatchRenderer} from './flamechart'
import {Profile} from './profile'
import {Flamechart, FlamechartView} from './flamechart'
import { request } from 'https';
interface Rect {
left: number
top: number
width: number
height: number
interface ApplicaionState {
profile: Profile | null
flamechart: Flamechart | null
}
class Canvas extends Component<{}, void> {
canvasRef = (element?: Element) => {
if (element) {
const rectangles: Rect[] = []
const colors: [number, number, number][] = []
for (let i = 0; i < 1000; i++) {
rectangles.push({
left: 2 * Math.random() - 1,
top: 2 * Math.random() - 1,
width: 2 * Math.random(),
height: 2 * Math.random(),
})
colors.push([Math.random(), Math.random(), Math.random()])
}
const renderer = rectangleBatchRenderer((element as HTMLCanvasElement).getContext('webgl')!, rectangles, colors)
;(function doit() {
renderer({
configSpaceToNDC: [
1, 0, 0,
0, 1, 0,
0, 0, 1,
]
})
requestAnimationFrame(doit)
})()
}
}
render() {
const width = window.innerWidth
const height = window.innerHeight
return <canvas width={width} height={height} ref={this.canvasRef} className={css(style.root)}></canvas>
}
}
class Application extends Component<{}, void> {
class Application extends Component<{}, ApplicaionState> {
onDrop = (ev: DragEvent) => {
const reader = new FileReader
reader.addEventListener('loadend', () => {
console.log(importFromStackprof(reader.result))
const profile = importFromStackprof(reader.result)
const flamechart = new Flamechart(profile)
this.setState({profile, flamechart})
})
reader.readAsText(ev.dataTransfer.files.item(0))
ev.preventDefault()
@@ -65,8 +29,9 @@ class Application extends Component<{}, void> {
}
render() {
const {flamechart} = this.state
return <div onDrop={this.onDrop} onDragOver={this.onDragOver} className={css(style.root)}>
<Canvas />
{flamechart && <FlamechartView flamechart={flamechart} />}
</div>
}
}
+32
View File
@@ -0,0 +1,32 @@
export class Vec2 {
constructor(readonly x = 0, readonly y = 0) {}
plus(other: Vec2) { return new Vec2(this.x + other.x, this.y + other.y) }
minus(other: Vec2) { return new Vec2(this.x - other.x, this.y - other.y) }
times(scalar: number) { return new Vec2(this.x * scalar, this.y * scalar) }
dot(other: Vec2) { return this.x * other.x + this.y * other.y }
length2() { return this.dot(this) }
length() { return Math.sqrt(this.length2()) }
flatten(): [number, number] { return [this.x, this.y] }
}
export class Rect {
constructor(
readonly origin = new Vec2(),
readonly size = new Vec2()
) {}
width() { return this.size.x }
height() { return this.size.y }
left() { return this.origin.x }
right() { return this.left() + this.width() }
top() { return this.origin.y }
bottom() { return this.top() + this.height() }
topLeft() { return this.origin }
topRight() { return this.origin.plus(new Vec2(this.width(), 0)) }
bottomRight() { return this.origin.plus(this.size) }
bottomLeft() { return this.origin.plus(new Vec2(0, this.height())) }
}
Vendored
+5 -1
View File
@@ -10,6 +10,10 @@ declare module "regl" {
type ReglValue<P> = ReglPrimitiveValue | {(context: any, props: P, batchId: number): ReglPrimitiveValue}
export type vec2 = [number, number]
export type vec3 = [number, number, number]
export type mat3 = [number, number, number, number, number, number, number, number, number]
interface ReglCommandParameters<P> {
/** Source code of vertex shader */
vert: string
@@ -31,7 +35,7 @@ declare module "regl" {
offset?: number
}
interface ReglCommand<P> {
export interface ReglCommand<P> {
(p: P): void
}