有没有从XNA中的Bitmap对象创建Texture2D的快速替代方法?

我已经环顾了很多,我发现从Bitmap创建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(rgbValues, 0, rgbValues.Length, SetDataOptions.NoOverwrite); 

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

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

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

任何帮助将非常感谢。

谢谢

你想要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; } 

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

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

我发现在使用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(bytes); // unlock the bitmap data bmp.UnlockBits(bmd); return t2d; 

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

作为替代方案,考虑使用您根据需要分配的Texture2D池,在不再需要时返回池。

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

[我遗漏了重要的细节,例如需要将纹理与特定设备相关联 – 但您可以确定从参数到您调用的方法的任何需求。 我要说的是最小化对Texture2D构造函数的调用,特别是如果你有很多大的纹理。

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

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