hephae_render/
vertex.rs

1//! The heart of Hephae.
2//!
3//! See the documentation of [Vertex] for more information.
4
5use std::{any::TypeId, hash::Hash, marker::PhantomData, ops::Range, sync::Mutex};
6
7use bevy_app::prelude::*;
8use bevy_ecs::{
9    component::ComponentId,
10    entity::EntityHashMap,
11    prelude::*,
12    storage::SparseSet,
13    system::{ReadOnlySystemParam, SystemParam, SystemParamItem, SystemState, lifetimeless::Read},
14    world::FilteredEntityRef,
15};
16use bevy_render::{
17    prelude::*,
18    primitives::{Aabb, Frustum, Sphere},
19    render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, RenderCommand, SortedPhaseItem},
20    render_resource::{CachedRenderPipelineId, RenderPipelineDescriptor, TextureFormat},
21    sync_world::MainEntity,
22    view::{NoCpuCulling, NoFrustumCulling, RenderLayers, VisibilityRange, VisibleEntities, VisibleEntityRanges},
23};
24use bevy_transform::prelude::*;
25use bevy_utils::{Parallel, TypeIdMap};
26use smallvec::SmallVec;
27
28use crate::{
29    attribute::VertexLayout,
30    drawer::{Drawer, HasDrawer},
31};
32
33/// The heart of Hephae. Instances of `Vertex` directly represent the elements of the vertex buffer
34/// in the GPU.
35pub trait Vertex: Send + Sync + VertexLayout {
36    /// System parameter to fetch when initializing
37    /// [`VertexPipeline`](crate::pipeline::VertexPipeline) to create a
38    /// [`PipelineProp`](Vertex::PipelineProp).
39    type PipelineParam: SystemParam;
40    /// The additional property of the [common pipeline definition](crate::pipeline::VertexPipeline)
41    /// that may used when specializing based on [`PipelineKey`](Vertex::PipelineKey). For example,
42    /// this may be used to create a
43    /// [`BindGroupLayout`](bevy_render::render_resource::BindGroupLayout) for texture-sampling.
44    type PipelineProp: Send + Sync;
45    /// Key used to specialize the render pipeline. For example, this may be an
46    /// [`AssetId<Image>`](bevy_asset::Handle<bevy_image::Image>) used to reference a
47    /// [`GpuImage`](bevy_render::texture::GpuImage) for texture-sampling.
48    type PipelineKey: Send + Sync + Clone + Eq + PartialEq + Hash;
49    /// Format of the depth-stencil pass supplied to the rendering pipeline creation parameters.
50    /// Defaults to [`Some(TextureFormat::Depth32Float)`], which is the default for 2D core pipeline
51    /// depth-stencil format. [`None`] means the pipeline will not have a depth-stencil state.
52    const DEPTH_FORMAT: Option<TextureFormat> = Some(TextureFormat::Depth32Float);
53
54    /// System parameter to fetch when [creating the batch](Vertex::create_batch).
55    type BatchParam: SystemParam;
56    /// Additional property that is embedded into the [batch](crate::pipeline::ViewBatches)
57    /// components for use in [`RenderCommand`](Vertex::RenderCommand). For example, this may be
58    /// an [`AssetId<Image>`](bevy_asset::Handle<bevy_image::Image>) from
59    /// [`PipelineKey`](Vertex::PipelineKey) to attach the associated bind
60    /// group for texture-sampling.
61    type BatchProp: Send + Sync;
62
63    /// The [`PhaseItem`](bevy_render::render_phase::PhaseItem) that this vertex works with.
64    type Item: CachedRenderPipelinePhaseItem + SortedPhaseItem;
65    /// Additional GPU render commands to invoke before actually drawing the vertex and index
66    /// buffers. For example, this may be used to set the texture-sampling bind group provided by
67    /// [`BatchProp`](Vertex::BatchProp).
68    type RenderCommand: RenderCommand<Self::Item, Param: ReadOnlySystemParam> + Send + Sync;
69
70    /// Path to the shader rendering vertex attributes of this type. Entry points should be
71    /// `vertex(...)` and `fragment(...)`.
72    const SHADER: &'static str;
73
74    /// Further customizes the application. Called in [`Plugin::finish`]. For example, this may be
75    /// used to add systems extracting texture atlas pages and validating bind groups associated
76    /// with them.
77    #[allow(unused)]
78    fn setup(app: &mut App) {}
79
80    /// Creates the additional render pipeline property for use in
81    /// [specialization](Vertex::specialize_pipeline).
82    fn init_pipeline(param: SystemParamItem<Self::PipelineParam>) -> Self::PipelineProp;
83
84    /// Specializes the render pipeline descriptor based off of the [key](Vertex::PipelineKey) and
85    /// [prop](Vertex::PipelineProp) of the common render pipeline descriptor.
86    fn specialize_pipeline(key: Self::PipelineKey, prop: &Self::PipelineProp, desc: &mut RenderPipelineDescriptor);
87
88    /// Creates the phase item associated with a [`Drawer`] based on its layer, render and
89    /// main entity, rendering pipeline ID, draw function ID, and command index.
90    fn create_item(
91        layer: f32,
92        entity: (Entity, MainEntity),
93        pipeline: CachedRenderPipelineId,
94        draw_function: DrawFunctionId,
95        command: usize,
96    ) -> Self::Item;
97
98    /// Creates additional batch property for use in rendering.
99    fn create_batch(param: &mut SystemParamItem<Self::BatchParam>, key: Self::PipelineKey) -> Self::BatchProp;
100}
101
102/// Stores the runtime-only type information of [`Drawer`] that is associated with a [`Vertex`] for
103/// use in [`check_visibilities`].
104#[derive(Resource)]
105pub struct VertexDrawers<T: Vertex>(pub(crate) SparseSet<ComponentId, TypeId>, PhantomData<fn() -> T>);
106impl<T: Vertex> Default for VertexDrawers<T> {
107    #[inline]
108    fn default() -> Self {
109        Self(SparseSet::new(), PhantomData)
110    }
111}
112
113impl<T: Vertex> VertexDrawers<T> {
114    /// Registers a [`Drawer`] to be checked in [`check_visibilities`].
115    #[inline]
116    pub fn add<D: Drawer<Vertex = T>>(&mut self, world: &mut World) {
117        self.0
118            .insert(world.register_component::<HasDrawer<D>>(), TypeId::of::<With<HasDrawer<D>>>());
119    }
120}
121
122#[derive(Component)]
123pub(crate) struct DrawItems<T: Vertex>(pub Mutex<SmallVec<[(Range<usize>, f32, T::PipelineKey); 8]>>);
124impl<T: Vertex> Default for DrawItems<T> {
125    #[inline]
126    fn default() -> Self {
127        Self(Mutex::new(SmallVec::new()))
128    }
129}
130
131/// Calculates [`ViewVisibility`] of [drawable](Drawer) entities.
132///
133/// Similar to [`check_visibility`](bevy_render::view::check_visibility) that is generic over
134/// [`HasDrawer`], except the filters are configured dynamically by
135/// [`DrawerPlugin`](crate::DrawerPlugin). This makes it so that all drawers that share the
136/// same [`Vertex`] type also share the same visibility system.
137pub fn check_visibilities<T: Vertex>(
138    world: &mut World,
139    visibility: &mut QueryState<FilteredEntityRef>,
140    views: &mut SystemState<(
141        Query<(
142            Entity,
143            Read<Frustum>,
144            Option<Read<RenderLayers>>,
145            Read<Camera>,
146            Has<NoCpuCulling>,
147        )>,
148        Query<(
149            Entity,
150            &InheritedVisibility,
151            &mut ViewVisibility,
152            Option<&RenderLayers>,
153            Option<&Aabb>,
154            &GlobalTransform,
155            Has<NoFrustumCulling>,
156            Has<VisibilityRange>,
157        )>,
158        Option<Res<VisibleEntityRanges>>,
159    )>,
160    visible_entities: &mut SystemState<Query<(Entity, &mut VisibleEntities)>>,
161    mut thread_queues: Local<Parallel<Vec<Entity>>>,
162    mut view_queues: Local<EntityHashMap<Vec<Entity>>>,
163    mut view_maps: Local<EntityHashMap<TypeIdMap<Vec<Entity>>>>,
164) {
165    world.resource_scope(|world, drawers: Mut<VertexDrawers<T>>| {
166        if drawers.is_changed() {
167            let mut builder = QueryBuilder::<FilteredEntityRef>::new(world);
168            builder.or(|query| {
169                for id in drawers.0.indices() {
170                    query.with_id(id);
171                }
172            });
173
174            *visibility = builder.build();
175        }
176    });
177
178    let (view_query, mut visible_aabb_query, visible_entity_ranges) = views.get_mut(world);
179    let visible_entity_ranges = visible_entity_ranges.as_deref();
180    for (view, &frustum, maybe_view_mask, camera, no_cpu_culling) in &view_query {
181        if !camera.is_active {
182            continue;
183        }
184
185        let view_mask = maybe_view_mask.unwrap_or_default();
186        visible_aabb_query.par_iter_mut().for_each_init(
187            || thread_queues.borrow_local_mut(),
188            |queue,
189             (
190                entity,
191                inherited_visibility,
192                mut view_visibility,
193                maybe_entity_mask,
194                maybe_model_aabb,
195                transform,
196                no_frustum_culling,
197                has_visibility_range,
198            )| {
199                if !inherited_visibility.get() {
200                    return;
201                }
202
203                let entity_mask = maybe_entity_mask.unwrap_or_default();
204                if !view_mask.intersects(entity_mask) {
205                    return;
206                }
207
208                // If outside of the visibility range, cull.
209                if has_visibility_range &&
210                    visible_entity_ranges.is_some_and(|visible_entity_ranges| {
211                        !visible_entity_ranges.entity_is_in_range_of_view(entity, view)
212                    })
213                {
214                    return;
215                }
216
217                // If we have an AABB, do frustum culling.
218                if !no_frustum_culling && !no_cpu_culling {
219                    if let Some(model_aabb) = maybe_model_aabb {
220                        let world_from_local = transform.affine();
221                        let model_sphere = Sphere {
222                            center: world_from_local.transform_point3a(model_aabb.center),
223                            radius: transform.radius_vec3a(model_aabb.half_extents),
224                        };
225
226                        // Do quick sphere-based frustum culling.
227                        if !frustum.intersects_sphere(&model_sphere, false) {
228                            return;
229                        }
230
231                        // Do AABB-based frustum culling.
232                        if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) {
233                            return;
234                        }
235                    }
236                }
237
238                view_visibility.set();
239                queue.push(entity);
240            },
241        );
242
243        thread_queues.drain_into(view_queues.entry(view).or_default());
244    }
245
246    visibility.update_archetypes(world);
247
248    let drawers = world.resource::<VertexDrawers<T>>();
249    for (&view, queues) in &mut view_queues {
250        let map = view_maps.entry(view).or_default();
251        for e in queues.drain(..) {
252            let Ok(visible) = visibility.get_manual(world, e) else {
253                continue;
254            };
255
256            for (&id, &key) in drawers.0.iter() {
257                if visible.contains_id(id) {
258                    map.entry(key).or_default().push(e);
259                }
260            }
261        }
262    }
263
264    let mut visible_entities = visible_entities.get_mut(world);
265    for (view, mut visible_entities) in &mut visible_entities {
266        let Some(map) = view_maps.get_mut(&view) else {
267            continue;
268        };
269        for (&id, entities) in map {
270            let dst = visible_entities.entities.entry(id).or_default();
271            dst.clear();
272            dst.append(entities);
273        }
274    }
275}