Skip to main content

bevy_render/
lib.rs

1//! # Useful Environment Variables
2//!
3//! Both `bevy_render` and `wgpu` have a number of environment variable options for changing the runtime behavior
4//! of both crates. Many of these may be useful in development or release environments.
5//!
6//! - `WGPU_DEBUG=1` enables debug labels, which can be useful in release builds.
7//! - `WGPU_VALIDATION=0` disables validation layers. This can help with particularly spammy errors.
8//! - `WGPU_FORCE_FALLBACK_ADAPTER=1` attempts to force software rendering. This typically matches what is used in CI.
9//! - `WGPU_ADAPTER_NAME` allows selecting a specific adapter by name.
10//! - `WGPU_SETTINGS_PRIO=webgl2` uses webgl2 limits.
11//! - `WGPU_SETTINGS_PRIO=webgpu` uses webgpu limits.
12//! - `VERBOSE_SHADER_ERROR=1` prints more detailed information about WGSL compilation errors, such as shader defs and shader entrypoint.
13
14#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
15#![expect(unsafe_code, reason = "Unsafe code is used to improve performance.")]
16#![cfg_attr(
17    any(docsrs, docsrs_dep),
18    expect(
19        internal_features,
20        reason = "rustdoc_internals is needed for fake_variadic"
21    )
22)]
23#![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
24#![cfg_attr(docsrs, feature(doc_cfg))]
25#![doc(
26    html_logo_url = "https://bevy.org/assets/icon.png",
27    html_favicon_url = "https://bevy.org/assets/icon.png"
28)]
29
30#[cfg(target_pointer_width = "16")]
31compile_error!("bevy_render cannot compile for a 16-bit platform.");
32
33extern crate alloc;
34extern crate core;
35
36// Required to make proc macros work in bevy itself.
37extern crate self as bevy_render;
38
39pub mod batching;
40pub mod camera;
41pub mod diagnostic;
42pub mod erased_render_asset;
43pub mod error_handler;
44pub mod extract_component;
45pub mod extract_instances;
46mod extract_param;
47pub mod extract_plugin;
48pub mod extract_resource;
49pub mod globals;
50pub mod gpu_component_array_buffer;
51pub mod gpu_readback;
52pub mod mesh;
53pub mod occlusion_culling;
54#[cfg(not(target_arch = "wasm32"))]
55pub mod pipelined_rendering;
56pub mod render_asset;
57pub mod render_phase;
58pub mod render_resource;
59pub mod renderer;
60pub mod settings;
61pub mod slab_allocator;
62pub mod storage;
63pub mod sync_component;
64pub mod sync_world;
65pub mod texture;
66pub mod uniform;
67pub mod view;
68
69/// The render prelude.
70///
71/// This includes the most common types in this crate, re-exported for your convenience.
72pub mod prelude {
73    #[doc(hidden)]
74    pub use crate::{
75        camera::NormalizedRenderTargetExt as _, renderer::RenderGraph, texture::ManualTextureViews,
76        view::Msaa, ExtractSchedule,
77    };
78}
79
80pub use extract_param::Extract;
81pub use extract_plugin::{ExtractSchedule, MainWorld};
82
83use crate::{
84    camera::CameraPlugin,
85    error_handler::{RenderErrorHandler, RenderState},
86    extract_plugin::ExtractPlugin,
87    gpu_readback::GpuReadbackPlugin,
88    mesh::{MeshRenderAssetPlugin, RenderMesh},
89    render_asset::prepare_assets,
90    render_resource::{PipelineCache, SparseBufferPlugin},
91    renderer::{render_system, RenderAdapterInfo, RenderGraph},
92    settings::{RenderCreation, WgpuLimits},
93    storage::StoragePlugin,
94    texture::TexturePlugin,
95    view::{ViewPlugin, WindowRenderPlugin},
96};
97use alloc::sync::Arc;
98use batching::gpu_preprocessing::BatchingPlugin;
99use bevy_app::{App, AppLabel, Plugin, SubApp};
100use bevy_asset::{AssetApp, AssetServer};
101use bevy_derive::Deref;
102use bevy_ecs::{
103    prelude::*,
104    schedule::{InternedScheduleLabel, ScheduleLabel},
105};
106use bevy_platform::time::Instant;
107use bevy_shader::{load_shader_library, Shader, ShaderLoader};
108use bevy_time::TimeSender;
109use bevy_window::{PrimaryWindow, RawHandleWrapperHolder};
110use bitflags::bitflags;
111use globals::GlobalsPlugin;
112use occlusion_culling::OcclusionCullingPlugin;
113use render_asset::{
114    extract_render_asset_bytes_per_frame, reset_render_asset_bytes_per_frame,
115    RenderAssetBytesPerFrame, RenderAssetBytesPerFrameLimiter,
116};
117use settings::RenderResources;
118use std::sync::{Mutex, OnceLock};
119
120/// Contains the default Bevy rendering backend based on wgpu.
121///
122/// Rendering is done in a [`SubApp`], which exchanges data with the main app
123/// between main schedule iterations.
124///
125/// Rendering can be executed between iterations of the main schedule,
126/// or it can be executed in parallel with main schedule when
127/// [`PipelinedRenderingPlugin`](pipelined_rendering::PipelinedRenderingPlugin) is enabled.
128#[derive(Default)]
129pub struct RenderPlugin {
130    pub render_creation: RenderCreation,
131    /// If `true`, disables asynchronous pipeline compilation.
132    /// This has no effect on macOS, Wasm, iOS, or without the `multi_threaded` feature.
133    pub synchronous_pipeline_compilation: bool,
134    /// Debugging flags that can optionally be set when constructing the renderer.
135    pub debug_flags: RenderDebugFlags,
136}
137
138bitflags! {
139    /// Debugging flags that can optionally be set when constructing the renderer.
140    #[derive(Clone, Copy, PartialEq, Default, Debug)]
141    pub struct RenderDebugFlags: u8 {
142        /// If true, this sets the `COPY_SRC` flag on indirect draw parameters
143        /// so that they can be read back to CPU.
144        ///
145        /// This is a debugging feature that may reduce performance. It
146        /// primarily exists for the `occlusion_culling` example.
147        const ALLOW_COPIES_FROM_INDIRECT_PARAMETERS = 1;
148    }
149}
150
151/// The systems sets of the default [`App`] rendering schedule.
152///
153/// These can be useful for ordering, but you almost never want to add your systems to these sets.
154#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
155pub enum RenderSystems {
156    /// This is used for applying the commands from the [`ExtractSchedule`]
157    ExtractCommands,
158    /// Prepare assets that have been created/modified/removed this frame.
159    PrepareAssets,
160    /// Prepares extracted meshes.
161    PrepareMeshes,
162    /// Create any additional views such as those used for shadow mapping.
163    CreateViews,
164    /// Specialize material meshes and shadow views.
165    Specialize,
166    /// Prepare any additional views such as those used for shadow mapping.
167    PrepareViews,
168    /// Queue drawable entities as phase items in render phases ready for
169    /// sorting (if necessary)
170    Queue,
171    /// A sub-set within [`Queue`](RenderSystems::Queue) where mesh entity queue systems are executed. Ensures `prepare_assets::<RenderMesh>` is completed.
172    QueueMeshes,
173    /// A sub-set within [`Queue`](RenderSystems::Queue) where meshes that have
174    /// become invisible or changed phases are removed from the bins.
175    QueueSweep,
176    // TODO: This could probably be moved in favor of a system ordering
177    // abstraction in `Render` or `Queue`
178    /// Sort the [`SortedRenderPhase`](render_phase::SortedRenderPhase)s and
179    /// [`BinKey`](render_phase::BinnedPhaseItem::BinKey)s here.
180    PhaseSort,
181    /// Prepare render resources from extracted data for the GPU based on their sorted order.
182    /// Create [`BindGroups`](render_resource::BindGroup) that depend on those data.
183    Prepare,
184    /// A sub-set within [`Prepare`](RenderSystems::Prepare) for initializing buffers, textures and uniforms for use in bind groups.
185    PrepareResources,
186    /// A sub-set within [`Prepare`](RenderSystems::Prepare) that creates batches for render phases.
187    PrepareResourcesBatchPhases,
188    /// A sub-set within [`Prepare`](RenderSystems::Prepare) that writes batches
189    /// for render phases to the GPU.
190    PrepareResourcesWritePhaseBuffers,
191    /// A sub-set within [`Prepare`](RenderSystems::Prepare) to collect phase buffers after
192    /// [`PrepareResourcesBatchPhases`](RenderSystems::PrepareResourcesBatchPhases) has run.
193    PrepareResourcesCollectPhaseBuffers,
194    /// Flush buffers after [`PrepareResources`](RenderSystems::PrepareResources), but before [`PrepareBindGroups`](RenderSystems::PrepareBindGroups).
195    PrepareResourcesFlush,
196    /// A sub-set within [`Prepare`](RenderSystems::Prepare) for constructing bind groups, or other data that relies on render resources prepared in [`PrepareResources`](RenderSystems::PrepareResources).
197    PrepareBindGroups,
198    /// Actual rendering happens here.
199    /// In most cases, only the render backend should insert resources here.
200    Render,
201    /// Cleanup render resources here.
202    Cleanup,
203    /// Final cleanup occurs: any entities with
204    /// [`TemporaryRenderEntity`](sync_world::TemporaryRenderEntity) will be despawned.
205    ///
206    /// Runs after [`Cleanup`](RenderSystems::Cleanup).
207    PostCleanup,
208}
209
210/// The startup schedule of the [`RenderApp`].
211/// This can potentially run multiple times, and not on a fresh render world.
212/// Every time a new [`RenderDevice`](renderer::RenderDevice) is acquired,
213/// this schedule runs to initialize any gpu resources needed for rendering on it.
214#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
215pub struct RenderStartup;
216
217/// Constructs a `T` resource with `from_world` and inserts it.
218pub fn init_gpu_resource<R: Resource + FromWorld>(world: &mut World) {
219    let res = R::from_world(world);
220    world.insert_resource(res);
221}
222
223/// Convenience methods for render-recovery-aware resource initialization.
224pub trait GpuResourceAppExt {
225    /// Causes the provided GPU resource to be re-initialized during [`RenderStartup`].
226    ///
227    /// This is useful when recovering from lost render devices.
228    ///
229    /// Shorthand for:
230    /// ```ignore
231    /// app.add_systems(RenderStartup, init_gpu_resource::<R>.ambiguous_with_all());
232    /// ```
233    fn init_gpu_resource<R: Resource + FromWorld>(&mut self) -> &mut Self;
234}
235
236impl GpuResourceAppExt for SubApp {
237    fn init_gpu_resource<R: Resource + FromWorld>(&mut self) -> &mut Self {
238        self.add_systems(RenderStartup, init_gpu_resource::<R>.ambiguous_with_all())
239    }
240}
241
242/// The render recovery schedule. This schedule runs the [`RenderScheduleOrder`] schedules if
243/// we are in [`RenderState::Ready`], and is otherwise hidden from users.
244#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
245struct RenderRecovery;
246
247/// Defines the schedules to be run for the rendering, including their order.
248///
249/// This is the same approach as [`MainScheduleOrder`](`bevy_app::MainScheduleOrder`).
250#[derive(Resource, Debug)]
251pub struct RenderScheduleOrder {
252    /// The labels to run for the rendering schedule (in the order they will be run).
253    pub labels: Vec<InternedScheduleLabel>,
254}
255
256impl Default for RenderScheduleOrder {
257    fn default() -> Self {
258        Self {
259            labels: vec![Render.intern()],
260        }
261    }
262}
263
264impl RenderScheduleOrder {
265    /// Adds the given `schedule` after the `after` schedule
266    pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) {
267        let index = self
268            .labels
269            .iter()
270            .position(|current| (**current).eq(&after))
271            .unwrap_or_else(|| panic!("Expected {after:?} to exist"));
272        self.labels.insert(index + 1, schedule.intern());
273    }
274
275    /// Adds the given `schedule` before the `before` schedule
276    pub fn insert_before(&mut self, before: impl ScheduleLabel, schedule: impl ScheduleLabel) {
277        let index = self
278            .labels
279            .iter()
280            .position(|current| (**current).eq(&before))
281            .unwrap_or_else(|| panic!("Expected {before:?} to exist"));
282        self.labels.insert(index, schedule.intern());
283    }
284}
285
286/// The main render schedule.
287#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
288pub struct Render;
289
290impl Render {
291    /// Sets up the base structure of the rendering [`Schedule`].
292    ///
293    /// The sets defined in this enum are configured to run in order.
294    pub fn base_schedule() -> Schedule {
295        use RenderSystems::*;
296
297        let mut schedule = Schedule::new(Self);
298
299        schedule.configure_sets(
300            (
301                ExtractCommands,
302                PrepareMeshes,
303                CreateViews,
304                Specialize,
305                PrepareViews,
306                Queue,
307                PhaseSort,
308                Prepare,
309                Render,
310                Cleanup,
311                PostCleanup,
312            )
313                .chain(),
314        );
315        schedule.ignore_ambiguity(Specialize, Specialize);
316
317        schedule.configure_sets((ExtractCommands, PrepareAssets, PrepareMeshes, Prepare).chain());
318        schedule.configure_sets(
319            (QueueMeshes, QueueSweep)
320                .chain()
321                .in_set(Queue)
322                .after(prepare_assets::<RenderMesh>),
323        );
324        schedule.configure_sets(
325            (
326                PrepareResources,
327                PrepareResourcesBatchPhases,
328                PrepareResourcesWritePhaseBuffers,
329                PrepareResourcesCollectPhaseBuffers,
330                PrepareResourcesFlush,
331                PrepareBindGroups,
332            )
333                .chain()
334                .in_set(Prepare),
335        );
336
337        schedule
338    }
339}
340
341#[derive(Resource, Default, Clone, Deref)]
342pub(crate) struct FutureRenderResources(Arc<Mutex<Option<RenderResources>>>);
343
344/// A label for the rendering sub-app.
345#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
346pub struct RenderApp;
347
348impl Plugin for RenderPlugin {
349    /// Initializes the renderer, sets up the [`RenderSystems`] and creates the rendering sub-app.
350    fn build(&self, app: &mut App) {
351        app.init_asset::<Shader>()
352            .init_asset_loader::<ShaderLoader>();
353        load_shader_library!(app, "maths.wgsl");
354        load_shader_library!(app, "color_operations.wgsl");
355        load_shader_library!(app, "bindless.wgsl");
356
357        if insert_future_resources(&self.render_creation, app.world_mut()) {
358            // We only create the render world and set up extraction if we
359            // have a rendering backend available.
360            app.add_plugins(ExtractPlugin {
361                pre_extract: error_handler::update_state,
362            });
363        };
364
365        app.add_plugins((
366            WindowRenderPlugin,
367            CameraPlugin,
368            ViewPlugin,
369            MeshRenderAssetPlugin,
370            GlobalsPlugin,
371            TexturePlugin,
372            BatchingPlugin {
373                debug_flags: self.debug_flags,
374            },
375            StoragePlugin,
376            GpuReadbackPlugin::default(),
377            OcclusionCullingPlugin,
378            SparseBufferPlugin,
379            #[cfg(feature = "tracing-tracy")]
380            diagnostic::RenderDiagnosticsPlugin,
381        ));
382
383        let (sender, receiver) = bevy_time::create_time_channels();
384        app.insert_resource(receiver);
385
386        let asset_server = app.world().resource::<AssetServer>().clone();
387        app.init_resource::<RenderAssetBytesPerFrame>()
388            .init_resource::<RenderErrorHandler>();
389        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
390            render_app.init_resource::<RenderScheduleOrder>();
391            render_app.init_resource::<RenderAssetBytesPerFrameLimiter>();
392            render_app.init_gpu_resource::<renderer::PendingCommandBuffers>();
393            render_app.insert_resource(sender);
394            render_app.insert_resource(asset_server);
395            render_app.insert_resource(RenderState::Initializing);
396            render_app.add_systems(
397                ExtractSchedule,
398                (
399                    extract_render_asset_bytes_per_frame,
400                    PipelineCache::extract_shaders,
401                ),
402            );
403
404            #[cfg(not(feature = "reflect_auto_register"))]
405            render_app.init_resource::<AppTypeRegistry>();
406
407            #[cfg(feature = "reflect_auto_register")]
408            render_app.insert_resource(AppTypeRegistry::new_with_derived_types());
409
410            #[cfg(feature = "reflect_functions")]
411            render_app.init_resource::<AppFunctionRegistry>();
412
413            render_app.add_schedule(RenderGraph::base_schedule());
414
415            render_app.init_schedule(RenderStartup);
416            render_app
417                .get_schedule_mut(RenderStartup)
418                .unwrap()
419                .set_executor(bevy_ecs::schedule::SingleThreadedExecutor::new());
420            render_app.update_schedule = Some(RenderRecovery.intern());
421            render_app.add_systems(
422                RenderRecovery,
423                (run_render_schedule.run_if(renderer_is_ready), send_time).chain(),
424            );
425            render_app.add_systems(
426                Render,
427                (
428                    (PipelineCache::process_pipeline_queue_system, render_system)
429                        .chain()
430                        .in_set(RenderSystems::Render),
431                    reset_render_asset_bytes_per_frame.in_set(RenderSystems::Cleanup),
432                ),
433            );
434        }
435    }
436
437    fn ready(&self, app: &App) -> bool {
438        // This is a little tricky. `FutureRenderResources` is added in `build`, which runs synchronously before `ready`.
439        // It is only added if there is a wgpu backend and thus the renderer can be created.
440        // Hence, if we try and get the resource and it is not present, that means we are ready, because we dont need it.
441        // On the other hand, if the resource is present, then we try and lock on it. The lock can fail, in which case
442        // we currently can assume that means the `FutureRenderResources` is in the act of being populated, because
443        // that is the only other place the lock may be held. If it is being populated, we can assume we're ready. This
444        // happens via the `and_then` falling through to the same `unwrap_or(true)` case as when there's no resource.
445        // If the lock succeeds, we can straightforwardly check if it is populated. If it is not, then we're not ready.
446        app.world()
447            .get_resource::<FutureRenderResources>()
448            .and_then(|frr| frr.try_lock().map(|locked| locked.is_some()).ok())
449            .unwrap_or(true)
450    }
451
452    fn finish(&self, app: &mut App) {
453        if let Some(future_render_resources) =
454            app.world_mut().remove_resource::<FutureRenderResources>()
455        {
456            let bevy_app::SubApps { main, sub_apps } = app.sub_apps_mut();
457            let render = sub_apps.get_mut(&RenderApp.intern()).unwrap();
458            let render_resources = future_render_resources.0.lock().unwrap().take().unwrap();
459
460            render_resources.unpack_into(
461                main.world_mut(),
462                render.world_mut(),
463                self.synchronous_pipeline_compilation,
464            );
465        }
466    }
467}
468
469fn renderer_is_ready(state: Res<RenderState>) -> bool {
470    matches!(*state, RenderState::Ready)
471}
472
473fn run_render_schedule(world: &mut World) {
474    world.resource_scope(|world, order: Mut<RenderScheduleOrder>| {
475        for &label in &order.labels {
476            let _ = world.try_run_schedule(label);
477        }
478    });
479}
480
481fn send_time(time_sender: Res<TimeSender>) {
482    // update the time and send it to the app world regardless of whether we render
483    if let Err(error) = time_sender.0.try_send(Instant::now()) {
484        match error {
485            bevy_time::TrySendError::Full(_) => {
486                panic!(
487                    "The TimeSender channel should always be empty during render. \
488                            You might need to add the bevy::core::time_system to your app."
489                );
490            }
491            bevy_time::TrySendError::Disconnected(_) => {
492                // ignore disconnected errors, the main world probably just got dropped during shutdown
493            }
494        }
495    }
496}
497
498/// Inserts a [`FutureRenderResources`] created from this [`RenderCreation`].
499///
500/// Returns true if creation was successful, false otherwise.
501fn insert_future_resources(render_creation: &RenderCreation, main_world: &mut World) -> bool {
502    let primary_window = main_world
503        .query_filtered::<&RawHandleWrapperHolder, With<PrimaryWindow>>()
504        .single(main_world)
505        .ok()
506        .cloned();
507
508    #[cfg(feature = "raw_vulkan_init")]
509    let raw_vulkan_init_settings = main_world
510        .get_resource::<renderer::raw_vulkan_init::RawVulkanInitSettings>()
511        .cloned()
512        .unwrap_or_default();
513
514    let future_resources = FutureRenderResources::default();
515    let success = render_creation.create_render(
516        future_resources.clone(),
517        primary_window,
518        #[cfg(feature = "raw_vulkan_init")]
519        raw_vulkan_init_settings,
520    );
521    if success {
522        // Note that `future_resources` is not necessarily populated here yet.
523        main_world.insert_resource(future_resources);
524    }
525    success
526}
527
528/// If the [`RenderAdapterInfo`] is a Qualcomm Adreno, returns its model number.
529///
530/// This lets us work around hardware bugs.
531pub fn get_adreno_model(adapter_info: &RenderAdapterInfo) -> Option<u32> {
532    if !cfg!(target_os = "android") {
533        return None;
534    }
535
536    let adreno_model = adapter_info.name.strip_prefix("Adreno (TM) ")?;
537
538    // Take suffixes into account (like Adreno 642L).
539    Some(
540        adreno_model
541            .chars()
542            .map_while(|c| c.to_digit(10))
543            .fold(0, |acc, digit| acc * 10 + digit),
544    )
545}
546
547/// Get the Mali driver version if the adapter is a Mali GPU.
548pub fn get_mali_driver_version(adapter_info: &RenderAdapterInfo) -> Option<u32> {
549    if !cfg!(target_os = "android") {
550        return None;
551    }
552
553    if !adapter_info.name.contains("Mali") {
554        return None;
555    }
556    let driver_info = &adapter_info.driver_info;
557    if let Some(start_pos) = driver_info.find("v1.r")
558        && let Some(end_pos) = driver_info[start_pos..].find('p')
559    {
560        let start_idx = start_pos + 4; // Skip "v1.r"
561        let end_idx = start_pos + end_pos;
562
563        return driver_info[start_idx..end_idx].parse::<u32>().ok();
564    }
565
566    None
567}
568
569pub fn get_pixel10_driver_version(adapter_info: &RenderAdapterInfo) -> Option<u32> {
570    if !cfg!(target_os = "android") {
571        return None;
572    }
573
574    if adapter_info.name != "PowerVR D-Series DXT-48-1536 MC1" {
575        return None;
576    }
577
578    let (_, driver_version) = adapter_info.driver_info.split_once('@')?;
579    driver_version.parse::<u32>().ok()
580}
581
582/// Returns true if storage buffers are unsupported on this platform or false
583/// if they are supported.
584pub fn storage_buffers_are_unsupported(limits: &WgpuLimits) -> bool {
585    static STORAGE_BUFFERS_UNSUPPORTED: OnceLock<bool> = OnceLock::new();
586    *STORAGE_BUFFERS_UNSUPPORTED.get_or_init(|| limits.max_storage_buffers_per_shader_stage == 0)
587}