C# (Unity) 不同托管数组类型之间的快速复制
Posted
技术标签:
【中文标题】C# (Unity) 不同托管数组类型之间的快速复制【英文标题】:C# (Unity) Quick copy between different managed array types 【发布时间】:2016-04-05 23:15:32 【问题描述】:我有一个byte[]
的颜色,需要通过SetPixels(Color32[])
转移到Texture2D
对象。
我想知道从byte[]
创建Color32[]
的最快方法是什么。
由于每个Color32
是 4 个字节(每个通道 1 个字节),因此元素数量将是四分之一。
Array.CopyTo
不允许不同的对象数组类型。
根据我的阅读,托管代码似乎无法将 byte[]
重新解释为 Color32[]
并完全跳过副本。
与此相反 (Color32[]
-> byte[]
) has a SO solution 通过编组,但似乎涉及大量复制。
private static byte[] Color32ArrayToByteArray(Color32[] colors)
int length = 4 * colors.Length;
byte[] bytes = new byte[length];
IntPtr ptr = Marshal.AllocHGlobal(length); // unmanaged memory alloc
Marshal.StructureToPtr(colors, ptr, true); // memcpy 1
Marshal.Copy(ptr, bytes, 0, length); // memcpy 2
Marshal.FreeHGlobal(ptr);
return bytes;
就我而言,我正在使用:
int length = bytes.Length;
IntPtr ptr = Marshal.AllocHGlobal(length); // unmanaged mem alloc
Marshal.Copy(bytes, 0, ptr, length); // memcpy 1
Marshal.PtrToStructure(ptr, colors); // memcpy 2
Marshal.FreeHGlobal(ptr);
但是,在执行myTexture.SetPixels(colors)
之后,我没有看到纹理更新。仍在试图找出原因。
无论如何,我是否必须做 2 个内存副本才能在 C# 中完成此操作?好像很浪费。。 我正在查看 C# 中的联合以绕过副本。
【问题讨论】:
Fast copy of Color32[] array to byte[] array的可能重复 我在上面的描述中明确提到了这个问题。该链接提供了一种可能的解决方案(在我的情况下不起作用),更重要的是,这是一个关于是否可以避免 2 个副本的问题。 为什么不能使用 Texture2D 对象的 LoadRawTextureData()?文档说在更改纹理后调用 Apply() 以便实际将更改上传到显卡。 我怎么会错过这个功能? :( 完美,谢谢。我的联合解决方案也有效,但这更容易。有趣的部分是 Texture2D.SetPixels 需要时间,我将使用本机插件解决方案将图像数据blit到 gfx 纹理资源上。我怀疑它使用UpdateSubresource
并且每帧都这样做会杀死一点性能。
【参考方案1】:
这是我学到的:
1) 无需编组黑白托管和非托管内存即可将 byte[] 转换为 Color32[],反之亦然。相反,在 C# 中使用联合:
[StructLayout(LayoutKind.Explicit)]
public struct Color32Bytes
[FieldOffset(0)]
public byte[] byteArray;
[FieldOffset(0)]
public Color32[] colors;
Color32Bytes data = new Color32Bytes();
data.byteArray = new byte[width * height * bytesPerPixel];
data.colors = new Color32[width * height];
现在,如果您使用来自相机的数据更新 byteArray,您可以使用 myTexture.SetPixels(data.colors)
将其应用于 Texture2D
;
不涉及复制。
2) 不需要上面的联合解决方案。哈哈。
正如NineBerry 指出的那样,您可以通过将原始字节缓冲区上传到纹理
myTexture.LoadRawTextureData(bytes);
这对我来说是完美的,因为我的相机字节缓冲区来自 BGRA(从低到高),我需要在上传之前调整通道(昂贵)或者如果我使用 (1) 在我的着色器中解决它
3) Unity 中的SetPixels
方法在低端系统上可能会很昂贵。我没有深入挖掘,但我怀疑这是因为在引擎盖下创建的 D3D 纹理使用D3D_USAGE_DEFAULT,这意味着更新它们必须使用UpdateSubresource,这会导致驱动程序副本和可能的停顿(目前无法更新资源由 GPU 使用)。
因此,理想的解决方案是编写一个原生插件,使用 D3D_USAGE_DYNAMIC 创建一个 2D 纹理,映射-更新-取消映射,然后将该引用传递回 Unity。现在有点矫枉过正,但我可能会稍后再谈。
【讨论】:
由于byteArray
和colors
实际上指向的是同一个内存,所以只需要初始化其中一个即可。因此,您可以删除其中一个数组构造函数。
我也这么认为,但是数组对象不会被初始化(即,有多少 Color32?v/s 有多少字节)。结果我遇到了运行时错误。我来自 C++ 背景,所以这些东西还不太清楚:)
因未提及解决方案 (2) 的一个关键缺点而被否决:必须在提供的数据中提供所有 MIP 级别,并且不会在您 Apply() 时为您生成它们的方式如果你使用过 SetPixels()以上是关于C# (Unity) 不同托管数组类型之间的快速复制的主要内容,如果未能解决你的问题,请参考以下文章
不同应用程序(WPF 和 Unity)中的 C# struct 序列化返回不同大小的字节数组
如何在托管 (C#) 和非托管 (C++) 代码之间来回传递数组内容