Recently, I’ve been working on a larger game project which should run on the Adobe Flash and Adobe AIR platforms.
TL;DR – If you want to create 3D realtime games, use something different, don’t use Flash.
Because the preferred client technology was Flash anyway, we did some quick tests with some groups of meshes rotating around. The results were okay so after some additional tests, we gave the “OK” to do it with Flash.
Development has started with Away3D 4.0 beta, at that time, Away3D had a lot of bugs and a lot of features were missing. Some of the bugs have been fixed by myself, others were reported and were fixed by the Away3D team.
Let me say that, although Away3D looked unfinished at that time and even nowadays lacks some features, in general, Away3D is not a bad engine and looks like it has been coded by people who know how to do it right.
But whatever improvement the Away3D team or me implemented, in the end we all had to cope with the poor Stage3D interface.
For standard textures, the size is limited to be power-of-two. If you work for mobile devices, you know for sure that you need to save memory wherever you can. POT-textures force you to insert empty areas into a texture which will eat up additional memory. As plenty of mobile devices accept NPOT-textures, this hard restriction doesn’t make sense.
You can create standard textures without mip maps. But even if you do not need them, Flash allocates the whole space for mipmaps on the GPU, always.
Recently, a new texture type became available, which is called “RectangleTexture” (who can I blame for this name?) which supports NPOT-textures. Having this advantage now, the downside of RectangleTexture is: There is no mip map support at all.
Flash supports runtime texture compression. Not a bad thing, but this feature is not supported on mobile devices! Mobile devices badly need it while it is not so important for web, so why isn’t it available?
There is also an offline tool available, png2atf, but it takes several minutes for a single texture to be compressed.
Additionally, most of the time we cannot use them because they look so bad. At least when using alpha textures, the visual artifacts are unacceptable.
Rendering single objects with a lot of polygons works as expected, performance is good. No 3D api usually has problems rendering static meshes, so this is no surprise.
Things get worse when using more draw calls or if geometries need shader parameters. I haven’t seen any API that is slower here. Context3D.setProgramConstantsFromXXX are totally useless if you want to do something real, for example skeletal animation. Using those functions together with drawTriangles will eat up most of your precious CPU time. Not a big problem on desktop systems, but on iOS, you’ll notice how awfully slow the Context3D interface is here.
This is probably “by design”: While the GPU / DirectX / OpenGL work usually with 32 bit floating point data, Flash only supports Numbers, which are 64 bit float. So everything needs to be converted everytime you call these functions. And there is no way around it when you do for example skeletal animation.
I tried to use FlasCC to work around this problem, but guess what, there are no native calls to these functions where you can use C float data. You are forced to use ActionScript-Variants even there, so there is no win at all.
The language used by Adobe for shaders is called AGAL – Adobe Graphics Assembly Language.
Assembly? Yes, indeed. I’ve worked with D3D, OpenGL, NVidia Cg for 10+ years, they all offer high level shading languages. But with Adobe, you get ported back into the 1980s to fight for register usage, use mov, add, sub again. You don’t want to do that in 2014 anymore. Needles to say that it’s also quite limited in terms of available registers, number of opcodes etc.
If you create a 3D game, you need strong math classes. Unfortunately, ActionScript 3 is not the best language for this and the classes that are provided by the runtime lack a lot of features.
In ActionScript, Vector3D is not a value type but a full fledged class with all its advantages but also disadvantages.
There are only three static variables inside, one for each axis. A zero is missing. Due to the fact that all Vector3Ds are references, you can create ugly things if you don’t be careful. For example, take a look at the following code:
var foo:Vector3D = Vector3D.X_AXIS;
What actually happens here is that you invert the value Vector3D.X_AXIS because foo is only a reference. Looking to prank your coworkers? Just fool around with everyones coordinate system.
Missing operator overloading is also something that doesn’t really help to achieve code clarity.
var meshPos:Vector3D = m_camera.forwardVector.clone();
meshPos.scaleBy( distToCam );
meshPos.incrementBy( m_camera.position );
It would be a lot easier to read:
var meshPos:Vector3D = m_camera.position + m_camera.forwardVector * distToCam;
When creating realtime applications, it is eligible to prevent any allocation at all. Unfortunately, because Vector3D is not a value type, flash doesn’t help here. Prevent use of
vecA = vecA.add( vecB );
Because this allocates a new vector. Instead, use
vecA.incrementBy( vecB );
This can be solved, but what if you would like to transform e.g. 10000 Vector3D objects by a Matrix3D? You’re lost. All transformation functions of the Matrix3D class return new Vector3D objects, there is no function to work on a Vector3D inplace. More Matrix3D functions act this way, for example decompose and even the position and rawData properties.
All this leads to a weak math performance of Flash in general.
More language issues are problematic, for example the lack of real inlining. The ASC2 compiler introduced an inlining feature that is so limited that it can be used only on very simple functions. It is not automatic, but must be inserted manually. If you overuse it, you’ll often end up with cryptic compilation errors like “stack underflow”. If you also use C++ sometimes, with “inlining support” you for sure expect more than what Adobe has to offer here.
Ever tried to deploy a larger AIR app to iOS? Waiting more than 30 minutes caused us to use separate build machines to build iOS versions. They must be optimizing really hard, you may think. Would be nice if the performance would justify this, unfortunately, it doesn’t.
With regards to Stage3D, there isn’t any that I’m aware of. There is Starling, but as long as you cannot use your UI that you designed in Flash Pro, it’s more or less useless. We had the idea to write a converter, but animated MovieClips are problematic because you won’t win any performance when rendering a MovieClip to a texture every frame. Or you’ll lose a lot of memory for sprite sheets that may become very large.
So UI with Stage2D is the current solution if you want to have a graphics artist to be able to design it.
Be warned, the 2D scanline renderer of Flash is awfully slow on mobile devices, if your artist really does a good job visually, you’ll probably end up spending days to optimize his number of display objects to something acceptable to reach at least 15 frames/sec.
Adobe decided to always render Stage2D above Stage3D. This may be useful in most cases, but imagine a 2D UI control that should display details of a 3D unit. You’ll start cuting holes into the 2D interface using masking which makes the 2D UI even slower.
An incomplete list of some of the things that happen more or less on a daily base.
- In Flash Pro, you better split a bigger UI into multiple SWCs and embed them so the artist can work without interrupting the coders. Randomly while compiling the main project, you’ll end up with “cannot convert foo to MovieClip” while the Flash runtime constructs the display object hierarchy. Just re-publish in Flash Pro and pray to get it running.
- iOS frame rate drops down heavily on every touch event because the AIR runtime searches for SimpleButton instances inside the whole display list. Adobe is aware of the issue but doesn’t fix it.
- AIR deployment on iOS fails most of the time. Sometimes with an error message, sometimes not. Just try it again, sometimes it works. I switched to iFunBox (App/file manager for iOS devices) to prevent getting annoyed too often.
- TextField.textWidth and TextField.textHeight may return wrong numbers if text fields use embedded fonts. Highly depends on the content.
- Adobe Scout (realtime profiler from Adobe, part of the Adobe Gaming SDK or Creative Cloud) tracks less than 20% of the really used memory in a lot of cases. Additionally, “Other memory” and “Other bitmap data” do not really help identifying memory issues at all.
- Some areas of the 2D UI are sometimes not redrawn. Doesn’t happen in all browsers and varies between different Flash player versions and also debug/release builds.
- If you fail to set up your native extension properly, iOS version will hang in its splash screen without any useful info to debug what the problem is.
Tips & Tricks
- Vector3D.length is slower than calculating it manually, both on Web & iOS, tested with AIR 4.0 / FP 12.
- Vector3D.copyFrom() doesn’t copy the w component, remember this if you use Vector3D e.g. for colors
- A static Vector3D to be used as temporary vector for calculations can be a huge speed improvement.
- Throw away your BitmapData with dispose as soon as you uploaded your bitmap to a Texture to prevent doubled memory usage
- To get rid of 2D/3D sorting problems, on desktop, Context3D.renderToBitmapData is fast enough to even reach 60 fps, but do not try this on mobile devices.
- TheMiner is a good profiling tool that runs inside your Flash application.
- Rendering a DisplayObject to a texture is costly, but reduces work on the 2D renderer and is usually a good caching solution.
- null-out everything when you don’t need it anymore to reduce garbage collection cycles. Even remove weak event listeners.
- Profiling Hint: Adobe Scout displays memory used by Vector.<int> and Vector.<Number> as “Other”, Vector.<Boolean> as “ActionScript objects”, Vector.<String> as both “Other” (about 1/3) and “ActionScript Objects” (about 2/3)