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