//! 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:?}"); } }