使用RenderTarget2D从Stream加载Texture2D,在游戏窗口最小化后纹理消失

kem*_*t90 1 c# xna-4.0

我从目录加载纹理有一些问题.也许代码优先:

private Texture2D LoadTextureStream(string filePath)
{
    Texture2D file = null;
    RenderTarget2D result = null;

    try
    {
        using (System.IO.Stream titleStream = TitleContainer.OpenStream(filePath))
        {
            file = Texture2D.FromStream(GraphicsDevice, titleStream);
        }
    }
    catch
    {
        throw new System.IO.FileLoadException("Cannot load '" + filePath + "' file!");
    }
    PresentationParameters pp = GraphicsDevice.PresentationParameters;
    //Setup a render target to hold our final texture which will have premulitplied alpha values
    result = new RenderTarget2D(GraphicsDevice, file.Width, file.Height, true, pp.BackBufferFormat, pp.DepthStencilFormat);

    GraphicsDevice.SetRenderTarget(result);
    GraphicsDevice.Clear(Color.Black);

    //Multiply each color by the source alpha, and write in just the color values into the final texture
    BlendState blendColor = new BlendState();
    blendColor.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;

    blendColor.AlphaDestinationBlend = Blend.Zero;
    blendColor.ColorDestinationBlend = Blend.Zero;

    blendColor.AlphaSourceBlend = Blend.SourceAlpha;
    blendColor.ColorSourceBlend = Blend.SourceAlpha;

    SpriteBatch spriteBatch = new SpriteBatch(GraphicsDevice);
    spriteBatch.Begin(SpriteSortMode.Immediate, blendColor);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Now copy over the alpha values from the PNG source texture to the final one, without multiplying them
    BlendState blendAlpha = new BlendState();
    blendAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;

    blendAlpha.AlphaDestinationBlend = Blend.Zero;
    blendAlpha.ColorDestinationBlend = Blend.Zero;

    blendAlpha.AlphaSourceBlend = Blend.One;
    blendAlpha.ColorSourceBlend = Blend.One;

    spriteBatch.Begin(SpriteSortMode.Immediate, blendAlpha);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Release the GPU back to drawing to the screen
    GraphicsDevice.SetRenderTarget(null);

    return result as Texture2D;
}
Run Code Online (Sandbox Code Playgroud)

首先,纹理从流加载.然后,我更改了一些混合选项以达到行为,例如ContentPipeline加载的纹理.不幸的是,以这种方式获得的纹理,在游戏窗口最小化后消失.我读了一些关于这个问题的东西,很多东西都表明RenderTarget2D是错误的,因为渲染目标毕竟设置为null.我应该怎么做以永久保持我的纹理?

编辑 - 固定代码

好的,我使用了第四个选项,它的工作非常完美.这是固定的代码:

private Texture2D LoadTextureStream(string filePath)
{
    Texture2D file = null;
    Texture2D resultTexture;
    RenderTarget2D result = null;

    try
    {
        using (System.IO.Stream titleStream = TitleContainer.OpenStream(filePath))
        {
            file = Texture2D.FromStream(GraphicsDevice, titleStream);
        }
    }
    catch
    {
        throw new System.IO.FileLoadException("Cannot load '" + filePath + "' file!");
    }
    PresentationParameters pp = GraphicsDevice.PresentationParameters;
    //Setup a render target to hold our final texture which will have premulitplied alpha values
    result = new RenderTarget2D(GraphicsDevice, file.Width, file.Height, true, pp.BackBufferFormat, pp.DepthStencilFormat);

    GraphicsDevice.SetRenderTarget(result);
    GraphicsDevice.Clear(Color.Black);

    //Multiply each color by the source alpha, and write in just the color values into the final texture
    BlendState blendColor = new BlendState();
    blendColor.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;

    blendColor.AlphaDestinationBlend = Blend.Zero;
    blendColor.ColorDestinationBlend = Blend.Zero;

    blendColor.AlphaSourceBlend = Blend.SourceAlpha;
    blendColor.ColorSourceBlend = Blend.SourceAlpha;

    SpriteBatch spriteBatch = new SpriteBatch(GraphicsDevice);
    spriteBatch.Begin(SpriteSortMode.Immediate, blendColor);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Now copy over the alpha values from the PNG source texture to the final one, without multiplying them
    BlendState blendAlpha = new BlendState();
    blendAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;

    blendAlpha.AlphaDestinationBlend = Blend.Zero;
    blendAlpha.ColorDestinationBlend = Blend.Zero;

    blendAlpha.AlphaSourceBlend = Blend.One;
    blendAlpha.ColorSourceBlend = Blend.One;

    spriteBatch.Begin(SpriteSortMode.Immediate, blendAlpha);
    spriteBatch.Draw(file, file.Bounds, Color.White);
    spriteBatch.End();

    //Release the GPU back to drawing to the screen
    GraphicsDevice.SetRenderTarget(null);

    resultTexture = new Texture2D(GraphicsDevice, result.Width, result.Height);
    Color[] data = new Color[result.Height * result.Width];
    Color[] textureColor = new Color[result.Height * result.Width];

    result.GetData<Color>(textureColor);

    for (int i = 0; i < result.Height; i++)
    {
        for (int j = 0; j < result.Width; j++)
        {
            data[j + i * result.Width] = textureColor[j + i * result.Width];
        }
    }

    resultTexture.SetData(data);

    return resultTexture;
}
Run Code Online (Sandbox Code Playgroud)

