draw2d.js
  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 };