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