Open-source project
not-fl3/miniquad avatar
not-fl3/miniquad

Miniquad: one Rust file per platform, on purpose

Cross platform rendering in Rust

2,064 stars244 forksRustApache-2.0

At a glance

What is it?
Miniquad is a cross-platform rendering and windowing abstraction for Rust that keeps every platform backend to roughly one pure Rust file, targeting anything from OpenGL 2.2 to WebGL 1. It trades type-level guarantees for hackability, and its stated non-goals rule out Vulkan and DirectX 12 outright.
Who is it for?
Adopt Miniquad when you want to ship a small Rust program or game to a wide range of hardware without inheriting a large graphics dependency tree, and when you would rather read and patch a backend than work around an abstraction. Its per-platform files and its build instructions for WASM, Android and iOS are the parts that make it practical on its own.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 78 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 6, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The premise: no dependency tree, no abstraction layers

Miniquad states its motivation as a dream of a world where drawing things with a computer does not require a deep dependency tree and thousands of lines of code. That framing explains the design decisions, including the ones that would otherwise look like limitations.

The goal is a graphics abstraction that behaves the same way on any platform with a GPU, kept as light as possible while covering as many machines as possible. Fast compilation is listed as a goal with a number attached: around five seconds from cargo clean for both desktop and web.

The two goals that matter most are hackability and forkability. The README says that when you work on your own game, some hardware incompatibility will turn up, and working around that kind of bug should be easy with implementation details not hidden under layers of abstraction. Forkability is the sharper claim: each platform implementation is usually just one pure Rust file, that file is copy-paste friendly, and it does not use Miniquad-specific abstractions, so you can lift part of a backend and use it standalone.

That is an architectural stance rather than a limitation to work around. It also tells you how to use the library when you outgrow it, which is to stop using it and keep the file you needed.

The repository reflects the same economy: a src/ directory, a js/ directory for the web loader, a java/ directory for the Android side, a shell.nix for NixOS, and an examples/ folder carrying twelve runnable samples.

The platform matrix, and what the old floor costs you

The supported platforms list is the most informative part of the README, because it names the graphics API per platform rather than claiming universal coverage.

Windows covers OpenGL 3 and OpenGL 2.2. Linux covers OpenGL 2.2, OpenGL 3, GLES 2 and GLES 3. macOS covers OpenGL 3 and Metal, and iOS covers GLES 2, GLES 3 and Metal. WASM targets WebGL 1, and the README states that path is tested on iOS Safari, Firefox and Chrome. Android covers GLES 2 and GLES 3.

Two things follow. The lowest common denominator across the whole list is GLES 2 or its desktop equivalent, OpenGL 2.2, which means code written to the minimum runs everywhere but cannot use anything newer. And WebGL 1 is the ceiling on the web, not the floor, which is a real constraint for browser targets.

Metal on Apple platforms is the other datapoint. A library that presents one abstraction across OpenGL, GLES, Metal and WebGL is translating between shading models, and the examples directory shows how far that goes: instancing.rs, post_processing.rs, msaa_render_texture.rs, offscreen.rs and mouse_cursor.rs.

High-end APIs are explicitly declined. Under non-goals the README rules out ultimate type safety, in the sense that correct GPU state is not guaranteed by the type system, and it rules out Vulkan and DirectX 12, pointing instead at gfx-rs and vulkano.

First build on Linux and Windows

Running an example is one command on a platform where the toolchain is already set up:

bash
cargo run --example quad

On NixOS the README points at shell.nix, which starts a development environment with the right dependencies already present. That file is a small but real convenience, since the alternative on Nix is discovering what is missing one error at a time.

Windows needs a target added first, and both toolchains are supported:

bash
rustup target add x86_64-pc-windows-msvc
# or
rustup target add x86_64-pc-windows-gnu

cargo run --example quad

The second form is what the cross-compilation section relies on, since it notes that this is how Windows builds are tested from a Linux machine:

bash
rustup target add x86_64-pc-windows-gnu
cargo run --example quad --target x86_64-pc-windows-gnu

There is a continuous integration workflow named Cross-compile in the repository, which matches that practice: builds for several targets are verified rather than assumed.

The crate itself is modest in metadata. Edition 2018, rust-version 1.70, keywords of graphics, 3D, gamedev and windowing, and per-target dependencies that are the platform APIs directly: libc on Linux, winapi on Windows, libc plus ndk-sys on Android, and objc-rs on the Apple targets.

Web builds need a host page and a static server

The web path is two steps: compile to wasm, then serve an HTML page that loads it.

bash
rustup target add wasm32-unknown-unknown
cargo build --example quad --target wasm32-unknown-unknown

The loader side is the part that trips people up. The README supplies a complete index.html, and its structure is the minimum needed: a canvas element with id glcanvas, a script tag pointing at a minified and statically hosted copy of js/gl.js from the repository, and one call that starts the module.

html
<canvas id="glcanvas" tabindex='1'></canvas>
<script src="https://not-fl3.github.io/miniquad-samples/gl.js"></script>
<script>load("quad.wasm");</script>

Note what that means in practice: the page fetches the loader from a hosted URL, so a production deployment would host its own copy rather than depending on someone else's project pages. The style block is only there to make the canvas fill the viewport.

Serving the files is equally manual. The README points at installing basic-http-server with cargo install and then running it against the directory, and it is worth being explicit that a plain file open will not work here, because WebGL modules and wasm need to be fetched over http.

There is also a mandelbrot example built on the library, hosted with a web demo, which is a more realistic test of throughput than the quad sample.

Android through Docker, iOS through a hand-built bundle

