import { assert , log , logIf , LogLevel } from './auxiliaries' ; import { byteSizeOfFormat } from './formatbytesizes' ; import { GLsizei2 } from './tuples' ; import { Bindable } from './bindable' ; import { TexImage2DData } from './gl2facade' ; import { Initializable } from './initializable' ; import { AbstractObject } from './object' ; /**
 * Wrapper for an WebGL 2D texture providing size accessors and requiring for bind, unbind, resize, validity, and
 * initialization implementations. The texture object is created on initialization and deleted on uninitialization.
 * After being initialized, the texture can be resized, reformated, and data can set directly or via fetch:
 * ```
 * const texture = new Texture2D(context, 'Texture');
 * texture.initialize(1, 1, gl.RGB8, gl.RGB, gl.UNSIGNED_BYTE);
 * texture.fetch('/img/webgl-operate-logo.png', true);
 * ```
 */ export class Texture2D extends AbstractObject < WebGLTexture > implements Bindable { /**
     * Default texture, e.g., used for unbind.
     */ static readonly DEFAULT_TEXTURE = undefined ; static MAX_ANISOTROPY : GLfloat | undefined = undefined ; see width protected _width : GLsizei = 0 ; see height protected _height : GLsizei = 0 ; see internalFormat protected _internalFormat : GLenum = 0 ; see format protected _format : GLenum = 0 ; see type protected _type : GLenum = 0 ; /**
     * Whether or not to generate mip maps (based on minification settings).
     * If true, gl.generateMipmap will be called whenever the image data changes.
     */ protected _mipmap = false ; see anisotropy protected _anisotropy : GLfloat | undefined = undefined ; param width param height param internalFormat param format param type protected create ( width , height , internalFormat , format , type ) { assert ( width > 0 && height > 0 , `texture requires valid width and height of greater than zero` ) ; const gl = this . _context . gl ; const gl2facade = this . _context . gl2facade ; this . _object = gl . createTexture ( ) ; this . _width = width ; this . _height = height ; this . _internalFormat = internalFormat ; this . _format = format ; this . _type = type ; gl . bindTexture ( gl . TEXTURE_2D , this . _object ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MAG_FILTER , gl . NEAREST ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MIN_FILTER , gl . NEAREST ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_S , gl . CLAMP_TO_EDGE ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_T , gl . CLAMP_TO_EDGE ) ; if ( Texture2D . MAX_ANISOTROPY === undefined ) { if ( this . _context . supportsTextureFilterAnisotropic ) { const ext = this . _context . textureFilterAnisotropic ; Texture2D . MAX_ANISOTROPY = gl . getParameter ( ext . MAX_TEXTURE_MAX_ANISOTROPY_EXT ) ; } else { Texture2D . MAX_ANISOTROPY = 0.0 ; } } gl2facade . texImage2D ( gl . TEXTURE_2D , 0 , this . _internalFormat , this . _width , this . _height , 0 , this . _format , this . _type ) ; gl . bindTexture ( gl . TEXTURE_2D , Texture2D . DEFAULT_TEXTURE ) ; this . _valid = gl . isTexture ( this . _object ) ; this . reallocate ( ) ; return this . _object ; } /**
     * Delete the texture object on the GPU. This should have the reverse effect of `create`.
     */ protected delete ( ) { assert ( this . _object instanceof WebGLTexture , `expected WebGLTexture object` ) ; this . _context . gl . deleteTexture ( this . _object ) ; this . _object = undefined ; this . _valid = false ; this . _internalFormat = 0 ; this . _format = 0 ; this . _type = 0 ; this . _width = 0 ; this . _height = 0 ; } protected reallocate ( ) { const gl = this . context . gl ; const gl2facade = this . _context . gl2facade ; let bytes = this . _width * this . _height * byteSizeOfFormat ( this . context , this . _internalFormat ) ; if ( this . _type === gl2facade . HALF_FLOAT && this . _internalFormat !== gl . RGBA16F ) { bytes *= 2 ; } else if ( this . _type === gl . FLOAT && this . _internalFormat !== gl . RGBA16F ) { bytes *= 4 ; } this . context . allocationRegister . reallocate ( this . _identifier , bytes ) ; } /**
     * Bind the texture object to a texture unit.
     */ @ Initializable . assert_initialized ( ) bind ( unit ? ) { const gl = this . context . gl ; if ( unit ) { gl . activeTexture ( unit ) ; } gl . bindTexture ( gl . TEXTURE_2D , this . _object ) ; } /**
     * Unbind the texture object from a texture unit.
     */ @ Initializable . assert_initialized ( ) unbind ( unit ? ) { const gl = this . context . gl ; if ( unit ) { gl . activeTexture ( unit ) ; } gl . bindTexture ( gl . TEXTURE_2D , Texture2D . DEFAULT_TEXTURE ) ; } param url param crossOrigin returns @ Initializable . assert_initialized ( ) fetch ( url , crossOrigin = false , flipY = false ) { const gl = this . context . gl ; return new Promise ( ( resolve , reject ) => { const image = new Image ( ) ; image . onerror = ( ) => { log ( LogLevel . Error , `loading image from '${ image . src }' failed` ) ; reject ( ) ; } ; image . onload = ( ) => { this . resize ( image . width , image . height ) ; if ( ! flipY ) { gl . pixelStorei ( gl . UNPACK_FLIP_Y_WEBGL , true ) ; } this . data ( image ) ; gl . pixelStorei ( gl . UNPACK_FLIP_Y_WEBGL , false ) ; resolve ( ) ; } ; if ( crossOrigin ) { image . crossOrigin = 'anonymous' ; } image . src = url ; } ) ; } param data param bind param unbind @ Initializable . assert_initialized ( ) data ( data , bind = true , unbind = true ) { const gl = this . context . gl ; const gl2facade = this . _context . gl2facade ; if ( bind ) { this . bind ( ) ; } gl2facade . texImage2D ( gl . TEXTURE_2D , 0 , this . _internalFormat , this . _width , this . _height , 0 , this . _format , this . _type , data ) ; if ( this . _anisotropy !== undefined && this . _anisotropy > 0.0 ) { this . maxAnisotropy ( this . _anisotropy , false , false ) ; } else if ( this . _mipmap ) { this . generateMipMap ( false , false ) ; } if ( unbind ) { this . unbind ( ) ; } this . reallocate ( ) ; } /**
     * Sets the texture object's magnification and minification filter. If a mipmap mode is set for minification, will
     * be generated automatically whenever the image data changes. The MipMap can be generated manually as well by
     * calling generateMipMap (@see  generateMipMap ).
     *  param mag param min param bind param unbind @ Initializable . assert_initialized ( ) filter ( mag , min , bind = true , unbind = true ) { const gl = this . context . gl ; if ( bind ) { this . bind ( ) ; } logIf ( mag === gl . LINEAR_MIPMAP_LINEAR || mag === gl . LINEAR_MIPMAP_NEAREST , LogLevel . Debug , `magnification does not utilize a MipMap (refer to LINEAR and NEAREST only)` ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MAG_FILTER , mag ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MIN_FILTER , min ) ; this . _mipmap = min === gl . LINEAR_MIPMAP_LINEAR || min === gl . LINEAR_MIPMAP_NEAREST ; if ( this . _mipmap ) { gl . generateMipmap ( gl . TEXTURE_2D ) ; } if ( unbind ) { this . unbind ( ) ; } } param wrap_s param wrap_t param bind param unbind @ Initializable . assert_initialized ( ) wrap ( wrap_s , wrap_t , bind = true , unbind = true ) { const gl = this . context . gl ; if ( bind ) { this . bind ( ) ; } gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_S , wrap_s ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_T , wrap_t ) ; if ( unbind ) { this . unbind ( ) ; } } /**
     * This can be used to reformat the texture image without creating a new texture object. Please note that this
     * resets the texture's image data to undefined. @see  data for setting new image data.
     *  param internalFormat param format param type param bind param unbind @ Initializable . assert_initialized ( ) reformat ( internalFormat , format ? , type ? , bind = true , unbind = true ) { if ( internalFormat === this . _internalFormat && ( format === undefined || format === this . _format ) && ( type === undefined || type === this . _type ) ) { return ; } assert ( internalFormat !== undefined , `valid internal format expected` ) ; this . _internalFormat = internalFormat ; if ( format ) { this . _format = format ; } if ( type ) { this . _type = type ; } this . data ( undefined , bind , unbind ) ; } param width param height param bind param unbind @ Initializable . assert_initialized ( ) resize ( width , height , bind = true , unbind = true ) { if ( width === this . _width && height === this . _height ) { return ; } this . _width = width ; this . _height = height ; this . data ( undefined , bind , unbind ) ; } param bind param unbind generateMipMap ( bind = true , unbind = true ) { const gl = this . context . gl ; if ( bind ) { this . bind ( ) ; } gl . generateMipmap ( gl . TEXTURE_2D ) ; if ( unbind ) { this . unbind ( ) ; } this . _mipmap = true ; } param max param bind param unbind returns maxAnisotropy ( max , bind = true , unbind = true ) { if ( this . _context . supportsTextureFilterAnisotropic === false ) { log ( LogLevel . Debug , `setting anisotropy not supported (EXT_texture_filter_anisotropic missing)` ) ; return undefined ; } const gl = this . context . gl ; const ext = this . _context . textureFilterAnisotropic ; this . _anisotropy = max === undefined ? undefined : Math . max ( 0.0 , Math . min ( Texture2D . MAX_ANISOTROPY ! , max ) ) ; logIf ( max !== this . _anisotropy , LogLevel . Debug , `value clamped to max supported anisotropy of ${ Texture2D . MAX_ANISOTROPY }, given ${ max }` ) ; if ( bind ) { this . bind ( ) ; } gl . texParameterf ( gl . TEXTURE_2D , ext . TEXTURE_MAX_ANISOTROPY_EXT , this . _anisotropy === undefined ? 0.0 : this . _anisotropy ) ; this . generateMipMap ( false , unbind ) ; return this . _anisotropy ; } /**
     * Returns the number of bytes this object approximately allocates on the GPU. The size will be zero when no
     * image data was passed to the texture object.
     */ get bytes ( ) : GLsizei { this . assertInitialized ( ) ; return this . context . allocationRegister . allocated ( this . _identifier ) ; } /**
     * Cached internal format of the texture for efficient resize. This can only be changed by re-initialization.
     */ get internalFormat ( ) : GLenum { this . assertInitialized ( ) ; return this . _internalFormat ! ; } /**
     * Cached format of the data provided to the texture object for efficient resize. This is set on initialization and
     * might change on data transfers.
     */ get format ( ) : GLenum { this . assertInitialized ( ) ; return this . _format ! ; } /**
     * Cached type of the data provided to the texture used for efficient resize. This is set on initialization and
     * might change on data transfers.
     */ get type ( ) : GLenum { this . assertInitialized ( ) ; return this . _type ! ; } /**
     * The width of the texture object in px.
     */ get width ( ) : GLsizei { this . assertInitialized ( ) ; return this . _width ; } /**
     * The height of the texture object in px.
     */ get height ( ) : GLsizei { this . assertInitialized ( ) ; return this . _height ; } see width see height get size ( ) : GLsizei2 { this . assertInitialized ( ) ; return [ this . _width , this . _height ] ; } }