Drawing from a Job/ECS

Inside the Unity Job System or ECS you cannot use the Draw class. Instead you have to create a custom builder that you pass to the job and then use for drawing.

Below you will find an example of a job that draws 10000 boxes every frame at an estimated 500 fps (according to the Profiler). The output looks like this:

using Drawing;
using Unity.Jobs;
using Unity.Burst;
using UnityEngine;
using Unity.Mathematics;

namespace Drawing.Examples {
public class BurstExample : MonoBehaviour {
// Use [BurstCompile] to allow Unity to compile the job using the Burst compiler
[BurstCompile]
struct DrawingJob : IJob {
public float2 offset;
// The job takes a command builder which we can use to draw things with
public CommandBuilder builder;

Color Colormap (float x) {
// Simple color map that goes from black through red to yellow
float r = math.clamp(8.0f / 3.0f * x, 0.0f, 1.0f);
float g = math.clamp(8.0f / 3.0f * x - 1.0f, 0.0f, 1.0f);
float b = math.clamp(4.0f * x - 3.0f, 0.0f, 1.0f);

return new Color(r, g, b, 1.0f);
}

public void Execute (int index) {
int x = index / 100;
int z = index % 100;

// Draw a solid box and a wire box
// Use Perlin noise to generate a procedural height map
var noise = Mathf.PerlinNoise(x * 0.05f + offset.x, z * 0.05f + offset.y);
Bounds bounds = new Bounds(new float3(x, 0, z), new float3(1, 14 * noise, 1));

//builder.WireBox(bounds, new Color(0, 0, 0, 0.2f));
builder.SolidBox(bounds, Colormap(noise));
}

public void Execute () {
for (int index = 0; index < 100 * 100; index++) {
Execute(index);
}
}
}

public void Update () {
var builder = DrawingManager.GetBuilder(true);

// Not necessary, but we know we are going to draw a lot
// so preallocating some buffers makes things a tiny bit faster
builder.Preallocate(100 * 100 * 400);

// Create a new job struct and schedule it using the Unity Job System
new DrawingJob {
builder = builder,
offset = new float2(Time.time * 0.2f, Time.time * 0.2f),
}.Schedule().Complete();

// Dispose the builder after the job is complete
builder.Dispose();
}
}
}

The script uses perlin noise to produce a smoothly varying height map.

Note

Make sure that you only dispose the builder instance after the job has completed.

Scopes with Burst

Scopes cannot be used in jobs compiled with the Burst compiler. This is because the Burst compiler does not support the using statement. Instead you can use the lower level Push and Pop methods.

// Both boxes will be red
builder.PushColor(Color.red);
builder.WireBox(Vector3.zero, Vector3.one);
builder.WireBox(Vector3.zero, Vector3.one);
builder.PopColor();

// This box will be drawn for 2 seconds
builder.PushDuration(2);
builder.WireBox(Vector3.zero, Vector3.one);
builder.PopDuration();

// Scopes can be nested
builder.PushColor(Color.red);
builder.PushDuration(2);
builder.WireBox(Vector3.zero, Vector3.one);
builder.PopDuration();
builder.PopColor();
If you call a number of push methods you must call the pop methods in the reverse order. You will get an error if you use the push or pop methods in the wrong order.