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 渲染
每月242次下载
用于 ultralight-rs
23KB
373 代码行
WGPU-Async
此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::Device
和wgpu::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