webgl/BaseFramebuffer.tstypescript
import { Handler } from "./Handler";
export interface IBaseFramebufferParams {
width?: number;
height?: number;
useDepth?: boolean;
depthComponent?: string;
size?: number;
filter?: string;
sharedDepthFramebuffer?: BaseFramebuffer | null;
}
export class BaseFramebuffer {
static __counter__: number = 0;
protected __id: number;
public handler: Handler;
public _fbo: WebGLFramebuffer | null;
protected _depthRenderbuffer: WebGLRenderbuffer | null;
protected _sharedDepthFramebuffer: BaseFramebuffer | null;
public _width: number;
public _height: number;
protected _depthComponent: string;
protected _size: number;
protected _active: boolean;
protected _useDepth: boolean;
protected _filter: string;
constructor(handler: Handler, options: IBaseFramebufferParams = {}) {
this.handler = handler;
this.__id = BaseFramebuffer.__counter__++;
this._fbo = null;
this._width = options.width || handler.canvas!.width;
this._height = options.height || handler.canvas!.height;
this._depthComponent = options.depthComponent != undefined ? options.depthComponent : "DEPTH_COMPONENT24";
this._useDepth = options.useDepth != undefined ? options.useDepth : true;
this._active = false;
this._size = options.size || 1;
this._depthRenderbuffer = null;
this._sharedDepthFramebuffer = options.sharedDepthFramebuffer || null;
this._filter = options.filter || "NEAREST";
}
static blitTo(
destFramebuffer: BaseFramebuffer,
sourceFramebuffer: BaseFramebuffer,
glAttachmentIndex: number | null,
glMask: number,
glFilter: number
) {
let gl = sourceFramebuffer.handler.gl!;
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, sourceFramebuffer._fbo);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, destFramebuffer._fbo);
if (glAttachmentIndex !== null) {
gl.readBuffer(gl.COLOR_ATTACHMENT0 + glAttachmentIndex);
gl.clearBufferfv(gl.COLOR, 0, [0.0, 0.0, 0.0, 1.0]);
}
gl.blitFramebuffer(
0,
0,
sourceFramebuffer._width,
sourceFramebuffer._height,
0,
0,
destFramebuffer._width,
destFramebuffer._height,
glMask,
glFilter
);
gl.bindFramebuffer(gl.FRAMEBUFFER, null!);
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null!);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null!);
}
public blitDepthFrom(sourceFramebuffer: BaseFramebuffer, glFilter?: number) {
const gl = this.handler.gl!;
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, sourceFramebuffer._fbo);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this._fbo);
gl.blitFramebuffer(
0,
0,
sourceFramebuffer._width,
sourceFramebuffer._height,
0,
0,
this._width,
this._height,
gl.DEPTH_BUFFER_BIT,
glFilter ?? gl.NEAREST
);
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null!);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null!);
}
public get width(): number {
return this._width;
}
public get height(): number {
return this._height;
}
public get depthRenderbuffer(): WebGLRenderbuffer | null {
return this.sharedDepthRenderbuffer || this._depthRenderbuffer;
}
public get sharedDepthFramebuffer(): BaseFramebuffer | null {
return this._sharedDepthFramebuffer;
}
public get sharedDepthRenderbuffer(): WebGLRenderbuffer | null {
let current = this._sharedDepthFramebuffer;
let guard = 0;
while (current && guard++ < 32) {
if (current === this) {
return null;
}
if (!current._sharedDepthFramebuffer) {
return current._depthRenderbuffer;
}
current = current._sharedDepthFramebuffer;
}
return null;
}
/**
* Sets framebuffer viewport size.
* @public
* @param {number} width - Framebuffer width.
* @param {number} height - Framebuffer height.
* @param {boolean} [forceDestroy] -
*/
public setSize(width: number, height: number, forceDestroy: boolean = false) {
this._width = width;
this._height = height;
if (this._active) {
this.handler.gl!.viewport(0, 0, this._width, this._height);
}
if (this._useDepth || forceDestroy) {
this.destroy();
this.init();
}
}
public init() {}
public destroy() {}
/**
* Returns framebuffer completed.
* @public
* @returns {boolean} -
*/
public isComplete(): boolean {
let gl = this.handler.gl!;
return gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
}
public checkStatus(): number {
let gl = this.handler.gl!;
return gl.checkFramebufferStatus(gl.FRAMEBUFFER);
}
/**
* Checks active framebuffer completeness and logs readable status on failure.
* @public
* @param {string} [message] - Warning message prefix.
* @returns {boolean} `true` when active framebuffer is complete.
*/
public validateComplete(message?: string): boolean {
const gl = this.handler.gl;
if (!gl) {
return false;
}
const status = this.checkStatus();
if (status === gl.FRAMEBUFFER_COMPLETE) {
return true;
}
const statusText = `status=${this.statusToText(status)}`;
console.warn(
message
? `${message} (${statusText})`
: `BaseFramebuffer.validateComplete(): framebuffer incomplete (${statusText}).`
);
return false;
}
/**
* Binds a 2D texture to the active framebuffer color attachment.
* @public
* @param {WebGLTexture} texture - Output texture.
* @param {number} [glAttachment=COLOR_ATTACHMENT0] - GL color attachment.
*/
public bindOutputTexture(texture: WebGLTexture, glAttachment?: number): void {
const gl = this.handler.gl!;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.framebufferTexture2D(gl.FRAMEBUFFER, glAttachment || gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.bindTexture(gl.TEXTURE_2D, null!);
}
/**
* Binds a texture array layer to the active framebuffer color attachment.
* Activate framebuffer before calling this method.
* @public
* @param {WebGLTexture | null} texture - Output texture array.
* @param {number} layer - Array layer index.
* @param {number} [colorAttachmentIndex=0] - Color attachment index.
* @param {number} [level=0] - Mipmap level.
*/
public bindOutputTextureLayer(
texture: WebGLTexture | null,
layer: number,
colorAttachmentIndex: number = 0,
level: number = 0
): void {
const gl = this.handler.gl!;
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + colorAttachmentIndex, texture, level, layer);
}
/**
* Converts framebuffer status enum to readable text.
*/
public statusToText(status: number): string {
const gl = this.handler.gl;
if (!gl) {
return `0x${status.toString(16)}`;
}
switch (status) {
case gl.FRAMEBUFFER_COMPLETE:
return "FRAMEBUFFER_COMPLETE";
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
return "FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
return "FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
return "FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
case gl.FRAMEBUFFER_UNSUPPORTED:
return "FRAMEBUFFER_UNSUPPORTED";
case gl.FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
return "FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
default:
return `0x${status.toString(16)}`;
}
}
/**
* Activate framebuffer frame to draw.
* @public
* @returns {Framebuffer} Returns Current framebuffer.
*/
public activate() {
let gl = this.handler.gl!;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
gl.viewport(0, 0, this._width, this._height);
this._active = true;
let c = this.handler.framebufferStack.current().data;
c && (c._active = false);
this.handler.framebufferStack.push(this);
return this;
}
/**
* Deactivate framebuffer frame.
* @public
*/
public deactivate() {
let h = this.handler,
gl = h.gl!;
gl.bindFramebuffer(gl.FRAMEBUFFER, null!);
this._active = false;
let f = this.handler.framebufferStack.popPrev();
if (f) {
gl.bindFramebuffer(gl.FRAMEBUFFER, f._fbo);
gl.viewport(0, 0, f._width, f._height);
} else {
gl.viewport(0, 0, h.canvas!.width, h.canvas!.height);
}
}
public isEqual(framebuffer: BaseFramebuffer | null): boolean {
return framebuffer ? this.__id === framebuffer.__id : false;
}
}