Sign in to follow this  

[XNA] Partial Updates of Textures/RenderTargets

This topic is 3097 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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)

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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 PreserveContents

void 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
}
}

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

This topic is 3097 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this