写入RenderTarget后,如何有效地克隆输出?

XNA noob在这里,每天都在学习。 我刚刚研究了如何使用RenderTarget2D将多个纹理合成为一个。 然而,虽然我可以将RenderTarget2D用作Texture2D用于大多数目的,但是存在一个重要的区别:当后缓冲区resize时这些渲染的纹理会丢失(毫无疑问,在其他情况下,如图形设备内存不足)。

目前,我只是将完成的RenderTarget2D复制到一个新的非易失性Texture2D对象中。 不过,我这样做的代码非常难看。 有没有更优雅的方式来做到这一点? 也许我只是累了,但我无法在Google或SO上找到答案。

略有简化:

public static Texture2D MergeTextures(int width, int height, IEnumerable<Tuple> textures) { RenderTarget2D buffer = new RenderTarget2D(_device, width, height); _device.SetRenderTarget(buffer); _device.Clear(Color.Transparent); SpriteBatch spriteBatch = new SpriteBatch(_device); spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied); // Paint each texture over the one before, in the appropriate color Rectangle rectangle = new Rectangle(0, 0, width, height); foreach (Tuple texture in textures) spriteBatch.Draw(texture.Item1, rectangle, texture.Item2); spriteBatch.End(); _device.SetRenderTarget((RenderTarget2D)null); // Write the merged texture to a Texture2D, so we don't lose it when resizing the back buffer // This is POWERFUL ugly code, and probably terribly, terribly slow Texture2D mergedTexture = new Texture2D(_device, width, height); Color[] content = new Color[width * height]; buffer.GetData(content); mergedTexture.SetData(content); return mergedTexture; } 

我想我应该检查IsContentLost并根据需要重新渲染,但这发生在我的主绘图循环中间,当然你不能嵌套SpriteBatches。 我可以维护一个“渲染TODO”列表,在主SpriteBatch结束后处理那些,然后它们可用于下一帧。 这是首选策略吗?

这个代码只被调用了几次,因此性能不是一个问题,但我想学习如何做正确的事情。

实际上,如果您在正常加载内容(游戏开始,关卡更改,房间更改等)时在一次性过程中生成纹理,那么您的代码就不那么糟糕了。 你在CPU和GPU之间传输纹理,你正在加载普通的纹理。 它很简单,它的工作原理!

如果您更频繁地生成纹理,并且它开始变成每帧成本而不是加载时间成本,那么您将需要担心其性能并可能将它们保留为渲染目标。

您不应该在绘图过程中获取ContentLost ,因此您可以安全地响应该事件并重新创建渲染目标。 或者你可以在每个上面检查IsContentLost ,理想情况是在渲染其他任何内容之前在帧的开头。 无论哪种方式,都应该在SpriteBatch开始之前检查所有内容。

(通常在使用渲染目标时,无论如何都会为每个帧重新生成它们,因此在这种情况下您无需检查它们。)

更换

 Texture2D mergedTexture = new Texture2D(_device, width, height); Color[] content = new Color[width * height]; buffer.GetData(content); mergedTexture.SetData(content); return mergedTexture; 

 return buffer; 

因为RenderTarget2D扩展了Texture2D,所以只返回返回的Texture2D类数据。 如果你感兴趣的话,这是我用来构建多个纹理的GUI库的小部件的类。 如果你需要做很多这样的事情。

 using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.IO; namespace Voodo.Utils { ///  /// ///  public class TextureBaker { private readonly SpriteBatch _batch; private readonly RenderTarget2D _renderTarget; private readonly GraphicsDevice _graphicsDevice; ///  /// ///  public Rectangle Bounds { get { return _renderTarget.Bounds; } } ///  /// ///  ///  ///  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 ///  /// ///  ///  public void BakeTexture(Texture2D texture) { _batch.Draw( texture, new Rectangle(0, 0, Bounds.Width, Bounds.Height), Color.White); } ///  /// ///  ///  ///  public void BakeTexture(Texture2D texture, Rectangle destination) { _batch.Draw( texture, destination, Color.White); } ///  /// ///  ///  ///  ///  public void BakeTexture(Texture2D texture, Rectangle destination, Rectangle source) { _batch.Draw( texture, destination, source, Color.White); } ///  /// ///  ///  ///  ///  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); } ///  /// ///  ///  ///  ///  ///  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 ///  /// ///  ///  ///  ///  ///  public void BakeText(SpriteFont font, string text, Vector2 location, Color textColor) { _batch.DrawString(font, text, location, textColor); } ///  /// ///  ///  ///  ///  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 ///  /// ///  ///  public Texture2D GetTexture() { _batch.End(); _graphicsDevice.SetRenderTarget(null); return _renderTarget; } } } 

如果在将其绘制到其他地方时动态调整了rendertarget的问题,你可以使用一个屏幕外的rendertarget,其设置大小可以将完成的RT复制到这样:

 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);