polygonjs-engine
Version:
node-based webgl 3D engine https://polygonjs.com
158 lines (138 loc) • 4.94 kB
text/typescript
import {WebGLRenderer} from 'three/src/renderers/WebGLRenderer';
import {Texture} from 'three/src/textures/Texture';
import {WebGLRenderTarget, WebGLRenderTargetOptions} from 'three/src/renderers/WebGLRenderTarget';
import {WebGLMultisampleRenderTarget} from 'three/src/renderers/WebGLMultisampleRenderTarget';
interface RendererByString {
[propName: string]: WebGLRenderer;
}
interface TextureByString {
[propName: string]: Texture;
}
interface POLYWebGLRenderer extends WebGLRenderer {
_polygon_id: number;
}
const CONTEXT_OPTIONS = {
// antialias: false, // leave that to the renderer node
// preserveDrawingBuffer: true, // this could only be useful to capture static images
};
type Callback = (value: WebGLRenderer) => void;
enum WebGLContext {
WEBGL = 'webgl',
WEBGL2 = 'webgl2',
EXPERIMENTAL_WEBGL = 'experimental-webgl',
EXPERIMENTAL_WEBGL2 = 'experimental-webgl2',
}
export class RenderersController {
_next_renderer_id: number = 0;
_next_env_map_id: number = 0;
_renderers: RendererByString = {};
_env_maps: TextureByString = {};
private _require_webgl2: boolean = false;
private _resolves: Callback[] = [];
private _webgl2_available: boolean | undefined;
constructor() {}
setRequireWebGL2() {
if (!this._require_webgl2) {
this._require_webgl2 = true;
}
}
webgl2Available() {
if (this._webgl2_available === undefined) {
this._webgl2_available = this._set_webgl2_available();
}
return this._webgl2_available;
}
private _set_webgl2_available() {
const canvas = document.createElement('canvas');
return (window.WebGL2RenderingContext && canvas.getContext(WebGLContext.WEBGL2)) != null;
}
renderingContext(canvas: HTMLCanvasElement): WebGLRenderingContext | null {
let gl: WebGLRenderingContext | null = null;
if (this._require_webgl2) {
gl = this._rendering_context_webgl(canvas, true);
if (!gl) {
console.warn('failed to create webgl2 context');
}
}
if (!gl) {
gl = this._rendering_context_webgl(canvas, false);
}
// gl.getExtension('OES_standard_derivatives') // for derivative normals, but it cannot work at the moment (see node Gl/DerivativeNormals)
// to test data texture
// gl.getExtension('OES_texture_float')
// gl.getExtension('OES_texture_float_linear')
return gl;
}
private _rendering_context_webgl(canvas: HTMLCanvasElement, webgl2: boolean): WebGLRenderingContext | null {
let context_name: WebGLContext;
if (this.webgl2Available()) {
context_name = WebGLContext.WEBGL2;
} else {
context_name = webgl2 ? WebGLContext.WEBGL2 : WebGLContext.WEBGL;
}
let gl = canvas.getContext(context_name, CONTEXT_OPTIONS);
if (!gl) {
context_name = webgl2 ? WebGLContext.EXPERIMENTAL_WEBGL2 : WebGLContext.EXPERIMENTAL_WEBGL;
gl = canvas.getContext(context_name, CONTEXT_OPTIONS);
}
return gl as WebGLRenderingContext | null;
}
registerRenderer(renderer: WebGLRenderer) {
if ((renderer as POLYWebGLRenderer)._polygon_id) {
throw new Error('render already registered');
}
(renderer as POLYWebGLRenderer)._polygon_id = this._next_renderer_id += 1;
// there is a bug where 2 renderers are created from the beginning
// because the from_json of the viewer_component is called after
// the camera being set for the first time
// console.log("register renderer", renderer, renderer._polygon_id)
// this is being tested in PostProcess
// const canvas = renderer.domElement
// const gl = canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' )
// const extension_exist = gl.getExtension('OES_standard_derivatives')
// if(!extension_exist){
// console.warn("renderers controller: gl extension not available")
// }
this._renderers[(renderer as POLYWebGLRenderer)._polygon_id] = renderer;
if (Object.keys(this._renderers).length == 1) {
this.flush_callbacks_with_renderer(renderer);
}
}
deregisterRenderer(renderer: WebGLRenderer) {
delete this._renderers[(renderer as POLYWebGLRenderer)._polygon_id];
renderer.dispose();
}
firstRenderer(): WebGLRenderer | null {
const first_id = Object.keys(this._renderers)[0];
if (first_id) {
return this._renderers[first_id];
}
return null;
}
renderers(): WebGLRenderer[] {
return Object.values(this._renderers);
}
private flush_callbacks_with_renderer(renderer: WebGLRenderer) {
let callback: Callback | undefined;
while ((callback = this._resolves.pop())) {
callback(renderer);
}
}
async waitForRenderer(): Promise<WebGLRenderer> {
const renderer = this.firstRenderer();
if (renderer) {
return renderer;
} else {
return new Promise((resolve, reject) => {
this._resolves.push(resolve);
});
}
}
renderTarget(width: number, height: number, parameters: WebGLRenderTargetOptions) {
if (this.webgl2Available()) {
return new WebGLMultisampleRenderTarget(width, height, parameters);
} else {
return new WebGLRenderTarget(width, height, parameters);
}
}
}