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

Question Can you call a burst compiled function from native code?

Discussion in 'Burst' started by rohan-zatun, Mar 18, 2021.

  1. rohan-zatun

    rohan-zatun

    Joined:
    Jan 13, 2021
    Posts:
    14
    I'm working on a native plugin for Unity, and I was wondering if it is possible to call a burst compiled function from native code?
    Something like
    Code (CSharp):
    1.  
    2.  
    3. public delegate byte SomeDelegate(float A, float B);
    4.  
    5. [BurstCompile]
    6. public class SomeClass
    7. {
    8.     [BurstCompile, AOT.MonoPInvokeCallback(SomeDelegate)]
    9.     public static byte PerformSomeCalculations(float A, float B)
    10.     {
    11.         // do something with it
    12.         return output;
    13.     }
    14. }
    15.  
    16. public class SomeOtherClass
    17. {
    18.     [DllImport("MyPlugin")]
    19.     public static extern void InitializeNativeFunction([In] FunctionPointer<SomeDelegate> callbackFunction);
    20.  
    21.     public static void InitializeSomeOtherClass()
    22.     {
    23.         var fnPtr = BurstCompiler.CompileFunctionPointer<SomeDelegate>(SomeClass.PerformSomeCalculations);
    24.  
    25.         InitializeNativeFunction(fnPtr);
    26.         // FunctionPointer struct itself only contains a read-only IntPtr
    27.         // which I assume is the actual burst-compiled function, because
    28.         // the "Invoke" property (yes, property, not a function, uses
    29.         // Marshal.GetDelegateForFunctionPointer to convert it into a delegate.
    30.     }
    31. }
    32.  
    My main aim with this kind of setup is to provide C# extensibility to my native code, but while typing this, another question popped in my head. So, in the end, I have two questions.

    1. Is this doable? Has someone done it before? Any gotchas I should be careful about?
    2. Will this actually reduce interop costs? Or end up increasing them instead?

    As some more context, this is from burst docs:
    And it's what made me think of whether there would be an interop cost in
     
    Last edited: Mar 18, 2021
  2. rohan-zatun

    rohan-zatun

    Joined:
    Jan 13, 2021
    Posts:
    14
    Okay, I did some basic tests, and I think it works, at least on Windows.

    Here's the source code:

    Code (CSharp):
    1.  
    2. using System.Globalization;
    3. using System.Runtime.InteropServices;
    4. using System.Security;
    5. using Unity.Burst;
    6. using UnityEngine;
    7.  
    8. namespace DefaultNamespace
    9. {
    10.     public delegate ulong CalculateFactorialDelegate(ulong input);
    11.  
    12.     public class BurstInteropTester : MonoBehaviour
    13.     {
    14.         [DllImport("MyDLL", CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
    15.         private static extern ulong SomethingAddFactorials(ulong a, ulong b);
    16.  
    17.         [DllImport("MyDLL", CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
    18.         private static extern void InitSomethingCalculateFactorialManagedOverride(CalculateFactorialDelegate function);
    19.  
    20.         [DllImport("MyDLL", CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
    21.         private static extern void InitSomethingCalculateFactorialManagedOverride(FunctionPointer<CalculateFactorialDelegate> function);
    22.  
    23.         [SerializeField] private ulong a = 0;
    24.         [SerializeField] private ulong b = 0;
    25.         [SerializeField] private bool useBurst = false;
    26.         [SerializeField] private ulong calculationCount = 100;
    27.  
    28.         private void Awake()
    29.         {
    30.             InitSomethingCalculateFactorialManagedOverride(Library.CalculateFactorial);
    31.             a = 2;
    32.             b = 2;
    33.             useBurst = false;
    34.             calculationCount = 1000000;
    35.         }
    36.  
    37.         private void OnGUI()
    38.         {
    39.             using (new GUILayout.HorizontalScope())
    40.             {
    41.                 if (ulong.TryParse(GUILayout.TextField(a.ToString(CultureInfo.InvariantCulture)), out var aTemp))
    42.                 {
    43.                     a = aTemp;
    44.                 }
    45.  
    46.                 if (ulong.TryParse(GUILayout.TextField(b.ToString(CultureInfo.InvariantCulture)), out var bTemp))
    47.                 {
    48.                     b = bTemp;
    49.                 }
    50.             }
    51.  
    52.             var newValue = GUILayout.Toggle(useBurst, "Use Burst");
    53.             if (useBurst != newValue)
    54.             {
    55.                 useBurst = newValue;
    56.                 if (useBurst)
    57.                 {
    58.                     var function = BurstCompiler.CompileFunctionPointer<CalculateFactorialDelegate>(Library.CalculateFactorial);
    59.                     InitSomethingCalculateFactorialManagedOverride(function);
    60.                 }
    61.                 else InitSomethingCalculateFactorialManagedOverride(Library.CalculateFactorial);
    62.             }
    63.  
    64.             if (ulong.TryParse(GUILayout.TextField(calculationCount.ToString(CultureInfo.InvariantCulture)), out var calcCountTemp))
    65.             {
    66.                 calculationCount = calcCountTemp;
    67.             }
    68.  
    69.             ulong result = 0;
    70.             for (ulong i = 0; i < calculationCount; i++)
    71.             {
    72.                 result = SomethingAddFactorials(a, b);
    73.             }
    74.  
    75.             GUILayout.Label($"Result: {result}");
    76.             GUILayout.Label($"FrameTime: {Time.deltaTime * 1000} milliseconds");
    77.         }
    78.  
    79.         [BurstCompile]
    80.         public static class Library
    81.         {
    82.             [BurstCompile]
    83.             [AOT.MonoPInvokeCallback(typeof(CalculateFactorialDelegate))]
    84.             public static ulong CalculateFactorial(ulong input)
    85.             {
    86.                 var output = input;
    87.                 for (var i = input - 1; i > 1; --i)
    88.                 {
    89.                     output *= i;
    90.                 }
    91.  
    92.                 return output;
    93.             }
    94.         }
    95.     }
    96. }
    97.  
    Code (C++):
    1.  
    2. struct Something
    3. {
    4.     static unsigned long long CalculateFactorial(unsigned long long Input);
    5.     static unsigned long long AddFactorials(unsigned long long A, unsigned long long B);
    6. };
    7.  
    8. unsigned long long (__stdcall*SomethingCalculateFactorialManagedOverride)(unsigned long long Input) = nullptr;
    9.  
    10. extern "C" void __stdcall InitSomethingCalculateFactorialManagedOverride(unsigned long long (__stdcall*InDelegate)(unsigned long long Input))
    11. {
    12.     SomethingCalculateFactorialManagedOverride = InDelegate;
    13. }
    14.  
    15. unsigned long long Something::CalculateFactorial(unsigned long long Input)
    16. {
    17.     return SomethingCalculateFactorialManagedOverride(Input);
    18. }
    19.  
    20. extern "C" unsigned long long __stdcall SomethingAddFactorials(const unsigned long long A, const unsigned long long B)
    21. {
    22.     return Something::AddFactorials(A, B);
    23. }
    24.  
    25. unsigned long long Something::AddFactorials(const unsigned long long A, const unsigned long long B)
    26. {
    27.     return CalculateFactorial(A) + CalculateFactorial(B);
    28. }
    29.  
    Results (in frame-time, rough averages):
    • Editor (Win64):
      • useBurst = false: 66ms
      • useBurst = true: 33ms
    • Standalone Player (Mono-Win64):
      • useBurst = false: 60ms
      • useBurst = true: 30ms
    • Standalone Player (IL2CPP-Win64):
      • useBurst = false: 30ms
      • useBurst = true: 30ms

    Thinking about it, it's not terribly surprising that IL2CPP does not get affected, but there's still a significant improvement for Mono/Editor.