Skip to content

Repository files navigation

FireMonkey Animation Demos

License: MITDelphiFMXSkiaPlatformsGame loopGitHub releaseStars

Repository: still published as omonien/AetherOrbits (unchanged clone URL).
Collection name:FireMonkey Animation Demos — playable samples around one idea: smooth frame loops in Delphi 13.


Why this exists

Since Delphi 13, FireMonkey drives TAnimation through the platform Display Link (VSync). That means you can build buttery-smooth animation loops — including for games — out of the box, without a third-party engine and without fighting a coarse TTimer.

This collection shows that path end-to-end:

PieceRole
TGameLoop (TAnimation subclass)The reusable frame clock — copy one unit into your own app
SkiaHigh-quality 2D / soft-3D drawing on GPU paths where available
DemosFull apps that prove the loop feels right under real load

No black-box game framework. The clock is FMX. The pixels are Skia. Your scene logic stays plain Delphi.


The core unit: copy-and-go game loop

src/FMXAnimation.GameLoop.pas is intentionally demo-agnostic:

  • Depends only on System.* + FMX.Types / FMX.Ani (TAnimation)
  • No forms, no Skia, no scene types
  • Fixed timestep + optional Preferred-FPS pacing on top of Display Link

That is the only unit you need to copy for a VSync-aligned loop in your own app.

Optional shared helpers (used by both demos)

UnitWhen you need it
FMXAnimation.SystemInfoPlatform / backend / CPU labels
FMXAnimation.Stats.HudSkia stats footer overlay
FMXAnimation.DemoShellClient TSkPaintBox, HUD strip split, dark segment chrome, Preferred-FPS restart

To reuse the loop in your project:

  1. Copy FMXAnimation.GameLoop.pas into your FMX app.
  2. Create a TGameLoop, assign OnUpdate / OnRender.
  3. Call StartLoop when the form is shown (Root must be set — parent the loop to the form).
  4. (Optional) Copy DemoShell if you want the same paint-box / chrome patterns as the demos.

Deep dive: docs/GameLoop.md
(Display Link vs Preferred FPS vs fixed simulation timestep, Windows DWM pacing, pitfalls.)

Code quickstart

uses
FMXAnimation.GameLoop;
// Optional for demos-style UI:// FMXAnimation.DemoShell, FMXAnimation.Stats.Hud, FMXAnimation.SystemInfotype
TFormMain = class(TForm)
procedureFormCreate(Sender: TObject);
procedureFormDestroy(Sender: TObject);
procedureFormShow(Sender: TObject);
private
FGameLoop: TGameLoop;
procedureDoUpdate(const ADeltaTime: Double);
procedureDoRender;
end;
procedureTFormMain.FormCreate(Sender: TObject);
begin// Owner = form → Parent is set so Root <> nil (required for Display Link).
FGameLoop := TGameLoop.Create(Self);
FGameLoop.OnUpdate := DoUpdate;
FGameLoop.OnRender := DoRender;
// Optional: FGameLoop.FixedTimeStep := 1 / 60; // simulation step onlyend;
procedureTFormMain.FormShow(Sender: TObject);
begin// Start when visible — not only in OnCreate.ifnot FGameLoop.Running then
FGameLoop.StartLoop;
end;
procedureTFormMain.FormDestroy(Sender: TObject);
begin
FGameLoop.StopLoop;
// FGameLoop is owned by Self; no Free needed if Owner = Selfend;
procedureTFormMain.DoUpdate(const ADeltaTime: Double);
begin// Called 0..N times per display frame with a *fixed* step (default 1/60 s).// Advance the world here — not the pixels://// Player.X := Player.X + Player.SpeedX * ADeltaTime;// ParticleSystem.Step(ADeltaTime);//// Keep this free of drawing code so the sim stays deterministic and testable.end;
procedureTFormMain.DoRender;
begin// Called once per processed frame *after* updates. Only show the result://// PaintBox.Redraw; // draw sprites / HUD / background from current state//// Do not advance physics here — that belongs in DoUpdate.end;

Pitfalls

IssueFix
Scene freezes after first frameRoot was nil — pass the form as Owner (or set Parent) and call StartLoop from OnShow
Preferred FPS 30 does nothing on WindowsSet GlobalPreferredFramesPerSecond (keep PaceToPreferredFps true — default); see docs/GameLoop.md
Simulation too fast/slowChange FixedTimeStep, not Preferred FPS

Demos

Two applications share the same loop + diagnostics. Each is a full FMX project under src/.

DemoProjectWhat it shows
Aether Orbitssrc/AetherOrbits/AetherOrbits.dprojParticle field, glowing orbs, pointer forces, Preferred FPS
Heliossrc/Helios/Helios.dprojSoft-3D solar system, sim speed, pause, trails, click-to-focus

Aether Orbits on Windows — live demo: Preferred FPS bar, orbiting orbs, stats footer

Aether Orbits · ≈8s capture · MP4 · PNG

Using Aether Orbits

ControlAction
Mouse / touch moveRepels nearby particles (soft force field under the pointer)
Click / tapSpawns a short particle burst at the pointer
Preferred FPS (top bar)Request 30, 60, or 120 frames per second
Stats footerLive FPS, frame ms, Preferred value, CPU, platform, render backend

Using Helios

