Skip to main content

bevy_render/renderer/
mod.rs

1#[cfg(feature = "raw_vulkan_init")]
2pub mod raw_vulkan_init;
3mod render_context;
4mod render_device;
5mod wgpu_wrapper;
6
7pub use render_context::{
8    CurrentView, FlushCommands, PendingCommandBuffers, RenderContext, RenderContextState, ViewQuery,
9};
10pub use render_device::*;
11pub use wgpu_wrapper::WgpuWrapper;
12
13use crate::{
14    settings::{RenderResources, WgpuSettings, WgpuSettingsPriority},
15    view::{ExtractedWindows, ViewTarget},
16};
17use alloc::sync::Arc;
18use bevy_camera::NormalizedRenderTarget;
19use bevy_derive::{Deref, DerefMut};
20use bevy_ecs::schedule::ScheduleLabel;
21use bevy_ecs::{prelude::*, system::SystemState};
22#[cfg(feature = "trace")]
23use bevy_log::info_span;
24use bevy_log::{debug, info, warn};
25use bevy_render::camera::ExtractedCamera;
26use bevy_window::RawHandleWrapperHolder;
27use wgpu::{
28    Adapter, AdapterInfo, Backends, DeviceType, ForceShaderModelToken, Instance, Queue,
29    RequestAdapterOptions, Trace,
30};
31
32/// Schedule label for the root render graph schedule. This schedule runs once per frame
33/// in the [`render_system`] system and is responsible for driving the entire rendering process.
34#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)]
35pub struct RenderGraph;
36
37impl RenderGraph {
38    pub fn base_schedule() -> Schedule {
39        let mut schedule = Schedule::new(Self);
40        schedule.configure_sets(
41            (
42                RenderGraphSystems::Begin,
43                RenderGraphSystems::Render,
44                RenderGraphSystems::Submit,
45                RenderGraphSystems::Finish,
46            )
47                .chain(),
48        );
49        schedule
50    }
51}
52
53/// System sets for the root [`RenderGraph`] schedule.
54#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
55pub enum RenderGraphSystems {
56    /// Runs before rendering. Used for per-frame setup.
57    Begin,
58    /// The main rendering phase.
59    Render,
60    /// Submits pending command buffers generated during [`RenderGraphSystems::Render`]
61    Submit,
62    /// Runs after rendering and submit. Used for per-frame finalization.
63    Finish,
64}
65
66/// The main render system that drives the rendering process. This system runs the [`RenderGraph`]
67/// schedule, runs any finalization commands like screenshot captures and GPU readbacks, and
68/// calls present on swap chains that need to be presented.
69pub fn render_system(
70    world: &mut World,
71    state: &mut SystemState<Query<(&ViewTarget, &ExtractedCamera)>>,
72) {
73    #[cfg(feature = "trace")]
74    let _span = info_span!("main_render_schedule").entered();
75
76    world.run_schedule(RenderGraph);
77
78    {
79        let render_device = world.resource::<RenderDevice>();
80        let render_queue = world.resource::<RenderQueue>();
81
82        let mut encoder =
83            render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
84
85        crate::view::screenshot::submit_screenshot_commands(world, &mut encoder);
86        crate::gpu_readback::submit_readback_commands(world, &mut encoder);
87
88        render_queue.submit([encoder.finish()]);
89    }
90
91    {
92        #[cfg(feature = "trace")]
93        let _span = info_span!("present_frames").entered();
94
95        world.resource_scope(|world, mut windows: Mut<ExtractedWindows>| {
96            let views = state.get(world).unwrap();
97            for window in windows.values_mut() {
98                let view_needs_present = views.iter().any(|(view_target, camera)| {
99                    matches!(
100                        camera.target,
101                        Some(NormalizedRenderTarget::Window(w)) if w.entity() == window.entity
102                    ) && view_target.needs_present()
103                });
104
105                if view_needs_present || window.needs_initial_present {
106                    window.present();
107                    window.needs_initial_present = false;
108                }
109            }
110        });
111
112        #[cfg(feature = "tracing-tracy")]
113        bevy_log::event!(
114            bevy_log::Level::INFO,
115            message = "finished frame",
116            tracy.frame_mark = true
117        );
118    }
119
120    crate::view::screenshot::collect_screenshots(world);
121}
122
123/// This queue is used to enqueue tasks for the GPU to execute asynchronously.
124#[derive(Resource, Clone, Deref, DerefMut)]
125pub struct RenderQueue(pub Arc<WgpuWrapper<Queue>>);
126
127/// The handle to the physical device being used for rendering.
128/// See [`Adapter`] for more info.
129#[derive(Resource, Clone, Debug, Deref, DerefMut)]
130pub struct RenderAdapter(pub Arc<WgpuWrapper<Adapter>>);
131
132/// The GPU instance is used to initialize the [`RenderQueue`] and [`RenderDevice`],
133/// as well as to create [`WindowSurfaces`](crate::view::window::WindowSurfaces).
134#[derive(Resource, Clone, Deref, DerefMut)]
135pub struct RenderInstance(pub Arc<WgpuWrapper<Instance>>);
136
137/// The [`AdapterInfo`] of the adapter in use by the renderer.
138#[derive(Resource, Clone, Deref, DerefMut)]
139pub struct RenderAdapterInfo(pub WgpuWrapper<AdapterInfo>);
140
141const GPU_NOT_FOUND_ERROR_MESSAGE: &str = if cfg!(target_os = "linux") {
142    "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"
143} else {
144    "Unable to find a GPU! Make sure you have installed required drivers!"
145};
146
147#[cfg(not(target_family = "wasm"))]
148async fn find_adapter_by_name(
149    instance: &Instance,
150    options: &WgpuSettings,
151    compatible_surface: Option<&wgpu::Surface<'_>>,
152    adapter_name: &str,
153) -> Option<Adapter> {
154    for adapter in instance
155        .enumerate_adapters(options.backends.expect(
156            "The `backends` field of `WgpuSettings` must be set to use a specific adapter.",
157        ))
158        .await
159    {
160        bevy_log::trace!("Checking adapter: {:?}", adapter.get_info());
161        let info = adapter.get_info();
162        if let Some(surface) = compatible_surface
163            && !adapter.is_surface_supported(surface)
164        {
165            continue;
166        }
167
168        if info
169            .name
170            .to_lowercase()
171            .contains(&adapter_name.to_lowercase())
172        {
173            return Some(adapter);
174        }
175    }
176    None
177}
178
179/// Initializes the renderer by retrieving and preparing the GPU instance, device and queue
180/// for the specified backend.
181pub async fn initialize_renderer(
182    backends: Backends,
183    primary_window: Option<RawHandleWrapperHolder>,
184    options: &WgpuSettings,
185    #[cfg(feature = "raw_vulkan_init")]
186    raw_vulkan_init_settings: raw_vulkan_init::RawVulkanInitSettings,
187) -> RenderResources {
188    let instance_descriptor = wgpu::InstanceDescriptor {
189        backends,
190        flags: options.instance_flags,
191        memory_budget_thresholds: options.instance_memory_budget_thresholds,
192        display: None,
193        backend_options: wgpu::BackendOptions {
194            gl: wgpu::GlBackendOptions {
195                gles_minor_version: options.gles3_minor_version,
196                fence_behavior: wgpu::GlFenceBehavior::Normal,
197                debug_fns: wgpu::GlDebugFns::Auto,
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                force_shader_model: ForceShaderModelToken::default(),
205                agility_sdk: None,
206            },
207            noop: wgpu::NoopBackendOptions { enable: false },
208        },
209    };
210
211    #[cfg(not(feature = "raw_vulkan_init"))]
212    let instance = Instance::new(instance_descriptor);
213    #[cfg(feature = "raw_vulkan_init")]
214    let mut additional_vulkan_features = raw_vulkan_init::AdditionalVulkanFeatures::default();
215    #[cfg(feature = "raw_vulkan_init")]
216    let instance = raw_vulkan_init::create_raw_vulkan_instance(
217        instance_descriptor,
218        &raw_vulkan_init_settings,
219        &mut additional_vulkan_features,
220    );
221
222    let surface = primary_window.and_then(|wrapper| {
223        let maybe_handle = wrapper
224            .0
225            .lock()
226            .expect("Couldn't get the window handle in time for renderer initialization");
227        if let Some(wrapper) = maybe_handle.as_ref() {
228            // SAFETY: Plugins should be set up on the main thread.
229            let handle = unsafe { wrapper.get_handle() };
230            Some(
231                instance
232                    .create_surface(handle)
233                    .expect("Failed to create wgpu surface"),
234            )
235        } else {
236            None
237        }
238    });
239
240    let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER")
241        .map_or(options.force_fallback_adapter, |v| {
242            !(v.is_empty() || v == "0" || v == "false")
243        });
244
245    let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME")
246        .as_deref()
247        .map_or(options.adapter_name.clone(), |x| Some(x.to_lowercase()));
248
249    let request_adapter_options = RequestAdapterOptions {
250        power_preference: options.power_preference,
251        compatible_surface: surface.as_ref(),
252        force_fallback_adapter,
253    };
254
255    #[cfg(not(target_family = "wasm"))]
256    let mut selected_adapter = if let Some(adapter_name) = desired_adapter_name {
257        find_adapter_by_name(
258            &instance,
259            options,
260            request_adapter_options.compatible_surface,
261            &adapter_name,
262        )
263        .await
264    } else {
265        None
266    };
267    #[cfg(target_family = "wasm")]
268    let mut selected_adapter = None;
269
270    #[cfg(target_family = "wasm")]
271    if desired_adapter_name.is_some() {
272        warn!("Choosing an adapter is not supported on wasm.");
273    }
274
275    if selected_adapter.is_none() {
276        debug!(
277            "Searching for adapter with options: {:?}",
278            request_adapter_options
279        );
280        selected_adapter = instance
281            .request_adapter(&request_adapter_options)
282            .await
283            .ok();
284    }
285
286    let adapter = selected_adapter.expect(GPU_NOT_FOUND_ERROR_MESSAGE);
287    let adapter_info = adapter.get_info();
288    info!("{:?}", adapter_info);
289
290    if adapter_info.device_type == DeviceType::Cpu {
291        warn!(
292            "The selected adapter is using a driver that only supports software rendering. \
293             This is likely to be very slow. See https://bevy.org/learn/errors/b0006/"
294        );
295    }
296
297    // Maybe get features and limits based on what is supported by the adapter/backend
298    let mut features = wgpu::Features::empty();
299    let mut limits = options.limits.clone();
300    if matches!(options.priority, WgpuSettingsPriority::Functionality) {
301        features = adapter.features();
302        if adapter_info.device_type == DeviceType::DiscreteGpu {
303            // `MAPPABLE_PRIMARY_BUFFERS` can have a significant, negative performance impact for
304            // discrete GPUs due to having to transfer data across the PCI-E bus and so it
305            // should not be automatically enabled in this case. It is however beneficial for
306            // integrated GPUs.
307            features.remove(wgpu::Features::MAPPABLE_PRIMARY_BUFFERS);
308        }
309
310        limits = adapter.limits();
311    }
312
313    // Enforce the disabled features
314    if let Some(disabled_features) = options.disabled_features {
315        features.remove(disabled_features);
316    }
317    // NOTE: |= is used here to ensure that any explicitly-enabled features are respected.
318    features |= options.features;
319
320    // Enforce the limit constraints
321    if let Some(constrained_limits) = options.constrained_limits.as_ref() {
322        // NOTE: Respect the configured limits as an 'upper bound'. This means for 'max' limits, we
323        // take the minimum of the calculated limits according to the adapter/backend and the
324        // specified max_limits. For 'min' limits, take the maximum instead. This is intended to
325        // err on the side of being conservative. We can't claim 'higher' limits that are supported
326        // but we can constrain to 'lower' limits.
327        limits = limits.or_worse_values_from(constrained_limits);
328    }
329
330    let device_descriptor = wgpu::DeviceDescriptor {
331        label: options.device_label.as_ref().map(AsRef::as_ref),
332        required_features: features,
333        required_limits: limits,
334        // SAFETY: TODO, see https://github.com/bevyengine/bevy/issues/22082
335        experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() },
336        memory_hints: options.memory_hints.clone(),
337        // See https://github.com/gfx-rs/wgpu/issues/5974
338        trace: Trace::Off,
339    };
340
341    #[cfg(not(feature = "raw_vulkan_init"))]
342    let (device, queue) = adapter.request_device(&device_descriptor).await.unwrap();
343
344    #[cfg(feature = "raw_vulkan_init")]
345    let (device, queue) = raw_vulkan_init::create_raw_device(
346        &adapter,
347        &device_descriptor,
348        &raw_vulkan_init_settings,
349        &mut additional_vulkan_features,
350    )
351    .await
352    .unwrap();
353
354    debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
355    debug!("Configured wgpu adapter Features: {:#?}", device.features());
356
357    RenderResources(
358        RenderDevice::from(device),
359        RenderQueue(Arc::new(WgpuWrapper::new(queue))),
360        RenderAdapterInfo(WgpuWrapper::new(adapter_info)),
361        RenderAdapter(Arc::new(WgpuWrapper::new(adapter))),
362        RenderInstance(Arc::new(WgpuWrapper::new(instance))),
363        #[cfg(feature = "raw_vulkan_init")]
364        additional_vulkan_features,
365    )
366}