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 };