После записи в RenderTarget, как эффективно клонировать вывод? - PullRequest
9 голосов
/ 01 апреля 2011

XNA нуб, учусь каждый день.Я только что разработал, как объединить несколько текстур в одну, используя RenderTarget2D.Тем не менее, хотя я могу использовать RenderTarget2D в качестве Texture2D для большинства целей, есть критическое различие: эти визуализированные текстуры теряются при изменении размера буфера (и, без сомнения, при других обстоятельствах, например, на графическом устройстве с нехваткой памяти).

На данный момент я просто копирую готовый RenderTarget2D в новый энергонезависимый объект Texture2D.Мой код, чтобы сделать это довольно глупо, хотя.Есть ли более изящный способ сделать это?Может быть, я просто устал, но не могу найти ответ в Google или SO.

Немного упрощено:

public static Texture2D  MergeTextures(int width, int height, IEnumerable<Tuple<Texture2D, Color>> 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<Texture2D, Color> 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<Color>(content);
    mergedTexture.SetData<Color>(content);
    return mergedTexture;
    }

Полагаю, мне следует проверить IsContentLostи выполнить рендеринг по мере необходимости, но это происходит в середине моего основного цикла рисования, и, конечно, вы не можете вкладывать SpriteBatches.Я мог бы поддерживать список «render TODO», обрабатывать их после окончания основного SpriteBatch, и тогда они будут доступны для следующего кадра.Является ли это предпочтительной стратегией?

Этот код вызывается только несколько раз, поэтому производительность не имеет значения, но я хотел бы узнать, как все делать правильно.

Ответы [ 3 ]

4 голосов
/ 01 апреля 2011

На самом деле ваш код не так уж плох, если вы генерируете текстуры в однократном процессе, когда вы обычно загружаете контент (начало игры, изменение уровня, изменение комнаты и т. Д.).Вы передаете текстуры между процессором и графическим процессором, то же самое вы делаете, загружая обычные старые текстуры.Это просто и работает!

Если вы генерируете свои текстуры чаще, и это становится стоимостью за кадр, а не временем загрузки, тогда вам захочется беспокоиться о его производительности.и, возможно, сохраняя их в качестве целей рендеринга.

Вы не должны получать ContentLost в середине чертежа, так что вы можете спокойно просто отреагировать на это событие и воссоздать цели рендеринга.Или вы можете проверить IsContentLost на каждом из них, в идеале в начале вашего кадра, прежде чем рендерить что-либо еще.В любом случае все должно быть проверено до начала SpriteBatch.

(Обычно при использовании целей рендеринга вы регенерируете их каждый кадр в любом случае, поэтому вам не нужно проверять их в этом случае.)

2 голосов
/ 01 мая 2011

Замените

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

на

return buffer;

Поскольку RenderTarget2D расширяет Texture2D, вы просто получите возвращенные данные класса Texture2D.Также, если вам интересно, вот класс, который я сделал для создания виджетов моей библиотеки GUI из нескольких текстур.На случай, если вам нужно много заниматься этим.

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;
        }
    }
}
0 голосов
/ 01 апреля 2011

если у вас возникают проблемы с динамическим изменением размера цели рендеринга при ее рисовании где-то еще, вы можете просто создать цель рендеринга за пределами экрана с заданным размером, в которую вы скопируете законченный 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);
...