Skip to content

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.

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.

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.

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.

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.

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 .hdr loading, 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.

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.

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.

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.