Burst supports most of the expressions and statements:
if
/else
/switch
/case
/for
/while
/break
/continue
.DllImport
and internal calls.throw
expressions, assuming simple throw patterns (e.g throw new ArgumentException("Invalid argument")
). In that case, we will try to extract the static string exception message to include it in the generated code.cpblk
, initblk
, sizeof
.fixed
statements.try
/finally
and associated IDisposable patterns (using
and foreach
)
try
block, control flow would go to the finally
block. In Burst, if an exception occurs whether inside or outside a try
block, the currently running job or function pointer will terminate immediately.Debug.Log
.ProfilerMarker
.Burst also provides alternatives for some C# constructions not directly accessible to HPC#:
Burst does not support:
catch
in a try
/catch
.Burst will try to evaluate all static fields and all static constructors at compile-time. The same base language is supported at compile-time, with some exceptions:
UnityEngine.Debug.Assert
NUnit.Framework.Assert.AreEqual
NUnit.Framework.Assert.AreNotEqual
Unity.Burst.BurstCompiler.IsEnabled
Unity.Burst.BurstRuntime.GetHashCode32
and Unity.Burst.BurstRuntime.GetHashCode64
Simple throw patterns are also supported. Exceptions thrown during evaluation become compiler errors.
All the static fields and the static constructor for a given struct are evaluated together. Having non-readonly static fields in a Burst compiled struct will therefore cause a compilation error, since only readonly static fields are supported. If any static field or the static constructor fails to evaluate then all of them fail to evaluate for that struct. When compile-time evaluation fails Burst will try to fall back to compiling all static initialization code into an initialization function to be called once at runtime. This means that if code fails compile time evaluation it needs to be Burst compatible, otherwise compilation will fail. An exception to this is that there’s still limited support for initializing static readonly array fields as long as they are initialized from either an array constructor or from static data:
static readonly int[] MyArray0 = { 1, 2, 3, .. };
static readonly int[] MyArray1 = new int[10];
Burst supports the ability to call out to Burst compiled methods directly from managed code (2019.4+
only). Calling generic methods or methods whose declaring type is generic is not supported, otherwise the rules as for Function pointers apply. However as a user you don’t need to worry about the extra boiler plate needed for function pointers.
As an example here is a utility class which is Burst compiled (notice because we are using structs we are passing by reference as per the Function Pointer rules).
[BurstCompile]
public static class MyBurstUtilityClass
{
[BurstCompile]
public static void BurstCompiled_MultiplyAdd(in float4 mula, in float4 mulb, in float4 add, out float4 result)
{
result = mula * mulb + add;
}
}
Now we can utilise this method from our Managed code e.g.
public class MyMonoBehaviour : MonoBehaviour
{
void Start()
{
var mula = new float4(1, 2, 3, 4);
var mulb = new float4(-1,1,-1,1);
var add = new float4(99,0,0,0);
MyBurstUtilityClass.BurstCompiled_MultiplyAdd(mula, mulb, add, out var result);
Debug.Log(result);
}
}
and if we were to run (with the managed script attached to an object), we should now see in the log float4(98f, 2f, -3f, 4f)
.
This is achieved by using IL Post Processing in order to automatically transform the code into a function pointer and call, which is why you must adhere to the rules of Function pointers.
You can disable the direct call transformation by adding DisableDirectCall = true
to the BurstCompile options, which will prevent the Post Processor from running on the code. E.g.
[BurstCompile]
public static class MyBurstUtilityClass
{
[BurstCompile(DisableDirectCall = true)]
public static void BurstCompiled_MultiplyAdd(in float4 mula, in float4 mulb, in float4 add, out float4 result)
{
result = mula * mulb + add;
}
}
Burst supports throw
expressions for exceptions in the Editor (2019.3+
only), but crucially does not in Standalone Player builds. Exceptions in Burst are to be used solely for exceptional behavior. To ensure that code does not end up relying on exceptions for things like general control flow, Burst will produce the following warning on code that tries to throw
within a method not attributed by [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
:
Burst warning BC1370: An exception was thrown from a function without the correct [Conditional(“ENABLE_UNITY_COLLECTIONS_CHECKS”)] guard. Exceptions only work in the editor and so should be protected by this guard
Debug.Log
Burst provides partial support for using strings in the following two main scenarios:
Debug.Log
(see below)Unity.Collections
(e.g FixedString128
)A string can be either:
"This is a string literal"
).$"This is an integer {value}
or using string.Format
) where the string to format is also a string literalFor example, the following constructions are supported:
Debug.Log("This a string literal");
int value = 256;
Debug.Log($"This is an integer value {value}");
Which is equivalent to using string.Format
directly:
int value = 256;
Debug.Log(string.Format("This is an integer value {0}", value));
While it is possible to pass a managed string
literal or an interpolated string directly to Debug.Log
, it is not possible to pass a string to a user method or to use them as fields in a struct. In order to pass or store strings around, you need to use one of the FixedString
structs provided by the Unity.Collections
package:
int value = 256;
FixedString128 text = $"This is an integer value {value} used with FixedString128";
MyCustomLog(text);
// ...
// String can be passed as an argument to a method using a FixedString,
// but not using directly a managed `string`:
public static void MyCustomLog(in FixedString128 log)
{
Debug.Log(text);
}
Burst has limited support for string format arguments and specifiers:
int value = 256;
// Padding left: "This value ` 256`
Debug.Log($"This value `{value,5}`");
// Padding right: "This value `256 `
Debug.Log($"This value `{value,-5}`");
// Hexadecimal uppercase: "This value `00FF`
Debug.Log($"This value `{value:X4}`");
// Hexadecimal lowercase: "This value `00ff`
Debug.Log($"This value `{value:x4}`");
// Decimal with leading-zero: "This value `0256`
Debug.Log($"This value `{value:D4}`");
What is supported currently:
Debug.Log
methods:
Debug.Log(object)
Debug.LogWarning(object)
Debug.LogError(object)
string.Format
methods are supported:string.Format(string, object)
, string.Format(string, object, object)
, string.Format(string, object, object, object)
and more if the .NET API provides specializations with object arguments.string.Format(string, object[])
: which can happen for a string interpolation that would contain more than 3 arguments (e.g $"{arg1} {arg2} {arg3} {arg4} {arg5}..."
). In that case, we expect the object[] array to be of a constant size and no arguments should involve control flows (e.g $"This is a {(cond ? arg1 : arg2)}"
)char
, boolean
, byte
, sbyte
, ushort
, short
, uint
, int
, ulong
, long
, float
, double
.int2
, float3
are supported, except half
vector types.
c#
var value = new float3(1.0f, 2.0f, 3.0f);
// Logs "This value float3(1f, 2f, 3f)"
Debug.Log($"This value `{value}`");
ToString()
of structs. It will display the full name of the struct instead.G
, g
, D
, d
and X
, x
, with padding and specifier. For more details:
ProfilerMarker
In Unity 2020.2 and above, you can call new ProfilerMarker("MarkerName")
from Burst code:
[BurstCompile]
private static class ProfilerMarkerWrapper
{
private static readonly ProfilerMarker StaticMarker = new ProfilerMarker("TestStaticBurst");
[BurstCompile(CompileSynchronously = true)]
public static int CreateAndUseProfilerMarker(int start)
{
using (StaticMarker.Auto())
{
var p = new ProfilerMarker("TestBurst");
p.Begin();
var result = 0;
for (var i = start; i < start + 100000; i++)
{
result += i;
}
p.End();
return result;
}
}
}