GPU compositor declines devices whose limits its pipelines exceed

egui-wgpu creates its device on GL with WebGL2's limits (no storage buffers), so the desktop
app on FreeBSD built the compositor's pipelines on it: validation errors (only logged in
release builds) and wrong frames. GpuContext::new now checks the limits its pipelines bind
(storage buffers and textures per stage, bind groups, workgroup sizes) and returns Err, which
keeps the app on the CPU. ui_gpu skips its GPU-vs-CPU comparison when that happens on GL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-05 10:52:04 -04:00
co-authored by Claude Opus 5.5
parent 6eedf8e2c5
commit 3767332efd
3 changed files with 39 additions and 3 deletions
+16 -1
View File
@@ -294,12 +294,27 @@ fn storage_tex_entry(binding: u32, format: wgpu::TextureFormat) -> wgpu::BindGro
impl GpuContext {
/// Build on an existing device (the desktop app shares egui-wgpu's). `Err` when the
/// adapter cannot run the compositor (no compute shaders, e.g. WebGL2; no float storage
/// textures).
/// textures) or the device was created with limits below what its pipelines bind.
pub fn new(adapter: &wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue) -> Result<GpuContext, String> {
let info = adapter.get_info();
if !adapter.get_downlevel_capabilities().flags.contains(wgpu::DownlevelFlags::COMPUTE_SHADERS) {
return Err(format!("{} ({:?}): no compute shaders", info.name, info.backend));
}
// egui-wgpu asks for WebGL2's limits on GL (no storage buffers at all): building the
// pipelines there would be a validation error, which release builds only log.
let l = device.limits();
let needs = [
// `adv3d`'s post kernels bind 7 (its rasteriser's fragment stage 6).
("max_storage_buffers_per_shader_stage", l.max_storage_buffers_per_shader_stage, 7),
("max_storage_textures_per_shader_stage", l.max_storage_textures_per_shader_stage, 1),
("max_bind_groups", l.max_bind_groups, 2),
("max_compute_workgroup_size_x", l.max_compute_workgroup_size_x, 64),
("max_compute_workgroup_size_y", l.max_compute_workgroup_size_y, 16),
("max_compute_invocations_per_workgroup", l.max_compute_invocations_per_workgroup, 256),
];
if let Some((limit, have, need)) = needs.iter().find(|(_, have, need)| have < need) {
return Err(format!("{} ({:?}): device limit {limit} is {have}, needs {need}", info.name, info.backend));
}
for f in [FORMAT, wgpu::TextureFormat::Rgba8Unorm] {
if !adapter.get_texture_format_features(f).allowed_usages.contains(wgpu::TextureUsages::STORAGE_BINDING) {
return Err(format!("{} ({:?}): {f:?} storage textures unsupported", info.name, info.backend));