Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

BlockCopy on .NET native toolchain

Discussion in 'Windows' started by tonemcbride, Nov 22, 2016.

  1. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    1,077
    Hi,

    I'm using BlockCopy in a game but it seems to cause an exception when I use it in a Master build (for Windows Store apps). I'm assuming this is because the Master build is using the .NET Native tool chain where the debug/release builds aren't.

    I'm decompressing a byte[] array from a file and then copying it to a 2-dimensional short array. (i.e. byte[] to short[][]). Unfortunately this seems to cause an exception 'Arg_MustBePrimArray'. I'm not quite sure why because I'm assuming short is a primitive type? Does anyone have any ideas why that might be happening on .NET only or whether there's a workaround to just copy blocks of memory without type checking?

    Thanks!
     
  2. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,624
    There is a known bug in .NET Native regarding handling multidimensional arrays, might be related. AFAIK Microsoft hasn't yet released the fix for it.
    Perhaps you can write the Copy function yourself for it? For efficiency you could used unsafe block and pointers.
     
  3. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    1,077
    Thanks for the fast reply - I replaced it temporarily with a for-loop to copy the data but it's probably pretty slow compared to BlockCopy. I'll have a look at the unsafe block/pointers and see if I can make it faster.
     
  4. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,478
  5. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    1,077
    Yeah, it looks like that's what is happening. I tried using unsafe/fixed code to copy the arrays but it didn't seem to like the 'array.GetRawArrayData' part in that code you linked to.

    I ended up just getting a handle to the destination (since it was a short[][] array) and doing a Marshal.Copy into it from my byte[] array.

    e.g.

    Code (CSharp):
    1.     public static void CustomBlockCopy( byte[] src , int srcOffset , Array dst , int dstOffset , int count )
    2.     {
    3.         GCHandle hDst = GCHandle.Alloc( dst, GCHandleType.Pinned );
    4.         IntPtr DstPtr = hDst.AddrOfPinnedObject();
    5.  
    6.         Marshal.Copy( src , 0 , DstPtr , count );
    7.        
    8.         hDst.Free();
    9.     }
    10.  
    That seems to work fine in the editor but on Master builds I get an error with the GCHandle.Alloc (the array isn't isomorphic). I've always assumed 2d arrays are really just 1d arrays internally but perhaps Microsoft is now saying that this can't be guaranteed?
     
  6. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,478
    They should be 1d arrays under the hood. That's exactly the bug we've filled with them. Does using fixed expression to get the pointer not work? Like this:

    Code (csharp):
    1. fixed (short* ptr = my2dArray)
    2. {
    3.     // do stuff with ptr
    4. }
     
  7. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    1,077
    It doesn't seem to work. If I do this:

    Code (CSharp):
    1.     public unsafe static void CustomBlockCopy( Array src , int srcOffset , Array dst , int dstOffset , int count )
    2.     {
    3.         fixed( byte* pSrc = &src.GetRawArrayData() , pDst = &dst.GetRawArrayData() )
    4.         {
    5.             Buffer.Memmove(pDst + dstOffset, pSrc + srcOffset, uCount);
    6.         }      
    7.     }
    8.  
    I get this error in the editor (and doing a build for Windows Store): 'Cannot take the address of the given expression' (for the line with 'fixed' in it).

    If I do this instead:

    Code (CSharp):
    1. fixed( short* ptr = dst )
    I get these errors:

    You cannot use the fixed statement to take the address of an already fixed expression
    Cannot implicitly convert type `System.Array' to `short*'

    and this

    Code (CSharp):
    1. fixed( short* ptr = &dst )
    gives me this error:

    Cannot take the address of, get the size of, or declare a pointer to a managed type `System.Array'.

    I'll mess around with it some more and let you know if I find any good workarounds.
     
    Last edited: Nov 22, 2016
  8. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,478
    "dst" should be an actual typed array, like "short[,]". Not System.Array. Then it should let you take its address.
     
  9. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    1,077
    Sorry, I had changed that to short[,] afterwards. I eventually got something working that copies my byte[] array to my 2 dimensional short[,] array. It won't be as fast as a BlockCopy but it's definitely faster than doing it manually in safe code. It could easily be optimised in a memcpy style by unrolling the loop for chunks.

    Code (CSharp):
    1.     public unsafe static void CustomBlockCopy( byte[] src , short[,] dst , int count )
    2.     {
    3.         fixed( byte* pSrc = src )
    4.         {
    5.             fixed( short* pDst = dst )
    6.             {
    7.                 ulong* pSrc64 = (ulong*)pSrc;
    8.                 ulong* pDst64 = (ulong*)pDst;
    9.              
    10.                 int CopyCount = count / sizeof( ulong );
    11.              
    12.                 for( int Idx = 0 ; Idx < CopyCount ; Idx++ )
    13.                 {
    14.                     pDst64[ Idx ] = pSrc64[ Idx ];
    15.                 }
    16.             }
    17.         }
    18.     }
    19.  
     
  10. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,478
    Looks good - you might want to check if the "count" doesn't divide by 8 without a remainder - in that case you're currently missing copying last few bytes.
     
  11. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    1,077
    Yeah, it's a bit specific to my needs - all my data is in 8k chunks. Here's a more generic solution that works with any size in case anyone else wants to modify it to their needs:

    Code (CSharp):
    1.     public unsafe static void CustomBlockCopy( byte[] src , short[,] dst , int count )
    2.     {
    3.         fixed( byte* pSrc = src )
    4.         {
    5.             fixed( short* pDst = dst )
    6.             {
    7.                 // Copy 64-bit blocks
    8.                 int CopyBlockCount = count / sizeof( ulong );
    9.  
    10.                 ulong* pSrc64 = (ulong*)pSrc;
    11.                 ulong* pDst64 = (ulong*)pDst;
    12.                 for( int BlockIdx = 0 ; BlockIdx < CopyBlockCount ; BlockIdx++ )
    13.                 {
    14.                     pDst64[ BlockIdx ] = pSrc64[ BlockIdx ];
    15.                 }
    16.  
    17.                 // Copy remainder as bytes
    18.                 int CopyByteCount = count % sizeof( ulong );
    19.  
    20.                 byte* pSrc8 = ( (byte*)pSrc ) + ( CopyBlockCount * sizeof( ulong ) );
    21.                 byte* pDst8 = ( (byte*)pDst ) + ( CopyBlockCount * sizeof( ulong ) );
    22.                 for( int ByteIdx = 0 ; ByteIdx < CopyByteCount ; ByteIdx++ )
    23.                 {
    24.                     pDst8[ ByteIdx ] = pSrc8[ ByteIdx ];
    25.                 }
    26.             }
    27.         }
    28.     }