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