      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 webgl画布渲染器。所有可视对象将渲染在canvas画布上。
     15  * @augments Renderer
     16  * @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。
     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 webgl上下文。只读属性。
     23  */
     24 var WebGLRenderer = Class.create(/** @lends WebGLRenderer.prototype */{
     25     Extends: Renderer,
     26     Statics:/** @lends WebGLRenderer */{
     27         /**
     28          * 最大批渲染数量。
     29          * @type {Number}
     30          */
     31         MAX_BATCH_NUM:2000,
     32         /**
     33          * 顶点属性数。只读属性。
     34          * @type {Number}
     35          */
     36         ATTRIBUTE_NUM:5,
     37         /**
     38          * 是否支持WebGL。只读属性。
     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 };