Skip to content

Performance

The six examples the gallery lists under Performance. Each block is what the gallery’s code panel shows beside that example, copied from the example itself: the lines that do it rather than the whole file, so it isn’t compiled here the way the guides’ code is. The file it came from is linked under each one.

A whole scene sent every frame, and only what moved paid for.

// The whole scene, every frame. There is no add, move or remove call: a
// description that says everything cannot go stale, and a key nobody sends
// this time is an object that has left.
OrblitScene(
objects: [
for (var i = 0; i < total; i++)
OrblitObject(
key: 1000 + i, // its own, for as long as it exists
transform: placementOf(i, seconds),
colour: colourOf(i),
castShadows: i % 7 == 0,
),
],
lights: [sun],
camera: camera,
)
// On the other side: a transform is compared before it is written, because
// writing one dirties the node and everything under it. A mesh instance whose
// object is gone goes back to a pool rather than being destroyed. Only a
// change of mesh rebuilds anything.

From many.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='a thousand objects' flutter run -d macos

Thousands of identical crates, merged into instanced draws without the scene saying so.

// Nothing about the objects changes. Each crate is still its own object
// with its own key; the scene just says that batching is allowed.
OrblitScene(
batching: true,
objects: [
for (var i = 0; i < 3000; i++)
OrblitObject(
key: 1000 + i,
transform: placementOf(i),
colour: crateColour, // the same for all, or they cannot share
castShadows: i % 5 == 0, // casters batch with casters
),
],
camera: camera,
)
// On the other side, objects with the same mesh, material, flags and — on
// the default surface — colour are counted as they arrive. Groups of four
// or more share one material instance, and Filament merges their draws into
// instanced ones. A transform written to one crate moves that one only.

From batching.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='batching' flutter run -d macos

One buffer of transforms, sent once, drawn in a handful of calls.

// One mesh, one buffer of transforms, one submission.
//
// The buffer is written once and kept. `revision` is what tells the renderer
// whether it has to be sent again — leave it alone and a hundred thousand
// members cost nothing per frame at all.
final transforms = Float32List(count * 16);
final colours = Float32List(count * 3);
for (var i = 0; i < count; i++) {
// Column-major, straight into the buffer. Nothing is allocated per member.
transforms[i * 16 + 0] = width;
transforms[i * 16 + 5] = height;
transforms[i * 16 + 10] = width;
transforms[i * 16 + 12] = x;
transforms[i * 16 + 13] = y;
transforms[i * 16 + 14] = z;
transforms[i * 16 + 15] = 1;
}
OrblitScene(
populations: [
OrblitPopulation(
key: 1,
transforms: transforms,
colours: colours,
// Every member is culled by this one box, so it must cover all of them.
minimum: Vector3(-spread, -2, -spread),
maximum: Vector3(spread, 14, spread),
// Bump this when you write into the buffers. Do not, and nothing is
// sent.
revision: revision,
),
],
camera: camera,
);

From crowd.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='a hundred thousand' flutter run -d macos

Slabs that pass through each other, so that every pixel is covered as many times as they overlap.

// Slabs all crossing at the middle, so no order of objects is the right
// order for every pixel and each one is covered many times over.
OrblitScene(
materials: [OrblitMaterial(key: 3, clearCoat: 1, anisotropy: 0.7)],
objects: [
for (var i = 0; i < 48; i++)
OrblitObject(
key: 100 + i,
material: 3,
transform: Matrix4.identity()
..rotateY(i * math.pi / 48)
..scaleByDouble(5, 3, 0.05, 1),
),
],
camera: camera,
)
// What it is for is measuring. Turn the slabs up and watch the frame's GPU
// time: on this machine it barely moves, because the hardware already
// decides which surface wins a tile before shading any of it.

From overdraw.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='overdraw' flutter run -d macos

Shadows, cascades, multisampling and render scale — the four named settings, and every dial behind them.

// One pipeline. The four named settings are settings of its dials, not
// different pipelines — nothing appears or disappears between them.
final pipeline = OrblitPipeline.at(OrblitDetail.high);
// Or every dial by hand.
final mine = OrblitPipeline(
shadows: OrblitShadows(
kind: OrblitShadowKind.soft,
mapSize: 2048,
// The single most effective shadow setting there is: one map over a
// hundred metres puts a centimetre in each pixel; four cascades over the
// same hundred metres puts a millimetre in the first.
cascades: 3,
distance: 80,
contact: true,
),
// Runs while the frame is drawn, unlike the anti-aliasing in post, which
// runs on the finished image. Sharper, and costs bandwidth on everything.
samples: 4,
// A frame that arrives on time slightly soft beats one that arrives late
// sharp.
resolution: OrblitResolution(adaptive: true, minScale: 0.6),
);
OrblitScene(objects: objects, camera: camera, pipeline: pipeline);

From pipeline.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='pipeline' flutter run -d macos

Turn the load up and watch what a frame costs. Nothing here is staged.

// The three loads, and why they are separate dials.
//
// A member of a population is a row in a buffer. Sixty-four share a draw, and
// standing still costs nothing at all — the buffer is only sent when its
// revision moves.
OrblitPopulation(key: 1, transforms: transforms, colours: colours,
revision: revision, minimum: ..., maximum: ...)
// An object is tracked one at a time: its own key, its own entity, its own
// draw, compared against last frame every frame.
OrblitObject(key: 2000 + i, transform: ..., colour: ...)
// And the sky is neither. It is a volume marched per pixel, so its cost has
// nothing to do with how much of anything is in the scene.
OrblitSky(quality: SkyQuality.fair, clouds: OrblitClouds.cumulus(cover: 0.42))
// What a frame cost, from Filament's own frame history — the median of the
// last handful, because a mean is dragged about by the one frame in thirty
// that hits a hitch.
final ms = await OrblitView.gpuMilliseconds(viewport);

From benchmark.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='benchmark' flutter run -d macos