3

I have a byte[] of colors that I need to transfer to Texture2D object via SetPixels(Color32[]). I want to know what the fastest way to create the Color32[] from the byte[] is. Since each Color32 is 4 bytes (1 byte per channel), there would be one quarter the number of elements.

Array.CopyTo doesn't allow different object array types.

From what I read, it seems like managed code can't reinterpret the byte[] as Color32[] and skip the copy altogether.

The reverse of this (Color32[] -> byte[]) has a SO solution via marshalling, but there seems to be a ton of copying involved.

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

For my case, I'm using:

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

However, I don't see the texture updating after doing myTexture.SetPixels(colors). Still trying to figure out why.

Regardless, do I have to do 2 mem copies to accomplish this in C#? Seems like such a waste.. I was looking at unions in C# to get around the copies.

Community
  • 1
  • 1
Raja
  • 2,846
  • 5
  • 19
  • 28
  • Possible duplicate of [Fast copy of Color32\[\] array to byte\[\] array](http://stackoverflow.com/questions/21512259/fast-copy-of-color32-array-to-byte-array) – NineBerry Apr 05 '16 at 23:42
  • I have explicitly referenced that question in my description above. The link gives one possible solution (that doesn't work in my case) and more importantly, it's a question on whether 2 copies can be avoided. – Raja Apr 05 '16 at 23:45
  • 1
    Why can you not use LoadRawTextureData() of the Texture2D object? The documentations says to call Apply() after changing the texture so that the changes are actually uploaded to the graphics card. – NineBerry Apr 05 '16 at 23:58
  • How did I miss that function? :( Perfect, thank you. My union solution worked as well, but this is just easier. Funny part is Texture2D.SetPixels takes time, and I'm going with a native plugin solution to blit image data onto the gfx texture resource. I suspect it uses `UpdateSubresource` and doing it every frame kills a little perf. – Raja Apr 06 '16 at 00:04

1 Answers1

3

Here's what I learned:

1) There is no need to Marshal b/w managed and unmanaged memory to convert byte[] to Color32[] and vice versa. Instead, use a union in 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];

Now, if you update the byteArray with data from a camera, you can apply it to the Texture2D using myTexture.SetPixels(data.colors);

No copying involved.

2) No need for the union solution above. Haha. As NineBerry pointed out, you can just upload a raw byte buffer to a texture via myTexture.LoadRawTextureData(bytes);

This is perfect in my case, since my camera bytebuffer comes as BGRA (low to high) and I'd need to swizzle the channels before upload (expensive) OR solve it in my shader if i went with (1)

3) The SetPixels method in Unity can be expensive on low end systems. I haven't dug deeper, but I suspect it's because the D3D textures created under the hood use D3D_USAGE_DEFAULT, which means updating them has to use UpdateSubresource which incurs a driver copy and possible stall (can't update a resource currently in use by the GPU).

So, the ideal solution is to write a native plugin that creates a 2D texture with D3D_USAGE_DYNAMIC, maps-updates-unmaps it, and pass that reference back to Unity. Overkill for now, but I might revisit this later.

Community
  • 1
  • 1
Raja
  • 2,846
  • 5
  • 19
  • 28
  • Since `byteArray` and `colors` actually point to the same memory, you only need to initialize one of them. So you can remove one of the array constructors. – NineBerry Apr 06 '16 at 09:33
  • I thought so too, but the array object wouldn't be initialized (i.e., how many Color32's are there? v/s how many byte's are there). I ran into runtime errors as a result. I come from a C++ background, so this stuff isn't too clear yet :) – Raja Apr 06 '16 at 17:57
  • Downvoted for failing to mention a critical shortcoming of solution (2): all MIP levels have to be provided in the supplied data and won't be generated for you when you Apply() the way they would be if you had used SetPixels() – Display Name Mar 01 '19 at 22:49