Search Unity

Fast memory copy between arrays?

Discussion in 'Scripting' started by imaginaryhuman, Jul 1, 2015.

  1. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    I have a byte array and a Color32() array. The byte array contains color component data, but it cannot be in a Color32() format for various reasons.

    I want to do a very fast/fastest possible copy of a rectangular area of that byte array into the Color32() array, which later will be uploaded to a texture. The bytearray represents a much larger rectangle of pixels than the Color32() array, so I only need to copy a subsection.

    At present I can do this in a loop myself by constructing new Color32()'s from 4 bytes. But I am wondering is there some API function to do like a fast `memcopy` or to cast the array or something, so that I can copy between them much faster?
     
  2. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    System.Array.Copy exists, but i doubt it will convert from byte to color component
     
  3. flaminghairball

    flaminghairball

    Joined:
    Jun 12, 2008
    Posts:
    868
    I haven't done any timed tests so can't speak to the speed of this, but:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Runtime.InteropServices;
    4.  
    5. public class StructTest : MonoBehaviour {
    6.  
    7.     // Use this for initialization
    8.     void Start () {
    9.  
    10.         var colorBytes = new Color32Bytes();
    11.         colorBytes.byteArray = new byte[16] {
    12.             255,0,0,255,    //red
    13.             255,0,0,255,    //red
    14.             0,255,0,255,    //green
    15.             255,0,0,255     //red
    16.         };
    17.  
    18.         GetComponent<Renderer>().material.SetColor("_Color", colorBytes.colors[2]); // sets color of attached renderer's material to green
    19.     }
    20. }
    21.  
    22. [StructLayout(LayoutKind.Explicit)]
    23. struct Color32Bytes
    24. {
    25.     [FieldOffset(0)]
    26.     public byte[] byteArray;   //4 bytes per color32
    27.  
    28.     [FieldOffset(0)]
    29.     public Color32[] colors;
    30. }
    31.  
     
  4. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    What is that code doing? Color32Bytes is some kind of new `type` which acts both as a byte array and a color32 array, sharing the same memory? I guess that might possibly work, but not much different to Color32(bytearray[]).

    Wondering mainly if there's a system function which, at the lower level, is designed for copying memory like this without lots of Unity script being involved, since native functions are faster... even if I could just use to copy one row at a time.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    It is the C# equivalent of a C/C++ union. I've not used it but essentially it is telling the compiler that you want access to a block of memory in various ways, such as by Color32 objects or by sets of four bytes.

    I'm sure C# imposes some additional restrictions on it to keep you within managed code boundaries, but essentially that data layout allows you to access it either way.

    None of this speaks to what kind of performance you might see under the hood however. Depending on the types, it may be necessary for some very unoptimized byte-level copying to actually take place.

    There's a blurb about it here:

    https://social.msdn.microsoft.com/F.../c-equivalent-to-c-union?forum=csharplanguage

    You might also be able to use System.Runtime.InteropServices calls to pin each array and then use Marshal.Copy blast stuff around in memory, and that ought to be pretty close to your basic C/C++ memcpy() style burst transfer mode.
     
  6. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    Can this be done in Unity script?
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    That is outside my knowledge. Sorry. Google is your friend there. I imagine that it depends if UnityScript properly exposes those services in the .NET library.
     
  8. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    Which platform are you on? If it's desktop, you might be able to do this using an 'unsafe' block in C#.
     
    MNNoxMortem, Tomnnn and Kiwasi like this.
  9. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    'Unsafe' was the first thing that popped into my mind too. It lets you play with pointers and memory directly.

    Be warned, its called 'unsafe' for a reason. Its pretty easy to mess things up if you are not careful. You are basically taking your memory into your own hands.
     
  10. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
  11. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    I saw that too... the big problem though is array.copy and blockcopy will not work copying bytes from a byte array to a color32[] array. They are different types. You can only blockcopy primitive types. And although array.copy says it will typecast and stuff, it doesn't work in this case.
     
  12. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    Are you sure? Bytes are bytes regardless of data type. As long as it's all sequential in memory and you compute the number of bytes to copy correctly I'd think that ought to work.
     
  13. Todd-Wasson

    Todd-Wasson

    Joined:
    Aug 7, 2014
    Posts:
    1,079
    BlockCopy uses a source and destination address and the number of bytes to copy. It doesn't care what the type is.
     
  14. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    Unity throws an error when trying to blockcopy from a bytearray to a color32 array. The error goes away when both arrays are of the same type.
     
  15. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    System.Buffer.BlockCopy(LevelPixels,CacheLevelRow+LevelScrollX,VisiblePixels,CacheYRow,CacheTextureSize);

    (Copying 1 row of data, LevelPixels is a byte array, VisiblePixels is a Color32[] array)

    produces:

    "ArgumentException: Object must be an array of primitives.
    System.Buffer.ByteLength (System.Array array) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Buffer.cs:64)
    System.Buffer.BlockCopy (System.Array src, Int32 srcOffset, System.Array dst, Int32 dstOffset, Int32 count) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Buffer.cs:"
     
  16. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    How though can you get the pointer to a Color32[] array and a byte array, to be able to then pass this into the unsafe copy?
     
  17. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    This is a sample snippet to get a System.IntPtr out of a Color32[] array.

    This particular code just passes it to a native function, but you can do whatever you want with it.

    Code (csharp):
    1.     public static void PinTextureAndCallNativeRenderColor( Texture2D t2d, int span)
    2.     {
    3.         if (pixels == null || span != pixelSpan)
    4.         {
    5.             pixelSpan = span;
    6.             pixels = new Color32[ pixelSpan * pixelSpan];
    7.         }
    8.  
    9.         GCHandle handle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
    10.         try
    11.         {
    12.             dispatcher1_rendercolor( handle.AddrOfPinnedObject(), span, 0);
    13.         }
    14.         finally
    15.         {
    16.             if (handle.IsAllocated)
    17.             {
    18.                 handle.Free ();
    19.             }
    20.         }
    21.        
    22.         t2d.SetPixels32( pixels);
    23.         t2d.Apply();
    24.     }
     
  18. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    is the try/finally block really needed, or is that just part of something else you're doing?
     
  19. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Well yes, if you don't want to be leaking little GCHandle-sized blocks of memory every time you sail through here.

    Take a look at some of the docs on the functions involved because before using them "in anger," you might want to develop a sense of what you are getting into with it all.
     
  20. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    Based on above code and similar code found elsewhere, I came up with this which works in Unity and is legal code (currently, anyway).

    You need a "using System.Runtime.InteropServices;" at the top of your script. C#

    Code (CSharp):
    1.     // Cast a byte array as a Color32 array to allow unity to directly upload byte-array data using SetColors32() function to an RGBA texture without having to 'convert' all the byte data from byte to Color32 first.
    2. //You can then just SetPixels32() using the byte array, while still retaining the speed ability of reading/writing bytes instead of Color32's.
    3.     void Start () {
    4.         byte[] mybytearray = new byte[1000];
    5.         object inputObj = mybytearray;
    6.         mybytearray[0]=10;
    7.         mybytearray[1]=20;
    8.         mybytearray[2]=30;
    9.         mybytearray[3]=40;
    10.         Color32[] mycolors = Convert(mybytearray);    //'Cast' byte array as Color32 array
    11.         Debug.Log(mycolors[0]);    //Should show 10,20,30,40 without having ever copied any data to a separate Color32 array
    12.     }
    13.  
    14.     static Color32[] Convert(byte[] input){
    15.         //This function is called in order to pass in a byte array and convert it to a Color32 array.
    16.         //The 'Converter' type defined below in the struct, is basically just a custom type with a byte array field
    17.         //Except that also in that structure, the [FieldOffset] is used to say at what memory location to locate the data for a given field
    18.         //Then within that custom type, we have a Color32 field (at this point unused), which happens to map to the same memory location as the byte array, so they share memory
    19.         //So if we simply set the byte array to that custom type's byte array, the type's Color32 array will also be pointing to that same memory reference, with no 'copy' of any memory bytes
    20.         //So the reference in it to the byte array points to the same mybytearray[] memory as the Color32[] array.
    21.         //We then simply return the Color32[] array and hey presto, the byte array has been interpreted as a Color32 array with almost zero overhead.
    22.         Converter converter = new Converter();
    23.         converter.mybytes = input;
    24.         return converter.mycols;
    25.     }
    26.     [StructLayout(LayoutKind.Explicit)]
    27.     struct Converter
    28.     {
    29.         [FieldOffset(0)]
    30.         public byte[] mybytes;
    31.         [FieldOffset(0)]
    32.         public Color32[] mycols;
    33.     }
    34.     //We’re using a Converter-struct that uses some interop-magic to define two fields that map to the same location in memory. Now we can access the same actual memory but using different variable types
     
    Todd-Wasson and Kurt-Dekker like this.