      1 /**
      2  * Hilo
      3  * Copyright 2015 alibaba.com
      4  * Licensed under the MIT License
      5  */
      6
      7 /**
      8  * Heavily inspired by PIXI's SpriteRenderer:
      9  * https://github.com/pixijs/pixi.js/blob/v3.0.9/src/core/sprites/webgl/SpriteRenderer.js
     10  */
     11
     12 var DEG2RAD = Math.PI / 180;
     13 /**
     14  * @class WebGLRenderer The WebGLRenderer, all the visual object is drawing on the canvas using WebGL.The stage will create different renderer depend on the canvas and renderType properties, developer need not use this class directly.
     15  * @augments Renderer
     16  * @param {Object} properties The properties to create a renderer, contains all writeable props of this class.
     17  * @module hilo/renderer/WebGLRenderer
     18  * @requires hilo/core/Class
     19  * @requires hilo/core/Hilo
     20  * @requires hilo/renderer/Renderer
     21  * @requires  hilo/geom/Matrix
     22  * @property {WebGLRenderingContext} gl The WebGL context of the renderer, readonly.
     23  */
     24 var WebGLRenderer = Class.create(/** @lends WebGLRenderer.prototype */{
     25     Extends: Renderer,
     26     Statics:/** @lends WebGLRenderer */{
     27         /**
     28          * The max num of batch draw, default is 2000.
     29          * @type {Number}
     30          */
     31         MAX_BATCH_NUM:2000,
     32         /**
     33          * The num of vertex attributes, readonly.
     34          * @type {Number}
     35          */
     36         ATTRIBUTE_NUM:5,
     37         /**
     38          * is WebGL supported, readonly.
     39          * @type {Boolean}
     40          */
     41         isSupport:function(){
     42             if(this._isSupported == undefined){
     43                 var canvas = document.createElement('canvas');
     44                 if(canvas.getContext && (canvas.getContext('webgl')||canvas.getContext('experimental-webgl'))){
     45                     this._isSupported = true;
     46                 }
     47                 else{
     48                     this._isSupported = false;
     49                 }
     50             }
     51             return this._isSupported;
     52         },
     53         /**
     54          * WebGL context Options
     55          * @see  https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getContextAttributes
     56          * @type {Object}
     57          */
     58         contextOptions: null
     59     },
     60     renderType:'webgl',
     61     gl:null,
     62     _isContextLost:false,
     63     _cacheTexture:{},
     64     constructor: function(properties){
     65         WebGLRenderer.superclass.constructor.call(this, properties);
     66         var that = this;
     67         var contextOptions = WebGLRenderer.contextOptions || {};
     68         this.gl = this.canvas.getContext("webgl", contextOptions)||this.canvas.getContext('experimental-webgl', contextOptions);
     69
     70         this.maxBatchNum = WebGLRenderer.MAX_BATCH_NUM;
     71         this.positionStride = WebGLRenderer.ATTRIBUTE_NUM * 4;
     72         var vertexNum = this.maxBatchNum * WebGLRenderer.ATTRIBUTE_NUM * 4;
     73         var indexNum = this.maxBatchNum * 6;
     74         this.arrayBuffer = new ArrayBuffer(vertexNum * 4);
     75         this.float32Array = new Float32Array(this.arrayBuffer);
     76         this.uint32Array = new Uint32Array(this.arrayBuffer);
     77         this.indexs = new Uint16Array(indexNum);
     78         for (var i=0, j=0; i < indexNum; i += 6, j += 4)
     79         {
     80             this.indexs[i + 0] = j + 0;
     81             this.indexs[i + 1] = j + 1;
     82             this.indexs[i + 2] = j + 2;
     83             this.indexs[i + 3] = j + 1;
     84             this.indexs[i + 4] = j + 2;
     85             this.indexs[i + 5] = j + 3;
     86         }
     87         this.batchIndex = 0;
     88         this.sprites = [];
     89
     90         this.canvas.addEventListener('webglcontextlost', function(e){
     91             that._isContextLost = true;
     92             e.preventDefault();
     93         }, false);
     94
     95         this.canvas.addEventListener('webglcontextrestored', function(e){
     96             that._isContextLost = false;
     97             that.setupWebGLStateAndResource();
     98         }, false);
     99
    100         this.setupWebGLStateAndResource();
    101     },
    102     setupWebGLStateAndResource:function(){
    103         var gl = this.gl;
    104         gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
    105         gl.clearColor(0, 0, 0, 0);
    106         gl.disable(gl.DEPTH_TEST);
    107         gl.disable(gl.CULL_FACE);
    108         gl.enable(gl.BLEND);
    109
    110         this._cacheTexture = {};
    111         this._initShaders();
    112         this.defaultShader.active();
    113
    114         this.positionBuffer = gl.createBuffer();
    115         this.indexBuffer = gl.createBuffer();
    116
    117         gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
    118         gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indexs, gl.STATIC_DRAW);
    119
    120         gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
    121         gl.bufferData(gl.ARRAY_BUFFER, this.arrayBuffer, gl.DYNAMIC_DRAW);
    122
    123         gl.vertexAttribPointer(this.a_position, 2, gl.FLOAT, false, this.positionStride, 0);//x, y
    124         gl.vertexAttribPointer(this.a_TexCoord, 2, gl.FLOAT, false, this.positionStride, 2 * 4);//x, y
    125         gl.vertexAttribPointer(this.a_tint, 4, gl.UNSIGNED_BYTE, true, this.positionStride, 4 * 4);//alpha
    126     },
    127
    128     context: null,
    129
    130     /**
    131      * @private
    132      * @see Renderer#startDraw
    133      */
    134     startDraw: function(target){
    135         if(target.visible && target.alpha > 0){
    136             if(target === this.stage){
    137                 this.clear();
    138             }
    139             return true;
    140         }
    141         return false;
    142     },
    143     /**
    144      * @private
    145      * @see Renderer#draw
    146      */
    147     draw: function(target){
    148         var w = target.width,
    149             h = target.height;
    150
    151         //TODO:draw background
    152         var bg = target.background; // jshint ignore:line
    153
    154         //draw image
    155         var drawable = target.drawable, image = drawable && drawable.image;
    156         if(image){
    157             var rect = drawable.rect, sw = rect[2], sh = rect[3];
    158             if(!w && !h){
    159                 //fix width/height TODO: how to get rid of this?
    160                 w = target.width = sw;
    161                 h = target.height = sh;
    162             }
    163
    164             if(this.batchIndex >= this.maxBatchNum){
    165                 this._renderBatches();
    166             }
    167
    168             var vertexs = this._createVertexs(image, rect[0], rect[1], sw, sh, 0, 0, w, h);
    169             var index = this.batchIndex * this.positionStride;
    170             var float32Array = this.float32Array;
    171             var uint32Array = this.uint32Array;
    172
    173             var tint = (target.tint >> 16) + (target.tint & 0xff00) + ((target.tint & 0xff) << 16) + (target.__webglRenderAlpha * 255 << 24);
    174
    175             float32Array[index + 0] = vertexs[0];//x
    176             float32Array[index + 1] = vertexs[1];//y
    177             float32Array[index + 2] = vertexs[2];//uvx
    178             float32Array[index + 3] = vertexs[3];//uvy
    179             uint32Array[index + 4] = tint;//tint
    180
    181             float32Array[index + 5] = vertexs[4];
    182             float32Array[index + 6] = vertexs[5];
    183             float32Array[index + 7] = vertexs[6];
    184             float32Array[index + 8] = vertexs[7];
    185             uint32Array[index + 9] = tint;
    186
    187             float32Array[index + 10] = vertexs[8];
    188             float32Array[index + 11] = vertexs[9];
    189             float32Array[index + 12] = vertexs[10];
    190             float32Array[index + 13] = vertexs[11];
    191             uint32Array[index + 14] = tint;
    192
    193             float32Array[index + 15] = vertexs[12];
    194             float32Array[index + 16] = vertexs[13];
    195             float32Array[index + 17] = vertexs[14];
    196             float32Array[index + 18] = vertexs[15];
    197             uint32Array[index + 19] = tint;
    198
    199             var matrix = target.__webglWorldMatrix;
    200             for(var i = 0;i < 4;i ++){
    201                 var x = float32Array[index + i*5];
    202                 var y = float32Array[index + i*5 + 1];
    203
    204                 float32Array[index + i*5] = matrix.a*x + matrix.c*y + matrix.tx;
    205                 float32Array[index + i*5 + 1] = matrix.b*x + matrix.d*y + matrix.ty;
    206             }
    207
    208             target.__textureImage = image;
    209             this.sprites[this.batchIndex++] = target;
    210         }
    211     },
    212
    213     /**
    214      * @private
    215      * @see Renderer#endDraw
    216      */
    217     endDraw: function(target){
    218         if(target === this.stage){
    219             this._renderBatches();
    220         }
    221     },
    222     /**
    223      * @private
    224      * @see Renderer#transform
    225      */
    226     transform: function(target){
    227         var drawable = target.drawable;
    228         if(drawable && drawable.domElement){
    229             Hilo.setElementStyleByView(target);
    230             return;
    231         }
    232
    233         var scaleX = target.scaleX,
    234             scaleY = target.scaleY;
    235
    236         if(target === this.stage){
    237             var style = this.canvas.style,
    238                 oldScaleX = target._scaleX,
    239                 oldScaleY = target._scaleY,
    240                 isStyleChange = false;
    241
    242             if((!oldScaleX && scaleX != 1) || (oldScaleX && oldScaleX != scaleX)){
    243                 target._scaleX = scaleX;
    244                 style.width = scaleX * target.width + "px";
    245                 isStyleChange = true;
    246             }
    247             if((!oldScaleY && scaleY != 1) || (oldScaleY && oldScaleY != scaleY)){
    248                 target._scaleY = scaleY;
    249                 style.height = scaleY * target.height + "px";
    250                 isStyleChange = true;
    251             }
    252             if(isStyleChange){
    253                 target.updateViewport();
    254             }
    255             target.__webglWorldMatrix = target.__webglWorldMatrix||new Matrix(1, 0, 0, 1, 0, 0);
    256         }
    257         else if(target.parent){
    258             target.__webglWorldMatrix = target.__webglWorldMatrix||new Matrix(1, 0, 0, 1, 0, 0);
    259             this._setConcatenatedMatrix(target, target.parent);
    260         }
    261
    262         if(target.alpha > 0) {
    263             if(target.parent && target.parent.__webglRenderAlpha){
    264                 target.__webglRenderAlpha = target.alpha * target.parent.__webglRenderAlpha;
    265             }
    266             else{
    267                 target.__webglRenderAlpha = target.alpha;
    268             }
    269         }
    270     },
    271
    272     /**
    273      * @private
    274      * @see Renderer#remove
    275      */
    276     remove: function(target){
    277         var drawable = target.drawable;
    278         var elem = drawable && drawable.domElement;
    279
    280         if(elem){
    281             var parentElem = elem.parentNode;
    282             if(parentElem){
    283                 parentElem.removeChild(elem);
    284             }
    285         }
    286     },
    287
    288     /**
    289      * @private
    290      * @see Renderer#clear
    291      */
    292     clear: function(x, y, width, height){
    293         this.gl.clear(this.gl.COLOR_BUFFER_BIT);
    294     },
    295
    296     /**
    297      * @private
    298      * @see Renderer#resize
    299      */
    300     resize: function(width, height){
    301         if(this.width !== width || this.height !== height){
    302             var canvas = this.canvas;
    303             var stage = this.stage;
    304             var style = canvas.style;
    305
    306             this.width = canvas.width = width;
    307             this.height = canvas.height = height;
    308
    309             style.width = stage.width * stage.scaleX + 'px';
    310             style.height = stage.height * stage.scaleY + 'px';
    311
    312             this.gl.viewport(0, 0, width, height);
    313
    314             this.canvasHalfWidth = width * .5;
    315             this.canvasHalfHeight = height * .5;
    316
    317             this._uploadProjectionTransform(true);
    318         }
    319     },
    320     _renderBatches:function(){
    321         var gl = this.gl;
    322         gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uint32Array.subarray(0, this.batchIndex * this.positionStride));
    323         var startIndex = 0;
    324         var batchNum = 0;
    325         var preTextureImage = null;
    326         for(var i = 0;i < this.batchIndex;i ++){
    327             var sprite = this.sprites[i];
    328             if(preTextureImage && preTextureImage !== sprite.__textureImage){
    329                 this._renderBatch(startIndex, i);
    330                 startIndex = i;
    331                 batchNum = 1;
    332             }
    333             preTextureImage = sprite.__textureImage;
    334         }
    335         this._renderBatch(startIndex, this.batchIndex);
    336         this.batchIndex = 0;
    337     },
    338     _renderBatch:function(start, end){
    339         var gl = this.gl;
    340         var num = end - start;
    341         if(num > 0){
    342             gl.bindTexture(gl.TEXTURE_2D, this._getTexture(this.sprites[start]));
    343             gl.drawElements(gl.TRIANGLES, num * 6, gl.UNSIGNED_SHORT, start * 6 * 2);
    344         }
    345     },
    346     _uploadProjectionTransform:function(force){
    347         if(!this._projectionTransformElements||force){
    348             this._projectionTransformElements = new Float32Array([
    349                 1/this.canvasHalfWidth, 0, 0,
    350                 0, -1/this.canvasHalfHeight, 0,
    351                 -1, 1, 1,
    352             ]);
    353         }
    354
    355         this.gl.uniformMatrix3fv(this.u_projectionTransform, false, this._projectionTransformElements);
    356     },
    357     _initShaders:function(){
    358         var VSHADER_SOURCE ='\
    359             attribute vec2 a_position;\n\
    360             attribute vec2 a_TexCoord;\n\
    361             attribute vec4 a_tint;\n\
    362             uniform mat3 u_projectionTransform;\n\
    363             varying vec2 v_TexCoord;\n\
    364             varying vec4 v_tint;\n\
    365             void main(){\n\
    366                 gl_Position =  vec4((u_projectionTransform * vec3(a_position, 1.0)).xy, 1.0, 1.0);\n\
    367                 v_TexCoord = a_TexCoord;\n\
    368                 v_tint = vec4(a_tint.rgb * a_tint.a, a_tint.a);\n\
    369             }\n\
    370         ';
    371
    372         var FSHADER_SOURCE = '\n\
    373             precision mediump float;\n\
    374             uniform sampler2D u_Sampler;\n\
    375             varying vec2 v_TexCoord;\n\
    376             varying vec4 v_tint;\n\
    377             void main(){\n\
    378                 gl_FragColor = texture2D(u_Sampler, v_TexCoord) * v_tint;\n\
    379             }\n\
    380         ';
    381
    382         this.defaultShader = new Shader(this, {
    383             v:VSHADER_SOURCE,
    384             f:FSHADER_SOURCE
    385         },{
    386             attributes:["a_position", "a_TexCoord", "a_tint"],
    387             uniforms:["u_projectionTransform", "u_Sampler"]
    388         });
    389     },
    390     _createVertexs:function(img, tx, ty, tw, th, x, y, w, h){
    391         var tempVertexs = this.__tempVertexs||[];
    392         var width = img.width;
    393         var height = img.height;
    394
    395         tw = tw/width;
    396         th = th/height;
    397         tx = tx/width;
    398         ty = ty/height;
    399
    400         w = w;
    401         h = h;
    402         x = x;
    403         y = y;
    404
    405         if(tw + tx > 1){
    406             tw = 1 - tx;
    407         }
    408
    409         if(th + ty > 1){
    410             th = 1 - ty;
    411         }
    412
    413         var index = 0;
    414         tempVertexs[index++] = x; tempVertexs[index++] = y; tempVertexs[index++] = tx; tempVertexs[index++] = ty;
    415         tempVertexs[index++] = x+w;tempVertexs[index++] = y; tempVertexs[index++] = tx+tw; tempVertexs[index++] = ty;
    416         tempVertexs[index++] = x; tempVertexs[index++] = y+h; tempVertexs[index++] = tx;tempVertexs[index++] = ty+th;
    417         tempVertexs[index++] = x+w;tempVertexs[index++] = y+h;tempVertexs[index++] = tx+tw;tempVertexs[index++] = ty+th;
    418
    419         return tempVertexs;
    420     },
    421     _setConcatenatedMatrix:function(view, ancestor){
    422         var mtx = view.__webglWorldMatrix;
    423         var cos = 1, sin = 0,
    424             rotation = view.rotation % 360,
    425             pivotX = view.pivotX, pivotY = view.pivotY,
    426             scaleX = view.scaleX, scaleY = view.scaleY,
    427             transform = view.transform;
    428
    429         if (transform) {
    430             mtx.copy(transform);
    431         }
    432         else {
    433             if(rotation){
    434                 var r = rotation * DEG2RAD;
    435                 cos = Math.cos(r);
    436                 sin = Math.sin(r);
    437             }
    438
    439             var pos = view.getAlignPosition();
    440
    441             mtx.a = cos*scaleX;
    442             mtx.b = sin*scaleX;
    443             mtx.c = -sin*scaleY;
    444             mtx.d = cos*scaleY;
    445             mtx.tx =  pos.x - mtx.a * pivotX - mtx.c * pivotY;
    446             mtx.ty =  pos.y - mtx.b * pivotX - mtx.d * pivotY;
    447         }
    448
    449         mtx.concat(ancestor.__webglWorldMatrix);
    450     },
    451     _getTexture:function(sprite){
    452         var image = sprite.__textureImage;
    453         var texture = this._cacheTexture[image.src];
    454         if(!texture){
    455             texture = this.activeShader.uploadTexture(image);
    456         }
    457         return texture;
    458     }
    459 });
    460
    461 /**
    462  * shader
    463  * @param {WebGLRenderer} renderer [description]
    464  * @param {Object} source
    465  * @param {String} source.v 顶点shader
    466  * @param {String} source.f 片段shader
    467  * @param {Object} attr
    468  * @param {Array} attr.attributes attribute数组
    469  * @param {Array} attr.uniforms uniform数组
    470  */
    471 var Shader = function(renderer, source, attr){
    472     this.renderer = renderer;
    473     this.gl = renderer.gl;
    474     this.program = this._createProgram(this.gl, source.v, source.f);
    475
    476     attr = attr||{};
    477     this.attributes = attr.attributes||[];
    478     this.uniforms = attr.uniforms||[];
    479 };
    480
    481 Shader.prototype = {
    482     active:function(){
    483         var that = this;
    484         var renderer = that.renderer;
    485         var gl = that.gl;
    486         var program = that.program;
    487
    488         if(program && gl){
    489             renderer.activeShader = that;
    490             gl.useProgram(program);
    491             that.attributes.forEach(function(attribute){
    492                 renderer[attribute] = gl.getAttribLocation(program, attribute);
    493                 gl.enableVertexAttribArray(renderer[attribute]);
    494             });
    495
    496             that.uniforms.forEach(function(uniform){
    497                 renderer[uniform] = gl.getUniformLocation(program, uniform);
    498             });
    499
    500             if(that.width !== renderer.width || that.height !== renderer.height){
    501                 that.width = renderer.width;
    502                 that.height = renderer.height;
    503                 renderer._uploadProjectionTransform();
    504             }
    505         }
    506     },
    507     uploadTexture:function(image){
    508         var gl = this.gl;
    509         var renderer = this.renderer;
    510         var texture = gl.createTexture();
    511         var u_Sampler = renderer.u_Sampler;
    512
    513         gl.activeTexture(gl.TEXTURE0);
    514         gl.bindTexture(gl.TEXTURE_2D, texture);
    515
    516         // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    517         gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
    518         gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    519
    520         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    521         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    522         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    523         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    524         gl.uniform1i(u_Sampler, 0);
    525         gl.bindTexture(gl.TEXTURE_2D, null);
    526
    527         this.renderer._cacheTexture[image.src] = texture;
    528         return texture;
    529     },
    530     _createProgram:function(gl, vshader, fshader){
    531         var vertexShader = this._createShader(gl, gl.VERTEX_SHADER, vshader);
    532         var fragmentShader = this._createShader(gl, gl.FRAGMENT_SHADER, fshader);
    533         if (!vertexShader || !fragmentShader) {
    534             return null;
    535         }
    536
    537         var program = gl.createProgram();
    538         if (program) {
    539             gl.attachShader(program, vertexShader);
    540             gl.attachShader(program, fragmentShader);
    541
    542             gl.linkProgram(program);
    543
    544             gl.deleteShader(fragmentShader);
    545             gl.deleteShader(vertexShader);
    546             var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    547             if (!linked) {
    548                 var error = gl.getProgramInfoLog(program);
    549                 console.log('Failed to link program: ' + error);
    550                 gl.deleteProgram(program);
    551                 return null;
    552             }
    553         }
    554         return program;
    555     },
    556     _createShader:function(gl, type, source){
    557         var shader = gl.createShader(type);
    558         if(shader){
    559             gl.shaderSource(shader, source);
    560             gl.compileShader(shader);
    561
    562             var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    563             if (!compiled) {
    564                 var error = gl.getShaderInfoLog(shader);
    565                 console.log('Failed to compile shader: ' + error);
    566                 gl.deleteShader(shader);
    567                 return null;
    568             }
    569         }
    570         return shader;
    571     }
    572 };