import { DesignerNode , NodeInput } from "./designer/designernode" ; import { DesignerNodeConn } from "./designer/designerconnection" ; import { DesignerLibrary } from "./designer/library" ; import { Guid } from "./utils" ; import { ImageCanvas } from "./designer/imagecanvas" ; import { Image } from "./designer/image" ; import { buildShaderProgram } from "./designer/gl" ; import { IntProperty , FloatProperty , BoolProperty , EnumProperty , ColorProperty , Property , PropertyType } from "./designer/properties" ; import { Color } from "./designer/color" ; import { DesignerVariable , DesignerVariableType , DesignerNodePropertyMap } from "./designer/designervariable" ; export class NodeRenderContext { canvas : HTMLCanvasElement ; gl : WebGLRenderingContext ; fbo : WebGLFramebuffer ; inputs : NodeInput [ ] ; textureWidth : number ; textureHeight : number ; randomSeed : number ; } export class NodeRenderTimer { query : WebGLQuery ; ms : number ; node : DesignerNode ; } export class Designer { canvas : HTMLCanvasElement ; gl : WebGL2RenderingContext ; public texCoordBuffer : WebGLBuffer ; public posBuffer : WebGLBuffer ; vertexShaderSource : string ; fbo : WebGLFramebuffer ; thumbnailProgram : WebGLProgram ; randomSeed : number ; width : number ; height : number ; nodes : DesignerNode [ ] ; conns : DesignerNodeConn [ ] ; updateList : DesignerNode [ ] ; library : DesignerLibrary ; variables : DesignerVariable [ ] ; renderContext : NodeRenderContext ; renderTimers : NodeRenderTimer [ ] ; queryExt : any ; onthumbnailgenerated : ( DesignerNode , HTMLImageElement ) => void ; onnodetextureupdated : ( DesignerNode , number ) => void ; onnodetimeupdated : ( DesignerNode , number ) => void ; public constructor ( ) { this . width = 1024 ; this . height = 1024 ; this . randomSeed = 32 ; this . canvas = < HTMLCanvasElement > document . createElement ( "canvas" ) ; this . canvas . width = this . width ; this . canvas . height = this . height ; this . gl = this . canvas . getContext ( "webgl2" ) ; let result = this . gl . getExtension ( "EXT_disjoint_timer_query_webgl2" ) ; if ( result ) console . log ( "TIMER QUERY SUPPORTED" , result ) ; else console . log ( "TIMER QUERY NOT SUPPORTED" , result ) ; this . queryExt = result ; result = this . gl . getExtension ( "EXT_color_buffer_float" ) ; if ( ! result ) console . log ( "COLOR BUFFER FLOAT NOT SUPPORTED" , result ) ; result = this . gl . getExtension ( "OES_texture_float_linear" ) ; if ( ! result ) console . log ( "TEXTURE FLOAT LINEAR NOT SUPPORTED" , result ) ; result = this . gl . getExtension ( "EXT_texture_norm16" ) ; if ( ! result ) console . log ( "TEXTURE NORM16 NOT SUPPORTED" , result ) ; this . renderContext = new NodeRenderContext ( ) ; this . nodes = [ ] ; this . conns = [ ] ; this . updateList = [ ] ; this . variables = [ ] ; this . renderTimers = [ ] ; this . init ( ) ; } public calculateNodeProcessingTimes ( ) { const gl = this . gl ; let completeTimers = [ ] ; for ( let timer of this . renderTimers ) { var available = gl . getQueryParameter ( timer . query , gl . QUERY_RESULT_AVAILABLE ) ; var disjoint = gl . getParameter ( this . queryExt . GPU_DISJOINT_EXT as GLenum ) ; if ( available && ! disjoint ) { var timeElapsed = gl . getQueryParameter ( timer . query , gl . QUERY_RESULT ) ; timer . ms = timeElapsed / ( 1000 * 1000 ) ; completeTimers . push ( timer ) ; } } return completeTimers ; } public setTextureSize ( width , height ) { this . width = width ; this . height = height ; this . canvas . width = this . width ; this . canvas . height = this . height ; for ( const node of this . nodes ) { node . createTexture ( ) ; this . requestUpdate ( node ) ; } } public randomizeSeed ( ) { this . setRandomSeed ( Math . floor ( Math . random ( ) * 256 ) ) ; } public setRandomSeed ( newSeed ) { this . randomSeed = newSeed ; this . invalidateAllNodes ( ) ; } public getRandomSeed ( ) { return this . randomSeed ; } init ( ) { this . createVertexBuffers ( ) ; this . createFBO ( ) ; this . createThumbmailProgram ( ) ; } update ( ) { this . updateRenderTimers ( ) ; let updateQuota = 10000000000000 ; while ( this . updateList . length != 0 ) { for ( const node of this . updateList ) { if ( this . haveAllUpdatedLeftNodes ( node ) ) { this . generateImageFromNode ( node ) ; this . updateList . splice ( this . updateList . indexOf ( node ) , 1 ) ; node . needsUpdate = false ; updateQuota -- ; if ( updateQuota < 0 ) break ; } } } } updateRenderTimers ( ) { if ( ! this . onnodetimeupdated ) return ; let timers = this . calculateNodeProcessingTimes ( ) ; for ( let timer of timers ) { this . onnodetimeupdated ( timer . node , timer . ms ) ; } } haveAllUpdatedLeftNodes ( node ) { for ( const con of this . conns ) { if ( con . rightNode == node ) { if ( con . leftNode . needsUpdate == true ) { return false ; } } } return true ; } requestUpdate ( node ) { if ( this . updateList . indexOf ( node ) == - 1 ) { node . needsUpdate = true ; this . updateList . push ( node ) ; } for ( const con of this . conns ) { if ( con . leftNode == node ) { this . requestUpdate ( con . rightNode ) ; } } } public invalidateAllNodes ( ) { for ( const node of this . nodes ) { this . requestUpdate ( node ) ; } } setLibrary ( lib ) { this . library = lib ; } createNode ( name ) { const node = this . library . create ( name ) ; this . addNode ( node ) ; return node ; } createFBO ( ) { const gl = this . gl ; this . fbo = gl . createFramebuffer ( ) ; } createVertexBuffers ( ) { const gl = this . gl ; this . texCoordBuffer = gl . createBuffer ( ) ; gl . bindBuffer ( gl . ARRAY_BUFFER , this . texCoordBuffer ) ; gl . bufferData ( gl . ARRAY_BUFFER , new Float32Array ( [ 0.0 , 0.0 , 1.0 , 0.0 , 0.0 , 1.0 , 0.0 , 1.0 , 1.0 , 0.0 , 1.0 , 1.0 ] ) , gl . STATIC_DRAW ) ; this . posBuffer = gl . createBuffer ( ) ; gl . bindBuffer ( gl . ARRAY_BUFFER , this . posBuffer ) ; gl . bufferData ( gl . ARRAY_BUFFER , new Float32Array ( [ - 1.0 , - 1.0 , 0.0 , 1.0 , - 1.0 , 0.0 , - 1.0 , 1.0 , 0.0 , - 1.0 , 1.0 , 0.0 , 1.0 , - 1.0 , 0.0 , 1.0 , 1.0 , 0.0 ] ) , gl . STATIC_DRAW ) ; gl . bindBuffer ( gl . ARRAY_BUFFER , null ) ; } addNode ( node , init = true ) { this . nodes . push ( node ) ; if ( init ) { node . gl = this . gl ; node . designer = this ; node . _init ( ) ; } this . requestUpdate ( node ) ; } getNodeById ( nodeId ) { for ( const node of this . nodes ) { if ( node . id == nodeId ) return node ; } return null ; } addConnection ( leftNode , rightNode , rightIndex ) { const con = new DesignerNodeConn ( ) ; con . leftNode = leftNode ; con . rightNode = rightNode ; con . rightNodeInput = rightIndex ; this . conns . push ( con ) ; this . requestUpdate ( rightNode ) ; return con ; } removeConnection ( leftNode , rightNode , rightIndex ) { for ( const con of this . conns ) { if ( con . leftNode == leftNode && con . rightNode == rightNode && con . rightNodeInput == rightIndex ) { this . requestUpdate ( rightNode ) ; this . conns . splice ( this . conns . indexOf ( con ) , 1 ) ; return con ; } } return null ; } removeNode ( nodeId ) { const node = this . getNodeById ( nodeId ) ; if ( ! node ) { return null ; } this . nodes . splice ( this . nodes . indexOf ( node ) , 1 ) ; while ( this . updateList . indexOf ( node ) !== - 1 ) this . updateList . splice ( this . updateList . indexOf ( node ) , 1 ) ; } generateImage ( name ) { const node = this . getNodeByName ( name ) ; return this . generateImageFromNode ( node ) ; } generateImageFromNode ( node ) { const inputs = this . getNodeInputs ( node ) ; for ( const input of inputs ) { if ( input . node . needsUpdate ) { this . generateImageFromNode ( input . node ) ; input . node . needsUpdate = false ; this . updateList . splice ( this . updateList . indexOf ( input . node ) , 1 ) ; } } const gl = this . gl ; let context = this . renderContext ; context . gl = gl ; context . canvas = this . canvas ; context . inputs = inputs ; context . randomSeed = this . randomSeed ; context . fbo = this . fbo ; context . textureWidth = this . width ; context . textureHeight = this . height ; let dtInMs = 0 ; if ( node . isCpu ( ) ) { let startTime = Date . now ( ) ; node . render ( context ) ; let endTime = Date . now ( ) ; dtInMs = endTime - startTime ; } else { let query = gl . createQuery ( ) ; gl . beginQuery ( this . queryExt . TIME_ELAPSED_EXT , query ) ; node . render ( context ) ; gl . endQuery ( this . queryExt . TIME_ELAPSED_EXT ) ; let timer = new NodeRenderTimer ( ) ; timer . node = node ; timer . query = query ; timer . ms = 0 ; this . renderTimers . push ( timer ) ; dtInMs = - 1 ; } if ( this . onnodetextureupdated ) { this . onnodetextureupdated ( node , dtInMs ) ; } const thumb = this . generateThumbnailFromNode ( node ) ; if ( this . onthumbnailgenerated ) { this . onthumbnailgenerated ( node , thumb ) ; } return thumb ; } generateThumbnailFromNode ( node ) { const gl = this . gl ; gl . clear ( gl . COLOR_BUFFER_BIT | gl . DEPTH_BUFFER_BIT ) ; gl . useProgram ( this . thumbnailProgram ) ; const posLoc = gl . getAttribLocation ( this . thumbnailProgram , "a_pos" ) ; const texCoordLoc = gl . getAttribLocation ( this . thumbnailProgram , "a_texCoord" ) ; gl . bindBuffer ( gl . ARRAY_BUFFER , this . posBuffer ) ; gl . enableVertexAttribArray ( posLoc ) ; gl . vertexAttribPointer ( posLoc , 3 , gl . FLOAT , false , 0 , 0 ) ; gl . bindBuffer ( gl . ARRAY_BUFFER , this . texCoordBuffer ) ; gl . enableVertexAttribArray ( texCoordLoc ) ; gl . vertexAttribPointer ( texCoordLoc , 2 , gl . FLOAT , false , 0 , 0 ) ; gl . uniform1i ( gl . getUniformLocation ( this . thumbnailProgram , "tex" ) , 0 ) ; gl . activeTexture ( gl . TEXTURE0 ) ; gl . bindTexture ( gl . TEXTURE_2D , node . tex ) ; gl . drawArrays ( gl . TRIANGLES , 0 , 6 ) ; gl . disableVertexAttribArray ( posLoc ) ; gl . disableVertexAttribArray ( texCoordLoc ) ; return null ; } copyNodeTextureToImageCanvas ( node , canvas ) { const gl = this . gl ; gl . clear ( gl . COLOR_BUFFER_BIT | gl . DEPTH_BUFFER_BIT ) ; gl . useProgram ( this . thumbnailProgram ) ; const posLoc = gl . getAttribLocation ( this . thumbnailProgram , "a_pos" ) ; const texCoordLoc = gl . getAttribLocation ( this . thumbnailProgram , "a_texCoord" ) ; gl . bindBuffer ( gl . ARRAY_BUFFER , this . posBuffer ) ; gl . enableVertexAttribArray ( posLoc ) ; gl . vertexAttribPointer ( posLoc , 3 , gl . FLOAT , false , 0 , 0 ) ; gl . bindBuffer ( gl . ARRAY_BUFFER , this . texCoordBuffer ) ; gl . enableVertexAttribArray ( texCoordLoc ) ; gl . vertexAttribPointer ( texCoordLoc , 2 , gl . FLOAT , false , 0 , 0 ) ; gl . uniform1i ( gl . getUniformLocation ( this . thumbnailProgram , "tex" ) , 0 ) ; gl . activeTexture ( gl . TEXTURE0 ) ; gl . bindTexture ( gl . TEXTURE_2D , node . tex ) ; gl . drawArrays ( gl . TRIANGLES , 0 , 6 ) ; gl . disableVertexAttribArray ( posLoc ) ; gl . disableVertexAttribArray ( texCoordLoc ) ; canvas . copyFromCanvas ( this . canvas , true ) ; } createThumbmailProgram ( ) { const prog = buildShaderProgram ( this . gl , `precision mediump float;

        attribute vec3 a_pos;
        attribute vec2 a_texCoord;
            
        // the texCoords passed in from the vertex shader.
        varying vec2 v_texCoord;
            
        void main() {
            gl_Position = vec4(a_pos,1.0);
            v_texCoord = a_texCoord;
        }` , `precision mediump float;
        varying vec2 v_texCoord;
        uniform sampler2D tex;

        vec4 process(vec2 uv);
        
        void main() {
            gl_FragColor = texture2D(tex,v_texCoord);
        }` ) ; this . thumbnailProgram = prog ; } getNodeByName ( exportName ) { for ( const node of this . nodes ) { if ( node . exportName == exportName ) return node ; } return null ; } getNodeInputs ( node ) { const inputs = [ ] ; for ( const con of this . conns ) { if ( con . rightNode == node ) { const input = new NodeInput ( ) ; input . name = con . rightNodeInput ; input . node = con . leftNode ; inputs . push ( input ) ; } } return inputs ; } public addVariable ( name , displayName , varType ) { const variable = new DesignerVariable ( ) ; variable . type = varType ; variable . id = Guid . newGuid ( ) ; switch ( varType ) { case DesignerVariableType . Int : variable . property = new IntProperty ( name , displayName , 0 ) ; break ; case DesignerVariableType . Float : variable . property = new FloatProperty ( name , displayName , 0 ) ; break ; case DesignerVariableType . Bool : variable . property = new BoolProperty ( name , displayName , false ) ; break ; case DesignerVariableType . Enum : variable . property = new EnumProperty ( name , displayName , [ ] ) ; break ; case DesignerVariableType . Color : variable . property = new ColorProperty ( name , displayName , new Color ( ) ) ; break ; } this . variables . push ( variable ) ; return variable ; } public mapNodePropertyToVariable ( varName , node , nodePropName ) { const variable = this . findVariable ( varName ) ; if ( variable == null ) return ; const map = new DesignerNodePropertyMap ( ) ; map . node = node ; map . propertyName = nodePropName ; variable . nodes . push ( map ) ; } public setVariable ( name , value ) { const variable = this . findVariable ( name ) ; if ( variable ) { variable . property . setValue ( value ) ; for ( const nodeMap of variable . nodes ) { nodeMap . node . setProperty ( nodeMap . propertyName , value ) ; } } else { } } public findVariable ( name ) { for ( const variable of this . variables ) if ( variable . property . name == name ) return variable ; return null ; } public hasVariable ( name ) { for ( const variable of this . variables ) if ( variable . property . name == name ) return true ; return false ; } public variableCount ( ) { return this . variables . length ; } public save ( ) { const nodes = [ ] ; for ( const node of this . nodes ) { const n = { } ; n [ "id" ] = node . id ; n [ "typeName" ] = node . typeName ; n [ "exportName" ] = node . exportName ; n [ "randomSeed" ] = node . randomSeed ; const props = { } ; for ( const prop of node . properties ) { if ( prop . type == PropertyType . Image ) { let imageData = ( prop . getValue ( ) as Image ) . serialize ( ) ; props [ prop . name ] = imageData ; } else { props [ prop . name ] = prop . getValue ( ) ; } } n [ "properties" ] = props ; nodes . push ( n ) ; } const connections = [ ] ; for ( const con of this . conns ) { const c = { } ; c [ "id" ] = con . id ; c [ "leftNodeId" ] = con . leftNode . id ; c [ "leftNodeOutput" ] = con . leftNodeOutput ; c [ "rightNodeId" ] = con . rightNode . id ; c [ "rightNodeInput" ] = con . rightNodeInput ; connections . push ( c ) ; } const variables = [ ] ; for ( const dvar of this . variables ) { const v = { } ; v [ "id" ] = dvar . id ; v [ "type" ] = dvar . type ; v [ "property" ] = dvar . property ; const nodeIds = [ ] ; for ( const n of dvar . nodes ) { nodeIds . push ( { nodeId : n . node . id , name : n . propertyName } ) ; } v [ "linkedProperties" ] = nodeIds ; variables . push ( v ) ; } const data = { } ; data [ "nodes" ] = nodes ; data [ "connections" ] = connections ; data [ "variables" ] = variables ; return data ; } static load ( data , lib ) { const d = new Designer ( ) ; const nodes = data [ "nodes" ] ; for ( const node of nodes ) { const n = lib . create ( node [ "typeName" ] ) ; n . exportName = node [ "exportName" ] ; n . id = node [ "id" ] ; n . randomSeed = node [ "randomSeed" ] || 0 ; d . addNode ( n ) ; const properties = node [ "properties" ] ; for ( const prop in properties ) { n . setProperty ( prop , properties [ prop ] ) ; } } const connections = data [ "connections" ] ; for ( const con of connections ) { const left = d . getNodeById ( con . leftNodeId ) ; const right = d . getNodeById ( con . rightNodeId ) ; d . addConnection ( left , right , con . rightNodeInput ) ; } if ( data . variables ) { const variables = < DesignerVariable [ ] > data . variables ; for ( const v of variables ) { const dvar = d . addVariable ( v . property . name , v . property . displayName , v . type ) ; dvar . id = v . id ; switch ( dvar . type ) { case DesignerVariableType . Float : ( < FloatProperty > dvar . property ) . copyValuesFrom ( < FloatProperty > v . property ) ; break ; case DesignerVariableType . Int : ( < IntProperty > dvar . property ) . copyValuesFrom ( < IntProperty > v . property ) ; break ; case DesignerVariableType . Bool : ( < BoolProperty > dvar . property ) . copyValuesFrom ( < BoolProperty > v . property ) ; break ; case DesignerVariableType . Enum : ( < EnumProperty > dvar . property ) . copyValuesFrom ( < EnumProperty > v . property ) ; break ; case DesignerVariableType . Color : ( < ColorProperty > dvar . property ) . copyValuesFrom ( < ColorProperty > v . property ) ; break ; } for ( const lp of ( < any > v ) . linkedProperties ) { const node = d . getNodeById ( lp . nodeId ) ; d . mapNodePropertyToVariable ( v . property . name , node , lp . name ) ; } } } return d ; } }