Ugly flamechart rendering working
This commit is contained in:
-128
@@ -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
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())) }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user