import { Visual } from '../layer/index' ; import { Movie } from '../movie' ; import { val } from '../util' ; import { Base } from './base' ; export interface UniformOptions { type ? : string ; defaultFloatComponent ? : number ; } export interface TextureOptions { createUniform ? : boolean ; target ? ; level ? : number ; internalFormat ? ; srcFormat ? ; srcType ? ; wrapS ? ; wrapT ? ; minFilter ? ; magFilter ? ; } export interface ShaderOptions { fragmentSource ? : string ; uniforms ? : Record < string , ( UniformOptions | string ) > ; textures ? : Record < string , TextureOptions > ; sourceTextureOptions ? : TextureOptions ; } /**
 * A hardware-accelerated pixel mapping using WebGL
 */ export class Shader extends Base { /**
     * WebGL texture units consumed by  Shader static INTERNAL_TEXTURE_UNITS = 1 ; private static _DEFAULT_TEXTURE_OPTIONS = { createUniform : true , target : 'TEXTURE_2D' , level : 0 , internalFormat : 'RGBA' , srcFormat : 'RGBA' , srcType : 'UNSIGNED_BYTE' , minFilter : 'LINEAR' , magFilter : 'LINEAR' , wrapS : 'CLAMP_TO_EDGE' , wrapT : 'CLAMP_TO_EDGE' } ; private static _VERTEX_SOURCE = `
    attribute vec4 a_VertexPosition;
    attribute vec2 a_TextureCoord;

    varying highp vec2 v_TextureCoord;

    void main() {
        // no need for projection or model-view matrices, since we're just rendering a rectangle
        // that fills the screen (see position values)
        gl_Position = a_VertexPosition;
        v_TextureCoord = a_TextureCoord;
    }
  ` ; private static _IDENTITY_FRAGMENT_SOURCE = `
    precision mediump float;

    uniform sampler2D u_Source;

    varying highp vec2 v_TextureCoord;

    void main() {
        gl_FragColor = texture2D(u_Source, v_TextureCoord);
    }
  ` ; private _program : WebGLProgram ; private _buffers : { position : WebGLBuffer ; textureCoord : WebGLBuffer ; } ; private _canvas : HTMLCanvasElement ; private _gl : WebGLRenderingContext ; private _uniformLocations : Record < string , WebGLUniformLocation > ; private _attribLocations : Record < string , GLint > ; private _userUniforms : Record < string , ( UniformOptions | string ) > ; private _userTextures : Record < string , TextureOptions > ; private _sourceTextureOptions : TextureOptions ; private _inputTexture : WebGLTexture ; param fragmentSrc param userUniforms param userTextures param sourceTextureOptions constructor ( options = { } ) { super ( ) ; const fragmentSrc = options . fragmentSource || Shader . _IDENTITY_FRAGMENT_SOURCE ; const userUniforms = options . uniforms || { } ; const userTextures = options . textures || { } ; const sourceTextureOptions = options . sourceTextureOptions || { } ; const gl = this . _initGl ( ) ; this . _program = Shader . _initShaderProgram ( gl , Shader . _VERTEX_SOURCE , fragmentSrc ) ; this . _buffers = Shader . _initRectBuffers ( gl ) ; this . _initTextures ( userUniforms , userTextures , sourceTextureOptions ) ; this . _initAttribs ( ) ; this . _initUniforms ( userUniforms ) ; } private _initGl ( ) { this . _canvas = document . createElement ( 'canvas' ) ; const gl = this . _canvas . getContext ( 'webgl' ) ; if ( gl === null ) throw new Error ( 'Unable to initialize WebGL. Your browser or machine may not support it.' ) ; this . _gl = gl ; return gl ; } private _initTextures ( userUniforms , userTextures , sourceTextureOptions ) { const gl = this . _gl ; const maxTextures = gl . getParameter ( gl . MAX_TEXTURE_IMAGE_UNITS ) ; if ( userTextures . length > maxTextures ) console . warn ( 'Too many textures!' ) ; this . _userTextures = { } ; for ( const name in userTextures ) { const userOptions = userTextures [ name ] ; const options = { ... Shader . _DEFAULT_TEXTURE_OPTIONS , ... userOptions } ; if ( options . createUniform ) { if ( userUniforms [ name ] ) throw new Error ( `Texture - uniform naming conflict: ${ name }!` ) ; userUniforms [ name ] = '1i' ; } this . _userTextures [ name ] = options ; } this . _sourceTextureOptions = { ... Shader . _DEFAULT_TEXTURE_OPTIONS , ... sourceTextureOptions } ; } private _initAttribs ( ) { const gl = this . _gl ; this . _attribLocations = { textureCoord : gl . getAttribLocation ( this . _program , 'a_TextureCoord' ) } ; } private _initUniforms ( userUniforms ) { const gl = this . _gl ; this . _uniformLocations = { source : gl . getUniformLocation ( this . _program , 'u_Source' ) , size : gl . getUniformLocation ( this . _program , 'u_Size' ) } ; this . _userUniforms = { } ; for ( const name in userUniforms ) { const val = userUniforms [ name ] ; this . _userUniforms [ name ] = typeof val === 'string' ? { type : val } : val ; } for ( const unprefixed in userUniforms ) { const prefixed = 'u_' + unprefixed . charAt ( 0 ) . toUpperCase ( ) + ( unprefixed . length > 1 ? unprefixed . slice ( 1 ) : '' ) ; this . _uniformLocations [ unprefixed ] = gl . getUniformLocation ( this . _program , prefixed ) ; } } apply ( target , reltime ) { this . _checkDimensions ( target ) ; this . _refreshGl ( ) ; this . _enablePositionAttrib ( ) ; this . _enableTexCoordAttrib ( ) ; this . _prepareTextures ( target , reltime ) ; this . _gl . useProgram ( this . _program ) ; this . _prepareUniforms ( target , reltime ) ; this . _draw ( target ) ; } private _checkDimensions ( target ) { const gl = this . _gl ; if ( this . _canvas . width !== target . canvas . width || this . _canvas . height !== target . canvas . height ) { this . _canvas . width = target . canvas . width ; this . _canvas . height = target . canvas . height ; gl . viewport ( 0 , 0 , target . canvas . width , target . canvas . height ) ; } } private _refreshGl ( ) { const gl = this . _gl ; gl . clearColor ( 0 , 0 , 0 , 1 ) ; gl . blendFuncSeparate ( gl . SRC_ALPHA , gl . SRC_ALPHA , gl . ONE , gl . ZERO ) ; gl . enable ( gl . BLEND ) ; gl . disable ( gl . DEPTH_TEST ) ; gl . clear ( gl . COLOR_BUFFER_BIT | gl . DEPTH_BUFFER_BIT ) ; } private _enablePositionAttrib ( ) { const gl = this . _gl ; const numComponents = 2 ; const type = gl . FLOAT ; const normalize = false ; const stride = 0 ; const offset = 0 ; gl . bindBuffer ( gl . ARRAY_BUFFER , this . _buffers . position ) ; gl . vertexAttribPointer ( this . _attribLocations . vertexPosition , numComponents , type , normalize , stride , offset ) ; gl . enableVertexAttribArray ( this . _attribLocations . vertexPosition ) ; } private _enableTexCoordAttrib ( ) { const gl = this . _gl ; const numComponents = 2 ; const type = gl . FLOAT ; const normalize = false ; const stride = 0 ; const offset = 0 ; gl . bindBuffer ( gl . ARRAY_BUFFER , this . _buffers . textureCoord ) ; gl . vertexAttribPointer ( this . _attribLocations . textureCoord , numComponents , type , normalize , stride , offset ) ; gl . enableVertexAttribArray ( this . _attribLocations . textureCoord ) ; } private _prepareTextures ( target , reltime ) { const gl = this . _gl ; gl . activeTexture ( gl . TEXTURE0 ) ; this . _inputTexture = Shader . _loadTexture ( gl , target . canvas , this . _sourceTextureOptions ) ; gl . bindTexture ( gl . TEXTURE_2D , this . _inputTexture ) ; let i = 0 ; for ( const name in this . _userTextures ) { const options = this . _userTextures [ name ] ; gl . activeTexture ( gl . TEXTURE0 + ( Shader . INTERNAL_TEXTURE_UNITS + i ) ) ; const preparedTex = Shader . _loadTexture ( gl , val ( this , name , reltime ) , options ) ; gl . bindTexture ( gl [ options . target ] , preparedTex ) ; i ++ ; } } private _prepareUniforms ( target , reltime ) { const gl = this . _gl ; if ( this . _uniformLocations . source ) gl . uniform1i ( this . _uniformLocations . source , 0 ) ; if ( this . _uniformLocations . size ) gl . uniform2iv ( this . _uniformLocations . size , [ target . canvas . width , target . canvas . height ] ) ; for ( const unprefixed in this . _userUniforms ) { const options = this . _userUniforms [ unprefixed ] as UniformOptions ; const value = val ( this , unprefixed , reltime ) ; const preparedValue = this . _prepareValue ( value , options . type , reltime , options ) ; const location = this . _uniformLocations [ unprefixed ] ; gl [ 'uniform' + options . type ] ( location , preparedValue ) ; } gl . uniform1i ( this . _uniformLocations . test , 0 ) ; } private _draw ( target ) { const gl = this . _gl ; const offset = 0 ; const vertexCount = 4 ; gl . drawArrays ( gl . TRIANGLE_STRIP , offset , vertexCount ) ; target . cctx . clearRect ( 0 , 0 , target . canvas . width , target . canvas . height ) ; target . cctx . drawImage ( this . _canvas , 0 , 0 ) ; } param value param outputType param reltime param options private _prepareValue ( value , outputType , reltime , options = { } ) { const def = options . defaultFloatComponent || 0 ; if ( outputType === '1i' ) { let i = 0 ; for ( const name in this . _userTextures ) { const testValue = val ( this , name , reltime ) ; if ( value === testValue ) value = Shader . INTERNAL_TEXTURE_UNITS + i ; i ++ ; } } if ( outputType === '3fv' ) { if ( Array . isArray ( value ) && ( value . length === 3 || value . length === 4 ) ) return value ; if ( typeof value === 'object' ) return [ value . r !== undefined ? value . r : def , value . g !== undefined ? value . g : def , value . b !== undefined ? value . b : def ] ; throw new Error ( `Invalid type: ${ outputType } or value: ${ value }` ) ; } if ( outputType === '4fv' ) { if ( Array . isArray ( value ) && value . length === 4 ) return value ; if ( typeof value === 'object' ) return [ value . r !== undefined ? value . r : def , value . g !== undefined ? value . g : def , value . b !== undefined ? value . b : def , value . a !== undefined ? value . a : def ] ; throw new Error ( `Invalid type: ${ outputType } or value: ${ value }` ) ; } return value ; } private static _initRectBuffers ( gl ) { const position = [ - 1.0 , 1.0 , 1.0 , 1.0 , - 1.0 , - 1.0 , 1.0 , - 1.0 ] ; const textureCoord = [ 0.0 , 0.0 , 1.0 , 0.0 , 0.0 , 1.0 , 1.0 , 1.0 ] ; return { position : Shader . _initBuffer ( gl , position ) , textureCoord : Shader . _initBuffer ( gl , textureCoord ) } ; } /**
     * Creates the quad covering the screen
     */ private static _initBuffer ( gl , data ) { const buffer = gl . createBuffer ( ) ; gl . bindBuffer ( gl . ARRAY_BUFFER , buffer ) ; gl . bufferData ( gl . ARRAY_BUFFER , new Float32Array ( data ) , gl . STATIC_DRAW ) ; return buffer ; } param options param options . target param options . level param options . internalFormat param options . srcFormat param options . srcType param options . wrapS param options . wrapT param options . minFilter param options . magFilter private static _loadTexture ( gl , source , options = { } ) { options = { ... Shader . _DEFAULT_TEXTURE_OPTIONS , ... options } ; const target = gl [ options . target ] ; const level = options . level ; const internalFormat = gl [ options . internalFormat ] ; const srcFormat = gl [ options . srcFormat ] ; const srcType = gl [ options . srcType ] ; const wrapS = gl [ options . wrapS ] ; const wrapT = gl [ options . wrapT ] ; const minFilter = gl [ options . minFilter ] ; const magFilter = gl [ options . magFilter ] ; const tex = gl . createTexture ( ) ; gl . bindTexture ( target , tex ) ; gl . texImage2D ( target , level , internalFormat , srcFormat , srcType , source ) ; const w = target instanceof HTMLVideoElement ? target . videoWidth : target . width ; const h = target instanceof HTMLVideoElement ? target . videoHeight : target . height ; gl . texParameteri ( target , gl . TEXTURE_MIN_FILTER , minFilter ) ; gl . texParameteri ( target , gl . TEXTURE_MAG_FILTER , magFilter ) ; if ( ( w && isPowerOf2 ( w ) ) && ( h && isPowerOf2 ( h ) ) ) { gl . texParameteri ( target , gl . TEXTURE_WRAP_S , wrapS ) ; gl . texParameteri ( target , gl . TEXTURE_WRAP_T , wrapT ) ; gl . generateMipmap ( target ) ; } else { if ( wrapS !== gl . CLAMP_TO_EDGE || wrapT !== gl . CLAMP_TO_EDGE ) console . warn ( 'Wrap mode is not CLAMP_TO_EDGE for a non-power-of-two texture. Defaulting to CLAMP_TO_EDGE' ) ; gl . texParameteri ( target , gl . TEXTURE_WRAP_S , gl . CLAMP_TO_EDGE ) ; gl . texParameteri ( target , gl . TEXTURE_WRAP_T , gl . CLAMP_TO_EDGE ) ; } return tex ; } private static _initShaderProgram ( gl , vertexSrc , fragmentSrc ) { const vertexShader = Shader . _loadShader ( gl , gl . VERTEX_SHADER , vertexSrc ) ; const fragmentShader = Shader . _loadShader ( gl , gl . FRAGMENT_SHADER , fragmentSrc ) ; const shaderProgram = gl . createProgram ( ) ; gl . attachShader ( shaderProgram , vertexShader ) ; gl . attachShader ( shaderProgram , fragmentShader ) ; gl . linkProgram ( shaderProgram ) ; if ( ! gl . getProgramParameter ( shaderProgram , gl . LINK_STATUS ) ) { console . warn ( 'Unable to link shader program: ' + gl . getProgramInfoLog ( shaderProgram ) ) ; return null ; } return shaderProgram ; } private static _loadShader ( gl , type , source ) { const shader = gl . createShader ( type ) ; gl . shaderSource ( shader , source ) ; gl . compileShader ( shader ) ; if ( ! gl . getShaderParameter ( shader , gl . COMPILE_STATUS ) ) { console . warn ( 'An error occured compiling shader: ' + gl . getShaderInfoLog ( shader ) ) ; gl . deleteShader ( shader ) ; return null ; } return shader ; } } const isPowerOf2 = value => ( value && ( value - 1 ) ) === 0 ;