import Check from '../Core/Check.js'; import defaultValue from '../Core/defaultValue.js'; import defined from '../Core/defined.js'; import defineProperties from '../Core/defineProperties.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) { var gl = framebuffer._gl; gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, texture._target, texture._texture, 0); } function attachRenderbuffer(framebuffer, attachment, renderbuffer) { var 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, and depthStencilRenderbuffer. * * @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. * var width = context.canvas.clientWidth; * var height = context.canvas.clientHeight; * var 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 */ function Framebuffer(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var context = options.context; //>>includeStart('debug', pragmas.debug); Check.defined('options.context', context); //>>includeEnd('debug'); var gl = context._gl; var 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 var depthAttachment = (defined(options.depthTexture) || defined(options.depthRenderbuffer)); var 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(); var texture; var renderbuffer; var i; var length; var attachmentEnum; if (defined(options.colorTextures)) { var 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)) { var 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(); } 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(); var 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() { var gl = this._gl; gl.bindFramebuffer(gl.FRAMEBUFFER, this._framebuffer); }; Framebuffer.prototype._unBind = function() { var gl = this._gl; gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; 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. var i = 0; var textures = this._colorTextures; var length = textures.length; for (; i < length; ++i) { var texture = textures[i]; if (defined(texture)) { texture.destroy(); } } var renderbuffers = this._colorRenderbuffers; length = renderbuffers.length; for (i = 0; i < length; ++i) { var 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;