It has been said that the only constant in the computer industry is change. Such adage is the more true when applied to software engineering and, within that field, the area of web development seems inherently prone to quick obsolescence. Now consider rich internet applicacions (RIA’s) of the sort that sprouted in the last decade, constantly pushing the envelope of web technologies and standards by squeezing hardware performance to the last drop, ensuring workability on multiple browsers, often making the most of low-bandwith or high-latency connections and adapting to an ever-wider array of mobile computers and novel input devices. All the while complying with all the requirements of social networks, design and aesthetics, responsiveness and multimedia. Indeed, that is the arena where the Flash platform, including Flex and AIR, thrives.
The bottom line is that when an ActionScript coder faces a new project, it is not uncommon that they have to discard a good number of the previous architectures/frameworks/libraries/you-name-it and instead, tread a new path. Such approach requires constant learning, testing and trial-and-error. On the other hand, that is also where most of the fun is!
Here at Cycle-IT we got through yet another 3D application not long ago. In the (very brief) interim before tackling other projects that were in the pipeline, I’m taking a moment to share some of the things we learnt during the last few months of 3D ActionScript development, regarding performance.
Mainly following up from the results of our recent comparison of 3D libraries [in Spanish], we opted for Away3D [in Spanish] as our 3D engine of choice. The ideas, performance tips and strategies that we discuss on this post range approximately from the general to the specific, thus: 3D in general → 3D with Flash → Away3D.
For the moment, we are describing only recipes that do not require modifications of Away3D’s source code. On a later post, we’ll dive closer to the internals of Away3D and share our hacks for Away3D’s codebase to enhance its performance in certain situations, or to extend it with new features.
After as little as a few weeks coding 3D scenes or animations, no matter the language or the platform, every developer starts building their own personal list of do’s and, most importantly, dont’s. Such a list of good practices usually includes the obvious (eg don’t over-tessellate a continuous, flat surface with few or no bumps) to the advanced (eg crop your textures to sizes that are divisible by a nice power of 2 to optimise mipmapping). A quick online search will reveal dozens of those performance tips. Here come five of them, which we applied recently ourselves.
1. Avoid intersecting polygons when possible
3D engines usually have a hard time computing intersections of triangles, and the results of these computations might be surprising, depending on how the particular engine deals with entangled surfaces. Occlusion culling is the process of deciding which faces are hidden behind other objects in order to improve performance by excluding them from the scene. Because of the algorithms involved, occlusion culling is faster when objects are far from each other. In 3D scenes replicating real-world environments it is not very likely that objects will enter other objects, so that their surfaces will be both facing out and very close to each other (think of animated cartoons: a ghost the moment it starts leaving a recently deceased body, when body and ghost are splitting). However, in other family of applications (3D charting, fluid simulation, computer art) it might happen.
2. Minimise the number of transparent and translucent objects
Again, letting some objects be partially visible through objects that are closer to the camera adds an extra layer of complexity within the engine: for one reason, the average count of non-completely-invisible triangles in your scene will grow. On top of that, and even if the number of rendered polygons did not increase, the low-level rasterisation algorithms run by the engine would have to take into account alpha channels and combine colours, eg via pixel shaders, to work out the resulting image.
3. Use flags to decide when re-rendering is not necessary
3D libraries written in ActionScript require the user to explicitely invoke their core routine, the one that renders a new frame. Typically the method render()
of the scene or the view is called once for every Flash frame:
addEventListener(Event.ENTER_FRAME, onEnterFrame); // … protected function onEnterFrame (event:Event):void { // Do stuff. // Render the scene: _view.render (); // Away3D. _view.singleRender (); // Papervision3D. }
The libraries are also smart enough to skip some stages along their graphics pipeline when there has been little or no change in the scene. However, we can still save on resources by simply not re-rendering the scene when we know for sure that it is not necessary. In certain cases, when there are few elements and there isn’t any continuous animation, it is easy to detect such condition.
Use one or more movement flags in your code and bind the 3D rendering to a logical condition built upon those flags in such a way that the scene isn’t recalculated until there is some noticeable change in it. That way, Flash Player can save precious resources for other tasks for a while, like animating your GUI overlays or crunching the data it just received from the server.
addEventListener(Event.ENTER_FRAME, onEnterFrame); // … protected function onEnterFrame (event:Event):void { // Do stuff. // Render the scene (only if it's necessary): if (_relevantChangesInTheScene) { _view.render (); // Away3D. _view.singleRender (); // Papervision3D. } }
4. Simplify your objects temporarily to make things responsive
Sometimes you can afford detailed geometries and fine textures in your 3D environments because all you need is to re-create a static scene intended for architecture or for artistic purposes, or because camera movements and animations are short and gentle. Unfortunately, the opposite is more common.
In those cases, you surely hate the idea of degrading your beautiful scene — simplifying lighting, textures and/or meshes to ensure smooth animations. However, there’s an intermediate solution: swap some of those elements for simpler, faster versions of themselves during the most expensive transitions and swap them back at the end. One example: maybe you can turn off all directional lights and reflections in the scene until the main introductory travelling finishes, and switch on cheaper ambient lighting in the meantime. Another example? Perhaps your users won’t notice if that fancy Phong shading in the background and all environment mapping disappear while all the attention is on the main character, who is much closer to the camera.
We recently put this idea into practice by swapping a moderately expensive textured material and a much simpler outline material. The main object in front of the camera was rendered using the complex texture when the camera was still. Meanwhile, the simpler material was already instantiated, with all its properties set and ready to be used, but not assigned to the object yet. Every time the user moved around (dragging the scene with the mouse or pressing the arrow keys on the keyboard) the simplified material was assigned to that object to ensure a smooth and responsive animation. Of course the user could notice the lower quality of the result. But that was OK, because as soon as the camera completed its tween (a few tenths of a second after the MouseEvent.MOUSE_UP
or KeyboardEvent.KEY_UP
event) the original material was used again. If we had kept the same material during tweens, camera animations would have been much more bumpy.
5. Turn down FP’s quality settings
In Flash, the stage has a property named quality
, which fine-tunes the quality of the rendering. The quality
property may take any of the four values defined as constants in flash.display.StageQuality
, namely: LOW
, MEDIUM
, HIGH
, BEST
.
This property affect three settings within Flash Player: the algorithm used to make bitmaps smoother, the dimension of the anti-aliasing matrix (if any at all) and mipmapping. When your application uses only device fonts (which are unaffected by this property) and crisp outlines at pixel-level are admissible, programmatically setting the quality of FP to MEDIUM
, or even LOW
, may be a quick way to gain a couple of extra frames per second.
To expand on all this, make sure to check Flash Player 10.1 hardware acceleration for video and graphics on Adobe Developer Connection (ADC), which lists useful tips for improving GPU performance and graphics rendering. That article is focused on FP 10.1, but most of what it says makes perfect sense for any version of Flash.
Buen punto. Te ha hecho propaganda Javi sobre el artículo !! Saludos