From 141fd5239511f21eb7286ec9698c028338e0401e Mon Sep 17 00:00:00 2001 From: Jamie Wong Date: Thu, 23 Nov 2017 01:25:25 -0800 Subject: [PATCH] Ugly flamechart rendering working --- flamechart.ts | 128 -------------------------------- flamechart.tsx | 194 +++++++++++++++++++++++++++++++++++++++++++++++++ index.tsx | 57 +++------------ math.ts | 32 ++++++++ regl.d.ts | 6 +- 5 files changed, 242 insertions(+), 175 deletions(-) delete mode 100644 flamechart.ts create mode 100644 flamechart.tsx create mode 100644 math.ts diff --git a/flamechart.ts b/flamechart.ts deleted file mode 100644 index 11ef3c5..0000000 --- a/flamechart.ts +++ /dev/null @@ -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 - }) -} \ No newline at end of file diff --git a/flamechart.tsx b/flamechart.tsx new file mode 100644 index 0000000..051c5d9 --- /dev/null +++ b/flamechart.tsx @@ -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 { + renderer: ReglCommand | 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 + } +} + +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)({ + 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 + }) +} \ No newline at end of file diff --git a/index.tsx b/index.tsx index 26c7c49..30ccee1 100644 --- a/index.tsx +++ b/index.tsx @@ -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 - } -} - -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
- + {flamechart && }
} } diff --git a/math.ts b/math.ts new file mode 100644 index 0000000..753bebb --- /dev/null +++ b/math.ts @@ -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())) } +} \ No newline at end of file diff --git a/regl.d.ts b/regl.d.ts index e9e756d..dfea8fb 100644 --- a/regl.d.ts +++ b/regl.d.ts @@ -10,6 +10,10 @@ declare module "regl" { type ReglValue

= 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

{ /** Source code of vertex shader */ vert: string @@ -31,7 +35,7 @@ declare module "regl" { offset?: number } - interface ReglCommand

{ + export interface ReglCommand

{ (p: P): void }