Merge remote-tracking branch 'origin/rustify' into worktree-agent-a27094a7db775552a
# Conflicts: # docs/IRIS.md
This commit is contained in:
commit
800da46188
8 files changed
+222
-13
No files matched your search
@@ -7,6 +7,48 @@ marked **DEFERRED** are ones the agent chose not to decide alone.
|
|||||||
|
|
||||||
## 2026-09-05
|
## 2026-09-05
|
||||||
|
|
||||||
|
- **iris no longer asks every device for compute-shader limits it never
|
||||||
|
uses.** `adapter.request_device` (both `iris/src/android/render.rs` and
|
||||||
|
`iris/src/default/render.rs`) used `Limits::default()` plus an override
|
||||||
|
for `max_buffer_size`, and `Limits::default()` unconditionally requests
|
||||||
|
desktop-tier compute limits (`max_compute_workgroups_per_dimension:
|
||||||
|
65535`, per `wgpu_types`) even though nothing in `iris`/`iris-core`
|
||||||
|
creates a `ComputePipeline` or writes a `@compute` shader stage —
|
||||||
|
confirmed by grepping the whole tree, not assumed. That crashed
|
||||||
|
`request_device` outright on the Android emulator's software GL path
|
||||||
|
(`EMU_GPU=software`, `--features 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 against the unconditional request for 65535 —
|
||||||
|
`RUST.md`'s "Software mode ... crashes for a third, different reason,"
|
||||||
|
2026-09-05, earlier today. The same would happen on any real
|
||||||
|
GLES-3.0-only Android device, not just the emulator. Fixed by a new
|
||||||
|
`iris_core::device_limits()` (`iris/core/src/render/mod.rs`), shared by
|
||||||
|
both platform backends so the two requests cannot drift, that zeros the
|
||||||
|
six `max_compute_*` fields explicitly rather than switching to a
|
||||||
|
downlevel `Limits` preset — `Limits::downlevel_webgl2_defaults()` was
|
||||||
|
considered and rejected: it also zeros
|
||||||
|
`max_storage_buffers_per_shader_stage`, and `shader.wgsl`'s vertex stage
|
||||||
|
reads four `var<storage>` buffers (rects, glyphs, masks, move_offsets),
|
||||||
|
so that preset would trade the compute crash for a bind-group-layout
|
||||||
|
one on the same downlevel hardware this is meant to support. No
|
||||||
|
capability check or fallback path was needed since nothing is being
|
||||||
|
disabled — the request is simply narrowed to what the pipeline actually
|
||||||
|
uses. `rigs/gpu-probe`'s own mirrored limits (it is deliberately its own
|
||||||
|
crate, not a workspace member, so it cannot call `device_limits()`
|
||||||
|
directly) were updated to match, and confirm `IRIS DEVICE: ok` against
|
||||||
|
this VM's own Vulkan and GL adapters. **Not verified this pass**: the
|
||||||
|
specific SwiftShader-ES-3.0 crash this fixes, on-device — the
|
||||||
|
`EMU_GPU=software` cold boot this needs would have force-restarted this
|
||||||
|
checkout's emulator while another session was actively running its own
|
||||||
|
app on it (`com.example.aiapp` had window focus at the time), so it was
|
||||||
|
left for a pass when the emulator is free rather than disrupting that
|
||||||
|
session. Everything reachable without the emulator is clean: `cargo
|
||||||
|
fmt`/`clippy --workspace --all-targets`/`test --workspace`, `cargo ndk
|
||||||
|
build`/`clippy` for `iris-android-app` with `force-gles`, and
|
||||||
|
`gpu-probe` against this VM's own Vulkan and GL(ES 3.2, which still has
|
||||||
|
compute and so would not have reproduced the crash even before this
|
||||||
|
fix — not a substitute for the real ES-3.0 test).
|
||||||
|
|
||||||
- **P0's Compose half is built and smoke-tested on the emulator** — the
|
- **P0's Compose half is built and smoke-tested on the emulator** — the
|
||||||
`bench` build type, the shared `app/bench-fixture/` transcript, and an
|
`bench` build type, the shared `app/bench-fixture/` transcript, and an
|
||||||
in-process fake backend (`BenchFixture.kt`/`BenchNetwork.kt`) that
|
in-process fake backend (`BenchFixture.kt`/`BenchNetwork.kt`) that
|
||||||
|
|||||||
@@ -31,6 +31,31 @@ platform handle. `vm`/`view` are independent handles from the ones
|
|||||||
`new_peer` keeps for its own `RequestRedraw` (a fresh `get_java_vm`/
|
`new_peer` keeps for its own `RequestRedraw` (a fresh `get_java_vm`/
|
||||||
`new_global_ref` each), so storing them has no effect on that mechanism.
|
`new_global_ref` each), so storing them has no effect on that mechanism.
|
||||||
|
|
||||||
|
## 2026-09-05 (later still): `iris_core::device_limits()`, and iris no longer requests compute-shader limits
|
||||||
|
|
||||||
|
New public function, `iris_core::device_limits() -> wgpu::Limits`. Why:
|
||||||
|
`adapter.request_device`'s `required_limits` was `Limits::default()` plus
|
||||||
|
a `max_buffer_size` override in both platform backends, and
|
||||||
|
`Limits::default()` requests desktop-tier compute-shader limits
|
||||||
|
unconditionally (`max_compute_workgroups_per_dimension: 65535`) even
|
||||||
|
though nothing in `iris`/`iris-core` uses a `ComputePipeline` — that
|
||||||
|
crashed device creation outright on a downlevel GL adapter reporting
|
||||||
|
OpenGL ES 3.0 (no compute shaders at all: the Android emulator's
|
||||||
|
`EMU_GPU=software` path, and any real GLES-3.0-only Android device).
|
||||||
|
`device_limits()` is what both `android::render::AndroidRenderer::new`
|
||||||
|
and `default::render::UiRenderer::new` now build their `required_limits`
|
||||||
|
from, so the request cannot drift between the two backends.
|
||||||
|
|
||||||
|
Before: `Limits { max_buffer_size: 1 << 30, ..Default::default() }`
|
||||||
|
inlined in each backend. After: `iris_core::device_limits()`, which is
|
||||||
|
the same thing with the six `max_compute_*` fields additionally zeroed.
|
||||||
|
A caller building its own `DeviceDescriptor` outside these two backends
|
||||||
|
(there are none today, but a third platform backend would want this)
|
||||||
|
should call `device_limits()` rather than reaching for
|
||||||
|
`Limits::default()` directly, unless it genuinely adds a compute pass —
|
||||||
|
in which case it wants the specific compute limits that pass needs, not
|
||||||
|
the desktop-tier default for everything.
|
||||||
|
|
||||||
## 2026-09-05 (later the same day): `iris_core::FrameReport` (RUST.md's I5 box)
|
## 2026-09-05 (later the same day): `iris_core::FrameReport` (RUST.md's I5 box)
|
||||||
|
|
||||||
New public type, `iris_core::FrameReport` (re-exported from `iris_core`'s
|
New public type, `iris_core::FrameReport` (re-exported from `iris_core`'s
|
||||||
|
|||||||
@@ -7,6 +7,29 @@ order and what "done" looks like. Tick and date them in place.
|
|||||||
|
|
||||||
## Fix
|
## Fix
|
||||||
|
|
||||||
|
- [x] **`request_device` asked for compute-shader limits it never uses
|
||||||
|
(2026-09-05).** `Limits::default()` (both `iris/src/android/render.rs`
|
||||||
|
and `iris/src/default/render.rs`) requests desktop-tier compute limits
|
||||||
|
unconditionally, even though nothing in `iris`/`iris-core` creates a
|
||||||
|
`ComputePipeline` or writes a `@compute` shader stage — confirmed by
|
||||||
|
grepping the whole tree, not assumed. That crashed device creation
|
||||||
|
outright on the Android emulator's software GL path (`EMU_GPU=software`,
|
||||||
|
`--features force-gles`): SwiftShader's GL reports itself as OpenGL ES
|
||||||
|
3.0, which has no compute shaders, so the adapter's real limit is 0
|
||||||
|
against the unconditional request for 65535 — the same would happen on
|
||||||
|
any real GLES-3.0-only Android device. Fixed by a new, shared
|
||||||
|
`iris_core::device_limits()` (`iris/core/src/render/mod.rs`) that 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 (four `var<storage>` buffers), so that preset would trade
|
||||||
|
this crash for a bind-group-layout one on the same hardware.
|
||||||
|
`rigs/gpu-probe`'s own hand-mirrored `Limits` (it is deliberately its
|
||||||
|
own crate, not able to call `device_limits()` directly) was updated to
|
||||||
|
match. See `DECISIONS.md` and RUST.md's I5 box for the account,
|
||||||
|
including what could not be re-verified on-device this pass (the
|
||||||
|
emulator was in concurrent use by another session).
|
||||||
|
|
||||||
- [x] **Input does not fall through by input type (2026-09-04).**
|
- [x] **Input does not fall through by input type (2026-09-04).**
|
||||||
`SensorUi::run_sensors` (`src/default/sense.rs`) used to set "consumed,
|
`SensorUi::run_sensors` (`src/default/sense.rs`) used to set "consumed,
|
||||||
stop checking lower layers" from mere hover — a widget registered for
|
stop checking lower layers" from mere hover — a widget registered for
|
||||||
|
|||||||
@@ -59,6 +59,21 @@ session spending an afternoon on them again.
|
|||||||
end-to-end this pass. The fix itself is verified by direct, targeted
|
end-to-end this pass. The fix itself is verified by direct, targeted
|
||||||
logcat traces taken before that interference began, not by the
|
logcat traces taken before that interference began, not by the
|
||||||
aggregate script.
|
aggregate script.
|
||||||
|
- **iris no longer requests compute-shader limits it never uses,
|
||||||
|
2026-09-05.** `adapter.request_device`'s `Limits::default()` asks for
|
||||||
|
desktop-tier compute limits unconditionally even though nothing in
|
||||||
|
`iris`/`iris-core` uses a `ComputePipeline` -- confirmed by grep, not
|
||||||
|
assumed -- which is what crashed `request_device` outright under
|
||||||
|
`EMU_GPU=software`'s `force-gles` path (SwiftShader's GL reports OpenGL
|
||||||
|
ES 3.0, no compute at all). New shared `iris_core::device_limits()`
|
||||||
|
zeros exactly the six compute fields; `rigs/gpu-probe`'s own mirrored
|
||||||
|
limits were updated and confirm `IRIS DEVICE: ok` on this VM's own
|
||||||
|
Vulkan and GL adapters. **The specific SwiftShader-ES-3.0 crash this
|
||||||
|
fixes was not re-verified on-device this pass** -- the cold boot needed
|
||||||
|
would have force-restarted this checkout's emulator while another
|
||||||
|
session had its own app focused on it, so it was left rather than
|
||||||
|
disrupted. See this box's "Fixed, 2026-09-05, later the same day"
|
||||||
|
subsection (under the software-mode crash it fixes) and `DECISIONS.md`.
|
||||||
- **Decided 2026-09-05: iris over Masonry**, by Iris, from the host-GPU
|
- **Decided 2026-09-05: iris over Masonry**, by Iris, from the host-GPU
|
||||||
numbers in I5's box and E1/E2's findings. See the Recommendation's item
|
numbers in I5's box and E1/E2's findings. See the Recommendation's item
|
||||||
3 and `DECISIONS.md`. Next: the remaining screens and the app on iris —
|
3 and `DECISIONS.md`. Next: the remaining screens and the app on iris —
|
||||||
@@ -3211,6 +3226,45 @@ silently on real hardware.
|
|||||||
general) explains the ~80-150ms software-mode numbers, since no GLES
|
general) explains the ~80-150ms software-mode numbers, since no GLES
|
||||||
number under software mode could be taken at all.
|
number under software mode could be taken at all.
|
||||||
|
|
||||||
|
**Fixed, 2026-09-05, later the same day.** Not "requesting compute
|
||||||
|
limits only when the adapter reports them" (a capability check with
|
||||||
|
a fallback) -- simpler than that, because iris has no code path that
|
||||||
|
needs compute at all: grepped the whole `iris`/`iris-core` tree for
|
||||||
|
`ComputePipeline`/`@compute` and found none, so the right fix is to
|
||||||
|
stop asking for compute limits, full stop, rather than to build a
|
||||||
|
fallback for a capability nothing uses. `iris_core::device_limits()`
|
||||||
|
(`iris/core/src/render/mod.rs`) is the one place both platform
|
||||||
|
backends now build their `required_limits` from: `Limits::default()`
|
||||||
|
with the six `max_compute_*` fields zeroed and `max_buffer_size`
|
||||||
|
still raised, as before. `Limits::downlevel_webgl2_defaults()` was
|
||||||
|
the first thing tried and rejected -- it also zeros
|
||||||
|
`max_storage_buffers_per_shader_stage`, and `shader.wgsl`'s vertex
|
||||||
|
stage reads four `var<storage>` buffers, so it would have traded
|
||||||
|
this crash for a bind-group-layout one on the same hardware.
|
||||||
|
`rigs/gpu-probe`'s own `Limits` (necessarily a hand-mirrored copy --
|
||||||
|
that rig is deliberately its own crate, not a workspace member) was
|
||||||
|
updated to match and re-run: `IRIS DEVICE: ok` against this VM's own
|
||||||
|
Vulkan (Venus) and GL (virgl, reports OpenGL ES 3.2) adapters.
|
||||||
|
**Not verified against the actual SwiftShader-ES-3.0 failure this
|
||||||
|
pass**: the `EMU_GPU=software` cold boot needed to reproduce it would
|
||||||
|
have force-restarted this checkout's shared emulator while another
|
||||||
|
session had `com.example.aiapp` focused and running on it (`adb
|
||||||
|
shell dumpsys window`), so this pass left that measurement rather
|
||||||
|
than disrupting concurrent work -- matching AGENTS.md's "coordinate
|
||||||
|
with peer agents" guidance rather than contending for the emulator.
|
||||||
|
Everything else: `cargo fmt --all`/`clippy --workspace --all-targets`/
|
||||||
|
`test --workspace` clean, `cargo ndk build`/`clippy` for
|
||||||
|
`iris-android-app --features transcript-screen,force-gles` clean
|
||||||
|
(only the pre-existing unused-`tabs-ui`-dependency warning, unrelated
|
||||||
|
to this change). This also means the software-mode question two
|
||||||
|
boxes up is still open, for the same original reason plus this new
|
||||||
|
one: a GLES number under `EMU_GPU=software` still has not been
|
||||||
|
taken, now blocked on emulator availability rather than on the
|
||||||
|
crash. A future pass should cold-boot `EMU_GPU=software` once the
|
||||||
|
emulator is free, confirm `dev.iris.android.demo` no longer aborts
|
||||||
|
on `request_device`, and take the `iris-scroll.sh` FrameReport row
|
||||||
|
that pairs with this box's host-GPU one.
|
||||||
|
|
||||||
**Verification, this update.** `cargo fmt --all` (no diff),
|
**Verification, this update.** `cargo fmt --all` (no diff),
|
||||||
`cargo clippy --workspace --all-targets` (no warnings from the new
|
`cargo clippy --workspace --all-targets` (no warnings from the new
|
||||||
code; pre-existing `wgpu`/`winit`/`naga` future-incompat notices
|
code; pre-existing `wgpu`/`winit`/`naga` future-incompat notices
|
||||||
|
|||||||
@@ -23,6 +23,48 @@ pub use primitive::*;
|
|||||||
|
|
||||||
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||||
|
|
||||||
|
/// The `wgpu::Limits` both platform backends (`android::render::
|
||||||
|
/// AndroidRenderer::new`, `default::render::UiRenderer::new`) ask
|
||||||
|
/// `Adapter::request_device` for -- shared so the two copies cannot drift,
|
||||||
|
/// per AGENTS.md's "write the logic once."
|
||||||
|
///
|
||||||
|
/// Built from `Limits::default()`, **not** a downlevel variant: the shader
|
||||||
|
/// (`shader.wgsl`) reads four `var<storage>` buffers (rects, glyphs, masks,
|
||||||
|
/// move_offsets) from the vertex stage, and `Limits::downlevel_webgl2_defaults()`
|
||||||
|
/// zeroes `max_storage_buffers_per_shader_stage` along with the compute
|
||||||
|
/// limits below -- switching to it would trade one `request_device` crash
|
||||||
|
/// for a bind-group-layout one on the same downlevel hardware this is meant
|
||||||
|
/// to support. `max_buffer_size` is raised for the growing instance/atlas
|
||||||
|
/// buffers (`ArrBuf`, `GpuTextures`); everything else is `default()`'s
|
||||||
|
/// desktop-tier value, unchanged.
|
||||||
|
///
|
||||||
|
/// The six `max_compute_*` fields are zeroed because nothing in this crate
|
||||||
|
/// creates a `ComputePipeline` or writes a `@compute` shader stage --
|
||||||
|
/// grepped for both across `iris`/`iris-core` before writing this, found
|
||||||
|
/// none. `Limits::default()` requests desktop-tier compute limits
|
||||||
|
/// unconditionally (`max_compute_workgroups_per_dimension: 65535`) even
|
||||||
|
/// though nothing asks a device to actually support compute, which 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, so the adapter's real limit
|
||||||
|
/// is 0 and the unconditional request fails outright
|
||||||
|
/// (`RUST.md`'s "Software mode ... crashes for a third, different reason").
|
||||||
|
/// The same would happen on a real GLES-3.0-only Android device. If a
|
||||||
|
/// future change adds a compute pass, request the specific limits it needs
|
||||||
|
/// here rather than reverting to the desktop-tier default for everything.
|
||||||
|
pub fn device_limits() -> Limits {
|
||||||
|
Limits {
|
||||||
|
max_buffer_size: 1 << 30,
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct UiRenderNode {
|
pub struct UiRenderNode {
|
||||||
uniform_group: BindGroup,
|
uniform_group: BindGroup,
|
||||||
primitive_layout: BindGroupLayout,
|
primitive_layout: BindGroupLayout,
|
||||||
|
|||||||
@@ -86,12 +86,11 @@ impl AndroidRenderer {
|
|||||||
|
|
||||||
// Same request as the winit backend's `UiRenderer::new` -- no
|
// Same request as the winit backend's `UiRenderer::new` -- no
|
||||||
// binding-array features, see TEXTURES.md's "Recommended shape".
|
// binding-array features, see TEXTURES.md's "Recommended shape".
|
||||||
|
// `iris_core::device_limits()` is shared between the two backends;
|
||||||
|
// see its own doc for why it is not simply `Limits::default()`.
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
.request_device(&DeviceDescriptor {
|
.request_device(&DeviceDescriptor {
|
||||||
required_limits: Limits {
|
required_limits: iris_core::device_limits(),
|
||||||
max_buffer_size: 1 << 30,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.block_on()
|
.block_on()
|
||||||
|
|||||||
@@ -102,13 +102,12 @@ impl UiRenderer {
|
|||||||
// needs descriptor indexing. See TEXTURES.md's "Recommended shape"
|
// needs descriptor indexing. See TEXTURES.md's "Recommended shape"
|
||||||
// for why the old binding array asked for
|
// for why the old binding array asked for
|
||||||
// VK_EXT_descriptor_indexing unconditionally and did not survive a
|
// VK_EXT_descriptor_indexing unconditionally and did not survive a
|
||||||
// real share of Android GPUs.
|
// real share of Android GPUs. `iris_core::device_limits()` is
|
||||||
|
// shared with the Android backend; see its own doc for why it is
|
||||||
|
// not simply `Limits::default()`.
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
.request_device(&DeviceDescriptor {
|
.request_device(&DeviceDescriptor {
|
||||||
required_limits: Limits {
|
required_limits: iris_core::device_limits(),
|
||||||
max_buffer_size: 1 << 30,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.block_on()
|
.block_on()
|
||||||
|
|||||||
@@ -35,6 +35,34 @@ fn iris_features() -> Features {
|
|||||||
/// kept for the big storage buffers behind rects/glyphs.
|
/// kept for the big storage buffers behind rects/glyphs.
|
||||||
const IRIS_MAX_BUFFER_SIZE: u64 = 1 << 30;
|
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() {
|
fn main() {
|
||||||
vk::report();
|
vk::report();
|
||||||
|
|
||||||
@@ -104,10 +132,7 @@ fn main() {
|
|||||||
// limits requested, this is expected to succeed everywhere -- this rig
|
// limits requested, this is expected to succeed everywhere -- this rig
|
||||||
// is what turned that from an assumption into a measurement, first on
|
// is what turned that from an assumption into a measurement, first on
|
||||||
// this emulator's software Vulkan.
|
// this emulator's software Vulkan.
|
||||||
let wanted = Limits {
|
let wanted = iris_limits();
|
||||||
max_buffer_size: IRIS_MAX_BUFFER_SIZE,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
match pollster::block_on(adapter.request_device(&DeviceDescriptor {
|
match pollster::block_on(adapter.request_device(&DeviceDescriptor {
|
||||||
required_features: iris_features(),
|
required_features: iris_features(),
|
||||||
required_limits: wanted,
|
required_limits: wanted,
|
||||||
|
|||||||
Reference in new issue
Block a user