bevy/crates
Carter Anderson e369a8ad51 Mesh vertex buffer layouts (#3959)
This PR makes a number of changes to how meshes and vertex attributes are handled, which the goal of enabling easy and flexible custom vertex attributes:
* Reworks the `Mesh` type to use the newly added `VertexAttribute` internally
  * `VertexAttribute` defines the name, a unique `VertexAttributeId`, and a `VertexFormat`
  *  `VertexAttributeId` is used to produce consistent sort orders for vertex buffer generation, replacing the more expensive and often surprising "name based sorting"  
  * Meshes can be used to generate a `MeshVertexBufferLayout`, which defines the layout of the gpu buffer produced by the mesh. `MeshVertexBufferLayouts` can then be used to generate actual `VertexBufferLayouts` according to the requirements of a specific pipeline. This decoupling of "mesh layout" vs "pipeline vertex buffer layout" is what enables custom attributes. We don't need to standardize _mesh layouts_ or contort meshes to meet the needs of a specific pipeline. As long as the mesh has what the pipeline needs, it will work transparently. 
* Mesh-based pipelines now specialize on `&MeshVertexBufferLayout` via the new `SpecializedMeshPipeline` trait (which behaves like `SpecializedPipeline`, but adds `&MeshVertexBufferLayout`). The integrity of the pipeline cache is maintained because the `MeshVertexBufferLayout` is treated as part of the key (which is fully abstracted from implementers of the trait ... no need to add any additional info to the specialization key).    
* Hashing `MeshVertexBufferLayout` is too expensive to do for every entity, every frame. To make this scalable, I added a generalized "pre-hashing" solution to `bevy_utils`: `Hashed<T>` keys and `PreHashMap<K, V>` (which uses `Hashed<T>` internally) . Why didn't I just do the quick and dirty in-place "pre-compute hash and use that u64 as a key in a hashmap" that we've done in the past? Because its wrong! Hashes by themselves aren't enough because two different values can produce the same hash. Re-hashing a hash is even worse! I decided to build a generalized solution because this pattern has come up in the past and we've chosen to do the wrong thing. Now we can do the right thing! This did unfortunately require pulling in `hashbrown` and using that in `bevy_utils`, because avoiding re-hashes requires the `raw_entry_mut` api, which isn't stabilized yet (and may never be ... `entry_ref` has favor now, but also isn't available yet). If std's HashMap ever provides the tools we need, we can move back to that. Note that adding `hashbrown` doesn't increase our dependency count because it was already in our tree. I will probably break these changes out into their own PR.
* Specializing on `MeshVertexBufferLayout` has one non-obvious behavior: it can produce identical pipelines for two different MeshVertexBufferLayouts. To optimize the number of active pipelines / reduce re-binds while drawing, I de-duplicate pipelines post-specialization using the final `VertexBufferLayout` as the key.  For example, consider a pipeline that needs the layout `(position, normal)` and is specialized using two meshes: `(position, normal, uv)` and `(position, normal, other_vec2)`. If both of these meshes result in `(position, normal)` specializations, we can use the same pipeline! Now we do. Cool!

To briefly illustrate, this is what the relevant section of `MeshPipeline`'s specialization code looks like now:

```rust
impl SpecializedMeshPipeline for MeshPipeline {
    type Key = MeshPipelineKey;

    fn specialize(
        &self,
        key: Self::Key,
        layout: &MeshVertexBufferLayout,
    ) -> RenderPipelineDescriptor {
        let mut vertex_attributes = vec![
            Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
            Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
            Mesh::ATTRIBUTE_UV_0.at_shader_location(2),
        ];

        let mut shader_defs = Vec::new();
        if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
            shader_defs.push(String::from("VERTEX_TANGENTS"));
            vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));
        }

        let vertex_buffer_layout = layout
            .get_layout(&vertex_attributes)
            .expect("Mesh is missing a vertex attribute");
```

Notice that this is _much_ simpler than it was before. And now any mesh with any layout can be used with this pipeline, provided it has vertex postions, normals, and uvs. We even got to remove `HAS_TANGENTS` from MeshPipelineKey and `has_tangents` from `GpuMesh`, because that information is redundant with `MeshVertexBufferLayout`.

This is still a draft because I still need to:

* Add more docs
* Experiment with adding error handling to mesh pipeline specialization (which would print errors at runtime when a mesh is missing a vertex attribute required by a pipeline). If it doesn't tank perf, we'll keep it.
* Consider breaking out the PreHash / hashbrown changes into a separate PR.
* Add an example illustrating this change
* Verify that the "mesh-specialized pipeline de-duplication code" works properly

Please dont yell at me for not doing these things yet :) Just trying to get this in peoples' hands asap.

Alternative to #3120
Fixes #3030


Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-02-23 23:21:13 +00:00
..
bevy_app small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_asset Internal Asset Hot Reloading (#3966) 2022-02-18 22:56:57 +00:00
bevy_audio Update rodio 0.15 (#3846) 2022-02-03 04:25:44 +00:00
bevy_core small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_core_pipeline small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_crevice Don't panic in macro shape validation (#3647) 2022-01-15 22:14:43 +00:00
bevy_derive small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_diagnostic small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_dylib Release 0.6.0 (#3587) 2022-01-08 10:18:22 +00:00
bevy_dynamic_plugin Release 0.6.0 (#3587) 2022-01-08 10:18:22 +00:00
bevy_ecs Fix all_tuples macro for non-0/1 starts (#4002) 2022-02-21 23:49:08 +00:00
bevy_ecs_compile_fail_tests Release 0.6.0 (#3587) 2022-01-08 10:18:22 +00:00
bevy_gilrs Release 0.6.0 (#3587) 2022-01-08 10:18:22 +00:00
bevy_gltf Mesh vertex buffer layouts (#3959) 2022-02-23 23:21:13 +00:00
bevy_input Proper prehashing (#3963) 2022-02-18 03:26:01 +00:00
bevy_internal Internal Asset Hot Reloading (#3966) 2022-02-18 22:56:57 +00:00
bevy_log Implement init_resource for Commands and World (#3079) 2022-02-08 23:04:19 +00:00
bevy_macro_utils small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_math Release 0.6.0 (#3587) 2022-01-08 10:18:22 +00:00
bevy_pbr Mesh vertex buffer layouts (#3959) 2022-02-23 23:21:13 +00:00
bevy_reflect remove external_type_uuid macro (#4018) 2022-02-22 23:21:39 +00:00
bevy_render Mesh vertex buffer layouts (#3959) 2022-02-23 23:21:13 +00:00
bevy_scene small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_sprite Mesh vertex buffer layouts (#3959) 2022-02-23 23:21:13 +00:00
bevy_tasks small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_text small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_transform small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_ui Mesh vertex buffer layouts (#3959) 2022-02-23 23:21:13 +00:00
bevy_utils Proper prehashing (#3963) 2022-02-18 03:26:01 +00:00
bevy_window small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00
bevy_winit small and mostly pointless refactoring (#2934) 2022-02-13 22:33:55 +00:00