import { assert , bitInBitfield , logIf , LogLevel } from './auxiliaries' ; import { GLclampf4 , GLsizei2 } from './tuples' ; import { Bindable } from './bindable' ; import { Context } from './context' ; import { Initializable } from './initializable' ; import { AbstractObject } from './object' ; import { Renderbuffer } from './renderbuffer' ; import { Texture2D } from './texture2d' ; todo export class Framebuffer extends AbstractObject < WebGLFramebuffer > implements Bindable { /**
     * Default framebuffer, e.g., used for unbind.
     */ static readonly DEFAULT_FRAMEBUFFER = undefined ; /**
     * Access to all attached renderbuffer objects.
     */ protected _buffersByAttachment = new Map < GLenum , Renderbuffer > ( ) ; /**
     * Access to all attached texture objects.
     */ protected _texturesByAttachment = new Map < GLenum , Texture2D > ( ) ; /**
     * RGBA color, depth value, or stencil value used for clearing the
     * associated buffer.
     */ protected _clearDepth : GLfloat ; protected _clearStencil : GLint ; protected _clearColors : Array < GLclampf4 > ; /**
     * Queue of all draw buffers that are to be cleared on `clear`.
     */ protected _colorClearQueue = new Array < GLint > ( ) ; see drawBuffers protected _drawBuffers = new Array < GLenum > ( ) ; protected _drawBuffersChanged = false ; param mask param bind param unbind param colorClearQueue clear : ( ( mask : GLbitfield , bind ? : boolean , unbind ? : boolean , colorClearQueue ? : Array < GLint > ) => void ) ; param context param status protected static statusString ( context , status ) { const gl = context . gl ; switch ( status ) { case gl . FRAMEBUFFER_COMPLETE : return 'the framebuffer is ready to display (COMPLETE)' ; case gl . FRAMEBUFFER_INCOMPLETE_ATTACHMENT : return 'the attachment types are mismatched or not all framebuffer attachment points are ' + 'framebuffer attachment complete (INCOMPLETE_ATTACHMENT)' ; case gl . FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : return 'there is no attachment (INCOMPLETE_MISSING_ATTACHMENT)' ; case gl . FRAMEBUFFER_INCOMPLETE_DIMENSIONS : return 'height and width of the attachment are not the same (INCOMPLETE_DIMENSIONS)' ; case gl . FRAMEBUFFER_UNSUPPORTED : return 'the format of the attachment is not supported or if depth and stencil attachments are not ' + 'the same renderbuffer (UNSUPPORTED)' ; case gl . FRAMEBUFFER_INCOMPLETE_MULTISAMPLE : return 'the values of gl.RENDERBUFFER_SAMPLES are different among attached renderbuffers, or are ' + 'non-zero if the attached images are a mix of renderbuffers and textures (INCOMPLETE_MULTISAMPLE)' ; default : assert ( false , `expected known framebuffer status, given ${ status }` ) ; return '' ; } } param attachments protected create ( attachments ) { const gl = this . _context . gl ; const gl2facade = this . context . gl2facade ; this . _object = gl . createFramebuffer ( ) ; this . _clearColors = new Array < GLclampf4 > ( gl2facade . COLOR_ATTACHMENT_MAX - gl2facade . COLOR_ATTACHMENT0 ) ; this . _clearDepth = 1.0 ; this . _clearStencil = 0 ; this . clear = this . context . isWebGL1 ? this . es2Clear : this . es3Clear ; for ( const tuple of attachments ) { const attachment = tuple [ 0 ] ; const bufferOrTexture = tuple [ 1 ] ; if ( bufferOrTexture instanceof Renderbuffer ) { this . _buffersByAttachment . set ( attachment , bufferOrTexture as Renderbuffer ) ; } else if ( bufferOrTexture instanceof Texture2D ) { this . _texturesByAttachment . set ( attachment , bufferOrTexture as Texture2D ) ; } if ( attachment < gl2facade . COLOR_ATTACHMENT_MIN || attachment > gl2facade . COLOR_ATTACHMENT_MAX ) { continue ; } this . _drawBuffers . push ( attachment ) ; const index = attachment - gl . COLOR_ATTACHMENT0 ; this . _colorClearQueue . push ( index as GLint ) ; this . _clearColors [ index ] = [ 0.0 , 0.0 , 0.0 , 0.0 ] ; } this . _drawBuffersChanged = true ; gl . bindFramebuffer ( gl . FRAMEBUFFER , this . _object ) ; this . _buffersByAttachment . forEach ( ( buffer , attachment ) => { gl . framebufferRenderbuffer ( gl . FRAMEBUFFER , attachment , gl . RENDERBUFFER , buffer . object ) ; } ) ; this . _texturesByAttachment . forEach ( ( texture , attachment ) => { gl . framebufferTexture2D ( gl . FRAMEBUFFER , attachment , gl . TEXTURE_2D , texture . object , 0 ) ; } ) ; if ( gl2facade . drawBuffers ) { gl2facade . drawBuffers ( this . _drawBuffers ) ; } const status = gl . checkFramebufferStatus ( gl . FRAMEBUFFER ) ; this . _valid = gl . isFramebuffer ( this . _object ) && ( status === gl . FRAMEBUFFER_COMPLETE ) ; logIf ( ! this . _valid , LogLevel . Warning , Framebuffer . statusString ( this . context , status ) ) ; gl . bindFramebuffer ( gl . FRAMEBUFFER , Framebuffer . DEFAULT_FRAMEBUFFER ) ; return this . _object ; } /**
     * Delete the framebuffer object on the GPU. This should have the reverse effect of `create`.
     */ protected delete ( ) { assert ( this . _object instanceof WebGLFramebuffer , `expected WebGLFramebuffer object` ) ; this . context . gl . deleteFramebuffer ( this . _object ) ; this . _object = undefined ; this . _valid = false ; } param mask param bind param unbind @ Initializable . assert_initialized ( ) protected es2Clear ( mask , bind = true , unbind = true ) { const gl = this . context . gl ; const clearDepth = bitInBitfield ( mask , gl . DEPTH_BUFFER_BIT ) ; const clearStencil = bitInBitfield ( mask , gl . STENCIL_BUFFER_BIT ) ; const clearColor = bitInBitfield ( mask , gl . COLOR_BUFFER_BIT ) ; if ( ! clearColor && ! clearDepth && ! clearStencil ) { return ; } if ( bind ) { this . bind ( ) ; } if ( clearColor && this . _clearColors [ 0 ] !== undefined ) { const color = this . _clearColors [ 0 ] ; gl . clearColor ( color [ 0 ] , color [ 1 ] , color [ 2 ] , color [ 3 ] ) ; } if ( clearDepth && this . _clearDepth !== undefined ) { gl . clearDepth ( this . _clearDepth ) ; } if ( clearStencil && this . _clearStencil !== undefined ) { gl . clearStencil ( this . _clearStencil ) ; } gl . clear ( mask ) ; if ( unbind ) { this . unbind ( ) ; } } param mask param bind param unbind param colorClearQueue @ Initializable . assert_initialized ( ) protected es3Clear ( mask , bind = true , unbind = true , colorClearQueue ? ) { const gl = this . context . gl ; const clearDepth = bitInBitfield ( mask , gl . DEPTH_BUFFER_BIT ) ; const clearStencil = bitInBitfield ( mask , gl . STENCIL_BUFFER_BIT ) ; const clearColor = bitInBitfield ( mask , gl . COLOR_BUFFER_BIT ) ; if ( ! clearColor && ! clearDepth && ! clearStencil ) { return ; } if ( bind ) { this . bind ( ) ; } if ( clearColor ) { const isChrome = ! ! ( window as any ) [ 'chrome' ] && ( ! ! ( window as any ) [ 'chrome' ] [ 'webstore' ] || ! ! ( window as any ) [ 'chrome' ] [ 'runtime' ] ) ; if ( isChrome ) { /**
                 * Unfortunately, the clearBufferfv doesn't work in Chrome (symptom: ID buffer is not cleared), so
                 * clear every color buffer with the first clear color.
                 */ const color = this . _clearColors [ 0 ] ? this . _clearColors [ 0 ] : [ 0.0 , 0.0 , 0.0 , 1.0 ] ; gl . clearColor ( color [ 0 ] , color [ 1 ] , color [ 2 ] , color [ 3 ] ) ; gl . clear ( gl . COLOR_BUFFER_BIT ) ; } /**
             * Since for chrome, the above code cleared every color buffer with the same color, we clear again, but now
             * with the corresponding color.
             * NOTE: In chrome, we are double-clearing here. Be aware that if we remove the code above, some color
             * bufferes are not cleared at all, while using ONLY the above code would clear them with the wrong color.
             */ for ( const drawBuffer of colorClearQueue ? colorClearQueue : this . _colorClearQueue ) { gl . clearBufferfv ( gl . COLOR , drawBuffer , this . _clearColors [ drawBuffer ] ) ; } } if ( clearDepth && clearStencil ) { /**
             * Unfortunately, the following code doesn't work, on Intel HD, -> fallback to WebGL 1 interface for
             * clearing depth and stencil.
             *
             * gl.clearBufferfi(gl.DEPTH_STENCIL, 0, this._clearDepth, this._clearStencil);
             */ gl . clearStencil ( this . _clearStencil ) ; gl . clearDepth ( this . _clearDepth ) ; gl . clear ( gl . STENCIL_BUFFER_BIT | gl . DEPTH_BUFFER_BIT ) ; } else if ( clearDepth ) { gl . clearBufferfv ( gl . DEPTH , 0 , [ this . _clearDepth ] ) ; } else if ( clearStencil ) { /**
             * Unfortunately, the following code doesn't work, at least in Chrome, -> fallback to WebGL 1 interface
             * for clearing depth and stencil.
             *
             * gl.clearBufferiv(gl.STENCIL, 0, [this._clearStencil]);
             */ gl . clearStencil ( this . _clearStencil ) ; gl . clear ( gl . STENCIL_BUFFER_BIT ) ; } if ( unbind ) { this . unbind ( ) ; } } param attachment protected hasAttachment ( attachment ) { return this . _texturesByAttachment . has ( attachment ) || this . _buffersByAttachment . has ( attachment ) ; } param target @ Initializable . assert_initialized ( ) bind ( target = this . context . gl . FRAMEBUFFER ) { this . context . gl . bindFramebuffer ( target , this . _object ) ; if ( this . _drawBuffersChanged && this . context . gl2facade . drawBuffers ) { this . context . gl2facade . drawBuffers ( this . _drawBuffers ) ; this . _drawBuffersChanged = false ; } } param target @ Initializable . assert_initialized ( ) unbind ( target = this . context . gl . FRAMEBUFFER ) { this . context . gl . bindFramebuffer ( target , Framebuffer . DEFAULT_FRAMEBUFFER ) ; } param color param drawBuffer @ Initializable . assert_initialized ( ) clearColor ( color , drawBuffer ? ) { assert ( drawBuffer === undefined || drawBuffer === 0 || this . context . isWebGL2 || this . context . supportsDrawBuffers , `WebGL2 context expected for clearing multiple color attachments.` ) ; const alphaIssue = color [ 3 ] < 1.0 && ! this . context . alpha ; logIf ( alphaIssue , LogLevel . Warning , `context has alpha disabled, clear color alpha is ignored` ) ; const color2 = [ color [ 0 ] , color [ 1 ] , color [ 2 ] , alphaIssue ? 1.0 : color [ 3 ] ] ; if ( this . context . premultipliedAlpha && ! alphaIssue ) { color2 [ 0 ] *= color2 [ 3 ] ; color2 [ 1 ] *= color2 [ 3 ] ; color2 [ 2 ] *= color2 [ 3 ] ; } if ( drawBuffer !== undefined ) { this . _clearColors [ drawBuffer ] = color2 ; } else { for ( let i = 0 ; i < this . _clearColors . length ; ++ i ) { this . _clearColors [ i ] = color2 ; } } } @ Initializable . assert_initialized ( ) clearDepth ( depth ) { this . _clearDepth = depth ; } @ Initializable . assert_initialized ( ) clearStencil ( stencil ) { this . _clearStencil = stencil ; } param attachment returns @ Initializable . assert_initialized ( ) texture ( attachment ) { return this . _texturesByAttachment . get ( attachment ) ; } param width param height param bind param unbind @ Initializable . assert_initialized ( ) resize ( width , height , bind = true , unbind = true ) { this . _buffersByAttachment . forEach ( ( buffer ) => { buffer . resize ( width , height , bind , unbind ) ; } ) ; this . _texturesByAttachment . forEach ( ( texture ) => { texture . resize ( width , height , bind , unbind ) ; } ) ; } returns get width ( ) : GLsizei { this . assertInitialized ( ) ; let width = NaN ; this . _buffersByAttachment . forEach ( ( buffer ) => { if ( isNaN ( width ) || buffer . width < width ) { width = buffer . width ; } } ) ; this . _texturesByAttachment . forEach ( ( texture ) => { if ( isNaN ( width ) || texture . width < width ) { width = texture . width ; } } ) ; return width ; } returns get height ( ) : GLsizei { this . assertInitialized ( ) ; let height = NaN ; this . _buffersByAttachment . forEach ( ( buffer ) => { if ( isNaN ( height ) || buffer . height < height ) { height = buffer . height ; } } ) ; this . _texturesByAttachment . forEach ( ( texture ) => { if ( isNaN ( height ) || texture . height < height ) { height = texture . height ; } } ) ; return height ; } see width see heigth get size ( ) : GLsizei2 { return [ this . width , this . height ] ; } param attachments set drawBuffers ( attachments ) { this . assertInitialized ( ) ; const gl2facade = this . context . gl2facade ; for ( const attachment of attachments ) { assert ( this . hasAttachment ( attachment ) , `valid attachment expected for draw buffer, given ${ attachment }` ) ; assert ( attachment >= gl2facade . COLOR_ATTACHMENT_MIN && attachment <= gl2facade . COLOR_ATTACHMENT_MAX , `color attachment expected for draw buffer, given ${ attachment }` ) ; this . _drawBuffersChanged = this . _drawBuffersChanged || this . _drawBuffers . indexOf ( attachment ) === - 1 ; } this . _drawBuffersChanged = this . _drawBuffersChanged || attachments . length !== this . _drawBuffers . length ; if ( this . _drawBuffersChanged ) { this . _drawBuffers = attachments ; } } /**
     * Used to remember which attachments are available as potential draw buffers.
     */ get drawBuffers ( ) : Array < GLenum > { this . assertInitialized ( ) ; return this . _drawBuffers ; } }