iris: the Android renderer falls back to GLES, and every failure reports
The bench app crash-looped on this checkout's emulator with the default
features (RUST.md's queue item). Not the surface lifecycle and not "once
backgrounded": a build without `force-gles` never got a first frame.
`AndroidRenderer::new` asked wgpu for `Backends::PRIMARY`, which does not
contain `GL`, and this emulator advertises a Vulkan ICD with no adapter
behind it -- `NotFound { active_backends: VULKAN, no_adapter_backends:
VULKAN, supported_backends: VULKAN | GL }`, `.expect`ed, so SIGABRT, so
the launcher restarts it. iris was refusing a device whose only usable
adapter is a GLES one.
It now probes for a `PRIMARY` adapter and rebuilds the instance on
`Backends::GL` when there is none. The probe runs on an instance that
never touches the window on purpose: **an Android window can be
connected to one graphics API only**, so one instance carrying both
backends fails worse -- measured here on the way to this fix, Vulkan's
`vkCreateAndroidSurfaceKHR` claims the window in `create_surface` and
the GLES surface from the same window then reports `In
Surface::configure / Invalid surface`, aborting a frame later in
`Surface::get_current_texture_view`. Vulkan still wins wherever it has
an adapter (`PowerPreference::None` does not sort, and Vulkan is
enumerated first), so nothing changes on the phone.
Second half, the same rule applied to the whole set: the surface,
adapter and device requests all report through the `Result<Self,
String>` this function already returns, where two of the three used to
panic. `surface_changed` puts that string on screen and in the log
ring, which is what the Result was added for.
Emulator evidence (API 36 x86_64, debug): after, `iris renderer: no
Backends(VULKAN | METAL | DX12 | BROWSER_WEBGPU) adapter on this
device, falling back to GLES` then `new renderer built (Gl)` and
frames. Clean on both the default and a `force-gles` build for the
cases this had no reason to touch: two background/return cycles,
rotation there and back (the `already_live=true` reuse branch), a
background/return after the rotation, and cold starts. Vulkan could not
be exercised here -- that this emulator has no Vulkan adapter is the
defect itself.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
f99ae4c366
commit
85869d02f8
2 files changed
+81
-26
No files matched your search
@@ -8,6 +8,28 @@ capability that moved. Small and trivial changes do not go here.
|
||||
An entry gives the date, what changed, why, and a short before/after where
|
||||
it helps judge the change without the session that made it. Newest first.
|
||||
|
||||
## 2026-09-07: iris runs on a GLES-only Android device, and reports the renderer it cannot build
|
||||
|
||||
`AndroidRenderer::new` asked wgpu for `Backends::PRIMARY`, which does not
|
||||
include `GL`. A device that offers a Vulkan driver with no adapter behind
|
||||
it -- this checkout's emulator -- therefore had no adapter at all, and the
|
||||
`.expect` on that turned into a crash loop with nothing on screen. It now
|
||||
probes for a `PRIMARY` adapter first and falls back to `Backends::GL` when
|
||||
there is none, so **Vulkan still wins wherever it has an adapter** and
|
||||
nothing changes on a phone.
|
||||
|
||||
The probe deliberately runs on an instance that never touches the window:
|
||||
an Android window can be connected to one graphics API only, so an
|
||||
instance carrying both backends lets Vulkan claim the window and leaves
|
||||
the GLES surface unusable. That is why this is a second instance rather
|
||||
than one wider `Backends` value.
|
||||
|
||||
The other half a caller sees: `AndroidRenderer::new` already returned
|
||||
`Result<Self, String>`, and now **every** way it can fail goes through
|
||||
that -- no surface, no adapter, no device, as well as the bind-group
|
||||
validation failure it was originally written for. `surface_changed` puts
|
||||
that string on screen and in the log ring instead of aborting.
|
||||
|
||||
## 2026-09-07: `VelocityTracker` takes positions, not deltas
|
||||
|
||||
A flick released at the wrong speed because the tracker averaged. It now
|
||||
|
||||
+59
-26
@@ -88,9 +88,10 @@ pub struct FrameDiagnostics {
|
||||
}
|
||||
|
||||
impl AndroidRenderer {
|
||||
/// `Err` holds a full, human-readable report -- wgpu's own error text
|
||||
/// (`UiRenderNode::new`'s doc comment) plus the adapter identity and
|
||||
/// the limits/downlevel flags bind-group-layout validation checks
|
||||
/// `Err` holds a full, human-readable report for **every** way this
|
||||
/// can fail -- no surface, no adapter, no device, or wgpu's own error
|
||||
/// text (`UiRenderNode::new`'s doc comment) plus the adapter identity
|
||||
/// and the limits/downlevel flags bind-group-layout validation checks
|
||||
/// against -- rather than the panic wgpu's default error handler would
|
||||
/// otherwise raise with no caller able to see it. This is what aborted
|
||||
/// the P0 bench APK on Iris's phone with only "wgpu error: Validation
|
||||
@@ -107,29 +108,67 @@ impl AndroidRenderer {
|
||||
height: u32,
|
||||
content_scale: f32,
|
||||
) -> Result<Self, String> {
|
||||
// `force-gles` (RUST.md's I5 "Where iris's frame time goes") swaps
|
||||
// the software-Vulkan (SwiftShader) path for GLES/virgl on the same
|
||||
// build, to isolate whether the backend itself explains the frame
|
||||
// time gap against Compose. `cfg!` rather than a runtime switch:
|
||||
// there is no way to hand an env var to an already-launched Android
|
||||
// process on this machine (see the feature's doc in Cargo.toml).
|
||||
let backends = if cfg!(feature = "force-gles") {
|
||||
// `force-gles` (RUST.md's I5 "Where iris's frame time goes") pins
|
||||
// the build to GLES, to isolate whether the backend itself explains
|
||||
// the frame time gap against Compose. `cfg!` rather than a runtime
|
||||
// switch: there is no way to hand an env var to an already-launched
|
||||
// Android process on this machine (see the feature's doc in
|
||||
// Cargo.toml).
|
||||
//
|
||||
// Otherwise: **Vulkan where it has an adapter at all, GLES where it
|
||||
// has none.** `Backends::PRIMARY` leaves `GL` out, so a device
|
||||
// offering only a GLES adapter had no adapter at all and this
|
||||
// function aborted the process -- this checkout's emulator, whose
|
||||
// Vulkan ICD carries no adapter behind it (`NotFound {
|
||||
// active_backends: VULKAN, no_adapter_backends: VULKAN,
|
||||
// supported_backends: VULKAN | GL }`), and the crash loop in
|
||||
// RUST.md's queue.
|
||||
//
|
||||
// The choice is made *before any surface exists*, with an instance
|
||||
// that never touches the window, because **an Android window can be
|
||||
// connected to one graphics API only**. One instance carrying both
|
||||
// backends does not work: `create_surface` builds a raw surface per
|
||||
// backend, Vulkan's `vkCreateAndroidSurfaceKHR` claims the window
|
||||
// first, and the GLES surface made from the same window then fails
|
||||
// `configure` as lost -- measured here as "In Surface::configure /
|
||||
// Invalid surface" followed by an abort in
|
||||
// `Surface::get_current_texture_view`, "Surface is not configured
|
||||
// for presentation".
|
||||
let mut backends = if cfg!(feature = "force-gles") {
|
||||
Backends::GL
|
||||
} else {
|
||||
Backends::PRIMARY
|
||||
};
|
||||
let instance = Instance::new(&InstanceDescriptor {
|
||||
let mut instance = Instance::new(&InstanceDescriptor {
|
||||
backends,
|
||||
..Default::default()
|
||||
});
|
||||
// A build already pinned to GLES has nowhere to fall back to.
|
||||
if backends != Backends::GL && instance.enumerate_adapters(backends).block_on().is_empty() {
|
||||
log::warn!(
|
||||
"iris renderer: no {backends:?} adapter on this device, falling back to GLES"
|
||||
);
|
||||
backends = Backends::GL;
|
||||
instance = Instance::new(&InstanceDescriptor {
|
||||
backends,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
// SAFETY: the `NativeWindow` outlives the surface built from it --
|
||||
// android-view drops the old renderer (and this surface with it)
|
||||
// before handing over a new window, in `surface_changed` below.
|
||||
let surface = instance
|
||||
.create_surface(SurfaceTarget::from(AndroidWindowHandle { window }))
|
||||
.expect("Could not create android surface!");
|
||||
.map_err(|error| format!("Could not create the android surface: {error}"))?;
|
||||
|
||||
// Every step from here to a live device reports rather than
|
||||
// panics, for the one reason: on the phone these builds run on
|
||||
// there is no `adb`, so an abort's message reaches a tombstone
|
||||
// nobody can read and the launcher simply restarts the app --
|
||||
// which is what a crash loop with no explanation is. The caller
|
||||
// (`android::view::IrisViewPeer::surface_changed`) puts this
|
||||
// string on screen and in the app's own log ring instead.
|
||||
let adapter = instance
|
||||
.request_adapter(&RequestAdapterOptions {
|
||||
power_preference: PowerPreference::default(),
|
||||
@@ -137,19 +176,7 @@ impl AndroidRenderer {
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.block_on()
|
||||
.expect("Could not get adapter!");
|
||||
|
||||
// Requesting the device itself still panics on failure: that is a
|
||||
// `RequestDeviceError` (a limit or feature the adapter cannot grant
|
||||
// at all), a different and already-diagnosable failure from the one
|
||||
// this function now recovers from -- `RUST.md`'s "Software mode ...
|
||||
// crashes for a third, different reason" is exactly that class, and
|
||||
// its message already names the limit and the requested/allowed
|
||||
// values with no truncation risk (it never reaches wgpu's
|
||||
// uncaptured-error path). What this function's `Result` return
|
||||
// covers is the *next* class of failure: the adapter grants the
|
||||
// device, and validation only fails once a specific bind group
|
||||
// layout is checked against it.
|
||||
.map_err(|error| format!("No usable GPU adapter for backends {backends:?}: {error}"))?;
|
||||
|
||||
// Same request as the winit backend's `UiRenderer::new` -- no
|
||||
// binding-array features, see TEXTURES.md's "Recommended shape".
|
||||
@@ -161,7 +188,13 @@ impl AndroidRenderer {
|
||||
..Default::default()
|
||||
})
|
||||
.block_on()
|
||||
.expect("Could not get device!");
|
||||
.map_err(|error| {
|
||||
format!(
|
||||
"The adapter {} ({:?}) refused a device: {error}",
|
||||
adapter.get_info().name,
|
||||
adapter.get_info().backend,
|
||||
)
|
||||
})?;
|
||||
|
||||
// wgpu's default handler for an error raised outside `UiRenderNode::
|
||||
// new`'s own error scopes (i.e. everything past device creation --
|
||||
|
||||
Reference in new issue
Block a user