// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { Framebuffer, assert } from '@luma.gl/core'; import { GL } from '@luma.gl/constants'; import { WEBGLTexture } from "./webgl-texture.js"; import { WEBGLTextureView } from "./webgl-texture-view.js"; import { getDepthStencilAttachmentWebGL } from "../converters/texture-formats.js"; /** luma.gl Framebuffer, WebGL implementation */ export class WEBGLFramebuffer extends Framebuffer { device; gl; handle; get texture() { return this.colorAttachments[0]; } constructor(device, props) { super(device, props); // WebGL default framebuffer handle is null const isDefaultFramebuffer = props.handle === null; this.device = device; this.gl = device.gl; this.handle = this.props.handle || isDefaultFramebuffer ? this.props.handle : this.gl.createFramebuffer(); if (!isDefaultFramebuffer) { // default framebuffer handle is null, so we can't set spector metadata... device.setSpectorMetadata(this.handle, { id: this.props.id, props: this.props }); // Auto create textures for attachments if needed this.autoCreateAttachmentTextures(); /** Attach from a map of attachments */ // @ts-expect-error native bindFramebuffer is overridden by our state tracker const prevHandle = this.gl.bindFramebuffer(36160, this.handle); // Walk the attachments for (let i = 0; i < this.colorAttachments.length; ++i) { const attachment = this.colorAttachments[i]; const attachmentPoint = 36064 + i; if (attachment) { this._attachOne(attachmentPoint, attachment); } } if (this.depthStencilAttachment) { this._attachOne(getDepthStencilAttachmentWebGL(this.depthStencilAttachment.props.format), this.depthStencilAttachment); } /** Check the status */ // @ts-expect-error if (props.check !== false) { const status = this.gl.checkFramebufferStatus(36160); if (status !== 36053) { throw new Error(`Framebuffer ${_getFrameBufferStatus(status)}`); } } this.gl.bindFramebuffer(36160, prevHandle); } } /** destroys any auto created resources etc. */ destroy() { super.destroy(); // destroys owned resources etc. if (!this.destroyed && this.handle !== null) { this.gl.deleteFramebuffer(this.handle); // this.handle = null; } } // PRIVATE /** In WebGL we must use renderbuffers for depth/stencil attachments (unless we have extensions) */ createDepthStencilTexture(format) { // return new WEBGLRenderbuffer(this.device, { return new WEBGLTexture(this.device, { id: `${this.id}-depth-stencil`, format, width: this.width, height: this.height, mipmaps: false }); } /** * Attachment resize is expected to be a noop if size is same */ resizeAttachments(width, height) { // for default framebuffer, just update the stored size if (this.handle === null) { // assert(width === undefined && height === undefined); this.width = this.gl.drawingBufferWidth; this.height = this.gl.drawingBufferHeight; return this; } if (width === undefined) { width = this.gl.drawingBufferWidth; } if (height === undefined) { height = this.gl.drawingBufferHeight; } // TODO Not clear that this is better than default destroy/create implementation for (const colorAttachment of this.colorAttachments) { colorAttachment.texture.resize({ width, height }); } if (this.depthStencilAttachment) { this.depthStencilAttachment.texture.resize({ width, height }); } return this; } /** Attach one attachment */ _attachOne(attachmentPoint, attachment) { // if (attachment instanceof WEBGLRenderbuffer) { // this._attachWEBGLRenderbuffer(attachmentPoint, attachment); // return attachment; // } if (Array.isArray(attachment)) { const [texture, layer = 0, level = 0] = attachment; this._attachTexture(attachmentPoint, texture, layer, level); return texture; } if (attachment instanceof WEBGLTexture) { this._attachTexture(attachmentPoint, attachment, 0, 0); return attachment; } if (attachment instanceof WEBGLTextureView) { const textureView = attachment; this._attachTexture(attachmentPoint, textureView.texture, textureView.props.baseMipLevel, textureView.props.baseArrayLayer); return attachment.texture; } throw new Error('attach'); } // TODO - we do not seem to need render buffers in WebGL 2 // protected _attachWEBGLRenderbuffer(attachment: GL, renderbuffer: WEBGLRenderbuffer): void { // this.gl.framebufferRenderbuffer( // GL.FRAMEBUFFER, // attachment, // GL.RENDERBUFFER, // renderbuffer.handle // ); // } /** * @param attachment * @param texture * @param layer = 0 - index into WEBGLTextureArray and Texture3D or face for `TextureCubeMap` * @param level = 0 - mipmapLevel */ _attachTexture(attachment, texture, layer, level) { const { gl } = this.device; gl.bindTexture(texture.target, texture.handle); switch (texture.target) { case 35866: case 32879: gl.framebufferTextureLayer(36160, attachment, texture.target, level, layer); break; case 34067: // layer must be a cubemap face (or if index, converted to cube map face) const face = mapIndexToCubeMapFace(layer); gl.framebufferTexture2D(36160, attachment, face, texture.handle, level); break; case 3553: gl.framebufferTexture2D(36160, attachment, 3553, texture.handle, level); break; default: assert(false, 'Illegal texture type'); } gl.bindTexture(texture.target, null); } } // Helper functions // Map an index to a cube map face constant function mapIndexToCubeMapFace(layer) { // TEXTURE_CUBE_MAP_POSITIVE_X is a big value (0x8515) // if smaller assume layer is index, otherwise assume it is already a cube map face constant return layer < 34069 ? layer + 34069 : layer; } // Helper METHODS // Get a string describing the framebuffer error if installed function _getFrameBufferStatus(status) { switch (status) { case 36053: return 'success'; case 36054: return 'Mismatched attachments'; case 36055: return 'No attachments'; case 36057: return 'Height/width mismatch'; case 36061: return 'Unsupported or split attachments'; // WebGL2 case 36182: return 'Samples mismatch'; // OVR_multiview2 extension // case GL.FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR: return 'baseViewIndex mismatch'; default: return `${status}`; } }