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