What a frame reports
Open the live demo · Read the source · View on GitHub
Rendering returns more than a texture. FrameResult records how much work was
submitted, which passes ran, and whether requested features were skipped. These
numbers are useful in diagnostics, tests, and an in-game performance display.
Step 1: Give the counters varied work #
The scene mixes a floor, cube, and torus across two materials. Different mesh sizes and material pipelines make draw, triangle, and pipeline counts distinct enough to catch a broken counter.
final Material matte = Material(
name: 'matte',
baseColor: Vector4(0.24, 0.62, 0.82, 1.0),
roughness: 0.72,
);
final Material metal = Material(
name: 'metal',
baseColor: Vector4(0.82, 0.43, 0.18, 1.0),
metallic: 0.76,
roughness: 0.24,
);
final Scene scene = Scene()
..ambientColor = Vector3(0.42, 0.5, 0.68)
..ambientIntensity = 0.12
..add(
MeshNode(
DeviceMesh.upload(
context.device,
const PlaneShape(width: 8.0, depth: 7.0).build(),
),
matte.copy()..doubleSided = true,
name: 'floor',
)..castsShadow = false,
)
..add(
MeshNode(
DeviceMesh.upload(
context.device,
CuboidShape(size: Vector3.all(1.5)).build(),
),
matte,
name: 'cube',
)..setPosition(-1.25, 0.75, 0.0),
)
..add(
MeshNode(
DeviceMesh.upload(
context.device,
const TorusShape(radius: 0.72, tubeRadius: 0.28).build(),
),
metal,
name: 'torus',
)..setPosition(1.35, 1.0, 0.0),
);
Step 2: Add an optional shadow pass #
The control changes castsShadow on the directional light. Switching it off
removes shadow work from later frame results while the colour scene remains.
_sun = LightNode(name: 'sun', intensity: 3.2, castsShadow: shadows)
..setLocalForward(Vector3(-0.45, -0.82, -0.35));
scene.add(_sun);
Step 3: Find work by pass name #
Each FramePass carries its graph-node name, CPU time, draws, triangles, and
pipeline switches. Looking up scene and composite separates geometry work
from the final full-screen draw. Summing pass draws reconstructs the frame
total.
final FramePass scenePass = frame.passes.firstWhere(
(FramePass pass) => pass.name == 'scene',
);
final FramePass compositePass = frame.passes.firstWhere(
(FramePass pass) => pass.name == 'composite',
);
final int passDraws = frame.passes.fold<int>(
0,
(int total, FramePass pass) => total + pass.drawCalls,
);
Step 4: Check the report #
The page verifies scene and composite draws, the frame total, triangle and pipeline counts, and the relationship between total CPU time and submission time. A picture can look correct while one of these diagnostic numbers is wrong, so they are checked directly.
if (scenePass.drawCalls < 3 ||
compositePass.drawCalls != 1 ||
frame.drawCalls != passDraws ||
frame.triangles <= 0 ||
frame.pipelineSwitches <= 0 ||
frame.cpuMicros < frame.submitMicros) {
throw StateError('the frame counters do not describe the rendered scene');
}