viewer/renderbuffer.js
export const COLOR_FLOAT_DEPTH_NORMAL = 0xff01;
export const COLOR_ALPHA_DEPTH = 0xff02;
/**
*
* @ignore
* @class RenderBuffer
*/
export class RenderBuffer {
constructor(viewer, canvas, gl, purpose, supersample) {
this.viewer = viewer;
this.gl = gl;
this.allocated = false;
this.canvas = canvas;
this.buffer = null;
this.bound = false;
this.purpose = purpose;
this.supersample = supersample || 1;
}
bind() {
this._touch();
/* if (this.bound) {
return;
} */
var gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, this.buffer.framebuf);
gl.drawBuffers(this.attachments);
gl.viewport(0, 0, this.buffer.width, this.buffer.height);
this.bound = true;
}
_touch() { // Lazy-creates buffer if needed, resizes to canvas if needed
var gl = this.gl;
var width = this.canvas.clientWidth * this.supersample;
var height = this.canvas.clientHeight * this.supersample;
if (this.buffer) {
if (this.buffer.width === width && this.buffer.height === height) {
return;
} else {
gl.deleteTexture(this.buffer.texture);
gl.deleteFramebuffer(this.buffer.framebuf);
gl.deleteRenderbuffer(this.buffer.renderbuf);
}
}
// TODO The EXT_color_buffer_float is an extension to WebGL2 and so far Safari on IOS does not seem to support it (on MacOS it works though)
// Do we really need these? Can we not quantize the depth buffer?
// Answer: No, the code has now been converted to only use integer color buffers. Moved the extension requirement to COLOR_ALPHA_DEPTH (which can probably also be converted)
var framebuf = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuf);
let attachments = this.attachments = [gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1];
let i = 0;
let createTexture = (format) => {
let t = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, t);
gl.texStorage2D(gl.TEXTURE_2D, 1, format, width, height);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.supersample !== 1 ? gl.LINEAR : gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.supersample !== 1 ? gl.LINEAR : 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);
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments[i++], gl.TEXTURE_2D, t, 0);
return t;
}
if (this.purpose === COLOR_FLOAT_DEPTH_NORMAL) {
this.attachments.push(gl.COLOR_ATTACHMENT2);
this.colorBuffer = createTexture(gl.RGBA8UI);
if (this.viewer.useFloatColorBuffer) {
this.depthFloat = createTexture(gl.R32F);
// @todo, just have depth in normalBuffer.w?
this.normalBuffer = createTexture(gl.RGBA32F);
} else {
this.depthFloat = createTexture(gl.RGBA8UI);
this.normalBuffer = createTexture(gl.RGBA8UI);
}
} else if (this.purpose === COLOR_ALPHA_DEPTH) {
// var ext = gl.getExtension('WEBGL_draw_buffers');
var ext = gl.getExtension('EXT_color_buffer_float');
if (!ext) {
throw "EXT_color_buffer_float is required";
}
this.colorBuffer = createTexture(gl.RGBA16F);
this.alphaBuffer = createTexture(gl.R16F);
} else {
throw "Unknown purpose";
}
this.depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthBuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.depthBuffer);
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);
var 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 = {framebuf: framebuf, depthBuffer: this.depthBuffer, colorBuffer: this.colorBuffer, width: width, height: height};
this.bound = false;
}
clear(depth) {
if (!this.bound) {
throw "Render buffer not bound";
}
var gl = this.gl;
gl.clear(gl.COLOR_BUFFER_BIT | ((depth === false) ? 0 : gl.DEPTH_BUFFER_BIT));
}
read(pickX, pickY) {
var x = pickX;
var y = this.canvas.height - pickY;
var pix = new Uint32Array(4);
var gl = this.gl;
gl.readBuffer(gl.COLOR_ATTACHMENT0);
gl.readPixels(x, y, 1, 1, gl.RGBA_INTEGER, gl.UNSIGNED_INT, pix);
return pix;
}
depth(pickX, pickY) {
var x = pickX;
var y = this.canvas.height - pickY;
var gl = this.gl;
gl.readBuffer(gl.COLOR_ATTACHMENT1);
if (this.viewer.useFloatColorBuffer) {
// Reading only gl.R should be sufficient, but at least on Google Chrome and Firefox on OSX, the gl.R could not be read. So that's why we are reading RGBA here
// Don't think this can hurt performance. Seems to be caused by a vague spec: https://github.com/KhronosGroup/WebGL/issues/2747
var pix = new Float32Array(4);
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.FLOAT, pix);//To review
return pix.slice(0, 1);
} else {
var pix = new Uint8Array(4);
gl.readPixels(x, y, 1, 1, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, pix);
const maxInt = 1 << 24;
var intDepth = pix[0] * (1 << 24) + pix[1] * (1 << 16) + pix[2] * (1 << 8);
var floatDepth = intDepth / maxInt;
return floatDepth;
}
}
normal(pickX, pickY) {
var x = pickX;
var y = this.canvas.height - pickY;
var gl = this.gl;
gl.readBuffer(gl.COLOR_ATTACHMENT2);
if (this.viewer.useFloatColorBuffer) {
var pix = new Float32Array(4);
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.FLOAT, pix);
return pix;
} else {
var pix = new Uint8Array(4);
gl.readPixels(x, y, 1, 1, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, pix);
var normal = new Float32Array(4);
for (var i=0; i<4; i++) {
normal[i] = pix[i] / 127;
}
return normal;
}
}
unbind() {
var gl = this.gl;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
this.bound = false;
}
destroy() {
if (this.allocated) {
var gl = this.gl;
gl.deleteTexture(this.buffer.texture);
gl.deleteFramebuffer(this.buffer.framebuf);
gl.deleteRenderbuffer(this.buffer.renderbuf);
this.allocated = false;
this.buffer = null;
this.bound = false;
}
}
}