XNA noob here, learning every day. I just worked out how to composite multiple textures into one using a RenderTarget2D. However, while I can use the RenderTarget2D as a Te
Replace
Texture2D mergedTexture = new Texture2D(_device, width, height);
Color[] content = new Color[width * height];
buffer.GetData<Color>(content);
mergedTexture.SetData<Color>(content);
return mergedTexture;
with
return buffer;
Because RenderTarget2D extends Texture2D you will just get Texture2D class data returned. Also in case you are interested here's a class i made for building my GUI library's widgets out of multiple textures. In case you need to be doing this sort of thing a lot.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
namespace Voodo.Utils {
/// <summary>
///
/// </summary>
public class TextureBaker {
private readonly SpriteBatch _batch;
private readonly RenderTarget2D _renderTarget;
private readonly GraphicsDevice _graphicsDevice;
/// <summary>
///
/// </summary>
public Rectangle Bounds {
get { return _renderTarget.Bounds; }
}
/// <summary>
///
/// </summary>
/// <param name="graphicsDevice"></param>
/// <param name="size"></param>
public TextureBaker(GraphicsDevice graphicsDevice, Vector2 size) {
_graphicsDevice = graphicsDevice;
_batch = new SpriteBatch(_graphicsDevice);
_renderTarget = new RenderTarget2D(
_graphicsDevice,
(int)size.X,
(int)size.Y);
_graphicsDevice.SetRenderTarget(_renderTarget);
_graphicsDevice.Clear(Color.Transparent);
_batch.Begin(
SpriteSortMode.Immediate,
BlendState.AlphaBlend,
SamplerState.LinearClamp,
DepthStencilState.Default,
RasterizerState.CullNone);
}
#region Texture2D baking
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
public void BakeTexture(Texture2D texture) {
_batch.Draw(
texture,
new Rectangle(0, 0, Bounds.Width, Bounds.Height),
Color.White);
}
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
/// <param name="destination"></param>
public void BakeTexture(Texture2D texture, Rectangle destination) {
_batch.Draw(
texture,
destination,
Color.White);
}
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
/// <param name="destination"></param>
/// <param name="source"></param>
public void BakeTexture(Texture2D texture, Rectangle destination, Rectangle source) {
_batch.Draw(
texture,
destination,
source,
Color.White);
}
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
/// <param name="sourceModification"></param>
/// <param name="destination"></param>
public void BakeTexture(Texture2D texture, System.Drawing.RotateFlipType sourceModification, Rectangle destination) {
Stream sourceBuffer = new MemoryStream();
texture.SaveAsPng(sourceBuffer, texture.Width, texture.Height);
System.Drawing.Image sourceImage = System.Drawing.Image.FromStream(sourceBuffer);
sourceBuffer = new MemoryStream();
sourceImage.RotateFlip(sourceModification);
sourceImage.Save(sourceBuffer, System.Drawing.Imaging.ImageFormat.Png);
_batch.Draw(
Texture2D.FromStream(_graphicsDevice, sourceBuffer),
destination,
Color.White);
}
/// <summary>
///
/// </summary>
/// <param name="texture"></param>
/// <param name="sourceModification"></param>
/// <param name="destination"></param>
/// <param name="source"></param>
public void BakeTexture(Texture2D texture, System.Drawing.RotateFlipType sourceModification, Rectangle destination, Rectangle source) {
Stream sourceBuffer = new MemoryStream();
texture.SaveAsPng(sourceBuffer, texture.Width, texture.Height);
System.Drawing.Image sourceImage = System.Drawing.Image.FromStream(sourceBuffer);
sourceBuffer = new MemoryStream();
sourceImage.RotateFlip(sourceModification);
sourceImage.Save(sourceBuffer, System.Drawing.Imaging.ImageFormat.Png);
_batch.Draw(
Texture2D.FromStream(_graphicsDevice, sourceBuffer),
destination,
source,
Color.White);
}
#endregion
#region SpriteFont baking
/// <summary>
///
/// </summary>
/// <param name="font"></param>
/// <param name="text"></param>
/// <param name="location"></param>
/// <param name="textColor"></param>
public void BakeText(SpriteFont font, string text, Vector2 location, Color textColor) {
_batch.DrawString(font, text, location, textColor);
}
/// <summary>
///
/// </summary>
/// <param name="font"></param>
/// <param name="text"></param>
/// <param name="location"></param>
public void BakeTextCentered(SpriteFont font, string text, Vector2 location, Color textColor) {
var shifted = new Vector2 {
X = location.X - font.MeasureString(text).X / 2,
Y = location.Y - font.MeasureString(text).Y / 2
};
_batch.DrawString(font, text, shifted, textColor);
}
#endregion
/// <summary>
///
/// </summary>
/// <returns></returns>
public Texture2D GetTexture() {
_batch.End();
_graphicsDevice.SetRenderTarget(null);
return _renderTarget;
}
}
}
if you are having problems with your rendertarget being dynamically resized when drawing it somewhere else, you could just have an off-screen rendertarget with a set size that you copy your finished RT to like this:
Rendertarget2D offscreenRT = new RenderTarget2D(_device, width, height);
_device.SetRenderTarget(offscreenRT);
_device.Clear(Color.Transparent);
SpriteBatch spriteBatch = new SpriteBatch(_device);
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied);
spriteBatch.Draw(buffer, Vector2.Zero, Color.White);
spriteBatch.End();
_device.SetRenderTarget(null);
Actually your code is not so bad if you're generating textures in a once-off process when you'd normally load content (game start, level change, room change, etc). You're transferring textures between CPU and GPU, same thing you'd be doing loading plain ol' textures. It's simple and it works!
If you're generating your textures more frequently, and it starts to become a per-frame cost, rather than a load-time cost, then you will want to worry about its performance and perhaps keeping them as render targets.
You shouldn't get ContentLost
in the middle of drawing, so you can safely just respond to that event and recreate the render targets then. Or you can check for IsContentLost
on each of them, ideally at the start of your frame before you render anything else. Either way everything should be checked before your SpriteBatch
begins.
(Normally when using render targets you're regenerating them each frame anyway, so you don't need to check them in that case.)