Platform support
Being precise about this is more use to you than a roadmap, so what follows is the state of things rather than the plan. The rendering column is deliberately fussy about the difference between code that compiles and a frame somebody has actually looked at.
| Platform | Simulation, geometry, agents, 2D, networking | Rendering |
|---|---|---|
| macOS | Yes | Yes, and CI draws a frame on every change |
| iOS | Yes | Yes, on the simulator |
| Android | Yes | Yes, including on a handset |
| Linux | Yes | Yes, though not yet on a real GPU |
| Windows | Yes | It builds. Nothing has drawn yet |
| Web | Yes | Yes, in Chrome, with a build step of its own |
Everything in the first column is plain Dart, so it runs anywhere Dart does, including a CI runner with no display. That isn’t a consolation prize. It means the simulation, the rig, the agents, the collision and the sequencer are all unit-testable without a GPU, and most of the engine’s tests do exactly that.
One renderer, six hosts
Section titled “One renderer, six hosts”The renderer itself is portable C++, orblit::Renderer, with nothing Apple in
it. It sits behind a C ABI, orblit_renderer.h, that any host can call: a
Flutter plugin on each platform, or a program with no Flutter at all, which
native/headless proves by drawing offscreen to a PNG. Filament supplies the
backends and the renderer picks one per platform, which is Metal on Apple,
Vulkan first with OpenGL behind it everywhere else, and WebGL on the web.
What differs between platforms is not the renderer. It is how a finished frame reaches Flutter’s compositor, and that varies more than you would hope.
| Platform | How a frame reaches Flutter | Copy per frame |
|---|---|---|
| macOS, iOS | A CVPixelBuffer backed by an IOSurface |
No |
| Android | A native window from Flutter’s SurfaceProducer |
No |
| Linux | An FlPixelBufferTexture |
Yes |
| Windows | A flutter::PixelBufferTexture |
Yes |
| Web | A canvas, laid out as a platform view | No |
The two copies are deliberate rather than careless, and the headers that carry
them say so at length. On Linux the copy-free route wants the name of a GL
texture on Flutter’s own context, which the GTK embedder offers no public way
to obtain. On Windows the route that would work across devices is reported to
crash under Impeller, and that was never reproduced on a running machine, so
it sits in windows/orblit_viewport.h as unrefuted rather than confirmed.
The asset pipeline, platform by platform
Section titled “The asset pipeline, platform by platform”Sprites, splats, scene files, models and textures are newer than the rest of the renderer, and they’ve been run in fewer places. “Seen” below means a frame somebody looked at, or a check that measured one, on that platform.
| Feature | Seen on | Not run on |
|---|---|---|
Bytes by name (OrblitResources) |
macOS, Chrome, the iOS simulator, the Android emulator | Phones, Linux, Windows, Safari, Firefox |
| Sprites | macOS, Chrome | iOS, Android, Linux, Windows, Safari, Firefox |
| Gaussian splats | macOS, Chrome | Phones, Linux, Windows, Safari, Firefox |
| Scene files | Plain Dart, tested on CI. The drawing goes through the same objects, lights and layers as everything else | No platform-by-platform frame check yet |
| Models: clips, skins, variants, FBX and OBJ | macOS, Chrome, the iOS simulator, the Android emulator | Phones, Linux, Windows, Safari, Firefox |
Textures: cooked sets, the texture queue, fromImage |
macOS, Chrome, the iOS simulator, the Android emulator | Phones, Linux, Windows, Android on OpenGL ES, Safari, Firefox |
The Android emulator draws with Vulkan through SwiftShader, a software renderer, so its timings say nothing about a phone. The device tiers’ budgets for splats and textures are starting points for the same reason: no phone or tablet has measured them.
Two limits are specific to a platform rather than untested on it:
- Apple devices can’t take sRGB ASTC. Filament’s Metal backend doesn’t sample it, so a cooked colour texture comes from the BC file in its set on a Mac, and from the ETC2 file on iOS.
- So colour on iOS is ETC1 quality for now. The cooker makes its ETC2 files from UASTC, and that only reaches the ETC1 subset of ETC2: 23.6 dB on the Bistro’s cobblestone normal map.
The reference platform, and the one everything else is compared against. CI builds the gallery and runs it until the renderer reports a drawn frame, on every change. Building proves the renderer compiles and links, which is not the thing that breaks. What breaks is a Filament precondition that aborts on the first frame that hits it, in a build that compiled perfectly, so the frame is the gate rather than the build.
iOS shares one implementation with macOS, meaning the same sources, the same surface and the same Metal backend, and it does draw. The gallery’s examples render on the simulator, and CI builds the gallery for it and draws a frame on every change.
It draws with a slimmer surface than macOS does. The standard lit surface
binds twelve samplers, which Filament only allows at feature level 3, and
Filament’s Metal backend grants that level to MTLGPUFamilyApple6 and newer
(A13, so an iPhone 11 or later) and to Apple silicon Macs. The simulator’s
virtual GPU reports MTLGPUFamilyApple2, which is level 2. Below level 3, the
renderer picks a lit surface with nine samplers instead. That keeps every map,
ground blending and decals, and gives up two things: rectangular lights’
shadows, and the irradiance field. It says so in the scene’s notes if a scene
asks for either.
A current iPhone or iPad would report level 3 and get the standard surface. That hasn’t been run, because it needs a signing identity, which is why the simulator is what CI checks.
Android
Section titled “Android”A Kotlin and JNI plugin over the same C ABI, presenting into a texture from
Flutter’s SurfaceProducer. Vulkan is the default and reaches feature level 3,
so Android gets the standard surface rather than the slimmer one.
It has been seen to draw on a handset rather than only on an emulator: a Galaxy S24+ running Android 16, on Vulkan, at feature level 3, with the engine up in 79 milliseconds. CI builds the APK on every change but does not run it, because the runner has neither a device nor an emulator, so the on-device check is still done by hand.
A GTK plugin under linux/, putting the same portable core behind Flutter’s
GTK embedder. Vulkan where a driver answers, and OpenGL behind it.
It draws, and the honest caveat is where. It has been exercised in a container against Mesa’s software rasterisers, llvmpipe answering OpenGL and lavapipe answering Vulkan, which is enough to prove the plugin, the bindings and the buffer sizes, and nothing at all about a real driver. There is no Linux job in CI yet either, which is the cheapest gap on this page to close.
Windows
Section titled “Windows”A Win32 plugin, the same core again, speaking the same channel with the same
method names and wire shapes, so OrblitView and every other Dart caller works
there unchanged.
Nothing has drawn a frame on it. There is no Windows machine behind this work, so CI is the only compiler, and a green Windows job means it compiles, links and bundles. GitHub’s Windows runners have no GPU, no Vulkan driver, and an OpenGL path well below what the standard surface needs, so the frame check there reports rather than fails. Treat Windows as built and unproven, and if you have a Windows machine and half an hour, that is among the most useful things anyone could hand this project right now.
The web
Section titled “The web”Browsers are in scope, and WebGL 2 is the target for now. The renderer is compiled to WebAssembly and draws into a canvas that Flutter lays out as a platform view, at feature level 1, so the web gets the slimmer surface too.
A browser page has one thread to draw on, and the renderer doesn’t use WebAssembly threads, because they would need the page to be cross-origin isolated and a second Filament build. So the work that would stall a frame goes to Web Workers instead:
- Splat sorting. A cloud is sorted on a worker, and one small enough is sorted on the page because handing it over would cost more. A worker that hasn’t answered in half a second is sorted for on the page instead.
- Decoding. Pictures, Basis and GPU-ready KTX2 textures and environment
pictures are decoded on a pool of workers: half the machine’s threads, at
most four. With twelve 2048² textures or a 2K
.hdrloading, no page task ran over 50 ms in Chrome, where it had been 118 to 206 ms.
That decoder is carried inside orblit_renderer.js, which makes it 679 KB
(440 KB gzipped), and the .wasm is 7.57 MB.
A browser has no disk, so everything a scene names reaches the renderer as
bytes through OrblitResources. A cooked texture set can’t be looked for
beside a file either, so OrblitDeviceProfile.textureCandidates says which
names are worth fetching, best first.
Only Chrome has been tried: headless Chrome with SwiftShader standing in for the GPU, and for the texture work, Chrome in real time. Safari and Firefox haven’t been run at all.
Unlike the others, there is no native build step Flutter knows how to run on
your behalf. Your app’s own web/ directory has to carry orblit_renderer.js
and orblit_renderer.wasm, built by native/web/build.sh against a Filament
built for WebAssembly, with the WebGL 2 materials compiled first. That is the
one manual step left anywhere in the platform list.
Portrait, and small panels
Section titled “Portrait, and small panels”A stated field of view is measured across whichever axis is shorter, so a scene framed on a desktop still frames sensibly on a phone held upright. Before that it was always measured vertically, and a portrait window showed about 46 per cent of the width a desktop window did, which read as the camera having been shoved into the scene.
OrblitDisplay states a panel’s size and shape, and OrblitDisplay.handheld
is a Steam Deck’s 1280 by 800, which is 16:10 rather than the 16:9 most code
assumes. OrblitPipeline.forDisplay moves the three dials that depend on how
many pixels there are rather than on how fast the machine is: the shadow map,
the floor under adaptive resolution, and multisampling.
Consoles
Section titled “Consoles”Reachable in principle through an SDL3 embedder, which is why the core takes no Flutter dependency. That rule is what keeps a native front end a front end, rather than a second engine.
In practice it’s gated on two things that aren’t code: console SDK licensing, and a gap in Filament’s backend support. Neither is close.
Version pins
Section titled “Version pins”| Flutter | 3.47.0 or newer, stable |
| Dart SDK | 3.10.0 or newer |
| Filament | v1.77.0, downloaded on first build |
Filament is pinned rather than tracked. The material format, the compiled shader bundle and the engine’s own API all move between releases, and taking whatever happens to be newest turns an unrelated build red.
