1 /*{# Copyright (c) 2012 Turbulenz Limited #}*/ 2 /* 3 * @title: Draw2D 4 * @description: 5 * This sample demonstrates the capabilities of the Draw2D API. 6 * You can drag the slider around to increase or decrease the amount of sprites rendered using the Draw2D API. 7 * You can also select between different rendering options (draw mode, texture mode, sort mode and blend mode) 8 * and see the impact on performance reflected on the frames per second counter at the bottom of the left pane. 9 */ 10 /*{{ javascript("jslib/observer.js") }}*/ 11 /*{{ javascript("jslib/requesthandler.js") }}*/ 12 /*{{ javascript("jslib/utilities.js") }}*/ 13 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/ 14 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/ 15 /*{{ javascript("jslib/services/gamesession.js") }}*/ 16 /*{{ javascript("jslib/services/mappingtable.js") }}*/ 17 /*{{ javascript("jslib/shadermanager.js") }}*/ 18 /*{{ javascript("jslib/draw2d.js") }}*/ 19 /*{{ javascript("jslib/textureeffects.js") }}*/ 20 /*{{ javascript("scripts/htmlcontrols.js") }}*/ 21 /*global TurbulenzEngine: true */ 22 /*global TurbulenzServices: false */ 23 /*global RequestHandler: false */ 24 /*global Draw2D: false */ 25 /*global Draw2DSprite: false */ 26 /*global HTMLControls: false */ 27 TurbulenzEngine.onload = function onloadFn() { 28 //========================================================================== 29 // HTML Controls 30 //========================================================================== 31 var htmlControls; 32 33 var objectCount = 500; 34 var moveSprites = true; 35 var rotateSprites = false; 36 var cycleColorSprites = false; 37 38 var drawMode = 'drawSprite'; 39 var texMode = 'single'; 40 var sortMode = 'deferred'; 41 var blendMode = 'alpha'; 42 var scaleMode = 'scale'; 43 44 //========================================================================== 45 // Turbulenz Initialization 46 //========================================================================== 47 var graphicsDevice = TurbulenzEngine.createGraphicsDevice({}); 48 var mathDevice = TurbulenzEngine.createMathDevice({}); 49 var requestHandler = RequestHandler.create({}); 50 51 var loadedResources = 0; 52 53 // Textures to load: 54 var spriteTextureNames = [ 55 "textures/draw2DCircle.png", 56 "textures/draw2DSquare.png", 57 "textures/draw2DStar.png" 58 ]; 59 60 // List to store Texture objects. 61 var textures = {}; 62 var numResources = spriteTextureNames.length; 63 64 function mappingTableReceived(mappingTable) { 65 function textureParams(src) { 66 return { 67 src: mappingTable.getURL(src), 68 mipmaps: true, 69 onload: function (texture) { 70 if (texture) { 71 textures[src] = texture; 72 loadedResources += 1; 73 } 74 } 75 }; 76 } 77 78 var i; 79 for (i = 0; i < spriteTextureNames.length; i += 1) { 80 graphicsDevice.createTexture(textureParams(spriteTextureNames[i])); 81 } 82 } 83 84 var gameSession; 85 function sessionCreated() { 86 TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived); 87 } 88 89 gameSession = TurbulenzServices.createGameSession(requestHandler, sessionCreated); 90 91 //========================================================================== 92 // Draw2D initialization 93 //========================================================================== 94 var draw2D = Draw2D.create({ 95 graphicsDevice: graphicsDevice 96 }); 97 98 // Viewport for Draw2D. 99 var gameWidth = graphicsDevice.width; 100 var gameHeight = graphicsDevice.height; 101 102 var viewport = mathDevice.v4Build(0, 0, gameWidth, gameHeight); 103 var configureParams = { 104 scaleMode: undefined, 105 viewportRectangle: viewport 106 }; 107 108 //========================================================================== 109 // Sprite drawing. 110 //========================================================================== 111 // Draw2DSprite object collections. 112 // Lazily constructed and cached. 113 var sprites = []; 114 var spriteSize = 32; 115 116 function getTexture() { 117 if (texMode === 'none') { 118 return null; 119 } else if (texMode === 'single') { 120 return textures[spriteTextureNames[0]]; 121 } else { 122 var ind = Math.floor(Math.random() * spriteTextureNames.length); 123 return textures[spriteTextureNames[ind]]; 124 } 125 } 126 127 function invalidateSpriteTextures() { 128 var i; 129 var limit = sprites.length; 130 for (i = 0; i < limit; i += 1) { 131 var sprite = sprites[i]; 132 var tex = getTexture(); 133 sprite.body.setTexture(tex); 134 sprite.halo.setTexture(tex); 135 } 136 } 137 138 var prevObjectCount; 139 function validateSprites() { 140 var tex; 141 while (sprites.length < objectCount) { 142 // We don't do any scaling or shearing as standard draw mode 143 // cannot support this. 144 tex = getTexture(); 145 var body = Draw2DSprite.create({ 146 texture: tex, 147 width: spriteSize, 148 height: spriteSize, 149 x: (Math.random() * (gameWidth - spriteSize) + (spriteSize * 0.5)), 150 y: (Math.random() * (gameHeight - spriteSize) + (spriteSize * 0.5)), 151 rotation: 0, 152 color: mathDevice.v4Build(1, 1, 1, 1), 153 textureRectangle: mathDevice.v4Build(0, 0, spriteSize, spriteSize) 154 }); 155 var halo = Draw2DSprite.create({ 156 texture: tex, 157 width: spriteSize, 158 height: spriteSize, 159 x: body.x, 160 y: body.y, 161 rotation: body.rotation, 162 color: mathDevice.v4Build(1, 1, 1, 1), 163 textureRectangle: mathDevice.v4Build(spriteSize, 0, spriteSize * 2, spriteSize) 164 }); 165 166 sprites.push({ 167 body: body, 168 halo: halo, 169 colorModifier: mathDevice.v4Build(Math.random() / 100, Math.random() / 100, Math.random() / 100, Math.random() / 100), 170 velocity: [Math.random() * 4 - 2, Math.random() * 4 - 2], 171 angularVel: Math.random() / 10 - (1 / 20) 172 }); 173 } 174 175 // Reset position/rotation/color of reused objects etc. 176 var i; 177 for (i = prevObjectCount; i < objectCount; i += 1) { 178 var sprite = sprites[i]; 179 tex = getTexture(); 180 181 sprite.body.setTexture(tex); 182 sprite.body.x = (Math.random() * (gameWidth - spriteSize) + (spriteSize * 0.5)); 183 sprite.body.y = (Math.random() * (gameHeight - spriteSize) + (spriteSize * 0.5)); 184 sprite.body.rotation = 0; 185 sprite.body.setColor(mathDevice.v4Build(1, 1, 1, 1)); 186 187 sprite.halo.setTexture(tex); 188 sprite.halo.x = sprite.body.x; 189 sprite.halo.y = sprite.body.y; 190 sprite.halo.rotation = sprite.body.rotation; 191 sprite.halo.setColor(mathDevice.v4Build(1, 1, 1, 1)); 192 } 193 prevObjectCount = objectCount; 194 } 195 196 // Object used when performing draw() calls. 197 var drawObjectColor = mathDevice.v4BuildZero(); 198 var drawObjectDest = mathDevice.v4BuildZero(); 199 var drawObjectSrc = mathDevice.v4BuildZero(); 200 var drawObject = { 201 color: drawObjectColor, 202 rotation: 0, 203 destinationRectangle: drawObjectDest, 204 sourceRectangle: drawObjectSrc, 205 texture: null 206 }; 207 208 // Buffer used when performing drawRaw() calls. 209 var rawBuffer; 210 if (typeof Float32Array !== "undefined") { 211 rawBuffer = new Float32Array(20000 * 16); 212 } else { 213 rawBuffer = []; 214 } 215 216 //========================================================================== 217 // Main loop. 218 //========================================================================== 219 var fpsElement = document.getElementById("fps"); 220 var gpuElement = document.getElementById("gpu"); 221 var dataElement = document.getElementById("dataTransfers"); 222 var batchElement = document.getElementById("batchCount"); 223 var minElement = document.getElementById("minBatch"); 224 var maxElement = document.getElementById("maxBatch"); 225 var avgElement = document.getElementById("avgBatch"); 226 227 var lastFPS = ""; 228 var lastGPU = ""; 229 var lastData = ""; 230 var lastBatch = ""; 231 var lastMin = ""; 232 var lastMax = ""; 233 var lastAvg = ""; 234 235 var nextUpdate = 0; 236 function displayPerformance() { 237 var currentTime = TurbulenzEngine.time; 238 if (currentTime > nextUpdate) { 239 nextUpdate = (currentTime + 0.1); 240 241 var data = draw2D.performanceData; 242 243 var fpsText = (graphicsDevice.fps).toFixed(2) + " fps"; 244 var gpuText = (data.gpuMemoryUsage / 1024).toFixed(2) + " KiB"; 245 var dataText = (data.dataTransfers).toString(); 246 var batchText = (data.batchCount).toString(); 247 var minText = (data.batchCount === 0) ? "" : (data.minBatchSize + " sprites"); 248 var maxText = (data.batchCount === 0) ? "" : (data.maxBatchSize + " sprites"); 249 var avgText = (data.batchCount === 0) ? "" : (Math.round(data.avgBatchSize) + " sprites"); 250 251 if (fpsText !== lastFPS) { 252 lastFPS = fpsText; 253 fpsElement.innerHTML = fpsText; 254 } 255 if (gpuText !== lastGPU) { 256 lastGPU = gpuText; 257 gpuElement.innerHTML = gpuText; 258 } 259 260 if (dataText !== lastData) { 261 lastData = dataText; 262 dataElement.innerHTML = dataText; 263 } 264 if (batchText !== lastBatch) { 265 lastBatch = batchText; 266 batchElement.innerHTML = batchText; 267 } 268 if (minText !== lastMin) { 269 lastMin = minText; 270 minElement.innerHTML = minText; 271 } 272 if (maxText !== lastMax) { 273 lastMax = maxText; 274 maxElement.innerHTML = maxText; 275 } 276 if (avgText !== lastAvg) { 277 lastAvg = avgText; 278 avgElement.innerHTML = avgText; 279 } 280 } 281 } 282 283 // array of blend modes for when 'cycle' option is chosen. 284 var blendCycleModes = ['alpha', 'additive', 'opaque']; 285 var curBlend; 286 287 var colorTmp = mathDevice.v4Build(0, 0, 0, 0); 288 function mainLoop() { 289 if (!graphicsDevice.beginFrame()) 290 return; 291 292 draw2D.resetPerformanceData(); 293 294 // reset any reused sprites, create any new sprites necessary 295 // to reach current spriteCnt value. 296 validateSprites(); 297 298 if (scaleMode === 'none') { 299 // with scale mode none, we resize the viewport so that 300 // balls can expand into the new area, or become restricted 301 // to the smaller screen area. 302 gameWidth = graphicsDevice.width; 303 gameHeight = graphicsDevice.height; 304 viewport[2] = gameWidth; 305 viewport[3] = gameHeight; 306 } 307 308 configureParams.scaleMode = scaleMode; 309 draw2D.configure(configureParams); 310 311 draw2D.setBackBuffer(); 312 draw2D.clear(); 313 314 curBlend = 0; 315 draw2D.begin(blendMode === 'cycle' ? blendCycleModes[curBlend] : blendMode, sortMode); 316 317 var halfSize = spriteSize * 0.5; 318 319 var xMin = halfSize; 320 var yMin = halfSize; 321 var xMax = gameWidth - halfSize; 322 var yMax = gameHeight - halfSize; 323 324 var i; 325 326 for (i = 0; i < objectCount; i += 1) { 327 if (drawMode !== 'drawRaw' && blendMode === 'cycle' && ((i % 100) === 99)) { 328 draw2D.end(); 329 curBlend = (curBlend + 1) % blendCycleModes.length; 330 draw2D.begin(blendCycleModes[curBlend], sortMode); 331 } 332 333 var sprite = sprites[i]; 334 var body = sprite.body; 335 var halo = sprite.halo; 336 337 if (moveSprites) { 338 body.x += sprite.velocity[0]; 339 body.y += sprite.velocity[1]; 340 341 if (body.x < xMin) { 342 body.x = xMin; 343 sprite.velocity[0] *= -1; 344 } 345 if (body.y < yMin) { 346 body.y = yMin; 347 sprite.velocity[1] *= -1; 348 } 349 if (body.x > xMax) { 350 body.x = xMax; 351 sprite.velocity[0] *= -1; 352 } 353 if (body.y > yMax) { 354 body.y = yMax; 355 sprite.velocity[1] *= -1; 356 } 357 358 halo.x = body.x; 359 halo.y = body.y; 360 } 361 362 if (rotateSprites) { 363 body.rotation += sprite.angularVel; 364 halo.rotation = body.rotation; 365 } 366 367 if (cycleColorSprites) { 368 body.getColor(colorTmp); 369 370 var j; 371 for (j = 0; j < 4; j += 1) { 372 var c = colorTmp[j] + sprite.colorModifier[j]; 373 if (c < 0) { 374 c = 0; 375 sprite.colorModifier[j] *= -1; 376 } else if (c > 1) { 377 c = 1; 378 sprite.colorModifier[j] *= -1; 379 } 380 colorTmp[j] = c; 381 } 382 383 body.setColor(colorTmp); 384 385 // Halo has same alpha, but white color. 386 colorTmp[0] = colorTmp[1] = colorTmp[2] = 1; 387 halo.setColor(colorTmp); 388 } 389 390 if (drawMode === 'drawSprite') { 391 draw2D.drawSprite(halo); 392 draw2D.drawSprite(body); 393 } else if (drawMode === 'draw') { 394 drawObject.texture = body.getTexture(); 395 drawObject.rotation = body.rotation; 396 drawObjectDest[0] = body.x - halfSize; 397 drawObjectDest[1] = body.y - halfSize; 398 drawObjectDest[2] = body.x + halfSize; 399 drawObjectDest[3] = body.y + halfSize; 400 401 halo.getTextureRectangle(drawObjectSrc); 402 halo.getColor(drawObjectColor); 403 draw2D.draw(drawObject); 404 405 body.getTextureRectangle(drawObjectSrc); 406 body.getColor(drawObjectColor); 407 draw2D.draw(drawObject); 408 } else { 409 draw2D.bufferSprite(rawBuffer, halo, (i * 2)); 410 draw2D.bufferSprite(rawBuffer, body, (i * 2) + 1); 411 } 412 } 413 414 if (drawMode === 'drawRaw' && objectCount !== 0) { 415 draw2D.drawRaw(sprites[0].body.getTexture(), rawBuffer, objectCount * 2); 416 } 417 418 draw2D.end(); 419 420 graphicsDevice.endFrame(); 421 422 if (fpsElement) { 423 displayPerformance(); 424 } 425 } 426 427 var intervalID; 428 function loadingLoop() { 429 if (loadedResources === numResources) { 430 TurbulenzEngine.clearInterval(intervalID); 431 intervalID = TurbulenzEngine.setInterval(mainLoop, 1000 / 60); 432 } 433 } 434 435 intervalID = TurbulenzEngine.setInterval(loadingLoop, 100); 436 437 //========================================================================== 438 function loadHtmlControls() { 439 htmlControls = HTMLControls.create(); 440 htmlControls.addSliderControl({ 441 id: "spriteSlider", 442 value: (objectCount * 2), 443 max: 20000, 444 min: 2, 445 step: 2, 446 fn: function () { 447 objectCount = Math.floor(this.value / 2); 448 htmlControls.updateSlider("spriteSlider", objectCount * 2); 449 } 450 }); 451 452 htmlControls.addCheckboxControl({ 453 id: "moveBox", 454 value: "moveSprites", 455 isSelected: moveSprites, 456 fn: function () { 457 moveSprites = !moveSprites; 458 return moveSprites; 459 } 460 }); 461 htmlControls.addCheckboxControl({ 462 id: "rotateBox", 463 value: "rotateSprites", 464 isSelected: rotateSprites, 465 fn: function () { 466 rotateSprites = !rotateSprites; 467 return rotateSprites; 468 } 469 }); 470 htmlControls.addCheckboxControl({ 471 id: "colorBox", 472 value: "cycleColorSprites", 473 isSelected: cycleColorSprites, 474 fn: function () { 475 cycleColorSprites = !cycleColorSprites; 476 return cycleColorSprites; 477 } 478 }); 479 480 htmlControls.addRadioControl({ 481 id: "draw0", 482 groupName: "drawMode", 483 radioIndex: 0, 484 value: "drawSprite", 485 fn: function () { 486 drawMode = 'drawSprite'; 487 }, 488 isDefault: true 489 }); 490 htmlControls.addRadioControl({ 491 id: "draw1", 492 groupName: "drawMode", 493 radioIndex: 1, 494 value: "draw", 495 fn: function () { 496 drawMode = 'draw'; 497 }, 498 isDefault: false 499 }); 500 htmlControls.addRadioControl({ 501 id: "draw2", 502 groupName: "drawMode", 503 radioIndex: 2, 504 value: "drawRaw", 505 fn: function () { 506 drawMode = 'drawRaw'; 507 }, 508 isDefault: false 509 }); 510 511 htmlControls.addRadioControl({ 512 id: "tex0", 513 groupName: "texMode", 514 radioIndex: 0, 515 value: "none", 516 fn: function () { 517 texMode = 'none'; 518 invalidateSpriteTextures(); 519 }, 520 isDefault: false 521 }); 522 htmlControls.addRadioControl({ 523 id: "tex1", 524 groupName: "texMode", 525 radioIndex: 1, 526 value: "single", 527 fn: function () { 528 texMode = 'single'; 529 invalidateSpriteTextures(); 530 }, 531 isDefault: true 532 }); 533 htmlControls.addRadioControl({ 534 id: "tex2", 535 groupName: "texMode", 536 radioIndex: 2, 537 value: "many", 538 fn: function () { 539 texMode = 'many'; 540 invalidateSpriteTextures(); 541 }, 542 isDefault: false 543 }); 544 545 htmlControls.addRadioControl({ 546 id: "sort0", 547 groupName: "sortMode", 548 radioIndex: 0, 549 value: "immediate", 550 fn: function () { 551 sortMode = 'immediate'; 552 }, 553 isDefault: false 554 }); 555 htmlControls.addRadioControl({ 556 id: "sort1", 557 groupName: "sortMode", 558 radioIndex: 1, 559 value: "deferred", 560 fn: function () { 561 sortMode = 'deferred'; 562 }, 563 isDefault: true 564 }); 565 htmlControls.addRadioControl({ 566 id: "sort2", 567 groupName: "sortMode", 568 radioIndex: 2, 569 value: "texture", 570 fn: function () { 571 sortMode = 'texture'; 572 }, 573 isDefault: false 574 }); 575 576 htmlControls.addRadioControl({ 577 id: "blend0", 578 groupName: "blendMode", 579 radioIndex: 0, 580 value: "opaque", 581 fn: function () { 582 blendMode = 'opaque'; 583 }, 584 isDefault: false 585 }); 586 htmlControls.addRadioControl({ 587 id: "blend1", 588 groupName: "blendMode", 589 radioIndex: 1, 590 value: "alpha", 591 fn: function () { 592 blendMode = 'alpha'; 593 }, 594 isDefault: true 595 }); 596 htmlControls.addRadioControl({ 597 id: "blend2", 598 groupName: "blendMode", 599 radioIndex: 2, 600 value: "additive", 601 fn: function () { 602 blendMode = 'additive'; 603 }, 604 isDefault: false 605 }); 606 htmlControls.addRadioControl({ 607 id: "blend3", 608 groupName: "blendMode", 609 radioIndex: 3, 610 value: "cycle", 611 fn: function () { 612 blendMode = 'cycle'; 613 }, 614 isDefault: false 615 }); 616 617 htmlControls.addRadioControl({ 618 id: "scale0", 619 groupName: "scaleMode", 620 radioIndex: 0, 621 value: "none", 622 fn: function () { 623 scaleMode = 'none'; 624 }, 625 isDefault: false 626 }); 627 htmlControls.addRadioControl({ 628 id: "scale1", 629 groupName: "scaleMode", 630 radioIndex: 1, 631 value: "scale", 632 fn: function () { 633 scaleMode = 'scale'; 634 }, 635 isDefault: true 636 }); 637 638 htmlControls.register(); 639 } 640 641 loadHtmlControls(); 642 643 // Create a scene destroy callback to run when the window is closed 644 TurbulenzEngine.onunload = function destroyScene() { 645 if (intervalID) { 646 TurbulenzEngine.clearInterval(intervalID); 647 } 648 649 if (gameSession) { 650 gameSession.destroy(); 651 gameSession = null; 652 } 653 }; 654 };