12

我环顾四周,发现从位图创建 Texture2D 的唯一方法是:

using  (MemoryStream s = new  MemoryStream())
{
   bmp.Save(s, System.Drawing.Imaging.ImageFormat.Png);
   s.Seek(0, SeekOrigin.Begin);
   Texture2D tx = Texture2D.FromFile(device, s);
}

Texture2D tx = new Texture2D(device, bmp.Width, bmp.Height,
                        0, TextureUsage.None, SurfaceFormat.Color);
tx.SetData<byte>(rgbValues, 0, rgbValues.Length, SetDataOptions.NoOverwrite);

其中 rgbValues 是一个字节数组,其中包含 32 位 ARGB 格式的位图像素数据。

我的问题是,有没有更快的方法可以尝试?

我正在编写一个地图编辑器,它必须读取自定义格式的图像(地图图块)并将它们转换为 Texture2D 纹理以显示。以前版本的编辑器是一个 C++ 实现,它首先将图像转换为位图,然后再转换为要使用 DirectX 绘制的纹理。我在这里尝试了相同的方法,但是上述两种方法都太慢了。在合理规格的计算机上,将地图所需的所有纹理加载到内存中,第一种方法大约需要 250 秒,第二种方法大约需要 110 秒(相比之下,C++ 代码大约需要 5 秒)。如果有直接编辑纹理数据的方法(例如使用Bitmap类'

任何帮助将不胜感激。

谢谢

4

4 回答 4

10

他们在 XNA 4.0 中将格式从 bgra 更改为 rgba,因此该方法给出了奇怪的颜色,需要切换红色和蓝色通道。这是我写的一个超级快的方法!(在大约 3 秒内加载 1500x 256x256 像素纹理)。

    private Texture2D GetTexture(GraphicsDevice dev, System.Drawing.Bitmap bmp)
    {
        int[] imgData = new int[bmp.Width * bmp.Height];
        Texture2D texture = new Texture2D(dev, bmp.Width, bmp.Height);

        unsafe
        {
            // lock bitmap
            System.Drawing.Imaging.BitmapData origdata = 
                bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);

            uint* byteData = (uint*)origdata.Scan0;

            // Switch bgra -> rgba
            for (int i = 0; i < imgData.Length; i++)
            {
                byteData[i] = (byteData[i] & 0x000000ff) << 16 | (byteData[i] & 0x0000FF00) | (byteData[i] & 0x00FF0000) >> 16 | (byteData[i] & 0xFF000000);                        
            }                

            // copy data
            System.Runtime.InteropServices.Marshal.Copy(origdata.Scan0, imgData, 0, bmp.Width * bmp.Height);

            byteData = null;

            // unlock bitmap
            bmp.UnlockBits(origdata);
        }

        texture.SetData(imgData);

        return texture;
    }
于 2011-09-12T21:18:07.960 回答
10

你想要LockBits吗?你会得到 LockBits。

在我的实现中,我从调用者传入了 GraphicsDevice,因此我可以使这个方法成为通用和静态的。

public static Texture2D GetTexture2DFromBitmap(GraphicsDevice device, Bitmap bitmap)
{
    Texture2D tex = new Texture2D(device, bitmap.Width, bitmap.Height, 1, TextureUsage.None, SurfaceFormat.Color);

    BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);

    int bufferSize = data.Height * data.Stride;

    //create data buffer 
    byte[] bytes = new byte[bufferSize];    

    // copy bitmap data into buffer
    Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);

    // copy our buffer to the texture
    tex.SetData(bytes);

    // unlock the bitmap data
    bitmap.UnlockBits(data);

    return tex;
}
于 2010-05-20T00:52:43.583 回答
3

我发现在使用 LockBits 时,我必须将 PixelFormat 指定为 .Format32bppArgb,因为您建议使用它来获取网络摄像头图像。

        BitmapData bmd = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
            System.Drawing.Imaging.ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        int bufferSize = bmd.Height * bmd.Stride;
        //create data buffer 
        byte[] bytes = new byte[bufferSize];
        // copy bitmap data into buffer
        Marshal.Copy(bmd.Scan0, bytes, 0, bytes.Length);

        // copy our buffer to the texture
        Texture2D t2d = new Texture2D(_graphics.GraphicsDevice, bmp.Width, bmp.Height, 1, TextureUsage.None, SurfaceFormat.Color);
        t2d.SetData<byte>(bytes);
        // unlock the bitmap data
        bmp.UnlockBits(bmd);
        return t2d;
于 2010-05-20T07:41:42.950 回答
1

当我第一次阅读这个问题时,我认为SetData性能是极限。然而,阅读 OP 在最佳答案中的评论,他似乎分配了很多Texture2D。

作为替代方案,考虑拥有一个您根据需要分配的 Texture2D 池,在不再需要时返回池中。

第一次需要每个纹理文件时(或在进程开始时的“预加载”中,取决于您想要延迟的位置),将每个文件加载到byte[]数组中。(将这些byte[]数组存储在LRU 缓存中- 除非您确定有足够的内存来始终保持它们。)然后,当您需要其中一个纹理时,抓取其中一个池纹理,(分配一个新的,如果没有合适的大小可用),来自字节数组的 SetData - 中提琴,你有一个纹理。

[我省略了重要的细节,例如需要将纹理与特定设备关联 - 但您可以确定从参数到您正在调用的方法的任何需求。我要说的一点是尽量减少对 Texture2D 构造函数的调用,尤其是在你有很多大纹理的情况下。]

如果您真的很喜欢,并且正在处理许多不同大小的纹理,您还可以将LRU 缓存原则应用于池。具体来说,跟踪池中“空闲”对象的总字节数。如果该总数超过您设置的某个阈值(可能与“空闲”对象的总数相结合),那么在下一次请求时,丢弃最旧的空闲池项目(大小错误或其他错误参数),以保持低于您的允许“浪费”缓存空间的阈值。

顺便说一句,您可以简单地跟踪阈值,并在超过阈值时丢弃所有空闲对象。缺点是下次分配一堆新纹理时会出现短暂的打嗝——如果你有关于应该保留什么尺寸的信息,你可以改善这种情况。如果这还不够好,那么您需要LRU

于 2018-03-03T19:02:52.727 回答