Refactor into a single CanvasContext

This commit is contained in:
Jamie Wong
2018-01-23 09:59:14 -08:00
parent b3213a2f44
commit fe9561f709
5 changed files with 218 additions and 192 deletions
+21 -28
View File
@@ -1,12 +1,12 @@
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 { RectangleBatch } from './rectangle-batch-renderer'
import { CanvasContext } from './canvas-context'
import {Profile, Frame} from './profile'
import {Flamechart} from './flamechart'
@@ -124,24 +124,25 @@ export class Toolbar extends ReloadableComponent<ToolbarProps, void> {
}
interface GLCanvasProps {
setGL(gl: regl.Instance | null): void
setCanvasContext(canvasContext: CanvasContext | null): void
}
export class GLCanvas extends ReloadableComponent<GLCanvasProps, void> {
private canvas: HTMLCanvasElement | null
private gl: regl.Instance | null
private canvas: HTMLCanvasElement | null = null
private canvasContext: CanvasContext | null = null
private ref = (canvas?: Element) => {
if (canvas instanceof HTMLCanvasElement) {
this.canvas = canvas
this.gl = regl(canvas)
this.canvasContext = new CanvasContext(canvas)
} else {
this.gl = null
this.canvas = null
this.canvasContext = null
}
this.props.setGL(this.gl)
this.props.setCanvasContext(this.canvasContext)
}
private maybeResize() {
if (!this.canvas || !this.gl) return
if (!this.canvas) return
let { width, height } = this.canvas.getBoundingClientRect()
width = Math.floor(width) * window.devicePixelRatio
height = Math.floor(height) * window.devicePixelRatio
@@ -175,10 +176,10 @@ export class GLCanvas extends ReloadableComponent<GLCanvasProps, void> {
}
}
function rectangleBatchForFlamechart(gl: regl.Instance, flamechart: Flamechart) {
function rectangleBatchForFlamechart(canvasContext: CanvasContext, flamechart: Flamechart) {
console.time('rectangle batch generation')
const batch = new RectangleBatch(gl)
const batch = canvasContext.createRectangleBatch()
const layers = flamechart.getLayers()
for (let i = 0; i < layers.length; i++) {
@@ -217,7 +218,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
}
loadFromString(fileName: string, contents: string) {
if (!this.gl) return
if (!this.canvasContext) return
console.time('import')
const profile = importProfile(contents, fileName)
@@ -241,7 +242,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
formatValue: profile.formatValue.bind(profile),
getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator)
})
const flamechartRectBatch = rectangleBatchForFlamechart(this.gl, flamechart)
const flamechartRectBatch = rectangleBatchForFlamechart(this.canvasContext, flamechart)
const sortedFlamechart = new Flamechart({
getTotalWeight: profile.getTotalNonIdleWeight.bind(profile),
@@ -249,7 +250,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
formatValue: profile.formatValue.bind(profile),
getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator)
})
const sortedFlamechartRectBatch = rectangleBatchForFlamechart(this.gl, sortedFlamechart)
const sortedFlamechartRectBatch = rectangleBatchForFlamechart(this.canvasContext, sortedFlamechart)
console.timeEnd('import')
@@ -361,16 +362,9 @@ 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
}
private canvasContext: CanvasContext | null = null
private setCanvasContext = (canvasContext: CanvasContext | null) => {
this.canvasContext = canvasContext
}
render() {
@@ -379,14 +373,13 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
const rectangleBatch = sortOrder == SortOrder.CHRONO ? flamechartRectBatch : sortedFlamechartRectBatch
return <div onDrop={this.onDrop} onDragOver={this.onDragOver} className={css(style.root)}>
<GLCanvas setGL={this.setGL} />
<GLCanvas setCanvasContext={this.setCanvasContext} />
<Toolbar setSortOrder={this.setSortOrder} {...this.state} />
{loading ?
this.renderLoadingBar() :
this.gl && flamechartToView && this.rectangleBatchRenderer && rectangleBatch ?
this.canvasContext && flamechartToView && rectangleBatch ?
<FlamechartView
gl={this.gl}
renderer={this.rectangleBatchRenderer}
canvasContext={this.canvasContext}
rectangles={rectangleBatch}
ref={this.flamechartRef}
flamechart={flamechartToView} /> :
+63
View File
@@ -0,0 +1,63 @@
import * as regl from 'regl'
import { RectangleBatchRenderer, RectangleBatch, RectangleBatchRendererProps } from './rectangle-batch-renderer';
import { ViewportRectangleRenderer, ViewportRectangleRendererProps } from './overlay-rectangle-renderer';
import { Vec2, Rect } from './math';
export class CanvasContext {
private gl: regl.Instance
private rectangleBatchRenderer: RectangleBatchRenderer
private viewportRectangleRenderer: ViewportRectangleRenderer
private setViewportScope: regl.Command<{ physicalBounds: Rect }>
constructor(canvas: HTMLCanvasElement) {
this.gl = regl(canvas)
this.rectangleBatchRenderer = new RectangleBatchRenderer(this.gl)
this.viewportRectangleRenderer = new ViewportRectangleRenderer(this.gl)
this.setViewportScope = this.gl({
viewport: (context, props) => {
const { physicalBounds } = props
return {
x: physicalBounds.left(),
y: window.devicePixelRatio * window.innerHeight - physicalBounds.top() - physicalBounds.height(),
width: physicalBounds.width(),
height: physicalBounds.height()
}
},
scissor: (context, props) => {
const { physicalBounds } = props
return {
enable: true,
box: {
x: physicalBounds.left(),
y: window.devicePixelRatio * window.innerHeight - physicalBounds.top() - physicalBounds.height(),
width: physicalBounds.width(),
height: physicalBounds.height()
}
}
}
})
}
drawRectangleBatch(props: RectangleBatchRendererProps) {
this.rectangleBatchRenderer.render(props)
}
createRectangleBatch(): RectangleBatch {
return new RectangleBatch(this.gl)
}
drawViewportRectangle(props: ViewportRectangleRendererProps){
this.viewportRectangleRenderer.render(props)
}
renderInto(el: HTMLElement, cb: () => void) {
const bounds = el.getBoundingClientRect()
const physicalBounds = new Rect(
new Vec2(bounds.left * window.devicePixelRatio, bounds.top * window.devicePixelRatio),
new Vec2(bounds.width * window.devicePixelRatio, bounds.height * window.devicePixelRatio)
)
this.setViewportScope({ physicalBounds }, cb)
}
}
+10 -139
View File
@@ -1,13 +1,12 @@
import * as regl 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, RectangleBatch } from "./rectangle-batch-renderer"
import { RectangleBatch } from "./rectangle-batch-renderer"
import { atMostOnceAFrame, cachedMeasureTextWidth } from "./utils";
import { style, Sizes } from "./flamechart-style";
import { FontFamily, FontSize, Colors } from "./style"
import { CanvasContext } from './canvas-context'
const DEVICE_PIXEL_RATIO = window.devicePixelRatio
@@ -15,10 +14,7 @@ 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
canvasContext: CanvasContext
rectangles: RectangleBatch
transformViewport: (transform: AffineTransform) => void
@@ -31,8 +27,6 @@ enum DraggingMode {
}
export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps, {}> {
viewportRectRenderer: Command<OverlayRectangleRendererProps> | null = null
canvas: HTMLCanvasElement | null = null
overlayCanvas: HTMLCanvasElement | null = null
@@ -85,35 +79,19 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
this.resizeCanvasIfNeeded()
const configSpaceToNDC = this.physicalViewSpaceToNDC().times(this.configSpaceToPhysicalViewSpace())
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({
this.props.canvasContext.renderInto(this.canvas, () => {
this.props.canvasContext.drawRectangleBatch({
configSpaceToNDC: configSpaceToNDC,
physicalSize: this.physicalViewSize(),
strokeSize: 0,
batch: this.props.rectangles
})
this.props.canvasContext.drawViewportRectangle({
configSpaceViewportRect: this.props.configSpaceViewportRect,
configSpaceToPhysicalViewSpace: this.configSpaceToPhysicalViewSpace(),
physicalSize: this.physicalViewSize()
})
})
/*
this.viewportRectRenderer({
configSpaceViewportRect: this.props.configSpaceViewportRect,
configSpaceToPhysicalViewSpace: this.configSpaceToPhysicalViewSpace(),
physicalSize: this.physicalViewSize()
})
*/
}
private renderOverlays() {
@@ -451,110 +429,3 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
)
}
}
export interface OverlayRectangleRendererProps {
configSpaceToPhysicalViewSpace: AffineTransform
configSpaceViewportRect: Rect
physicalSize: Vec2
}
export const viewportRectangleRenderer = (regl: regl.Instance) => {
return regl<OverlayRectangleRendererProps>({
vert: `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
}
`,
frag: `
precision mediump float;
uniform mat3 configSpaceToPhysicalViewSpace;
uniform vec2 physicalSize;
uniform vec2 configSpaceViewportOrigin;
uniform vec2 configSpaceViewportSize;
void main() {
vec2 origin = (configSpaceToPhysicalViewSpace * vec3(configSpaceViewportOrigin, 1.0)).xy;
vec2 size = (configSpaceToPhysicalViewSpace * vec3(configSpaceViewportSize, 0.0)).xy;
vec2 halfSize = physicalSize / 2.0;
float borderWidth = 2.0;
origin = floor(origin * halfSize) / halfSize + borderWidth * vec2(1.0, 1.0);
size = floor(size * halfSize) / halfSize - 2.0 * borderWidth * vec2(1.0, 1.0);
vec2 coord = gl_FragCoord.xy;
coord.y = physicalSize.y - coord.y;
vec2 clamped = clamp(coord, origin, origin + size);
vec2 gap = clamped - coord;
float maxdist = max(abs(gap.x), abs(gap.y));
// TOOD(jlfwong): Could probably optimize this to use mix somehow.
if (maxdist == 0.0) {
// Inside viewport rectangle
gl_FragColor = vec4(0, 0, 0, 0);
} else if (maxdist < borderWidth) {
// Inside viewport rectangle at border
gl_FragColor = vec4(0.7, 0.7, 0.7, 0.8);
} else {
// Outside viewport rectangle
gl_FragColor = vec4(0.7, 0.7, 0.7, 0.5);
}
}
`,
blend: {
enable: true,
func: {
srcRGB: 'src alpha',
srcAlpha: 'one',
dstRGB: 'one minus src alpha',
dstAlpha: 'one'
}
},
depth: {
enable: false
},
attributes: {
// Cover full canvas with a rectangle
// with 2 triangles using a triangle
// strip.
//
// 0 +--+ 1
// | /|
// |/ |
// 2 +--+ 3
position: [
[-1, 1],
[1, 1],
[-1, -1],
[1, -1]
]
},
uniforms: {
configSpaceToPhysicalViewSpace: (context, props) => {
return props.configSpaceToPhysicalViewSpace.flatten()
},
configSpaceViewportOrigin: (context, props) => {
return props.configSpaceViewportRect.origin.flatten()
},
configSpaceViewportSize: (context, props) => {
return props.configSpaceViewportRect.size.flatten()
},
physicalSize: (context, props) => {
return props.physicalSize.flatten()
}
},
primitive: 'triangle strip',
count: 4
})
}
+8 -25
View File
@@ -1,4 +1,3 @@
import * as regl from 'regl'
import {h} from 'preact'
import {css} from 'aphrodite'
import {ReloadableComponent} from './reloadable'
@@ -8,11 +7,12 @@ import { Flamechart, FlamechartFrame } from './flamechart'
import { Rect, Vec2, AffineTransform, clamp } from './math'
import { atMostOnceAFrame, cachedMeasureTextWidth } from "./utils";
import { RectangleBatchRenderer, RectangleBatch } from "./rectangle-batch-renderer"
import { RectangleBatch } from "./rectangle-batch-renderer"
import { FlamechartMinimapView } from "./flamechart-minimap-view"
import { style, Sizes } from './flamechart-style'
import { FontSize, FontFamily, Colors } from './style'
import { CanvasContext } from './canvas-context'
interface FlamechartFrameLabel {
configSpaceBounds: Rect
@@ -73,8 +73,7 @@ const DEVICE_PIXEL_RATIO = window.devicePixelRatio
interface FlamechartPanZoomViewProps {
flamechart: Flamechart
gl: regl.Instance
renderer: RectangleBatchRenderer
canvasContext: CanvasContext
rectangles: RectangleBatch
setNodeHover: (node: CallTreeNode | null, logicalViewSpaceMouse: Vec2) => void
@@ -336,21 +335,8 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
const configSpaceToNDC = this.physicalViewSpaceToNDC().times(this.configSpaceToPhysicalViewSpace())
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({
this.props.canvasContext.renderInto(this.canvas, () => {
this.props.canvasContext.drawRectangleBatch({
configSpaceToNDC: configSpaceToNDC,
physicalSize: this.physicalViewSize(),
strokeSize: 1,
@@ -604,8 +590,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
interface FlamechartViewProps {
flamechart: Flamechart
gl: regl.Instance
renderer: RectangleBatchRenderer
canvasContext: CanvasContext
rectangles: RectangleBatch
}
@@ -736,15 +721,13 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
<FlamechartMinimapView
configSpaceViewportRect={this.state.configSpaceViewportRect}
transformViewport={this.transformViewport}
gl={this.props.gl}
renderer={this.props.renderer}
canvasContext={this.props.canvasContext}
rectangles={this.props.rectangles}
setConfigSpaceViewportRect={this.setConfigSpaceViewportRect}
flamechart={this.props.flamechart} />
<FlamechartPanZoomView
ref={this.panZoomRef}
gl={this.props.gl}
renderer={this.props.renderer}
canvasContext={this.props.canvasContext}
rectangles={this.props.rectangles}
flamechart={this.props.flamechart}
setNodeHover={this.onNodeHover}
+116
View File
@@ -0,0 +1,116 @@
import * as regl from 'regl'
import { AffineTransform, Rect, Vec2 } from './math'
export interface ViewportRectangleRendererProps {
configSpaceToPhysicalViewSpace: AffineTransform
configSpaceViewportRect: Rect
physicalSize: Vec2
}
export class ViewportRectangleRenderer {
private command: regl.Command<ViewportRectangleRendererProps>
constructor(regl: regl.Instance) {
this.command = regl<ViewportRectangleRendererProps>({
vert: `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
}
`,
frag: `
precision mediump float;
uniform mat3 configSpaceToPhysicalViewSpace;
uniform vec2 physicalSize;
uniform vec2 configSpaceViewportOrigin;
uniform vec2 configSpaceViewportSize;
void main() {
vec2 origin = (configSpaceToPhysicalViewSpace * vec3(configSpaceViewportOrigin, 1.0)).xy;
vec2 size = (configSpaceToPhysicalViewSpace * vec3(configSpaceViewportSize, 0.0)).xy;
vec2 halfSize = physicalSize / 2.0;
float borderWidth = 2.0;
origin = floor(origin * halfSize) / halfSize + borderWidth * vec2(1.0, 1.0);
size = floor(size * halfSize) / halfSize - 2.0 * borderWidth * vec2(1.0, 1.0);
vec2 coord = gl_FragCoord.xy;
coord.y = physicalSize.y - coord.y;
vec2 clamped = clamp(coord, origin, origin + size);
vec2 gap = clamped - coord;
float maxdist = max(abs(gap.x), abs(gap.y));
// TOOD(jlfwong): Could probably optimize this to use mix somehow.
if (maxdist == 0.0) {
// Inside viewport rectangle
gl_FragColor = vec4(0, 0, 0, 0);
} else if (maxdist < borderWidth) {
// Inside viewport rectangle at border
gl_FragColor = vec4(0.7, 0.7, 0.7, 0.8);
} else {
// Outside viewport rectangle
gl_FragColor = vec4(0.7, 0.7, 0.7, 0.5);
}
}
`,
blend: {
enable: true,
func: {
srcRGB: 'src alpha',
srcAlpha: 'one',
dstRGB: 'one minus src alpha',
dstAlpha: 'one'
}
},
depth: {
enable: false
},
attributes: {
// Cover full canvas with a rectangle
// with 2 triangles using a triangle
// strip.
//
// 0 +--+ 1
// | /|
// |/ |
// 2 +--+ 3
position: [
[-1, 1],
[1, 1],
[-1, -1],
[1, -1]
]
},
uniforms: {
configSpaceToPhysicalViewSpace: (context, props) => {
return props.configSpaceToPhysicalViewSpace.flatten()
},
configSpaceViewportOrigin: (context, props) => {
return props.configSpaceViewportRect.origin.flatten()
},
configSpaceViewportSize: (context, props) => {
return props.configSpaceViewportRect.size.flatten()
},
physicalSize: (context, props) => {
return props.physicalSize.flatten()
}
},
primitive: 'triangle strip',
count: 4
})
}
render(props: ViewportRectangleRendererProps) {
this.command(props)
}
}