Skip to content

Proposal for Underlying Api Interoperability #4067

Open
@cwfitzgerald

Description

@cwfitzgerald

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)

  1. Release resource for external use.
  2. External use (either via a wgpu command encoder, or via an imported command buffer)
  3. 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)]
    );
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions