In my last article I wrote about the different methods that Silverlight provides for doing rendering. Now I am going to write about how ExEn uses Silverlight in order to implement XNA’s SpriteBatch class.
(I am currently crowd-funding a project to make ExEn open source. If this is something that you would find useful, please go there and contribute!)
Sprites in Silverlight
The normal way to do sprite graphics in Silverlight is to put Image elements on a Canvas element and manipulate them as your game state changes. This is known as a retained-mode API – Silverlight retains the data about your scene in its own format, and you have to synchronise it with your game state – a tedious and error-prone task.
In contrast XNA uses an immediate-mode API – you specify what you want to it draw, it draws it immediately and discards the information. This is much more versatile for game development.
The problem with this approach is that it loses information about the scene being displayed, making it difficult or impossible to reconstruct.
The way that ExEn overcomes this problem is by treating the Silverlight scene as a pool of sprites. When it wants to draw a sprite, it finds an appropriate existing sprite in the pool and makes it visible (or creates it) and topmost in the Z-order. Any sprites not used on a frame are made invisible.
The exact mechanism by which it does this (and it does it very quickly) is best described by reading the ExEn source code. So it would be really cool if ExEn was open source, hint-hint, nudge-nudge 😉
Sprites in XNA
The properties that determine what a sprite looks like in XNA are specified in SpriteBatch’s Draw method:
public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth);
- Transformation (position, rotation, origin, scale, reflection – which is the effects parameter),
- Colour (including opacity in the alpha channel), and
- Source rectangle
Additionally there is the Begin call, which can specify additive or alpha blending and an overall transformation matrix.
(This is for XNA 3.1, XNA 4 allows you to set the entire GPU state in the Begin call – although this isn’t really applicable to Silverlight, or useful for 2D graphics anyway. Currently ExEn matches the XNA 3.1 API, although I plan to add support for the XNA 4 calls as well.)
To implement Texture2D in Silverlight, a texture is loaded into a BitmapImage which is then applied to Image elements to render each sprite. This is quite straightforward.
This includes the sprite’s position, rotation, origin, scale, reflection overall transformation of the batch, as well as fix-ups necessary to support the optional source-rectangle parameter.
This is handled in Silverlight using a RenderTransform (which is hardware accelerated in Silverlight’s hardware renderer).
Because of the way ExEn pools sprites, it does not matter if the RenderTransform changes each frame (the recalculation itself is heavily optimised). In fact, by recalculating it each frame, the sprite pool is free to reuse sprites without regard for their positioning. This is how I got one of my big performance wins over SilverSprite.
I also managed to get my matrix calculations correct! I found bugs here in both SilverSprite and XnaTouch 😐
Colour and Additive Blending
If you were paying close attention in my last article, you may have noticed that I said that Silverlight can do opacity (which is how the alpha channel of the sprite’s colour is handled), but I never said anything about colour tinting.
Unfortunately Silverlight does not directly support this common effect. But it can be implemented using pixel shaders.
As I mentioned in my last article, Silverlight’s pixel shaders run on the CPU and are not fast. They also prevent elements from being cached in hardware. So, to improve performance, the shader is executed only once per colour and the result is cached in a WriteableBitmap which is then used to texture the sprite.
Silverlight also does not normally support additive blending. But it does use premultiplied alpha (explanation), which is capable of doing both additive and alpha blending. To perform additive blending, the alpha channel of an image must be set to zero, without modifying the RGB – this is something that a pixel shader and WriteableBitmap can do. (Light Blocks uses additive blending, if you would like to see an example.)
(Actually, the Silverlight software renderer makes some assumptions that break this approach to additive blending. But there is also a work-around for this, which is implemented in ExEn.)
The primary use for the source-rectangle parameter is to implement sprite sheets. This is where you pack multiple sprites into a single texture – often for animation:
Silverlight does provide a mechanism to perform clipping, but it cannot be hardware accelerated unless the clip is an oriented rectangle, relative to the screen. Any sprite that, for example, rotates would end up being rendered in software.
ExEn aims to render as much as possible in hardware for maximum performance. So, as with colours, ExEn pre-renders clipped versions of the original texture for each source-rectangle. These can then be rendered in hardware.
Caching and Hints
Now, what if you are not using fixed colour tints for your sprites? Perhaps a smooth colour animation:
Or what if you are not using source-rectangle to implement sprite sheets, but are instead animating it for some kind of effect?
If you attempted to pre-render these and cache them, you’d create hundreds of in-between surfaces! You would very quickly run out of memory!
To work around this, ExEn on Silverlight provides some “hint” functions that tell the renderer that the colour or the source-rectangle will be dynamic:
These functions cause the creation of special “dynamic” sprites in the sprite pool, which allow their colour and/or source-rectangle to be modified. The dynamic sprite is re-rendered as needed by Silverlight’s software renderer.
In my last article I mentioned that it is preferable in Silverlight to make all software rendering contiguous in the Z-order.
For example, Captain Stretchy-Arms’ arms are drawn using my line rendering code (supporters of ExEn get a copy of this), which uses Polyline elements in Silverlight which can only be rendered in software. In between his arms, in the Z-order, are a few sprites that make up his body.
It would be better for performance to render those sprites in software along with the arms on a single software surface, rather than allowing the arm rendering to be split into multiple software surfaces. So there is a hint function to disable hardware caching of some sprites:
In XNA, and in ExEn on the iPhone, these hint functions are simply blank extension methods that get removed by the compiler at compile time – so they have no effect on your game on other platforms.
This was a general overview of how ExEn works in Silverlight behind the scenes.
If you found this article interesting or useful, then please contribute to the ExEn project over at RocketHub. You will not be disappointed. I strongly believe that ExEn is not only by far the best implementation of XNA on Silverlight, it is also the best way to make games on Silverlight.
And it works on the iPhone! And I’m bringing it to Android!