import Check from "../Core/Check.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import PixelFormat from "../Core/PixelFormat.js"; import ContextLimits from "./ContextLimits.js"; import PixelDatatype from "./PixelDatatype.js"; function attachTexture(framebuffer, attachment, texture) { const gl = framebuffer._gl; gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment, texture._target, texture._texture, 0 ); } function attachRenderbuffer(framebuffer, attachment, renderbuffer) { const gl = framebuffer._gl; gl.framebufferRenderbuffer( gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, renderbuffer._getRenderbuffer() ); } /** * Creates a framebuffer with optional initial color, depth, and stencil attachments. * Framebuffers are used for render-to-texture effects; they allow us to render to * textures in one pass, and read from it in a later pass. * * @param {Object} options The initial framebuffer attachments as shown in the example below. context is required. The possible properties are colorTextures, colorRenderbuffers, depthTexture, depthRenderbuffer, stencilRenderbuffer, depthStencilTexture, depthStencilRenderbuffer, and destroyAttachments. * * @exception {DeveloperError} Cannot have both color texture and color renderbuffer attachments. * @exception {DeveloperError} Cannot have both a depth texture and depth renderbuffer attachment. * @exception {DeveloperError} Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment. * @exception {DeveloperError} Cannot have both a depth and depth-stencil renderbuffer. * @exception {DeveloperError} Cannot have both a stencil and depth-stencil renderbuffer. * @exception {DeveloperError} Cannot have both a depth and stencil renderbuffer. * @exception {DeveloperError} The color-texture pixel-format must be a color format. * @exception {DeveloperError} The depth-texture pixel-format must be DEPTH_COMPONENT. * @exception {DeveloperError} The depth-stencil-texture pixel-format must be DEPTH_STENCIL. * @exception {DeveloperError} The number of color attachments exceeds the number supported. * @exception {DeveloperError} The color-texture pixel datatype is HALF_FLOAT and the WebGL implementation does not support the EXT_color_buffer_half_float extension. * @exception {DeveloperError} The color-texture pixel datatype is FLOAT and the WebGL implementation does not support the EXT_color_buffer_float or WEBGL_color_buffer_float extensions. * * @example * // Create a framebuffer with color and depth texture attachments. * const width = context.canvas.clientWidth; * const height = context.canvas.clientHeight; * const framebuffer = new Framebuffer({ * context : context, * colorTextures : [new Texture({ * context : context, * width : width, * height : height, * pixelFormat : PixelFormat.RGBA * })], * depthTexture : new Texture({ * context : context, * width : width, * height : height, * pixelFormat : PixelFormat.DEPTH_COMPONENT, * pixelDatatype : PixelDatatype.UNSIGNED_SHORT * }) * }); * * @private * @constructor */ function Framebuffer(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); const context = options.context; //>>includeStart('debug', pragmas.debug); Check.defined("options.context", context); //>>includeEnd('debug'); const gl = context._gl; const maximumColorAttachments = ContextLimits.maximumColorAttachments; this._gl = gl; this._framebuffer = gl.createFramebuffer(); this._colorTextures = []; this._colorRenderbuffers = []; this._activeColorAttachments = []; this._depthTexture = undefined; this._depthRenderbuffer = undefined; this._stencilRenderbuffer = undefined; this._depthStencilTexture = undefined; this._depthStencilRenderbuffer = undefined; /** * When true, the framebuffer owns its attachments so they will be destroyed when * {@link Framebuffer#destroy} is called or when a new attachment is assigned * to an attachment point. * * @type {Boolean} * @default true * * @see Framebuffer#destroy */ this.destroyAttachments = defaultValue(options.destroyAttachments, true); // Throw if a texture and renderbuffer are attached to the same point. This won't // cause a WebGL error (because only one will be attached), but is likely a developer error. //>>includeStart('debug', pragmas.debug); if (defined(options.colorTextures) && defined(options.colorRenderbuffers)) { throw new DeveloperError( "Cannot have both color texture and color renderbuffer attachments." ); } if (defined(options.depthTexture) && defined(options.depthRenderbuffer)) { throw new DeveloperError( "Cannot have both a depth texture and depth renderbuffer attachment." ); } if ( defined(options.depthStencilTexture) && defined(options.depthStencilRenderbuffer) ) { throw new DeveloperError( "Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment." ); } //>>includeEnd('debug'); // Avoid errors defined in Section 6.5 of the WebGL spec const depthAttachment = defined(options.depthTexture) || defined(options.depthRenderbuffer); const depthStencilAttachment = defined(options.depthStencilTexture) || defined(options.depthStencilRenderbuffer); //>>includeStart('debug', pragmas.debug); if (depthAttachment && depthStencilAttachment) { throw new DeveloperError( "Cannot have both a depth and depth-stencil attachment." ); } if (defined(options.stencilRenderbuffer) && depthStencilAttachment) { throw new DeveloperError( "Cannot have both a stencil and depth-stencil attachment." ); } if (depthAttachment && defined(options.stencilRenderbuffer)) { throw new DeveloperError( "Cannot have both a depth and stencil attachment." ); } //>>includeEnd('debug'); /////////////////////////////////////////////////////////////////// this._bind(); let texture; let renderbuffer; let i; let length; let attachmentEnum; if (defined(options.colorTextures)) { const textures = options.colorTextures; length = this._colorTextures.length = this._activeColorAttachments.length = textures.length; //>>includeStart('debug', pragmas.debug); if (length > maximumColorAttachments) { throw new DeveloperError( "The number of color attachments exceeds the number supported." ); } //>>includeEnd('debug'); for (i = 0; i < length; ++i) { texture = textures[i]; //>>includeStart('debug', pragmas.debug); if (!PixelFormat.isColorFormat(texture.pixelFormat)) { throw new DeveloperError( "The color-texture pixel-format must be a color format." ); } if ( texture.pixelDatatype === PixelDatatype.FLOAT && !context.colorBufferFloat ) { throw new DeveloperError( "The color texture pixel datatype is FLOAT and the WebGL implementation does not support the EXT_color_buffer_float or WEBGL_color_buffer_float extensions. See Context.colorBufferFloat." ); } if ( texture.pixelDatatype === PixelDatatype.HALF_FLOAT && !context.colorBufferHalfFloat ) { throw new DeveloperError( "The color texture pixel datatype is HALF_FLOAT and the WebGL implementation does not support the EXT_color_buffer_half_float extension. See Context.colorBufferHalfFloat." ); } //>>includeEnd('debug'); attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i; attachTexture(this, attachmentEnum, texture); this._activeColorAttachments[i] = attachmentEnum; this._colorTextures[i] = texture; } } if (defined(options.colorRenderbuffers)) { const renderbuffers = options.colorRenderbuffers; length = this._colorRenderbuffers.length = this._activeColorAttachments.length = renderbuffers.length; //>>includeStart('debug', pragmas.debug); if (length > maximumColorAttachments) { throw new DeveloperError( "The number of color attachments exceeds the number supported." ); } //>>includeEnd('debug'); for (i = 0; i < length; ++i) { renderbuffer = renderbuffers[i]; attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i; attachRenderbuffer(this, attachmentEnum, renderbuffer); this._activeColorAttachments[i] = attachmentEnum; this._colorRenderbuffers[i] = renderbuffer; } } if (defined(options.depthTexture)) { texture = options.depthTexture; //>>includeStart('debug', pragmas.debug); if (texture.pixelFormat !== PixelFormat.DEPTH_COMPONENT) { throw new DeveloperError( "The depth-texture pixel-format must be DEPTH_COMPONENT." ); } //>>includeEnd('debug'); attachTexture(this, this._gl.DEPTH_ATTACHMENT, texture); this._depthTexture = texture; } if (defined(options.depthRenderbuffer)) { renderbuffer = options.depthRenderbuffer; attachRenderbuffer(this, this._gl.DEPTH_ATTACHMENT, renderbuffer); this._depthRenderbuffer = renderbuffer; } if (defined(options.stencilRenderbuffer)) { renderbuffer = options.stencilRenderbuffer; attachRenderbuffer(this, this._gl.STENCIL_ATTACHMENT, renderbuffer); this._stencilRenderbuffer = renderbuffer; } if (defined(options.depthStencilTexture)) { texture = options.depthStencilTexture; //>>includeStart('debug', pragmas.debug); if (texture.pixelFormat !== PixelFormat.DEPTH_STENCIL) { throw new DeveloperError( "The depth-stencil pixel-format must be DEPTH_STENCIL." ); } //>>includeEnd('debug'); attachTexture(this, this._gl.DEPTH_STENCIL_ATTACHMENT, texture); this._depthStencilTexture = texture; } if (defined(options.depthStencilRenderbuffer)) { renderbuffer = options.depthStencilRenderbuffer; attachRenderbuffer(this, this._gl.DEPTH_STENCIL_ATTACHMENT, renderbuffer); this._depthStencilRenderbuffer = renderbuffer; } this._unBind(); } Object.defineProperties(Framebuffer.prototype, { /** * The status of the framebuffer. If the status is not WebGLConstants.FRAMEBUFFER_COMPLETE, * a {@link DeveloperError} will be thrown when attempting to render to the framebuffer. * @memberof Framebuffer.prototype * @type {Number} */ status: { get: function () { this._bind(); const status = this._gl.checkFramebufferStatus(this._gl.FRAMEBUFFER); this._unBind(); return status; }, }, numberOfColorAttachments: { get: function () { return this._activeColorAttachments.length; }, }, depthTexture: { get: function () { return this._depthTexture; }, }, depthRenderbuffer: { get: function () { return this._depthRenderbuffer; }, }, stencilRenderbuffer: { get: function () { return this._stencilRenderbuffer; }, }, depthStencilTexture: { get: function () { return this._depthStencilTexture; }, }, depthStencilRenderbuffer: { get: function () { return this._depthStencilRenderbuffer; }, }, /** * True if the framebuffer has a depth attachment. Depth attachments include * depth and depth-stencil textures, and depth and depth-stencil renderbuffers. When * rendering to a framebuffer, a depth attachment is required for the depth test to have effect. * @memberof Framebuffer.prototype * @type {Boolean} */ hasDepthAttachment: { get: function () { return !!( this.depthTexture || this.depthRenderbuffer || this.depthStencilTexture || this.depthStencilRenderbuffer ); }, }, }); Framebuffer.prototype._bind = function () { const gl = this._gl; gl.bindFramebuffer(gl.FRAMEBUFFER, this._framebuffer); }; Framebuffer.prototype._unBind = function () { const gl = this._gl; gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; Framebuffer.prototype.bindDraw = function () { const gl = this._gl; gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this._framebuffer); }; Framebuffer.prototype.bindRead = function () { const gl = this._gl; gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this._framebuffer); }; Framebuffer.prototype._getActiveColorAttachments = function () { return this._activeColorAttachments; }; Framebuffer.prototype.getColorTexture = function (index) { //>>includeStart('debug', pragmas.debug); if (!defined(index) || index < 0 || index >= this._colorTextures.length) { throw new DeveloperError( "index is required, must be greater than or equal to zero and must be less than the number of color attachments." ); } //>>includeEnd('debug'); return this._colorTextures[index]; }; Framebuffer.prototype.getColorRenderbuffer = function (index) { //>>includeStart('debug', pragmas.debug); if ( !defined(index) || index < 0 || index >= this._colorRenderbuffers.length ) { throw new DeveloperError( "index is required, must be greater than or equal to zero and must be less than the number of color attachments." ); } //>>includeEnd('debug'); return this._colorRenderbuffers[index]; }; Framebuffer.prototype.isDestroyed = function () { return false; }; Framebuffer.prototype.destroy = function () { if (this.destroyAttachments) { // If the color texture is a cube map face, it is owned by the cube map, and will not be destroyed. let i = 0; const textures = this._colorTextures; let length = textures.length; for (; i < length; ++i) { const texture = textures[i]; if (defined(texture)) { texture.destroy(); } } const renderbuffers = this._colorRenderbuffers; length = renderbuffers.length; for (i = 0; i < length; ++i) { const renderbuffer = renderbuffers[i]; if (defined(renderbuffer)) { renderbuffer.destroy(); } } this._depthTexture = this._depthTexture && this._depthTexture.destroy(); this._depthRenderbuffer = this._depthRenderbuffer && this._depthRenderbuffer.destroy(); this._stencilRenderbuffer = this._stencilRenderbuffer && this._stencilRenderbuffer.destroy(); this._depthStencilTexture = this._depthStencilTexture && this._depthStencilTexture.destroy(); this._depthStencilRenderbuffer = this._depthStencilRenderbuffer && this._depthStencilRenderbuffer.destroy(); } this._gl.deleteFramebuffer(this._framebuffer); return destroyObject(this); }; export default Framebuffer;