mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-25 06:05:52 +02:00
Archive commit
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
import { Texture2D, resizeTexture, disposeTexture, createEmptyTexture } from './texture2d';
|
||||
import { array } from '../../common/utils';
|
||||
|
||||
export interface FBOOptions {
|
||||
/* Upgrade to floating point if available, otherwise fallback to 8bit. (default false) */
|
||||
preferFloat?: boolean;
|
||||
/* Use floating point textures (default false) */
|
||||
float?: boolean;
|
||||
/* The number of color buffers to create (default 1) */
|
||||
color?: number;
|
||||
/* If fbo has a depth buffer (default: true) */
|
||||
depth?: boolean;
|
||||
/* If fbo has a stencil buffer (default: false) */
|
||||
stencil?: boolean;
|
||||
}
|
||||
|
||||
type WebGL = WebGLRenderingContext;
|
||||
type FBOState = [WebGLFramebuffer | null, WebGLRenderbuffer | null, WebGLTexture | null];
|
||||
|
||||
let colorAttachmentArrays: number[][] | null = null;
|
||||
|
||||
export interface FrameBuffer {
|
||||
color: (Texture2D | undefined)[];
|
||||
depth: Texture2D | undefined;
|
||||
handle: WebGLFramebuffer | null;
|
||||
colorRenderBuffer: WebGLRenderbuffer | null;
|
||||
depthRenderBuffer: WebGLRenderbuffer | null;
|
||||
width: number;
|
||||
height: number;
|
||||
gl: WebGLRenderingContext;
|
||||
colorType: number;
|
||||
useDepth: boolean;
|
||||
useStencil: boolean;
|
||||
ext: WEBGL_draw_buffers | null;
|
||||
}
|
||||
|
||||
export function createFrameBuffer(gl: WebGL, width: number, height: number, options: FBOOptions = {}): FrameBuffer {
|
||||
const { color = 1, depth = true, stencil = false, float = false, preferFloat = false } = options;
|
||||
const ext = gl.getExtension('WEBGL_draw_buffers');
|
||||
|
||||
width = width | 0;
|
||||
height = height | 0;
|
||||
|
||||
if (!colorAttachmentArrays && ext) {
|
||||
lazyInitColorAttachments(gl, ext);
|
||||
}
|
||||
|
||||
const maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
|
||||
|
||||
if (maxFBOSize != null && (width < 0 || width > maxFBOSize || height < 0 || height > maxFBOSize)) {
|
||||
throw new Error('Parameters are too large for FBO');
|
||||
}
|
||||
|
||||
const numColors = Math.max(color, 0);
|
||||
|
||||
if (numColors < 0) {
|
||||
throw new Error('Must specify a nonnegative number of colors');
|
||||
} else if (numColors > 1) {
|
||||
if (!ext) {
|
||||
throw new Error('Multiple draw buffer extension not supported');
|
||||
} else if (numColors > gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL)) {
|
||||
throw new Error(`Context does not support ${numColors} draw buffers`);
|
||||
}
|
||||
}
|
||||
|
||||
let colorType = gl.UNSIGNED_BYTE;
|
||||
const OES_texture_float = gl.getExtension('OES_texture_float');
|
||||
|
||||
if (float && numColors > 0) {
|
||||
if (!OES_texture_float) {
|
||||
throw new Error('Context does not support floating point textures');
|
||||
}
|
||||
|
||||
colorType = gl.FLOAT;
|
||||
} else if (preferFloat && numColors > 0) {
|
||||
if (OES_texture_float) {
|
||||
colorType = gl.FLOAT;
|
||||
}
|
||||
}
|
||||
|
||||
const fbo: FrameBuffer = {
|
||||
gl, width, height, colorType, color: array(numColors, undefined), useDepth: depth, useStencil: stencil, ext,
|
||||
colorRenderBuffer: null, depth: undefined, depthRenderBuffer: null, handle: null,
|
||||
};
|
||||
|
||||
rebuild(fbo);
|
||||
return fbo;
|
||||
}
|
||||
|
||||
export function resizeFrameBuffer(fbo: FrameBuffer, w: number, h: number) {
|
||||
if (fbo.width === w && fbo.height === h) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gl = fbo.gl;
|
||||
const maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
|
||||
|
||||
if (maxFBOSize != null && (w < 0 || w > maxFBOSize || h < 0 || h > maxFBOSize)) {
|
||||
throw new Error(`Can't resize FBO, invalid dimensions`);
|
||||
}
|
||||
|
||||
fbo.width = w;
|
||||
fbo.height = h;
|
||||
|
||||
const state = saveFBOState(gl);
|
||||
|
||||
for (const color of fbo.color) {
|
||||
if (color) {
|
||||
resizeTexture(gl, color, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
if (fbo.colorRenderBuffer) {
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.colorRenderBuffer);
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, w, h);
|
||||
}
|
||||
|
||||
if (fbo.depth) {
|
||||
resizeTexture(gl, fbo.depth, w, h);
|
||||
}
|
||||
|
||||
if (fbo.depthRenderBuffer) {
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, fbo.depthRenderBuffer);
|
||||
|
||||
if (fbo.useDepth && fbo.useStencil) {
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, w, h);
|
||||
} else if (fbo.useDepth) {
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h);
|
||||
} else if (fbo.useStencil) {
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.handle);
|
||||
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
||||
|
||||
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
||||
disposeFrameBuffer(fbo);
|
||||
restoreFBOState(gl, state);
|
||||
throwFBOError(gl, status, `reshape(${w}, ${h})`);
|
||||
}
|
||||
|
||||
restoreFBOState(gl, state);
|
||||
}
|
||||
|
||||
export function bindFrameBuffer(fbo: FrameBuffer) {
|
||||
fbo.gl.bindFramebuffer(fbo.gl.FRAMEBUFFER, fbo.handle);
|
||||
fbo.gl.viewport(0, 0, fbo.width, fbo.height);
|
||||
}
|
||||
|
||||
export function disposeFrameBuffer(fbo: FrameBuffer | undefined): undefined {
|
||||
if (fbo) {
|
||||
fbo.gl.deleteFramebuffer(fbo.handle);
|
||||
fbo.handle = null;
|
||||
fbo.depth = disposeTexture(fbo.gl, fbo.depth);
|
||||
|
||||
if (fbo.depthRenderBuffer) {
|
||||
fbo.gl.deleteRenderbuffer(fbo.depthRenderBuffer);
|
||||
fbo.depthRenderBuffer = null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < fbo.color.length; i++) {
|
||||
fbo.color[i] = disposeTexture(fbo.gl, fbo.color[i]);
|
||||
}
|
||||
|
||||
if (fbo.colorRenderBuffer) {
|
||||
fbo.gl.deleteRenderbuffer(fbo.colorRenderBuffer);
|
||||
fbo.colorRenderBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function rebuild(fbo: FrameBuffer) {
|
||||
const state = saveFBOState(fbo.gl);
|
||||
const gl = fbo.gl;
|
||||
const handle = fbo.handle = gl.createFramebuffer();
|
||||
const numColors = fbo.color.length;
|
||||
const { width, height, ext, useStencil, useDepth, colorType } = fbo;
|
||||
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, handle);
|
||||
|
||||
for (let i = 0; i < numColors; ++i) {
|
||||
fbo.color[i] = initTexture(gl, width, height, colorType, gl.RGBA, gl.COLOR_ATTACHMENT0 + i);
|
||||
}
|
||||
|
||||
if (numColors === 0) {
|
||||
fbo.colorRenderBuffer = initRenderBuffer(gl, width, height, gl.RGBA4, gl.COLOR_ATTACHMENT0);
|
||||
|
||||
if (ext) {
|
||||
ext.drawBuffersWEBGL(colorAttachmentArrays![0]);
|
||||
}
|
||||
} else if (numColors > 1) {
|
||||
if (ext) {
|
||||
ext.drawBuffersWEBGL(colorAttachmentArrays![numColors]);
|
||||
}
|
||||
}
|
||||
|
||||
const WEBGL_depth_texture = gl.getExtension('WEBGL_depth_texture');
|
||||
|
||||
if (WEBGL_depth_texture) {
|
||||
if (useStencil) {
|
||||
fbo.depth = initTexture(
|
||||
gl, width, height, WEBGL_depth_texture.UNSIGNED_INT_24_8_WEBGL, gl.DEPTH_STENCIL, gl.DEPTH_STENCIL_ATTACHMENT);
|
||||
} else if (useDepth) {
|
||||
fbo.depth = initTexture(gl, width, height, gl.UNSIGNED_SHORT, gl.DEPTH_COMPONENT, gl.DEPTH_ATTACHMENT);
|
||||
}
|
||||
} else {
|
||||
if (useDepth && useStencil) {
|
||||
fbo.depthRenderBuffer = initRenderBuffer(gl, width, height, gl.DEPTH_STENCIL, gl.DEPTH_STENCIL_ATTACHMENT);
|
||||
} else if (useDepth) {
|
||||
fbo.depthRenderBuffer = initRenderBuffer(gl, width, height, gl.DEPTH_COMPONENT16, gl.DEPTH_ATTACHMENT);
|
||||
} else if (useStencil) {
|
||||
fbo.depthRenderBuffer = initRenderBuffer(gl, width, height, gl.STENCIL_INDEX8, gl.STENCIL_ATTACHMENT);
|
||||
}
|
||||
}
|
||||
|
||||
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
||||
|
||||
if (status !== gl.FRAMEBUFFER_COMPLETE) {
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
gl.deleteFramebuffer(fbo.handle);
|
||||
fbo.handle = null;
|
||||
fbo.depth = disposeTexture(gl, fbo.depth);
|
||||
|
||||
if (fbo.depthRenderBuffer) {
|
||||
gl.deleteRenderbuffer(fbo.depthRenderBuffer);
|
||||
fbo.depthRenderBuffer = null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < fbo.color.length; i++) {
|
||||
fbo.color[i] = disposeTexture(gl, fbo.color[i]);
|
||||
}
|
||||
|
||||
if (fbo.colorRenderBuffer) {
|
||||
gl.deleteRenderbuffer(fbo.colorRenderBuffer);
|
||||
fbo.colorRenderBuffer = null;
|
||||
}
|
||||
|
||||
restoreFBOState(gl, state);
|
||||
throwFBOError(gl, status);
|
||||
}
|
||||
|
||||
restoreFBOState(gl, state);
|
||||
}
|
||||
|
||||
function saveFBOState(gl: WebGL): FBOState {
|
||||
const fbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
|
||||
const rbo = gl.getParameter(gl.RENDERBUFFER_BINDING);
|
||||
const tex = gl.getParameter(gl.TEXTURE_BINDING_2D);
|
||||
return [fbo, rbo, tex];
|
||||
}
|
||||
|
||||
function restoreFBOState(gl: WebGL, [fbo, rbo, tex]: FBOState) {
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
}
|
||||
|
||||
function lazyInitColorAttachments(gl: WebGL, ext: WEBGL_draw_buffers) {
|
||||
const maxColorAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL);
|
||||
colorAttachmentArrays = [];
|
||||
|
||||
for (let i = 0; i <= maxColorAttachments; ++i) {
|
||||
const x: number[] = [];
|
||||
|
||||
for (let j = 0; j < i; ++j) {
|
||||
x.push(gl.COLOR_ATTACHMENT0 + j);
|
||||
}
|
||||
|
||||
for (let j = i; j < maxColorAttachments; ++j) {
|
||||
x.push(gl.NONE);
|
||||
}
|
||||
|
||||
colorAttachmentArrays.push(x);
|
||||
}
|
||||
}
|
||||
|
||||
function throwFBOError(gl: WebGL, status: number, message = '') {
|
||||
switch (status) {
|
||||
case gl.FRAMEBUFFER_UNSUPPORTED:
|
||||
throw new Error('Framebuffer unsupported');
|
||||
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
||||
throw new Error('Framebuffer incomplete attachment');
|
||||
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
|
||||
throw new Error('Framebuffer incomplete dimensions');
|
||||
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
||||
throw new Error('Framebuffer incomplete missing attachment');
|
||||
default:
|
||||
throw new Error(`Framebuffer failed for unspecified reason [${status}] (${message})`);
|
||||
}
|
||||
}
|
||||
|
||||
function initTexture(gl: WebGL, width: number, height: number, type: number, format: number, attachment: number) {
|
||||
const texture = createEmptyTexture(gl, width, height, format, type);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture.handle);
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, texture.handle, 0);
|
||||
return texture;
|
||||
}
|
||||
|
||||
function initRenderBuffer(gl: WebGL, width: number, height: number, component: number, attachment: number) {
|
||||
const result = gl.createRenderbuffer();
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, result);
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, component, width, height);
|
||||
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, result);
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user