【问题标题】:C# (Unity) Quick copy between different managed array typesC# (Unity) 不同托管数组类型之间的快速复制
【发布时间】: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# 中的联合以绕过副本。

【问题讨论】:

  • 我在上面的描述中明确提到了这个问题。该链接提供了一种可能的解决方案(在我的情况下不起作用),更重要的是,这是一个关于是否可以避免 2 个副本的问题。
  • 为什么不能使用 Texture2D 对象的 LoadRawTextureData()?文档说在更改纹理后调用 Apply() 以便实际将更改上传到显卡。
  • 我怎么会错过这个功能? :( 完美,谢谢。我的联合解决方案也有效,但这更容易。有趣的部分是 Texture2D.SetPixels 需要时间,我将使用本机插件解决方案将图像数据blit到 gfx 纹理资源上。我怀疑它使用UpdateSubresource 并且每帧都这样做会杀死一点性能。

标签: c# unity3d marshalling arrays


【解决方案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()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-31
  • 2020-03-12
  • 2013-11-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多