4
\$\begingroup\$

I see that some XNA resources implement ICloneable (namely the Effect class) but textures don't. I have encountered a situation where I need to make a complete copy of a RenderTarget2D texture and use it as inputs for two separate rendering effects. One effect will change the contents of the RenderTarget2D so I can't pass it to the other effect, which is why I need two copies.

Can you "clone" or deep copy a RenderTarget2D in an easy way? My current work around is that I wrote a copy function that sets a new render target, uses a simple effect to just re-draw the contents of the target to be copied to into a quad and return the new render target. But seems like a lot of work around simply to create a copy.

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I guess textures don't implement ICloneable for a good reason: it would be too easy to accidentally cause a FPS sink. One might think, XNA stores texture resources in the managed memory pool, so when someRt.GetData() gets called, texture data seems to be already there for locking, which should be fast, right? Not really, since getting actual data requires the driver to flush all commands in queue, wait for GPU to process them, and then send fresh data back for reading.

Chances are, if you're going to do your thing on a per-frame basis, it will be better to just render twice. But if you want to clone a texture once in while, a simple way of deep cloning is to walk through mipmap levels and clone them one by one:

static RenderTarget2D CloneRenderTarget(RenderTarget2D target)
{
    var clone = new RenderTarget2D(target.GraphicsDevice, target.Width,
        target.Height, target.LevelCount > 1, target.Format,
        target.DepthStencilFormat, target.MultiSampleCount,
        target.RenderTargetUsage);

    for (int i = 0; i < target.LevelCount; i++)
    {
        double rawMipWidth = target.Width / Math.Pow(2, i);
        double rawMipHeight = target.Height / Math.Pow(2, i);

        // make sure that mipmap dimensions are always > 0.
        int mipWidth = (rawMipWidth < 1) ? 1 : (int)rawMipWidth;
        int mipHeight = (rawMipHeight < 1) ? 1 : (int)rawMipHeight;

        var mipData = new Color[mipWidth * mipHeight];
        target.GetData(i, null, mipData, 0, mipData.Length);
        clone.SetData(i, null, mipData, 0, mipData.Length);
    }

    return clone;
}
\$\endgroup\$
2
  • \$\begingroup\$ It sounds like the OP wants this to be part of the frame rendering process, so the GPU should be used to do the copies rather than GetData / SetData, which (as you mentioned) requires stalling the GPU to read the data back into main memory. \$\endgroup\$ Commented Sep 30, 2011 at 21:03
  • \$\begingroup\$ Rendering twice may be my only option then, if GetData is the only way to directly copy a texture. I would only need the full-sized texture and getting mipmap data is completely optional for my case. \$\endgroup\$
    – ChrisC
    Commented Sep 30, 2011 at 21:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .