WIP: Single GL context
This commit is contained in:
+128
-4
@@ -1,16 +1,19 @@
|
||||
import {h} from 'preact'
|
||||
import * as regl from 'regl'
|
||||
import {StyleSheet, css} from 'aphrodite'
|
||||
import {ReloadableComponent} from './reloadable'
|
||||
|
||||
import {importFromBGFlameGraph} from './import/bg-flamegraph'
|
||||
import {importFromStackprof} from './import/stackprof'
|
||||
import {importFromChromeTimeline, importFromChromeCPUProfile} from './import/chrome'
|
||||
import { RectangleBatch, RectangleBatchRenderer } from './rectangle-batch-renderer'
|
||||
|
||||
import {Profile, Frame} from './profile'
|
||||
import {Flamechart} from './flamechart'
|
||||
import { FlamechartView } from './flamechart-view'
|
||||
import { FontFamily, FontSize, Colors } from './style'
|
||||
import { FrameColorGenerator } from './color'
|
||||
import { Vec2, Rect } from './math'
|
||||
|
||||
const enum SortOrder {
|
||||
CHRONO,
|
||||
@@ -20,7 +23,9 @@ const enum SortOrder {
|
||||
interface ApplicationState {
|
||||
profile: Profile | null
|
||||
flamechart: Flamechart | null
|
||||
flamechartRectBatch: RectangleBatch | null
|
||||
sortedFlamechart: Flamechart | null
|
||||
sortedFlamechartRectBatch: RectangleBatch | null
|
||||
sortOrder: SortOrder
|
||||
loading: boolean
|
||||
}
|
||||
@@ -118,6 +123,85 @@ export class Toolbar extends ReloadableComponent<ToolbarProps, void> {
|
||||
}
|
||||
}
|
||||
|
||||
interface GLCanvasProps {
|
||||
setGL(gl: regl.Instance | null): void
|
||||
}
|
||||
export class GLCanvas extends ReloadableComponent<GLCanvasProps, void> {
|
||||
private canvas: HTMLCanvasElement | null
|
||||
private gl: regl.Instance | null
|
||||
|
||||
private ref = (canvas?: Element) => {
|
||||
if (canvas instanceof HTMLCanvasElement) {
|
||||
this.canvas = canvas
|
||||
this.gl = regl(canvas)
|
||||
} else {
|
||||
this.gl = null
|
||||
}
|
||||
this.props.setGL(this.gl)
|
||||
}
|
||||
|
||||
private maybeResize() {
|
||||
if (!this.canvas || !this.gl) return
|
||||
let { width, height } = this.canvas.getBoundingClientRect()
|
||||
width = Math.floor(width) * window.devicePixelRatio
|
||||
height = Math.floor(height) * window.devicePixelRatio
|
||||
|
||||
// Still initializing: don't resize yet
|
||||
if (width < 4 || height < 4) return
|
||||
const oldWidth = this.canvas.width
|
||||
const oldHeight = this.canvas.height
|
||||
|
||||
// Already at the right size
|
||||
if (width === oldWidth && height === oldHeight) return
|
||||
|
||||
this.canvas.width = width
|
||||
this.canvas.height = height
|
||||
}
|
||||
|
||||
onWindowResize = () => {
|
||||
this.maybeResize()
|
||||
window.addEventListener('resize', this.onWindowResize)
|
||||
}
|
||||
componentDidMount() {
|
||||
window.addEventListener('resize', this.onWindowResize)
|
||||
requestAnimationFrame(() => this.maybeResize())
|
||||
}
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.onWindowResize)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <canvas className={css(style.glCanvasView)} ref={this.ref} width={1} height={1} />
|
||||
}
|
||||
}
|
||||
|
||||
function rectangleBatchForFlamechart(gl: regl.Instance, flamechart: Flamechart) {
|
||||
console.time('rectangle batch generation')
|
||||
|
||||
const batch = new RectangleBatch(gl)
|
||||
|
||||
const layers = flamechart.getLayers()
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
const layer = layers[i]
|
||||
for (let flamechartFrame of layer) {
|
||||
const configSpaceBounds = new Rect(
|
||||
new Vec2(flamechartFrame.start, i + 1),
|
||||
new Vec2(flamechartFrame.end - flamechartFrame.start, 1)
|
||||
)
|
||||
const color = flamechart.getColorForFrame(flamechartFrame.node.frame)
|
||||
batch.addRect(configSpaceBounds, color)
|
||||
}
|
||||
}
|
||||
|
||||
// GPU upload
|
||||
batch.uploadToGPU()
|
||||
|
||||
console.timeEnd('rectangle batch generation')
|
||||
|
||||
return batch
|
||||
}
|
||||
|
||||
|
||||
export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
constructor() {
|
||||
super()
|
||||
@@ -125,12 +209,16 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
loading: false,
|
||||
profile: null,
|
||||
flamechart: null,
|
||||
flamechartRectBatch: null,
|
||||
sortedFlamechart: null,
|
||||
sortedFlamechartRectBatch: null,
|
||||
sortOrder: SortOrder.CHRONO
|
||||
}
|
||||
}
|
||||
|
||||
loadFromString(fileName: string, contents: string) {
|
||||
if (!this.gl) return
|
||||
|
||||
console.time('import')
|
||||
const profile = importProfile(contents, fileName)
|
||||
if (profile == null) {
|
||||
@@ -153,6 +241,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
formatValue: profile.formatValue.bind(profile),
|
||||
getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator)
|
||||
})
|
||||
const flamechartRectBatch = rectangleBatchForFlamechart(this.gl, flamechart)
|
||||
|
||||
const sortedFlamechart = new Flamechart({
|
||||
getTotalWeight: profile.getTotalNonIdleWeight.bind(profile),
|
||||
@@ -160,10 +249,19 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
formatValue: profile.formatValue.bind(profile),
|
||||
getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator)
|
||||
})
|
||||
const sortedFlamechartRectBatch = rectangleBatchForFlamechart(this.gl, sortedFlamechart)
|
||||
|
||||
console.timeEnd('import')
|
||||
|
||||
console.time('first setState')
|
||||
this.setState({ profile, flamechart, sortedFlamechart, loading: false }, () => {
|
||||
this.setState({
|
||||
profile,
|
||||
flamechart,
|
||||
flamechartRectBatch,
|
||||
sortedFlamechart,
|
||||
sortedFlamechartRectBatch,
|
||||
loading: false
|
||||
}, () => {
|
||||
console.timeEnd('first setState')
|
||||
})
|
||||
}
|
||||
@@ -263,22 +361,48 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
this.setState({ sortOrder })
|
||||
}
|
||||
|
||||
private gl: regl.Instance | null = null
|
||||
private rectangleBatchRenderer: RectangleBatchRenderer | null = null
|
||||
private setGL = (gl: regl.Instance | null) => {
|
||||
if (gl) {
|
||||
this.gl = gl
|
||||
this.rectangleBatchRenderer = new RectangleBatchRenderer(gl)
|
||||
} else {
|
||||
this.gl = null
|
||||
this.rectangleBatchRenderer = null
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {flamechart, sortedFlamechart, sortOrder, loading} = this.state
|
||||
const {flamechart, flamechartRectBatch, sortedFlamechart, sortedFlamechartRectBatch, sortOrder, loading} = this.state
|
||||
const flamechartToView = sortOrder == SortOrder.CHRONO ? flamechart : sortedFlamechart
|
||||
const rectangleBatch = sortOrder == SortOrder.CHRONO ? flamechartRectBatch : sortedFlamechartRectBatch
|
||||
|
||||
return <div onDrop={this.onDrop} onDragOver={this.onDragOver} className={css(style.root)}>
|
||||
<GLCanvas setGL={this.setGL} />
|
||||
<Toolbar setSortOrder={this.setSortOrder} {...this.state} />
|
||||
{loading ?
|
||||
this.renderLoadingBar() :
|
||||
flamechartToView ?
|
||||
<FlamechartView ref={this.flamechartRef} flamechart={flamechartToView} /> :
|
||||
this.gl && flamechartToView && this.rectangleBatchRenderer && rectangleBatch ?
|
||||
<FlamechartView
|
||||
gl={this.gl}
|
||||
renderer={this.rectangleBatchRenderer}
|
||||
rectangles={rectangleBatch}
|
||||
ref={this.flamechartRef}
|
||||
flamechart={flamechartToView} /> :
|
||||
this.renderLanding()}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
glCanvasView: {
|
||||
position: 'absolute',
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
zIndex: -1,
|
||||
pointerEvents: 'none'
|
||||
},
|
||||
loading: {
|
||||
height: 3,
|
||||
marginBottom: -3,
|
||||
|
||||
+33
-47
@@ -1,10 +1,10 @@
|
||||
import * as regl from 'regl'
|
||||
import { vec3, Command } from 'regl'
|
||||
import { Command } from 'regl'
|
||||
import { h, Component } from 'preact'
|
||||
import { css } from 'aphrodite'
|
||||
import { Flamechart } from './flamechart'
|
||||
import { Rect, Vec2, AffineTransform, clamp } from './math'
|
||||
import { rectangleBatchRenderer, RectangleBatchRendererProps } from "./rectangle-batch-renderer"
|
||||
import { RectangleBatchRenderer, RectangleBatch } from "./rectangle-batch-renderer"
|
||||
import { atMostOnceAFrame, cachedMeasureTextWidth } from "./utils";
|
||||
import { style, Sizes } from "./flamechart-style";
|
||||
import { FontFamily, FontSize, Colors } from "./style"
|
||||
@@ -14,6 +14,13 @@ const DEVICE_PIXEL_RATIO = window.devicePixelRatio
|
||||
interface FlamechartMinimapViewProps {
|
||||
flamechart: Flamechart
|
||||
configSpaceViewportRect: Rect
|
||||
|
||||
// TODO(jlfwong): Encapsulate the regl.Instance and the batch renderer
|
||||
// in a CanvasContext object or something along those lines
|
||||
gl: regl.Instance
|
||||
renderer: RectangleBatchRenderer
|
||||
rectangles: RectangleBatch
|
||||
|
||||
transformViewport: (transform: AffineTransform) => void
|
||||
setConfigSpaceViewportRect: (rect: Rect) => void
|
||||
}
|
||||
@@ -24,11 +31,8 @@ enum DraggingMode {
|
||||
}
|
||||
|
||||
export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps, {}> {
|
||||
renderer: Command<RectangleBatchRendererProps> | null = null
|
||||
viewportRectRenderer: Command<OverlayRectangleRendererProps> | null = null
|
||||
|
||||
ctx: WebGLRenderingContext | null = null
|
||||
gl: regl.Instance | null = null
|
||||
canvas: HTMLCanvasElement | null = null
|
||||
|
||||
overlayCanvas: HTMLCanvasElement | null = null
|
||||
@@ -77,21 +81,39 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
}
|
||||
|
||||
private renderRects() {
|
||||
if (!this.renderer || !this.canvas || !this.viewportRectRenderer) return
|
||||
if (!this.canvas) return
|
||||
this.resizeCanvasIfNeeded()
|
||||
|
||||
const configSpaceToNDC = this.physicalViewSpaceToNDC().times(this.configSpaceToPhysicalViewSpace())
|
||||
|
||||
this.renderer({
|
||||
configSpaceToNDC: configSpaceToNDC,
|
||||
physicalSize: this.physicalViewSize()
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
const physicalBounds = this.logicalToPhysicalViewSpace().transformRect(new Rect(
|
||||
new Vec2(bounds.left, bounds.top),
|
||||
new Vec2(bounds.width, bounds.height)
|
||||
))
|
||||
|
||||
this.props.gl({
|
||||
viewport: {
|
||||
x: physicalBounds.left(),
|
||||
y: window.devicePixelRatio * window.innerHeight - physicalBounds.top() - physicalBounds.height(),
|
||||
width: physicalBounds.width(),
|
||||
height: physicalBounds.height()
|
||||
}
|
||||
})(() => {
|
||||
this.props.renderer.render({
|
||||
configSpaceToNDC: configSpaceToNDC,
|
||||
physicalSize: this.physicalViewSize(),
|
||||
strokeSize: 0,
|
||||
batch: this.props.rectangles
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
this.viewportRectRenderer({
|
||||
configSpaceViewportRect: this.props.configSpaceViewportRect,
|
||||
configSpaceToPhysicalViewSpace: this.configSpaceToPhysicalViewSpace(),
|
||||
physicalSize: this.physicalViewSize()
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
private renderOverlays() {
|
||||
@@ -152,7 +174,6 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
|
||||
componentWillReceiveProps(nextProps: FlamechartMinimapViewProps) {
|
||||
if (this.props.flamechart !== nextProps.flamechart) {
|
||||
this.renderer = null
|
||||
this.renderCanvas()
|
||||
} else if (this.props.configSpaceViewportRect != nextProps.configSpaceViewportRect) {
|
||||
this.renderCanvas()
|
||||
@@ -160,7 +181,7 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
}
|
||||
|
||||
private resizeCanvasIfNeeded() {
|
||||
if (!this.canvas || !this.ctx) return
|
||||
if (!this.canvas) return
|
||||
let { width, height } = this.canvas.getBoundingClientRect()
|
||||
width = Math.floor(width) * DEVICE_PIXEL_RATIO
|
||||
height = Math.floor(height) * DEVICE_PIXEL_RATIO
|
||||
@@ -175,8 +196,6 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
|
||||
this.canvas.width = width
|
||||
this.canvas.height = height
|
||||
|
||||
this.ctx.viewport(0, 0, width, height)
|
||||
}
|
||||
|
||||
private resizeOverlayCanvasIfNeeded() {
|
||||
@@ -211,12 +230,6 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
// size.
|
||||
requestAnimationFrame(() => this.renderCanvas())
|
||||
} else {
|
||||
if (!this.gl) return;
|
||||
if (!this.renderer) this.preprocess(this.props.flamechart)
|
||||
this.gl.clear({
|
||||
color: [1, 1, 1, 1],
|
||||
depth: 1
|
||||
})
|
||||
this.renderRects()
|
||||
this.renderOverlays()
|
||||
}
|
||||
@@ -225,8 +238,6 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
private canvasRef = (element?: Element) => {
|
||||
if (element) {
|
||||
this.canvas = element as HTMLCanvasElement
|
||||
this.ctx = this.canvas.getContext('webgl')!
|
||||
this.gl = regl(this.ctx)
|
||||
this.renderCanvas()
|
||||
} else {
|
||||
this.canvas = null
|
||||
@@ -410,31 +421,6 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
this.updateCursor(configSpaceMouse)
|
||||
}
|
||||
|
||||
private preprocess(flamechart: Flamechart) {
|
||||
if (!this.canvas || !this.gl) return
|
||||
console.time('minimap preprocess')
|
||||
const configSpaceRects: Rect[] = []
|
||||
const colors: vec3[] = []
|
||||
|
||||
const layers = flamechart.getLayers()
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
const layer = layers[i]
|
||||
for (let flamechartFrame of layer) {
|
||||
const configSpaceBounds = new Rect(
|
||||
new Vec2(flamechartFrame.start, i),
|
||||
new Vec2(flamechartFrame.end - flamechartFrame.start, 1)
|
||||
)
|
||||
configSpaceRects.push(configSpaceBounds)
|
||||
const color = flamechart.getColorForFrame(flamechartFrame.node.frame)
|
||||
colors.push([color.r, color.g, color.b])
|
||||
}
|
||||
}
|
||||
|
||||
this.renderer = rectangleBatchRenderer(this.gl, configSpaceRects, colors, 0)
|
||||
this.viewportRectRenderer = viewportRectangleRenderer(this.gl);
|
||||
console.timeEnd('minimap preprocess')
|
||||
}
|
||||
|
||||
private overlayCanvasRef = (element?: Element) => {
|
||||
if (element) {
|
||||
this.overlayCanvas = element as HTMLCanvasElement
|
||||
|
||||
+38
-47
@@ -1,3 +1,4 @@
|
||||
import * as regl from 'regl'
|
||||
import {h} from 'preact'
|
||||
import {css} from 'aphrodite'
|
||||
import {ReloadableComponent} from './reloadable'
|
||||
@@ -5,12 +6,9 @@ import {ReloadableComponent} from './reloadable'
|
||||
import { CallTreeNode } from './profile'
|
||||
import { Flamechart, FlamechartFrame } from './flamechart'
|
||||
|
||||
import * as regl from 'regl'
|
||||
import { vec3, Command, Instance } from 'regl'
|
||||
|
||||
import { Rect, Vec2, AffineTransform, clamp } from './math'
|
||||
import { atMostOnceAFrame, cachedMeasureTextWidth } from "./utils";
|
||||
import { rectangleBatchRenderer, RectangleBatchRendererProps } from "./rectangle-batch-renderer"
|
||||
import { RectangleBatchRenderer, RectangleBatch } from "./rectangle-batch-renderer"
|
||||
import { FlamechartMinimapView } from "./flamechart-minimap-view"
|
||||
|
||||
import { style, Sizes } from './flamechart-style'
|
||||
@@ -74,6 +72,11 @@ const DEVICE_PIXEL_RATIO = window.devicePixelRatio
|
||||
*/
|
||||
interface FlamechartPanZoomViewProps {
|
||||
flamechart: Flamechart
|
||||
|
||||
gl: regl.Instance
|
||||
renderer: RectangleBatchRenderer
|
||||
rectangles: RectangleBatch
|
||||
|
||||
setNodeHover: (node: CallTreeNode | null, logicalViewSpaceMouse: Vec2) => void
|
||||
configSpaceViewportRect: Rect
|
||||
transformViewport: (transform: AffineTransform) => void
|
||||
@@ -81,10 +84,6 @@ interface FlamechartPanZoomViewProps {
|
||||
}
|
||||
|
||||
export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoomViewProps, {}> {
|
||||
renderer: Command<RectangleBatchRendererProps> | null = null
|
||||
|
||||
ctx: WebGLRenderingContext | null = null
|
||||
gl: Instance | null = null
|
||||
canvas: HTMLCanvasElement | null = null
|
||||
|
||||
overlayCanvas: HTMLCanvasElement | null = null
|
||||
@@ -96,38 +95,9 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
this.props.setConfigSpaceViewportRect(r)
|
||||
}
|
||||
|
||||
private preprocess(flamechart: Flamechart) {
|
||||
if (!this.canvas || !this.gl) return
|
||||
console.time('panzoom preprocess')
|
||||
const configSpaceRects: Rect[] = []
|
||||
const colors: vec3[] = []
|
||||
|
||||
const layers = flamechart.getLayers()
|
||||
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
const layer = layers[i]
|
||||
for (let flamechartFrame of layer) {
|
||||
const configSpaceBounds = new Rect(
|
||||
new Vec2(flamechartFrame.start, i+1),
|
||||
new Vec2(flamechartFrame.end - flamechartFrame.start, 1)
|
||||
)
|
||||
configSpaceRects.push(configSpaceBounds)
|
||||
const color = flamechart.getColorForFrame(flamechartFrame.node.frame)
|
||||
colors.push([color.r, color.g, color.b])
|
||||
}
|
||||
}
|
||||
|
||||
this.renderer = rectangleBatchRenderer(this.gl, configSpaceRects, colors)
|
||||
this.setConfigSpaceViewportRect(new Rect())
|
||||
this.hoveredLabel = null
|
||||
console.timeEnd('panzoom preprocess')
|
||||
}
|
||||
|
||||
private canvasRef = (element?: Element) => {
|
||||
if (element) {
|
||||
this.canvas = element as HTMLCanvasElement
|
||||
this.ctx = this.canvas.getContext('webgl')!
|
||||
this.gl = regl(this.ctx)
|
||||
this.renderCanvas()
|
||||
} else {
|
||||
this.canvas = null
|
||||
@@ -319,7 +289,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
}
|
||||
|
||||
private resizeCanvasIfNeeded(windowResized = false) {
|
||||
if (!this.canvas || !this.ctx) return
|
||||
if (!this.canvas) return
|
||||
let { width, height } = this.canvas.getBoundingClientRect()
|
||||
const logicalHeight = height
|
||||
width = Math.floor(width) * DEVICE_PIXEL_RATIO
|
||||
@@ -351,8 +321,6 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
|
||||
this.canvas.width = width
|
||||
this.canvas.height = height
|
||||
|
||||
this.ctx.viewport(0, 0, width, height)
|
||||
}
|
||||
|
||||
onWindowResize = () => {
|
||||
@@ -361,17 +329,33 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
}
|
||||
|
||||
private renderRects() {
|
||||
if (!this.renderer || !this.canvas) return
|
||||
if (!this.canvas) return
|
||||
this.resizeCanvasIfNeeded()
|
||||
|
||||
|
||||
if (this.props.configSpaceViewportRect.isEmpty()) return
|
||||
|
||||
const configSpaceToNDC = this.physicalViewSpaceToNDC().times(this.configSpaceToPhysicalViewSpace())
|
||||
|
||||
this.renderer({
|
||||
configSpaceToNDC: configSpaceToNDC,
|
||||
physicalSize: this.physicalViewSize()
|
||||
const bounds = this.canvas.getBoundingClientRect()
|
||||
const physicalBounds = this.logicalToPhysicalViewSpace().transformRect(new Rect(
|
||||
new Vec2(bounds.left, bounds.top),
|
||||
new Vec2(bounds.width, bounds.height)
|
||||
))
|
||||
|
||||
this.props.gl({
|
||||
viewport: {
|
||||
x: physicalBounds.left(),
|
||||
y: window.devicePixelRatio * window.innerHeight - physicalBounds.top() - physicalBounds.height(),
|
||||
width: physicalBounds.width(),
|
||||
height: physicalBounds.height()
|
||||
}
|
||||
})(() => {
|
||||
this.props.renderer.render({
|
||||
configSpaceToNDC: configSpaceToNDC,
|
||||
physicalSize: this.physicalViewSize(),
|
||||
strokeSize: 1,
|
||||
batch: this.props.rectangles
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -411,7 +395,6 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
// size.
|
||||
requestAnimationFrame(() => this.renderCanvas())
|
||||
} else {
|
||||
if (!this.renderer) this.preprocess(this.props.flamechart)
|
||||
this.renderRects()
|
||||
this.renderOverlays()
|
||||
}
|
||||
@@ -584,7 +567,6 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
shouldComponentUpdate() { return false }
|
||||
componentWillReceiveProps(nextProps: FlamechartPanZoomViewProps) {
|
||||
if (this.props.flamechart !== nextProps.flamechart) {
|
||||
this.renderer = null
|
||||
this.renderCanvas()
|
||||
} else if (this.props.configSpaceViewportRect !== nextProps.configSpaceViewportRect) {
|
||||
this.renderCanvas()
|
||||
@@ -622,6 +604,9 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
|
||||
interface FlamechartViewProps {
|
||||
flamechart: Flamechart
|
||||
gl: regl.Instance
|
||||
renderer: RectangleBatchRenderer
|
||||
rectangles: RectangleBatch
|
||||
}
|
||||
|
||||
interface FlamechartViewState {
|
||||
@@ -751,10 +736,16 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
|
||||
<FlamechartMinimapView
|
||||
configSpaceViewportRect={this.state.configSpaceViewportRect}
|
||||
transformViewport={this.transformViewport}
|
||||
gl={this.props.gl}
|
||||
renderer={this.props.renderer}
|
||||
rectangles={this.props.rectangles}
|
||||
setConfigSpaceViewportRect={this.setConfigSpaceViewportRect}
|
||||
flamechart={this.props.flamechart} />
|
||||
<FlamechartPanZoomView
|
||||
ref={this.panZoomRef}
|
||||
gl={this.props.gl}
|
||||
renderer={this.props.renderer}
|
||||
rectangles={this.props.rectangles}
|
||||
flamechart={this.props.flamechart}
|
||||
setNodeHover={this.onNodeHover}
|
||||
transformViewport={this.transformViewport}
|
||||
|
||||
+140
-74
@@ -1,44 +1,82 @@
|
||||
import * as regl from 'regl'
|
||||
import { Rect, Vec2, AffineTransform } from './math'
|
||||
import { vec3 } from 'regl'
|
||||
import { Rect, Vec2, AffineTransform } from './math'
|
||||
import { Color } from './color'
|
||||
|
||||
export interface RectangleBatchRendererProps {
|
||||
configSpaceToNDC: AffineTransform
|
||||
physicalSize: Vec2
|
||||
}
|
||||
export class RectangleBatch {
|
||||
private vertexCapacity = 60
|
||||
private vertexCount = 0
|
||||
private positions = new Float32Array(this.vertexCapacity * 6 * 2)
|
||||
private physicalSpaceOffsets = new Float32Array(this.vertexCapacity * 6 * 2)
|
||||
private vertexColors = new Float32Array(this.vertexCapacity * 6 * 3)
|
||||
|
||||
export const rectangleBatchRenderer = (gl: regl.Instance, rects: Rect[], colors: vec3[], strokeSize = 1) => {
|
||||
const positions = new Float32Array(rects.length * 6 * 2)
|
||||
const physicalSpaceOffsets = new Float32Array(rects.length * 6 * 2)
|
||||
const vertexColors = new Float32Array(rects.length * 6 * 3)
|
||||
private offset = {
|
||||
topLeft: new Vec2(1, -1),
|
||||
topRight: new Vec2(-1, -1),
|
||||
bottomRight: new Vec2(-1, 1),
|
||||
bottomLeft: new Vec2(1, 1)
|
||||
}
|
||||
constructor(private gl: regl.Instance) { }
|
||||
|
||||
const offset = {
|
||||
topLeft: new Vec2(strokeSize, -strokeSize),
|
||||
topRight: new Vec2(-strokeSize, -strokeSize),
|
||||
bottomRight: new Vec2(-strokeSize, strokeSize),
|
||||
bottomLeft: new Vec2(strokeSize, strokeSize)
|
||||
getVertexCount() { return this.vertexCount }
|
||||
|
||||
private positionBuffer: regl.Buffer | null = null
|
||||
getPositionBuffer() {
|
||||
if (!this.positionBuffer) this.positionBuffer = this.gl.buffer(this.positions)
|
||||
return this.positionBuffer
|
||||
}
|
||||
|
||||
let vertexIndex = 0
|
||||
function addVertex(y: number, x: number, offset: Vec2, color: vec3) {
|
||||
const index = vertexIndex++
|
||||
positions[index * 2] = x
|
||||
positions[index * 2 + 1] = y
|
||||
physicalSpaceOffsets[index * 2] = offset.x
|
||||
physicalSpaceOffsets[index * 2 + 1] = offset.y
|
||||
vertexColors[index * 3] = color[0]
|
||||
vertexColors[index * 3 + 1] = color[1]
|
||||
vertexColors[index * 3 + 2] = color[2]
|
||||
private offsetBuffer: any = null
|
||||
getPhysicalSpaceOffsetBuffer() {
|
||||
if (!this.offsetBuffer) this.offsetBuffer = this.gl.buffer(this.physicalSpaceOffsets)
|
||||
return this.offsetBuffer
|
||||
}
|
||||
|
||||
for (let i = 0; i < rects.length; i++) {
|
||||
const r = rects[i]
|
||||
const color = colors[i]
|
||||
private colorBuffer: any = null
|
||||
getVertexColorBuffer() {
|
||||
if (!this.colorBuffer) this.colorBuffer = this.gl.buffer(this.vertexColors)
|
||||
return this.colorBuffer
|
||||
}
|
||||
|
||||
const top = r.top()
|
||||
const bottom = r.bottom()
|
||||
const left = r.left()
|
||||
const right = r.right()
|
||||
uploadToGPU() {
|
||||
this.getPositionBuffer()
|
||||
this.getPhysicalSpaceOffsetBuffer()
|
||||
this.getVertexColorBuffer()
|
||||
}
|
||||
|
||||
private addVertex(y: number, x: number, offset: Vec2, color: vec3) {
|
||||
const index = this.vertexCount++
|
||||
if (index >= this.vertexCapacity) {
|
||||
// Not enough capacity, time to resize! We'll double the capacity each time.
|
||||
this.vertexCapacity *= 2
|
||||
const positions = new Float32Array(this.vertexCapacity * 6 * 2)
|
||||
const physicalSpaceOffsets = new Float32Array(this.vertexCapacity * 6 * 2)
|
||||
const vertexColors = new Float32Array(this.vertexCapacity * 6 * 3)
|
||||
|
||||
positions.set(this.positions)
|
||||
physicalSpaceOffsets.set(this.physicalSpaceOffsets)
|
||||
vertexColors.set(this.vertexColors)
|
||||
|
||||
this.positions = positions
|
||||
this.physicalSpaceOffsets = physicalSpaceOffsets
|
||||
this.vertexColors = vertexColors
|
||||
}
|
||||
this.positions[index * 2] = x
|
||||
this.positions[index * 2 + 1] = y
|
||||
this.physicalSpaceOffsets[index * 2] = offset.x
|
||||
this.physicalSpaceOffsets[index * 2 + 1] = offset.y
|
||||
this.vertexColors[index * 3] = color[0]
|
||||
this.vertexColors[index * 3 + 1] = color[1]
|
||||
this.vertexColors[index * 3 + 2] = color[2]
|
||||
}
|
||||
|
||||
addRect(rect: Rect, color: Color) {
|
||||
const color_: vec3 = [color.r, color.g, color.b]
|
||||
|
||||
const top = rect.top()
|
||||
const bottom = rect.bottom()
|
||||
const left = rect.left()
|
||||
const right = rect.right()
|
||||
|
||||
// 2 disjoint triangles.
|
||||
//
|
||||
@@ -46,19 +84,31 @@ export const rectangleBatchRenderer = (gl: regl.Instance, rects: Rect[], colors:
|
||||
// | /|
|
||||
// |/ |
|
||||
// 3 +--+ 2
|
||||
addVertex(top, left, offset.topLeft, color)
|
||||
addVertex(bottom, left, offset.bottomLeft, color)
|
||||
addVertex(top, right, offset.topRight, color)
|
||||
this.addVertex(top, left, this.offset.topLeft, color_)
|
||||
this.addVertex(bottom, left, this.offset.bottomLeft, color_)
|
||||
this.addVertex(top, right, this.offset.topRight, color_)
|
||||
|
||||
addVertex(bottom, left, offset.bottomLeft, color)
|
||||
addVertex(top, right, offset.topRight, color)
|
||||
addVertex(bottom, right, offset.bottomRight, color)
|
||||
this.addVertex(bottom, left, this.offset.bottomLeft, color_)
|
||||
this.addVertex(top, right, this.offset.topRight, color_)
|
||||
this.addVertex(bottom, right, this.offset.bottomRight, color_)
|
||||
}
|
||||
}
|
||||
|
||||
return gl<RectangleBatchRendererProps>({
|
||||
vert: `
|
||||
export interface RectangleBatchRendererProps {
|
||||
configSpaceToNDC: AffineTransform
|
||||
physicalSize: Vec2
|
||||
strokeSize: number
|
||||
batch: RectangleBatch
|
||||
}
|
||||
|
||||
export class RectangleBatchRenderer {
|
||||
private command: regl.Command<RectangleBatchRendererProps>
|
||||
constructor(gl: regl.Instance) {
|
||||
this.command = gl({
|
||||
vert: `
|
||||
uniform mat3 configSpaceToNDC;
|
||||
uniform vec2 physicalSize;
|
||||
uniform float strokeSize;
|
||||
attribute vec2 position;
|
||||
attribute vec3 color;
|
||||
attribute vec2 physicalSpaceOffset;
|
||||
@@ -69,15 +119,15 @@ export const rectangleBatchRenderer = (gl: regl.Instance, rects: Rect[], colors:
|
||||
vec2 halfSize = physicalSize / 2.0;
|
||||
vec2 physicalPixelSize = 2.0 / physicalSize;
|
||||
roundedPosition = floor(roundedPosition * halfSize) / halfSize;
|
||||
gl_Position = vec4(roundedPosition + physicalPixelSize * physicalSpaceOffset, 0, 1);
|
||||
gl_Position = vec4(roundedPosition + physicalPixelSize * physicalSpaceOffset * strokeSize, 0, 1);
|
||||
}
|
||||
`,
|
||||
|
||||
depth: {
|
||||
enable: false
|
||||
},
|
||||
depth: {
|
||||
enable: false
|
||||
},
|
||||
|
||||
frag: `
|
||||
frag: `
|
||||
precision mediump float;
|
||||
varying vec3 vColor;
|
||||
void main() {
|
||||
@@ -85,38 +135,54 @@ export const rectangleBatchRenderer = (gl: regl.Instance, rects: Rect[], colors:
|
||||
}
|
||||
`,
|
||||
|
||||
attributes: {
|
||||
position: {
|
||||
buffer: gl.buffer(positions),
|
||||
offset: 0,
|
||||
stride: 2 * 4,
|
||||
size: 2
|
||||
attributes: {
|
||||
position: (context, props) => {
|
||||
return {
|
||||
buffer: props.batch.getPositionBuffer(),
|
||||
offset: 0,
|
||||
stride: 2 * 4,
|
||||
size: 2
|
||||
}
|
||||
},
|
||||
physicalSpaceOffset: (context, props) => {
|
||||
return {
|
||||
buffer: props.batch.getPhysicalSpaceOffsetBuffer(),
|
||||
offset: 0,
|
||||
stride: 2 * 4,
|
||||
size: 2
|
||||
}
|
||||
},
|
||||
color: (context, props) => {
|
||||
return {
|
||||
buffer: props.batch.getVertexColorBuffer(),
|
||||
offset: 0,
|
||||
stride: 3 * 4,
|
||||
size: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
physicalSpaceOffset: {
|
||||
buffer: gl.buffer(physicalSpaceOffsets),
|
||||
offset: 0,
|
||||
stride: 2 * 4,
|
||||
size: 2
|
||||
|
||||
uniforms: {
|
||||
configSpaceToNDC: (context, props) => {
|
||||
return props.configSpaceToNDC.flatten()
|
||||
},
|
||||
physicalSize: (context, props) => {
|
||||
return props.physicalSize.flatten()
|
||||
},
|
||||
strokeSize: (context, props) => {
|
||||
return props.strokeSize
|
||||
}
|
||||
},
|
||||
color: {
|
||||
buffer: gl.buffer(vertexColors),
|
||||
offset: 0,
|
||||
stride: 3 * 4,
|
||||
size: 3
|
||||
|
||||
primitive: 'triangles',
|
||||
|
||||
count: (context, props) => {
|
||||
return props.batch.getVertexCount()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
uniforms: {
|
||||
configSpaceToNDC: (context, props) => {
|
||||
return props.configSpaceToNDC.flatten()
|
||||
},
|
||||
physicalSize: (context, props) => {
|
||||
return props.physicalSize.flatten()
|
||||
}
|
||||
},
|
||||
|
||||
primitive: 'triangles',
|
||||
|
||||
count: vertexColors.length / 3
|
||||
})
|
||||
render(props: RectangleBatchRendererProps) {
|
||||
this.command(props)
|
||||
}
|
||||
}
|
||||
@@ -285,7 +285,7 @@ declare module "regl" {
|
||||
|
||||
uniforms?: { [uniformName: string]: MaybeComputed<P, Uniform> }
|
||||
|
||||
attributes: { [attributeName: string]: MaybeComputed<P, Attribute> }
|
||||
attributes?: { [attributeName: string]: MaybeComputed<P, Attribute> }
|
||||
|
||||
primitive?: DrawMode
|
||||
|
||||
|
||||
Reference in New Issue
Block a user