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
496 lines (424 loc) • 13.9 kB
JavaScript
/* 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(function(require, exports){
//internal, HACK to simulate gl (during dev)
gl = {
getUniformLocation: function() {}
,getAttribLocation: function() {}
,uniform4f: function() {}
,drawArrays: function() {}
,framebufferTexture2D: function() {}
,bindRenderbuffer: function() {}
,createRenderbuffer: function() { return {};}
,createFramebuffer: function() { return {};}
,bindFramebuffer: function() {}
,renderbufferStorage: function() {}
,framebufferRenderbuffer: function() {}
,viewport: function() {}
,clearColor: function() {}
,clear: function() {}
,createShader: function() {}
,attachShader: function() {}
,shaderSource: function() {}
,compileShader: function() {}
,createTexture: function() { return {};}
,activeTexture: function() {}
,bindTexture: function() {}
,getShaderParameter: function() {}
,getShaderInfoLog: function() {return ''}
,useProgram: function() {}
,createBuffer: function() {return 0;}
,deleteBuffer: function() {}
,bindBuffer: function() {}
,bufferData: function() {}
,enable: function() {}
,disable: function() {}
,blendEquation: function() {}
,blendFunc: function() {}
,enableVertexAttribArray: function() {}
,vertexAttribPointer: function() {}
,uniform1f: function(l,v) {console.log('uniform1f', l,v);return 0;}
,uniform2f: function() {return 0;}
,uniform3f: function() {return 0;}
,uniform4f: function() {return 0;}
,uniform1i: function() {return 0;}
,uniformMatrix4fv: function() {return 0;}
,pixelStorei: function() {}
,texParameterf: function() {}
,texParameteri: function() {}
,depthFunc: function() {}
,texImage2D: function() {}
};
this.Keyboard = require('./keyboarddali')
this.Pointer = require('./pointerdali')
// require embedded classes
this.Shader = require('./shaderdali')
this.Texture = require('./texturedali')
this.DrawPass = require('./drawpassdali')
this.preserveDrawingBuffer = false
this.premultipliedAlpha = false
this.antialias = false
this.debug_pick = false
this.document = null
this.atConstructor = function(previous){
// DaliApi is a static object to access the dali api
this.DaliApi = require('./dali_api')
this.extensions = previous && previous.extensions || {}
this.shadercache = previous && previous.shadercache || {}
this.drawpass_list = previous && previous.drawpass_list || []
this.layout_list = previous && previous.layout_list || []
this.pick_resolve = []
this.anim_redraws = []
this.animate_hooks = []
this.doPick = this.doPick.bind(this)
//TODO Use setTimeout for animation until dali animation ready (DALI)
this.time = 0;
this.animFrame = function(time){
//console.log('animFrame', time);
var interval = 16;
var cur = new Date().getTime();
var t = this.doColor(cur);
if(t){
this.anim_req = true
this.time += interval;
setTimeout(function() {this.animFrame(this.time);}.bind(this), interval)
}
else this.anim_req = false
//if(this.pick_resolve.length) this.doPick()
}.bind(this)
if(previous){
this.canvas = previous.canvas
this.gl = previous.gl
this.keyboard = previous.keyboard
this.pointer = previous.pointer
this.parent = previous.parent
this.drawtarget_pools = previous.drawtarget_pools
this.frame = this.main_frame = previous.main_frame
}
else{
this.frame =
this.main_frame = this.Texture.fromType('rgb_depth')
this.keyboard = new this.Keyboard(this)
this.pointer = new this.Pointer(this)
this.drawtarget_pools = {}
this.createContext()
this.createWakeupWatcher()
}
this.initResize()
//TODO Force an update
//this.doColor(0);
}
this.createWakeupWatcher = function(){
var last = Date.now()
setInterval(function(){
var now = Date.now()
if(now - last > 1000 && this.screen){
//this.doresize()
this.redraw()
this.screen.emit('wakeup')
}
last = now
}.bind(this), 200)
}
this.createContext = function(){
console.log('devicedali.createContext NOT implemented')
}
this.initResize = function(){
// Get size of stage
var size = this.DaliApi.dali.stage.getSize();
var dpi = this.DaliApi.dali.stage.getDpi();
this.width = size.x;
this.height = size.y;
this.ratio = dpi.x / dpi.y;
//console.log('initResize size ', size, dpi);
//HACK to emulate gl (to avoid javascript errors)
this.gl = gl;
//TODO Use real values
this.main_frame = {ratio: this.ratio, size: vec2(this.width, this.height)}
this.size = vec2(this.width, this.height);
}
this.clear = function(r, g, b, a){
if(arguments.length === 1){
a = r.length === 4? r[3]: 1, b = r[2], g = r[1], r = r[0]
}
if(arguments.length === 3) a = 1
DaliApi.setBackgroundColor([r,g,b,a]);
this.gl.clearColor(r, g, b, a)
this.gl.clear(this.gl.COLOR_BUFFER_BIT|this.gl.DEPTH_BUFFER_BIT|this.gl.STENCIL_BUFFER_BIT)
}
this.getExtension = function(name){
var ext = this.extensions[name]
if(ext) return ext
return this.extensions[name] = this.gl.getExtension(name)
}
this.redraw = function(){
if(this.anim_req) return
this.anim_req = true
//TODO
this.time = 0
setTimeout(function() {this.animFrame(this.time);}.bind(this), 0)
}
this.bindFramebuffer = function(frame){
if(!frame) frame = this.main_frame
this.frame = frame
this.size = vec2(frame.size[0]/frame.ratio, frame.size[1]/frame.ratio)
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, frame.glframe_buf || null)
this.gl.viewport(0, 0, frame.size[0], frame.size[1])
// Set the layer to use (root layer if frame.dali_layer doesn't exist)
//TODO When layers are used, use framebuffers to render them.
//this.DaliApi.setLayer(frame.dali_layer);
}
this.readPixels = function(x, y, w, h){
var buf = new Uint8Array(w * h * 4)
this.gl.readPixels(x , y , w , h, this.gl.RGBA, this.gl.UNSIGNED_BYTE, buf)
return buf
}
this.doPick = function(){
this.pick_timer = 0
var x = this.pick_x, y = this.pick_y
if(!this.first_draw_done){
this.doColor(this.last_time)
}
for(var i = 0, len = this.drawpass_list.length; i < len; i++){
var last = i === len - 1
var skip = false
var view = this.drawpass_list[i]
// little hack to dont use rtt if you only use a single view
if(view.parent == this.screen && view.flex ==1 && this.screen.children.length ===1){
skip = last = true
}
// lets set up glscissor on last
// and then read the goddamn pixel
if(last || view.draw_dirty & 2){
view.draw_dirty &= 1
view.drawpass.drawPick(last, i + 1, x, y, this.debug_pick)
}
if(skip){
this.screen.draw_dirty &= 1
break
}
}
// now lets read the pixel under the pointer
var pick_resolve = this.pick_resolve
this.pick_resolve = []
if(this.debug_pick){
var data = this.readPixels(x * this.ratio,this.main_frame.size[1] - y * this.ratio, 1, 1)
}
else{
var data = this.readPixels(0, 0, 1, 1)
}
// decode the pass and drawid
var passid = data[0]//(data[0]*43)%256
var drawid = (((data[2]<<8) | data[1]))//*60777)%65536
// lets find the view.
var passview = this.drawpass_list[passid]
var drawpass = passview && passview.drawpass
var view = drawpass && drawpass.draw_list[drawid]
while(view && view.nopick){
view = view.parent
}
for(var i = 0; i < pick_resolve.length; i++){
pick_resolve[i](view)
}
}
this.pickScreen = function(x, y){
// promise based pickray rendering
return new define.Promise(function(resolve, reject){
this.pick_resolve.push(resolve)
this.pick_x = x
this.pick_y = y
if(!this.pick_timer){
this.pick_timer = setTimeout(this.doPick, 0)
}
//this.doPick()
}.bind(this))
}
this.doColor = function(time){
if(!this.first_time) this.first_time = time
this.last_time = time
if(!this.screen) return
this.first_draw_done = true
var stime = (time - this.first_time) / 1000
//console.log(this.last_time - stime)
// lets layout shit that needs layouting.
var anim_redraw = this.anim_redraws
anim_redraw.length = 0
this.screen.doAnimation(stime, anim_redraw)
this.screen._maxsize =
this.screen._size = vec2(this.main_frame.size[0] / this.ratio, this.main_frame.size[1] / this.ratio)
// do all the animate hooks
var animate_hooks = this.animate_hooks
for(var i = 0; i < animate_hooks.length; i++){
var item = animate_hooks[i]
//console.log(item)
if(item.atAnimate(stime)){
anim_redraw.push(item)
}
}
// do the dirty layouts
for(var i = 0; i < this.layout_list.length; i++){
// lets do a layout?
var view = this.layout_list[i]
if(view.layout_dirty){
view.doLayout()
view.layout_dirty = false
}
}
// do the dirty matrix regen
for(var i = 0; i < this.layout_list.length; i++){
// lets do a layout?
var view = this.layout_list[i]
if(view.matrix_dirty){
view.updateMatrices(view.parent? view.parent.totalmatrix: undefined, view._viewport)
}
}
var hastime = false
var clipview = undefined
// lets draw draw all dirty passes.
for(var i = 0, len = this.drawpass_list.length; i < len; i++){
var view = this.drawpass_list[i]
//var skip = false
var last = i === len - 1
//if(view.parent == this.screen && view.flex == 1 && this.screen.children.length ===1){
// skip = last = true
//}
if(view.draw_dirty & 1 || last){
if(!last){
if(clipview === undefined) clipview = view
else clipview = null
}
hastime = view.drawpass.drawColor(last, stime, clipview)
view.draw_dirty &= 2
if(hastime){
anim_redraw.push(view)
}
}
//if(skip){
// this.screen.drawpass.calculateDrawMatrices(false, this.screen.drawpass.colormatrices);
// this.screen.draw_dirty &= 2
// break
//}
}
if(anim_redraw.length){
//console.log("REDRAWIN", anim_redraw)
var redraw = false
for(var i = 0; i < anim_redraw.length; i++){
var aredraw = anim_redraw[i]
if(!aredraw.atAfterDraw || aredraw.atAfterDraw()){
redraw = true
aredraw.redraw()
}
}
return redraw
}
return hastime
}
this.atNewlyRendered = function(view){
// if view is not a layer we have to find the layer, and regenerate that whole layer.
if(!view.parent) this.screen = view // its the screen
// alright lets do this.
var node = view
while(!node._viewport){
node = node.parent
}
if(!node.parent){ // fast path to chuck the whole set
//console.log("FLUSHING ALL")
// lets put all the drawpasses in a pool for reuse
for(var i = 0; i < this.drawpass_list.length; i++) {
var draw = this.drawpass_list[i]
draw.drawpass.poolDrawTargets()
draw.layout_dirty = true
draw.draw_dirth = 3
}
this.drawpass_list = []
this.layout_list = []
this.drawpass_idx = 0
this.layout_idx_first = 0
this.layout_idx = 0
this.addDrawPassRecursive(node)
this.first_draw_done = false
this.redraw()
}
else{ // else we remove drawpasses first then re-add them
this.removeDrawPasses(node)
this.layout_idx_first = this.layout_idx
this.addDrawPassRecursive(node)
}
node.relayout()
}
// internal, remove drawpasses related to a view
this.removeDrawPasses = function(view){
// we have to remove all the nodes which have view as their parent layer
var drawpass_list = this.drawpass_list
this.drawpass_idx = Infinity
for(var i = 0; i < drawpass_list.length; i++){
var node = drawpass_list[i]
while(node.parent && node !== view){
node = node.parent
}
if(node === view){
if(i < this.drawpass_idx) this.drawpass_idx = i
node.drawpass.poolDrawTargets()
drawpass_list.splice(i, 1)
break
}
}
if(this.drawpass_idx === Infinity) this.drawpass_idx = 0
// now remove all layouts too
this.layout_idx = Infinity
var layout_list = this.layout_list
for(var i = 0; i < layout_list.length; i++){
var pass = layout_list[i]
var node = pass
while(node.parent && node !== view){
node = node.parent
}
if(node === view){
if(i < this.layout_idx) this.layout_idx = i
layout_list.splice(i, 1)
}
}
if(this.layout_idx === Infinity) this.layout_idx = 0
}
// internal, add drawpasses and layouts recursively from a view
this.addDrawPassRecursive = function(view){
// lets first walk our children( depth first)
var children = view.children
if(children) for(var i = 0; i < children.length; i++){
this.addDrawPassRecursive(children[i])
}
// lets create a drawpass
if(view._viewport){
var pass = new this.DrawPass(this, view)
this.drawpass_list.splice(this.drawpass_idx,0,view)
this.drawpass_idx++
// lets also add a layout pass
if(isNaN(view._flex)){ // if not flex, make sure layout runs before the rest
// we are self contained
this.layout_list.splice(this.layout_idx_first,0,view)
}
else{ // we are flex, make sure we layout after
this.layout_list.splice(this.layout_idx,0,view)
}
//this.layout_idx++
}
}
this.relayout = function(){
var layout_list = this.layout_list
for(var i = 0; i < layout_list.length; i++){
view = layout_list[i]
if(!isNaN(view._flex) || view == this.screen){
view.relayout()
}
}
}
this.atResize = function(){
// lets relayout the whole fucker
this.relayout()
this.redraw()
// do stuff
}
})