使用RenderTarget2D从Stream加载Texture2D,游戏窗口最小化后纹理消失 [英] Load Texture2D from Stream with RenderTarget2D, Textures disapear after game window minimizing

查看:36
本文介绍了使用RenderTarget2D从Stream加载Texture2D,游戏窗口最小化后纹理消失的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在从目录加载纹理时遇到了一些问题.也许先写代码:

I hava some problem with loading textures from directory. Maybe code first:

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

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

First, texture is loading from stream. Then, I change some blending options to reach behaviour such as in textures loaded by ContentPipeline. Unfortunately, textures obtained in this way, disapear after game window minimizing. I read something about this problem and a lot of things indicates that RenderTarget2D is fault because render target is set to null after all. What should I do to keep permanently my textures?

好的,我使用了第四个选项,效果很好.这是固定代码:

OK, I used 4th option and it's work perfectly. Here's fixed code:

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

非常感谢您的帮助!

推荐答案

这个开始让我发疯.我已经多次回答这个问题.所以我会努力让这个成为明确的.

This one is starting to drive me insane. I've answered this issue so many times. So I will try and make this one definitive.

这个问题不断出现,因为 - 首先,XNA 的文档很差 - 还因为人们不断在教程和论坛中发布这样的代码,并坚持认为它没问题",因为它似乎...直到您最小化窗口并且您的所有纹理都丢失了!另外还有一种误解,即通过在 GPU 上进行工作,它必须更快(可能不是).

This question keeps coming back because - first of all XNA is poorly documented - but also because people keep posting code like this in tutorials and forums and insisting that it's "ok" because it seems to work... until you minimise the window and all your textures go missing! Plus there's the misconception that - by doing the work on the GPU - it must be faster (it might not be).

C#/CPU层,一个RenderTarget2D一个Texture2D.方法末尾的 as Texture2D 完全没有任何作用.您制作的演员表可能是隐式的.强制转换不会对引用的对象实例进行任何更改.您可以将其转换回 RenderTarget2D 并且同样不会更改对象本身.

At the C#/CPU layer, a RenderTarget2D is a Texture2D. The as Texture2D at the end of your method there does precisely nothing. The cast you make could be implicit. The cast makes no changes to the referenced object instance. You could cast it back to a RenderTarget2D and, again, it wouldn't change the object itself.

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

The reason that RenderTarget2D inherits from Texture2D is so that you can pass a render target to any method that expects a texture and have it work correctly. But their underlying functionality has some important differences:

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

At the Direct3D/GPU layer, what is happening is that you are receiving a "Device Lost" error, due to the fact that the device context you were using went away (due to the window being minimised - but that is not the only thing that can cause it). This means that you lose all the GPU memory that you were using - including textures and render targets.

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

A regular Texture2D (that you load with ContentManager.Load or Texture2D.FromStream, or set up with SetData) maintains a CPU-side copy of the data. So when the device is lost, XNA will automatically re-create the contents of that texture from the CPU-side copy.

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

But a RenderTarget2D is kept entirely on the GPU. XNA has no way to recreate it if it goes missing. Getting a CPU-side copy of its contents would require an extremely expensive copy back from the GPU whenever you changed it.

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

  • Option 1 is to always re-render the contents of the render target at the start of each frame. This is the standard way to use render targets, as you usually have their contents change each frame anyway. Not really applicable to your case.

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

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

Option 3 is to create a CPU-side copy of the texture. Basically get the data from the render target with GetData. Then create a new Texture2D and set the data onto it with SetData. XNA will then handle the any device loss for you (as described above).

选项 4 是根本不使用渲染目标!使用 GetData 获取纹理数据,在软件中执行转换,然后使用 SetData 将其重新设置.看到纹理数据无论如何都会被复制 - 为什么不自己复制并同时对其进行预乘?

Option 4 is to not use a render target at all! Use GetData to get your texture data, perform your transformation in software, and then set it back with SetData. Seeing as texture data is getting copied around anyway - why not do a copy yourself and premultiply it at the same time?

选项 5 是将 FromStream 替换为加载时预乘的内容.这类似于选项 4,但可以为您节省一些副本.可能矫枉过正.

Option 5 is to replace FromStream with something that premultiplies as it loads. This is like option 4, but saves you a few copies. Probably overkill.

选项 6 是首先以预乘格式存储您的纹理.尽管此时您还不如使用内容管道.

Option 6 is to store your textures in a pre-multiplied format in the first place. Although at this point you may as well be using the content pipeline.

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

Personally I would probably choose option 4 for your situation.

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

Finally: Don't forget to call Dispose on any resources (textures, render targets, etc) that you created yourself (with new or FromStream, but not from ContentManager) that you have finished using.

我注意到,在您的代码中,您正在泄漏 Texture2D 文件.

I notice that, in your code, you're leaking Texture2D file.

为了避免一些疯狂,我将在我的答案中添加一个简单的、未经测试的方法,该方法完全在 CPU 上预乘纹理:

To save some insanity, I'll add to my answer a simple, untested method that premultiplies a texture entirely on the 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);
}

这篇关于使用RenderTarget2D从Stream加载Texture2D,游戏窗口最小化后纹理消失的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