flutter3d
Showcase Changelog 38 packages API reference

What a frame reports

since 0.7.0 Scene and geometry

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');
}