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';
|
2018-01-24 10:48:13 +03:00
|
|
|
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
|
|
|
|
2018-01-24 10:48:13 +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
|
2018-01-24 10:48:13 +03:00
|
|
|
private textureRenderer: TextureRenderer
|
2018-01-23 20:59:14 +03:00
|
|
|
private setViewportScope: regl.Command<{ physicalBounds: Rect }>
|
|
|
|
|
|
|
|
constructor(canvas: HTMLCanvasElement) {
|
2018-01-24 10:58:23 +03:00
|
|
|
this.gl = regl({
|
|
|
|
canvas: canvas,
|
2018-01-25 19:59:48 +03:00
|
|
|
attributes: {
|
|
|
|
antialias: false
|
|
|
|
},
|
2018-01-24 22:06:48 +03:00
|
|
|
extensions: ['ANGLE_instanced_arrays'],
|
2018-01-24 10:58:23 +03:00
|
|
|
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)
|
2018-01-24 10:48:13 +03:00
|
|
|
this.textureRenderer = new TextureRenderer(this.gl)
|
2018-01-23 20:59:14 +03:00
|
|
|
|
2018-01-24 10:48:13 +03:00
|
|
|
this.setViewportScope = this.gl<SetViewportScopeProps>({
|
2018-01-23 22:36:39 +03:00
|
|
|
context: {
|
2018-01-24 10:48:13 +03:00
|
|
|
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
|
|
|
|
2018-01-25 23:04:30 +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) => {
|
2018-01-25 23:04:30 +03:00
|
|
|
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) {
|
2018-01-25 23:04:30 +03:00
|
|
|
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.
|
|
|
|
|
2018-01-25 23:04:30 +03:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-01-24 10:48:13 +03:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-01-24 10:48:13 +03:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|