//WWG Simple WebGL wrapper library // Version 0.9 // 2016-2017 wakufactory.jp // license: MIT // WWG // init(canvas) // init2(canvas) // loadAjax(src,opt) // loadImageAjax(src) // createRender() // Render // setRender(scene) // draw(update,cls) // addModel(model) // updateModel(name,mode,buf) // addTex(texobj) // loadTex(tex) function WWG() { this.version = "0.9.10" ; this.can = null ; this.gl = null ; this.vsize = {"float":1,"vec2":2,"vec3":3,"vec4":4,"mat2":4,"mat3":9,"mat4":16} ; } WWG.prototype.init = function(canvas,opt) { this.can = canvas ; var gl if(!((gl = canvas.getContext("experimental-webgl",opt)) || (gl = canvas.getContext("webgl",opt)))) { return false } ; if(!window.Promise) return false ; this.gl = gl this.ext_vao = gl.getExtension('OES_vertex_array_object'); if(this.ext_vao) { this.vao_create = function(){return this.ext_vao.createVertexArrayOES()} ; this.vao_bind = function(o){this.ext_vao.bindVertexArrayOES(o)} ; } this.ext_inst = gl.getExtension('ANGLE_instanced_arrays'); if(this.ext_inst) { this.inst_divisor = function(p,d){this.ext_inst.vertexAttribDivisorANGLE(p, d)} this.inst_draw = function(m,l,s,o,c){this.ext_inst.drawElementsInstancedANGLE(m,l, s, o, c);} this.inst_drawa = function(m,s,o,c) {this.ext_inst.drawArrayInstancedANGLE(m, s, o, c);} } this.ext_anis = gl.getExtension("EXT_texture_filter_anisotropic"); this.ext_ftex = gl.getExtension('OES_texture_float'); this.ext_mrt = gl.getExtension('WEBGL_draw_buffers'); if(this.ext_mrt) { this.mrt_att = this.ext_mrt.COLOR_ATTACHMENT0_WEBGL ; this.mrt_draw = function(b,d){return this.ext_mrt.drawBuffersWEBGL(b,d)} ; } this.ext_i32 = gl.getExtension('OES_element_index_uint') this.ext_mv = gl.getExtension('WEBGL_multiview'); if (this.ext_mv) console.log("MULTIVIEW extension is supported"); else { this.ext_mv = gl.getExtension('OVR_multiview'); if (this.ext_mv) console.log("OVR MULTIVIEW extension is supported"); } this.dmodes = {"tri_strip":gl.TRIANGLE_STRIP,"tri":gl.TRIANGLES,"points":gl.POINTS,"lines":gl.LINES,"line_strip":gl.LINE_STRIP } this.version = 1 ; return true ; } WWG.prototype.init2 = function(canvas,opt) { this.can = canvas ; var gl if(!((gl = canvas.getContext("experimental-webgl2",opt)) || (gl = canvas.getContext("webgl2",opt)))) { return false } ; if(!window.Promise) return false ; console.log("init for webGL2") ; this.gl = gl ; this.ext_vao = true ; this.vao_create = function(){ return this.gl.createVertexArray()} ; this.vao_bind = function(o){this.gl.bindVertexArray(o)} ; this.ext_inst = true ; this.inst_divisor = function(p,d){this.gl.vertexAttribDivisor(p, d)} this.inst_draw = function(m,l,s,o,c){this.gl.drawElementsInstanced(m,l, s, o, c);} this.inst_drawa = function(m,s,o,c) {this.gl.drawArrayInstanced(m, s, o, c);} this.ext_anis = gl.getExtension("EXT_texture_filter_anisotropic"); this.ext_ftex = true ; this.ext_mrt = (gl.getParameter(gl.MAX_DRAW_BUFFERS)>1) ; if(this.ext_mrt) { this.mrt_att = gl.COLOR_ATTACHMENT0 ; this.mrt_draw = function(b,d){return gl.drawBuffers(b,d)} ; } this.ext_i32 = true ; this.ext_mv = gl.getExtension('WEBGL_multiview'); if (this.ext_mv) console.log("MULTIVIEW extension is supported"); else { this.ext_mv = gl.getExtension('OVR_multiview'); if (this.ext_mv) console.log("OVR MULTIVIEW extension is supported"); } this.dmodes = {"tri_strip":gl.TRIANGLE_STRIP,"tri":gl.TRIANGLES,"points":gl.POINTS,"lines":gl.LINES,"line_strip":gl.LINE_STRIP } this.version = 2 ; return true ; } WWG.prototype.loadAjax = function(src,opt) { return new Promise(function(resolve,reject) { var req = new XMLHttpRequest(); req.open("get",src,true) ; req.responseType = (opt && opt.type)?opt.type:"text" ; req.onload = function() { if(this.status==200) { resolve(this.response) ; } else { reject("Ajax error:"+this.statusText) ; } } req.onerror = function() { reject("Ajax error:"+this.statusText) } req.send() ; }) } WWG.prototype.loadImageAjax = function(src) { var self = this ; return new Promise(function(resolve,reject){ self.loadAjax(src,{type:"blob"}).then(function(b){ var timg = new Image ; const url = URL.createObjectURL(b); timg.onload = function() { URL.revokeObjectURL(url) resolve(timg) ; } timg.src = url }).catch(function(err){ resolve(null) ; }) }) } // Render unit WWG.prototype.createRender = function() { return new this.Render(this) ; } WWG.prototype.Render = function(wwg) { this.wwg = wwg ; this.gl = wwg.gl ; this.env = {} ; this.obuf = [] ; this.modelCount = 0 ; this.modelHash = {} ; } WWG.prototype.Render.prototype.setUnivec = function(uni,value) { if(uni.pos==null) return // console.log("set "+uni.type+"("+uni.pos+") = "+value) ; let ar = [] ; if(uni.dim>0 && !(value instanceof Float32Array)) for(let i=0;ireject(err)); } else { var img = new Image() ; img.onload = function() { resolve( self.genTex(img,tex.opt) ) ; } img.onerror = function() { reject("cannot load image") ; } img.src = tex.src ; } } else if(tex.img instanceof Image) { resolve( self.genTex(tex.img,tex.opt) ) } else if(tex.video ) { resolve( self.genTex(tex.video,{nomipmap:true,flevel:0,repeat:2}) ) } else if(tex.buffer) { if(tex.mrt!=undefined) { resolve( tex.buffer.fb.t[tex.mrt]) } else resolve( tex.buffer.fb.t) ; } else if(tex.texture) { resolve( tex.texture) ; } else if(tex.canvas) { resolve( self.genTex(tex.canvas,tex.opt)) ; } else if(tex.array) { tex.opt.isarray = true ; resolve( self.genTex(tex.array,tex.opt)) ; } else { reject("no image") } }) } WWG.prototype.Render.prototype.getTexIndex = function(name) { for(var i=0;i{ this.data.texture.push(texdata) this.loadTex(texdata).then((tex)=>{ this.texobj.push(tex) ; resolve(this.texobj.length-1) }).catch((err)=>reject(err)); }) } WWG.prototype.Render.prototype.frameBuffer = function(os) { var gl = this.gl ; console.log("create framebuffer "+os.width+"x"+os.height) ; var mrt = os.mrt ; var ttype = gl.UNSIGNED_BYTE ; var tfilter = gl.LINEAR ; if(this.wwg.ext_ftex && os.float ) { ttype = gl.FLOAT ; tfilter = gl.NEAREST ; console.log("use float tex") ; } var fblist = [] ; var frameBuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer); //depth var renderBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, renderBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, os.width, os.height); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderBuffer); //texture if(mrt) { var fTexture = [] ; for(var i=0;i 65536 && this.wwg.ext_i32) { gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.i32Array(geo.idx),gl.STATIC_DRAW ) ; ret.i32 = true ; } else { gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.i16Array(geo.idx),gl.STATIC_DRAW ) ; ret.i32 = false ; } } if(flag && inst) { gl.bindBuffer(gl.ARRAY_BUFFER, ibuf) ; gl.bufferData(gl.ARRAY_BUFFER, this.f32Array(inst.data),(inst.dynamic)?gl.DYNAMIC_DRAW:gl.STATIC_DRAW ) ; } if(this.wwg.ext_vao) this.wwg.vao_bind(null); return ret ; } WWG.prototype.Render.prototype.getModelIdx = function(name) { var idx if(typeof name != 'string') idx = parseInt(name) ; else idx = this.modelHash[name] ; return idx ; } // add model WWG.prototype.Render.prototype.addModel = function(model) { this.data.model.push(model) ; this.obuf.push(this.setObj(model,true)) ; this.modelCount = this.data.model.length ; if(model.name) this.modelHash[model.name] = this.data.model.length -1 ; } // remove model WWG.prototype.Render.prototype.removeModel = function(model) { } // update attribute buffer WWG.prototype.Render.prototype.updateModel = function(name,mode,buf,subflag=true) { var idx = this.getModelIdx(name) ; var obuf = this.obuf[idx] ; switch(mode) { case "vbo": this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obuf.vbo) ; break ; case "inst": this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obuf.inst) ; break ; } if(subflag) this.gl.bufferSubData(this.gl.ARRAY_BUFFER, 0, this.f32Array(buf)) else this.gl.bufferData(this.gl.ARRAY_BUFFER, this.f32Array(buf),this.gl.DYNAMIC_DRAW ) ; } WWG.prototype.Render.prototype.updateModelInstance = function(name,buf,count) { var idx = this.getModelIdx(name) ; var obuf = this.obuf[idx] ; this.data.model[idx].inst.count = count ; this.gl.bindBuffer(this.gl.ARRAY_BUFFER, obuf.inst) ; // this.gl.bufferSubData(this.gl.ARRAY_BUFFER, 0, this.f32Array(buf)) this.gl.bufferData(this.gl.ARRAY_BUFFER, this.f32Array(buf),this.gl.DYNAMIC_DRAW ) ; } WWG.prototype.Render.prototype.getModelData =function(name) { var idx = this.getModelIdx(name) ; return this.data.model[idx] ; } // update texture WWG.prototype.Render.prototype.updateTex = function(idx,tex,opt) { if(typeof idx == 'string') idx = this.getTexIndex(idx) let tdat = this.data.texture[idx] this.gl.bindTexture(this.gl.TEXTURE_2D, this.texobj[idx]); if(tdat.array) { if(!opt) this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA16F, tdat.opt.width,tdat.opt.height,0,this.gl.RGBA, this.gl.FLOAT, tex); else this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, opt.sx,opt.sy,opt.width,opt.height,this.gl.RGBA, this.gl.FLOAT, tex,opt.ofs); } else { if(!opt) { this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, tex); } else { if(opt.wx>0 && opt.wy>0) this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, opt.sx,opt.sy,opt.wx,opt.wy,this.gl.RGBA, this.gl.UNSIGNED_BYTE, tex); else this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, opt.sx,opt.sy , this.gl.RGBA, this.gl.UNSIGNED_BYTE, tex); } } if(this.data.texture[idx].opt && !this.data.texture[idx].opt.nomipmap) this.gl.generateMipmap(this.gl.TEXTURE_2D); } //update uniform values WWG.prototype.Render.prototype.pushUniValues = function(u) { if(u.vs_uni) { for(var i in u.vs_uni) { this.update_uni.vs_uni[i] = u.vs_uni[i] ; } } if(u.fs_uni) { for(var i in u.fs_uni) { this.update_uni.fs_uni[i] = u.fs_uni[i] ; } } } WWG.prototype.Render.prototype.updateUniValues = function(u) { if(!u) { this.update_uni = {vs_uni:{},fs_uni:{}} ; return ; } // console.log("update uni"); this.setUniValues(this.update_uni) } // draw call WWG.prototype.Render.prototype.draw = function(update,cls) { //console.log("draw"); var gl = this.gl ; gl.useProgram(this.program); if(this.env.offscreen) {// renderbuffer gl.bindFramebuffer(gl.FRAMEBUFFER, this.fb.f); if(this.env.offscreen.mrt) this.wwg.mrt_draw(this.fb.fblist); gl.viewport(0,0,this.fb.width,this.fb.height) ; } if(!cls) this.clear() ; for(var b=0;b0)?geo.ofs:0 ; if(this.wwg.ext_vao) this.wwg.vao_bind(obuf.vao); else { gl.bindBuffer(gl.ARRAY_BUFFER, obuf.vbo) ; var aofs = 0 ; for(var i in this.vs_att ) { gl.disableVertexAttribArray(this.vs_att[i].pos); } for(var i=0;i0)?geo.count:geo.vtx.length/obuf.tl); } if(this.wwg.ext_vao) this.wwg.vao_bind(null); else { } if(cmodel.blend!==undefined) { gl.disable(gl.BLEND) ; } if(cmodel.cull!==undefined) { if(this.env.cull) gl.enable(gl.CULL_FACE); else gl.disable(gl.CULL_FACE); } if(cmodel.postFunction) { cmodel.postFunction(gl,cmodel) ; } } if(this.env.offscreen) {// unbind renderbuffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.viewport(0,0,this.wwg.can.width,this.wwg.can.height) ; } return true ; }