非常感谢您的帮助!

And*_*ell 6

这个开始让我疯了.我已经回答了这个问题,所以很多次.所以我会努力使这一个明确.

这个问题不断回归,因为 - 首先XNA记录很少 - 但也因为人们不断在教程和论坛中发布这样的代码,并坚持认为它"好"因为它似乎有效...直到你最小化窗口和所有你的纹理丢失了!另外还有一种误解 - 通过在GPU上完成工作 - 它必须更快(可能不是).


这是发生了什么:

C#/ CPU层,a RenderTarget2D a Texture2D.在as Texture2D你的方法到底有没有做什么都不准确.你做的演员可能是隐含的.强制转换不会对引用的对象实例进行任何更改.您可以将其强制转换为a RenderTarget2D,同样,它不会更改对象本身.

RenderTarget2D继承的原因Texture2D是您可以将渲染目标传递给任何需要纹理并使其正常工作的方法.但它们的底层功能有一些重要的区别:

Direct3D/GPU层,正在发生的是您收到"设备丢失"错误,因为您使用的设备上下文消失了(由于窗口被最小化 - 但这不是唯一的事情这可能导致它).这意味着您将丢失正在使用的所有GPU内存 - 包括纹理和渲染目标.

常规Texture2D(使用ContentManager.Load或加载Texture2D.FromStream或设置SetData)维护数据的CPU端副本.因此,当设备丢失时,XNA将自动从CPU端副本重新创建该纹理的内容.

但是RenderTarget2D完全保留在GPU上.如果丢失,XNA无法重新创建它.获取其内容的CPU端副本需要在更改时从GPU返回极其昂贵的副本.


以下是您修复它的方法:

  • 选项1总是在每个帧的开始重新呈现渲染目标的内容.这是使用渲染目标的标准方法,因为您通常会让内容更改每个帧.不适用于您的情况.

  • 选项2RenderTarget2D.ContentLost通过重新创建渲染目标的内容来响应事件.(或者:IsContentLost每帧检查标志.)

  • 选项3是创建纹理的CPU端副本.基本上从渲染目标中获取数据GetData.然后创建一个新的Texture2D并将数据设置到其上SetData.然后,XNA将为您处理任何设备损失(如上所述).

  • 选项4是根本不使用渲染目标!使用GetData让您的纹理数据,在软件执行转换,然后将其放回SetData.看到纹理数据无论如何都会被复制 - 为什么不自己复制并同时预先复制呢?

  • 选项5是替换FromStream为加载时预乘的东西.这与选项4类似,但可以节省一些副本.可能是矫枉过正.

  • 选项6是首先以预乘格式存储纹理.虽然此时你也可以使用内容管道.

就个人而言,我可能会根据您的情况选择选项4.


最后:不要忘记调用Dispose的任何资源(纹理,渲染目标等),您自己创建的(有newFromStream,但不是ContentManager)你已经使用完.

我注意到,在你的代码中,你正在泄漏Texture2D file.


为了节省一些精神错乱,我将在我的答案中添加一个简单的,未经测试的方法,它完全在CPU上预处理纹理:

public static void PremultiplyTexture(Texture2D texture)
{
    Color[] buffer = new Color[texture.Width * texture.Height];
    texture.GetData(buffer);
    for(int i = 0; i < buffer.Length; i++)
    {
        buffer[i] = Color.FromNonPremultiplied(
                buffer[i].R, buffer[i].G, buffer[i].B, buffer[i].A);
    }
    texture.SetData(buffer);
}
Run Code Online (Sandbox Code Playgroud)