UNPKG

dreemgl

Version:

DreemGL is an open-source multi-screen prototyping framework for mediated environments, with a visual editor and shader styling for webGL and DALi runtimes written in JavaScript. As a toolkit for gpu-accelerated multiscreen development, DreemGL includes

763 lines (693 loc) 22.1 kB
/* DreemGL is a collaboration between Teeming Society & Samsung Electronics, sponsored by Samsung and others. Copyright 2015-2016 Teeming Society. Licensed under the Apache License, Version 2.0 (the "License"); You may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.*/ define.class('$system/parse/onejsserialize', function(require, exports, baseclass){ var gltypes = require('./gltypes') var OneJSParser = require('$system/parse/onejsparser') var OneJSGen = require('$system/parse/onejsgen.js') var Texture = require('$system/platform/$platform/texture$platform') //var vectorParser = require('$system/parse/vectorparser') var onejsparser = new OneJSParser() onejsparser.parser_cache = {} this.newState = function(context, varyings, uniforms){ return { depth: '', basename: '', stack: 0, context: context, attributes: {}, reference_is_attr: {}, varyings: varyings || {}, uniforms: uniforms || {}, structs: [], scope: {}, scopeio: {}, fnorder: [], functions: {}, astcache: {}, textures: {}, call:{deps:{}}, debug:{}, dump:{} } } // returns a gl type this.getType = function(infer, state){ // OK so, mesh doesnt get typed here but in glshader if(infer.fn_t === 'attribute') infer = infer.array.struct return gltypes.getType(infer) } this.resolveContext = function(node, context, name, basename, state){ // compute output name var outname if(basename) outname = basename + "_DOT_" + name else outname = name // uniform if(name === 'super'){ node.infer = {fn_t:'object', object:Object.getPrototypeOf(context)} return name } var attr_name = '_' + name var obj if(attr_name in context){ // its an attribute reference obj = context[attr_name] state.reference_is_attr[outname] = 1 } else{ obj = context[name] } if(typeof obj === 'undefined' && context.wildcard){ obj = context.wildcard } if(typeof obj === 'number'){ state.uniforms[outname] = node.infer = float32 return outname } // uniform if(typeof obj === 'boolean'){ node.infer = state.uniforms[outname] = bool return outname } // we are a custom type if(typeof obj === 'function' && obj.struct){ state.structs[name] = obj node.infer = { fn_t:'constructor', ret: obj } return outname } // attribute if(obj && typeof obj === 'object' && obj.struct){ // if we are an array, we are an attribute if(obj.isArray){ if(obj.struct.id){ state.attributes[outname] = obj.struct } node.infer = { fn_t: 'attribute', array: obj } } else{ node.infer = state.uniforms[outname] = obj.struct } return outname } else if (typeof obj === 'object'){ if(obj instanceof Texture.Image){ obj = context[name] = Texture.fromImage(obj) } node.infer = {fn_t:'object', object:obj} //if(state.basename) return state.basename + '_DOT_' + outname return outname } if(typeof obj === 'string' || typeof obj === 'function'){ var functionref = obj if(typeof obj === 'function'){ if(obj.is_wired){ state.uniforms[outname] = node.infer = float32 return outname } obj = obj.__string__ || (obj.__string__ = obj.toString()) } // lets parse and figure out what we are var ast = onejsparser.parse(obj).steps[0] // lets check what thing we have if(ast.type === 'Function'){ node.infer = {fn_t:'ast', context:context, basename:basename, name:outname, ast:ast, functionref:functionref, source:obj} return outname } // otherwise we recur on the ast in place //!TODO add proper error reporting inside this thing var fstate = Object.create(state) fstate.functionref = functionref fstate.source = obj fstate.callname = name return this.expand(ast, node, fstate) } } this.This = function(node, parent, state){ node.infer = {fn_t:'object', object:state.context} return state.basename } this.Id = function(node, parent, state){ // lets resolve ourself var name = node.name var def_t = define.typemap.types[name] if(def_t){ node.infer = {fn_t:'constructor', ret:def_t} return name } // lets check if we have a function var inscope = state.scope[name] if(inscope){ var io = state.scopeio[name] || 0 if(state.assignvarying){ // we have to assign an 'out' value to it state.scopeio[name] = io|1 } else{ state.scopeio[name] = io|2 } node.infer = inscope return name } // lets check gl variables var glvar = gltypes.variables[name] if(glvar){ node.infer = glvar return name } var glfn = gltypes.functions[name] if(glfn){ node.infer = {fn_t:'builtin', ret:glfn} return name } // gl functions // resolve on context var res = this.resolveContext(node, state.context, name, state.basename, state) if(res !== undefined) return res var varying = state.varyings[name] if(varying){ node.infer = varying return name } // what if we are an id and we cant resolve ourselves, if(state.assignvarying){ // lets check if our name is debug if(name === 'dbg'){ if(state.debug.type) throw new Error('Please only use one debug statement') state.debug.type = node.infer = state.assignvarying return 'dbg' } if(name === 'dump'){ //if(state.dump.type) throw new Error('Please only use one dump statement') state.dump.set = true//type = node.infer = state.assignvarying node.infer = {fn_t:'dump'} return 'dump' } state.varyings[name] = state.assignvarying return name } if(name in state.context){ throw new Error('Identifier "'+name +'" exists but is undefined in '+state.callname+'(...)\n'+state.source) } else { //var gen = new OneJSGen() // lets find the parent //var p = node //while(p.parent) p = p.parent //var str = gen.expand(p, null, {}) //var name = gen.expand(node, null, {}) console.error('Identifier cannot be resolved '+name+' in ' +state.callname+'()\n'+state.source) // make it throw in the function so we can find it //state.functionref() //state.fn() //throw new Error('Identifier cannot be resolved '+name+' in ' +state.callname+'()\n'+state.source) } } this.Key = function(node, parent, state){ // lets expand the object var obj = this.expand(node.object, node, state) //$$(obj, node.object.infer.id) // lets access the type property var key = node.key.name // lets fetch the type for our key access var infer = node.infer // we can also be referencing another object if(infer.fn_t === 'object'){ // lets switch context and expand id var ret = this.resolveContext(node, infer.object, key, obj, state) //if(key === 'bgcolor')console.log(ret) //if(ret === 'view_DOT_layoutview_DOT_width') console.log(key, obj, state.basename) if(ret === undefined){ console.log(infer.object, state) throw new Error('Cannot resolve ' + obj + '.' + key + ' in ' + state.callname + '(...)\n' + state.source) } return ret } var struct = infer if(infer.fn_t === 'attribute'){ // we can access uniform properties on arrays if(key in infer.array){ return this.resolveContext(node, infer.array, key, obj, state) } // otherwise fall through to attribute struct access struct = infer.array.struct } node.infer = struct.keyType(key) if(!node.infer){ console.log(infer.array.font) throw new Error('Cannot find property ' + obj + '.' + key + ' on ' + struct.id + ' in ' + state.callname + '(...)\n' + state.source) } if(!struct.id && infer.fn_t === 'attribute'){ var name = obj + '_DOT_' + key state.attributes[name] = node.infer return name } return (obj?obj + '.':'') + key } this.Index = function(node, parent, state){ // ok doing an index. lets look it up var obj = this.expand(node.object, node, state) if(!node.index){ // we are an [] attribute return obj } return obj + '[' + this.expand(node.index, node, state) + ']' } this.texture2DSampler = { MIN_FILTER: 'LINEAR', MAG_FILTER: 'LINEAR', WRAP_S: 'CLAMP_TO_EDGE', WRAP_T: 'CLAMP_TO_EDGE' } this.texture2DShorten = { MIN_FILTER:'I', MAG_FILTER:'A', WRAP_S:'S', WRAP_T:'T', NEAREST:'N', LINEAR:'L', NEAREST_MIPMAP_NEAREST:'NN', LINEAR_MIPMAP_NEAREST:'LN', NEAREST_MIPMAP_LINEAR:'NL', LINEAR_MIPMAP_LINEAR:'LL', REPEAT:'R', CLAMP_TO_EDGE:'C', MIRRORED_REPEAT:'M' } // custom handle function this.macro_texture2D = function(node, parent, state){ // lets build the args // the first argument is the texture object var args = node.args if(args.length < 2) throw new Error('texture2D needs atleast 2 arguments') // lets resolve the texture var tex_ast = args[0] var tex_obj, tex_name if(tex_ast.type === 'This'){ // process the this tex_obj = state.context tex_name = state.basename } else if(tex_ast.type == 'Id'){ // otherwise process the Id tex_name = tex_ast.name tex_obj = state.context[tex_name] } else{ tex_name = this.expand(tex_ast, node, state) tex_obj = tex_ast.infer.object } //else throw new Error('texture2D can only resolve single identifiers or this') //if(!(tex_obj instanceof Texture)) throw new Error('texture2D only accepts GLTexture') // lets get the sampler info var sampler = {} if(args.length >=3 ){ var obj = args[2] if(obj.type === 'Id') sampler = state.context[obj.name] else if(obj.type === 'Object'){ sampler = {} var keys = obj.keys for(var i = 0; i < keys.length; i++){ var elem = keys[i] sampler[elem.key.name] = elem.value.value } } else throw new Error('texture2D needs an object as argument 3') } var dec = '' for(var key in this.texture2DSampler){ if(!sampler[key]) sampler[key] = this.texture2DSampler[key] if(dec) dec += '_' dec += this.texture2DShorten[key] + this.texture2DShorten[sampler[key]] } // ok lets define the texture var final_name = tex_name + '_' + dec var out = state.textures[final_name] = { samplerdef:sampler, samplerid:dec, name:tex_name } node.infer = vec4 return 'texture2D('+final_name+','+this.expand(args[1], node, state)+')' } this.macro_typeof = function(node, parent, state){ var arg0 = node.args[0] this.expand(arg0, node, state) node.infer = { fn_t:'constructor', ret: arg0.infer } return node.infer.ret.id } this.Call = function(node, parent, state){ var fn = this.expand(node.fn, node, state) var type = node.fn.infer if(!type) throw new Error('Cannot infer type for call on '+fn) if(!type.fn_t) throw new Error('Call on a non function '+fn) // set the return type if(type.fn_t === 'constructor'){ node.infer = type.ret } if(type.fn_t == 'builtin'){ if(node.fn.name == 'texture2D'){ return this.macro_texture2D(node, parent, state) } if(node.fn.name == 'typeof'){ return this.macro_typeof(node, parent, state) } if(node.fn.name == 'debug'){ return this.macro_debug(node, parent, state) } } // lets process the arg var args = this.callArgs(node, parent, state) if(type.fn_t === 'ast'){ // we have to expand the function fn = type.name var undecorated = fn // lets annotate the function name by arg type if(node.args){ fn += '_T' for(var i = 0; i<node.args.length; i++){ var infer = node.args[i].infer if(!infer)throw new Error('Argument type cannot be inferred ' + fn + ' ' + i) fn += '_' + this.getType(infer, state) } } // lets // check if we already compiled it var fnobj = state.functions[fn] if(!fnobj){ state.functions[fn] = fnobj = { args: node.args, name: fn, undecorated:undecorated, deps: {} } state.call.deps[fn] = fnobj // set argument types on scope var fstate = Object.create(state) // mark it fstate.depth = '' fstate.functionref = type.functionref fstate.source = type.source fstate.callname = fn fstate.scope = {} fstate.scopeio = {} // we need to switch context fstate.context = type.context fstate.basename = type.basename || state.basename fstate.call = fnobj // ok well we have a function. lets expand it var fnast = type.ast fnobj.code = this.expand(type.ast, undefined, fstate), fnobj.return_t = fstate.call.return_t if(fstate.call.dump){ console.log(fnobj.code) } } else{ state.call.deps[fn] = fnobj } node.infer = fnobj.return_t } else if(type.fn_t === 'builtin'){ // lets check if we are a texture2D if(typeof type.ret === 'number'){ // use arg type node.infer = node.args[type.ret - 1].infer } else{ node.infer = type.ret } } return fn + '(' + args + ')' // alright so we are calling something, lets check what it is. // if its a function we need to start inlining it } this.Var = function(node, parent, state){ // ok we have to define our local vars on scope var defs = node.defs var ret = '' for(var i = 0;i < defs.length; i++){ var def = defs[i] // lets expand a define var init = this.expand(def, node, state) // lets check the infer on defs if(i) ret += ';\n' ret += this.getType(def.infer, state) + ' ' + init state.scope[def.id.name] = def.infer } return ret } this.Binary = function(node, parent, state){ var left = this.expand(node.left, node, state) var right = this.expand(node.right, node, state) if(this.needsParens(node, node.left)) left = '(' + left + ')' if(this.needsParens(node, node.right)) right = '(' + right + ')' // ok lets propagate the types and do auto type conversion. var left_t = this.getType(node.left.infer, state) var right_t = this.getType(node.right.infer, state) //console.log(left+node.op+right,left_t,right_t) // automatic type conversions if(left_t === 'int' && right_t === 'float'){ left = 'float(' + left + ')' node.infer = float32 } else if(right_t === 'int' && left_t === 'float'){ right = 'float(' + right + ')' } else if(left_t === 'mat4'){ if(right_t === 'vec2'){ right = 'vec4(' + right + ',0.,1.)' node.infer = vec4 } else if(right_t === 'vec3'){ right = 'vec4(' + right + ',1.)' node.infer = vec4 } else if(right_t === 'vec4'){ node.infer = vec4 } } else if(right_t === 'mat4'){ if(left_t === 'vec2'){ left = 'vec4(' + left + ',0.,1.)' node.infer = vec4 } else if(left_t === 'vec3'){ left = 'vec4(' + left + ',1.)' node.infer = vec4 } else if(left_t === 'vec4'){ node.infer = vec4 } } else if(left_t === 'mat2'){ if(right_t === 'vec2'){ node.infer = vec2 } else if(right_t === 'mat2'){ node.infer = mat2 } } else if(right_t === 'mat2'){ if(left_t === 'vec2'){ node.infer = vec2 } } else if(left_t === 'float'){ if(right_t === 'vec2'){ node.infer = vec2 } if(right_t === 'vec3'){ node.infer = vec3 } if(right_t === 'vec4'){ node.infer = vec4 } } else if(left_t === 'int'){ if(right_t === 'vec2'){ left = 'float(' + left + ')' node.infer = vec2 } if(right_t === 'vec3'){ left = 'float(' + left + ')' node.infer = vec3 } if(right_t === 'vec4'){ left = 'float(' + left + ')' node.infer = vec4 } } else if(right_t === 'int'){ if(left_t === 'vec2'){ right = 'float(' + right + ')' node.infer = vec2 } if(left_t === 'vec3'){ right = 'float(' + right + ')' node.infer = vec3 } if(left_t === 'vec4'){ right = 'float(' + right + ')' node.infer = vec4 } } return left + this.space + node.op + this.space + right } this.Condition = function(node, parent, state){ var ret = baseclass.Condition.call(this, node, parent, state) // lets compare the types of var then_t = node.then.infer var else_t = node.else.infer if(then_t !== else_t){ throw new Error('Please make sure a?b:c b and c are the same type: '+ret) } node.infer = then_t return ret } this.Assign = function(node, parent, state){ // ok we run our lhs in varying mode var right = this.expand(node.right, node, state) var lstate = Object.create(state) lstate.assignvarying = node.right.infer var left = this.expand(node.left, node, lstate) if(node.left.infer && node.left.infer.fn_t == 'dump'){ var type = gltypes.getType(node.right.infer) if(type == 'int') return 'dump = vec4(vec3(0.5) + vec3(0.5) * cos(6.28318 * (vec3(1.) * (float(' + right + ')/10.) + vec3(0.,0.33,0.67))),1.);\n' if(type == 'float') return 'dump = vec4(vec3(0.5) + vec3(0.5) * cos(6.28318 * (vec3(1.) * (' + right + ') + vec3(0.,0.33,0.67))),1.);\n' if(type == 'ivec2') return 'dump = vec4(vec2(' + right + ').xyx/255.,1.);\n' if(type == 'ivec3') return 'dump = vec4(vec3(' + right + ')/255.,1.);\n' if(type == 'vec2') return 'dump = vec4(vec2(' + right + ').xyx/255.,1.);\n' if(type == 'vec3') return 'dump = vec4(' + right + ', 1.);\n' } return left + this.space + node.op + this.space + right } this.Logic = function(node, parent, state){ // return type boolean var ret = baseclass.Logic.call(this, node, parent, state) node.infer = boolean return ret } this.Return = function(node, parent, state){ var ret = 'return' if(node.arg){ ret += ' ' + this.expand(node.arg, node, state) var type = node.arg.infer if(state.call.return_t !== undefined && state.call.return_t !== type){ throw new Error('function ' + state.call.name + 'has more than one return type') } state.call.return_t = type } else{ state.call.return_t = null } return ret } this.Function = function(node, parent, state){ var ret = state.call.name + '(' var args = state.call.args var params = node.params if(args.length !== params.length) throw new Error('Calling function '+state.call.name+'with wrong argcount '+args.length+' instead of '+params.length+' in '+state.callname+'(...)\n'+state.source) // lets generate function arguments for(var i = 0; i < args.length; i++){ // lets fetch the type var type = args[i].infer var name = this.expand(params[i], node, state) state.scope[name] = type // define scope variable } var body = this.expand(node.body, node, state) for(var i = 0; i < args.length; i++){ var name = this.expand(params[i], node, state) var type = args[i].infer var glname = this.getType(type, state) if(i) ret += ', ' // so how do we know if its an 'in' var io = state.scopeio[name] if(io&2) ret += 'in' if(io&1) ret += 'out' if(io) ret += ' ' ret += glname + ' '+ name } ret += ')' ret += body // return type var return_t = state.call.return_t if(!return_t) ret = 'void ' + ret else{ ret = this.getType(return_t, state) + ' ' + ret } return ret } this.Def = function(node, parent, state){ // lets not resolve our Id if(node.id.type !== 'Id') throw new Error('Def unsupported') var ret = node.id.name if(node.init){ var init = this.expand(node.init, node, state) if(node.init.type == 'Call' && node.init.fn.infer.fn_t === 'constructor'){ if(node.init.args && node.init.args.length) ret += ' = ' + init } else ret += ' = ' + init } return ret } this.Value = function(node, parent, state){ // check if our parent is a call var floatcast if(parent && parent.type === 'Call' && parent.fn.infer && parent.fn.infer.fn_t === 'constructor' && parent.infer.primary === float32){ floatcast = true } if(node.kind === 'num'){ var isfloat = node.raw.indexOf('.') !== -1 || node.raw.indexOf('e') !== -1 if(node.raw.indexOf('0x') === -1 && floatcast && !isfloat){ node.infer = float32 return node.raw + '.0' } if(isfloat) node.infer = float32 else node.infer = int32 return node.raw } if(node.kind === 'string'){ if(node.value === 'dump'){ state.call.dump = 1 return '' } if(node.value === 'trace'){ state.trace = 1 return '' } // parseColor it var color = vec4.parse(node.value) node.infer = vec4 return 'vec4(' + color[0]+','+color[1]+','+color[2]+','+color[3]+')' } if(node.raw === 'true' || node.raw === 'false'){ node.infer = bool } return node.raw } // illegal tags this.ForIn = function(){ throw new Error('Cannot use for in in shader') } this.ForOf = function(){ throw new Error('Cannot use for of in shader') } this.Struct = function(){ throw new Error('Cannot use struct in shader') } this.Comprehension = function(){ throw new Error('Cannot use comprehension in shader') } this.ThisCall = function(){ throw new Error('Cannot use thiscall in shader') } this.Template = function(){ throw new Error('Cannot use template in shader') } this.Throw = function(){ throw new Error('Cannot use throw in shader') } this.Try = function(){ throw new Error('Cannot use try in shader') } this.Enum = function(){ throw new Error('Cannot use enum in shader') } this.Define = function(){ throw new Error('Cannot use define in shader') } this.New = function(){ throw new Error('Cannot use new in shader') } this.Nest = function(){ throw new Error('Cannot use nest in shader') } this.Class = function(){ throw new Error('Cannot use class in shader') } this.Quote = function(){ throw new Error('Cannot use quote in shader') } this.Rest = function(){ throw new Error('Cannot use rest in shader') } this.Then = function(){ throw new Error('Cannot use then in shader') } this.Debugger = function(){ throw new Error('Cannot use debugger in shader') } this.With = function(){ throw new Error('Cannot use with in shader') } })