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=en
 15  * @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.
 16  * @augments Renderer
 17  * @param {Object} properties The properties to create a renderer, contains all writeable props of this class.
 18  * @module hilo/renderer/WebGLRenderer
 19  * @requires hilo/core/Class
 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          * @language=en
 29          * The max num of batch draw, default is 2000.
 30          * @type {Number}
 31          */
 32         MAX_BATCH_NUM:2000,
 33         /**
 34          * @language=en
 35          * The num of vertex attributes, readonly.
 36          * @type {Number}
 37          */
 38         ATTRIBUTE_NUM:5,
 39         /**
 40          * @language=en
 41          * is WebGL supported, readonly.
 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=en
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 })();