Delete the decisions and design logs; scripts, rigs and xtask off the root
Iris: "remove both decisions and iris.md. I've decided to instead make decisions when planning with agents rather than after they do things, and they're both too long for me to wanna read, + don't cover all the decisions I'll wanna make about the code anyways. I'll just naturally run into things for now. Todo is important though." So docs/DECISIONS.md (850 lines) and docs/IRIS.md (1,986) are gone, and AGENTS.md now says not to start another: raise a choice while planning it with her, otherwise decide it and put the reasoning at the code it governs. The TODO lists stay. docs/SUBAGENTS_DECISIONS.md went with them -- same artefact, same reasoning, and she did not name it, so its six decisions were folded into docs/SUBAGENTS.md rather than deleted. Deleting the logs left ~30 citations dangling in code comments and docs. Each states its reason inline and cited the file only for provenance, so they now read "decided 2026-09-07" or name the module doc that carries the reasoning. The root had six things that were not a program or a document. Moved, per "I only meant top level sh files": run-tests.sh, test-wg-tunnel.sh, wg-setup-host.sh -> scripts/ rigs/ -> scripts/rigs/ xtask/ -> scripts/xtask/ A project's own scripts stayed with the project: app/*.sh, app-rust/*.sh, iris/*.sh and server/enroll-link.sh did not move. `target/` at the root is deleted and cannot come back: there was never a workspace there, and the 29 MB was only xtask's scratch space, now in scripts/xtask/target/. `cargo xtask apk` still runs from the repo root and now publishes to scripts/build/outputs/apk/<mode>/ -- one directory deep, because that is what Dev Updater's `*/build/outputs/apk/*/*.apk` discovery pattern needs, and scripts/xtask/build would have been two. Verified: ./scripts/run-tests.sh and `cd iris && cargo test` green, clippy and fmt clean everywhere, `cargo xtask apk debug --abi x86_64` builds and signs an APK carrying lib/x86_64/libai_app.so at the new publish path, and the repo root is now eleven entries with no build output among them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
09778346a0
commit
4ccfda6b8e
44 files changed
+198
-2994
No files matched your search
@@ -0,0 +1 @@
|
||||
target
|
||||
Generated
+1224
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "gpu-probe"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
# Deliberately its own crate rather than a member of iris's workspace: the
|
||||
# vendored `iris/` tree is meant to stay reconcilable with the iris/iris
|
||||
# repository, and this is a rig belonging to ai-app.
|
||||
|
||||
[dependencies]
|
||||
# Pinned to what iris asks for, so the answer is about iris rather than
|
||||
# about a different wgpu.
|
||||
wgpu = "30.0.1"
|
||||
pollster = "1.0.1"
|
||||
# Queried directly, because wgpu and `cmd gpu vkjson` disagreed about
|
||||
# descriptor indexing in the emulator and only the raw call says which is
|
||||
# right.
|
||||
ash = "0.38"
|
||||
@@ -0,0 +1,102 @@
|
||||
//! Why a GPU test segfaults *after* it has passed, and what stops it.
|
||||
//!
|
||||
//! Measured here 2026-09-08, on this VM's Venus adapter. Destroying the
|
||||
//! last `VkInstance` makes the Vulkan loader `dlclose` the ICD; Mesa's
|
||||
//! ICD (`/usr/lib/libvulkan_virtio.so`) registers a `pthread_key_create`
|
||||
//! destructor pointing into its own text and is not linked `-z nodelete`,
|
||||
//! so glibc calls that destructor through unmapped memory when the thread
|
||||
//! that used Vulkan exits. libtest runs every `#[test]` on a spawned
|
||||
//! thread, which is why it looked like "wgpu crashes on drop": the drop
|
||||
//! itself completes, and the crash lands as the thread unwinds.
|
||||
//!
|
||||
//! The four modes are the experiment, and each is one variable:
|
||||
//!
|
||||
//! | mode | what it does | 2026-09-08 |
|
||||
//! |---|---|---|
|
||||
//! | `main` | wgpu instance + device on the main thread, dropped | exits 0 |
|
||||
//! | `thread` | the same on a spawned thread | **SIGSEGV** |
|
||||
//! | `keep` | the same, but the instance is never dropped | exits 0 |
|
||||
//! | `raw` | raw Vulkan (`ash`), instance + device, spawned thread | **SIGSEGV** |
|
||||
//!
|
||||
//! `raw` is the one that says whose bug it is: no wgpu is involved, so
|
||||
//! there is nothing for wgpu or a caller to fix in its drop order. `keep`
|
||||
//! is the fix -- hold one `wgpu::Instance` for the process, which is what
|
||||
//! wgpu asks for anyway. `iris/tests/mask_sdf.rs` does exactly that.
|
||||
//!
|
||||
//! `VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING=1` also makes every mode
|
||||
//! exit cleanly, which is the confirmation that the unload is the
|
||||
//! mechanism -- but it is an environment variable every caller would have
|
||||
//! to remember, so it belongs in this comment rather than in a script.
|
||||
|
||||
use ash::vk;
|
||||
use pollster::block_on;
|
||||
use wgpu::*;
|
||||
|
||||
fn main() {
|
||||
let mode = std::env::args().nth(1).unwrap_or_else(|| "thread".into());
|
||||
let body = match mode.as_str() {
|
||||
"main" => return wgpu_open_and_close(false),
|
||||
"thread" => || wgpu_open_and_close(false),
|
||||
"keep" => || wgpu_open_and_close(true),
|
||||
"raw" => raw_vulkan_open_and_close,
|
||||
other => panic!("unknown mode {other:?}: main | thread | keep | raw"),
|
||||
};
|
||||
std::thread::spawn(body).join().expect("the spawned thread");
|
||||
// Not reached when the thread's exit takes the process with it.
|
||||
eprintln!("thread joined");
|
||||
}
|
||||
|
||||
/// A wgpu instance and device, opened and closed. `keep_instance` is the
|
||||
/// fix under test: everything else still drops normally.
|
||||
fn wgpu_open_and_close(keep_instance: bool) {
|
||||
let instance = Instance::default();
|
||||
let adapter =
|
||||
block_on(instance.request_adapter(&RequestAdapterOptions::default())).expect("no adapter");
|
||||
let info = adapter.get_info();
|
||||
eprintln!(
|
||||
"adapter: {} ({:?}, {})",
|
||||
info.name, info.backend, info.driver
|
||||
);
|
||||
let (device, queue) =
|
||||
block_on(adapter.request_device(&DeviceDescriptor::default())).expect("no device");
|
||||
|
||||
drop(queue);
|
||||
drop(device);
|
||||
drop(adapter);
|
||||
if keep_instance {
|
||||
std::mem::forget(instance);
|
||||
} else {
|
||||
drop(instance);
|
||||
}
|
||||
eprintln!("wgpu closed");
|
||||
}
|
||||
|
||||
/// The same shape with no wgpu in it at all, which is what makes this a
|
||||
/// loader/driver bug rather than a wgpu one.
|
||||
fn raw_vulkan_open_and_close() {
|
||||
unsafe {
|
||||
let entry = ash::Entry::load().expect("vulkan loader");
|
||||
let app = vk::ApplicationInfo::default().api_version(vk::make_api_version(0, 1, 1, 0));
|
||||
let instance = entry
|
||||
.create_instance(
|
||||
&vk::InstanceCreateInfo::default().application_info(&app),
|
||||
None,
|
||||
)
|
||||
.expect("instance");
|
||||
let phys = instance.enumerate_physical_devices().expect("devices")[0];
|
||||
let priorities = [1.0f32];
|
||||
let queues = [vk::DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(0)
|
||||
.queue_priorities(&priorities)];
|
||||
let device = instance
|
||||
.create_device(
|
||||
phys,
|
||||
&vk::DeviceCreateInfo::default().queue_create_infos(&queues),
|
||||
None,
|
||||
)
|
||||
.expect("device");
|
||||
device.destroy_device(None);
|
||||
instance.destroy_instance(None);
|
||||
}
|
||||
eprintln!("raw vulkan closed");
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
//! 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),
|
||||
..InstanceDescriptor::new_without_display_handle()
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
// Compute is a *downlevel* capability, not a feature: Vulkan
|
||||
// grants it to any 1.0 device, and GLES only from ES 3.1. So
|
||||
// "can iris use a compute pass here" is this flag on every
|
||||
// adapter iris might fall back to, not just the preferred one.
|
||||
let down = adapter.get_downlevel_capabilities();
|
||||
let limits = adapter.limits();
|
||||
println!(
|
||||
" compute shaders: {} (shader model {:?})",
|
||||
down.flags.contains(DownlevelFlags::COMPUTE_SHADERS),
|
||||
down.shader_model
|
||||
);
|
||||
println!(
|
||||
" max compute invocations/workgroup: {}, workgroup storage: {} bytes",
|
||||
limits.max_compute_invocations_per_workgroup, limits.max_compute_workgroup_storage_size
|
||||
);
|
||||
}
|
||||
|
||||
let Some(adapter) = pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
|
||||
power_preference: PowerPreference::default(),
|
||||
compatible_surface: None,
|
||||
force_fallback_adapter: false,
|
||||
..Default::default()
|
||||
}))
|
||||
.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:?}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//! The raw Vulkan half of the probe.
|
||||
//!
|
||||
//! wgpu reports a feature only after a chain of its own decisions -- which
|
||||
//! physical device, which API version, which extension list -- so "wgpu says
|
||||
//! no" and "the driver says no" are different claims. This asks
|
||||
//! `vkGetPhysicalDeviceFeatures2` itself and prints the inputs to that chain,
|
||||
//! so a disagreement can be attributed rather than guessed at.
|
||||
|
||||
use ash::{Entry, vk};
|
||||
use std::ffi::CStr;
|
||||
|
||||
pub fn report() {
|
||||
let entry = match unsafe { Entry::load() } {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
println!("\nraw vulkan: cannot load loader -- {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let instance_version = match unsafe { entry.try_enumerate_instance_version() } {
|
||||
Ok(Some(v)) => v,
|
||||
Ok(None) => vk::API_VERSION_1_0,
|
||||
Err(e) => {
|
||||
println!("\nraw vulkan: enumerate_instance_version failed -- {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
println!("\nraw vulkan:");
|
||||
println!(" loader instance version: {}", ver(instance_version));
|
||||
|
||||
// Ask for the highest instance version the loader admits to: wgpu clamps
|
||||
// the device version by the instance's, so an instance created at 1.0
|
||||
// makes a 1.3 device look like 1.0.
|
||||
let app_info = vk::ApplicationInfo::default().api_version(instance_version);
|
||||
let create = vk::InstanceCreateInfo::default().application_info(&app_info);
|
||||
let instance = match unsafe { entry.create_instance(&create, None) } {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
println!(" create_instance failed -- {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let devices = unsafe { instance.enumerate_physical_devices() }.unwrap_or_default();
|
||||
for phd in devices {
|
||||
let props = unsafe { instance.get_physical_device_properties(phd) };
|
||||
let name = unsafe { CStr::from_ptr(props.device_name.as_ptr()) };
|
||||
println!(" device: {}", name.to_string_lossy());
|
||||
println!(" device api version: {}", ver(props.api_version));
|
||||
|
||||
let exts =
|
||||
unsafe { instance.enumerate_device_extension_properties(phd) }.unwrap_or_default();
|
||||
let has_ext = exts.iter().any(|e| {
|
||||
(unsafe { CStr::from_ptr(e.extension_name.as_ptr()) }) == c"VK_EXT_descriptor_indexing"
|
||||
});
|
||||
println!(" VK_EXT_descriptor_indexing advertised: {has_ext}");
|
||||
println!(" device extensions: {}", exts.len());
|
||||
|
||||
let mut indexing = vk::PhysicalDeviceDescriptorIndexingFeatures::default();
|
||||
let mut features2 = vk::PhysicalDeviceFeatures2::default().push_next(&mut indexing);
|
||||
unsafe { instance.get_physical_device_features2(phd, &mut features2) };
|
||||
|
||||
for (name, v) in [
|
||||
(
|
||||
"shaderSampledImageArrayNonUniformIndexing",
|
||||
indexing.shader_sampled_image_array_non_uniform_indexing,
|
||||
),
|
||||
(
|
||||
"descriptorBindingSampledImageUpdateAfterBind",
|
||||
indexing.descriptor_binding_sampled_image_update_after_bind,
|
||||
),
|
||||
(
|
||||
"shaderStorageImageArrayNonUniformIndexing",
|
||||
indexing.shader_storage_image_array_non_uniform_indexing,
|
||||
),
|
||||
(
|
||||
"descriptorBindingStorageImageUpdateAfterBind",
|
||||
indexing.descriptor_binding_storage_image_update_after_bind,
|
||||
),
|
||||
(
|
||||
"shaderStorageBufferArrayNonUniformIndexing",
|
||||
indexing.shader_storage_buffer_array_non_uniform_indexing,
|
||||
),
|
||||
(
|
||||
"descriptorBindingStorageBufferUpdateAfterBind",
|
||||
indexing.descriptor_binding_storage_buffer_update_after_bind,
|
||||
),
|
||||
(
|
||||
"descriptorBindingPartiallyBound",
|
||||
indexing.descriptor_binding_partially_bound,
|
||||
),
|
||||
] {
|
||||
println!(" {name:48} {}", if v != 0 { "yes" } else { "NO" });
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { instance.destroy_instance(None) };
|
||||
}
|
||||
|
||||
fn ver(v: u32) -> String {
|
||||
format!(
|
||||
"{}.{}.{}",
|
||||
vk::api_version_major(v),
|
||||
vk::api_version_minor(v),
|
||||
vk::api_version_patch(v)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// What this VM's virtio-gpu actually offers, asked of the kernel and the
|
||||
// driver rather than inferred from the host's qemu command line.
|
||||
//
|
||||
// cc -O2 -o virtgpu-probe virtgpu-probe.c -I/usr/include/libdrm -ldrm -lvulkan
|
||||
// ./virtgpu-probe
|
||||
//
|
||||
// Written 2026-09-08 for the question "Venus keeps causing problems, is
|
||||
// there something to do with qemu instead" (docs/RUST.md, "Venus went
|
||||
// away for an hour"). Three things, each of which was guessed wrong at
|
||||
// least once before being measured:
|
||||
//
|
||||
// 1. Which capsets the host offers. Capset 6 (DRM) is the "native
|
||||
// context" one -- RADV running in the guest against a passed-through
|
||||
// DRM context instead of Venus proxying every Vulkan call. Whether
|
||||
// it is available is a host-side fact this is the only way to read
|
||||
// from in here.
|
||||
// 2. Whether the Vulkan device has compute. It does, and assuming it
|
||||
// did not sent one investigation down the wrong path: the "no
|
||||
// compute" finding on record is about the *Android emulator's*
|
||||
// SwiftShader GL path, a different machine entirely.
|
||||
// 3. Whether plain Vulkan device teardown crashes on this adapter. It
|
||||
// does not -- which is what makes wgpu's teardown SIGSEGV wgpu's and
|
||||
// not the driver's, and is the kind of claim that is worthless
|
||||
// without the negative half.
|
||||
//
|
||||
// C rather than a Rust crate on purpose: two of the three are ioctl and
|
||||
// loader questions that a wgpu-shaped rig cannot ask, and this needs to
|
||||
// keep working when the thing under suspicion is wgpu itself.
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <drm/virtgpu_drm.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <xf86drm.h>
|
||||
|
||||
static const char *capset_name(int id) {
|
||||
switch (id) {
|
||||
case 1: return "VIRGL";
|
||||
case 2: return "VIRGL2";
|
||||
case 3: return "GFXSTREAM_VULKAN";
|
||||
case 4: return "VENUS";
|
||||
case 5: return "CROSS_DOMAIN";
|
||||
case 6: return "DRM (native context)";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static int capsets(void) {
|
||||
int fd = open("/dev/dri/renderD128", O_RDWR);
|
||||
if (fd < 0) {
|
||||
printf("capsets: cannot open /dev/dri/renderD128: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
struct drm_virtgpu_getparam gp;
|
||||
uint64_t mask = 0;
|
||||
memset(&gp, 0, sizeof gp);
|
||||
gp.param = VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs;
|
||||
gp.value = (uint64_t)(uintptr_t)&mask;
|
||||
int rc = drmIoctl(fd, DRM_IOCTL_VIRTGPU_GETPARAM, &gp);
|
||||
close(fd);
|
||||
if (rc) {
|
||||
// Not a virtio-gpu at all, or a kernel without the param: say
|
||||
// which, rather than printing an empty list that reads like "the
|
||||
// host offers nothing".
|
||||
printf("capsets: SUPPORTED_CAPSET_IDs unavailable: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
printf("capsets: bitmask 0x%llx\n", (unsigned long long)mask);
|
||||
for (int i = 1; i <= 8; i++)
|
||||
if (mask & (1ull << i)) printf(" %d: %s\n", i, capset_name(i));
|
||||
if (!(mask & (1ull << 6)))
|
||||
printf(" (no capset 6: native context needs host-side virglrenderer + qemu support)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int devices(void) {
|
||||
VkInstanceCreateInfo ici = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
|
||||
VkInstance inst;
|
||||
if (vkCreateInstance(&ici, NULL, &inst) != VK_SUCCESS) {
|
||||
printf("vulkan: no instance -- the loader found no usable ICD\n");
|
||||
return 1;
|
||||
}
|
||||
uint32_t n = 0;
|
||||
vkEnumeratePhysicalDevices(inst, &n, NULL);
|
||||
if (n == 0) {
|
||||
// The exact state this VM was in for an hour on 2026-09-08.
|
||||
printf("vulkan: instance ok but ZERO devices -- the host refused a context\n");
|
||||
vkDestroyInstance(inst, NULL);
|
||||
return 1;
|
||||
}
|
||||
VkPhysicalDevice pd[8];
|
||||
if (n > 8) n = 8;
|
||||
vkEnumeratePhysicalDevices(inst, &n, pd);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
VkPhysicalDeviceProperties p;
|
||||
vkGetPhysicalDeviceProperties(pd[i], &p);
|
||||
printf("vulkan: %s (api %u.%u.%u)\n", p.deviceName, VK_VERSION_MAJOR(p.apiVersion),
|
||||
VK_VERSION_MINOR(p.apiVersion), VK_VERSION_PATCH(p.apiVersion));
|
||||
printf(" compute: %u invocations/workgroup, size %u,%u,%u, %u bytes shared\n",
|
||||
p.limits.maxComputeWorkGroupInvocations, p.limits.maxComputeWorkGroupSize[0],
|
||||
p.limits.maxComputeWorkGroupSize[1], p.limits.maxComputeWorkGroupSize[2],
|
||||
p.limits.maxComputeSharedMemorySize);
|
||||
}
|
||||
|
||||
// The negative half of "wgpu's teardown crashes on Venus": five
|
||||
// devices and the instance, created and destroyed the plain way.
|
||||
float prio = 1.0f;
|
||||
VkDeviceQueueCreateInfo q = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = 0, .queueCount = 1, .pQueuePriorities = &prio};
|
||||
VkDeviceCreateInfo dci = {.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||
.queueCreateInfoCount = 1, .pQueueCreateInfos = &q};
|
||||
for (int i = 0; i < 5; i++) {
|
||||
VkDevice dev;
|
||||
if (vkCreateDevice(pd[0], &dci, NULL, &dev) != VK_SUCCESS) {
|
||||
printf("teardown: device %d could not be created\n", i);
|
||||
return 1;
|
||||
}
|
||||
vkDestroyDevice(dev, NULL);
|
||||
}
|
||||
vkDestroyInstance(inst, NULL);
|
||||
printf("teardown: 5 devices + instance created and destroyed cleanly\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int bad = capsets();
|
||||
bad |= devices();
|
||||
return bad;
|
||||
}
|
||||
Reference in new issue
Block a user