Frame timing and memory pressure
Open the live demo · Read the source · View on GitHub
Three small instruments an application reaches for once it is running for real: how long the last frame actually took, what a window of frames cost on average and at their worst, and giving memory back when the platform says it is short of it.
Step 1: The frame clock #
FrameClock.tick measures the wall between calls. The very first tick
answers nought rather than a guessed sixtieth of a second, because nothing
has actually been measured yet.
static (String, double) _clockLine() {
final clock = FrameClock();
final first = clock.tick();
clock.secondsSince(const Duration(milliseconds: 16));
clock.secondsSince(const Duration(milliseconds: 32));
return (
'first tick: ${first}s, elapsed after two frames: '
'${clock.elapsed.toStringAsFixed(3)}s',
first,
);
}
Step 2: A window of frame costs #
FrameTimingLog.note takes a build duration and a raster duration and
returns a summary line once its window of frames is full, and null on every
frame before that.
static String? _timingLine() {
final log = FrameTimingLog(label: 'demo', window: 2);
log.note(
build: const Duration(milliseconds: 4),
raster: const Duration(milliseconds: 6),
);
return log.note(
build: const Duration(milliseconds: 8),
raster: const Duration(milliseconds: 10),
);
}
Step 3: Give memory back #
MemoryPressureRelease calls Renderer.releaseTransientTargets when the
platform warns about memory. Pooled render targets otherwise settle at a
high-water mark and stay there for the rest of the session.
// What a memory warning does: give the renderer's pooled render targets
// back. `MemoryPressureRelease` calls exactly this when the platform
// warns; calling it directly here proves the release itself.
context.renderer.releaseTransientTargets();