Description
It is decently common for people to want to integrate wgpu into a larger application that is using a graphics api or to use a library built around a graphics api to integrate with wgpu. There are many different ways and with a wide variety of resources that you could need to do interop with, so this proposal will be a set of smaller api changes that combine to have a unified picture to underlying apis.
This API is sure to evolve as it is refined, this is just a first attempt to unify it
wgpu layer
We currently only support getting the underlying types for a wgpu type if we're running on wgpu-core/hal. We should also allow this for access to the underlying WebGPU resource. To facilitate this, we should have a trait that mirrors wgpu_hal::Api but only for associated types. This is only there for allowing generic functions, so no real trait bounds need to exist.
trait Api {
type Instance;
type Adapter;
type Device;
type Texture;
type Buffer;
...
}
struct WebGPU;
pub use wgpu_hal::api::*;
impl Api for WebGPU {
type Instance = ();
type Adapter = GpuAdapter;
....
}
impl Api for A where A: wgpu_hal::Api {
// These are not wgpu_hal::*::Instance. It's the actual underlying api instance type
type Instance = A::RawInstance;
type Adapter = A::RawDevice;
....
}
Second we change the as_hal interop functions to be as_inner_api and take A: Api
impl CommandEncoder {
fn as_inner_api<A: Api>(&self, f: impl FnOnce(&mut A::CommandBuffer) -> R) -> R;
}
Ownership
The ownership of wgpu-created resources always lies in wgpu, all client code must keep the wgpu object alive while they are using the object.
The ownership of all wgpu-imported resources will depend on the presence of a DropGuard. This drop guard is a Box<dyn FnOnce(A::Raw*)>
which will be called when the resource is destroyed.
- If a drop guard is provided, wgpu does not own the resource, and it must be kept alive externally until the provided drop guard is call.
- If a drop guard is not provided, wgpu does own the resource, and it will be destroyed as if created by wgpu.
Creation
For each importable object, we will add a associated type that gives all the information wgpu-hal needs to successfully import a type.
trait Api {
type InstanceImportDescriptor;
type AdapterImportDescriptor;
type DeviceImportDescriptor;
type TextureImportDescriptor;
type BufferImportDescriptor;
// Note _not_ command encoder.
type CommandBufferImportDescriptor;
}
On the wgpu level, it will look like:
impl Device {
unsafe fn create_texture_from_inner<A: Api>(
&self,
raw: A::Texture,
desc: &TextureDescriptor,
import_desc: A::TextureImportDescriptor,
drop_guard: DropGuard,
);
}
This should work for all the importable objects.
Resource States
Add the following to the API trait
trait Api {
// Provides all information to be able to issue a barrier for the state.
type BufferState;
// Provides all information to be able to issue a barrier for the state.
type TextureState;
fn buffer_usage_to_state(hal::BufferUses) -> Self::BufferState;
fn texture_usage_to_state(hal::TextureState) -> Self::TextureState;
}
When using buffers and textures with external code, or external command buffers, you must follow the following flow (as seen by the queue command stream)
- Release resource for external use.
- External use (either via a wgpu command encoder, or via an imported command buffer)
- Acquire resource from external use.
The release/acquire terminology could probably be improved.
Step one and three can be accomplished with the following api:
struct ResourceStates<T, U> {
resource: T,
usage: U,
requirement: ResourceUsageRequirement,
}
enum ResourceUsageRequirement {
// If the command buffer does not know the state of the object, will move it to the provided state.
// otherwise will leave the state alone. If you are going to record your own barrier before using the resource, this will
// reduce the total amount o barriers.
Optional,
// Will unconditionally bring the state of the object to the given state.
Required,
}
impl CommandEncoder {
// Returns the state that the resources are in. These can be converted to API
// state using `A::*_usage_to_state`.
//
// All released resources must have their wgpu handle kept alive until the command re-consuming them is recorded onto a submitted command buffer.
unsafe fn release_resources(
&mut self,
textures: &[ResourceStates<&TextureView, hal::TextureUses>]
buffers: &[ResourceStates<&Buffer, hal::BufferUses>]
) -> (Vec<hal::TextureUses>, Vec<hal::BufferUses>);
unsafe fn acquire_resource(
&mut self,
textures: &[(&TextureView, hal::TextureUses)]
buffers: &[(&Buffer, hal::BufferUses)]
);
}