Skip to content

fix(native): gate wgpu-only items on the *selected* renderer, not on any wgpu feature - #5849

Closed
jcuffney wants to merge 1 commit into
DioxusLabs:mainfrom
jcuffney:fix/native-cfg-selected-renderer
Closed

jcuffney wants to merge 1 commit into
DioxusLabs:mainfrom
jcuffney:fix/native-cfg-selected-renderer

Conversation

@jcuffney

@jcuffney jcuffney commented Sep 18, 2026

Copy link
Copy Markdown

The problem

dioxus-native's renderer arms vello-cpu-base, vello-cpu-pixels,
vello-cpu-softbuffer and skia cannot be compiled by anything that depends on
dioxus. Selecting one is a hard compile error, so in practice vello and
vello-hybrid are the only two renderers that exist.

src/dioxus_renderer.rs picks a renderer with a cfg_if chain, in priority
order vellovello-cpu-baseskiavello-hybrid. Only the two wgpu
arms define InnerRendererOptions or re-export Features/Limits — the CPU and
Skia arms have no wgpu to name, which is correct.

But the five places that use those types are gated on
any(feature = "vello", feature = "vello-hybrid"). That predicate asks whether a
wgpu renderer is enabled. The cfg_if asks which renderer was selected.
The two disagree as soon as a CPU or Skia feature is enabled alongside a wgpu
one.

That is not a strange combination to be in — it is the only one available:

  • vello-hybrid is in dioxus-native's default feature set.
  • dioxus's native feature declares dioxus-native without
    default-features = false.
  • Cargo features are additive.

So no downstream crate can turn vello-hybrid off, and asking for
vello-cpu-softbuffer gets both. cfg_if correctly selects the CPU arm, the
any(...) gates fire anyway, and they name items that arm never defined.

default-features = false on a direct dioxus-native dependency does not help
either, because the dioxusdioxus-native edge still carries the defaults.

The change

One predicate, meaning the selected arm is a wgpu arm, mirroring the cfg_if
chain:

any(
    feature = "vello",
    all(feature = "vello-hybrid",
        not(feature = "vello-cpu-base"),
        not(feature = "skia"))
)

applied at dioxus_renderer.rs's with_features_and_limits and at the four
sites in lib.rs (the config bindings, the try_read_config! block, and both
renderer constructors — the last two stay exact complements of each other).

lib.rs's single pub use had to split in two. wgpu_context::DeviceHandle
keeps the original any(vello, vello-hybrid) condition, because that pair is
exactly what gates the optional wgpu_context dependency; only
dioxus_renderer::{Features, Limits} moves to the selected-arm predicate.
Gating both the same way would be wrong in one direction or the other.

Not a breaking change

Features enabled Before After
vello (± anything) vello arm; Features/Limits exported the feature = "vello" disjunct is still true — identical
vello-hybrid alone vello-hybrid arm; exported all(vello-hybrid, not cpu, not skia) is true — identical
vello-cpu-base and/or skia, no wgpu arm cpu/skia arm; nothing exported predicate false — identical
vello-hybrid + vello-cpu-base and/or skia hard compile error compiles; cpu/skia arm; Features/Limits not exported

Only the last row changes, and only from "does not build" to "builds". Every
configuration that compiles today keeps an identical public API.

What I ran

notes/CONTRIBUTING.md asks for cargo test --workspace --tests. I did not run
the full workspace suite — it needs the webkit system dependencies and builds the
whole monorepo. cargo test -p dioxus-native passes but the crate ships no
tests, so it is not evidence of anything; the matrix below is.

cargo check -p dioxus-native --features <arm>, on this branch's base commit and
then on this branch. Defaults left on, since that is what downstream actually
resolves:

--features before after
vello OK OK
vello-hybrid OK OK
vello-cpu-softbuffer E0425, E0432, E0433 OK
vello-cpu-pixels E0425, E0432, E0433 OK
skia E0425, E0432, E0433 OK

The two arms that worked before are unchanged; the three that were hard compile
errors now build. That is the whole behavioural difference. cargo fmt clean.

Why I went looking

On a Pixel 10 (PowerVR D-Series) both wgpu arms are unusable. Release builds
abort on
render_surface.device().poll(wgpu::PollType::wait_indefinitely()).unwrap()
returning Err(Timeout) — the driver accepts the submission and never signals
completion. I have proposed a fix for that separately in
DioxusLabs/anyrender#96. The app survives roughly 2 launches in 8.

The CPU arm sidesteps the driver entirely, because it never opens a wgpu device.
With this patch applied I get 10 launches out of 10, rendering correctly —
layout, fonts, position: fixed, nested routing, theme switching — at ~2–3% of
one core when idle. That is the difference between dioxus-native working on
this device and not, and it was unreachable.

Notes

  • The predicate is repeated at five sites, which works but is not lovely. If you
    would rather have a build.rs cargo::rustc-check-cfg alias, or a single
    internal feature the arms resolve against, say so and I will redo it that way -
    I have no attachment to this shape, only to the five sites agreeing with the
    cfg_if.
  • Happy to add a CI job that checks each arm compiles — a feature matrix is
    exactly what catches this class of bug.

🤖 Generated with Claude Code

`dioxus_renderer.rs` picks a renderer with a `cfg_if` chain - vello,
vello-cpu-base, skia, vello-hybrid - and only the two wgpu arms define
`InnerRendererOptions` or re-export `Features`/`Limits`.

The items that use those types were gated on
`any(feature = "vello", feature = "vello-hybrid")`, which asks whether a
wgpu renderer is *enabled* rather than which one was *selected*. The two
disagree whenever a CPU or Skia feature is on alongside a wgpu one - and
since `vello-hybrid` is a default feature and `dioxus`'s `native` feature
takes `dioxus-native` with its defaults, that is the only arrangement a
consumer of `dioxus` can produce. The CPU and Skia arms were therefore
unreachable downstream.

Replace that predicate with one that mirrors the `cfg_if` chain. The
`DeviceHandle` re-export keeps the original condition, because that pair
is exactly what gates the optional `wgpu_context` dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jcuffney
jcuffney marked this pull request as ready for review September 18, 2026 21:28
@nicoburns

nicoburns commented Sep 21, 2026

Copy link
Copy Markdown
Member

The correct fix for this is adding a mode the CLI that allows you to use the native backend without enabling default features on the dioxus-native crate. With this PR you're going to end up compiling multiple renderers into your binary. It's possible today if you don't directly depend on the dioxus crate (because the CLI special cases this).

@jcuffney jcuffney closed this Sep 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants