1mod graph_runner;
2#[cfg(feature = "raw_vulkan_init")]
3pub mod raw_vulkan_init;
4mod render_device;
5mod wgpu_wrapper;
6
7pub use graph_runner::*;
8pub use render_device::*;
9pub use wgpu_wrapper::WgpuWrapper;
10
11use crate::{
12 diagnostic::{internal::DiagnosticsRecorder, RecordDiagnostics},
13 render_graph::RenderGraph,
14 render_phase::TrackedRenderPass,
15 render_resource::RenderPassDescriptor,
16 settings::{RenderResources, WgpuSettings, WgpuSettingsPriority},
17 view::{ExtractedWindows, ViewTarget},
18};
19use alloc::sync::Arc;
20use bevy_derive::{Deref, DerefMut};
21use bevy_ecs::{prelude::*, system::SystemState};
22use bevy_platform::time::Instant;
23use bevy_time::TimeSender;
24use bevy_window::RawHandleWrapperHolder;
25use tracing::{debug, error, info, info_span, warn};
26use wgpu::{
27 Adapter, AdapterInfo, Backends, CommandBuffer, CommandEncoder, DeviceType, Instance, Queue,
28 RequestAdapterOptions, Trace,
29};
30
31pub fn render_system(world: &mut World, state: &mut SystemState<Query<Entity, With<ViewTarget>>>) {
33 world.resource_scope(|world, mut graph: Mut<RenderGraph>| {
34 graph.update(world);
35 });
36
37 let diagnostics_recorder = world.remove_resource::<DiagnosticsRecorder>();
38
39 let graph = world.resource::<RenderGraph>();
40 let render_device = world.resource::<RenderDevice>();
41 let render_queue = world.resource::<RenderQueue>();
42
43 let res = RenderGraphRunner::run(
44 graph,
45 render_device.clone(), diagnostics_recorder,
47 &render_queue.0,
48 world,
49 |encoder| {
50 crate::view::screenshot::submit_screenshot_commands(world, encoder);
51 crate::gpu_readback::submit_readback_commands(world, encoder);
52 },
53 );
54
55 match res {
56 Ok(Some(diagnostics_recorder)) => {
57 world.insert_resource(diagnostics_recorder);
58 }
59 Ok(None) => {}
60 Err(e) => {
61 error!("Error running render graph:");
62 {
63 let mut src: &dyn core::error::Error = &e;
64 loop {
65 error!("> {}", src);
66 match src.source() {
67 Some(s) => src = s,
68 None => break,
69 }
70 }
71 }
72
73 panic!("Error running render graph: {e}");
74 }
75 }
76
77 {
78 let _span = info_span!("present_frames").entered();
79
80 let view_entities = state.get(world).iter().collect::<Vec<_>>();
83 for view_entity in view_entities {
84 world.entity_mut(view_entity).remove::<ViewTarget>();
85 }
86
87 let mut windows = world.resource_mut::<ExtractedWindows>();
88 for window in windows.values_mut() {
89 if let Some(surface_texture) = window.swap_chain_texture.take() {
90 surface_texture.present();
95 }
96 }
97
98 #[cfg(feature = "tracing-tracy")]
99 tracing::event!(
100 tracing::Level::INFO,
101 message = "finished frame",
102 tracy.frame_mark = true
103 );
104 }
105
106 crate::view::screenshot::collect_screenshots(world);
107
108 let time_sender = world.resource::<TimeSender>();
110 if let Err(error) = time_sender.0.try_send(Instant::now()) {
111 match error {
112 bevy_time::TrySendError::Full(_) => {
113 panic!("The TimeSender channel should always be empty during render. You might need to add the bevy::core::time_system to your app.",);
114 }
115 bevy_time::TrySendError::Disconnected(_) => {
116 }
118 }
119 }
120}
121
122#[derive(Resource, Clone, Deref, DerefMut)]
124pub struct RenderQueue(pub Arc<WgpuWrapper<Queue>>);
125
126#[derive(Resource, Clone, Debug, Deref, DerefMut)]
129pub struct RenderAdapter(pub Arc<WgpuWrapper<Adapter>>);
130
131#[derive(Resource, Clone, Deref, DerefMut)]
134pub struct RenderInstance(pub Arc<WgpuWrapper<Instance>>);
135
136#[derive(Resource, Clone, Deref, DerefMut)]
138pub struct RenderAdapterInfo(pub WgpuWrapper<AdapterInfo>);
139
140const GPU_NOT_FOUND_ERROR_MESSAGE: &str = if cfg!(target_os = "linux") {
141 "Unable to find a GPU! Make sure you have installed required drivers! For extra information, see: https://github.com/bevyengine/bevy/blob/latest/docs/linux_dependencies.md"
142} else {
143 "Unable to find a GPU! Make sure you have installed required drivers!"
144};
145
146#[cfg(not(target_family = "wasm"))]
147fn find_adapter_by_name(
148 instance: &Instance,
149 options: &WgpuSettings,
150 compatible_surface: Option<&wgpu::Surface<'_>>,
151 adapter_name: &str,
152) -> Option<Adapter> {
153 for adapter in
154 instance.enumerate_adapters(options.backends.expect(
155 "The `backends` field of `WgpuSettings` must be set to use a specific adapter.",
156 ))
157 {
158 tracing::trace!("Checking adapter: {:?}", adapter.get_info());
159 let info = adapter.get_info();
160 if let Some(surface) = compatible_surface
161 && !adapter.is_surface_supported(surface)
162 {
163 continue;
164 }
165
166 if info.name.eq_ignore_ascii_case(adapter_name) {
167 return Some(adapter);
168 }
169 }
170 None
171}
172
173pub async fn initialize_renderer(
176 backends: Backends,
177 primary_window: Option<RawHandleWrapperHolder>,
178 options: &WgpuSettings,
179 #[cfg(feature = "raw_vulkan_init")]
180 raw_vulkan_init_settings: raw_vulkan_init::RawVulkanInitSettings,
181) -> RenderResources {
182 let instance_descriptor = wgpu::InstanceDescriptor {
183 backends,
184 flags: options.instance_flags,
185 memory_budget_thresholds: options.instance_memory_budget_thresholds,
186 backend_options: wgpu::BackendOptions {
187 gl: wgpu::GlBackendOptions {
188 gles_minor_version: options.gles3_minor_version,
189 fence_behavior: wgpu::GlFenceBehavior::Normal,
190 },
191 dx12: wgpu::Dx12BackendOptions {
192 shader_compiler: options.dx12_shader_compiler.clone(),
193 },
194 noop: wgpu::NoopBackendOptions { enable: false },
195 },
196 };
197
198 #[cfg(not(feature = "raw_vulkan_init"))]
199 let instance = Instance::new(&instance_descriptor);
200 #[cfg(feature = "raw_vulkan_init")]
201 let mut additional_vulkan_features = raw_vulkan_init::AdditionalVulkanFeatures::default();
202 #[cfg(feature = "raw_vulkan_init")]
203 let instance = raw_vulkan_init::create_raw_vulkan_instance(
204 &instance_descriptor,
205 &raw_vulkan_init_settings,
206 &mut additional_vulkan_features,
207 );
208
209 let surface = primary_window.and_then(|wrapper| {
210 let maybe_handle = wrapper
211 .0
212 .lock()
213 .expect("Couldn't get the window handle in time for renderer initialization");
214 if let Some(wrapper) = maybe_handle.as_ref() {
215 let handle = unsafe { wrapper.get_handle() };
217 Some(
218 instance
219 .create_surface(handle)
220 .expect("Failed to create wgpu surface"),
221 )
222 } else {
223 None
224 }
225 });
226
227 let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER")
228 .map_or(options.force_fallback_adapter, |v| {
229 !(v.is_empty() || v == "0" || v == "false")
230 });
231
232 let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME")
233 .as_deref()
234 .map_or(options.adapter_name.clone(), |x| Some(x.to_lowercase()));
235
236 let request_adapter_options = RequestAdapterOptions {
237 power_preference: options.power_preference,
238 compatible_surface: surface.as_ref(),
239 force_fallback_adapter,
240 };
241
242 #[cfg(not(target_family = "wasm"))]
243 let mut selected_adapter = desired_adapter_name.and_then(|adapter_name| {
244 find_adapter_by_name(
245 &instance,
246 options,
247 request_adapter_options.compatible_surface,
248 &adapter_name,
249 )
250 });
251 #[cfg(target_family = "wasm")]
252 let mut selected_adapter = None;
253
254 #[cfg(target_family = "wasm")]
255 if desired_adapter_name.is_some() {
256 warn!("Choosing an adapter is not supported on wasm.");
257 }
258
259 if selected_adapter.is_none() {
260 debug!(
261 "Searching for adapter with options: {:?}",
262 request_adapter_options
263 );
264 selected_adapter = instance
265 .request_adapter(&request_adapter_options)
266 .await
267 .ok();
268 }
269
270 let adapter = selected_adapter.expect(GPU_NOT_FOUND_ERROR_MESSAGE);
271 let adapter_info = adapter.get_info();
272 info!("{:?}", adapter_info);
273
274 if adapter_info.device_type == DeviceType::Cpu {
275 warn!(
276 "The selected adapter is using a driver that only supports software rendering. \
277 This is likely to be very slow. See https://bevy.org/learn/errors/b0006/"
278 );
279 }
280
281 let mut features = wgpu::Features::empty();
283 let mut limits = options.limits.clone();
284 if matches!(options.priority, WgpuSettingsPriority::Functionality) {
285 features = adapter.features();
286 if adapter_info.device_type == DeviceType::DiscreteGpu {
287 features.remove(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS);
292 }
293
294 limits = adapter.limits();
295 }
296
297 if let Some(disabled_features) = options.disabled_features {
299 features.remove(disabled_features);
300 }
301 features |= options.features;
303
304 if let Some(constrained_limits) = options.constrained_limits.as_ref() {
306 limits = wgpu::Limits {
312 max_texture_dimension_1d: limits
313 .max_texture_dimension_1d
314 .min(constrained_limits.max_texture_dimension_1d),
315 max_texture_dimension_2d: limits
316 .max_texture_dimension_2d
317 .min(constrained_limits.max_texture_dimension_2d),
318 max_texture_dimension_3d: limits
319 .max_texture_dimension_3d
320 .min(constrained_limits.max_texture_dimension_3d),
321 max_texture_array_layers: limits
322 .max_texture_array_layers
323 .min(constrained_limits.max_texture_array_layers),
324 max_bind_groups: limits
325 .max_bind_groups
326 .min(constrained_limits.max_bind_groups),
327 max_dynamic_uniform_buffers_per_pipeline_layout: limits
328 .max_dynamic_uniform_buffers_per_pipeline_layout
329 .min(constrained_limits.max_dynamic_uniform_buffers_per_pipeline_layout),
330 max_dynamic_storage_buffers_per_pipeline_layout: limits
331 .max_dynamic_storage_buffers_per_pipeline_layout
332 .min(constrained_limits.max_dynamic_storage_buffers_per_pipeline_layout),
333 max_sampled_textures_per_shader_stage: limits
334 .max_sampled_textures_per_shader_stage
335 .min(constrained_limits.max_sampled_textures_per_shader_stage),
336 max_samplers_per_shader_stage: limits
337 .max_samplers_per_shader_stage
338 .min(constrained_limits.max_samplers_per_shader_stage),
339 max_storage_buffers_per_shader_stage: limits
340 .max_storage_buffers_per_shader_stage
341 .min(constrained_limits.max_storage_buffers_per_shader_stage),
342 max_storage_textures_per_shader_stage: limits
343 .max_storage_textures_per_shader_stage
344 .min(constrained_limits.max_storage_textures_per_shader_stage),
345 max_uniform_buffers_per_shader_stage: limits
346 .max_uniform_buffers_per_shader_stage
347 .min(constrained_limits.max_uniform_buffers_per_shader_stage),
348 max_binding_array_elements_per_shader_stage: limits
349 .max_binding_array_elements_per_shader_stage
350 .min(constrained_limits.max_binding_array_elements_per_shader_stage),
351 max_binding_array_sampler_elements_per_shader_stage: limits
352 .max_binding_array_sampler_elements_per_shader_stage
353 .min(constrained_limits.max_binding_array_sampler_elements_per_shader_stage),
354 max_uniform_buffer_binding_size: limits
355 .max_uniform_buffer_binding_size
356 .min(constrained_limits.max_uniform_buffer_binding_size),
357 max_storage_buffer_binding_size: limits
358 .max_storage_buffer_binding_size
359 .min(constrained_limits.max_storage_buffer_binding_size),
360 max_vertex_buffers: limits
361 .max_vertex_buffers
362 .min(constrained_limits.max_vertex_buffers),
363 max_vertex_attributes: limits
364 .max_vertex_attributes
365 .min(constrained_limits.max_vertex_attributes),
366 max_vertex_buffer_array_stride: limits
367 .max_vertex_buffer_array_stride
368 .min(constrained_limits.max_vertex_buffer_array_stride),
369 max_push_constant_size: limits
370 .max_push_constant_size
371 .min(constrained_limits.max_push_constant_size),
372 min_uniform_buffer_offset_alignment: limits
373 .min_uniform_buffer_offset_alignment
374 .max(constrained_limits.min_uniform_buffer_offset_alignment),
375 min_storage_buffer_offset_alignment: limits
376 .min_storage_buffer_offset_alignment
377 .max(constrained_limits.min_storage_buffer_offset_alignment),
378 max_inter_stage_shader_components: limits
379 .max_inter_stage_shader_components
380 .min(constrained_limits.max_inter_stage_shader_components),
381 max_compute_workgroup_storage_size: limits
382 .max_compute_workgroup_storage_size
383 .min(constrained_limits.max_compute_workgroup_storage_size),
384 max_compute_invocations_per_workgroup: limits
385 .max_compute_invocations_per_workgroup
386 .min(constrained_limits.max_compute_invocations_per_workgroup),
387 max_compute_workgroup_size_x: limits
388 .max_compute_workgroup_size_x
389 .min(constrained_limits.max_compute_workgroup_size_x),
390 max_compute_workgroup_size_y: limits
391 .max_compute_workgroup_size_y
392 .min(constrained_limits.max_compute_workgroup_size_y),
393 max_compute_workgroup_size_z: limits
394 .max_compute_workgroup_size_z
395 .min(constrained_limits.max_compute_workgroup_size_z),
396 max_compute_workgroups_per_dimension: limits
397 .max_compute_workgroups_per_dimension
398 .min(constrained_limits.max_compute_workgroups_per_dimension),
399 max_buffer_size: limits
400 .max_buffer_size
401 .min(constrained_limits.max_buffer_size),
402 max_bindings_per_bind_group: limits
403 .max_bindings_per_bind_group
404 .min(constrained_limits.max_bindings_per_bind_group),
405 max_non_sampler_bindings: limits
406 .max_non_sampler_bindings
407 .min(constrained_limits.max_non_sampler_bindings),
408 max_blas_primitive_count: limits
409 .max_blas_primitive_count
410 .min(constrained_limits.max_blas_primitive_count),
411 max_blas_geometry_count: limits
412 .max_blas_geometry_count
413 .min(constrained_limits.max_blas_geometry_count),
414 max_tlas_instance_count: limits
415 .max_tlas_instance_count
416 .min(constrained_limits.max_tlas_instance_count),
417 max_color_attachments: limits
418 .max_color_attachments
419 .min(constrained_limits.max_color_attachments),
420 max_color_attachment_bytes_per_sample: limits
421 .max_color_attachment_bytes_per_sample
422 .min(constrained_limits.max_color_attachment_bytes_per_sample),
423 min_subgroup_size: limits
424 .min_subgroup_size
425 .max(constrained_limits.min_subgroup_size),
426 max_subgroup_size: limits
427 .max_subgroup_size
428 .min(constrained_limits.max_subgroup_size),
429 max_acceleration_structures_per_shader_stage: 0,
430 };
431 }
432
433 let device_descriptor = wgpu::DeviceDescriptor {
434 label: options.device_label.as_ref().map(AsRef::as_ref),
435 required_features: features,
436 required_limits: limits,
437 memory_hints: options.memory_hints.clone(),
438 trace: Trace::Off,
440 };
441
442 #[cfg(not(feature = "raw_vulkan_init"))]
443 let (device, queue) = adapter.request_device(&device_descriptor).await.unwrap();
444
445 #[cfg(feature = "raw_vulkan_init")]
446 let (device, queue) = raw_vulkan_init::create_raw_device(
447 &adapter,
448 &device_descriptor,
449 &raw_vulkan_init_settings,
450 &mut additional_vulkan_features,
451 )
452 .await
453 .unwrap();
454
455 debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
456 debug!("Configured wgpu adapter Features: {:#?}", device.features());
457
458 RenderResources(
459 RenderDevice::from(device),
460 RenderQueue(Arc::new(WgpuWrapper::new(queue))),
461 RenderAdapterInfo(WgpuWrapper::new(adapter_info)),
462 RenderAdapter(Arc::new(WgpuWrapper::new(adapter))),
463 RenderInstance(Arc::new(WgpuWrapper::new(instance))),
464 #[cfg(feature = "raw_vulkan_init")]
465 additional_vulkan_features,
466 )
467}
468
469pub struct RenderContext<'w> {
474 render_device: RenderDevice,
475 command_encoder: Option<CommandEncoder>,
476 command_buffer_queue: Vec<QueuedCommandBuffer<'w>>,
477 diagnostics_recorder: Option<Arc<DiagnosticsRecorder>>,
478}
479
480impl<'w> RenderContext<'w> {
481 pub fn new(
483 render_device: RenderDevice,
484 diagnostics_recorder: Option<DiagnosticsRecorder>,
485 ) -> Self {
486 Self {
487 render_device,
488 command_encoder: None,
489 command_buffer_queue: Vec::new(),
490 diagnostics_recorder: diagnostics_recorder.map(Arc::new),
491 }
492 }
493
494 pub fn render_device(&self) -> &RenderDevice {
496 &self.render_device
497 }
498
499 pub fn diagnostic_recorder(&self) -> impl RecordDiagnostics + use<> {
502 self.diagnostics_recorder.clone()
503 }
504
505 pub fn command_encoder(&mut self) -> &mut CommandEncoder {
507 self.command_encoder.get_or_insert_with(|| {
508 self.render_device
509 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default())
510 })
511 }
512
513 pub(crate) fn has_commands(&mut self) -> bool {
514 self.command_encoder.is_some() || !self.command_buffer_queue.is_empty()
515 }
516
517 pub fn begin_tracked_render_pass<'a>(
520 &'a mut self,
521 descriptor: RenderPassDescriptor<'_>,
522 ) -> TrackedRenderPass<'a> {
523 let command_encoder = self.command_encoder.get_or_insert_with(|| {
525 self.render_device
526 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default())
527 });
528
529 let render_pass = command_encoder.begin_render_pass(&descriptor);
530 TrackedRenderPass::new(&self.render_device, render_pass)
531 }
532
533 pub fn add_command_buffer(&mut self, command_buffer: CommandBuffer) {
539 self.flush_encoder();
540
541 self.command_buffer_queue
542 .push(QueuedCommandBuffer::Ready(command_buffer));
543 }
544
545 pub fn add_command_buffer_generation_task(
552 &mut self,
553 #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
554 task: impl FnOnce(RenderDevice) -> CommandBuffer + 'w + Send,
555 #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
556 task: impl FnOnce(RenderDevice) -> CommandBuffer + 'w,
557 ) {
558 self.flush_encoder();
559
560 self.command_buffer_queue
561 .push(QueuedCommandBuffer::Task(Box::new(task)));
562 }
563
564 pub fn finish(
571 mut self,
572 ) -> (
573 Vec<CommandBuffer>,
574 RenderDevice,
575 Option<DiagnosticsRecorder>,
576 ) {
577 self.flush_encoder();
578
579 let mut command_buffers = Vec::with_capacity(self.command_buffer_queue.len());
580
581 #[cfg(feature = "trace")]
582 let _command_buffer_generation_tasks_span =
583 info_span!("command_buffer_generation_tasks").entered();
584
585 #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
586 {
587 let mut task_based_command_buffers =
588 bevy_tasks::ComputeTaskPool::get().scope(|task_pool| {
589 for (i, queued_command_buffer) in
590 self.command_buffer_queue.into_iter().enumerate()
591 {
592 match queued_command_buffer {
593 QueuedCommandBuffer::Ready(command_buffer) => {
594 command_buffers.push((i, command_buffer));
595 }
596 QueuedCommandBuffer::Task(command_buffer_generation_task) => {
597 let render_device = self.render_device.clone();
598 task_pool.spawn(async move {
599 (i, command_buffer_generation_task(render_device))
600 });
601 }
602 }
603 }
604 });
605 command_buffers.append(&mut task_based_command_buffers);
606 }
607
608 #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
609 for (i, queued_command_buffer) in self.command_buffer_queue.into_iter().enumerate() {
610 match queued_command_buffer {
611 QueuedCommandBuffer::Ready(command_buffer) => {
612 command_buffers.push((i, command_buffer));
613 }
614 QueuedCommandBuffer::Task(command_buffer_generation_task) => {
615 let render_device = self.render_device.clone();
616 command_buffers.push((i, command_buffer_generation_task(render_device)));
617 }
618 }
619 }
620
621 #[cfg(feature = "trace")]
622 drop(_command_buffer_generation_tasks_span);
623
624 command_buffers.sort_unstable_by_key(|(i, _)| *i);
625
626 let mut command_buffers = command_buffers
627 .into_iter()
628 .map(|(_, cb)| cb)
629 .collect::<Vec<CommandBuffer>>();
630
631 let mut diagnostics_recorder = self.diagnostics_recorder.take().map(|v| {
632 Arc::try_unwrap(v)
633 .ok()
634 .expect("diagnostic recorder shouldn't be held longer than necessary")
635 });
636
637 if let Some(recorder) = &mut diagnostics_recorder {
638 let mut command_encoder = self
639 .render_device
640 .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
641 recorder.resolve(&mut command_encoder);
642 command_buffers.push(command_encoder.finish());
643 }
644
645 (command_buffers, self.render_device, diagnostics_recorder)
646 }
647
648 fn flush_encoder(&mut self) {
649 if let Some(encoder) = self.command_encoder.take() {
650 self.command_buffer_queue
651 .push(QueuedCommandBuffer::Ready(encoder.finish()));
652 }
653 }
654}
655
656enum QueuedCommandBuffer<'w> {
657 Ready(CommandBuffer),
658 #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
659 Task(Box<dyn FnOnce(RenderDevice) -> CommandBuffer + 'w + Send>),
660 #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
661 Task(Box<dyn FnOnce(RenderDevice) -> CommandBuffer + 'w>),
662}