pex-context
Version:
Modern WebGL state wrapper for PEX: allocate GPU resources (textures, buffers), setup state pipelines and passes, and combine them into commands.
147 lines (127 loc) • 3.64 kB
JavaScript
import { NAMESPACE } from "./utils.js";
/**
* @typedef {object} Attachment
* @property {import("./types.js").PexResource} texture
* @property {WebGLRenderingContext.FRAMEBUFFER} target
*/
function createFramebuffer(ctx, opts) {
const gl = ctx.gl;
const framebuffer = {
class: "framebuffer",
handle: gl.createFramebuffer(),
target: gl.FRAMEBUFFER,
drawBuffers: [],
color: [],
depth: null,
width: 0,
height: 0,
refCount: 0,
_update: updateFramebuffer,
_dispose() {
gl.deleteFramebuffer(this.handle);
this.color = null;
this.depth = null;
},
};
if (opts.color || opts.depth) {
updateFramebuffer(ctx, framebuffer, opts);
}
return framebuffer;
}
// opts = { color: [texture] }
// opts = { color: [texture], depth }
// opts = { color: [{texture, target}], depth }
function updateFramebuffer(ctx, framebuffer, opts) {
const gl = ctx.gl;
// TODO: if color.length > 1 check for WebGL2 or gl.getExtension('WEBGL_draw_buffers')
framebuffer.color = opts.color.map((attachment) => {
const colorAttachment = attachment.texture
? attachment
: { texture: attachment };
colorAttachment.level = 0; // we can't render to mipmap level other than 0 in webgl
if (!colorAttachment.target) {
colorAttachment.target = colorAttachment.texture.target;
}
return colorAttachment;
});
framebuffer.depth = opts.depth
? opts.depth.texture
? opts.depth
: { texture: opts.depth }
: null;
framebuffer.width = framebuffer.color[0].texture.width;
framebuffer.height = framebuffer.color[0].texture.height;
// TODO: ctx push framebuffer
gl.bindFramebuffer(framebuffer.target, framebuffer.handle);
framebuffer.drawBuffers.length = 0;
framebuffer.color.forEach((colorAttachment, i) => {
framebuffer.drawBuffers.push(gl.COLOR_ATTACHMENT0 + i);
gl.framebufferTexture2D(
framebuffer.target,
gl.COLOR_ATTACHMENT0 + i,
colorAttachment.target,
colorAttachment.texture.handle,
colorAttachment.level,
);
});
for (
let i = framebuffer.color.length;
i < ctx.capabilities.maxColorAttachments;
i++
) {
gl.framebufferTexture2D(
framebuffer.target,
gl.COLOR_ATTACHMENT0 + i,
gl.TEXTURE_2D,
null,
0,
);
}
if (framebuffer.depth) {
if (ctx.debugMode) {
console.debug(NAMESPACE, "fbo attaching depth", framebuffer.depth);
}
const depthAttachment = framebuffer.depth;
if (depthAttachment.texture.target === gl.RENDERBUFFER) {
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.RENDERBUFFER,
depthAttachment.texture.handle,
);
} else {
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
depthAttachment.texture.target,
depthAttachment.texture.handle,
depthAttachment.level,
);
}
} else {
if (ctx.debugMode) console.debug(NAMESPACE, "fbo deattaching depth");
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER,
gl.DEPTH_ATTACHMENT,
gl.RENDERBUFFER,
null,
);
gl.framebufferTexture2D(
framebuffer.target,
gl.DEPTH_ATTACHMENT,
gl.TEXTURE_2D,
null,
0,
);
}
if (ctx.debugMode) {
const fboStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
console.assert(
fboStatus === gl.FRAMEBUFFER_COMPLETE,
`FBO incomplete ${ctx.getGLString(fboStatus)}`,
);
}
// TODO: ctx. pop framebuffer
gl.bindFramebuffer(framebuffer.target, null);
}
export default createFramebuffer;