const RELEASE = false ; function assert ( condition ) { if ( ! RELEASE ) { if ( ! condition ) throw new Error ( "Assertion failed." ) ; } } function appendOne < T > ( ts , t ) { if ( ts . indexOf ( t ) === - 1 ) ts . push ( t ) ; } function removeOne < T > ( ts , t ) { const index = ts . indexOf ( t ) ; if ( index !== - 1 ) ts . splice ( index , 1 ) ; } function TEXTURE_N ( gl , index ) { assert ( index >= 0 && index <= 31 ) ; return ( gl . TEXTURE0 + index ) as GLenum ; } export namespace Graphics { export class Rect { constructor ( public x = 0 , public y = 0 , public width = 0 , public height = 0 ) { } set ( x , y , width , height ) { this . x = x ; this . y = y ; this . width = width ; this . height = height ; } equals ( other ) { return ( this . x === other . x && this . y === other . y && this . width === other . width && this . height === other . height ) ; } } export class Color { constructor ( public redF , public greenF , public blueF , public alphaF ) { } static TRANSPARENT = new Color ( 0 , 0 , 0 , 0 ) ; } export enum BlendOperation { ZERO = 0 , ONE = 1 , SOURCE_COLOR = 2 , TARGET_COLOR = 3 , INVERSE_SOURCE_COLOR = 4 , INVERSE_TARGET_COLOR = 5 , SOURCE_ALPHA = 6 , TARGET_ALPHA = 7 , INVERSE_SOURCE_ALPHA = 8 , INVERSE_TARGET_ALPHA = 9 , CONSTANT = 10 , INVERSE_CONSTANT = 11 } export enum Primitive { TRIANGLES , TRIANGLE_STRIP } export abstract class Context { abstract addContextResetHandler ( callback ) ; abstract beginFrame ( ) ; abstract clear ( color ) ; abstract createMaterial ( format , vertexSource , fragmentSource ) ; abstract createTexture ( format , width , height , pixels ? ) ; abstract createRenderTarget ( texture ) ; abstract createVertexBuffer ( byteCount ) ; abstract currentRenderTarget : RenderTarget | null ; abstract draw ( primitive , material , vertices ) ; abstract endFrame ( ) ; abstract removeContextResetHandler ( callback ) ; abstract resize ( widthInPixels , heightInPixels , widthInAppUnits , heightInAppUnits ) ; abstract setRenderTarget ( renderTarget ) ; abstract setViewport ( x , y , width , height ) ; abstract viewport : Rect ; abstract widthInPixels : number ; abstract heightInPixels : number ; abstract renderTargetHeightInPixels : number ; abstract renderTargetWidthInPixels : number ; abstract setBlendState ( source , target ) ; setCopyBlendState ( ) { this . setBlendState ( BlendOperation . ONE , BlendOperation . ZERO ) ; } setAddBlendState ( ) { this . setBlendState ( BlendOperation . ONE , BlendOperation . ONE ) ; } setPremultipliedBlendState ( ) { this . setBlendState ( BlendOperation . ONE , BlendOperation . INVERSE_SOURCE_ALPHA ) ; } setUnpremultipliedBlendState ( ) { this . setBlendState ( BlendOperation . SOURCE_ALPHA , BlendOperation . INVERSE_SOURCE_ALPHA ) ; } } export interface Material { context : Context ; format : VertexFormat ; fragmentSource : string ; vertexSource : string ; setUniformBool ( name , x ) : void ; setUniformFloat ( name , x ) : void ; setUniformInt ( name , x ) : void ; setUniformVec2 ( name , x , y ) : void ; setUniformVec3 ( name , x , y , z ) : void ; setUniformVec4 ( name , x , y , z , w ) : void ; setUniformMat3 ( name , m00 , m01 , m02 , m10 , m11 , m12 , m20 , m21 , m22 ) : void ; setUniformSampler ( name , texture , index ) : void ; free ( ) : void ; } export enum AttributeType { FLOAT , BYTE } export function attributeByteLength ( type ) { return type == AttributeType . FLOAT ? 4 : 1 ; } export class Attribute { constructor ( readonly name , readonly type , readonly count , readonly byteOffset ) { } } export class VertexFormat { private _attributes : Attribute [ ] = [ ] ; private _stride = 0 ; get attributes ( ) { return this . _attributes ; } get stride ( ) { return this . _stride ; } add ( name , type , count ) { this . attributes . push ( new Attribute ( name , type , count , this . stride ) ) ; this . _stride += count * attributeByteLength ( type ) ; return this ; } } export abstract class VertexBuffer { abstract byteCount : number ; abstract context : Context ; abstract move ( sourceByteOffset , targetByteOffset , byteCount ) ; abstract upload ( bytes , byteOffset ? ) ; uploadFloat32Array ( floats ) { this . upload ( new Uint8Array ( floats . buffer ) , 0 ) ; } uploadFloats ( floats ) { this . uploadFloat32Array ( new Float32Array ( floats ) ) ; } abstract free ( ) ; } export enum PixelFilter { NEAREST , LINEAR } export enum PixelWrap { REPEAT , CLAMP } export class TextureFormat { constructor ( readonly minFilter , readonly magFilter , readonly wrap ) { } static LINEAR_CLAMP = new TextureFormat ( PixelFilter . LINEAR , PixelFilter . LINEAR , PixelWrap . CLAMP ) ; static LINEAR_MIN_NEAREST_MAG_CLAMP = new TextureFormat ( PixelFilter . LINEAR , PixelFilter . NEAREST , PixelWrap . CLAMP ) ; static NEAREST_CLAMP = new TextureFormat ( PixelFilter . NEAREST , PixelFilter . NEAREST , PixelWrap . CLAMP ) ; } export interface Texture { context : Context ; format : TextureFormat ; width : number ; height : number ; resize ( width , height , pixels ? ) : void ; setFormat ( format ) : void ; free ( ) : void ; } export interface RenderTarget { context : Context ; texture : Texture ; setColor ( texture ) : void ; free ( ) : void ; } } export namespace WebGL { export class Context extends Graphics . Context { private _attributeCount = 0 ; private _blendOperationMap : { [ key ] : GLenum ; } ; private _blendOperations = 0 ; private _contextResetHandlers : ( ( ) => void ) [ ] = [ ] ; private _currentClearColor = Graphics . Color . TRANSPARENT ; private _currentRenderTarget : RenderTarget | null = null ; private _defaultViewport = new Graphics . Rect ( ) ; private _forceStateUpdate = true ; private _generation = 1 ; private _gl : WebGLRenderingContext ; private _height = 0 ; private _oldBlendOperations = 0 ; private _oldRenderTarget : RenderTarget | null = null ; private _oldViewport = new Graphics . Rect ( ) ; private _width = 0 ; get widthInPixels ( ) { return this . _width ; } get heightInPixels ( ) { return this . _height ; } constructor ( canvas = document . createElement ( "canvas" ) ) { super ( ) ; let gl = canvas . getContext ( "webgl" , { alpha : false , antialias : false , depth : false , preserveDrawingBuffer : true , stencil : false , } ) ; if ( gl == null ) { throw new Error ( "Setup failure" ) ; } this . _gl = gl ; let style = canvas . style ; canvas . width = 0 ; canvas . height = 0 ; style . width = style . height = "0" ; canvas . addEventListener ( "webglcontextlost" , ( e ) => { e . preventDefault ( ) ; } ) ; canvas . addEventListener ( "webglcontextrestored" , this . handleWebglContextRestored ) ; this . _blendOperationMap = { [ Graphics . BlendOperation . ZERO ] : this . _gl . ZERO , [ Graphics . BlendOperation . ONE ] : this . _gl . ONE , [ Graphics . BlendOperation . SOURCE_COLOR ] : this . _gl . SRC_COLOR , [ Graphics . BlendOperation . TARGET_COLOR ] : this . _gl . DST_COLOR , [ Graphics . BlendOperation . INVERSE_SOURCE_COLOR ] : this . _gl . ONE_MINUS_SRC_COLOR , [ Graphics . BlendOperation . INVERSE_TARGET_COLOR ] : this . _gl . ONE_MINUS_DST_COLOR , [ Graphics . BlendOperation . SOURCE_ALPHA ] : this . _gl . SRC_ALPHA , [ Graphics . BlendOperation . TARGET_ALPHA ] : this . _gl . DST_ALPHA , [ Graphics . BlendOperation . INVERSE_SOURCE_ALPHA ] : this . _gl . ONE_MINUS_SRC_ALPHA , [ Graphics . BlendOperation . INVERSE_TARGET_ALPHA ] : this . _gl . ONE_MINUS_DST_ALPHA , [ Graphics . BlendOperation . CONSTANT ] : this . _gl . CONSTANT_COLOR , [ Graphics . BlendOperation . INVERSE_CONSTANT ] : this . _gl . ONE_MINUS_CONSTANT_COLOR , } ; } private handleWebglContextRestored = ( ) => { this . _attributeCount = 0 ; this . _currentClearColor = Graphics . Color . TRANSPARENT ; this . _forceStateUpdate = true ; this . _generation ++ ; for ( let handler of this . _contextResetHandlers ) { handler ( ) ; } } ; public testContextLoss ( ) { this . handleWebglContextRestored ( ) ; } get gl ( ) { return this . _gl ; } get generation ( ) { return this . _generation ; } addContextResetHandler ( callback ) { appendOne ( this . _contextResetHandlers , callback ) ; } removeContextResetHandler ( callback ) { removeOne ( this . _contextResetHandlers , callback ) ; } get currentRenderTarget ( ) { return this . _currentRenderTarget ; } beginFrame ( ) { this . setRenderTarget ( null ) ; } endFrame ( ) { } setBlendState ( source , target ) { this . _blendOperations = Context . _packBlendModes ( source , target ) ; } setViewport ( x , y , width , height ) { ( this . _currentRenderTarget != null ? this . _currentRenderTarget . viewport : this . _defaultViewport ) . set ( x , y , width , height ) ; } get viewport ( ) { return this . _currentRenderTarget != null ? this . _currentRenderTarget . viewport : this . _defaultViewport ; } get renderTargetWidthInPixels ( ) { return this . _currentRenderTarget != null ? this . _currentRenderTarget . viewport . width : this . _width ; } get renderTargetHeightInPixels ( ) { return this . _currentRenderTarget != null ? this . _currentRenderTarget . viewport . height : this . _height ; } draw ( primitive , material , vertices ) { this . _updateRenderTargetAndViewport ( ) ; Material . from ( material ) . prepare ( ) ; VertexBuffer . from ( vertices ) . prepare ( ) ; this . _updateFormat ( material . format ) ; this . _updateBlendState ( ) ; this . _gl . drawArrays ( primitive == Graphics . Primitive . TRIANGLES ? this . _gl . TRIANGLES : this . _gl . TRIANGLE_STRIP , 0 , Math . floor ( vertices . byteCount / material . format . stride ) ) ; this . _forceStateUpdate = false ; } resize ( widthInPixels , heightInPixels , widthInAppUnits , heightInAppUnits ) { let canvas = this . _gl . canvas as HTMLCanvasElement ; const bounds = canvas . getBoundingClientRect ( ) ; if ( this . _width === widthInAppUnits && this . _height === heightInPixels && bounds . width === widthInAppUnits && bounds . height === heightInAppUnits ) { return ; } let style = canvas . style ; canvas . width = widthInPixels ; canvas . height = heightInPixels ; style . width = `${ widthInAppUnits }px` ; style . height = `${ heightInAppUnits }px` ; this . setViewport ( 0 , 0 , widthInPixels , heightInPixels ) ; this . _width = widthInPixels ; this . _height = heightInPixels ; } clear ( color ) { this . _updateRenderTargetAndViewport ( ) ; this . _updateBlendState ( ) ; if ( color != this . _currentClearColor ) { this . _gl . clearColor ( color . redF , color . greenF , color . blueF , color . alphaF ) ; this . _currentClearColor = color ; } this . _gl . clear ( this . _gl . COLOR_BUFFER_BIT ) ; } setRenderTarget ( renderTarget ) { this . _currentRenderTarget = RenderTarget . from ( renderTarget ) ; } createMaterial ( format , vertexSource , fragmentSource ) { let material = new Material ( this , format , vertexSource , fragmentSource ) ; material . program ; return material ; } createVertexBuffer ( byteCount ) { assert ( byteCount > 0 && byteCount % 4 == 0 ) ; return new VertexBuffer ( this , byteCount ) ; } createTexture ( format , width , height , pixels ? ) { return new Texture ( this , format , width , height , pixels ) ; } createRenderTarget ( texture ) { return new RenderTarget ( this , Texture . from ( texture ) ) ; } private ANGLE_instanced_arrays : ANGLE_instanced_arrays | null = null ; private ANGLE_instanced_arrays_generation : number = - 1 ; getANGLE_instanced_arrays ( ) { if ( this . ANGLE_instanced_arrays_generation !== this . _generation ) { this . ANGLE_instanced_arrays = null ; } if ( ! this . ANGLE_instanced_arrays ) { this . ANGLE_instanced_arrays = this . gl . getExtension ( "ANGLE_instanced_arrays" ) ; if ( ! this . ANGLE_instanced_arrays ) { throw new Error ( "Failed to get extension ANGLE_instanced_arrays" ) ; } } return this . ANGLE_instanced_arrays ! ; } _updateRenderTargetAndViewport ( ) { let renderTarget = this . _currentRenderTarget ; let viewport = renderTarget != null ? renderTarget . viewport : this . _defaultViewport ; let gl = this . _gl ; if ( this . _forceStateUpdate || this . _oldRenderTarget != renderTarget ) { gl . bindFramebuffer ( gl . FRAMEBUFFER , renderTarget ? renderTarget . framebuffer : null ) ; this . _oldRenderTarget = renderTarget ; } if ( this . _forceStateUpdate || ! this . _oldViewport . equals ( viewport ) ) { gl . viewport ( viewport . x , this . renderTargetHeightInPixels - viewport . y - viewport . height , viewport . width , viewport . height ) ; this . _oldViewport . set ( viewport . x , viewport . y , viewport . width , viewport . height ) ; } } _updateBlendState ( ) { if ( this . _forceStateUpdate || this . _oldBlendOperations != this . _blendOperations ) { let gl = this . _gl ; let operations = this . _blendOperations ; let oldOperations = this . _oldBlendOperations ; let source = ( operations & 0xf ) as Graphics . BlendOperation ; let target = ( operations >> 4 ) as Graphics . BlendOperation ; assert ( source in this . _blendOperationMap ) ; assert ( target in this . _blendOperationMap ) ; if ( operations == Context . COPY_BLEND_OPERATIONS ) { gl . disable ( gl . BLEND ) ; } else { if ( this . _forceStateUpdate || oldOperations == Context . COPY_BLEND_OPERATIONS ) { gl . enable ( gl . BLEND ) ; } gl . blendFunc ( this . _blendOperationMap [ source ] , this . _blendOperationMap [ target ] ) ; } this . _oldBlendOperations = operations ; } } _updateFormat ( format ) { let gl = this . _gl ; let attributes = format . attributes ; let count = attributes . length ; for ( let i = 0 ; i < count ; i ++ ) { let attribute = attributes [ i ] ; let isByte = attribute . type == Graphics . AttributeType . BYTE ; gl . vertexAttribPointer ( i , attribute . count , isByte ? gl . UNSIGNED_BYTE : gl . FLOAT , isByte , format . stride , attribute . byteOffset ) ; } while ( this . _attributeCount < count ) { gl . enableVertexAttribArray ( this . _attributeCount ) ; this . _attributeCount ++ ; } while ( this . _attributeCount > count ) { this . _attributeCount -- ; gl . disableVertexAttribArray ( this . _attributeCount ) ; } this . _attributeCount = count ; } getWebGLInfo ( ) { const ext = this . gl . getExtension ( "WEBGL_debug_renderer_info" ) ; const renderer = ext ? this . gl . getParameter ( ext . UNMASKED_RENDERER_WEBGL ) : null ; const vendor = ext ? this . gl . getParameter ( ext . UNMASKED_VENDOR_WEBGL ) : null ; const version = this . gl . getParameter ( this . gl . VERSION ) ; return { renderer , vendor , version } ; } static from ( context ) { assert ( context == null || context instanceof Context ) ; return context as Context ; } static _packBlendModes ( source , target ) { return source | ( target << 4 ) ; } static COPY_BLEND_OPERATIONS = Context . _packBlendModes ( Graphics . BlendOperation . ONE , Graphics . BlendOperation . ZERO ) ; } abstract class Uniform { constructor ( protected readonly _material , protected readonly _name , protected _generation = 0 , protected _location = null , protected _isDirty = true ) { } abstract prepare ( ) ; get location ( ) : WebGLUniformLocation { let context = Context . from ( this . _material . context ) ; if ( this . _generation != context . generation ) { this . _location = context . gl . getUniformLocation ( this . _material . program , this . _name ) ; this . _generation = context . generation ; if ( ! RELEASE ) { let program = this . _material . program ; let gl = context . gl ; for ( let i = 0 , ii = gl . getProgramParameter ( program , gl . ACTIVE_UNIFORMS ) ; i < ii ; i ++ ) { let info = gl . getActiveUniform ( program , i ) ; if ( info && info . name == this . _name ) { assert ( info . size == 1 ) ; switch ( info . type ) { case gl . FLOAT : { assert ( this instanceof UniformFloat ) ; break ; } case gl . BOOL : { assert ( this instanceof UniformBool ) ; break ; } case gl . FLOAT_MAT3 : { assert ( this instanceof UniformMat3 ) ; break ; } case gl . FLOAT_VEC2 : { assert ( this instanceof UniformVec2 ) ; break ; } case gl . FLOAT_VEC3 : { assert ( this instanceof UniformVec3 ) ; break ; } case gl . FLOAT_VEC4 : { assert ( this instanceof UniformVec4 ) ; break ; } case gl . INT : { assert ( this instanceof UniformInt ) ; break ; } case gl . SAMPLER_2D : { assert ( this instanceof UniformSampler ) ; break ; } default : assert ( false ) ; } } } } } if ( ! this . _location ) { throw new Error ( "Failed to get uniform location" ) ; } return this . _location ; } } class UniformFloat extends Uniform { private _x = 0.0 ; set ( x ) { if ( x != this . _x ) { this . _x = x ; this . _isDirty = true ; } } prepare ( ) { let context = Context . from ( this . _material . context ) ; if ( this . _generation != context . generation || this . _isDirty ) { context . gl . uniform1f ( this . location , this . _x ) ; this . _isDirty = false ; } } } class UniformBool extends Uniform { private _x = false ; set ( x ) { if ( x != this . _x ) { this . _x = x ; this . _isDirty = true ; } } prepare ( ) { let context = Context . from ( this . _material . context ) ; if ( this . _generation != context . generation || this . _isDirty ) { context . gl . uniform1i ( this . location , this . _x ? 1 : 0 ) ; this . _isDirty = false ; } } } class UniformInt extends Uniform { private _x = 0 ; set ( x ) { if ( x != this . _x ) { this . _x = x ; this . _isDirty = true ; } } prepare ( ) { let context = Context . from ( this . _material . context ) ; if ( this . _generation != context . generation || this . _isDirty ) { context . gl . uniform1i ( this . location , this . _x ) ; this . _isDirty = false ; } } } class UniformVec2 extends Uniform { private _x = 0.0 ; private _y = 0.0 ; set ( x , y ) { if ( x != this . _x || y != this . _y ) { this . _x = x ; this . _y = y ; this . _isDirty = true ; } } prepare ( ) { let context = Context . from ( this . _material . context ) ; if ( this . _generation != context . generation || this . _isDirty ) { context . gl . uniform2f ( this . location , this . _x , this . _y ) ; this . _isDirty = false ; } } } class UniformVec3 extends Uniform { private _x = 0.0 ; private _y = 0.0 ; private _z = 0.0 ; set ( x , y , z ) { if ( x != this . _x || y != this . _y || z != this . _z ) { this . _x = x ; this . _y = y ; this . _z = z ; this . _isDirty = true ; } } prepare ( ) { let context = Context . from ( this . _material . context ) ; if ( this . _generation != context . generation || this . _isDirty ) { context . gl . uniform3f ( this . location , this . _x , this . _y , this . _z ) ; this . _isDirty = false ; } } } class UniformVec4 extends Uniform { private _x = 0.0 ; private _y = 0.0 ; private _z = 0.0 ; private _w = 0.0 ; set ( x , y , z , w ) { if ( x != this . _x || y != this . _y || z != this . _z || w != this . _w ) { this . _x = x ; this . _y = y ; this . _z = z ; this . _w = w ; this . _isDirty = true ; } } prepare ( ) { let context = Context . from ( this . _material . context ) ; if ( this . _generation != context . generation || this . _isDirty ) { context . gl . uniform4f ( this . location , this . _x , this . _y , this . _z , this . _w ) ; this . _isDirty = false ; } } } class UniformMat3 extends Uniform { private _values = [ 1.0 , 0.0 , 0.0 , 0.0 , 1.0 , 0.0 , 0.0 , 0.0 , 1.0 ] ; set ( m00 , m01 , m02 , m10 , m11 , m12 , m20 , m21 , m22 ) { UniformMat3 . _cachedValues [ 0 ] = m00 ; UniformMat3 . _cachedValues [ 1 ] = m10 ; UniformMat3 . _cachedValues [ 2 ] = m20 ; UniformMat3 . _cachedValues [ 3 ] = m01 ; UniformMat3 . _cachedValues [ 4 ] = m11 ; UniformMat3 . _cachedValues [ 5 ] = m21 ; UniformMat3 . _cachedValues [ 6 ] = m02 ; UniformMat3 . _cachedValues [ 7 ] = m12 ; UniformMat3 . _cachedValues [ 8 ] = m22 ; for ( let i = 0 ; i < 9 ; i ++ ) { if ( UniformMat3 . _cachedValues [ i ] != this . _values [ i ] ) { let swap = this . _values ; this . _values = UniformMat3 . _cachedValues ; UniformMat3 . _cachedValues = swap ; this . _isDirty = true ; break ; } } } prepare ( ) { let context = Context . from ( this . _material . context ) ; if ( this . _generation != context . generation || this . _isDirty ) { context . gl . uniformMatrix3fv ( this . location , false , this . _values ) ; this . _isDirty = false ; } } static _cachedValues = [ 1.0 , 0.0 , 0.0 , 0.0 , 1.0 , 0.0 , 0.0 , 0.0 , 1.0 ] ; } class UniformSampler extends Uniform { private _texture : Texture | null = null ; private _index = - 1 ; set ( texture , index ) { if ( this . _texture != texture || this . _index != index ) { this . _texture = Texture . from ( texture ) ; this . _index = index ; this . _isDirty = true ; } } prepare ( ) { let context = Context . from ( this . _material . context ) ; let gl = context . gl ; assert ( this . _texture == null || context . currentRenderTarget == null || this . _texture != context . currentRenderTarget . texture ) ; if ( this . _generation != context . generation || this . _isDirty ) { gl . uniform1i ( this . location , this . _index ) ; this . _isDirty = false ; } gl . activeTexture ( TEXTURE_N ( gl , this . _index ) ) ; gl . bindTexture ( gl . TEXTURE_2D , this . _texture != null && this . _texture . width > 0 && this . _texture . height > 0 ? this . _texture . texture : null ) ; } } class Material implements Graphics . Material { constructor ( private readonly _context , private readonly _format , private readonly _vertexSource , private readonly _fragmentSource , private readonly _uniformsMap = { } , private readonly _uniformsList = [ ] , private _generation = 0 , private _program = null ) { } get context ( ) { return this . _context ; } get format ( ) { return this . _format ; } get vertexSource ( ) { return this . _vertexSource ; } get fragmentSource ( ) { return this . _fragmentSource ; } free ( ) { if ( this . _program ) { this . _context . gl . deleteProgram ( this . _program ) ; } this . _generation = 0 ; } setUniformBool ( name , x ) { let uniform = this . _uniformsMap [ name ] || null ; if ( uniform == null ) { uniform = new UniformBool ( this , name ) ; this . _uniformsMap [ name ] = uniform ; this . _uniformsList . push ( uniform ) ; } assert ( uniform instanceof UniformBool ) ; ( uniform as UniformBool ) . set ( x ) ; } setUniformFloat ( name , x ) { let uniform = this . _uniformsMap [ name ] || null ; if ( uniform == null ) { uniform = new UniformFloat ( this , name ) ; this . _uniformsMap [ name ] = uniform ; this . _uniformsList . push ( uniform ) ; } assert ( uniform instanceof UniformFloat ) ; ( uniform as UniformFloat ) . set ( x ) ; } setUniformInt ( name , x ) { let uniform = this . _uniformsMap [ name ] || null ; if ( uniform == null ) { uniform = new UniformInt ( this , name ) ; this . _uniformsMap [ name ] = uniform ; this . _uniformsList . push ( uniform ) ; } assert ( uniform instanceof UniformInt ) ; ( uniform as UniformInt ) . set ( x ) ; } setUniformVec2 ( name , x , y ) { let uniform = this . _uniformsMap [ name ] || null ; if ( uniform == null ) { uniform = new UniformVec2 ( this , name ) ; this . _uniformsMap [ name ] = uniform ; this . _uniformsList . push ( uniform ) ; } assert ( uniform instanceof UniformVec2 ) ; ( uniform as UniformVec2 ) . set ( x , y ) ; } setUniformVec3 ( name , x , y , z ) { let uniform = this . _uniformsMap [ name ] || null ; if ( uniform == null ) { uniform = new UniformVec3 ( this , name ) ; this . _uniformsMap [ name ] = uniform ; this . _uniformsList . push ( uniform ) ; } assert ( uniform instanceof UniformVec3 ) ; ( uniform as UniformVec3 ) . set ( x , y , z ) ; } setUniformVec4 ( name , x , y , z , w ) { let uniform = this . _uniformsMap [ name ] || null ; if ( uniform == null ) { uniform = new UniformVec4 ( this , name ) ; this . _uniformsMap [ name ] = uniform ; this . _uniformsList . push ( uniform ) ; } assert ( uniform instanceof UniformVec4 ) ; ( uniform as UniformVec4 ) . set ( x , y , z , w ) ; } setUniformMat3 ( name , m00 , m01 , m02 , m10 , m11 , m12 , m20 , m21 , m22 ) { let uniform = this . _uniformsMap [ name ] || null ; if ( uniform == null ) { uniform = new UniformMat3 ( this , name ) ; this . _uniformsMap [ name ] = uniform ; this . _uniformsList . push ( uniform ) ; } assert ( uniform instanceof UniformMat3 ) ; ( uniform as UniformMat3 ) . set ( m00 , m01 , m02 , m10 , m11 , m12 , m20 , m21 , m22 ) ; } setUniformSampler ( name , texture , index ) { let uniform = this . _uniformsMap [ name ] || null ; if ( uniform == null ) { uniform = new UniformSampler ( this , name ) ; this . _uniformsMap [ name ] = uniform ; this . _uniformsList . push ( uniform ) ; } assert ( uniform instanceof UniformSampler ) ; ( uniform as UniformSampler ) . set ( texture , index ) ; } get program ( ) : WebGLProgram { let gl = this . _context . gl ; if ( this . _generation != this . _context . generation ) { this . _program = gl . createProgram ( ) ! ; this . _compileShader ( gl , gl . VERTEX_SHADER , this . vertexSource ) ; this . _compileShader ( gl , gl . FRAGMENT_SHADER , this . fragmentSource ) ; let attributes = this . format . attributes ; for ( let i = 0 ; i < attributes . length ; i ++ ) { gl . bindAttribLocation ( this . _program , i , attributes [ i ] . name ) ; } gl . linkProgram ( this . _program ) ; if ( ! gl . getProgramParameter ( this . _program , gl . LINK_STATUS ) ) { throw new Error ( `${ gl . getProgramInfoLog ( this . _program ) }` ) ; } this . _generation = this . _context . generation ; if ( ! RELEASE ) { for ( let attribute of attributes ) { for ( let i = 0 , ii = gl . getProgramParameter ( this . program , gl . ACTIVE_ATTRIBUTES ) ; i < ii ; i ++ ) { let info = gl . getActiveAttrib ( this . program , i ) ; if ( info && info . name == attribute . name ) { assert ( info . size == 1 ) ; switch ( attribute . count ) { case 1 : { assert ( info . type == gl . FLOAT ) ; break ; } case 2 : { assert ( info . type == gl . FLOAT_VEC2 ) ; break ; } case 3 : { assert ( info . type == gl . FLOAT_VEC3 ) ; break ; } case 4 : { assert ( info . type == gl . FLOAT_VEC4 ) ; break ; } default : { assert ( false ) ; } } } } } } } return this . _program ! ; } prepare ( ) { this . _context . gl . useProgram ( this . program ) ; for ( let uniform of this . _uniformsList ) { uniform . prepare ( ) ; } } _compileShader ( gl , type , source ) { let shader = gl . createShader ( type ) ; if ( ! shader ) { throw new Error ( "Failed to create shader" ) ; } gl . shaderSource ( shader , source ) ; gl . compileShader ( shader ) ; if ( ! gl . getShaderParameter ( shader , gl . COMPILE_STATUS ) ) { throw new Error ( `${ gl . getShaderInfoLog ( shader ) }` ) ; } if ( ! this . _program ) { throw new Error ( "Tried to attach shader before program was created" ) ; } gl . attachShader ( this . _program , shader ) ; } static from ( material ) { assert ( material == null || material instanceof Material ) ; return material as Material ; } } class VertexBuffer extends Graphics . VertexBuffer { private readonly _context : Context ; private _generation = 0 ; private _buffer : WebGLBuffer | null = null ; private _bytes : Uint8Array | null = null ; private _isDirty = true ; private _dirtyMin = VertexBuffer . INT_MAX ; private _dirtyMax = 0 ; private _totalMin = VertexBuffer . INT_MAX ; private _totalMax = 0 ; private _byteCount = 0 ; constructor ( context , byteCount ) { super ( ) ; this . _context = context ; this . _byteCount = byteCount ; this . _bytes = new Uint8Array ( byteCount ) ; } get context ( ) { return this . _context ; } get byteCount ( ) { return this . _byteCount ; } move ( sourceByteOffset , targetByteOffset , byteCount ) { assert ( byteCount >= 0 ) ; assert ( 0 <= sourceByteOffset && sourceByteOffset + byteCount <= this . _byteCount ) ; assert ( 0 <= targetByteOffset && targetByteOffset + byteCount <= this . _byteCount ) ; if ( this . _bytes && sourceByteOffset != targetByteOffset && byteCount != 0 ) { this . _bytes . set ( this . _bytes . subarray ( sourceByteOffset , this . _byteCount ) , targetByteOffset ) ; this . _growDirtyRegion ( Math . min ( sourceByteOffset , targetByteOffset ) , Math . max ( sourceByteOffset , targetByteOffset ) + byteCount ) ; } } upload ( bytes , byteOffset = 0 ) { assert ( 0 <= byteOffset && byteOffset + bytes . length <= this . _byteCount ) ; assert ( this . _bytes != null ) ; this . _bytes ! . set ( bytes , byteOffset ) ; this . _growDirtyRegion ( byteOffset , byteOffset + bytes . length ) ; } free ( ) { if ( this . _buffer ) { this . _context . gl . deleteBuffer ( this . _buffer ) ; } this . _generation = 0 ; } prepare ( ) { let gl = this . _context . gl ; if ( this . _generation !== this . _context . generation ) { this . _buffer = gl . createBuffer ( ) ; this . _generation = this . _context . generation ; this . _isDirty = true ; } gl . bindBuffer ( gl . ARRAY_BUFFER , this . _buffer ) ; if ( this . _isDirty ) { gl . bufferData ( gl . ARRAY_BUFFER , this . _byteCount , gl . DYNAMIC_DRAW ) ; this . _dirtyMin = this . _totalMin ; this . _dirtyMax = this . _totalMax ; this . _isDirty = false ; } if ( this . _dirtyMin < this . _dirtyMax ) { gl . bufferSubData ( gl . ARRAY_BUFFER , this . _dirtyMin , this . _bytes ! . subarray ( this . _dirtyMin , this . _dirtyMax ) ) ; this . _dirtyMin = VertexBuffer . INT_MAX ; this . _dirtyMax = 0 ; } } _growDirtyRegion ( min , max ) { this . _dirtyMin = Math . min ( this . _dirtyMin , min ) ; this . _dirtyMax = Math . max ( this . _dirtyMax , max ) ; this . _totalMin = Math . min ( this . _totalMin , min ) ; this . _totalMax = Math . max ( this . _totalMax , max ) ; } static INT_MAX = 0x7fffffff ; static from ( buffer ) { assert ( buffer == null || buffer instanceof VertexBuffer ) ; return buffer as VertexBuffer ; } } class Texture implements Graphics . Texture { constructor ( private readonly _context , private _format , private _width , private _height , private _pixels = null , private _texture = null , private _generation = 0 , private _isFormatDirty = true , private _isContentDirty = true ) { } get context ( ) { return this . _context ; } get format ( ) { return this . _format ; } get width ( ) { return this . _width ; } get height ( ) { return this . _height ; } resize ( width , height , pixels = null ) { this . _width = width ; this . _height = height ; this . _pixels = pixels ; this . _isContentDirty = true ; } setFormat ( format ) { if ( this . _format != format ) { this . _format = format ; this . _isFormatDirty = true ; } } get texture ( ) : WebGLTexture { let gl = this . _context . gl ; if ( this . _generation != this . _context . generation ) { this . _texture = gl . createTexture ( ) ; this . _generation = this . _context . generation ; this . _isFormatDirty = true ; this . _isContentDirty = true ; } if ( this . _isFormatDirty ) { gl . bindTexture ( gl . TEXTURE_2D , this . _texture ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MAG_FILTER , this . format . magFilter == Graphics . PixelFilter . NEAREST ? gl . NEAREST : gl . LINEAR ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MIN_FILTER , this . format . minFilter == Graphics . PixelFilter . NEAREST ? gl . NEAREST : gl . LINEAR ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_S , this . format . wrap == Graphics . PixelWrap . REPEAT ? gl . REPEAT : gl . CLAMP_TO_EDGE ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_T , this . format . wrap == Graphics . PixelWrap . REPEAT ? gl . REPEAT : gl . CLAMP_TO_EDGE ) ; this . _isFormatDirty = false ; } if ( this . _isContentDirty ) { gl . bindTexture ( gl . TEXTURE_2D , this . _texture ) ; if ( this . _pixels instanceof HTMLCanvasElement ) { gl . texImage2D ( gl . TEXTURE_2D , 0 , gl . RGBA , gl . RGBA , gl . UNSIGNED_BYTE , this . _pixels ) ; } else { gl . texImage2D ( gl . TEXTURE_2D , 0 , gl . RGBA , this . _width , this . _height , 0 , gl . RGBA , gl . UNSIGNED_BYTE , this . _pixels ) ; } this . _isContentDirty = false ; } return this . _texture ! ; } free ( ) { if ( this . texture ) { this . _context . gl . deleteTexture ( this . texture ) ; this . _generation = 0 ; } } static from ( texture ) { assert ( texture == null || texture instanceof Texture ) ; return texture as Texture ; } } class RenderTarget implements Graphics . RenderTarget { constructor ( private _context , private _texture , private _framebuffer = null , private _generation = 0 , private _isDirty = true , private _viewport = new Graphics . Rect ( ) ) { } get context ( ) { return this . _context ; } get texture ( ) { return this . _texture ; } get viewport ( ) { return this . _viewport ; } setColor ( texture ) { if ( this . _texture != texture ) { this . _texture = Texture . from ( texture ) ; this . _isDirty = true ; } } get framebuffer ( ) : WebGLFramebuffer { let gl = this . _context . gl ; let texture = this . _texture . texture ; if ( this . _generation != this . _context . generation ) { this . _framebuffer = gl . createFramebuffer ( ) ; this . _generation = this . _context . generation ; this . _isDirty = true ; } if ( this . _isDirty ) { gl . bindFramebuffer ( gl . FRAMEBUFFER , this . _framebuffer ) ; gl . framebufferTexture2D ( gl . FRAMEBUFFER , gl . COLOR_ATTACHMENT0 , gl . TEXTURE_2D , texture , 0 ) ; assert ( gl . checkFramebufferStatus ( gl . FRAMEBUFFER ) == gl . FRAMEBUFFER_COMPLETE ) ; this . _isDirty = false ; } return this . _framebuffer ! ; } free ( ) { if ( this . _framebuffer ) { this . _context . gl . deleteFramebuffer ( this . _framebuffer ) ; this . _generation = 0 ; } } static from ( renderTarget ) { assert ( renderTarget == null || renderTarget instanceof RenderTarget ) ; return renderTarget as RenderTarget | null ; } } }