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
504 lines (443 loc) • 16.5 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, baseclass){
// internal, drawing
var Shader = require('./shaderwebgl')
this.atConstructor = function(gldevice, view){
this.device = gldevice
this.view = view
this.drawcount = 0
view.drawpass = this
// lets do the flatten
this.pickmatrices = {
viewmatrix: mat4.identity(),
noscrollmatrix: mat4.identity()
}
this.colormatrices = {
viewmatrix: mat4.identity(),
noscrollmatrix: mat4.identity()
}
}
this.atDestroy = function(){
this.releaseTexture()
}
this.poolDrawTargets = function(){
var pools = this.device.drawtarget_pools
if(!this.drawtargets) return
for(var i = 0; i < this.drawtargets.length; i ++){
var dt = this.drawtargets[i]
if(!pools[dt]) pools[dt] = []
pools[dt].push(this[dt])
this[dt] = null
}
}
this.allocDrawTarget = function(width, height, view, drawtarget, passid, ratio, isfloat){
width = floor(width)
height = floor(height)
var Texture = this.device.Texture
if(!this.drawtargets) this.drawtargets = []
if(this.drawtargets.indexOf(drawtarget) === -1) this.drawtargets.push(drawtarget)
texturetype = Texture.RGBA|Texture.DEPTH|Texture.STENCIL
if (isfloat) {
texturetype |= Texture.FLOAT
}
var dt = this[drawtarget]
//var twidth = this.nextPowerTwo(layout.width* main_ratio), theight = this.nextPowerTwo(layout.height* main_ratio)
if(!dt){
// lets scan the pools for a suitable drawtarget, otherwise create it
var pool = this.device.drawtarget_pools[drawtarget]
if(pool && pool.length){
// first find a drawtarget with the same size
for(var i = 0; i < pool.length; i ++){
var tgt = pool[i]
if(!tgt) continue
var size = tgt.size
if(size[0] === width && size[1] === height){
// lets remove it from the pool
pool.splice(i,1)
dt = tgt
break
}
}
// then we find a drawtarget with the same passid as last time
if(!dt){
for(var i = 0; i < pool.length; i++){
var tgt = pool[i]
if(!tgt) continue
if(passid === tgt.passid){
dt = tgt
pool.splice(i,1)
break
}
}
}
}
// otherwise we create a new one
if(!dt){
dt = this[drawtarget] = Texture.createRenderTarget(view._viewport === '2d'?Texture.RGB:texturetype, width, height, this.device)
}
else this[drawtarget] = dt
dt.passid = passid
}
// make sure the drawtarget has the right size
var tsize = this[drawtarget].size
if(width !== tsize[0] || height !== tsize[1]){
// reset drawcount
this.drawcount = 0
this[drawtarget].delete()
this[drawtarget] = Texture.createRenderTarget(view._viewport === '2d'?Texture.RGB:texturetype, width, height, this.device)
}
this[drawtarget].ratio = ratio
}
this.calculateDrawMatrices = function(isroot, storage, pointerx, pointery){
var view = this.view
var scroll = view._scroll
var layout = view._layout
if(view._viewport === '2d'){
if(isroot && pointerx !== undefined){
var sizel = 0
var sizer = 1
mat4.ortho(scroll[0] + pointerx - sizel, scroll[0] + pointerx + sizer, scroll[1] + pointery - sizer, scroll[1] + pointery + sizel, -100, 100, storage.viewmatrix)
mat4.ortho(pointerx - sizel, pointerx + sizer, pointery - sizer, pointery + sizel, -100, 100, storage.noscrollmatrix)
}
else{
var zoom = view._zoom
if (isroot){
mat4.ortho(scroll[0], layout.width*zoom+scroll[0], scroll[1], layout.height*zoom+scroll[1], -100, 100, storage.viewmatrix)
mat4.ortho(0, layout.width, 0, layout.height, -100, 100, storage.noscrollmatrix)
}
else{
mat4.ortho(scroll[0], layout.width*zoom+scroll[0], layout.height*zoom+scroll[1], scroll[1], -100, 100, storage.viewmatrix)
mat4.ortho(0, layout.width, layout.height, 0, -100, 100, storage.noscrollmatrix)
}
}
}
else if(view._viewport === '3d'){
storage.perspectivematrix = mat4.perspective(view._fov * PI * 2/360.0 , layout.width/layout.height, view._nearplane, view._farplane)
storage.lookatmatrix = mat4.lookAt(view._camera, view._lookat, view._up)
storage.viewmatrix = mat4.mat4_mul_mat4(storage.lookatmatrix,storage.perspectivematrix);
}
}
function isInBounds2D(view, draw){
if(draw.noboundscheck) return true
var height = view._layout.height
var width = view._layout.width
var drawlayout = draw._layout
if(draw.parent && draw.parent !== view){
drawlayout.absx = draw.parent._layout.absx + drawlayout.left
drawlayout.absy = draw.parent._layout.absy + drawlayout.top
}
else{
drawlayout.absx = drawlayout.left
drawlayout.absy = drawlayout.top
}
// if(draw === view && view.sublayout){
// width = view.sublayout.width
// height = view.sublayout.height
// }
// // early out check
// if(draw !== view && !draw.noscroll){
// var scroll = view._scroll
// var zoom = view._zoom
// if( drawlayout.absy - scroll[1] > height * zoom || drawlayout.absy + drawlayout.height - scroll[1] < 0){
// return false
// }
// if(drawlayout.absx - scroll[0] > width * zoom || drawlayout.absx + drawlayout.width - scroll[0] < 0){
// return false
// }
// }
return true
}
this.nextItem = function(draw, nottype){
var view = this.view
var next = (draw === view || (!draw._viewport && draw._visible && draw._drawtarget !== nottype)) && draw.children[0], next_index = 0
while(!next){ // skip to parent next
if(draw === view) break
next_index = draw.draw_index + 1
draw = draw.parent
next = draw.children[next_index]
}
if(next === view) return undefined
if(next) next.draw_index = next_index
return next
}
this.drawPick = function(isroot, passid, pointerx, pointery, debug){
var view = this.view
var device = this.device
var layout = view._layout
var gl = device.gl
if(!layout || layout.width === 0 || isNaN(layout.width) || layout.height === 0 || isNaN(layout.height)) return
if(isroot){
if(!debug) this.allocDrawTarget(1, 1, this.view, 'pick_buffer', passid)
}
else{
var ratio = view._pixelratio
if(isNaN(ratio)) ratio = device.main_frame.ratio
ratio = 1
var twidth = layout.width * ratio, theight = layout.height * ratio
this.allocDrawTarget(twidth, theight, this.view, 'pick_buffer', passid)
}
gl.disable(gl.SCISSOR_TEST)
device.bindFramebuffer(this.pick_buffer || null)
device.clear(0,0,0,0)
var matrices = this.pickmatrices
this.calculateDrawMatrices(isroot, matrices, debug?undefined:pointerx, pointery)
// calculate the colormatrices too
//if(!this.colormatrices.initialized){
// this.calculateDrawMatrices(isroot, this.colormatrices)
//}
var pickguid = vec3()
pickguid[0] = passid/255//(((passid)*131)%256)/255
// modulo inverse: http://www.wolframalpha.com/input/?i=multiplicative+inverse+of+31+mod+256
var pick_id = 0
var draw = view
while(draw){
draw.draw_dirty &= 1
pick_id+= draw.pickrange;
if(!draw._visible || draw._drawtarget==='color' || draw._first_draw_pick && view._viewport === '2d' && draw.boundscheck && !isInBounds2D(view, draw)){ // do early out check using bounding boxes
}
else{
draw._first_draw_pick = 1
var id = pick_id//(pick_id*29401)%65536
pickguid[1] = (id&255)/255
pickguid[2] = (id>>8)/255
draw.pickguid = pickguid[0]*255<<16 | pickguid[1]*255 << 8 | pickguid[2]*255
draw.viewmatrix = matrices.viewmatrix
if(!draw._visible) continue
if(draw._viewport && draw.drawpass !== this && draw.drawpass.pick_buffer){
// ok so the pick pass needs the alpha from the color buffer
// and then hard forward the color
var blendshader = draw.shaders.viewportblend
if (view._viewport === '3d'){
// dont do this!
if (shader.depth_test_eq.func === 0) shader.depth_test = 'src_depth <= dst_depth'
}
else{
blendshader.depth_test = ''
}
blendshader.texture = draw.drawpass.pick_buffer
blendshader._width = draw.layout.width
blendshader._height = draw.layout.height
blendshader.drawArrays(this.device)
}
else{
//draw.updateShaders()
// alright lets iterate the shaders and call em
var shaders = draw.shader_draw_list
for(var j = 0; j < shaders.length; j++){
var shader = shaders[j]
if(view._viewport === '3d'){
if (shader.depth_test_eq.func == 0) shader.depth_test = 'src_depth <= dst_depth'
}
shader.pickguid = pickguid
if(!shader.visible) continue
if(draw.pickalpha !== undefined)shader.pickalpha = draw.pickalpha
if(shader.noscroll) draw.viewmatrix = matrices.noscrollmatrix
else draw.viewmatrix = matrices.viewmatrix
shader.drawArrays(this.device, 'pick')
}
}
}
draw = this.nextItem(draw, 'color')
}
}
this.getDrawID = function(id){
var view = this.view
var draw = view
var pick_id = 0
while(draw){
if(id > pick_id && id <= pick_id + draw.pickrange){
draw.last_pick_id = (pick_id + draw.pickrange) - id
return draw
}
pick_id += draw.pickrange
draw = this.nextItem(draw)
}
}
// accepts a view reference and takes the viewportblend shader
this.drawBlend = function(draw){
if(!draw.drawpass.color_buffer){
console.error("Null color_buffer detected, did you forget sizing/flex:1 on your 3D viewport?", draw)
}
else {
// ok so when we are drawing a pick pass, we just need to 1 on 1 forward the color data
// lets render the view as a layer
var blendshader = draw.shaders.viewportblend
if (this.view._viewport === '3d'){
blendshader.depth_test = 'src_depth <= dst_depth'
}
else{
blendshader.depth_test = ''
}
blendshader.texture = draw.drawpass.blendbuffer
blendshader.width = draw._layout.width
blendshader.height = draw._layout.height
blendshader.drawArrays(this.device)
}
}
this.drawNormal = function(draw, view, matrices){
draw.updateShaders()
var vtx_count = 0
// alright lets iterate the shaders and call em
var shaders = draw.shader_draw_list
for(var j = 0; j < shaders.length; j++){
// lets draw em
var shader = shaders[j]
if(view._viewport === '3d'){
if(shader.depth_test_eq.func === 0)
shader.depth_test = 'src_depth < dst_depth'
}
if(shader.pickonly || !shader.visible) continue // was pick only
shader.view = draw
if(shader.atDraw) shader.atDraw(draw)
// we have to set our guid.
if(shader.noscroll) draw.viewmatrix = matrices.noscrollmatrix
else draw.viewmatrix = matrices.viewmatrix
vtx_count += shader.drawArrays(this.device)
}
return vtx_count
}
// currently renders into color_buffer
this.drawColor = function(isroot, time, clipview){
var view = this.view
var device = this.device
var layout = view._layout
var gl = device.gl
var dom_count = 0
var vtx_count = 0
if(!layout || layout.width === 0 || isNaN(layout.width) || layout.height === 0 || isNaN(layout.height)) return
// lets see if we need to allocate our framebuffer..
if(!isroot){
var ratio = view._pixelratio
if(isNaN(ratio)) ratio = device.main_frame.ratio
var twidth = layout.width * ratio, theight = layout.height * ratio
this.allocDrawTarget(twidth, theight, this.view, 'color_buffer', null, ratio)
if (view.passes > 0) {
var buffers = view.passes;
for (var i = 0; i < view.passes; i++) {
var usefloat = view.shaders['pass'+i].usefloat
// allocate a framebuffer for each pass
this.allocDrawTarget(twidth, theight, this.view, 'framebuffer' + i, null, ratio, usefloat)
if (view.shaders['pass'+i].doublebuffer) {
// allocate twice as many buffers
this.allocDrawTarget(twidth, theight, this.view, 'framebuffera' + i, null, ratio, usefloat)
}
}
}
}
this.device.bindFramebuffer(this.color_buffer || null)
if(layout.width === 0 || layout.height === 0) return false
var hastime = false
var zoom = view._zoom
var matrices = this.colormatrices
this.calculateDrawMatrices(isroot, matrices);
view.colormatrices = matrices
gl.disable(gl.SCISSOR_TEST)
if(isroot){
/*
if(clipview){
var ratio = this.device.frame.ratio
var xs = this.device.frame.size[0] / ratio
var ys = this.device.frame.size[1] / ratio
var clayout = clipview._layout
var c1 = vec2.mul_mat4(vec2(0, 0),clipview.viewportmatrix)
var c2 = vec2.mul_mat4(vec2(clayout.width, clayout.height),clipview.viewportmatrix)
var x1 = c1[0], y1 = c1[1], x2 = c2[0] - x1, y2 = c2[1] - y1
gl.enable(gl.SCISSOR_TEST)
gl.scissor((x1)*ratio, (ys - y2 - y1)*ratio, x2 * ratio, (y2)*ratio)//x1*2, y1*2, x2*2, y2*2)
}
*/
}
//console.log(matrices.viewmatrix)
device.clear(view._clearcolor)
// this is a tree walk down a view port tree. Every viewport that is 2d/3d is a render-to-texture pass.
var draw = view
while(draw){
draw.draw_dirty &= 2
//}
//for(var dl = this.draw_list, i = 0; i < dl.length; i++){
// var draw = dl[i]
if(!draw._visible || draw._drawtarget==='pick' || draw._first_draw_color && view._viewport === '2d' && draw.boundscheck && !isInBounds2D(view, draw)){ // do early out check using bounding boxes
}
else{
draw._first_draw_color = 1
//if(view.constructor.name === 'slideviewer')console.log('here',draw.constructor.name, draw.text)
draw._time = time
if(draw._listen_time || draw.ontime) hastime = true
draw.viewmatrix = matrices.viewmatrix
if(draw.atDraw && draw.atDraw(this)) hastime = true
// if we are hitting child node that is a 2d/3d viewport,
// and we aren't the root/canvas.
if(draw._viewport && draw.drawpass !== this){
// blend the already rendered child texture into the parent viewport.
this.drawBlend(draw)
}
else{
// otherwise, just draw all the shaders into the parent viewport.
vtx_count += this.drawNormal(draw, view, matrices)
}
if(draw.debug_view){
this.debugrect.view = draw
this.debugrect.drawArrays(this.device)
}
}
draw = this.nextItem(draw, 'pick')
}
if (view.passes > 0) {
// TODO: we have multiple passes, ignore pick_buffer it won't work.
for (var i = 0; i < view.passes; i++) {
var shader = view.shaders['pass' + i]
// pass in the draw count so the shader can behave differently on the first draw
shader.drawcount = view.drawpass.drawcount
// decide which buffers to use
var writebuffername = 'framebuffer'
var readbuffername = 'framebuffer'
if (shader.doublebuffer) {
var readbuffername = 'framebuffera'
if (shader.drawcount % 2) {
// Alternate buffer every other draw
writebuffername = 'framebuffera'
readbuffername = 'framebuffer'
}
}
device.bindFramebuffer(this[writebuffername + i])
device.clear(view._clearcolor)
// Add references so they work inside the shader
shader.framebuffer = this.color_buffer
for (var j = 0; j < 10; j++) {
// add pass0..9 references to corresponding buffers
shader['pass' + j] = this[readbuffername + j]
}
// set the texture to use its own framebuffer
shader.texture = this[writebuffername + i]
shader.width = view._layout.width
shader.height = view._layout.height
// draw it into the framebuffer
shader.drawArrays(this.device)
}
// draw the final pass
this.blendbuffer = shader.texture
} else {
// draw the ordinary color buffer
this.blendbuffer = this.color_buffer
}
view.drawpass.drawcount++
return hastime
}
/*
var DebugRect = define.class(Shader, function(){
this.view = {totalmatrix:mat4(), viewmatrix:mat4(), layout:{width:0, height:0}}
this.mesh = vec2.array()
this.mesh.pushQuad(0,0,1,0,0,1,1,1)
this.position = function(){
return vec4(vec2(mesh.x * view.layout.width, mesh.y * view.layout.height), 0, 1) * view.totalmatrix * view.viewmatrix
}
this.color = function(){
return vec4(1,0,0,0.5)
}
})
*/
})