#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
#![expect(unsafe_code, reason = "Unsafe code is used to improve performance.")]
#![cfg_attr(
any(docsrs, docsrs_dep),
expect(
internal_features,
reason = "rustdoc_internals is needed for fake_variadic"
)
)]
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
#![doc(
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
#[cfg(target_pointer_width = "16")]
compile_error!("bevy_render cannot compile for a 16-bit platform.");
extern crate alloc;
extern crate core;
extern crate self as bevy_render;
pub mod alpha;
pub mod batching;
pub mod camera;
pub mod diagnostic;
pub mod erased_render_asset;
pub mod experimental;
pub mod extract_component;
pub mod extract_instances;
mod extract_param;
pub mod extract_resource;
pub mod globals;
pub mod gpu_component_array_buffer;
pub mod gpu_readback;
pub mod mesh;
#[cfg(not(target_arch = "wasm32"))]
pub mod pipelined_rendering;
pub mod render_asset;
pub mod render_graph;
pub mod render_phase;
pub mod render_resource;
pub mod renderer;
pub mod settings;
pub mod storage;
pub mod sync_component;
pub mod sync_world;
pub mod texture;
pub mod view;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
alpha::AlphaMode, camera::NormalizedRenderTargetExt as _, texture::ManualTextureViews,
view::Msaa, ExtractSchedule,
};
}
pub use extract_param::Extract;
use crate::{
camera::CameraPlugin,
gpu_readback::GpuReadbackPlugin,
mesh::{MeshPlugin, MorphPlugin, RenderMesh},
render_asset::prepare_assets,
render_resource::{init_empty_bind_group_layout, PipelineCache},
renderer::{render_system, RenderAdapterInfo},
settings::RenderCreation,
storage::StoragePlugin,
texture::TexturePlugin,
view::{ViewPlugin, WindowRenderPlugin},
};
use alloc::sync::Arc;
use batching::gpu_preprocessing::BatchingPlugin;
use bevy_app::{App, AppLabel, Plugin, SubApp};
use bevy_asset::{AssetApp, AssetServer};
use bevy_ecs::{
prelude::*,
schedule::{ScheduleBuildSettings, ScheduleLabel},
};
use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats};
use bevy_shader::{load_shader_library, Shader, ShaderLoader};
use bevy_utils::prelude::default;
use bevy_window::{PrimaryWindow, RawHandleWrapperHolder};
use bitflags::bitflags;
use core::ops::{Deref, DerefMut};
use experimental::occlusion_culling::OcclusionCullingPlugin;
use globals::GlobalsPlugin;
use render_asset::{
extract_render_asset_bytes_per_frame, reset_render_asset_bytes_per_frame,
RenderAssetBytesPerFrame, RenderAssetBytesPerFrameLimiter,
};
use settings::RenderResources;
use std::sync::Mutex;
use sync_world::{despawn_temporary_render_entities, entity_sync_system, SyncWorldPlugin};
#[derive(Default)]
pub struct RenderPlugin {
pub render_creation: RenderCreation,
pub synchronous_pipeline_compilation: bool,
pub debug_flags: RenderDebugFlags,
}
bitflags! {
#[derive(Clone, Copy, PartialEq, Default, Debug)]
pub struct RenderDebugFlags: u8 {
const ALLOW_COPIES_FROM_INDIRECT_PARAMETERS = 1;
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum RenderSystems {
ExtractCommands,
PrepareAssets,
PrepareMeshes,
ManageViews,
Queue,
QueueMeshes,
QueueSweep,
PhaseSort,
Prepare,
PrepareResources,
PrepareResourcesCollectPhaseBuffers,
PrepareResourcesFlush,
PrepareBindGroups,
Render,
Cleanup,
PostCleanup,
}
#[deprecated(since = "0.17.0", note = "Renamed to `RenderSystems`.")]
pub type RenderSet = RenderSystems;
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct RenderStartup;
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)]
pub struct Render;
impl Render {
pub fn base_schedule() -> Schedule {
use RenderSystems::*;
let mut schedule = Schedule::new(Self);
schedule.configure_sets(
(
ExtractCommands,
PrepareMeshes,
ManageViews,
Queue,
PhaseSort,
Prepare,
Render,
Cleanup,
PostCleanup,
)
.chain(),
);
schedule.configure_sets((ExtractCommands, PrepareAssets, PrepareMeshes, Prepare).chain());
schedule.configure_sets(
(QueueMeshes, QueueSweep)
.chain()
.in_set(Queue)
.after(prepare_assets::<RenderMesh>),
);
schedule.configure_sets(
(
PrepareResources,
PrepareResourcesCollectPhaseBuffers,
PrepareResourcesFlush,
PrepareBindGroups,
)
.chain()
.in_set(Prepare),
);
schedule
}
}
#[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash, Default)]
pub struct ExtractSchedule;
#[derive(Resource, Default)]
pub struct MainWorld(World);
impl Deref for MainWorld {
type Target = World;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for MainWorld {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub mod graph {
use crate::render_graph::RenderLabel;
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
pub struct CameraDriverLabel;
}
#[derive(Resource)]
struct FutureRenderResources(Arc<Mutex<Option<RenderResources>>>);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
pub struct RenderApp;
impl Plugin for RenderPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<Shader>()
.init_asset_loader::<ShaderLoader>();
match &self.render_creation {
RenderCreation::Manual(resources) => {
let future_render_resources_wrapper = Arc::new(Mutex::new(Some(resources.clone())));
app.insert_resource(FutureRenderResources(
future_render_resources_wrapper.clone(),
));
unsafe { initialize_render_app(app) };
}
RenderCreation::Automatic(render_creation) => {
if let Some(backends) = render_creation.backends {
let future_render_resources_wrapper = Arc::new(Mutex::new(None));
app.insert_resource(FutureRenderResources(
future_render_resources_wrapper.clone(),
));
let primary_window = app
.world_mut()
.query_filtered::<&RawHandleWrapperHolder, With<PrimaryWindow>>()
.single(app.world())
.ok()
.cloned();
let settings = render_creation.clone();
#[cfg(feature = "raw_vulkan_init")]
let raw_vulkan_init_settings = app
.world_mut()
.get_resource::<renderer::raw_vulkan_init::RawVulkanInitSettings>()
.cloned()
.unwrap_or_default();
let async_renderer = async move {
let render_resources = renderer::initialize_renderer(
backends,
primary_window,
&settings,
#[cfg(feature = "raw_vulkan_init")]
raw_vulkan_init_settings,
)
.await;
*future_render_resources_wrapper.lock().unwrap() = Some(render_resources);
};
#[cfg(target_arch = "wasm32")]
bevy_tasks::IoTaskPool::get()
.spawn_local(async_renderer)
.detach();
#[cfg(not(target_arch = "wasm32"))]
bevy_tasks::block_on(async_renderer);
unsafe { initialize_render_app(app) };
}
}
};
app.add_plugins((
WindowRenderPlugin,
CameraPlugin,
ViewPlugin,
MeshPlugin,
GlobalsPlugin,
MorphPlugin,
TexturePlugin,
BatchingPlugin {
debug_flags: self.debug_flags,
},
SyncWorldPlugin,
StoragePlugin,
GpuReadbackPlugin::default(),
OcclusionCullingPlugin,
#[cfg(feature = "tracing-tracy")]
diagnostic::RenderDiagnosticsPlugin,
));
app.init_resource::<RenderAssetBytesPerFrame>();
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<RenderAssetBytesPerFrameLimiter>();
render_app
.add_systems(ExtractSchedule, extract_render_asset_bytes_per_frame)
.add_systems(
Render,
reset_render_asset_bytes_per_frame.in_set(RenderSystems::Cleanup),
);
render_app.add_systems(RenderStartup, init_empty_bind_group_layout);
}
}
fn ready(&self, app: &App) -> bool {
app.world()
.get_resource::<FutureRenderResources>()
.and_then(|frr| frr.0.try_lock().map(|locked| locked.is_some()).ok())
.unwrap_or(true)
}
fn finish(&self, app: &mut App) {
load_shader_library!(app, "maths.wgsl");
load_shader_library!(app, "color_operations.wgsl");
load_shader_library!(app, "bindless.wgsl");
if let Some(future_render_resources) =
app.world_mut().remove_resource::<FutureRenderResources>()
{
let render_resources = future_render_resources.0.lock().unwrap().take().unwrap();
let RenderResources(device, queue, adapter_info, render_adapter, instance, ..) =
render_resources;
let compressed_image_format_support = CompressedImageFormatSupport(
CompressedImageFormats::from_features(device.features()),
);
app.insert_resource(device.clone())
.insert_resource(queue.clone())
.insert_resource(adapter_info.clone())
.insert_resource(render_adapter.clone())
.insert_resource(compressed_image_format_support);
let render_app = app.sub_app_mut(RenderApp);
#[cfg(feature = "raw_vulkan_init")]
{
let additional_vulkan_features: renderer::raw_vulkan_init::AdditionalVulkanFeatures =
render_resources.5;
render_app.insert_resource(additional_vulkan_features);
}
render_app
.insert_resource(instance)
.insert_resource(PipelineCache::new(
device.clone(),
render_adapter.clone(),
self.synchronous_pipeline_compilation,
))
.insert_resource(device)
.insert_resource(queue)
.insert_resource(render_adapter)
.insert_resource(adapter_info);
}
}
}
#[derive(Resource, Default)]
struct ScratchMainWorld(World);
fn extract(main_world: &mut World, render_world: &mut World) {
let scratch_world = main_world.remove_resource::<ScratchMainWorld>().unwrap();
let inserted_world = core::mem::replace(main_world, scratch_world.0);
render_world.insert_resource(MainWorld(inserted_world));
render_world.run_schedule(ExtractSchedule);
let inserted_world = render_world.remove_resource::<MainWorld>().unwrap();
let scratch_world = core::mem::replace(main_world, inserted_world.0);
main_world.insert_resource(ScratchMainWorld(scratch_world));
}
unsafe fn initialize_render_app(app: &mut App) {
app.init_resource::<ScratchMainWorld>();
let mut render_app = SubApp::new();
render_app.update_schedule = Some(Render.intern());
let mut extract_schedule = Schedule::new(ExtractSchedule);
extract_schedule.set_build_settings(ScheduleBuildSettings {
auto_insert_apply_deferred: false,
..default()
});
extract_schedule.set_apply_final_deferred(false);
render_app
.add_schedule(extract_schedule)
.add_schedule(Render::base_schedule())
.init_resource::<render_graph::RenderGraph>()
.insert_resource(app.world().resource::<AssetServer>().clone())
.add_systems(ExtractSchedule, PipelineCache::extract_shaders)
.add_systems(
Render,
(
apply_extract_commands.in_set(RenderSystems::ExtractCommands),
(PipelineCache::process_pipeline_queue_system, render_system)
.chain()
.in_set(RenderSystems::Render),
despawn_temporary_render_entities.in_set(RenderSystems::PostCleanup),
),
);
let mut should_run_startup = true;
render_app.set_extract(move |main_world, render_world| {
if should_run_startup {
render_world.run_schedule(RenderStartup);
should_run_startup = false;
}
{
#[cfg(feature = "trace")]
let _stage_span = tracing::info_span!("entity_sync").entered();
entity_sync_system(main_world, render_world);
}
extract(main_world, render_world);
});
let (sender, receiver) = bevy_time::create_time_channels();
render_app.insert_resource(sender);
app.insert_resource(receiver);
app.insert_sub_app(RenderApp, render_app);
}
fn apply_extract_commands(render_world: &mut World) {
render_world.resource_scope(|render_world, mut schedules: Mut<Schedules>| {
schedules
.get_mut(ExtractSchedule)
.unwrap()
.apply_deferred(render_world);
});
}
pub fn get_adreno_model(adapter_info: &RenderAdapterInfo) -> Option<u32> {
if !cfg!(target_os = "android") {
return None;
}
let adreno_model = adapter_info.name.strip_prefix("Adreno (TM) ")?;
Some(
adreno_model
.chars()
.map_while(|c| c.to_digit(10))
.fold(0, |acc, digit| acc * 10 + digit),
)
}
pub fn get_mali_driver_version(adapter_info: &RenderAdapterInfo) -> Option<u32> {
if !cfg!(target_os = "android") {
return None;
}
if !adapter_info.name.contains("Mali") {
return None;
}
let driver_info = &adapter_info.driver_info;
if let Some(start_pos) = driver_info.find("v1.r")
&& let Some(end_pos) = driver_info[start_pos..].find('p')
{
let start_idx = start_pos + 4;
let end_idx = start_pos + end_pos;
return driver_info[start_idx..end_idx].parse::<u32>().ok();
}
None
}