mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
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 <[email protected]>
This commit is contained in:
1 parent
6eedf8e2c5
commit
3767332efd
3 files changed
+39
-3
No files matched your search
@@ -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));
|
||||
|
||||
@@ -650,3 +650,17 @@ fn wgsl_float_literals_fit_f32() {
|
||||
}
|
||||
assert!(checked > 0);
|
||||
}
|
||||
|
||||
/// A device created with WebGL2's limits (what egui-wgpu asks for on GL, as the desktop app on
|
||||
/// FreeBSD gets): the compositor declines it instead of building pipelines that fail validation
|
||||
/// (wgpu's default error handler panics here; release builds would only log it).
|
||||
#[test]
|
||||
fn devices_with_webgl2_limits_are_declined() {
|
||||
hold_gpu_lock();
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
|
||||
let Ok(adapter) = pollster::block_on(instance.request_adapter(&Default::default())) else { return };
|
||||
let desc = wgpu::DeviceDescriptor { required_limits: wgpu::Limits::downlevel_webgl2_defaults(), ..Default::default() };
|
||||
let Ok((device, queue)) = pollster::block_on(adapter.request_device(&desc)) else { return };
|
||||
let err = crate::context::GpuContext::new(&adapter, device, queue).err().expect("declined");
|
||||
assert!(err.contains("max_storage_buffers_per_shader_stage"), "{err}");
|
||||
}
|
||||
@@ -21,15 +21,22 @@ fn settle(h: &mut Harness<'_, EffectcraftApp>) {
|
||||
|
||||
#[test]
|
||||
fn viewer_draws_gpu_frames_that_match_the_cpu() {
|
||||
if effectcraft_gpu::Gpu::headless().is_none() {
|
||||
let Some(probe) = effectcraft_gpu::Gpu::headless() else {
|
||||
eprintln!("no GPU adapter: skipping");
|
||||
return;
|
||||
}
|
||||
};
|
||||
// egui-wgpu creates its GL device with WebGL2's limits, which the compositor declines.
|
||||
let gl = effectcraft_engine::render::Accelerator::name(&probe).ends_with("(Gl)");
|
||||
drop(probe);
|
||||
let mut s = Session::default();
|
||||
s.execute("file.openDemoProject", json!({})).unwrap();
|
||||
s.execute("time.set", json!({"time": 2.0})).unwrap();
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).with_pixels_per_point(1.0).wgpu().build_eframe(|_| EffectcraftApp::new(s));
|
||||
settle(&mut h);
|
||||
if gl && h.state().gpu_adapter().is_none() {
|
||||
eprintln!("egui-wgpu's GL device has WebGL2 limits: the viewer stays on the CPU, skipping");
|
||||
return;
|
||||
}
|
||||
assert!(h.state().gpu_adapter().is_some(), "GPU compositor on egui-wgpu's device");
|
||||
let gpu_px = h.state_mut().viewer_pixels().expect("read back the GPU frame");
|
||||
let gpu_shot = h.render().expect("render");
|
||||
|
||||
Reference in new issue
Block a user