The way rendertargets work in XNA is somewhat different to regular DirectX, and although we’ve already discussed most of the details in various forum threads, I recently noticed there doesn’t seem to be any one place that explains everything all together in one place. So here it is!
First, the basics. XNA provides just three GraphicsDevice methods relating to rendertargets:
And here is how you use them:
RenderTarget2D myRenderTarget = new RenderTarget2D(...); device.SetRenderTarget(0, myRenderTarget); // draw some stuff device.ResolveRenderTarget(0); device.SetRenderTarget(0, null); // use myRenderTarget.GetTexture to look up the data you just rendered
You can also resolve from the backbuffer, which you can think of as the default rendertarget used when you haven’t specified a custom one:
Texture2D resolveTexture = new Texture2D(device, w, h, 1, ResourceUsage.ResolveTarget, format, ResourceManagementMode.Manual); // draw some stuff device.ResolveBackBuffer(resolveTexture);
Unfortunately, there are three special rules to complicate the picture:
- After any call to SetRenderTarget, the contents of the previously active rendertarget are undefined
- After calling ResolveRenderTarget, the contents of the rendertarget you just resolved from are undefined
- After calling ResolveBackBuffer, the contents of the backbuffer are undefined
Note that “undefined” is a tricky word. It might mean “is set to zero”, or it could mean “contains random values”, or perhaps “hasn’t changed at all, and still contains exactly what you would expect”.
In XNA, the exact meaning of “undefined” is different on Xbox and Windows, and also depends on whether your rendertarget uses multisampling or not. This is unfortunate, because it lets you write code assuming one particular behavior, and this may happen to work on one platform but will then behave unexpectedly on another.
If you want your code to be robust and portable, you have to avoid making any assumptions about the contents of things that have become undefined. That means whenever you change rendertarget or call resolve, you must assume all existing rendertarget data has been lost. If you need to preserve the contents of a rendertarget across such an event, the only reliable way to do that is by drawing the texture you resolved into back over the top of the now undefined rendertarget:
device.ResolveBackBuffer(resolveTexture); spriteBatch.Begin(SpriteBlendMode.Opaque); spriteBatch.Draw(resolveTexture, Vector2.Zero, Color.White); spriteBatch.End();
You may be wondering why XNA imposes these peculiar rules. The reason is that there are three fundamentally different ways in which rendertargets can be implemented.
On Windows, if you are not multisampling, a single area of video memory can be used both for rendering and as a texture. This means resolve is essentially a no-op, and the contents of rendertargets are never actually lost.
On Windows, if you are multisampling, a rendertarget requires two areas of video memory: one large buffer for rendering into, plus a smaller texture for holding the downsampled result. The resolve call copies from one to the other, taking care of the multisampling as it goes, but each buffer still has its own area of video memory so the contents are never lost.
On Xbox, the GPU actually only has one physical rendertarget, which is stored in a special area of incredibly fast memory (this crazy silicon is one of the main reasons the Xbox GPU is so fast). You can’t texture from this special memory, and you can’t render into normal memory, so when you are done rendering to the special memory, you have to copy the results back into a normal texture before you can do anything with them (fortunately, there is more crazy silicon devoted to making that copy operation pleasingly quick). Because all rendertargets are really just pointers to the same special memory, there is fundamentally no way to persistently store an image in one while rendering something different to another, so the contents will always be destroyed as soon as you switch rendertarget. It is less obvious why the resolve call needs to clear the special memory, but apparently there is a performance gain from doing this: I don’t pretend to understand why but I’m not going to complain as long as this keeps my Xbox running as fast as it does!
This table might help clarify exactly what happens on each platform:
|Rendertarget and texture share video memory (resolve is a no-op)||Changing rendertarget destroys existing contents||Resolve destroys existing contents|
|Windows (not multisampled)||yes||no||no|
But remember, you only need to care about these details if you are planning on breaking the rules! If you play it safe by always assuming your buffer contents will be lost when you call SetRenderTarget or Resolve, that code will work consistently on all platforms.