HTML5 canvas med JavaScript og Flash Player performance

Dette indlæg er en undersøgelse af den performance som præsteres af henholdsvis JavaScript og Canvas elementet og dets rolle på mobile platforme i fremtiden. Inspirationen fik jeg da jeg kom forbi Almer Thies Water Ripple Canvas der kørte uden problemer (med Canvas: 20fps og Water:30 fps) på min computer men skræmte mig fra vid og sans med et maks på 0.5 fps på min iPhone (yep. En halv frame i sekundet!!). Jeg fik derfor en idé om at finde et sammenligneligt stykke kode, der kunne afvikles fra canvas og nemt porteres til Flash, så jeg kunne danne mig et overblik over hvor godt de hver i sær præsterede.
Selv er jeg tilhænger af Adobe Flash Player og har altid ment at iOS bandlysning af den på grund af performance ikke havde nogen gang på jord, set i lyset af alternativerne. At vi så grundlæggende skal tage os en grundig snak om, hvad der skal ske, når vores mobile enheder besøger en hjemmeside i årene der kommer, er en helt anden snak – en fremtid, som jeg spår som “det store dyk”, i værste tilfælde.

Forsøg 1

Valget faldt på de berømte snefnug, som er elsket/hadet af alle. Effekten bliver udelukkende produceret med kode, og kan skaleres nemt for at øge belastningen. Jeg fandt et eksempel på http://blog.webreakstuff.com/2010/11/building-a-canvas-snowglobe/ af Pedro Freitas. Koden kan findes på Github via https://gist.github.com/716215. Jeg skulle også bruge en FPS-counter, der nemt kunne porteres til ActionScript og valget faldt på kodestumpen fra http://stackoverflow.com/questions/4787431/check-fps-in-js
Jeg har brug det til at producere et virkende eksempel med FPS-counter, der kan findes på http://eksempler.hjaelpmignu.dk/flash/actionscript/snowflake/snowcanvas.html. Jeg er kommet frem til følgende data (max er sat til 100 FPS):
  • Desktop PC: 99.9 FPS (Firefox)
  • MacBook Pro: 97 - 98 FPS (Safari)
  • iPad (iOS 4.3): 19 - 20 FPS (opdateret 17/3 – 2011)
  • HTC Desire HD (2.2): 15.5 - 16.5 FPS
  • Inspire 4G (2.3): ~ 14 FPS (opdateret 17/3 – 2011)
  • HTC Desire (2.2): 14.4 - 14.7 FPS
  • iPad 1 (iOS 4.3): 13 – 13.5 FPS (opdateret 17/3 – 2011)
  • iPad 1 (iOS 4.2): 12.4 - 13.3 FPS
  • iPhone 4 (iOS 4.3): 9.9 - 10.5 FPS (opdateret 17/3 – 2011)
  • iPhone 4 (iOS 4.2): 9.9 - 10.3 FPS
  • iPhone 3GS (iOS 4.2): 9.9 - 10.3 FPS
  • HTC Legend: 7.5 - 8.9 FPS
Tak til @seb_ly og @leebrimelow for yderligere testresultater
Jeg droppede begrænsningen og så at PC’eren faktisk nemt snurrede den over 200 FPS, men meningen her er ikke at benchmarke i den høje ende, men finde et forhold i “den lave ende” og en relativ måling til en standard maskine. PC såvel som Mac varierer så meget på hardware samt udvalg af browsere, at det ikke vil bidrage til noget positivt at afdække alle facetter.

Konklusion

  • Det er klart at fornemme at begge computere rammer toppen. Den bærbare Mac har smidt en frame eller to i testen, men vil sikkert ramme 100 FPS i en anden. Selvfølgelig kan den ikke løbe fra et Quadro grafikkort, men klart er det at der er rigelig overskud til at løse opgaven.
  • iOS maskinerne (iPad 1 og iPhone 4) har en hård tid, med at producere frames til canvas elementet. I gennemsnit ligger de på lidt over 10 FPS.
  • Desire HD ligger en smule højere. Ikke meget, men alligevel nok til at det må betegnes som en bedre afvikling.

Forsøg 2

