Skip to main content

bevy_render/renderer/
render_context.rs

1use crate::diagnostic::internal::DiagnosticsRecorder;
2use crate::render_phase::TrackedRenderPass;
3use crate::render_resource::{CommandEncoder, RenderPassDescriptor};
4use crate::renderer::{wgpu_wrapper, RenderDevice};
5use alloc::borrow::Cow;
6use bevy_derive::{Deref, DerefMut};
7use bevy_ecs::change_detection::Tick;
8use bevy_ecs::component::ComponentId;
9use bevy_ecs::prelude::*;
10use bevy_ecs::query::{QueryData, QueryFilter, QueryState};
11use bevy_ecs::system::{
12    Deferred, SystemAccess, SystemBuffer, SystemMeta, SystemName, SystemParam,
13    SystemParamValidationError,
14};
15use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell;
16use bevy_ecs::world::DeferredWorld;
17#[cfg(feature = "trace")]
18use bevy_log::info_span;
19#[cfg(not(target_arch = "wasm32"))]
20use bevy_tasks::ComputeTaskPool;
21use core::marker::PhantomData;
22use wgpu::CommandBuffer;
23
24#[derive(Default)]
25struct PendingCommandBuffersInner {
26    commands: Vec<PendingCommandBuffer>,
27}
28
29enum PendingCommandBuffer {
30    Buffer(CommandBuffer),
31    Encoder {
32        encoder: CommandEncoder,
33        #[cfg(feature = "trace")]
34        name: Cow<'static, str>,
35    },
36}
37
38wgpu_wrapper!(struct WgpuPendingCommandBuffersInner(PendingCommandBuffersInner));
39
40/// A resource that holds command buffers and encoders that are pending submission to the render queue.
41#[derive(Resource)]
42pub struct PendingCommandBuffers(WgpuPendingCommandBuffersInner);
43
44impl Default for PendingCommandBuffers {
45    fn default() -> Self {
46        Self(WgpuPendingCommandBuffersInner::new(
47            PendingCommandBuffersInner::default(),
48        ))
49    }
50}
51
52impl PendingCommandBuffers {
53    pub fn push(&mut self, buffers: impl IntoIterator<Item = CommandBuffer>) {
54        self.0
55            .commands
56            .extend(buffers.into_iter().map(PendingCommandBuffer::Buffer));
57    }
58
59    fn append(&mut self, commands: &mut Vec<PendingCommandBuffer>) {
60        self.0.commands.append(commands);
61    }
62
63    pub fn push_encoder(&mut self, encoder: CommandEncoder, name: impl Into<Cow<'static, str>>) {
64        #[cfg(not(feature = "trace"))]
65        let _ = name;
66
67        self.0.commands.push(PendingCommandBuffer::Encoder {
68            encoder,
69            #[cfg(feature = "trace")]
70            name: name.into(),
71        });
72    }
73
74    pub fn finish(&mut self) -> impl Iterator<Item = CommandBuffer> {
75        #[cfg(feature = "trace")]
76        let _finish_command_buffers_span = info_span!("finish_command_buffers").entered();
77
78        let commands = self.0.commands.drain(..);
79
80        #[cfg(target_arch = "wasm32")]
81        {
82            finish_sequential(commands)
83        }
84
85        #[cfg(not(target_arch = "wasm32"))]
86        {
87            finish_parallel(commands)
88        }
89    }
90
91    pub fn is_empty(&self) -> bool {
92        self.0.commands.is_empty()
93    }
94
95    pub fn len(&self) -> usize {
96        self.0.commands.len()
97    }
98}
99
100/// Finishes pending command buffers sequentially, preserving their order.
101///
102/// Used on wasm, where wgpu command encoders and buffers are `!Send` and so
103/// cannot be finished across task pool threads.
104#[cfg(target_arch = "wasm32")]
105fn finish_sequential(
106    commands: impl Iterator<Item = PendingCommandBuffer>,
107) -> impl Iterator<Item = CommandBuffer> {
108    commands.into_iter().map(|command| match command {
109        PendingCommandBuffer::Buffer(command_buffer) => command_buffer,
110        PendingCommandBuffer::Encoder { encoder, .. } => encoder.finish(),
111    })
112}
113
114/// Finishes pending command encoders in parallel on the [`ComputeTaskPool`],
115/// then reassembles the command buffers in their original order.
116#[cfg(not(target_arch = "wasm32"))]
117fn finish_parallel(
118    commands: impl Iterator<Item = PendingCommandBuffer>,
119) -> impl Iterator<Item = CommandBuffer> {
120    let mut command_buffers = Vec::with_capacity(commands.size_hint().0);
121    let mut finished_encoders = ComputeTaskPool::get().scope(|scope| {
122        for (index, command) in commands.into_iter().enumerate() {
123            match command {
124                PendingCommandBuffer::Buffer(command_buffer) => {
125                    command_buffers.push((index, command_buffer));
126                }
127                PendingCommandBuffer::Encoder {
128                    encoder,
129                    #[cfg(feature = "trace")]
130                    name,
131                } => {
132                    scope.spawn(async move {
133                        #[cfg(feature = "trace")]
134                        let _span =
135                            info_span!("finish_command_buffer", system = name.as_ref()).entered();
136                        (index, encoder.finish())
137                    });
138                }
139            }
140        }
141    });
142
143    command_buffers.append(&mut finished_encoders);
144    command_buffers.sort_unstable_by_key(|(index, _)| *index);
145    command_buffers
146        .into_iter()
147        .map(|(_, command_buffer)| command_buffer)
148}
149
150#[derive(Default)]
151struct RenderContextStateInner {
152    command_encoder: Option<CommandEncoder>,
153    commands: Vec<PendingCommandBuffer>,
154    render_device: Option<RenderDevice>,
155}
156
157impl RenderContextStateInner {
158    fn flush_encoder(&mut self) {
159        if let Some(encoder) = self.command_encoder.take() {
160            self.commands.push(PendingCommandBuffer::Encoder {
161                encoder,
162                #[cfg(feature = "trace")]
163                name: "RenderContextState::flush_encoder".into(),
164            });
165        }
166    }
167}
168
169wgpu_wrapper!(struct WgpuRenderContextStateInner(RenderContextStateInner));
170
171/// A resource that holds the current render context state, including command encoder and command buffers.
172/// This is used internally by the [`RenderContext`] system parameter. Implements [`SystemBuffer`] to
173/// append command buffers and unfinished encoders in topological system order. Pending encoders are
174/// finished in parallel immediately before submission.
175pub struct RenderContextState(WgpuRenderContextStateInner);
176
177impl Default for RenderContextState {
178    fn default() -> Self {
179        Self(WgpuRenderContextStateInner::new(
180            RenderContextStateInner::default(),
181        ))
182    }
183}
184
185impl RenderContextState {
186    fn flush_encoder(&mut self) {
187        self.0.flush_encoder();
188    }
189
190    fn command_encoder(&mut self, label: &str) -> &mut CommandEncoder {
191        let render_device = self
192            .0
193            .render_device
194            .clone()
195            .expect("RenderDevice must be set before accessing command_encoder");
196
197        self.0.command_encoder.get_or_insert_with(|| {
198            render_device
199                .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(label) })
200        })
201    }
202}
203
204impl SystemBuffer for RenderContextState {
205    fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) {
206        #[cfg(feature = "trace")]
207        let _span =
208            info_span!("RenderContextState::apply", system = %_system_meta.name()).entered();
209
210        let inner = &mut *self.0;
211
212        inner.flush_encoder();
213
214        #[cfg(feature = "trace")]
215        for command in &mut inner.commands {
216            if let PendingCommandBuffer::Encoder { name, .. } = command {
217                *name = _system_meta.name().clone().into();
218            }
219        }
220
221        if !inner.commands.is_empty() {
222            let mut pending = world.resource_mut::<PendingCommandBuffers>();
223            pending.append(&mut inner.commands);
224        }
225
226        inner.render_device = None;
227    }
228}
229
230/// A system parameter that provides access to a command encoder and render device for issuing
231/// rendering commands inside any system running beneath the root [`super::RenderGraph`] schedule in the
232/// [`super::render_system`] system.
233#[derive(SystemParam)]
234pub struct RenderContext<'w, 's> {
235    state: Deferred<'s, RenderContextState>,
236    system_name: SystemName,
237    render_device: Res<'w, RenderDevice>,
238    diagnostics_recorder: Option<Res<'w, DiagnosticsRecorder>>,
239}
240
241impl<'w, 's> RenderContext<'w, 's> {
242    fn ensure_device(&mut self) {
243        if self.state.0.render_device.is_none() {
244            self.state.0.render_device = Some(self.render_device.clone());
245        }
246    }
247
248    /// Returns the render device associated with this render context.
249    pub fn render_device(&self) -> &RenderDevice {
250        &self.render_device
251    }
252
253    /// Returns the diagnostics recorder, if available.
254    pub fn diagnostic_recorder(&self) -> Option<Res<'w, DiagnosticsRecorder>> {
255        self.diagnostics_recorder.as_ref().map(Res::clone)
256    }
257
258    /// Returns the current command encoder, creating one if it does not already exist.
259    pub fn command_encoder(&mut self) -> &mut CommandEncoder {
260        self.ensure_device();
261        self.state.command_encoder(self.system_name.as_str())
262    }
263
264    /// Begins a tracked render pass with the given descriptor.
265    pub fn begin_tracked_render_pass<'a>(
266        &'a mut self,
267        descriptor: RenderPassDescriptor<'_>,
268    ) -> TrackedRenderPass<'a> {
269        self.ensure_device();
270
271        let command_encoder = self.state.0.command_encoder.get_or_insert_with(|| {
272            self.render_device
273                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
274                    label: Some(self.system_name.as_str()),
275                })
276        });
277
278        let render_pass = command_encoder.begin_render_pass(&descriptor);
279        TrackedRenderPass::new(&self.render_device, render_pass)
280    }
281
282    /// Adds a finished command buffer to be submitted later.
283    pub fn add_command_buffer(&mut self, command_buffer: CommandBuffer) {
284        self.state.flush_encoder();
285        self.state
286            .0
287            .commands
288            .push(PendingCommandBuffer::Buffer(command_buffer));
289    }
290}
291
292/// A system parameter that can be used to explicitly flush pending command buffers to the render queue.
293/// This is typically not necessary, as command buffers are automatically flushed at the end of each
294/// render system. However, in some cases it may be useful to flush command buffers earlier.
295#[derive(SystemParam)]
296pub struct FlushCommands<'w> {
297    pending: ResMut<'w, PendingCommandBuffers>,
298    queue: Res<'w, super::RenderQueue>,
299}
300
301impl<'w> FlushCommands<'w> {
302    /// Flushes all pending command buffers to the render queue.
303    pub fn flush(&mut self) {
304        let mut buffers = self.pending.finish().peekable();
305        if buffers.peek().is_some() {
306            self.queue.submit(buffers);
307        }
308    }
309}
310
311/// The entity corresponding to the current view being rendered.
312#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq, Deref, DerefMut)]
313pub struct CurrentView(pub Entity);
314
315/// A query that fetches components for the entity corresponding to the current view being rendered,
316/// as defined by the [`CurrentView`] resource, equivalent to `query.get(current_view.entity())`.
317pub struct ViewQuery<'w, 's, D: QueryData, F: QueryFilter = ()> {
318    entity: Entity,
319    item: D::Item<'w, 's>,
320    _filter: PhantomData<F>,
321}
322
323impl<'w, 's, D: QueryData, F: QueryFilter> ViewQuery<'w, 's, D, F> {
324    #[inline]
325    pub fn entity(&self) -> Entity {
326        self.entity
327    }
328
329    #[inline]
330    pub fn into_inner(self) -> D::Item<'w, 's> {
331        self.item
332    }
333}
334
335pub struct ViewQueryState<D: QueryData, F: QueryFilter> {
336    resource_id: ComponentId,
337    query_state: QueryState<D, F>,
338}
339
340// SAFETY: ViewQuery accesses the CurrentView resource (read) and query components.
341// Access is properly registered in init_access.
342unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
343    for ViewQuery<'a, '_, D, F>
344{
345    type State = ViewQueryState<D, F>;
346    type Item<'w, 's> = ViewQuery<'w, 's, D, F>;
347
348    fn init_state(world: &mut World) -> Self::State {
349        ViewQueryState {
350            resource_id: world
351                .components_registrator()
352                .register_component::<CurrentView>(),
353            query_state: QueryState::new(world),
354        }
355    }
356
357    fn init_access(
358        state: &Self::State,
359        system_meta: &mut SystemMeta,
360        system_access: &mut SystemAccess,
361        world: &mut World,
362    ) {
363        let component_access_set = system_access.require_shared_access::<Self>(system_meta);
364        component_access_set.add_resource_read(state.resource_id);
365
366        <Query<'_, '_, D, F> as SystemParam>::init_access(
367            &state.query_state,
368            system_meta,
369            system_access,
370            world,
371        );
372    }
373
374    #[inline]
375    unsafe fn get_param<'w, 's>(
376        state: &'s mut Self::State,
377        _system_meta: &SystemMeta,
378        world: UnsafeWorldCell<'w>,
379        _change_tick: Tick,
380    ) -> Result<Self::Item<'w, 's>, SystemParamValidationError> {
381        // SAFETY: We have registered resource read access in init_access
382        let current_view = unsafe { world.get_resource::<CurrentView>() };
383
384        let Some(current_view) = current_view else {
385            return Err(SystemParamValidationError::skipped::<Self>(
386                "CurrentView resource not present",
387            ));
388        };
389
390        let entity = current_view.entity();
391
392        // SAFETY: Query state access is properly registered in init_access.
393        // The caller ensures the world matches the one used in init_state.
394        let item = unsafe { state.query_state.get_unchecked(world, entity) }.map_err(|_| {
395            SystemParamValidationError::skipped::<Self>("Current view entity does not match query")
396        })?;
397
398        Ok(ViewQuery {
399            entity,
400            item,
401            _filter: PhantomData,
402        })
403    }
404}
405
406// SAFETY: ViewQuery with ReadOnlyQueryData only reads from the world.
407unsafe impl<'w, 's, D: bevy_ecs::query::ReadOnlyQueryData + 'static, F: QueryFilter + 'static>
408    bevy_ecs::system::ReadOnlySystemParam for ViewQuery<'w, 's, D, F>
409{
410}