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