Files
ai-app/rigs/gpu-probe/src/main.rs
T
irisandClaude Fable 5.1 d01c105037 iris: stop requesting compute-shader limits nothing uses
adapter.request_device asked for Limits::default(), which requests
desktop-tier compute-shader limits unconditionally even though nothing in
iris/iris-core creates a ComputePipeline or writes a @compute stage. That
crashed device creation outright on a downlevel GL adapter reporting
OpenGL ES 3.0 (no compute at all) -- the Android emulator's
EMU_GPU=software/force-gles path, and any real GLES-3.0-only device.

New iris_core::device_limits(), shared by both platform backends, zeros
exactly the six max_compute_* fields rather than switching to a downlevel
Limits preset -- downlevel_webgl2_defaults() also zeros
max_storage_buffers_per_shader_stage, which shader.wgsl's vertex stage
needs. rigs/gpu-probe's own mirrored limits were updated to match.

Not verified against the actual SwiftShader-ES-3.0 crash on-device this
pass: the cold boot needed would have force-restarted this checkout's
emulator while another session had its own app running on it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 21:18:30 -04:00

149 lines
5.7 KiB
Rust

//! Ask a device whether it can give iris the GPU it asks for.
//!
//! Until 2026-09-04 iris's renderer bound every texture it had drawn as one
//! binding array and indexed it non-uniformly from the shader, which needed
//! descriptor indexing and a very large per-stage binding-array limit
//! (101,000 elements: 100,000 textures and 1,000 samplers,
//! `UiLimits::default`). That was ordinary on a desktop and, per
//! TEXTURES.md's "iris's binding array does not survive real Android
//! hardware", not available on a real share of Android GPUs -- and it failed
//! outright on this emulator's software Vulkan, which is what this rig
//! caught first. iris now asks for nothing beyond wgpu's own defaults (see
//! `iris/src/default/render.rs`): the glyph atlas is one `texture_2d_array`
//! and a standalone image is its own ordinary bind group, and neither needs
//! descriptor indexing. This rig still asks `request_device` for exactly
//! what iris asks for, so it keeps being the answer to "does iris's actual
//! device request succeed here" rather than a guess from reading the code.
//!
//! It runs as a plain executable with no window and no APK, because
//! `request_adapter` needs no surface -- so it can be pushed to a device with
//! `adb push` and run from `/data/local/tmp`, which is far cheaper than an
//! app. What it therefore cannot answer is anything about presenting to a
//! surface; that is the Android backend's own problem.
mod vk;
use wgpu::*;
/// What `iris/src/default/render.rs` asks `request_device` for, now that the
/// binding array is gone: nothing beyond wgpu's own default feature set.
fn iris_features() -> Features {
Features::empty()
}
/// The one non-default limit iris asks for -- unrelated to the binding array,
/// kept for the big storage buffers behind rects/glyphs.
const IRIS_MAX_BUFFER_SIZE: u64 = 1 << 30;
/// Mirrors `iris_core::device_limits()` (`iris/core/src/render/mod.rs`) --
/// cannot call it directly, since this rig is deliberately its own crate,
/// not a workspace member (this file's own Cargo.toml comment). Keep the
/// two in sync by hand when one changes; this rig's whole purpose is "does
/// the device iris actually builds come back," so a stale copy here would
/// silently stop answering that question. Zeroed rather than left at
/// `Limits::default()`'s desktop-tier values because nothing in iris
/// creates a `ComputePipeline` or a `@compute` shader stage -- found by
/// grepping the whole `iris`/`iris-core` tree before this rig's comment was
/// written -- and the unconditional default request is what crashed
/// `request_device` on the Android emulator's software GL path
/// (`EMU_GPU=software`, `force-gles`: SwiftShader's GL reports itself as
/// OpenGL ES 3.0, which has no compute shaders at all, so the adapter's
/// real limit is 0). The same would happen on a real GLES-3.0-only Android
/// device.
fn iris_limits() -> Limits {
Limits {
max_buffer_size: IRIS_MAX_BUFFER_SIZE,
max_compute_workgroup_storage_size: 0,
max_compute_invocations_per_workgroup: 0,
max_compute_workgroup_size_x: 0,
max_compute_workgroup_size_y: 0,
max_compute_workgroup_size_z: 0,
max_compute_workgroups_per_dimension: 0,
..Default::default()
}
}
fn main() {
vk::report();
let instance = Instance::new(&InstanceDescriptor {
backends: Backends::from_env().unwrap_or(Backends::PRIMARY),
..Default::default()
});
let adapters = pollster::block_on(instance.enumerate_adapters(Backends::all()));
println!("adapters: {}", adapters.len());
for adapter in &adapters {
let info = adapter.get_info();
println!(
" {:?} {} ({:?})",
info.backend, info.name, info.device_type
);
}
let Some(adapter) = pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
power_preference: PowerPreference::default(),
compatible_surface: None,
force_fallback_adapter: false,
}))
.ok() else {
println!("\nNO ADAPTER");
std::process::exit(1);
};
let info = adapter.get_info();
println!(
"\nchosen: {:?} {} ({:?})",
info.backend, info.name, info.device_type
);
println!("driver: {} {}", info.driver, info.driver_info);
let have = adapter.features();
println!("\nfeatures iris requires:");
let mut missing = Features::empty();
for f in iris_features().iter() {
let ok = have.contains(f);
println!(
" {:60} {}",
format!("{f:?}"),
if ok { "yes" } else { "NO" }
);
if !ok {
missing |= f;
}
}
let limits = adapter.limits();
println!("\nlimits iris requires:");
println!(
" {:52} want {:>7} have {:>7} {}",
"max_buffer_size",
IRIS_MAX_BUFFER_SIZE,
limits.max_buffer_size,
if limits.max_buffer_size >= IRIS_MAX_BUFFER_SIZE {
"ok"
} else {
"TOO SMALL"
}
);
// The question that actually matters: does the device iris builds come
// back, or does wgpu refuse it? With no features and no binding-array
// limits requested, this is expected to succeed everywhere -- this rig
// is what turned that from an assumption into a measurement, first on
// this emulator's software Vulkan.
let wanted = iris_limits();
match pollster::block_on(adapter.request_device(&DeviceDescriptor {
required_features: iris_features(),
required_limits: wanted,
..Default::default()
})) {
Ok(_) => println!("\nIRIS DEVICE: ok"),
Err(e) => println!("\nIRIS DEVICE: FAILED -- {e}"),
}
if !missing.is_empty() {
println!("\nmissing features: {missing:?}");
}
}