src/viewer/scene/webgl/RenderBuffer.js
/**
* @desc Represents a WebGL render buffer.
* @private
*/
class RenderBuffer {
constructor(gl, colorFormats, hasDepthTexture) {
/** @type {WebGL2RenderingContext} */
this.gl = gl;
this.allocated = false;
this.buffer = null;
this.bound = false;
this._colorFormats = colorFormats;
this._hasDepthTexture = hasDepthTexture;
}
setSize(size) {
this.size = size;
}
bind() {
const width = this.size[0];
const height = this.size[1];
if (this.buffer && ((this.buffer.width !== width) || (this.buffer.height !== height))) {
this.buffer.cleanup();
this.buffer = null;
}
if (! this.buffer) {
this._allocateBuffer(width, height);
this.bound = false;
}
if (! this.bound) {
const gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this.buffer.framebuf);
this.bound = true;
}
}
_allocateBuffer(width, height) {
const internalformats = this._colorFormats || [];
const gl = this.gl;
const createTexture = () => {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
return tex;
};
const colorTextures = ((internalformats.length > 0) ? internalformats : [ null ]).map(internalformat => {
const tex = createTexture();
if (internalformat) {
gl.texStorage2D(gl.TEXTURE_2D, 1, internalformat, width, height);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
}
return tex;
});
const renderbuf = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuf);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT32F, width, height);
const framebuf = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuf);
for (let i = 0; i < colorTextures.length; i++) {
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, colorTextures[i], 0);
}
if (internalformats.length > 0) {
gl.drawBuffers(colorTextures.map((_, i) => gl.COLOR_ATTACHMENT0 + i));
}
const depthTexture = this._hasDepthTexture && createTexture();
if (depthTexture) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT32F, width, height, 0, gl.DEPTH_COMPONENT, gl.FLOAT, null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
} else {
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuf);
}
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
// Verify framebuffer is OK
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuf);
if (!gl.isFramebuffer(framebuf)) {
throw "Invalid framebuffer";
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
switch (status) {
case gl.FRAMEBUFFER_COMPLETE:
break;
case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
throw "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
throw "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
throw "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
case gl.FRAMEBUFFER_UNSUPPORTED:
throw "Incomplete framebuffer: FRAMEBUFFER_UNSUPPORTED";
default:
throw "Incomplete framebuffer: " + status;
}
this.buffer = {
cleanup: () => {
colorTextures.forEach(texture => gl.deleteTexture(texture));
depthTexture && gl.deleteTexture(depthTexture);
gl.deleteFramebuffer(framebuf);
gl.deleteRenderbuffer(renderbuf);
},
framebuf: framebuf,
width: width,
height: height
};
this.colorTextures = colorTextures.map(tex => ({
bind: function(unit) {
gl.activeTexture(gl["TEXTURE" + unit]);
gl.bindTexture(gl.TEXTURE_2D, tex);
return true;
}
}));
this.depthTexture = depthTexture && {
bind: function(unit) {
gl.activeTexture(gl["TEXTURE" + unit]);
gl.bindTexture(gl.TEXTURE_2D, depthTexture);
return true;
}
};
}
read(pickX, pickY, glFormat = null, glType = null, arrayType = Uint8Array, arrayMultiplier = 4, colorBufferIndex = 0) {
const pix = new arrayType(arrayMultiplier);
const gl = this.gl;
gl.readBuffer(gl.COLOR_ATTACHMENT0 + colorBufferIndex);
gl.readPixels(pickX, this.buffer.height - pickY - 1, 1, 1, glFormat || gl.RGBA, glType || gl.UNSIGNED_BYTE, pix, 0);
return pix;
}
unbind() {
const gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
this.bound = false;
}
destroy() {
if (this.allocated) {
this.buffer.cleanup();
this.allocated = false;
this.buffer = null;
this.bound = false;
}
this.colorTextures = null;
this.depthTexture = null;
}
}
export {RenderBuffer};