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