import { assert , log , logIf , LogLevel } from './auxiliaries' ; import { GLsizei2 } from './tuples' ; import { ChangeLookup } from './changelookup' ; import { Context } from './context' ; import { Framebuffer } from './framebuffer' ; import { Initializable } from './initializable' ; import { NdcFillingTriangle } from './ndcfillingtriangle' ; import { Program } from './program' ; import { Shader } from './shader' ; import { Texture2D } from './texture2d' ; import { Wizard } from './wizard' ; /**
 * This pass accumulates the color attachment 0 of a framebuffer, e.g., the result of an intermediate frame, into an
 * accumulation buffer. For accumulation the frame number is used to derive the accumulation weight. For rendering to
 * texture, a textured ndc-filling triangle is used.
 *
 * The accumulation pass can be used as follows:
 * ```
 * this.accumulate.initialize();
 * this.accumulate.texture = this.intermediateFBO.texture(gl2facade.COLOR_ATTACHMENT0);
 * this.accumulate.update();
 * this.accumulate.frame(frameNumber);
 * ```
 */ export class AccumulatePass extends Initializable { /**
     * Read-only access to the objects context, used to get context information and WebGL API access.
     */ protected _context : Context ; /**
     * Alterable auxiliary object for tracking changes on this object's input and lazy updates.
     */ protected readonly _altered = Object . assign ( new ChangeLookup ( ) , { any : false , texture : false , precision : false , passThrough : false , } ) ; see texture protected _texture : Texture2D ; see precision protected _precision : Wizard . Precision = Wizard . Precision . half ; see passThrough protected _passThrough : boolean ; /**
     * Two rgba-framebuffers used for accumulation (buffer ping-ponging is used for alternating the buffers for read
     * and write access due to a limitation in WebGL).
     */ protected _accumulationFBOs : [ Framebuffer , Framebuffer ] ; protected _accumulationTextures : [ Texture2D , Texture2D ] ; /**
     * Stores the index of the last buffer written to.
     */ protected _write : GLuint = 0 ; /**
     * Geometry used to draw on. This is not provided by default to allow for geometry sharing. If no triangle is given,
     * the ndc triangle will be created and managed internally.
     */ protected _ndcTriangle : NdcFillingTriangle ; /**
     * Tracks ownership of the ndc-filling triangle.
     */ protected _ndcTriangleShared = false ; protected _program : Program ; protected _uWeight : WebGLUniformLocation ; constructor ( context ) { super ( ) ; this . _context = context ; } param ndcTriangle @ Initializable . initialize ( ) initialize ( ndcTriangle ? ) { const gl = this . _context . gl ; this . _accumulationFBOs = [ new Framebuffer ( this . _context , 'AccumPingFBO' ) , new Framebuffer ( this . _context , 'AccumPongFBO' ) ] ; this . _accumulationTextures = [ new Texture2D ( this . _context , 'AccumPingTexture' ) , new Texture2D ( this . _context , 'AccumPongTexture' ) ] ; if ( ndcTriangle === undefined ) { this . _ndcTriangle = new NdcFillingTriangle ( this . _context , 'NdcFillingTriangle-Accumulate' ) ; } else { this . _ndcTriangle = ndcTriangle ; this . _ndcTriangleShared = true ; } const vert = new Shader ( this . _context , gl . VERTEX_SHADER , 'ndcvertices.vert (accumulate)' ) ; vert . initialize ( require ( './shaders/ndcvertices.vert' ) ) ; const frag = new Shader ( this . _context , gl . FRAGMENT_SHADER , 'accumulate.frag' ) ; frag . initialize ( require ( './shaders/accumulate.frag' ) ) ; this . _program = new Program ( this . _context , 'AccumulateProgram' ) ; this . _program . initialize ( [ vert , frag ] , false ) ; if ( ! this . _ndcTriangle . initialized ) { this . _ndcTriangle . initialize ( ) ; } this . _program . attribute ( 'a_vertex' , this . _ndcTriangle . vertexLocation ) ; this . _program . link ( ) ; this . _uWeight = this . _program . uniform ( 'u_weight' ) ; this . _program . bind ( ) ; gl . uniform1f ( this . _uWeight , 0.0 ) ; gl . uniform1i ( this . _program . uniform ( 'u_accumulationTexture' ) , 0 ) ; gl . uniform1i ( this . _program . uniform ( 'u_currentFrameTexture' ) , 1 ) ; this . _program . unbind ( ) ; return true ; } /**
     * Specializes this pass's uninitialization. Program and geometry resources are released (if allocated). Cached
     * uniform and attribute locations are invalidated.
     */ @ Initializable . uninitialize ( ) uninitialize ( ) { if ( ! this . _ndcTriangleShared && this . _ndcTriangle . initialized ) { this . _ndcTriangle . uninitialize ( ) ; } this . _program . uninitialize ( ) ; this . _accumulationFBOs [ 0 ] . uninitialize ( ) ; this . _accumulationFBOs [ 1 ] . uninitialize ( ) ; this . _accumulationTextures [ 0 ] . uninitialize ( ) ; this . _accumulationTextures [ 1 ] . uninitialize ( ) ; this . _write = 0 ; } /**
     * Initialize accumulation textures and FBOs (if not initialized yet). Then verifies if the texture's size has
     * changed, and if so, resizes the accumulation buffers.
     */ @ Initializable . assert_initialized ( ) update ( ) { if ( ! this . _texture || ! this . _texture . valid ) { log ( LogLevel . Warning , `valid texture for accumulation update expected, given ${ this . _texture }` ) ; return ; } if ( this . _passThrough ) { return ; } const sizeAltered = this . _altered . texture || this . _accumulationTextures [ 0 ] . width !== this . _texture . width || this . _accumulationTextures [ 0 ] . height !== this . _texture . height ; if ( ! this . _altered . any && ! sizeAltered ) { assert ( this . _accumulationFBOs [ 0 ] . valid && this . _accumulationFBOs [ 1 ] . valid , `valid accumulation framebuffers expected` ) ; return ; } const gl = this . _context . gl ; const gl2facade = this . _context . gl2facade ; const textureSize = this . _texture . size ; if ( ! this . _accumulationTextures [ 0 ] . initialized ) { const internalFormat = Wizard . queryInternalTextureFormat ( this . _context , gl . RGBA , this . _precision ) ; this . _accumulationTextures [ 0 ] . initialize ( textureSize [ 0 ] , textureSize [ 1 ] , internalFormat [ 0 ] , gl . RGBA , internalFormat [ 1 ] ) ; this . _accumulationTextures [ 1 ] . initialize ( textureSize [ 0 ] , textureSize [ 1 ] , internalFormat [ 0 ] , gl . RGBA , internalFormat [ 1 ] ) ; } else { if ( this . _altered . texture || sizeAltered ) { this . _accumulationTextures [ 0 ] . resize ( this . _texture . width , this . _texture . height ) ; this . _accumulationTextures [ 1 ] . resize ( this . _texture . width , this . _texture . height ) ; } if ( this . _altered . precision ) { const internalFormat = Wizard . queryInternalTextureFormat ( this . _context , gl . RGBA , this . _precision ) ; this . _accumulationTextures [ 0 ] . reformat ( internalFormat [ 0 ] , gl . RGBA , internalFormat [ 1 ] ) ; this . _accumulationTextures [ 1 ] . reformat ( internalFormat [ 0 ] , gl . RGBA , internalFormat [ 1 ] ) ; } } if ( ! this . _accumulationFBOs [ 0 ] . initialized ) { this . _accumulationFBOs [ 0 ] . initialize ( [ [ gl2facade . COLOR_ATTACHMENT0 , this . _accumulationTextures [ 0 ] ] ] ) ; this . _accumulationFBOs [ 1 ] . initialize ( [ [ gl2facade . COLOR_ATTACHMENT0 , this . _accumulationTextures [ 1 ] ] ] ) ; } assert ( this . _accumulationFBOs [ 0 ] . valid && this . _accumulationFBOs [ 1 ] . valid , `valid accumulation framebuffers expected` ) ; this . _altered . reset ( ) ; } param frameNumber param viewport @ Initializable . assert_initialized ( ) frame ( frameNumber , viewport ? ) { assert ( this . _accumulationFBOs [ 0 ] . valid && this . _accumulationFBOs [ 1 ] . valid , `valid framebuffer objects for accumulation expected (initialize or update was probably not called)` ) ; if ( this . _passThrough || this . _texture === undefined ) { return ; } logIf ( ! this . _texture || ! this . _texture . valid , LogLevel . Warning , `valid texture for accumulation frame expected, given ${ this . _texture }` ) ; const gl = this . _context . gl ; if ( viewport !== undefined ) { gl . viewport ( 0 , 0 , viewport [ 0 ] , viewport [ 1 ] ) ; } const readIndex = frameNumber % 2 ; const writeIndex = this . _write = 1 - readIndex ; const accumTexture = this . _accumulationTextures [ readIndex ] ; const frameTexture = this . _texture ; accumTexture . bind ( gl . TEXTURE0 ) ; frameTexture . bind ( gl . TEXTURE1 ) ; this . _program . bind ( ) ; gl . uniform1f ( this . _uWeight , 1.0 / ( frameNumber + 1 ) ) ; this . _accumulationFBOs [ writeIndex ] . bind ( gl . DRAW_FRAMEBUFFER ) ; this . _ndcTriangle . bind ( ) ; this . _ndcTriangle . draw ( ) ; this . _ndcTriangle . unbind ( ) ; this . _accumulationFBOs [ writeIndex ] . unbind ( gl . DRAW_FRAMEBUFFER ) ; /** Every pass is expected to bind its own program when drawing, thus, unbinding is not necessary. */ accumTexture . unbind ( gl . TEXTURE0 ) ; frameTexture . unbind ( gl . TEXTURE1 ) ; } param texture set texture ( texture ) { this . assertInitialized ( ) ; if ( this . _texture !== texture ) { this . _texture = texture ; this . _altered . alter ( 'texture' ) ; } } /**
     * Allows to specify the accumulation precision.
     */ set precision ( precision ) { this . assertInitialized ( ) ; if ( this . _precision !== precision ) { this . _precision = precision ; this . _altered . alter ( 'precision' ) ; } } /**
     * Allows to skip accumulation. If pass through is enabled, nothing will be rendered on frame at all and the
     * ping pong render textures will be reduced to a minimum size of [1, 1] until pass through is disabled.
     */ set passThrough ( passThrough ) { this . assertInitialized ( ) ; if ( this . _passThrough === passThrough ) { return ; } if ( this . _passThrough && this . _accumulationTextures [ 0 ] . initialized ) { this . _accumulationTextures [ 0 ] . uninitialize ( ) ; this . _accumulationTextures [ 1 ] . uninitialize ( ) ; } if ( this . _passThrough && this . _accumulationFBOs [ 0 ] . initialized ) { this . _accumulationFBOs [ 0 ] . uninitialize ( ) ; this . _accumulationFBOs [ 1 ] . uninitialize ( ) ; } this . _passThrough = passThrough ; this . _altered . alter ( 'passThrough' ) ; } returns get framebuffer ( ) : Framebuffer | undefined { return this . _passThrough ? undefined : this . _accumulationFBOs [ this . _write ] ; } }