import { Buffer } from './buffer' ; import { Context } from './context' ; import { Initializable } from './initializable' ; /**
 * Class to encapsulating a WebGL buffer adding functionality to record changes to the underlying buffer without
 * instantly propagating them to the WebGL buffer. It is intended to be a direct replacement for  Buffer .
 * When calling  subData the data will be copied to an internal, CPU-sided buffer and the affecting range will be
 * recorded. If the newly recorded range overrides a part of or the whole of a previously recorded range the older one
 * will either be discarded completely or merged with the new one according to a specifiable merge threshold.
 * To propagate all recorded changes to the GPU-sided buffer,  update has to be called. This will take care of
 * both the allocation of the GPU-sided buffer and the transfer of the changed data.
 * While  update can must only be called after initializing the object,  subData ,
 *  mergeSubDataRanges and resizing (@see size ) can be called on the unitialized object.
 * The GPU-sided buffer is created on initialization and deleted on uninitialization.
 * A typical usage could look like this:
 * ```
 * // Create a unified buffer with the size of 1028 bytes and a mergeThreshold of 32 bytes
 * const unifiedBuffer = new UnifiedBuffer(context, 1028, gl.STATIC_DRAW, 32, 'UnifiedBuffer');
 * unifiedBuffer.initialize(gl.ARRAY_BUFFER);
 * unifiedBuffer.attribEnable(0, 1, gl.FLOAT, gl.FALSE, 0, 0, true, false);
 * unifiedBuffer.attribEnable(1, 1, gl.SHORT, gl.FALSE, 0, 512, false, true);
 *
 * unifiedBuffer.subData(0, new Float32Array(64).fill(3.14));
 * unifiedBuffer.subData(512, new Int16Array(128).fill(1200));
 *
 * unifiedBuffer.update(true, true);
 *
 * unifiedBuffer.subData(128, new Float32Array(32).fill(1.57));
 * unifiedBuffer.subData(640, new Int36Array(64).fill(600));
 *
 * unifiedBuffer.mergeThreshold = -1;
 * // This will merge both already existing ranges resulting in a single update
 * unifiedBuffer.mergeSubDataRanges();
 *
 * unifiedBuffer.bind();
 * unifiedBuffer.update();
 * unifiedBuffer.unbind();
 * ```
  export class UnifiedBuffer extends Initializable { protected _cpuBuffer : ArrayBuffer ; protected _gpuBuffer : Buffer ; protected _updates = new Array < Update > ( ) ; see usage protected _usage : GLenum ; see mergeThreshold protected _mergeThreshold : number ; param lhsUpdate param rhsUpdate param mergeThreshold returns protected static updatesNeedMerge ( lhsUpdate , rhsUpdate , mergeThreshold ) { return rhsUpdate . begin - lhsUpdate . end < mergeThreshold || mergeThreshold === - 1 ; } param context param sizeInBytes param usage param mergeThreshold param identifier constructor ( context , sizeInBytes , usage , mergeThreshold = 0 , identifier ? ) { super ( ) ; this . _cpuBuffer = new ArrayBuffer ( sizeInBytes ) ; this . _gpuBuffer = new Buffer ( context , identifier ) ; this . _usage = usage ; this . _mergeThreshold = mergeThreshold ; } param index returns protected mergeUpdatesLeft ( index ) { let removeCount = 0 ; const rhs = this . _updates [ index ] ; for ( let i = index - 1 ; i >= 0 ; i -- ) { const lhs = this . _updates [ i ] ; if ( UnifiedBuffer . updatesNeedMerge ( lhs , rhs , this . _mergeThreshold ) ) { rhs . begin = Math . min ( rhs . begin , lhs . begin ) ; rhs . end = Math . max ( rhs . end , lhs . end ) ; removeCount ++ ; } else { break ; } } this . _updates . splice ( index - removeCount , removeCount ) ; return removeCount + 1 ; } param index returns protected mergeUpdatesRight ( index ) { let removeCount = 0 ; const lhs = this . _updates [ index ] ; for ( let i = index + 1 ; i < this . _updates . length ; i ++ ) { const rhs = this . _updates [ i ] ; if ( UnifiedBuffer . updatesNeedMerge ( lhs , rhs , this . _mergeThreshold ) ) { lhs . begin = Math . min ( lhs . begin , rhs . begin ) ; lhs . end = Math . max ( lhs . end , rhs . end ) ; removeCount ++ ; } else { break ; } } this . _updates . splice ( index + 1 , removeCount ) ; return removeCount + 1 ; } param update protected addUpdate ( update ) { const start = this . _updates . findIndex ( ( current ) => { return update . begin < current . begin ; } ) ; if ( start === - 1 ) { this . _updates . push ( update ) ; this . mergeUpdatesLeft ( this . _updates . length - 1 ) ; } else { this . _updates . splice ( start , 0 , update ) ; this . mergeUpdatesRight ( start ) ; this . mergeUpdatesLeft ( start ) ; } } param target @ Initializable . initialize ( ) initialize ( target ) { return this . _gpuBuffer . initialize ( target ) ; } /**
     * Delete the buffer object on the GPU. This should have the reverse effect of `create`.
     */ @ Initializable . uninitialize ( ) uninitialize ( ) { this . _gpuBuffer . uninitialize ( ) ; } /**
     * Binds the buffer object as buffer to predefined target.
     */ @ Initializable . assert_initialized ( ) bind ( ) { this . _gpuBuffer . bind ( ) ; } /**
     * Binds null as current buffer to predefined target;
     */ @ Initializable . assert_initialized ( ) unbind ( ) { this . _gpuBuffer . unbind ( ) ; } param index param size param type param normalized param stride param offset param bind param unbind @ Initializable . assert_initialized ( ) attribEnable ( index , size , type , normalized = false , stride = 0 , offset = 0 , bind = true , unbind = true ) { this . _gpuBuffer . attribEnable ( index , size , type , normalized , stride , offset , bind , unbind ) ; } param index param bind param unbind @ Initializable . assert_initialized ( ) attribDisable ( index , bind = true , unbind = true ) { this . _gpuBuffer . attribDisable ( index , bind , unbind ) ; } /**
     * Merges all recorded subData ranges.
     */ mergeSubDataRanges ( ) { let index = 0 ; while ( index < this . _updates . length ) { index += this . mergeUpdatesRight ( index ) ; } } param dstByteOffset param srcData subData ( dstByteOffset , srcData ) { let src ; if ( srcData instanceof ArrayBuffer ) { src = new Uint8Array ( srcData ) ; } else { src = new Uint8Array ( srcData . buffer ) . subarray ( srcData . byteOffset , srcData . byteOffset + srcData . byteLength ) ; } const dst = new Uint8Array ( this . _cpuBuffer ) ; dst . set ( src , dstByteOffset ) ; this . addUpdate ( { begin : dstByteOffset , end : dstByteOffset + src . byteLength } ) ; } param bind param unbind @ Initializable . assert_initialized ( ) update ( bind = false , unbind = false ) { if ( bind ) { this . _gpuBuffer . bind ( ) ; } if ( this . _gpuBuffer . bytes !== this . _cpuBuffer . byteLength ) { this . _gpuBuffer . data ( this . _cpuBuffer , this . _usage ) ; } else { const bufferView = new Uint8Array ( this . _cpuBuffer ) ; for ( const update of this . _updates ) { const subBufferView = bufferView . subarray ( update . begin , update . end ) ; this . _gpuBuffer . subData ( update . begin , subBufferView ) ; } } if ( unbind ) { this . _gpuBuffer . unbind ( ) ; } this . _updates . length = 0 ; } /**
     * Returns the size of the CPU-sided buffer. This is not necessarily the same as the size of the GPU-sided buffer,
     * if update has not been called after resizing the buffer.
     */ get size ( ) : number { return this . _cpuBuffer . byteLength ; } /**
     * Resizes the buffer. Note, that this does not resize the GPU-sided buffer. To update the size of the GPU-sided
     * buffer update has to be called.
     */ set size ( sizeInBytes ) { const oldBuffer = this . _cpuBuffer ; this . _cpuBuffer = new ArrayBuffer ( sizeInBytes ) ; const src = new Uint8Array ( oldBuffer ) . slice ( 0 , sizeInBytes ) ; const dst = new Uint8Array ( this . _cpuBuffer ) ; dst . set ( src ) ; } /**
     * Target to which the buffer object is bound (either GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER).
     * Readonly access to the target (as specified on initialization) the buffer will be bound to.
     */ get target ( ) : GLenum | undefined { this . assertInitialized ( ) ; return this . _gpuBuffer . target ; } /**
     * Returns the usage hint used for allocation of the GPU-sided buffer.
     */ get usage ( ) : GLenum { return this . _usage ; } /**
     * Sets the usage hint used for allocation of the GPU-sided buffer.
     */ set usage ( usage ) { this . _usage = usage ; } /**
     * Returns the threshold used to determine whether two ranges have to be merged.
     */ get mergeThreshold ( ) : number { return this . _mergeThreshold ; } /**
     * Sets the threshold determining whether two ranges have to be merged. If the mergeThreshold is set to -1 all
     * ranges will get merged disregarding their distance to each other.
     */ set mergeThreshold ( mergeThreshold ) { this . _mergeThreshold = mergeThreshold ; } } interface Update { /** inclusive */ begin : number ; /** exclusive */ end : number ; }