speedscope/canvas-context.ts

149 lines
4.7 KiB
TypeScript
Raw Normal View History

2018-01-23 20:59:14 +03:00
import * as regl from 'regl'
import { RectangleBatchRenderer, RectangleBatch, RectangleBatchRendererProps } from './rectangle-batch-renderer';
import { ViewportRectangleRenderer, ViewportRectangleRendererProps } from './overlay-rectangle-renderer';
import { TextureCachedRenderer, TextureRenderer } from './texture-catched-renderer'
2018-01-23 20:59:14 +03:00
import { Vec2, Rect } from './math';
2018-01-23 21:59:31 +03:00
type FrameCallback = () => void
2018-01-23 20:59:14 +03:00
interface SetViewportScopeProps {
physicalBounds: Rect
}
2018-01-23 20:59:14 +03:00
export class CanvasContext {
private gl: regl.Instance
private rectangleBatchRenderer: RectangleBatchRenderer
private viewportRectangleRenderer: ViewportRectangleRenderer
private textureRenderer: TextureRenderer
2018-01-23 20:59:14 +03:00
private setViewportScope: regl.Command<{ physicalBounds: Rect }>
constructor(canvas: HTMLCanvasElement) {
this.gl = regl({
canvas: canvas,
2018-01-25 19:59:48 +03:00
attributes: {
antialias: false
},
extensions: ['ANGLE_instanced_arrays'],
optionalExtensions: ['EXT_disjoint_timer_query'],
profile: true
})
;(window as any)['CanvasContext'] = this
2018-01-23 20:59:14 +03:00
this.rectangleBatchRenderer = new RectangleBatchRenderer(this.gl)
this.viewportRectangleRenderer = new ViewportRectangleRenderer(this.gl)
this.textureRenderer = new TextureRenderer(this.gl)
2018-01-23 20:59:14 +03:00
this.setViewportScope = this.gl<SetViewportScopeProps>({
2018-01-23 22:36:39 +03:00
context: {
viewportX: (context: regl.Context, props: SetViewportScopeProps) => {
return props.physicalBounds.left()
},
viewportY: (context: regl.Context, props: SetViewportScopeProps) => {
return props.physicalBounds.top()
}
2018-01-23 22:36:39 +03:00
},
2018-01-23 20:59:14 +03:00
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()
}
}
}
})
}
2018-01-23 21:59:31 +03:00
private tick: regl.Tick | null = null
private tickNeeded: boolean = false
private beforeFrameHandlers = new Set<FrameCallback>()
addBeforeFrameHandler(callback: FrameCallback) {
this.beforeFrameHandlers.add(callback)
}
removeBeforeFrameHandler(callback: FrameCallback) {
this.beforeFrameHandlers.delete(callback)
}
requestFrame() {
this.tickNeeded = true
if (!this.tick) {
this.tick = this.gl.frame(this.onBeforeFrame)
}
}
2018-01-25 22:50:40 +03:00
private perfDebug = window.location.href.indexOf('perf-debug=1') !== -1
2018-01-25 22:50:40 +03:00
private onBeforeFrame = (context: regl.Context) => {
this.gl({
scissor: { enable: false }
})(() => {
this.gl.clear({ color: [0, 0, 0, 0] })
})
2018-01-23 21:59:31 +03:00
this.tickNeeded = false
2018-01-25 22:50:40 +03:00
let beforeHandlers = performance.now()
2018-01-23 21:59:31 +03:00
for (const handler of this.beforeFrameHandlers) {
handler()
}
2018-01-25 22:50:40 +03:00
let cpuTimeElapsed = performance.now() - beforeHandlers;
2018-01-23 21:59:31 +03:00
if (this.tick && !this.tickNeeded) {
if (!this.perfDebug) {
this.tick.cancel()
this.tick = null
}
2018-01-23 21:59:31 +03:00
}
2018-01-25 20:22:15 +03:00
2018-01-25 22:50:40 +03:00
// TODO(jlfwong): It would be really nice to have GPU
// stats here, but I can't figure out how to interpret
// the gpuTime. I suspect this is caused by executing the same
// program multiple times without flushing.
// I'll investigate this at some point and report a bug to regl.
if (this.perfDebug) {
console.log('CPU Frame Generation Time (ms)', cpuTimeElapsed.toFixed(2))
}
2018-01-23 21:59:31 +03:00
}
2018-01-23 20:59:14 +03:00
drawRectangleBatch(props: RectangleBatchRendererProps) {
this.rectangleBatchRenderer.render(props)
}
createRectangleBatch(): RectangleBatch {
return new RectangleBatch(this.gl)
}
createTextureCachedRenderer<T>(options: {
render(t: T): void
shouldUpdate(oldProps: T, newProps: T): boolean
}): TextureCachedRenderer<T> {
return new TextureCachedRenderer(this.gl, {
...options,
textureRenderer: this.textureRenderer
})
}
2018-01-23 20:59:14 +03:00
drawViewportRectangle(props: ViewportRectangleRendererProps){
this.viewportRectangleRenderer.render(props)
}
renderInto(el: Element, cb: (context: regl.Context) => void) {
2018-01-23 20:59:14 +03:00
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)
}
}