[XNA] Partial Updates of Textures/RenderTargets

Started by
5 comments, last by MJP 14 years, 9 months ago
Before I start out, this is more of a "What is the best way to do this?" than anything else. I have a set of proceedurally generated textures that get updated every few frames based on certain parameters. Currently I'm updating the whole texture each frame, but I want to move to only partially updating the textures (so I can increase the size without a massive overhead). I've set up my quad drawing code to deal with the partial updates, but I'm wondering what the best way to go about doing them is. Note: all code below is super simplified. When I was doing full updates I was using something like this.
Texture2D resolvedTarget;
RenderTarget2D renderTarget;

void UpdateTextures()
{
   // Setup quads for full update
   device.SetRenderTarget(0, renderTarget);
   // Draw quads to target
   device.SetRenderTarget(0, null);
   resolvedTarget = renderTarget.GetTexture();
}

void Draw()
{
   effect.Parameters["texture"].SetValue(resolvedTarget)
   // Draw texture to screen
}


This however doesn't work as soon as I perform a partial update, as the resolvedTarget then only contains the updated area. I know in MDX I could pre-fill the render target each frame with resolvedTarget.GetSurfaceLevel(0), then perform my updates on top of that, but XNA doesn't seem to have an equivalent (I'm sure it used to, but that was probably in GS 1.0). So, my current solution for partial updates is as follows. (Note: I've not had a chance to test this, as I'm away from my dev machine, but I don't see why it wouldn't work?):
RenderTarget2D resolvedTarget;
RenderTarget2D renderTarget;

void UpdateTextures()
{
   // Setup quads for partial update
   renderTarget = resolvedTarget;
   device.SetRenderTarget(0, renderTarget);
   // Draw quads to target
   device.SetRenderTarget(0, null);
   resolvedTarget = renderTarget;
}

void Draw()
{
   effect.Parameters["texture"].SetValue(resolvedTarget.GetTexture())
   // Draw texture to screen
}


This just seems... messy though, am I missing something obvious here? Is there an XNA equivalent to the GetSurfaceLevel method? Or is there a better way of partially updating a texture? (without drawing it in the background, then drawing my quads on top, which I guess would work)
Advertisement
Are you creating your RenderTarget's with RenderTargetUsage.PreserveContents? The default behavior for RenderTarget's is that they are cleared every time you set them as the current RT for the device. This is because of the way RT's work on the Xbox 360, where preserving the contents requires that RT data is manually copied back into the eDRAM. If you're not worried about the Xbox at all, you can just use PreserveContents without any performance penalty.
Perhaps I've over-simplified. I'm effectively using discard contents as I'm reusing the same render target multiple times per frame.

Something more like this.

Texture2D[] resolvedTarget = new Texture2D[num];Texture2D[] additionalResolvedTarget = new Texture2D[num];void UpdateTextures(){   RenderTarget2D renderTarget;      for (int x = 0; x < num; x++)   {      // Setup quads for full update      renderTarget = new RenderTarget2D(...);            device.SetRenderTarget(0, renderTarget);      // Draw quads to target      device.SetRenderTarget(0, null);      resolvedTarget[x] = renderTarget.GetTexture();      renderTarget.Dispose();      renderTarget = new RenderTarget2D(...);      device.SetRenderTarget(0, renderTarget);      // Draw quads to target with different shader      device.SetRenderTarget(0, null);      additionalResolvedTarget[x] = renderTarget.GetTexture();      renderTarget.Dispose();   }}


As an additional note, if possible I would like to get this to work on the Xbox 360 without crippling performance.
I don't believe you can call Dispose on a RenderTarget and still use the Texture2D you get from GetTexture. On the PC that Texture isn't a copy...it contains the actual surface that's used as the render target for the device (unless you're using multisampling, in which case it will be the texture that contains the surface to which the multisampled RT data is resolved). Also you really don't want to be creating new RenderTarget's in the middle of a frame...it's a slow process since it results in large memory allocations.

What you might want to consider is using some sort of "pool" for your RT's. Basically how it works is at runtime, you request a RenderTarget of a certain size/format from the pool. The pool checks it's internal collection of unused RT's, and if it finds one that matches it returns it and marks it as "in use". If it doesn't find one, it creates it. Then the other parts of your code can just request an RT when it needs one, and as long as it doesn't "give it back" to the pool it won't get used or overwritten by other parts of code. This way you never have to worry about how many RT's you need to create, or making sure one part of your code doesn't step on another part.
Would perhaps an even easier solution be to set it up like this? Although I suppose it is a "pool" of sorts.

RenderTarget2D[] renderTarget = new RenderTarget2D[num];RenderTarget2D[] additionalRenderTarget = new RenderTarget2D[num];// Initialise render targets with PreserveContentsvoid UpdateTextures(){      for (int x = 0; x < num; x++)   {      // Setup quads for full update            device.SetRenderTarget(0, renderTarget[x]);      // Draw quads to target      device.SetRenderTarget(0, null);      device.SetRenderTarget(0, additionalRenderTarget[x]);      // Draw quads to target with different shader      device.SetRenderTarget(0, null);   }}void Draw(){   for (int x = 0; x < num; x++)   {      effect.Parameters["texture"].SetValue(renderTarget[x].GetTexture())      // Draw texture to screen      effect.Parameters["texture"].SetValue(additionalRenderTarget[x].GetTexture())      // Draw texture to screen   }}
It's working perfectly with the above style structure, now I just have to refine my noise generation.

Thanks MJP for getting my brain thinking in the correct way to sort this out, it's not quite a pool, but it's not far off.

Now, what ways are there to improve RenderTargetUsage.PreserveContents performance on the Xbox 360? Or is 16 32bit render targets (mix of Single and COlor), each 256x256 in size not going to be a problem?
Quote:Original post by adt7
Now, what ways are there to improve RenderTargetUsage.PreserveContents performance on the Xbox 360? Or is 16 32bit render targets (mix of Single and COlor), each 256x256 in size not going to be a problem?


There's not too much you can do about it...it's all handled by the driver/runtime. All it does is copy the texture out of main memory right into eDRAM so it's really a bandwidth hit...so unless you're bandwidth bound you probably won't notice. Most people using XNA are CPU-bound on the 360, anyway.

This topic is closed to new replies.

Advertisement