Mobile is where the lightweight design pays off most, because the platform tooling is usually the heaviest part of a project.

Android is recommended through Docker, using a slightly modified cargo-apk that mounts the current directory into the image:

bash
docker run --rm -v $(pwd)":/root/src" -w /root/src notfl3/cargo-apk cargo quad-apk build --example quad

The result lands at target/android-artifacts/(debug|release)/apk. There is one detail worth knowing: with the log-impl feature enabled, log calls are forwarded to the adb console, and the README states that no code modifications are required for Android.

That feature is disabled by default in Cargo.toml, so opting into visible logging on Android is a deliberate step. That is the right default for a library, and it also means that debugging on device is something you enable rather than something you get.

iOS is handled by building for the simulator target and assembling an app bundle by hand, writing an Info.plist with the executable, identifier, name and version keys, then installing and launching it through simctl.

For real devices the README defers to the macroquad documentation on provisioning, which is a reasonable division of labour: the library covers the bundle mechanics, and code signing is a platform problem rather than a graphics problem.

Why it says no to things you might want

Non-goals are more informative than feature lists in a library this small, and Miniquad states two of them directly.

The first is type-level safety of GPU state. The README is precise: the library should be entirely safe in Rust's sense, with no undefined behaviour or memory unsafety, but correct GPU state is not guaranteed by the types. It then invites users to provide that abstraction in their own code.

That division of labour is the point. Memory safety is enforced by the language, and the part that would require a large state machine to enforce is left to you, where it stays small enough to understand.

The second is the high-end API tier. Vulkan and DirectX 12 are named and declined, with gfx-rs and vulkano given as the places to go instead.

Both non-goals have a cost you should price before choosing. Without a state abstraction you can write a pipeline that compiles and produces nothing, and the failure will be at draw time on one driver. Without Vulkan support you lose compute shaders, modern binding models and the newer features that a Metal or Vulkan-targeting project would eventually need.

In exchange you get a build that takes about five seconds from clean, a dependency tree the project set out to avoid, and platform backends you can read in one sitting. That trade is good for a jam game, a tool, a teaching example or a small shipping app, and wrong for an engine.

Terms, version numbers and the build profile

Miniquad is dual licensed, MIT OR Apache-2.0, with both texts at the top of the repository as LICENSE-MIT and LICENSE-APACHE, and that choice suits a library which invites you to copy parts of its source into your own code: MIT is short and Apache-2.0 carries an explicit patent grant.

The version metadata is where the friction is. Cargo.toml declares version 0.4.11, while the documentation badge in the README points at docs.rs with version 0.3.13. Those are different versions, and the badge is what a reader sees first, so the hosted documentation you land on from the README may not describe the API you have. Pin a version and read the docs for that version rather than the default landing page.

The published build profile in Cargo.toml is tuned for size rather than build speed, which follows from the goals. It enables link-time optimisation, sets opt-level to s, uses a single codegen unit, aborts on panic, disables incremental builds and strips symbols. That gives a smaller, slower-to-compile binary, which is the right trade for something shipping to phones and browsers and the wrong trade if you are iterating on your own game all day. That tension is by design, and it explains why the five-second clean-build claim and the tuned profile both appear in the same README.

Toolchain requirements are modest and old: edition 2018 with rust-version 1.70, which matters if your project pins a current toolchain.

The project is community-run, with Discord and a Matrix room under quad-general, and its sponsor list names SourceGear.

Editorial conclusion

Adopt Miniquad when you want to ship a small Rust program or game to a wide range of hardware without inheriting a large graphics dependency tree, and when you would rather read and patch a backend than work around an abstraction. Its per-platform files and its build instructions for WASM, Android and iOS are the parts that make it practical on its own. Do not adopt it for a project that needs Vulkan, DirectX 12 or a modern high-end API: the README puts those in the non-goals and points at gfx-rs and vulkano instead, and it also declines type-level guarantees about GPU state, so correctness there is on you. Verify four things first. Check which version you actually get, because Cargo.toml declares 0.4.11 while the documentation badge in the README points at 0.3.13 on docs.rs, so read the docs for the version you depend on. Confirm your minimum toolchain, since rust-version is 1.70 and edition 2018, which is old enough to matter on a modern pinned toolchain. Decide whether GLES 2 is an acceptable floor for your Android and embedded targets. And read the build profile in Cargo.toml, because link-time optimisation, a single codegen unit and size optimisation tell you the library is built for small binaries over fast builds. Start with cargo run --example quad on Linux and read the examples directory, since instancing.rs and post_processing.rs show what the abstraction does and does not cover.

Frequently asked questions

How do I build a Miniquad example for the web?

Add the wasm32-unknown-unknown target and run cargo build --example quad --target wasm32-unknown-unknown. You then serve an HTML page with a glcanvas element, a script tag for gl.js and a call to load("quad.wasm"), over http, using a server such as basic-http-server.

How do I build Miniquad for Android?

The recommended path is Docker with the notfl3/cargo-apk image and the quad-apk command, which produces an APK under target/android-artifacts/(debug|release)/apk. Enabling the log-impl feature forwards log output to the adb console with no code changes.

Does Miniquad support Vulkan or DirectX 12?

No, and that is stated as a non-goal. The README points to gfx-rs and vulkano for high-end APIs, and also notes that correct GPU state is not guaranteed by the type system, with safety abstractions left to user code.

What licence does Miniquad use?

Dual licensed, MIT OR Apache-2.0, with both texts in the repository root. The project also encourages copying parts of a platform backend into your own project, which the forkability goal makes explicit.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. not-fl3/miniquad on GitHub
  4. README