docs.unity3d.com
Search Results for

    Show / Hide Table of Contents

    Function pointers

    To work with dynamic functions that process data based on other data states, use FunctionPointer<T>. Because Burst treats delegates as managed objects, you can't use C# delegates to work with dynamic functions.

    Support details

    Function pointers don't support generic delegates. Also, avoid wrapping BurstCompiler.CompileFunctionPointer<T> within another open generic method. If you do this, Burst can't apply required attributes to the delegate, perform additional safety analysis, or perform potential optimizations.

    Argument and return types are subject to the same restrictions as DllImport and internal calls. For more information, refer to the documentation on DllImport and internal calls.

    Interoperability with IL2CPP

    Interoperability of function pointers with IL2CPP requires System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute on the delegate. Set the calling convention to CallingConvention.Cdecl. Burst automatically adds this attribute to delegates that are used with BurstCompiler.CompileFunctionPointer<T>.

    Using function pointers

    To use function pointers, identify the static functions that you want Burst to compile and do the following:

    1. Add a [BurstCompile] attribute to these functions.

    2. Add a [BurstCompile] attribute to the containing type. This helps the Burst compiler find the static methods that have the [BurstCompile] attribute.

    3. Declare a delegate to create the "interface" of these functions.

    4. Add a [MonoPInvokeCallbackAttribute] attribute to the functions to make them compatible with IL2CPP. For example:

      // Instruct Burst to look for static methods with [BurstCompile] attribute
      [BurstCompile]
      class EnclosingType {
          [BurstCompile]
          [MonoPInvokeCallback(typeof(Process2FloatsDelegate))]
          public static float MultiplyFloat(float a, float b) => a * b;
      
          [BurstCompile]
          [MonoPInvokeCallback(typeof(Process2FloatsDelegate))]
          public static float AddFloat(float a, float b) => a + b;
      
          // A common interface for both MultiplyFloat and AddFloat methods
          public delegate float Process2FloatsDelegate(float a, float b);
      }
      
    5. Compile these function pointers from regular C# code:

          // Contains a compiled version of MultiplyFloat with Burst
          FunctionPointer<Process2FloatsDelegate> mulFunctionPointer = BurstCompiler.CompileFunctionPointer<Process2FloatsDelegate>(MultiplyFloat);
      
          // Contains a compiled version of AddFloat with Burst
          FunctionPointer<Process2FloatsDelegate> addFunctionPointer = BurstCompiler.CompileFunctionPointer<Process2FloatsDelegate>(AddFloat);
      

    The [MonoPInvokeCallbackAttribute] is available under the AOT namespace. If for any reason it's not available, you can create it locally by declaring it as follows:

    public class MonoPInvokeCallbackAttribute : Attribute
    {​
    
    }
    

    For more information on [MonoPInvokeCallbackAttribute] and example usage in the iOS context, refer to Callback from native code in the manual.

    Using function pointers in a job

    To use the function pointers directly from a job, pass them to the job struct:

        // Invoke the function pointers from HPC# jobs
        var resultMul = mulFunctionPointer.Invoke(1.0f, 2.0f);
        var resultAdd = addFunctionPointer.Invoke(1.0f, 2.0f);
    

    Burst compiles function pointers asynchronously for jobs by default. To force a synchronous compilation of function pointers use [BurstCompile(SynchronousCompilation = true)].

    Using function pointers in C# code

    To use these function pointers from regular C# code, cache the FunctionPointer<T>.Invoke property (which is the delegate instance) to a static field to get the best performance:

        private readonly static Process2FloatsDelegate mulFunctionPointerInvoke = BurstCompiler.CompileFunctionPointer<Process2FloatsDelegate>(MultiplyFloat).Invoke;
    
        // Invoke the delegate from C#
        var resultMul = mulFunctionPointerInvoke(1.0f, 2.0f);
    

    Using Burst-compiled function pointers from C# might be slower than their pure C# version counterparts if the function is too small compared to the overhead of P/Invoke interop.

    Performance considerations

    Where possible, use a job over a function pointer to run Burst compiled code, because jobs are more performant. Burst provides better aliasing calculations for jobs because the job safety system has more optimizations by default.

    You also can't pass most of the [NativeContainer] structs like NativeArray directly to function pointers and must use a job struct to do so. Native container structs contain managed objects for safety checks that the Burst compiler can work around when compiling jobs, but not for function pointers.

    The following example shows a bad example of how to use function pointers in Burst. The function pointer computes math.sqrt from an input pointer and stores it to an output pointer. MyJob feeds this function pointer sources from two NativeArrays which isn't optimal:

    ///Bad function pointer example
    [BurstCompile]
    public class MyFunctionPointers
    {
        public unsafe delegate void MyFunctionPointerDelegate(float* input, float* output);
    
        [BurstCompile]
        public static unsafe void MyFunctionPointer(float* input, float* output)
        {
            *output = math.sqrt(*input);
        }
    }
    
    [BurstCompile]
    struct MyJob : IJobParallelFor
    {
         public FunctionPointer<MyFunctionPointers.MyFunctionPointerDelegate> FunctionPointer;
    
        [ReadOnly] public NativeArray<float> Input;
        [WriteOnly] public NativeArray<float> Output;
    
        public unsafe void Execute(int index)
        {
            var inputPtr = (float*)Input.GetUnsafeReadOnlyPtr();
            var outputPtr = (float*)Output.GetUnsafePtr();
            FunctionPointer.Invoke(inputPtr + index, outputPtr + index);
        }
    }
    

    This example isn't optimal for the following reasons:

    • Burst can't vectorize the function pointer because it's being fed a single scalar element. This means that 4-8x performance is lost from a lack of vectorization.
    • The MyJob knows that the Input and Output native arrays can't alias, but this information isn't communicated to the function pointer.
    • There is a non-zero overhead to constantly branching to a function pointer somewhere else in memory.

    To use a function pointer in an optimal way, always process batches of data in the function pointer, like so:

    [BurstCompile]
    public class MyFunctionPointers
    {
        public unsafe delegate void MyFunctionPointerDelegate(int count, float* input, float* output);
    
        [BurstCompile]
        public static unsafe void MyFunctionPointer(int count, float* input, float* output)
        {
            for (int i = 0; i < count; i++)
            {
                output[i] = math.sqrt(input[i]);
            }
        }
    }
    
    [BurstCompile]
    struct MyJob : IJobParallelForBatch
    {
         public FunctionPointer<MyFunctionPointers.MyFunctionPointerDelegate> FunctionPointer;
    
        [ReadOnly] public NativeArray<float> Input;
        [WriteOnly] public NativeArray<float> Output;
    
        public unsafe void Execute(int index, int count)
        {
            var inputPtr = (float*)Input.GetUnsafeReadOnlyPtr() + index;
            var outputPtr = (float*)Output.GetUnsafePtr() + index;
            FunctionPointer.Invoke(count, inputPtr, outputPtr);
        }
    }
    

    The modified MyFunctionPointer takes a count of elements to process, and loops over the input and output pointers to do a lot of calculations. The MyJob becomes an IJobParallelForBatch, and the count is passed directly into the function pointer. This is better for performance because:

    • Burst vectorizes the MyFunctionPointer call.
    • Burst processes count items per function pointer, so any overhead of calling the function pointer is reduced by count times. For example, if you run a batch of 128, the function pointer overhead is 1/128th per index of what it was previously.
    • Batching results in a 1.53x performance gain over not batching.

    However, to get the best possible performance, use a job. This gives Burst the most visibility over what you want it to do, and the most opportunities to optimize:

    [BurstCompile]
    struct MyJob : IJobParallelFor
    {
        [ReadOnly] public NativeArray<float> Input;
        [WriteOnly] public NativeArray<float> Output;
    
        public unsafe void Execute(int index)
        {
            Output[i] = math.sqrt(Input[i]);
        }
    }
    

    This runs 1.26x faster than the batched function pointer example, and 1.93x faster than the non-batched function pointer examples. Burst has perfect aliasing knowledge and can make the broadest modifications to the above. This code is also a lot simpler than either of the function pointer cases.

    Additional resources

    • HPC# Overview
    • Calling Burst-compiled code

    Did you find this page useful? Please give it a rating:

    Thanks for rating this page!

    Report a problem on this page

    What kind of problem would you like to report?

    • This page needs code samples
    • Code samples do not work
    • Information is missing
    • Information is incorrect
    • Information is unclear or confusing
    • There is a spelling/grammar error on this page
    • Something else

    Thanks for letting us know! This page has been marked for review based on your feedback.

    If you have time, you can provide more information to help us fix the problem faster.

    Provide more information

    You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see:

    You've told us there are code samples on this page which don't work. If you know how to fix it, or have something better we could use instead, please let us know:

    You've told us there is information missing from this page. Please tell us more about what's missing:

    You've told us there is incorrect information on this page. If you know what we should change to make it correct, please tell us:

    You've told us this page has unclear or confusing information. Please tell us more about what you found unclear or confusing, or let us know how we could make it clearer:

    You've told us there is a spelling or grammar error on this page. Please tell us what's wrong:

    You've told us this page has a problem. Please tell us more about what's wrong:

    Thank you for helping to make the Unity documentation better!

    Your feedback has been submitted as a ticket for our documentation team to review.

    We are not able to reply to every ticket submitted.

    In This Article
    • Support details
      • Interoperability with IL2CPP
    • Using function pointers
      • Using function pointers in a job
      • Using function pointers in C# code
    • Performance considerations
    • Additional resources
    Back to top
    Copyright © 2025 Unity Technologies — Trademarks and terms of use
    • Legal
    • Privacy Policy
    • Cookie Policy
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)