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。现在有点矫枉过正,但我​​可能会稍后再谈。

【讨论】:

由于byteArraycolors实际上指向的是同一个内存,所以只需要初始化其中一个即可。因此,您可以删除其中一个数组构造函数。 我也这么认为,但是数组对象不会被初始化(即,有多少 Color32?v/s 有多少字节)。结果我遇到了运行时错误。我来自 C++ 背景,所以这些东西还不太清楚:) 因未提及解决方案 (2) 的一个关键缺点而被否决:必须在提供的数据中提供所有 MIP 级别,并且不会在您 Apply() 时为您生成它们的方式如果你使用过 SetPixels()

以上是关于C# (Unity) 不同托管数组类型之间的快速复制的主要内容,如果未能解决你的问题,请参考以下文章

C#高性能低GC 非托管动态扩容数组

不同应用程序(WPF 和 Unity)中的 C# struct 序列化返回不同大小的字节数组

如何在托管 (C#) 和非托管 (C++) 代码之间来回传递数组内容

P/Invoke 编组和解组 C# 和非托管 DLL 之间的二维数组、结构和指针

c#泛型中非托管和结构约束之间的区别

Unity——C#数组