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