bevy/crates/bevy_render/src/render_resource/bind_group.rs
robtfm 2cd0bd7575 improve compile time by type-erasing wgpu structs (#5950)
# Objective

structs containing wgpu types take a long time to compile. this is particularly bad for generics containing the wgpu structs (like the depth pipeline builder with `#[derive(SystemParam)]` i've been working on).

we can avoid that by boxing and type-erasing in the bevy `render_resource` wrappers.

type system magic is not a strength of mine so i guess there will be a cleaner way to achieve this, happy to take feedback or for it to be taken as a proof of concept if someone else wants to do a better job.

## Solution

- add macros to box and type-erase in debug mode
- leave current impl for release mode

timings:


<html xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns="http://www.w3.org/TR/REC-html40">

<head>

<meta name=ProgId content=Excel.Sheet>
<meta name=Generator content="Microsoft Excel 15">
<link id=Main-File rel=Main-File
href="file:///C:/Users/robfm/AppData/Local/Temp/msohtmlclip1/01/clip.htm">
<link rel=File-List
href="file:///C:/Users/robfm/AppData/Local/Temp/msohtmlclip1/01/clip_filelist.xml">
<!--table
	{mso-displayed-decimal-separator:"\.";
	mso-displayed-thousand-separator:"\,";}
@page
	{margin:.75in .7in .75in .7in;
	mso-header-margin:.3in;
	mso-footer-margin:.3in;}
tr
	{mso-height-source:auto;}
col
	{mso-width-source:auto;}
br
	{mso-data-placement:same-cell;}
td
	{padding-top:1px;
	padding-right:1px;
	padding-left:1px;
	mso-ignore:padding;
	color:black;
	font-size:11.0pt;
	font-weight:400;
	font-style:normal;
	text-decoration:none;
	font-family:Calibri, sans-serif;
	mso-font-charset:0;
	mso-number-format:General;
	text-align:general;
	vertical-align:bottom;
	border:none;
	mso-background-source:auto;
	mso-pattern:auto;
	mso-protection:locked visible;
	white-space:nowrap;
	mso-rotate:0;}
.xl65
	{mso-number-format:0%;}
.xl66
	{vertical-align:middle;
	white-space:normal;}
.xl67
	{vertical-align:middle;}
-->
</head>

<body link="#0563C1" vlink="#954F72">



current |   |   |  
-- | -- | -- | --
  | Total time: | 64.9s |  
  | bevy_pbr v0.9.0-dev | 19.2s |  
  | bevy_render v0.9.0-dev | 17.0s |  
  | bevy_sprite v0.9.0-dev | 15.1s |  
  | DepthPipelineBuilder | 18.7s |  
  |   |   |  
with type-erasing |   |   | diff
  | Total time: | 49.0s | -24%
  | bevy_render v0.9.0-dev | 12.0s | -38%
  | bevy_pbr v0.9.0-dev | 8.7s | -49%
  | bevy_sprite v0.9.0-dev | 6.1s | -60%
  | DepthPipelineBuilder | 1.2s | -94%



</body>

</html>

the depth pipeline builder is a binary with body: 
```rust
use std::{marker::PhantomData, hash::Hash};
use bevy::{prelude::*, ecs::system::SystemParam, pbr::{RenderMaterials, MaterialPipeline, ShadowPipeline}, render::{renderer::RenderDevice, render_resource::{SpecializedMeshPipelines, PipelineCache}, render_asset::RenderAssets}};

fn main() {
    println!("Hello, world p!\n");
}

#[derive(SystemParam)]
pub struct DepthPipelineBuilder<'w, 's, M: Material> 
where M::Data: Eq + Hash + Clone,
{
    render_device: Res<'w, RenderDevice>,
    material_pipeline: Res<'w, MaterialPipeline<M>>,
    material_pipelines: ResMut<'w, SpecializedMeshPipelines<MaterialPipeline<M>>>,
    shadow_pipeline: Res<'w, ShadowPipeline>,
    pipeline_cache: ResMut<'w, PipelineCache>,
    render_meshes: Res<'w, RenderAssets<Mesh>>,
    render_materials: Res<'w, RenderMaterials<M>>,
    msaa: Res<'w, Msaa>,
    #[system_param(ignore)]
    _p: PhantomData<&'s M>,
}
```
2022-11-18 22:04:23 +00:00

313 lines
12 KiB
Rust

pub use bevy_render_macros::AsBindGroup;
use encase::ShaderType;
use crate::{
prelude::Image,
render_asset::RenderAssets,
render_resource::{BindGroupLayout, Buffer, Sampler, TextureView},
renderer::RenderDevice,
texture::FallbackImage,
};
use bevy_reflect::Uuid;
use std::ops::Deref;
use wgpu::BindingResource;
use crate::render_resource::resource_macros::*;
render_resource_wrapper!(ErasedBindGroup, wgpu::BindGroup);
/// A [`BindGroup`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct BindGroupId(Uuid);
/// Bind groups are responsible for binding render resources (e.g. buffers, textures, samplers)
/// to a [`TrackedRenderPass`](crate::render_phase::TrackedRenderPass).
/// This makes them accessible in the pipeline (shaders) as uniforms.
///
/// May be converted from and dereferences to a wgpu [`BindGroup`](wgpu::BindGroup).
/// Can be created via [`RenderDevice::create_bind_group`](crate::renderer::RenderDevice::create_bind_group).
#[derive(Clone, Debug)]
pub struct BindGroup {
id: BindGroupId,
value: ErasedBindGroup,
}
impl BindGroup {
/// Returns the [`BindGroupId`].
#[inline]
pub fn id(&self) -> BindGroupId {
self.id
}
}
impl From<wgpu::BindGroup> for BindGroup {
fn from(value: wgpu::BindGroup) -> Self {
BindGroup {
id: BindGroupId(Uuid::new_v4()),
value: ErasedBindGroup::new(value),
}
}
}
impl Deref for BindGroup {
type Target = wgpu::BindGroup;
#[inline]
fn deref(&self) -> &Self::Target {
&self.value
}
}
/// Converts a value to a [`BindGroup`] with a given [`BindGroupLayout`], which can then be used in Bevy shaders.
/// This trait can be derived (and generally should be). Read on for details and examples.
///
/// This is an opinionated trait that is intended to make it easy to generically
/// convert a type into a [`BindGroup`]. It provides access to specific render resources,
/// such as [`RenderAssets<Image>`] and [`FallbackImage`]. If a type has a [`Handle<Image>`](bevy_asset::Handle),
/// these can be used to retrieve the corresponding [`Texture`](crate::render_resource::Texture) resource.
///
/// [`AsBindGroup::as_bind_group`] is intended to be called once, then the result cached somewhere. It is generally
/// ok to do "expensive" work here, such as creating a [`Buffer`] for a uniform.
///
/// If for some reason a [`BindGroup`] cannot be created yet (for example, the [`Texture`](crate::render_resource::Texture)
/// for an [`Image`] hasn't loaded yet), just return [`AsBindGroupError::RetryNextUpdate`], which signals that the caller
/// should retry again later.
///
/// # Deriving
///
/// This trait can be derived. Field attributes like `uniform` and `texture` are used to define which fields should be bindings,
/// what their binding type is, and what index they should be bound at:
///
/// ```
/// # use bevy_render::{color::Color, render_resource::*, texture::Image};
/// # use bevy_asset::Handle;
/// #[derive(AsBindGroup)]
/// struct CoolMaterial {
/// #[uniform(0)]
/// color: Color,
/// #[texture(1)]
/// #[sampler(2)]
/// color_texture: Handle<Image>,
/// }
/// ```
///
/// In WGSL shaders, the binding would look like this:
///
/// ```wgsl
/// struct CoolMaterial {
/// color: vec4<f32>,
/// };
///
/// @group(1) @binding(0)
/// var<uniform> material: CoolMaterial;
/// @group(1) @binding(1)
/// var color_texture: texture_2d<f32>;
/// @group(1) @binding(2)
/// var color_sampler: sampler;
/// ```
/// Note that the "group" index is determined by the usage context. It is not defined in [`AsBindGroup`]. For example, in Bevy material bind groups
/// are generally bound to group 1.
///
/// The following field-level attributes are supported:
/// * `uniform(BINDING_INDEX)`
/// * The field will be converted to a shader-compatible type using the [`ShaderType`] trait, written to a [`Buffer`], and bound as a uniform.
/// [`ShaderType`] is implemented for most math types already, such as [`f32`], [`Vec4`](bevy_math::Vec4), and
/// [`Color`](crate::color::Color). It can also be derived for custom structs.
/// * `texture(BINDING_INDEX)`
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture)
/// GPU resource, which will be bound as a texture in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
/// [`None`], the [`FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute
/// (with a different binding index) if a binding of the sampler for the [`Image`] is also required.
/// * `sampler(BINDING_INDEX)`
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Sampler`](crate::render_resource::Sampler) GPU
/// resource, which will be bound as a sampler in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
/// [`None`], the [`FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `texture` binding attribute
/// (with a different binding index) if a binding of the texture for the [`Image`] is also required.
///
/// Note that fields without field-level binding attributes will be ignored.
/// ```
/// # use bevy_render::{color::Color, render_resource::AsBindGroup};
/// # use bevy_asset::Handle;
/// #[derive(AsBindGroup)]
/// struct CoolMaterial {
/// #[uniform(0)]
/// color: Color,
/// this_field_is_ignored: String,
/// }
/// ```
///
/// As mentioned above, [`Option<Handle<Image>>`] is also supported:
/// ```
/// # use bevy_render::{color::Color, render_resource::AsBindGroup, texture::Image};
/// # use bevy_asset::Handle;
/// #[derive(AsBindGroup)]
/// struct CoolMaterial {
/// #[uniform(0)]
/// color: Color,
/// #[texture(1)]
/// #[sampler(2)]
/// color_texture: Option<Handle<Image>>,
/// }
/// ```
/// This is useful if you want a texture to be optional. When the value is [`None`], the [`FallbackImage`] will be used for the binding instead, which defaults
/// to "pure white".
///
/// Field uniforms with the same index will be combined into a single binding:
/// ```
/// # use bevy_render::{color::Color, render_resource::AsBindGroup};
/// #[derive(AsBindGroup)]
/// struct CoolMaterial {
/// #[uniform(0)]
/// color: Color,
/// #[uniform(0)]
/// roughness: f32,
/// }
/// ```
///
/// In WGSL shaders, the binding would look like this:
/// ```wgsl
/// struct CoolMaterial {
/// color: vec4<f32>,
/// roughness: f32,
/// };
///
/// @group(1) @binding(0)
/// var<uniform> material: CoolMaterial;
/// ```
///
/// Some less common scenarios will require "struct-level" attributes. These are the currently supported struct-level attributes:
/// * `uniform(BINDING_INDEX, ConvertedShaderType)`
/// * This also creates a [`Buffer`] using [`ShaderType`] and binds it as a uniform, much
/// much like the field-level `uniform` attribute. The difference is that the entire [`AsBindGroup`] value is converted to `ConvertedShaderType`,
/// which must implement [`ShaderType`], instead of a specific field implementing [`ShaderType`]. This is useful if more complicated conversion
/// logic is required. The conversion is done using the [`AsBindGroupShaderType<ConvertedShaderType>`] trait, which is automatically implemented
/// if `&Self` implements [`Into<ConvertedShaderType>`]. Only use [`AsBindGroupShaderType`] if access to resources like [`RenderAssets<Image>`] is
/// required.
/// * `bind_group_data(DataType)`
/// * The [`AsBindGroup`] type will be converted to some `DataType` using [`Into<DataType>`] and stored
/// as [`AsBindGroup::Data`] as part of the [`AsBindGroup::as_bind_group`] call. This is useful if data needs to be stored alongside
/// the generated bind group, such as a unique identifier for a material's bind group. The most common use case for this attribute
/// is "shader pipeline specialization". See [`SpecializedRenderPipeline`](crate::render_resource::SpecializedRenderPipeline).
///
/// The previous `CoolMaterial` example illustrating "combining multiple field-level uniform attributes with the same binding index" can
/// also be equivalently represented with a single struct-level uniform attribute:
/// ```
/// # use bevy_render::{color::Color, render_resource::{AsBindGroup, ShaderType}};
/// #[derive(AsBindGroup)]
/// #[uniform(0, CoolMaterialUniform)]
/// struct CoolMaterial {
/// color: Color,
/// roughness: f32,
/// }
///
/// #[derive(ShaderType)]
/// struct CoolMaterialUniform {
/// color: Color,
/// roughness: f32,
/// }
///
/// impl From<&CoolMaterial> for CoolMaterialUniform {
/// fn from(material: &CoolMaterial) -> CoolMaterialUniform {
/// CoolMaterialUniform {
/// color: material.color,
/// roughness: material.roughness,
/// }
/// }
/// }
/// ```
///
/// Setting `bind_group_data` looks like this:
/// ```
/// # use bevy_render::{color::Color, render_resource::AsBindGroup};
/// #[derive(AsBindGroup)]
/// #[bind_group_data(CoolMaterialKey)]
/// struct CoolMaterial {
/// #[uniform(0)]
/// color: Color,
/// is_shaded: bool,
/// }
///
/// #[derive(Copy, Clone, Hash, Eq, PartialEq)]
/// struct CoolMaterialKey {
/// is_shaded: bool,
/// }
///
/// impl From<&CoolMaterial> for CoolMaterialKey {
/// fn from(material: &CoolMaterial) -> CoolMaterialKey {
/// CoolMaterialKey {
/// is_shaded: material.is_shaded,
/// }
/// }
/// }
/// ```
pub trait AsBindGroup: Sized {
/// Data that will be stored alongside the "prepared" bind group.
type Data: Send + Sync;
/// Creates a bind group for `self` matching the layout defined in [`AsBindGroup::bind_group_layout`].
fn as_bind_group(
&self,
layout: &BindGroupLayout,
render_device: &RenderDevice,
images: &RenderAssets<Image>,
fallback_image: &FallbackImage,
) -> Result<PreparedBindGroup<Self>, AsBindGroupError>;
/// Creates the bind group layout matching all bind groups returned by [`AsBindGroup::as_bind_group`]
fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout;
}
/// An error that occurs during [`AsBindGroup::as_bind_group`] calls.
pub enum AsBindGroupError {
/// The bind group could not be generated. Try again next frame.
RetryNextUpdate,
}
/// A prepared bind group returned as a result of [`AsBindGroup::as_bind_group`].
pub struct PreparedBindGroup<T: AsBindGroup> {
pub bindings: Vec<OwnedBindingResource>,
pub bind_group: BindGroup,
pub data: T::Data,
}
/// An owned binding resource of any type (ex: a [`Buffer`], [`TextureView`], etc).
/// This is used by types like [`PreparedBindGroup`] to hold a single list of all
/// render resources used by bindings.
pub enum OwnedBindingResource {
Buffer(Buffer),
TextureView(TextureView),
Sampler(Sampler),
}
impl OwnedBindingResource {
pub fn get_binding(&self) -> BindingResource {
match self {
OwnedBindingResource::Buffer(buffer) => buffer.as_entire_binding(),
OwnedBindingResource::TextureView(view) => BindingResource::TextureView(view),
OwnedBindingResource::Sampler(sampler) => BindingResource::Sampler(sampler),
}
}
}
/// Converts a value to a [`ShaderType`] for use in a bind group.
/// This is automatically implemented for references that implement [`Into`].
/// Generally normal [`Into`] / [`From`] impls should be preferred, but
/// sometimes additional runtime metadata is required.
/// This exists largely to make some [`AsBindGroup`] use cases easier.
pub trait AsBindGroupShaderType<T: ShaderType> {
/// Return the `T` [`ShaderType`] for `self`. When used in [`AsBindGroup`]
/// derives, it is safe to assume that all images in `self` exist.
fn as_bind_group_shader_type(&self, images: &RenderAssets<Image>) -> T;
}
impl<T, U: ShaderType> AsBindGroupShaderType<U> for T
where
for<'a> &'a T: Into<U>,
{
#[inline]
fn as_bind_group_shader_type(&self, _images: &RenderAssets<Image>) -> U {
self.into()
}
}