ControlAction
Click / tap a bodySmooth camera focus on that body + info panel
Click empty space / OverviewReset camera to system overview
Speed (0.25× / 1× / 5× / 20×)Simulation time scale
Pause / ResumeFreeze orbits (camera ease still runs)
TrailsToggle orbital trail ribbons
Preferred FPSSame Display Link pacing as Aether Orbits
Stats footerFPS, frame ms, sim speed %, body count, platform, backend

Preferred FPS — what to expect

SettingTypical result
30~30 FPS (paced; useful to save work or compare)
60Default — matches most panels and FMX’s default preferred rate
120Up to 120 only on ProMotion / 120 Hz displays; otherwise capped by the panel

On iPhone 16 (non-Pro) the panel is 60 Hz — choosing 120 will not go above ~60.
On Windows, the OS display link often stays at monitor refresh; the demos pace the game loop so Preferred 30 still measures ~30 FPS in the footer.


Quick start

1. Clone (with tests submodule)

git clone --recurse-submodules https://github.com/omonien/AetherOrbits.git
cd AetherOrbits

If you already cloned without submodules:

git submodule update --init --recursive

2. Open in the IDE

Open FMXAnimationDemos.groupproj in RAD Studio / Delphi 13 (project group for FireMonkey Animation Demos).

  • Aether Orbits: src/AetherOrbits/AetherOrbits.dproj
  • Helios: src/Helios/Helios.dproj
  • Tests (both demos + shared units): tests/AetherOrbits.Tests.dproj

Select a platform (e.g. Win64, OSX64, iOS Device 64-bit) and run.

3. Command-line build (Windows)

.\build-scripts\DelphiBuildDPROJ.ps1 -Project src\AetherOrbits\AetherOrbits.dproj -Platform Win64 -Config Debug
.\build-scripts\DelphiBuildDPROJ.ps1 -Project src\Helios\Helios.dproj -Platform Win64 -Config Debug
.\build-scripts\DelphiBuildDPROJ.ps1 -Project tests\AetherOrbits.Tests.dproj -Platform Win64 -Config Debug
.\build\Win64\Debug\AetherOrbits.Tests.exe

Output goes to build/<Platform>/<Config>/ (git-ignored).


Platforms & backends

PlatformNotes
Windows 64Skia via OpenGL (or GPU path) when PreferRaster is off at startup
macOSMetal enabled for Skia GPU (GlobalUseMetal)
iOSSame Display Link / Metal path; responsive stats HUD for narrow screens

The footer’s Backend line shows what is actually active (e.g. Skia Metal, Skia OpenGL, Skia Raster).

Startup flags that matter are set in each demo’s .dpr (Skia on; Windows PreferRaster off; Metal on for Apple). Details: docs/FMX-Skia-Gotchas.md.


Project layout

AetherOrbits/ # GitHub repo name (stable URL)
├── src/
│ ├── FMXAnimation.GameLoop.pas # ★ shared frame loop — copy this
│ ├── FMXAnimation.SystemInfo.pas # shared diagnostics
│ ├── FMXAnimation.Stats.Hud.pas # shared stats overlay
│ ├── FMXAnimation.DemoShell.pas # shared paint-box / chrome helpers
│ ├── AetherOrbits/ # demo: orbs + particles
│ └── Helios/ # demo: solar system
├── tests/ # DUnitX (shared + both demos)
├── build-scripts/
├── docs/
│ ├── GameLoop.md # loop design & FPS concepts
│ ├── FMX-Skia-Gotchas.md
│ └── images/
├── libs/DUnitX/ # Git submodule
├── FMXAnimationDemos.groupproj
├── LICENSE # MIT
└── README.md

Architecture (short)

Separation of concerns is intentional — each layer has one job:

TGameLoop (Display Link) ← frame clock only
│ OnUpdate → *.Scene.Update ← simulation / world state (no Skia)
│ OnRender → PaintBox.Redraw
└─ OnDraw → *.Scene.Renderer ← Skia paint of current state
→ Stats.Hud ← diagnostics strip
Main.Form ← FMX controls + wiring only
DemoShell ← optional shared surface/chrome helpers
LayerUnitRole
Timingsrc/FMXAnimation.GameLoopDisplay Link + fixed timestep + Preferred pacing (shared, copyable)
Diagnosticssrc/FMXAnimation.SystemInfoPlatform, backend, CPU samples (shared)
HUDsrc/FMXAnimation.Stats.HudFooter text + Skia overlay (shared)
Demo shellsrc/FMXAnimation.DemoShellPaint box setup, HUD strip split, dark chip chrome (shared)
SimulationAetherOrbits.Scene / Helios.SceneDemo state — no UI, no Skia
Drawing*.Scene.RendererSkia paint of scene state
Form UI*.Main.FormForm events, demo-specific controls, scene wiring

Skia draws pixels; it does not own the frame clock. The loop does not know about orbs, planets, or Skia.


Requirements

  • Delphi 13 (Florence) or newer — Display Link–driven TAnimation
  • Integrated Skia for the demo renderers (not required by GameLoop itself)
  • Git with submodule support for DUnitX tests
  • Optional: macOS / iOS deploy targets and signing as usual for FMX

Documentation

DocAudience
docs/GameLoop.mdReusing / understanding TGameLoop
docs/FMX-Skia-Gotchas.mdMetal, PreferRaster, Root, HUD under Skia
docs/Delphi Style Guide EN.mdCoding conventions used in this repo

License

Copyright © 2026 Olaf Monien. Released under the MIT License.

About

FMX + Skia demo for Delphi 13: VSync game loop, particle scene, Preferred FPS — Windows, macOS, iOS

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages