Library / SDK
parasyte/pixels avatar
parasyte/pixels

pixels: a tiny GPU frame buffer for 2D prototypes and emulators

A tiny hardware-accelerated pixel frame buffer. ๐Ÿฆ€

2,146 stars147 forksRustMIT

At a glance

What is it?
pixels is a tiny hardware-accelerated pixel frame buffer in Rust, built on wgpu, for 2D prototypes, pixel animations, software renderers and emulators. You keep the window, the event loop and the input handling, and its workspace deliberately pins the dependency to the published crate.
Who is it for?
pixels is the right pick for a Rust developer who already owns a window stack and cares about integer scale scaling or hand written WGSL shaders; it is the wrong pick for anyone who wants the window bundled with the frame buffer, and for browser work beyond the experimental webgl path.
Can I use it commercially?
Yes. MIT 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 61 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 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A frame buffer, not a windowing framework

Rust developers reach for pixels when they want pixels on a screen quickly: a simple 2D game, pixel-based animations, a software renderer, or an emulator for a platform they care about. Once the first sprite moves, the plan is to attach shaders that simulate a CRT or otherwise dress up the output. The package manifest describes the crate as a tiny library providing a GPU-powered pixel frame buffer, and that adjective is doing real work.

What you get is a frame buffer and not much beside it. The window environment, the event loop and input handling stay with the caller, which is why the examples ship in winit, tao and fltk flavours instead of bundling one windowing backend. If a window stack already exists in your project, that arrangement costs you nothing. If it does not, assembling one is the bulk of the work before anything appears on screen, and no part of pixels does it for you.

wgpu backends decide what the GPU sees

The whole rendering path runs through one dependency, and the manifest pins it hard:

toml
[dependencies]
bytemuck = "1"
raw-window-handle = "0.6"
thiserror = "2"
ultraviolet = "0.10"
wgpu = "29"

That wgpu version is what covers Vulkan, Metal, DirectX 12 and OpenGL ES3 from one crate, and it is also where failures come from, since each backend has its own driver quirks on the host. Around it sit five smaller pieces: bytemuck 1 for turning pixel bytes into buffer data the GPU will accept, ultraviolet 0.10 for the color space conversions that would otherwise be hand written, raw-window-handle 0.6 so you can hand the library a window surface you already own, thiserror 2 for typed errors, and pollster 0.4 to make the asynchronous device calls callable from blocking native code.

Scaling is the reason the crate exists rather than a generic blitter. Hardware accelerated scaling happens only on perfect pixel boundaries, so a magnified image does not blur between texels.

Target selection is explicit too. Browser builds take a different feature set from native ones:

toml
[target.'cfg(target_arch = "wasm32")'.dependencies]
wgpu = { version = "29", features = ["webgl"] }

On everything that is not wasm32 the manifest adds pollster instead, which is how the examples acquire a device without an async main.

The workspace pins pixels to the published crate

One line of the manifest decides what you are really compiling. Workspace members are examples/*, internals/* and run-wasm, and the shared dependency table resolves to two alternative lines:

toml
pixels = "0.17"
pixels = { path = "." }

The first is active and the second is commented out, and the note above them explains the reason: pinning to a published version lets you copy working code out of the examples into your own project, and the example packages that use the workspace version cannot build against the local working copy while development continues. Edit src/ and the examples will not notice, because they resolve pixels from the registry instead. A close reading of that note prevents an afternoon of debugging a change that was never compiled.

Internals is not a private dump either. pixels-mocks sits there as a path dependency for dev, which is how the library can be tested without a real adapter. The published package include list keeps /Cargo.*, /README.md, /img/pixels.png, /shaders/*.wgsl and /src/**/*, so the WGSL shader files travel inside the crate rather than staying behind in the repository. Lift a shader out of the custom-shader example into your own project and it still resolves, so the WGSL sources you copy keep working.

Running the minimal winit example with trace logging

The crate is on crates.io and the documentation lives at the docs.rs page the repository points to. No dependency snippet for a fresh project appears in the README, so for a first real run take the repository instead: every example is a workspace member, which means each one is a Cargo package that can be started by name from the root.

bash
$ RUST_LOG=trace cargo run --package minimal-winit

That builds the winit example and sends trace level logging to stderr, which is the quickest way to see whether a window opened and whether an adapter was acquired at all. A second form exists because the same logging only appears outside debug builds when default features are switched off:

bash
$ RUST_LOG=trace cargo run --package minimal-winit --release --no-default-features

What lands on screen is one accelerated buffer that you size yourself, with no renderer, no scene graph and no text. When you want text or panels, two examples show ready integrations, a Dear ImGui example and an Egui example, both driven by winit. They are demos, not library features, so the code that wires them into your loop is yours to keep.

Outdated graphics drivers break the examples first

The failure mode is not exotic. The most common issue is an outdated graphics driver on the host machine, and there is a concrete reason a working example can go dark on a laptop: pixels requests a low power GPU by default, which means the integrated chip rather than the discrete one. Setting the WGPU_POWER_PREF=high environment variable is the first move, since it moves that request to the high power adapter and settles a large share of driver level problems without a single line of code changing.

Logging is the second lever. RUST_LOG goes through env_logger, and trace level dumps everything to stderr, naming the backend that wgpu actually chose on that machine. Keep graphics drivers current if the machine carries an older Intel integrated GPU, because the notes warn plainly that some drivers and GPUs are end of life and will not be supported. That bound matters: nothing inside pixels can talk to a GPU that its own driver has abandoned, and no amount of Rust code changes the outcome.

WebGL2 is demoed and unfinished at once

Two claims in the same file sit in tension, and both matter before you commit. The features list marks DirectX 11, WebGL2 and WebGPU support as a work in progress, and marks support for non-square pixel aspect ratios the same way. Yet the examples directory ships a minimal WebGL2 example, and the manifest already wires wasm32 builds to a wgpu carrying the webgl feature, so the browser path exists in code long before it is finished.

Treat that as a boundary rather than a defect. What is claimed as supported today is the four backends named above, and even there the work in progress tags mean the edges are still moving. A browser deployment has to treat webgl as experimental. An aspect ratio other than square, the normal case for arcade and emulator content, is exactly the case nobody has finished. WebGL2 and WebGPU are separate targets as well: the feature enabled for wasm32 is webgl, so a plan built on WebGPU in the browser needs another dependency choice and more patience than one built on Vulkan or Metal on the desktop.

Minifb and softbuffer cut the work differently

Two crates are compared directly in the README, and the comparison is about where the work goes rather than about feature counts.

minifb shares the goal of rapid 2D prototyping and takes the opposite side of the windowing question: it brings its own window and GUI management, event loop and input handling, so you write less before the first frame. The cost sits in the rendering path. It has no hardware acceleration, except on macOS where it uses Metal and that choice is not configurable. Its advantage is a much smaller dependency tree.

softbuffer covers similar capabilities and is intentionally limited to software-only rasterization. When your workload is blitting a small buffer at modest resolutions, software is adequate and its output is predictable across machines, which a GPU path is not.

The decision rule is short. Take pixels when boundary-exact scaling and your own shaders matter and a window stack already exists. Take a bundled shell when skipping the windowing work matters more than controlling the adapter.

No unsafe code and an explicit MSRV floor

Two constraints live in the metadata rather than in the prose. The badge at the top of the README is an unsafe forbidden badge pointing at safety-dance, so the policy is that the library contains no unsafe code, and moving pixel bytes toward the driver happens through safe abstractions such as bytemuck. For a crate that sits between your own buffer and a graphics driver, that is a guarantee you can audit, and it also obliges the author to keep finding safe routes through the same jobs.

The second constraint is the toolchain floor. The manifest declares edition 2024 and rust-version 1.88.0, and the stated policy is that the Minimum Supported Rust Version is always available in the MSRV.md file on GitHub. For an adopter the consequence is blunt: anything older than 1.88.0 will not build the crate, and on a repository pinned to an older toolchain pixels is out of reach until that pin moves. The licence identifier in the manifest is MIT, so linking the library into a closed source application carries no copyleft obligation, while the terms of the five dependencies listed earlier stay a separate question for anything you ship. The last push was on 2026-08-23, and the pinned 0.17 line has moved since April.

Editorial conclusion

pixels is the right pick for a Rust developer who already owns a window stack and cares about integer scale scaling or hand written WGSL shaders; it is the wrong pick for anyone who wants the window bundled with the frame buffer, and for browser work beyond the experimental webgl path. Before adopting it, run the minimal-winit example on the machine you ship from, confirm the toolchain is at or above rust-version 1.88.0, and check how non-square pixels behave, since aspect ratio support is still marked unfinished.

Frequently asked questions

Does pixels create a window and handle input for me?

No. It positions itself as more than a library to push pixels to a screen but less than a full framework, and window environment, event loop and input handling are yours to manage. That is why the examples cover winit, tao and fltk instead of one bundled backend.

Why does a pixels example not work on my machine?

The most common cause given is an outdated graphics driver on the host machine, and pixels requests a low power integrated GPU by default. Setting the WGPU_POWER_PREF=high environment variable is the first thing to try, and the RUST_LOG variable through env_logger shows which backend was chosen.

Can I build pixels for the browser?

The manifest has a wasm32 target that pulls in wgpu with the webgl feature, and the repository ships a minimal WebGL2 example. The features list still marks WebGL2 support as a work in progress, alongside DirectX 11 and WebGPU, so treat the browser path as unfinished.

Why does my change to the pixels source not affect the examples?

The workspace pins the dependency to a published version with pixels = "0.17" so that code copied from the examples keeps working elsewhere. The commented alternative pixels = { path = "." } switches the example packages onto your local checkout while you develop.

What is the minimum Rust version pixels needs?

The manifest declares rust-version 1.88.0 on edition 2024. The stated policy is that the Minimum Supported Rust Version is always kept in the MSRV.md file on GitHub, so an older toolchain will not build the crate until it is updated.

Does pixels use hardware acceleration, and how does it scale?

It renders through wgpu across Vulkan, Metal, DirectX 12 and OpenGL ES3, and hardware accelerated scaling is applied only on perfect pixel boundaries so magnified images stay sharp. Support for non-square pixel aspect ratios is still marked as work in progress.

Official sources

  1. License: MIT
  2. parasyte/pixels on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/parasyte-pixels.svg)](https://hysenlabs.com/projects/parasyte-pixels)

Was this useful?