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