var mojoHtml5Gl = function(undefined){ 'use strict'; document.addEventListener("DOMContentLoaded", function() { init("GameCanvas"); }, false); var WebGL2D = function WebGL2D(canvas) { this.api = undefined; this.canvas = canvas; this.gl = undefined; this.width = canvas.width; this.height = canvas.height; this.simpleShader = undefined; this.textureShader = undefined; this.maxTextureSize = undefined; canvas.gl2d = this; var gl = this.gl = canvas.getContext("webgl", {alpha: false}) || canvas.getContext("experimental-webgl", {alpha: false}); if (gl === undefined || gl === null) return; try { this.simpleShader = this.loadShaders(false); this.textureShader = this.loadShaders(true); } catch (e) { throw e; } this.api = new WebGL2DAPI(this); canvas.getContext = this.api; canvas.getContext = (function(api, gl) { return function(context) { if( context === "webgl" || context === "experimental-webgl"){ return gl; } return api; }; }(this.api, this.gl)); }; WebGL2D.prototype.getFragmentShaderSource = function getFragmentShaderSource(textured) { var fsSource = []; fsSource.push( "precision mediump float;", "varying vec4 vColor;" ); if (textured) { fsSource.push( "varying vec2 vTextureCoord;", "uniform sampler2D uSampler;" ); } fsSource.push("void main(void) {"); if (textured) { fsSource.push("gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor;"); } else { fsSource.push("gl_FragColor = vColor;"); } fsSource.push("}"); return fsSource.join("\n"); }; WebGL2D.prototype.getVertexShaderSource = function getVertexShaderSource(textured) { var w = 2 / this.canvas.width, h = -2 / this.canvas.height; var vsSource = []; vsSource.push( "attribute vec4 aVertexPosition;", "attribute vec4 aVertexColor;", "varying vec4 vColor;", "const mat4 pMatrix = mat4(" + w + ",0,0,0, 0," + h + ",0,0, 0,0,1.0,1.0, -1.0,1.0,0,0);" ); if (textured) { vsSource.push("varying vec2 vTextureCoord;"); } vsSource.push( "void main(void) {", "vec3 position = vec3(aVertexPosition.x, aVertexPosition.y, 1.0);", "gl_Position = pMatrix * vec4(position, 1.0);", "vColor = aVertexColor;" ); if (textured) { vsSource.push("vTextureCoord = aVertexPosition.zw;"); } vsSource.push("}"); return vsSource.join("\n"); }; WebGL2D.prototype.loadShaders = function loadShaders(textured) { var gl = this.gl; var fs = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fs, this.getFragmentShaderSource(textured)); gl.compileShader(fs); if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) { throw "fragment shader error: " + gl.getShaderInfoLog(fs); } var vs = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vs, this.getVertexShaderSource(textured)); gl.compileShader(vs); if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) { throw "vertex shader error: " + gl.getShaderInfoLog(vs); } var shaderProgram = this.shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, fs); gl.attachShader(shaderProgram, vs); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { throw "Could not initialise shaders."; } shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor"); gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute); shaderProgram.uColor = gl.getUniformLocation(shaderProgram, 'uColor'); shaderProgram.uSampler = gl.getUniformLocation(shaderProgram, 'uSampler'); return shaderProgram; }; var WebGL2DAPI = function WebGL2DAPI(gl2d) { if (CFG_CONFIG === "debug") { print("WebGL enabled"); } var gl = gl2d.gl; gl2d.width = -1; gl2d.height = -1; gl2d.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); if (!isPOT(gl2d.maxTextureSize)) gl2d.maxTextureSize += 1; var MAX_VERTICES = 2048; var MAX_RENDERS = (MAX_VERTICES / 2)|0; var red = 1.0, green = 1.0, blue = 1.0; var alpha = 1.0, blend = 0; var MODE_NONE = 0, MODE_TEXTURED = 1; var mode = MODE_NONE; var gxtk = null; var simpleShader = gl2d.simpleShader; var textureShader = gl2d.textureShader; var buffer = { vdata: new Float32Array(MAX_VERTICES * 4), cdata: new Float32Array(MAX_VERTICES * 4), vcount: 0, vpointer: 0, cpointer: 0, vbuffer: gl.createBuffer(), cbuffer: gl.createBuffer() }; var render = { last: new renderOp(-1, 0, null), next: 0 } var rendersPool = new Array(MAX_RENDERS); for (var i = 0; i < rendersPool.length; i++) { rendersPool[i] = new renderOp(-1, 0, null); } gl.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gxtkGraphics.prototype.LoadSurface=function( path ){ var game = this.game; var ty = game.GetMetaData( path,"type" ); if (ty.indexOf("image/") != 0) return null; game.IncLoading(); var image = new Image(); image.onload = function() { bindTexture(this); game.DecLoading(); }; image.meta_width = parseInt(game.GetMetaData(path, "width")); image.meta_height = parseInt(game.GetMetaData(path, "height")); image.src = game.PathToUrl(path); return new gxtkSurface(image, this); } BBAsyncImageLoaderThread.prototype.Start = function(){ var thread = this; var image = new Image(); image.crossOrigin = ''; image.onload = function(e) { image.meta_width = image.width; image.meta_height = image.height; thread._surface = new gxtkSurface(image, thread._device); bindTexture(this); thread.running = false; } image.onerror = function(e) { thread._surface = null; thread.running = false; } thread.running = true; image.src = BBGame.Game().PathToUrl(thread._path); } gxtkSurface.prototype.Discard = function(){ if (this.image){ gl.deleteTexture(this.image.texture); this.image = null; } } gxtkGraphics.prototype.CreateSurface=function( width,height ){ var canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; canvas.meta_width = width; canvas.meta_height = height; bindTexture(canvas); canvas.complete = true; var surface = new gxtkSurface(canvas, this); surface.gc = canvas.getContext("2d"); return surface; } gxtkGraphics.prototype.BeginRender = function() { if (!this.gc) return 0; gxtk = this; if (this.game.GetLoading()) return 2; if (gl2d.width !== gl2d.canvas.width || gl2d.height !== gl2d.canvas.height) { simpleShader = gl2d.simpleShader = gl2d.loadShaders(false); textureShader = gl2d.textureShader = gl2d.loadShaders(true); this.width = gl2d.width = gl2d.canvas.width; this.height = gl2d.height = gl2d.canvas.height; gl.viewport(0, 0, this.width, this.height); } return 1; } gxtkGraphics.prototype.EndRender = function(){ renderPull(); } gxtkGraphics.prototype.SetAlpha = function(a){ alpha = a; } gxtkGraphics.prototype.SetColor = function(r, g, b){ red = r / 255.0; green = g / 255.0; blue = b / 255.0; } gxtkGraphics.prototype.SetBlend = function(b){ if (blend === b) return; renderPull(); switch (b) { case 1: gl.blendFunc(gl.SRC_ALPHA, gl.ONE); break; default: gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); } blend = b; } gxtkGraphics.prototype.SetScissor = function(x,y,w,h) { renderPull(); if (x !== 0 || y !== 0 || w !== this.width || h !== this.height) { gl.enable(gl.SCISSOR_TEST); y = this.height - y - h; gl.scissor(x, y, w, h); } else { gl.disable(gl.SCISSOR_TEST); } } gxtkGraphics.prototype.SetMatrix = function(ix, iy, jx, jy, tx, ty) { this.ix = ix; this.iy = iy; this.jx = jx; this.jy = jy; this.tx = tx; this.ty = ty; this.tformed = (ix !== 1 || iy !== 0 || jx !== 0 || jy !== 1 || tx !== 0 || ty !== 0); } gxtkGraphics.prototype.Cls = function(r, g, b) { gl.clearColor(r / 255, g / 255, b / 255, 1); gl.clear(gl.COLOR_BUFFER_BIT); } gxtkGraphics.prototype.DrawPoint = function(x, y){ if (mode !== MODE_NONE) { renderPull(); mode = MODE_NONE; } renderPush(gl.POINTS, 1); if (this.tformed) { var px = x; x = px * this.ix + y * this.jx + this.tx; y = px * this.iy + y * this.jy + this.ty; } var p = buffer.vpointer; buffer.vdata[p] = x; buffer.vdata[p + 1] = y; } gxtkGraphics.prototype.DrawRect = function(x, y, w, h){ if (mode !== MODE_NONE) { renderPull(); mode = MODE_NONE; } renderPushRect(x, y, w, h); } gxtkGraphics.prototype.DrawLine = function(x1, y1, x2, y2) { if (mode !== MODE_NONE) { renderPull(); mode = MODE_NONE; } renderPush(gl.LINES, 2); if (this.tformed) { var tx0 = x1, tx1 = x2; x1 = tx0 * this.ix + y1 * this.jx + this.tx; y1 = tx0 * this.iy + y1 * this.jy + this.ty; x2 = tx1 * this.ix + y2 * this.jx + this.tx; y2 = tx1 * this.iy + y2 * this.jy + this.ty; } var p = buffer.vpointer; buffer.vdata[p] = x1; buffer.vdata[p + 1] = y1; buffer.vdata[p + 2] = 0; buffer.vdata[p + 3] = 0; buffer.vdata[p + 4] = x2; buffer.vdata[p + 5] = y2; buffer.vdata[p + 6] = 0; buffer.vdata[p + 7] = 0; } gxtkGraphics.prototype.DrawOval = function(x, y, w, h) { if (mode !== MODE_NONE) { renderPull(); mode = MODE_NONE; } var xr = w / 2.0; var yr = h / 2.0; var segs; if (this.tformed) { var xx = xr * this.ix, xy = xr * this.iy, xd = Math.sqrt(xx * xx + xy * xy); var yx = yr * this.jx, yy = yr * this.jy, yd = Math.sqrt(yx * yx + yy * yy); segs= (xd + yd)|0; }else{ segs = (Math.abs(xr) + Math.abs(yr))|0; } if (segs > MAX_VERTICES) { segs = MAX_VERTICES; } else if (segs < 12) { segs=12; } else { segs &=~ 3; } x += xr; y += yr; renderPush(gl.TRIANGLE_FAN, segs); var p = buffer.vpointer; for (var i=0; i < segs; i++) { var th = i * 6.28318531 / segs; var x0 = (x + Math.cos(th) * xr); var y0 = (y + Math.sin(th) * yr); if (this.tformed){ var tx0 = x0; x0 = tx0 * this.ix + y0 * this.jx + this.tx; y0 = tx0 * this.iy + y0 * this.jy + this.ty; } buffer.vdata[p] = x0; buffer.vdata[p + 1] = y0; p += 4; } } gxtkGraphics.prototype.DrawPoly = function(verts) { if (mode !== MODE_NONE) { renderPull(); mode = MODE_NONE; } if (verts.length < 6 || verts.length > MAX_VERTICES * 2) return; renderPush(gl.TRIANGLE_FAN, verts.length / 2); var p = buffer.vpointer; if (this.tformed) { for (var i = 0; i < verts.length; i += 2) { buffer.vdata[p] = verts[i] * this.ix + verts[i + 1] * this.jx + this.tx; buffer.vdata[p + 1] = verts[i] * this.iy + verts[i + 1] * this.jy + this.ty; p += 4; } } else { for (var i = 0; i < verts.length; i += 2) { buffer.vdata[p] = verts[i]; buffer.vdata[p + 1] = verts[i + 1]; p += 4; } } } gxtkGraphics.prototype.DrawPoly2 = function(verts, surface, srcx, srcy) { if (!surface.image.complete) return; if (mode !== MODE_TEXTURED) { renderPull(); mode = MODE_TEXTURED; } var vertexCount = verts.length / 4; if (vertexCount < 3 || vertexCount > MAX_VERTICES ) return; renderPush(gl.TRIANGLE_FAN, vertexCount); var p = buffer.vpointer; for (var i = 0; i < vertexCount; i++) { var index = i*4; var px = verts[index]; var py = verts[index+1]; if (this.tformed) { var ppx = px; px = ppx * this.ix + py * this.jx + this.tx; py = ppx * this.iy + py * this.jy + this.ty; } buffer.vdata[p] = px; buffer.vdata[p+1] = py; buffer.vdata[p+2] = (srcx + verts[index+2]) / surface.image.meta_width; buffer.vdata[p+3] = (srcy + verts[index+3]) / surface.image.meta_height; p += 4; } render.last.texture = surface.image.texture; } gxtkGraphics.prototype.DrawSurface = function(surface, x, y) { if (!surface.image.complete) return; if (mode !== MODE_TEXTURED) { renderPull(); mode = MODE_TEXTURED; } renderPushRect(x, y, surface.swidth, surface.sheight); render.last.texture = surface.image.texture; } gxtkGraphics.prototype.DrawSurface2 = function(surface, x, y, srcx, srcy, srcw, srch) { if (!surface.image.complete) return; if (mode !== MODE_TEXTURED) { renderPull(); mode = MODE_TEXTURED; } renderPushRect(x, y, srcw, srch, srcx / surface.image.meta_width, srcy / surface.image.meta_height, (srcx + srcw) / surface.image.meta_width, (srcy + srch) / surface.image.meta_height); render.last.texture = surface.image.texture; } gxtkGraphics.prototype.ReadPixels = function(pixels, x, y, width, height, offset, pitch ) { renderPull(); var data = new Uint8Array(width * height * 4); gl.readPixels(x, this.height - y - height, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data); var i = 0; for(var py = height-1; py >= 0; --py) { var j = offset + py * pitch; for(var px = 0; px < width; ++px) { pixels[j++] = (data[i+3]<<24) | (data[i]<<16) | (data[i+1]<<8) | data[i+2]; i+=4; } } } gxtkGraphics.prototype.WritePixels2 = function(surface, pixels, x, y, width, height, offset, pitch){ var imgData = surface.gc.createImageData(width, height); var p = imgData.data, i = 0, j = offset,px,py,argb; for(py = 0; py < height; ++py){ for(px = 0; px < width; ++px){ argb = pixels[j++]; p[i] = (argb>>16) & 0xff; p[i + 1] = (argb>>8) & 0xff; p[i + 2] = argb & 0xff; p[i + 3] = (argb>>24) & 0xff; i += 4; } j += pitch - width; } surface.gc.putImageData(imgData, x, y); gl.deleteTexture(surface.image.texture); surface.image.texture = null; bindTexture(surface.image); } function renderPush(type, count) { if (buffer.vcount + count > MAX_VERTICES || render.next === MAX_RENDERS) { renderPull(); } render.last = rendersPool[render.next]; render.next += 1; render.last.type = type; render.last.count = count; render.last.texture = null; buffer.vpointer = buffer.vcount * 4; buffer.cpointer = buffer.vcount * 4; buffer.vcount += count; var p = buffer.cpointer; for (var i = 0; i < count; i++) { buffer.cdata[p] = red; buffer.cdata[p + 1] = green; buffer.cdata[p + 2] = blue; buffer.cdata[p + 3] = alpha; p += 4; } } function renderPull() { if (buffer.vcount === 0) return; var cTexture = null; var index = 0; var r; switch (mode) { case MODE_NONE: gl.useProgram(simpleShader); gl.bindBuffer(gl.ARRAY_BUFFER, buffer.vbuffer); gl.bufferData(gl.ARRAY_BUFFER, buffer.vdata, gl.DYNAMIC_DRAW); gl.vertexAttribPointer(simpleShader.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, buffer.cbuffer); gl.bufferData(gl.ARRAY_BUFFER, buffer.cdata, gl.DYNAMIC_DRAW); gl.vertexAttribPointer(simpleShader.vertexColorAttribute, 4, gl.FLOAT, false, 0, 0); for (var i = 0; i < render.next; i++) { r = rendersPool[i]; gl.drawArrays(r.type, index, r.count); index += r.count; } break; case MODE_TEXTURED: gl.useProgram(textureShader); gl.bindBuffer(gl.ARRAY_BUFFER, buffer.vbuffer); gl.bufferData(gl.ARRAY_BUFFER, buffer.vdata, gl.DYNAMIC_DRAW); gl.vertexAttribPointer(textureShader.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, buffer.cbuffer); gl.bufferData(gl.ARRAY_BUFFER, buffer.cdata, gl.DYNAMIC_DRAW); gl.vertexAttribPointer(textureShader.vertexColorAttribute, 4, gl.FLOAT, false, 0, 0); for (var i = 0; i < render.next; i++) { r = rendersPool[i]; if (cTexture !== r.texture) { gl.bindTexture(gl.TEXTURE_2D, r.texture); gl.activeTexture(gl.TEXTURE0); gl.uniform1i(textureShader.uSampler, 0); cTexture = r.texture; } gl.drawArrays(r.type, index, r.count); index += r.count; } break; } renderReset(); } function renderReset() { buffer.vcount = 0; render.next = 0; } function renderPushRect(x, y, w, h, u0, v0, u1, v1) { renderPush(gl.TRIANGLE_FAN, 4); var x0 = x, x1 = x + w, x2 = x + w, x3 = x; var y0 = y, y1 = y, y2 = y + h, y3 = y + h; if (gxtk.tformed) { var tx0 = x0, tx1 = x1, tx2 = x2, tx3 = x3; x0 = tx0 * gxtk.ix + y0 * gxtk.jx + gxtk.tx; y0 = tx0 * gxtk.iy + y0 * gxtk.jy + gxtk.ty; x1 = tx1 * gxtk.ix + y1 * gxtk.jx + gxtk.tx; y1 = tx1 * gxtk.iy + y1 * gxtk.jy + gxtk.ty; x2 = tx2 * gxtk.ix + y2 * gxtk.jx + gxtk.tx; y2 = tx2 * gxtk.iy + y2 * gxtk.jy + gxtk.ty; x3 = tx3 * gxtk.ix + y3 * gxtk.jx + gxtk.tx; y3 = tx3 * gxtk.iy + y3 * gxtk.jy + gxtk.ty; } var p = buffer.vpointer; if (u0 === undefined) { buffer.vdata[p] = x0; buffer.vdata[p + 1] = y0; buffer.vdata[p + 2] = 0; buffer.vdata[p + 3] = 0; buffer.vdata[p + 4] = x1; buffer.vdata[p + 5] = y1; buffer.vdata[p + 6] = 1; buffer.vdata[p + 7] = 0; buffer.vdata[p + 8] = x2; buffer.vdata[p + 9] = y2; buffer.vdata[p + 10] = 1; buffer.vdata[p + 11] = 1; buffer.vdata[p + 12] = x3; buffer.vdata[p + 13] = y3; buffer.vdata[p + 14] = 0; buffer.vdata[p + 15] = 1; } else { buffer.vdata[p] = x0; buffer.vdata[p + 1] = y0; buffer.vdata[p + 2] = u0; buffer.vdata[p + 3] = v0; buffer.vdata[p + 4] = x1; buffer.vdata[p + 5] = y1; buffer.vdata[p + 6] = u1; buffer.vdata[p + 7] = v0; buffer.vdata[p + 8] = x2; buffer.vdata[p + 9] = y2; buffer.vdata[p + 10] = u1; buffer.vdata[p + 11] = v1; buffer.vdata[p + 12] = x3; buffer.vdata[p + 13] = y3; buffer.vdata[p + 14] = u0; buffer.vdata[p + 15] = v1; } } function bindTexture(image) { if (image.texture !== undefined && image.texture !== null) return; image.texture = gl.createTexture(); var mojoFilteringEnabled = (typeof(CFG_MOJO_IMAGE_FILTERING_ENABLED) === "undefined" || CFG_MOJO_IMAGE_FILTERING_ENABLED === "true" || CFG_MOJO_IMAGE_FILTERING_ENABLED === "1"); if (image.width > gl2d.maxTextureSize || image.height > gl2d.maxTextureSize) { var canvas = document.createElement("canvas"); canvas.width = (image.width > gl2d.maxTextureSize) ? gl2d.maxTextureSize : image.width; canvas.height = (image.height > gl2d.maxTextureSize) ? gl2d.maxTextureSize : image.height; var ctx = canvas.getContext("2d"); ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height); image = canvas; } gl.bindTexture(gl.TEXTURE_2D, image.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); if (mojoFilteringEnabled) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); } else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); } if (isPOT(image.width) && isPOT(image.height)) { if (mojoFilteringEnabled) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.generateMipmap(gl.TEXTURE_2D); } else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } } else { if (mojoFilteringEnabled) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); } else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); } } } function isPOT(value) { return value > 0 && ((value - 1) & value) === 0; } function renderOp(type, count, texture) { this.type = type; this.count = count; this.texture = texture; } } function init(id) { if (window.WebGLRenderingContext !== undefined) { try { new WebGL2D(document.getElementById(id)); } catch (e) { } } } }; mojoHtml5Gl();