Forsøg nummer to var lavet for at sammenligne performance mellem JavaScripts arbejde på canvas, samt Flash Playerens arbejde med BitmapData klassen. Teknikken der bruges til begge er meget ens, og det vil give det bedste sammenligningsgrundlag, og mindst mulig redigering i koden. Du kan se et eksempel på http://eksempler.hjaelpmignu.dk/flash/actionscript/snowflake/snowFLA.html
Følgende ændringer er blevet foretaget fra originalkoden:
  • Snefnug lavet som en selvstændig klasse (Flake.as)
  • Har opdateret variabler osv så de er strict typed
  • Mindre ændringer i variabel navne
  • Brugt Flash Playerens flash.sensors.Accelerometer til at håndtere bevægelse
Flake.as

package
{
import flash.display.Sprite;
import flash.display.BitmapData;

public class Flake extends Sprite
{
private var canvasWidth;
private var canvasHeight;
private var speed:Number;
private var alfa:Number;
private var size:Number;
private var amp:Number;
private var shift:Number;
private var drift:Number;

public function Flake(w,h,a,s)
{
this.canvasWidth = w;
this.canvasHeight = h;
this.x = 200;
this.y = Math.random() * -1 * h;
this.alfa = Math.random() * 0.5 + a;
this.speed = Math.random();
this.size = s - this.speed - this.alfa;
this.amp = Math.random() * 2;
this.shift = Math.random() * 25 + 25;
if (Math.random() > 0.5)
{
this.shift *=  -1;
}
this.drift = Math.random() - 0.5;
draw();
}
public function draw(canvas:BitmapData = null):void
{
this.graphics.beginFill(0xFFFFFF,this.alfa);
this.graphics.drawCircle(0,0,this.size);
this.graphics.endFill();
}
public function move(f,wind):void
{
this.y +=  this.speed;
this.x +=  Math.cos(f / this.shift) * this.amp + this.drift + wind;
if (this.y > this.canvasHeight)
{
this.restart();
}
}
public function restart():void
{
this.y = -20;
this.shift = Math.random() * 25 + 25;
this.x = 200;
}
}
}

 

MainApp.as
 

