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