import { SHADER_ATTRIBUTE , SHADER_UNIFORM , MATERIAL_CULL_MODE , MATERIAL_BLEND_MODE , SHADER_STAGE , MATERIAL_DEPTH_COMPARISON_MODE , TEXTURE_FORMAT , MATERIAL_COLOR_MASK } from "../constants" ; import { Shader } from "../" ; import { RenderPipelineGenerator , IRenderPipeline , IBindGroupResource } from "./RenderPipelineGenerator" ; import { Renderer , IUniformBindingEntry } from "../Renderer" ; import { Buffer } from "../Buffer" ; import { Texture } from "../Texture" ; import { Sampler } from "../Sampler" ; export interface IMaterialUniformOptions { id : number ; name : string ; type : SHADER_UNIFORM ; visibility ? : SHADER_STAGE ; isShared ? : boolean ; byteLength ? : number ; } export const MATERIAL_UNIFORM_DEFAULT_OPTIONS = { id : 0 , name : null , type : SHADER_UNIFORM . NONE , visibility : SHADER_STAGE . NONE , isShared : false , byteLength : 0x0 } ; export interface IMaterialAttributeOptions { id : number ; name ? : string ; type : SHADER_ATTRIBUTE ; byteOffset ? : number ; } export const MATERIAL_ATTRIBUTE_DEFAULT_OPTIONS = { id : 0 , name : null , type : SHADER_ATTRIBUTE . NONE , byteOffset : - 1 } ; export interface IMaterialShaderOptions { shader : Shader ; uniforms ? : IMaterialUniformOptions [ ] ; } export const MATERIAL_SHADER_DEFAULT_OPTIONS = { shader : null , uniforms : [ ] } ; export interface IMaterialOutputDepthOptions { format : TEXTURE_FORMAT ; comparisonMode ? : MATERIAL_DEPTH_COMPARISON_MODE ; } export const MATERIAL_OUTPUT_DEPTH_DEFAULT_OPTIONS = { format : TEXTURE_FORMAT . NONE , comparisonMode : MATERIAL_DEPTH_COMPARISON_MODE . LESS , } ; export interface IMaterialOutputColorOptions { format : TEXTURE_FORMAT ; mask ? : MATERIAL_COLOR_MASK ; blendMode ? : MATERIAL_BLEND_MODE ; } export const MATERIAL_OUTPUT_COLOR_DEFAULT_OPTIONS = { format : TEXTURE_FORMAT . NONE , mask : MATERIAL_COLOR_MASK . ALL , blendMode : MATERIAL_BLEND_MODE . NONE , } ; export interface IMaterialOptions { name ? : string ; attributes ? : IMaterialAttributeOptions [ ] ; cullMode ? : MATERIAL_CULL_MODE ; vertexShader : IMaterialShaderOptions ; fragmentShader : IMaterialShaderOptions ; depthOutput ? : IMaterialOutputDepthOptions ; colorOutputs : IMaterialOutputColorOptions [ ] ; } export const MATERIAL_DEFAULT_OPTIONS = { name : null , attributes : null , cullMode : MATERIAL_CULL_MODE . NONE , vertexShader : null , fragmentShader : null , depthOutput : null , colorOutputs : null , } ; export class Material { private _name : string = null ; private _attributes : IMaterialAttributeOptions [ ] = [ ] ; private _cullMode : MATERIAL_CULL_MODE = MATERIAL_CULL_MODE . NONE ; private _vertexShader : Shader = null ; private _fragmentShader : Shader = null ; private _uniforms : IMaterialUniformOptions [ ] = [ ] ; private _renderPipeline : IRenderPipeline = null ; private _bindGroupResources : IBindGroupResource [ ] = [ ] ; private _uniformBindingQueue : IUniformBindingEntry [ ] = [ ] ; private _uniformBindings : Map < string , ( Buffer | Sampler | Texture ) > = new Map ( ) ; private _attributeLayoutByteStride : number = 0 ; private _destroyedState : boolean = false ; private _needsRebuildState : boolean = false ; private _depthOutput : IMaterialOutputDepthOptions = null ; private _colorOutputs : IMaterialOutputColorOptions [ ] = [ ] ; private _rebuildCount : number = 0 ; param options public constructor ( options ? ) { options = Object . assign ( { } , MATERIAL_DEFAULT_OPTIONS , options ) ; options . vertexShader = Object . assign ( { } , MATERIAL_SHADER_DEFAULT_OPTIONS , options . vertexShader ) ; options . fragmentShader = Object . assign ( { } , MATERIAL_SHADER_DEFAULT_OPTIONS , options . fragmentShader ) ; this . setName ( options . name ) ; this . _attributes = options . attributes . map ( attr => Object . assign ( { } , MATERIAL_ATTRIBUTE_DEFAULT_OPTIONS , attr ) ) ; this . _cullMode = options . cullMode ; this . _vertexShader = options . vertexShader . shader ; this . _fragmentShader = options . fragmentShader . shader ; this . _depthOutput = options . depthOutput ? Object . assign ( { } , MATERIAL_OUTPUT_DEPTH_DEFAULT_OPTIONS , options . depthOutput ) : null ; this . _colorOutputs = options . colorOutputs . map ( c => Object . assign ( { } , MATERIAL_OUTPUT_COLOR_DEFAULT_OPTIONS , c ) ) ; this . _uniforms = this . _generateUniforms ( options ) ; this . _attributeLayoutByteStride = ( ( RenderPipelineGenerator . generateVertexStateDescriptor ( this ) as any ) . vertexBuffers [ 0 ] . arrayStride ) ; if ( ! this . _attributes . find ( l => l . name === "Position" ) ) { throw new ReferenceError ( `Attribute layout for material must contain a 'Position' entry` ) ; } this . _triggerRebuild ( ) ; } /**
     * The material name
     */ public getName ( ) { return this . _name ; } param value public setName ( value ) { this . _name = value ; } /**
     * The shader material uniforms
     */ public getUniforms ( ) { return this . _uniforms ; } param name public getUniformByName ( name ) { const uniforms = this . getUniforms ( ) ; for ( let ii = 0 ; ii < uniforms . length ; ++ ii ) { const uniform = uniforms [ ii ] ; if ( uniform . name === name ) return uniform ; } return null ; } param id public getBindingResourceById ( id ) { const bindGroupResources = this . _bindGroupResources ; for ( let ii = 0 ; ii < bindGroupResources . length ; ++ ii ) { const resource = bindGroupResources [ ii ] ; if ( resource !== null && resource . id === id ) return resource ; } return null ; } param name param data public bindUniform ( name , data ) { if ( this . isDestroyed ( ) ) return ; const uniform = this . getUniformByName ( name ) ; if ( uniform === null ) throw new ReferenceError ( `Failed to resolve material uniform for '${ name }'` ) ; if ( ! uniform . isShared ) throw new Error ( `Uniform '${ name }' isn't declared as shared and must be accessed through its mesh` ) ; if ( data !== null ) this . _uniformBindingQueue . push ( { id : uniform . id , data } ) ; const bindings = this . _uniformBindings ; bindings . set ( name , data ) ; } /**
     * The material attributes
     */ public getAttributes ( ) { return this . _attributes ; } /**
     * The material culling mode
     */ public getCullMode ( ) { return this . _cullMode ; } /**
     * The material vertex shader
     */ public getVertexShader ( ) { return this . _vertexShader ; } /**
     * The material fragment shader
     */ public getFragmentShader ( ) { return this . _fragmentShader ; } /**
     * The material depth output
     */ public getDepthOutput ( ) { return this . _depthOutput ; } /**
     * The material color outputs
     */ public getColorOutputs ( ) { return this . _colorOutputs ; } /**
     * A counter indicating how many times the material got rebuilt
     */ public getRebuildCount ( ) { return this . _rebuildCount ; } /**
     * The material render pipeline
     */ public getRenderPipeline ( ) { return this . _renderPipeline ; } /**
     * The byte stride of the attribute layout
     */ public getAttributeLayoutByteStride ( ) { return this . _attributeLayoutByteStride ; } /**
     * Indicates if the material is destroyed
     */ public isDestroyed ( ) { return this . _destroyedState ; } param renderer public build ( renderer ) { if ( this . isDestroyed ( ) ) return ; if ( ! this . _needsRebuild ( ) ) return ; const vertexShader = this . getVertexShader ( ) ; const fragmentShader = this . getFragmentShader ( ) ; if ( vertexShader !== null && ! vertexShader . getResource ( ) ) { vertexShader . create ( renderer , RenderPipelineGenerator . generateShaderModuleDescriptor ( vertexShader ) ) ; } if ( fragmentShader !== null && ! fragmentShader . getResource ( ) ) { fragmentShader . create ( renderer , RenderPipelineGenerator . generateShaderModuleDescriptor ( fragmentShader ) ) ; } this . _renderPipeline = RenderPipelineGenerator . generateRenderPipeline ( this , renderer . getDevice ( ) ) ; this . _bindGroupResources = RenderPipelineGenerator . generateBindGroupResources ( this , true , renderer . getDevice ( ) ) ; this . update ( renderer ) ; this . _rebuildCount ++ ; this . _resetRebuild ( ) ; } param renderer public update ( renderer ) { if ( this . isDestroyed ( ) ) return ; renderer . processUniformBindingQueue ( this . _uniformBindingQueue , this . _bindGroupResources ) ; renderer . processUniformBindings ( this . _uniformBindings ) ; } /**
     * Destroy this Object
     */ public destroy ( ) { this . _destroyedState = true ; this . _name = null ; this . _attributes = null ; this . _vertexShader = null ; this . _fragmentShader = null ; this . _uniforms = null ; this . _renderPipeline = null ; this . _uniformBindingQueue = null ; this . _bindGroupResources = null ; this . _uniformBindings = null ; } param options private _generateUniforms ( options ) { const out = [ ] ; const { vertexShader , fragmentShader } = options ; let bindingId = 0 ; for ( const uniform of vertexShader . uniforms ) { const normalized = Object . assign ( { } , MATERIAL_UNIFORM_DEFAULT_OPTIONS , uniform ) ; if ( normalized . visibility !== SHADER_STAGE . NONE ) throw new Error ( `Initial uniform visibility must be 'SHADER_STAGE.NONE'` ) ; normalized . visibility = SHADER_STAGE . VERTEX ; if ( normalized . id !== - 1 ) bindingId = normalized . id ; normalized . id = bindingId ; out . push ( normalized ) ; bindingId ++ ; } for ( const uniform of fragmentShader . uniforms ) { const normalized = Object . assign ( { } , MATERIAL_UNIFORM_DEFAULT_OPTIONS , uniform ) ; if ( normalized . visibility !== SHADER_STAGE . NONE ) throw new Error ( `Initial uniform visibility must be 'SHADER_STAGE.NONE'` ) ; normalized . visibility = SHADER_STAGE . FRAGMENT ; if ( normalized . id !== - 1 ) bindingId = normalized . id ; normalized . id = bindingId ; out . push ( normalized ) ; bindingId ++ ; } for ( const uniform of out ) { const duplicates = out . filter ( v => v . name === uniform . name ) ; if ( duplicates . length === 1 ) { } else if ( duplicates . length === 2 ) { const secondUniform = ( duplicates [ 0 ] !== uniform ? duplicates [ 0 ] : duplicates [ 1 ] ) ; if ( uniform . id !== secondUniform . id ) throw new Error ( `Ids of multi-uniforms '${ uniform . name }' are not identical` ) ; else if ( uniform . isShared !== secondUniform . isShared ) throw new Error ( `Invalid visibility state for uniform '${ uniform . name }'` ) ; else if ( uniform . visibility === secondUniform . visibility ) throw new Error ( `Duplicated uniform '${ uniform . name }'` ) ; else if ( uniform . type !== secondUniform . type ) throw new Error ( `Types of multi-uniforms '${ uniform . name }' are not identical` ) ; else if ( uniform . byteLength !== secondUniform . byteLength ) throw new Error ( `Sizes of multi-uniforms '${ uniform . name }' are not identical` ) ; uniform . visibility = SHADER_STAGE . VERTEX | SHADER_STAGE . FRAGMENT ; out . splice ( out . indexOf ( secondUniform ) , 1 ) ; } else { throw new Error ( `Uniform misusage for '${ uniform . name }'` ) ; } } return out ; } /**
     * Determines if the material has to build a new pipeline
     */ private _needsRebuild ( ) { return this . _needsRebuildState ; } /**
     * Triggers a rebuild of the material's pipeline
     */ private _triggerRebuild ( ) { this . _needsRebuildState = true ; } /**
     * Disables the rebuild trigger
     */ private _resetRebuild ( ) { this . _needsRebuildState = false ; } }