package
{
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.geom.Rectangle;
import flash.geom.Matrix;
import flashx.textLayout.elements.InlineGraphicElement;
import flash.sensors.Accelerometer;
import flash.events.AccelerometerEvent;
import flash.text.TextField;

public class MainApp extends Sprite
{
private var bgflakes:Array = new Array();
private var fgflakes:Array = new Array();
private var bgFlakeCount:int = 200;
private var fgFlakeCount:int = 50;
private var frameCount:int = 0;
private var wind:int = 0;
private var dWidth:int;
private var dHeight:int;
private var orientX:int = 1;

private var bgBitmapData:BitmapData;
private var bgCanvas:Bitmap;
private var fgBitmapData:BitmapData;
private var fgCanvas:Bitmap;
private var timer:Timer;
private var orientation:Boolean = false;
private var myAcc:Accelerometer;

private var filterStrength:int = 20;
private var frameTime:int = 0;
private var lastLoop:Date = new Date();
private var thisLoop:Date = new Date();

private var fps:TextField;

public function MainApp()
{
init();
}

private function init()
{
dWidth = this.stage.stageWidth;
dHeight = this.stage.stageHeight;
bgBitmapData = new BitmapData(dWidth,dHeight,false,0x000000);
bgCanvas = new Bitmap(bgBitmapData);
fgBitmapData = new BitmapData(dWidth,dHeight,true,0xFF0000);
fgCanvas = new Bitmap(fgBitmapData);
addChild(bgCanvas);
addChild(fgCanvas);

fps = new TextField();
addChild(fps);
fps.textColor = 0xFFFFFF;

var i = 0;
for (i=0; i 			{
bgflakes.push(new Flake(bgCanvas.width, bgCanvas.height,0,3));
}
for (i=0; i 			{
fgflakes.push(new Flake(fgCanvas.width, fgCanvas.height,0.2,4));
}
timer = new Timer(10);
timer.addEventListener(TimerEvent.TIMER, draw);
timer.start();

if (Accelerometer.isSupported)
{
orientation = true;
myAcc = new Accelerometer();
myAcc.addEventListener(AccelerometerEvent.UPDATE, onAccUpdate);
}
}

private function onAccUpdate(e:AccelerometerEvent):void
{
orientX = e.accelerationX;
}

private function setWind()
{
if (! orientation)
{
var mx:Number = mouseX - dWidth / 2;
wind = (mx/dWidth)*3;
}
else
{
wind = Number(orientX) * 3;
}
if (! wind)
{
wind = 0;
}
}

private function draw(e:TimerEvent)
{
frameCount +=  1;

bgBitmapData.fillRect(new Rectangle(0,0,dWidth, dHeight),0x000000);
fgBitmapData = new BitmapData(dWidth,dHeight,true,0xFF0000);
setWind();
var i:int = 0;
for (i=0; i 			{
bgflakes[i].move(frameCount, wind);

bgBitmapData.draw(bgflakes[i], new Matrix(1,0,0,1,bgflakes[i].x, bgflakes[i].y));
}
for (i=0; i 			{
fgflakes[i].move(frameCount, wind);

fgBitmapData.draw(bgflakes[i]);
}

var thisFrameTime:int = (Number(thisLoop=new Date())) - Number(lastLoop);
frameTime+= (thisFrameTime - frameTime) / filterStrength;
lastLoop = thisLoop;
fps.text = (1000/frameTime).toFixed(1) + " fps";
}
}
}

 

Kommentarer til koden
Jeg har beholdt draw() inde i Flake klassen og tegner den fysisk op med graphics.drawCircle() for at beholde koden på tværs af eksemplerne. Overførsel af værdier om canvas-størrelse osv. er også bevaret i Flash eksemplet er også bevaret. Endelig er der brugt Bitmap() og BitmapData() til at simulere de egenskaber som bruges på canvas elementet.

Resultat 2

På modeller, hvor der både kan køres Flash Player 10.1 og canvas vil jeg prøve at teste dem op mod hinanden. Ikke så meget en kamp mellem det ene og det andet, for fremtiden (tror jeg) tilhører dem begge, men mere et forsøg på at finde ud af, hvor dårlig Flash præsterer og om tilhængere af canvas, som “Flash Killer” har noget at have det i. Jeg vil opdatere efterhånden som jeg rammer flere forskellige telefoner på min vej:
  • Dersire HD (2.2): Canvas ~ 16.5 FPS og Flash 10.1 ~ 24.2 FPS
  • Inspire 4G (2.3): Canvas ~ 14 FPS og Flash 10.1 ~ 14 FPS (opdateret 17/3 – 2011)
  • HTC Desire (2.2): Canvas ~ 14.5 FPS og Flash 10.1 21.7 to 23.5 FPS
I er velkommen til at indsende oprigtige målinger

Konklusion

Jeg ser en relativ højere frame rate når jeg afvikler i Flash Player på Android, end hvis jeg udfører tilsvarende med canvas elementet. Det er ikke lykkedes mig at vende forholdet mellem tallene på min telefon, end ikke få dem til at nærme sig hinanden, mærkbart. Jeg vil derfor mene at brugeren vil have en bedre oplevelse af dette eksperiment i en Flash Player, end ved en canvas-løsning. Det er vigtigt at huske på, at canvas elementets evne til at præstere, ikke behøver at gå ud over den generelle opfattelse af HTML5 og CSS3. Min fornemmelse er, at CSS3 har en ok afvikling på de mobile enheder, måske på grund af deres forudsigelige natur, men det har jeg stadig tilbage at undersøge.
Endelig har jeg ikke forholdt mig til, hvad der er installeret på enhederne. Det kan være relevant i en fintælling at se på parametre som:
  • Hvor lang tid telefonen har været tændt.
  • Er den tilsluttet oplader.
  • Kører der programmer i baggrunden.
  • Videodokumentation af test
Mit ønske her er at få nogle tal i luften, og et forhold til den relativt ringe afvikling af materiale på mobile enheder.
Meld gerne tilbage med resultater af jeres egne målinger på computer, tablet og mobil.
Links til tests:
Links til dokumenter


Leave a Comment