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