using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Jobs; using Unity.Mathematics; [assembly: InternalsVisibleTo("Burst.Benchmarks")] namespace UnityBenchShared { internal struct Sphere { private float x, y, z, r; public Sphere(float x, float y, float z, float r) { this.x = x; this.y = y; this.z = z; this.r = r; } public bool Intersects(Sphere other) { float dx = x - other.x; float dy = y - other.y; float dz = z - other.z; float rs = r + other.r; return dx * dx + dy * dy + dz * dz < (rs * rs); } } internal static class SphereCulling { public static int BenchCount = 128 * 1024 + 3; } internal interface IJob : IJob { T Result { get; set; } } /// /// Simple AOS with a Sphere struct using plain floats /// internal struct SphereCullingAOSJob : IJob, IDisposable { public Sphere Against; [ReadOnly] public NativeArray Spheres; [MarshalAs(UnmanagedType.U1)] private bool result; public bool Result { get => result; set => result = value; } public void Execute() { bool result = false; for (int i = 0; i < Spheres.Length; ++i) { result |= Spheres[i].Intersects(Against); } Result = result; } public struct Provider : IArgumentProvider { public object Value { get { int length = SphereCulling.BenchCount * 2; var job = new SphereCullingAOSJob() { Spheres = new NativeArray(length, Allocator.Persistent), Against = new Sphere(0, 0, 0, 1) }; var random = new System.Random(0); for (int i = 0; i < job.Spheres.Length; i++) { // Most don't intersects var x = random.Next(100) + 3.0f; if (i == job.Spheres.Length / 2) { // only this one x = 0.5f; } job.Spheres[i] = new Sphere(x, x, x, 1.0f); } return job; } } } public void Dispose() { Spheres.Dispose(); } } /// /// Simple AOS with a float4 for the struct and using /// internal struct SphereCullingFloat4AOSJob : IJob, IDisposable { public float4 Against; [ReadOnly] public NativeArray Spheres; [MarshalAs(UnmanagedType.U1)] private bool result; public bool Result { get => result; set => result = value; } public static bool Intersects(float4 value, float4 other) { float rs = value.w + other.w; return math.dot(value.xyz, other.xyz) < (rs * rs); } public void Execute() { bool result = false; for (int i = 0; i < Spheres.Length; ++i) { result |= Intersects(Spheres[i], Against); } Result = result; } public struct Provider : IArgumentProvider { public object Value { get { int length = SphereCulling.BenchCount; var job = new SphereCullingFloat4AOSJob() { Spheres = new NativeArray(length, Allocator.Persistent), Against = new float4(0, 0, 0, 1) }; var random = new System.Random(0); for (int i = 0; i < job.Spheres.Length; i++) { // Most don't intersects var x = random.Next(100) + 3.0f; if (i == job.Spheres.Length / 2) { // only this one x = 0.5f; } job.Spheres[i] = new float4(x, x, x, 1.0f); } return job; } } } public void Dispose() { Spheres.Dispose(); } } /// /// Simple SOA with 4 NativeArray for X,Y,Z,R /// internal struct SphereCullingSOAJob : IJob, IDisposable { [ReadOnly] public NativeArray X; [ReadOnly] public NativeArray Y; [ReadOnly] public NativeArray Z; [ReadOnly] public NativeArray R; public float4 Against; [MarshalAs(UnmanagedType.U1)] private bool result; public bool Result { get => result; set => result = value; } public void Execute() { bool result = false; for (int i = 0; i < X.Length; ++i) { float dx = X[i] - Against.x; float dy = Y[i] - Against.y; float dz = Z[i] - Against.z; float rs = R[i] + Against.w; result |= dx * dx + dy * dy + dz * dz < (rs * rs); } Result = result; } public struct Provider : IArgumentProvider { public object Value { get { int length = SphereCulling.BenchCount * 2; var job = new SphereCullingSOAJob() { X = new NativeArray(length, Allocator.Persistent), Y = new NativeArray(length, Allocator.Persistent), Z = new NativeArray(length, Allocator.Persistent), R = new NativeArray(length, Allocator.Persistent), Against = new float4(0, 0, 0, 1) }; var random = new System.Random(0); for (int i = 0; i < job.X.Length; i++) { // Most don't intersects var x = random.Next(100) + 3.0f; if (i == job.X.Length / 2) { // only this one x = 0.5f; } job.X[i] = x; job.Y[i] = x; job.Z[i] = x; job.R[i] = 1; } return job; } } } public void Dispose() { X.Dispose(); Y.Dispose(); Z.Dispose(); R.Dispose(); } } /// /// SOA with chunks of x,y,z,r using `float4` /// internal struct SphereCullingChunkSOAJob : IJob, IDisposable { public struct Chunk { public float4 X; public float4 Y; public float4 Z; public float4 R; } [ReadOnly] public NativeArray Chunks; public float4 Against; [MarshalAs(UnmanagedType.U1)] private bool result; public bool Result { get => result; set => result = value; } public void Execute() { bool result = false; for (int i = 0; i < Chunks.Length; ++i) { var chunk = Chunks[i]; for (int j = 0; j < 4; j++) { float dx = chunk.X[j] - Against.x; float dy = chunk.Y[j] - Against.y; float dz = chunk.Z[j] - Against.z; float rs = chunk.R[j] + Against.w; result |= dx * dx + dy * dy + dz * dz < (rs * rs); } } Result = result; } public struct Provider : IArgumentProvider { public object Value { get { // Approximate a similar batch int length = ((SphereCulling.BenchCount * 2) + 4) / 4; var job = new SphereCullingChunkSOAJob { Chunks = new NativeArray(length, Allocator.Persistent), Against = new float4(0, 0, 0, 1) }; var random = new System.Random(0); for (int i = 0; i < job.Chunks.Length; i++) { var chunk = job.Chunks[i]; for (int j = 0; j < 4; j++) { // Most don't intersects var x = random.Next(100) + 3.0f; if (i == job.Chunks.Length / 2) { // only this one x = 0.5f; } chunk.X[j] = x; chunk.Y[j] = x; chunk.Z[j] = x; chunk.R[j] = 1; } job.Chunks[i] = chunk; } return job; } } } public void Dispose() { Chunks.Dispose(); } } /// /// SOA with chunks of x,y,z,r using `fixed float x[4]` /// internal struct SphereCullingChunkFixedSOAJob : IJob, IDisposable { public unsafe struct Chunk { public fixed float X[4]; public fixed float Y[4]; public fixed float Z[4]; public fixed float R[4]; } [ReadOnly] public NativeArray Chunks; public float4 Against; [MarshalAs(UnmanagedType.U1)] private bool result; public bool Result { get => result; set => result = value; } public unsafe void Execute() { bool result = false; for (int i = 0; i < Chunks.Length; ++i) { var chunk = Chunks[i]; for (int j = 0; j < 4; j++) { float dx = chunk.X[j] - Against.x; float dy = chunk.Y[j] - Against.y; float dz = chunk.Z[j] - Against.z; float rs = chunk.R[j] + Against.w; result |= dx * dx + dy * dy + dz * dz < (rs * rs); } } Result = result; } public struct Provider : IArgumentProvider { public unsafe object Value { get { // Approximate a similar batch int length = ((SphereCulling.BenchCount) + 4) / 2; var job = new SphereCullingChunkFixedSOAJob { Chunks = new NativeArray(length, Allocator.Persistent), Against = new float4(0, 0, 0, 1) }; var random = new System.Random(0); for (int i = 0; i < job.Chunks.Length; i++) { var chunk = job.Chunks[i]; for (int j = 0; j < 4; j++) { // Most don't intersects var x = random.Next(100) + 3.0f; if (i == job.Chunks.Length / 2) { // only this one x = 0.5f; } chunk.X[j] = x; chunk.Y[j] = x; chunk.Z[j] = x; chunk.R[j] = 1; } job.Chunks[i] = chunk; } return job; } } } public void Dispose() { Chunks.Dispose(); } } /// /// internal struct SphereCullingChunkSOAManualJob : IJob, IDisposable { public struct Chunk { public float4 X; public float4 Y; public float4 Z; public float4 R; } [ReadOnly] public NativeArray Chunks; public float4 Against; [MarshalAs(UnmanagedType.U1)] private bool result; public bool Result { get => result; set => result = value; } public void Execute() { bool4 result = false; for (int i = 0; i < Chunks.Length; ++i) { var chunk = Chunks[i]; float4 dx = chunk.X - Against.x; float4 dy = chunk.Y - Against.y; float4 dz = chunk.Z - Against.z; float4 rs = chunk.R + Against.w; result |= dx * dx + dy * dy + dz * dz < (rs * rs); } Result = math.any(result); } public struct Provider : IArgumentProvider { public object Value { get { // Approximate a similar batch int length = (SphereCulling.BenchCount + 4) / 4; var job = new SphereCullingChunkSOAManualJob { Chunks = new NativeArray(length, Allocator.Persistent), Against = new float4(0, 0, 0, 1) }; var random = new System.Random(0); for (int i = 0; i < job.Chunks.Length; i++) { var chunk = job.Chunks[i]; for (int j = 0; j < 4; j++) { // Most don't intersects var x = random.Next(100) + 3.0f; if (i == job.Chunks.Length / 2) { // only this one x = 0.5f; } chunk.X[j] = x; chunk.Y[j] = x; chunk.Z[j] = x; chunk.R[j] = 1; } job.Chunks[i] = chunk; } return job; } } } public void Dispose() { Chunks.Dispose(); } } }