#wgpu #async #web-gpu #utility #wasm

wgpu-async

将一些WGPU回调方法转换为异步方法

10个版本 (1 个稳定版)

22.1.0 2024年8月15日
0.20.1 2024年8月14日
0.19.4 2024年8月14日
0.19.1 2024年2月7日
0.16.3 2023年7月18日

#220 in 渲染

Download history 80/week @ 2024-04-26 28/week @ 2024-05-03 2/week @ 2024-05-17 1/week @ 2024-05-24 2/week @ 2024-05-31 5/week @ 2024-06-07 1/week @ 2024-06-14 3/week @ 2024-07-19 1/week @ 2024-07-26 238/week @ 2024-08-09

每月242次下载
用于 ultralight-rs

MIT 许可证

23KB
373 代码行

WGPU-Async

crates.io docs.rs crates.io

此crate在非WASM平台上添加了一个全局轮询循环线程,可用于创建一个持有任务完成状态的WgpuFuture。轮询循环是保守的,在没有future等待它时停止自己,这意味着此crate在改变范式时尽可能减少开销。

请注意,此crate的目标不是提高任何性能,快速应用程序应尽可能减少CPU-GPU通信和同步,无论使用的范式和目标平台如何。实际上,如果使用不当,此crate可能会大大降低性能,如常见陷阱部分所示。此crate适用于原型设计或测试时使用,当原生和Web目标之间的兼容性比速度更重要时。

动机

WGPU在初始化适配器和设备时提供了一些async方法,但在程序执行期间,CPU和GPU之间的大部分时间都是通过回调和轮询管理的。一种常见的模式如下

wgpu.do_something();
wgpu.on_something_done(|result| { /* Handle results */ });
wgpu.poll();

这是一个非常JavaScript式的模式,而在Rust中,我们可能期望编写看起来更像是以下代码的代码

let result = wgpu.do_something().await;

或者,如果我们仍然想要一个回调

wgpu.do_something().then(|result| { /* Handle results */ }).await;

在Web目标上,我们发现调用poll是完全不必要的,这增加了同时针对原生和Web的程序的概念复杂性。此crate将这两种方法统一在一个共同的async/await API下。

常见陷阱

由于轮询线程既间歇性又全局地运行,独立于您的代码的其他部分,因此在使用此库时,可能会在执行必须等待的操作时掩盖错误。例如,以下代码应该发生死锁

// BAD CODE - DON'T DO THIS
let (sender, receiver) = flume::bounded(1);
let mapping = wgpu::Buffer::slice(buffer, ..).map_async(.., |_| sender.send(()));
// ERROR: Poll not called, so buffer will never map, so recv will never complete.
receiver.recv().unwrap();

然而,使用此库,对map_async的调用可能最终会通过(如果您在其他地方使用futures),但需要未知的时间,这可能导致巨大的性能损失。假设这些性能损失足够明显,以至于会发现这个错误,但您应该知道,使用此crate有隐藏死锁在性能损失背后的可能性。

用法

要以异步的方式执行操作,您的wgpu::Devicewgpu::Queue需要被封装在异步智能指针版本中。这些实现了Deref<Device>Deref<Queue>,因此可以作为现有wgpu代码的插槽替代品。

// Create a device and queue like normal
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());

let adapter = instance
    .request_adapter(&wgpu::RequestAdapterOptions {
        power_preference: wgpu::PowerPreference::HighPerformance,
        compatible_surface: None,
        force_fallback_adapter: true,
    })
    .await
    .expect("missing adapter");

let (device, queue) = adapter
    .request_device(
        &wgpu::DeviceDescriptor {
            features: wgpu::Features::empty(),
            limits: adapter.limits(),
            label: None,
        },
        None,
    )
    .await
    .expect("missing device");

// Make them async
let (device, queue) = wgpu_async::wrap(Arc::new(device), Arc::new(queue));

然后您可以使用具有完全相同签名的阴影wgpu方法,但具有额外的async特性

queue.submit(&[/* commands */]).await; // An awaitable `Queue::submit`!

就像它们的基wgpu对应物一样,这些方法立即开始在GPU上工作。然而,直到等待未来为止,设备不会开始被轮询。

您还可以使用AsyncDevice::do_async将任何非阴影回调和轮询方法转换为异步方法

wgpu.do_something();
let future = device.do_async(move |callback| {
    wgpu.on_something_done(|result| callback(result));
});

let result = future.await;

依赖项

~2–33MB
~499K SLoC