Detach the GL context state to a separate module.

This commit is contained in:
2023-02-09 09:50:44 +08:00
parent 4cd5ebb936
commit c494156983
8 changed files with 118 additions and 7 deletions
@@ -0,0 +1,335 @@
import { ResizeObserver as ResizeObserverPolyfill } from '@juggle/resize-observer';
import { EventEmitter } from "kernel/EventEmitter";
const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill;
interface WebGLCanvasOption {
/**
* Canvas element for outputting renderer images.
* If a string is passed in:
* Here will try `document.getElementById` and then `document.querySelector`
* until a valid canvas element is found.
*/
canvas?: HTMLCanvasElement | OffscreenCanvas | string;
/**
* Initialize with WebGL context.
*/
context?: WebGLRenderingContext | WebGL2RenderingContext;
/**
* Parent node of canvas.
*/
container?: HTMLElement | string;
/**
* Automatically adjust width and height to parent node width and height.
*/
autoResize?: boolean;
/**
* Width of Canvas.
* It will become invalid when autoResize is set.
*/
width?: number;
/**
* Height of Canvas.
* It will become invalid when autoResize is set.
*/
height?: number;
/**
* Whether to use offline canvas.
*/
isOffScreen?: boolean;
/**
* WebGL context attributes.
*/
contextAttributes?: WebGLContextAttributes;
}
type WebGLCanvasEventMap = {
/**
* Emit When the canvas size changes.
*/
"resize": void;
};
class WebGLCanvas extends EventEmitter<WebGLCanvasEventMap> {
public readonly element: HTMLCanvasElement | OffscreenCanvas;
public readonly context: WebGLRenderingContext | WebGL2RenderingContext;
public readonly glVersion: 0 | 1 | 2;
public readonly isOffScreen: boolean;
public constructor(userOption?: WebGLCanvasOption) {
super();
const option: WebGLCanvasOption = userOption ?? {};
let targetCanvas: HTMLCanvasElement | OffscreenCanvas | undefined = void 0;
let targetContext: WebGLRenderingContext | WebGL2RenderingContext | undefined = void 0;
let targetGLVersion: 0 | 1 | 2 = 0;
let targetIsOffScreen: boolean = false;
if (option.canvas instanceof HTMLCanvasElement || option.canvas instanceof OffscreenCanvas) {
targetCanvas = option.canvas;
}
else if (typeof option.canvas === "string") {
let findTarget: HTMLElement | null = null;
findTarget = document.getElementById(option.canvas);
if (findTarget instanceof HTMLCanvasElement) {
targetCanvas = findTarget;
}
else {
findTarget = document.querySelector(option.canvas);
if (findTarget instanceof HTMLCanvasElement) {
targetCanvas = findTarget;
}
else {
console.warn(
`[ray-lab Renderer] Unable to search any valid canvas through "${option.canvas}". \r\n` +
"Created internal canvas instead..."
);
}
}
}
if (option.context) {
if (targetCanvas && targetCanvas !== option.context.canvas) {
console.warn(
"[ray-lab Renderer] The `canvas` element does not match the `context.canvas`: \r\n" +
"The `canvas` option will be ignored. \r\n" +
"Please do not use `canvas` and `context` at the same time!"
);
}
targetCanvas = option.context.canvas;
targetContext = option.context;
}
if (targetCanvas) {
if (targetCanvas instanceof HTMLCanvasElement) {
if (option.isOffScreen) {
if (OffscreenCanvas) {
targetCanvas = targetCanvas.transferControlToOffscreen();
targetIsOffScreen = true;
}
else {
targetIsOffScreen = false;
console.warn(
"[ray-lab Renderer] The target environment does not support OffscreenCanvas \r\n" +
"`isOffScreen` is ignored!"
);
}
}
else {
targetIsOffScreen = false;
}
}
else if (targetCanvas instanceof OffscreenCanvas) {
targetIsOffScreen = true;
}
}
else {
if (option.isOffScreen) {
if (OffscreenCanvas) {
targetIsOffScreen = true;
targetCanvas = new OffscreenCanvas(option.width ?? 300, option.height ?? 150);
}
else {
targetIsOffScreen = false;
targetCanvas = document.createElement("canvas");
console.warn(
"[ray-lab Renderer] The target environment does not support OffscreenCanvas \r\n" +
"`isOffScreen` is ignored!"
);
}
}
else {
targetIsOffScreen = false;
targetCanvas = document.createElement("canvas");
}
}
if (targetContext) {
if (targetCanvas instanceof WebGL2RenderingContext) {
targetGLVersion = 2;
}
else if (targetCanvas instanceof WebGLRenderingContext) {
targetGLVersion = 1;
}
}
else {
let getContext: OffscreenRenderingContext | RenderingContext | null;
getContext = targetCanvas.getContext("webgl2", option.contextAttributes);
if (!getContext) {
getContext = targetCanvas.getContext("webgl", option.contextAttributes);
}
if (WebGL2RenderingContext && (getContext instanceof WebGL2RenderingContext)) {
targetContext = getContext;
targetGLVersion = 2;
}
else if (getContext instanceof WebGLRenderingContext) {
targetContext = getContext;
targetGLVersion = 1;
}
else {
throw new Error("[ray-lab Renderer] The target environment does not support WebGL :(");
}
}
this.element = targetCanvas;
this.context = targetContext;
this.glVersion = targetGLVersion;
this.isOffScreen = targetIsOffScreen;
if (option.autoResize) {
this.enableAutoResize(option.container);
}
else {
this.resize(option.width, option.height);
}
}
/**
* Change the canvas size.
* @param width - canvas width.
* @param height - canvas height.
*/
public resize(width?: number, height?: number) {
let hasResized = false;
if (width !== void 0) {
this.element.width = width;
hasResized = true;
}
if (height !== void 0) {
this.element.height = height;
hasResized = true;
}
if (hasResized) {
this.emit("resize");
}
}
private resizeObserver: ResizeObserver | undefined;
/**
* enable auto resize.
* @param container - Parent node of canvas.
*/
public enableAutoResize(container?: HTMLElement | string) {
let targetcontainer: HTMLElement | null = null;
if (container instanceof HTMLElement) {
targetcontainer = container;
}
else if (typeof container === "string") {
targetcontainer = document.getElementById(container);
if (targetcontainer === null) {
targetcontainer = document.querySelector(container);
if (targetcontainer === null) {
console.warn(
`[ray-lab Renderer] Unable to search any valid container through "${container}".`
);
}
}
}
if (targetcontainer === null && this.element instanceof HTMLCanvasElement) {
targetcontainer = this.element.parentElement;
}
if (targetcontainer === null) {
console.warn(
`[ray-lab Renderer] Unable to enable auto resize. \r\n` +
`Because a suitable container could not be found.`
);
}
else {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
if (entry.contentRect) {
this.resize(entry.contentRect.width, entry.contentRect.height);
}
}
});
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
this.resizeObserver = resizeObserver;
if (this.element instanceof HTMLElement) {
this.element.style.width = "100%";
this.element.style.height = "100%";
this.element.style.boxSizing = "border-box";
this.element.style.display = "block";
}
resizeObserver.observe(targetcontainer);
this.resize(targetcontainer.offsetWidth, targetcontainer.offsetHeight);
}
}
/**
* disable auto resize.
*/
public disableAutoResize() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
this.resizeObserver = void 0;
}
}
export { WebGLCanvas };
export type { WebGLCanvasOption };
@@ -0,0 +1,37 @@
import type { WebGLCanvas } from "./WebGLCanvas";
import { WebGLStateAbstract } from "./WebGLStateAbstract";
class WebGLState extends WebGLStateAbstract {
private statePool: Array<WebGLStateAbstract> = [];
private registerState(state: WebGLStateAbstract) {
this.statePool.push(state);
}
public override contextWillBind(newcanvas: WebGLCanvas) {
for (const state of this.statePool) {
state.bindCanvas(newcanvas);
}
}
public override contextLost() {
for (const state of this.statePool) {
state.contextLost();
}
}
public override resetState() {
for (const state of this.statePool) {
state.resetState();
}
}
public override destroy() {
for (const state of this.statePool) {
state.destroy();
}
}
}
export { WebGLState };
@@ -0,0 +1,56 @@
import type { WebGLCanvas } from "./WebGLCanvas";
/**
* Used for management, caching, encapsulation, WebGL context.
*/
abstract class WebGLStateAbstract {
private _canvas: WebGLCanvas | undefined;
/**
* binded canvas.
*/
public get canvas(): WebGLCanvas {
if (this._canvas) {
return this._canvas;
}
else {
throw new Error(
"[ray-lab WebGLStateBasic] Please get canvas instance after context binding."
);
}
};
/**
* Bind new WebGLCanvas.
*/
public bindCanvas(canvas: WebGLCanvas) {
this.contextWillBind(canvas);
this._canvas = canvas;
};
/**
* Life cycle hook function.
* Triggered when the context is rebound.
* @param newcanvas - New canvas object to be bound.
*/
public abstract contextWillBind(newcanvas: WebGLCanvas): any;
/**
* Life cycle hook function.
* Triggered when the context is lost.
*/
public abstract contextLost(): any;
/**
* Reset all states.
*/
public abstract resetState(): any;
/**
* Triggered when destroyed.
*/
public abstract destroy(): any;
}
export { WebGLStateAbstract };