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