import { assert } from './auxiliaries' ; import { byteSizeOfFormat } from './formatbytesizes' ; import { GLsizei3 } from './tuples' ; import { Bindable } from './bindable' ; import { TexImage3DData } 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 load:
 * ```
 * const texture = new Texture3D(context, 'Texture');
 * texture.initialize(1, 1, 1, gl.RGB8, gl.RGB, gl.UNSIGNED_BYTE);
 * texture.load('/img/webgl-operate-logo.png', true)
 * ```
 */ export class Texture3D extends AbstractObject < WebGLTexture > implements Bindable { /**
     * Default texture, e.g., used for unbind.
     */ static readonly DEFAULT_TEXTURE = undefined ; see width protected _width : GLsizei = 0 ; see height protected _height : GLsizei = 0 ; see depth protected _depth : GLsizei = 0 ; see internalFormat protected _internalFormat : GLenum = 0 ; see format protected _format : GLenum = 0 ; see type protected _type : GLenum = 0 ; param width param height param depth param internalFormat param format param type protected create ( width , height , depth , internalFormat , format , type ) { assert ( this . _context . supportsTexImage3D , `expected texImage3D to be supported` ) ; assert ( width > 0 && height > 0 && depth > 0 , `texture requires valid width, height, and depth 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 . _depth = depth ; this . _internalFormat = internalFormat ; this . _format = format ; this . _type = type ; gl . bindTexture ( gl . TEXTURE_3D , this . _object ) ; gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_MAG_FILTER , gl . NEAREST ) ; gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_MIN_FILTER , gl . NEAREST ) ; gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_WRAP_S , gl . CLAMP_TO_EDGE ) ; gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_WRAP_T , gl . CLAMP_TO_EDGE ) ; gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_WRAP_R , gl . CLAMP_TO_EDGE ) ; gl2facade . texImage3D ( gl . TEXTURE_3D , 0 , this . _internalFormat , this . _width , this . _height , this . _depth , 0 , this . _format , this . _type ) ; gl . bindTexture ( gl . TEXTURE_3D , Texture3D . 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 ; this . _depth = 0 ; } protected reallocate ( ) { const gl = this . context . gl ; const gl2facade = this . _context . gl2facade ; let bytes = this . _width * this . _height * this . _depth * 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_3D , 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_3D , Texture3D . DEFAULT_TEXTURE ) ; } param uri param slices param crossOrigin param useHorizontalSlicing returns @ Initializable . assert_initialized ( ) load ( uri , slices , crossOrigin = false , useHorizontalSlicing = false ) { return new Promise ( ( resolve , reject ) => { const image = new Image ( ) ; image . onerror = ( ) => reject ( ) ; image . onload = ( ) => { const auxiliaryCanvas = document . createElement ( 'canvas' ) ; const auxiliaryContext = auxiliaryCanvas . getContext ( '2d' ) ; assert ( auxiliaryContext !== undefined , `expected auxiliary 2D context for temporary slicing` ) ; let width , height , depth , data ; if ( useHorizontalSlicing === false ) { assert ( image . height % slices === 0 && image . height / slices >= 1.0 , `expected height to be a multitude of number of slices` ) ; width = image . width ; height = Math . floor ( image . height / slices ) ; depth = slices ; auxiliaryCanvas . width = width ; auxiliaryCanvas . height = height ; data = new Uint8Array ( width * height * depth ) ; const subImageSize = width * height ; for ( let slice = 0 ; slice < slices ; ++ slice ) { auxiliaryContext ! . drawImage ( image , 0 , height * slice , width , height , 0 , 0 , width , height ) ; const subImageData = auxiliaryContext ! . getImageData ( 0 , 0 , width , height ) . data ; for ( let i = 0 ; i < subImageSize ; ++ i ) { data [ subImageSize * slice + i ] = subImageData [ 4 * i ] ; } } } else { assert ( image . width % slices === 0 && image . width / slices >= 1.0 , `expected width to be a multitude of number of slices` ) ; width = Math . floor ( image . width / slices ) ; height = image . height ; depth = slices ; auxiliaryCanvas . width = width ; auxiliaryCanvas . height = height ; data = new Uint8Array ( width * height * depth ) ; const subImageSize = width * height ; for ( let slice = 0 ; slice < slices ; ++ slice ) { auxiliaryContext ! . drawImage ( image , width * slice , 0 , width , height , 0 , 0 , width , height ) ; const subImageData = auxiliaryContext ! . getImageData ( 0 , 0 , width , height ) . data ; for ( let i = 0 ; i < subImageSize ; ++ i ) { data [ subImageSize * slice + i ] = subImageData [ 4 * i ] ; } } } this . resize ( width , height , depth ) ; this . data ( data ) ; resolve ( ) ; } ; if ( crossOrigin ) { image . crossOrigin = 'anonymous' ; } image . src = uri ; } ) ; } param uris param slices param crossOrigin param useHorizontalSlicing returns @ Initializable . assert_initialized ( ) loadFromSingleImages ( uris , slices , crossOrigin = false , useHorizontalSlicing = false ) { return new Promise ( ( resolve , reject ) => { const auxiliaryCanvas = document . createElement ( 'canvas' ) ; const auxiliaryContext = auxiliaryCanvas . getContext ( '2d' ) ; const drawingPromises = [ ] ; let mergedImageWidth ; let mergedImageHeight ; for ( let imageIndex = 0 ; imageIndex < uris . length ; imageIndex ++ ) { const singleImage = new Image ( ) ; singleImage . onerror = ( ) => reject ( ) ; const drawingPromise = new Promise ( ( resolve , reject ) => { try { singleImage . onload = ( ) => { const singleImageWidth = singleImage . width ; const singleImageHeight = singleImage . height ; if ( useHorizontalSlicing === false ) { if ( mergedImageWidth === undefined ) { mergedImageWidth = singleImageWidth * uris . length ; auxiliaryCanvas . width = mergedImageWidth ; } else { assert ( singleImageWidth * uris . length === mergedImageWidth , "The single images do not have the same width" ) ; } if ( mergedImageHeight === undefined ) { mergedImageHeight = singleImageHeight ; auxiliaryCanvas . height = mergedImageHeight ; } else { assert ( singleImageHeight === mergedImageHeight , "The single images do not have the same height" ) ; } auxiliaryContext ! . drawImage ( singleImage , singleImageWidth * imageIndex , 0 ) ; } else { if ( mergedImageWidth === undefined ) { mergedImageWidth = singleImageWidth ; auxiliaryCanvas . width = mergedImageWidth ; } else { assert ( singleImageWidth === mergedImageWidth , "The single images do not have the same width" ) ; } if ( mergedImageHeight === undefined ) { mergedImageHeight = singleImageHeight * uris . length ; auxiliaryCanvas . height = mergedImageHeight ; } else { assert ( singleImageHeight * uris . length === mergedImageHeight , "The single images do not have the same height" ) ; } auxiliaryContext ! . drawImage ( singleImage , 0 , singleImageHeight * imageIndex ) ; } resolve ( ) ; } ; } catch ( error ) { reject ( error ) ; } } ) ; drawingPromises . push ( drawingPromise ) ; if ( crossOrigin ) { singleImage . crossOrigin = 'anonymous' ; } singleImage . src = uris [ imageIndex ] ; } Promise . all ( drawingPromises ) . then ( ( ) => { const mergedImageDataUri = auxiliaryCanvas . toDataURL ( 'image/png' , 1.0 ) ; return this . load ( mergedImageDataUri , slices , crossOrigin , useHorizontalSlicing ) . then ( ( ) => { resolve ( ) ; } ) ; } ) ; } ) ; } 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 . texImage3D ( gl . TEXTURE_3D , 0 , this . _internalFormat , this . _width , this . _height , this . _depth , 0 , this . _format , this . _type , data ) ; if ( unbind ) { this . unbind ( ) ; } this . reallocate ( ) ; } 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 ( ) ; } gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_MAG_FILTER , mag ) ; gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_MIN_FILTER , min ) ; if ( unbind ) { this . unbind ( ) ; } } param wrap_s param wrap_t param wrap_r param bind param unbind @ Initializable . assert_initialized ( ) wrap ( wrap_s , wrap_t , wrap_r , bind = true , unbind = true ) { const gl = this . context . gl ; if ( bind ) { this . bind ( ) ; } gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_WRAP_S , wrap_s ) ; gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_WRAP_T , wrap_t ) ; gl . texParameteri ( gl . TEXTURE_3D , gl . TEXTURE_WRAP_R , wrap_r ) ; 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 depth param bind param unbind @ Initializable . assert_initialized ( ) resize ( width , height , depth , bind = true , unbind = true ) { if ( width === this . _width && height === this . _height && depth === this . _depth ) { return ; } this . _width = width ; this . _height = height ; this . _depth = depth ; this . data ( undefined , bind , unbind ) ; } /**
     * 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 ; } /**
     * The depth of the texture object in px.
     */ get depth ( ) : GLsizei { this . assertInitialized ( ) ; return this . _depth ; } see width see heigth see depth get size ( ) : GLsizei3 { this . assertInitialized ( ) ; return [ this . _width , this . _height , this . _depth ] ; } }