I'll use this issue to explore various ways we could make wgpu-core less generic.
The motivations include reducing the clutter of overly generic code and more importantly to reduce binary size, compile times.
Factor out non-generic code into separate functions
There is some amount of code that could be easily factored out, for example some of the validation. Isolating the validation also has the advantage that it can be laid out to match the specification so that we can more easily check that the validation is correct.
There is only a limited amount of bang for buck we can get
Making the resources not generic
We'll get the biggest wins by far if we can erase the type of the resources. most of wgpu-core is generic only because something needs to hold on to a generic resource.
Downcasting
Assuming we can accept the cost of downcasting every resource at the top of every hal functions, we could use trait objects and downcast via Any.
// in wgpu-hal:
/// Implemented by most hal types
pub trait Resource: 'static {
fn as_any(&self) -> &dyn std::any::Any;
}
// Helper for downcasting. Failure to downcast is always an internal error
pub fn downcast<T:'static>(resource: &dyn Resource) -> &T {
resource.as_any().downcast_ref::<T>().unwrap()
}
The gpu-hal APIs would take &dyn Resource parameters instead of some associated types, and the first thing they would do is downcast these dyn traits into the concrete types they are expecting.
Note that we call also break it down into plenty of traits like TextureResource, BufferResource, etc. That's a rather small detail once we have overcome the other obstacles.
Boxed resources
The simple approach (which I find very unsatisfying) is to box all hal resources:
The wgpu-core and wgpu-hal structures are no longer next to each other in memory, that most likely has a cost but it is hard to estimate it without doing all of the work.
Dynamically sized types
The last member of a struct can be a DST, which turns the structure into a dst as well:
pub struct BindGroupLayoutResource<Hal: ?Sized> {
// ...
pub raw: Hal
}
// The alias that is used in most places
pub type BindGroupLayout = BindGroupLayoutResource<dyn hal::Resource>;
pub fn create_bind_group_layout(device: &Device) -> Result<Arc<BindGroupLayout>, Error> {
let typed_bgl = Arc::new(BindGroupLayoutResource {
label: String::new(),
// TODO: Here we need the concrete hal structure before we can erase its type. Ideally
// we would like device_create_* functions to not be generic (to allow out of tree backends).
raw: hal::vulkan::BindGroupLayout {
},
});
// BindGroupLayoutResource<hal::BindGroupLayout> can be converted into a BindGroupLayoutReosurce<dyn hal::Resource>
Ok(typed_bgl)
}
It's quite nice, however there are a few constraints:
There are some restrictions to how DSTs are stored, for example you can't do Option<dyn hal::Resource>
to place the resource into the structure that contains it, you need to know the concrete type. In other words, the device_create_* functions would probably need to still be generic.
If we manage to make the generics completely disappear, then out-of-tree backends become very easy to add.
Note that the boxed resources solution does not suffer from this limitation.
I'll use this issue to explore various ways we could make wgpu-core less generic.
The motivations include reducing the clutter of overly generic code and more importantly to reduce binary size, compile times.
Factor out non-generic code into separate functions
There is some amount of code that could be easily factored out, for example some of the validation. Isolating the validation also has the advantage that it can be laid out to match the specification so that we can more easily check that the validation is correct. There is only a limited amount of bang for buck we can get
Making the resources not generic
We'll get the biggest wins by far if we can erase the type of the resources. most of wgpu-core is generic only because something needs to hold on to a generic resource.
Downcasting
Assuming we can accept the cost of downcasting every resource at the top of every hal functions, we could use trait objects and downcast via
Any
.The gpu-hal APIs would take
&dyn Resource
parameters instead of some associated types, and the first thing they would do is downcast these dyn traits into the concrete types they are expecting.Note that we call also break it down into plenty of traits like
TextureResource
,BufferResource
, etc. That's a rather small detail once we have overcome the other obstacles.Boxed resources
The simple approach (which I find very unsatisfying) is to box all hal resources:
The wgpu-core and wgpu-hal structures are no longer next to each other in memory, that most likely has a cost but it is hard to estimate it without doing all of the work.
Dynamically sized types
The last member of a struct can be a DST, which turns the structure into a dst as well:
It's quite nice, however there are a few constraints:
Option<dyn hal::Resource>
device_create_*
functions would probably need to still be generic.Code of the DST experiment: https://gist.github.com/nical/c5d88aaf97f20815756a36dc5c94b5a3 it also shows how snatchable resources would work.
Can we do it without Any/downcast?
Maybe, with a copious amount of unsafe code which might be fine. Suggestions are welcome!