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#[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#[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#[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
171pub 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#[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 pub fn render_device(&self) -> &RenderDevice {
250 &self.render_device
251 }
252
253 pub fn diagnostic_recorder(&self) -> Option<Res<'w, DiagnosticsRecorder>> {
255 self.diagnostics_recorder.as_ref().map(Res::clone)
256 }
257
258 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 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 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#[derive(SystemParam)]
296pub struct FlushCommands<'w> {
297 pending: ResMut<'w, PendingCommandBuffers>,
298 queue: Res<'w, super::RenderQueue>,
299}
300
301impl<'w> FlushCommands<'w> {
302 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#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq, Deref, DerefMut)]
313pub struct CurrentView(pub Entity);
314
315pub 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
340unsafe 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 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 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
406unsafe 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}