diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e57e51883df75..197e77ba6905e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,7 +144,7 @@ jobs: - name: Install Cargo APK run: cargo install --force cargo-apk - name: Build APK - run: cargo apk build --example android + run: cd examples/android && cargo apk build markdownlint: runs-on: ubuntu-latest @@ -288,7 +288,7 @@ jobs: source: './examples/' targets: '[ "./Cargo.toml", "./examples/README.md" ]' file-types: '[".rs"]' - exclude-folders: '["./examples/ios"]' + exclude-folders: '["./examples/ios", "./examples/android"]' exclude-files: '[]' check-unused-dependencies: diff --git a/Cargo.toml b/Cargo.toml index 812bcd4adf157..cb1a3841942d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/bevyengine/bevy" [workspace] exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"] -members = ["crates/*", "examples/ios", "tools/ci", "errors"] +members = ["crates/*", "examples/ios", "examples/android", "tools/ci", "errors"] [features] default = [ @@ -497,18 +497,3 @@ path = "examples/window/transparent_window.rs" [[example]] name = "window_settings" path = "examples/window/window_settings.rs" - -# Android -[[example]] -crate-type = ["cdylib"] -name = "android" -path = "examples/android/android.rs" - -[package.metadata.android] -apk_label = "Bevy Example" -assets = "assets" -res = "assets/android-res" -icon = "@mipmap/ic_launcher" -build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"] -min_sdk_version = 16 -target_sdk_version = 29 diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 88b06f93cbb75..36ed16d7081b5 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -30,7 +30,7 @@ use bevy_render::{ }, render_resource::*, renderer::RenderDevice, - texture::TextureCache, + texture::{TextureCache, DEFAULT_DEPTH_FORMAT}, view::{ExtractedView, Msaa, ViewDepthTexture}, RenderApp, RenderStage, RenderWorld, }; @@ -384,8 +384,7 @@ pub fn prepare_core_views_system( mip_level_count: 1, sample_count: msaa.samples, dimension: TextureDimension::D2, - format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24 - * bit depth for better performance */ + format: DEFAULT_DEPTH_FORMAT, usage: TextureUsages::RENDER_ATTACHMENT, }, ); diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index d939be0b593b1..5b0b66ae784cd 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -150,7 +150,7 @@ pub const MAX_POINT_LIGHT_SHADOW_MAPS: usize = 10; pub const MAX_DIRECTIONAL_LIGHTS: usize = 1; pub const POINT_SHADOW_LAYERS: u32 = (6 * MAX_POINT_LIGHT_SHADOW_MAPS) as u32; pub const DIRECTIONAL_SHADOW_LAYERS: u32 = MAX_DIRECTIONAL_LIGHTS as u32; -pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float; +pub const SHADOW_FORMAT: TextureFormat = DEFAULT_DEPTH_FORMAT; pub struct ShadowPipeline { pub view_layout: BindGroupLayout, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 3e8965aa28ee1..18f777be92308 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -17,7 +17,7 @@ use bevy_render::{ render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{std140::AsStd140, *}, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo}, + texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo, DEFAULT_DEPTH_FORMAT}, view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms}, RenderApp, RenderStage, }; @@ -527,7 +527,7 @@ impl SpecializedPipeline for MeshPipeline { strip_index_format: None, }, depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, + format: DEFAULT_DEPTH_FORMAT, depth_write_enabled, depth_compare: CompareFunction::Greater, stencil: StencilState { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 788dd9365b48f..2fa4ed29884b8 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -27,7 +27,7 @@ pub mod prelude { }; } -use bevy_utils::tracing::debug; +use bevy_utils::tracing::{debug, info}; pub use once_cell; use crate::{ @@ -113,35 +113,23 @@ impl Plugin for RenderPlugin { .get_resource::() .cloned() .unwrap_or_default(); - app.add_asset::() .init_asset_loader::() .register_type::(); if let Some(backends) = options.backends { let instance = wgpu::Instance::new(backends); - let surface = { - let world = app.world.cell(); - let windows = world.get_resource_mut::().unwrap(); - let raw_handle = windows.get_primary().map(|window| unsafe { - let handle = window.raw_window_handle().get_handle(); - instance.create_surface(&handle) - }); - raw_handle - }; - let request_adapter_options = wgpu::RequestAdapterOptions { - power_preference: options.power_preference, - compatible_surface: surface.as_ref(), - ..Default::default() - }; - let (device, queue) = futures_lite::future::block_on(renderer::initialize_renderer( - &instance, - &mut options, - &request_adapter_options, - )); - debug!("Configured wgpu adapter Limits: {:#?}", &options.limits); - debug!("Configured wgpu adapter Features: {:#?}", &options.features); + let (device, adapter, queue) = futures_lite::future::block_on( + renderer::initialize_renderer(app, &instance, backends, &mut options), + ); + info!("{:?}", adapter.get_info()); + debug!("Configured wgpu adapter Limits: {:#?}", &adapter.limits()); + debug!( + "Configured wgpu adapter Features: {:#?}", + &adapter.features() + ); app.insert_resource(device.clone()) + .insert_resource(adapter.clone()) .insert_resource(queue.clone()) .insert_resource(options.clone()) .init_resource::() @@ -170,6 +158,7 @@ impl Plugin for RenderPlugin { .add_stage(RenderStage::Cleanup, SystemStage::parallel()) .insert_resource(instance) .insert_resource(device) + .insert_resource(adapter) .insert_resource(queue) .insert_resource(options) .insert_resource(render_pipeline_cache) diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 1e5486f90b1eb..fd83c83cbcfd6 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -1,7 +1,7 @@ mod graph_runner; mod render_device; -use bevy_utils::tracing::{info, info_span}; +use bevy_utils::tracing::info_span; pub use graph_runner::*; pub use render_device::*; @@ -12,7 +12,7 @@ use crate::{ }; use bevy_ecs::prelude::*; use std::sync::Arc; -use wgpu::{CommandEncoder, Instance, Queue, RequestAdapterOptions}; +use wgpu::{Adapter, CommandEncoder, Instance, Queue}; /// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame. pub fn render_system(world: &mut World) { @@ -57,6 +57,8 @@ pub fn render_system(world: &mut World) { /// This queue is used to enqueue tasks for the GPU to execute asynchronously. pub type RenderQueue = Arc; +pub type RenderAdapter = Arc; + /// The GPU instance is used to initialize the [`RenderQueue`] and [`RenderDevice`], /// aswell as to create [`WindowSurfaces`](crate::view::window::WindowSurfaces). pub type RenderInstance = Instance; @@ -69,16 +71,86 @@ pub const DEFAULT_DISABLED_WGPU_FEATURES: wgpu::Features = wgpu::Features::MAPPA /// Initializes the renderer by retrieving and preparing the GPU instance, device and queue /// for the specified backend. pub async fn initialize_renderer( - instance: &Instance, + #[cfg_attr(not(target_arch = "wasm32"), allow(unused))] app: &mut bevy_app::App, + instance: &wgpu::Instance, + #[cfg_attr(target_arch = "wasm32", allow(unused))] backends: wgpu::Backends, options: &mut WgpuOptions, - request_adapter_options: &RequestAdapterOptions<'_>, -) -> (RenderDevice, RenderQueue) { - let adapter = instance - .request_adapter(request_adapter_options) - .await - .expect("Unable to find a GPU! Make sure you have installed required drivers!"); +) -> (RenderDevice, RenderAdapter, RenderQueue) { + let adapter = { + #[cfg(not(target_arch = "wasm32"))] + { + use crate::texture::{BevyDefault as _, DEFAULT_DEPTH_FORMAT}; + let mut adapters: Vec = instance.enumerate_adapters(backends).collect(); + let (mut integrated, mut discrete, mut virt, mut cpu, mut other) = + (None, None, None, None, None); - info!("{:?}", adapter.get_info()); + for (i, adapter) in adapters.iter().enumerate() { + let default_texture_format_features = + adapter.get_texture_format_features(wgpu::TextureFormat::bevy_default()); + let default_depth_format_features = + adapter.get_texture_format_features(DEFAULT_DEPTH_FORMAT); + if default_texture_format_features + .allowed_usages + .contains(wgpu::TextureUsages::RENDER_ATTACHMENT) + && default_depth_format_features + .allowed_usages + .contains(wgpu::TextureUsages::RENDER_ATTACHMENT) + { + let info = adapter.get_info(); + match info.device_type { + wgpu::DeviceType::IntegratedGpu => { + integrated = integrated.or(Some(i)); + } + wgpu::DeviceType::DiscreteGpu => { + discrete = discrete.or(Some(i)); + } + wgpu::DeviceType::VirtualGpu => { + virt = virt.or(Some(i)); + } + wgpu::DeviceType::Cpu => { + cpu = cpu.or(Some(i)); + } + wgpu::DeviceType::Other => { + other = other.or(Some(i)); + } + } + } + } + + let preferred_gpu_index = match options.power_preference { + wgpu::PowerPreference::LowPower => { + integrated.or(other).or(discrete).or(virt).or(cpu) + } + wgpu::PowerPreference::HighPerformance => { + discrete.or(other).or(integrated).or(virt).or(cpu) + } + } + .expect("Unable to find a GPU! Make sure you have installed required drivers!"); + + adapters.swap_remove(preferred_gpu_index) + } + + #[cfg(target_arch = "wasm32")] + { + let surface = { + let world = app.world.cell(); + let windows = world.get_resource_mut::().unwrap(); + let raw_handle = windows.get_primary().map(|window| unsafe { + let handle = window.raw_window_handle().get_handle(); + instance.create_surface(&handle) + }); + raw_handle + }; + instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: options.power_preference, + compatible_surface: surface.as_ref(), + ..Default::default() + }) + .await + .expect("Unable to find a GPU! Make sure you have installed required drivers!") + } + }; #[cfg(feature = "wgpu_trace")] let trace_path = { @@ -108,9 +180,10 @@ pub async fn initialize_renderer( ) .await .unwrap(); + let adapter = Arc::new(adapter); let device = Arc::new(device); let queue = Arc::new(queue); - (RenderDevice::from(device), queue) + (RenderDevice::from(device), adapter, queue) } /// The context with all information required to interact with the GPU. diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index e6658195bff54..b0eeb023496e5 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -43,6 +43,9 @@ impl Plugin for ImagePlugin { } } +// PERF: vulkan docs recommend using 24 bit depth for better performance +pub const DEFAULT_DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + pub trait BevyDefault { fn bevy_default() -> Self; } diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 94bf8a21c0fdb..5aa7de8e08986 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -1,6 +1,6 @@ use crate::{ render_resource::TextureView, - renderer::{RenderDevice, RenderInstance}, + renderer::{RenderAdapter, RenderDevice, RenderInstance}, texture::BevyDefault, RenderApp, RenderStage, RenderWorld, }; @@ -120,6 +120,7 @@ pub fn prepare_windows( _marker: NonSend, mut windows: ResMut, mut window_surfaces: ResMut, + render_adapter: Res, render_device: Res, render_instance: Res, ) { @@ -133,8 +134,12 @@ pub fn prepare_windows( render_instance.create_surface(&window.handle.get_handle()) }); + let surface_format = surface + .get_preferred_format(&render_adapter) + .unwrap_or_else(TextureFormat::bevy_default); + let swap_chain_descriptor = wgpu::SurfaceConfiguration { - format: TextureFormat::bevy_default(), + format: surface_format, width: window.physical_width, height: window.physical_height, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index a833ecf44b57f..8884f9a3908c8 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -36,6 +36,8 @@ impl Plugin for WinitPlugin { .set_runner(winit_runner) .add_system_to_stage(CoreStage::PostUpdate, change_window.exclusive_system()); let event_loop = EventLoop::new(); + // TODO: Required for WebGL. Currently breaks only android initialization. Should be a part of init system + #[cfg(not(target_os = "android"))] handle_initial_window_events(&mut app.world, &event_loop); app.insert_non_send_resource(event_loop); } @@ -495,12 +497,12 @@ pub fn winit_runner_with(mut app: App) { active = true; } event::Event::MainEventsCleared => { - handle_create_window_events( - &mut app.world, - event_loop, - &mut create_window_event_reader, - ); if active { + handle_create_window_events( + &mut app.world, + event_loop, + &mut create_window_event_reader, + ); app.update(); } } @@ -537,6 +539,7 @@ fn handle_create_window_events( } } +#[cfg(not(target_os = "android"))] fn handle_initial_window_events(world: &mut World, event_loop: &EventLoop<()>) { let world = world.cell(); let mut winit_windows = world.get_resource_mut::().unwrap(); diff --git a/examples/README.md b/examples/README.md index 1ddbd01a583fa..f9a8f9847b3a4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -277,7 +277,8 @@ When using `NDK (Side by side)`, the environment variable `ANDROID_NDK_ROOT` mus To run on a device setup for Android development, run: ```sh -cargo apk run --example android +cd examples/android +cargo apk run ``` :warning: At this time Bevy does not work in Android Emulator. @@ -299,7 +300,7 @@ Bevy by default targets Android API level 29 in its examples which is the Users of older phones may want to use an older API when testing. -To use a different API, the following fields must be updated in Cargo.toml: +To use a different API, the following fields must be updated in [Cargo.toml](./android/Cargo.toml): ```toml [package.metadata.android] @@ -307,10 +308,6 @@ target_sdk_version = >>API<< min_sdk_version = >>API or less<< ``` -Example | File | Description ---- | --- | --- -`android` | [`android/android.rs`](./android/android.rs) | The `3d/3d_scene.rs` example for Android - ## iOS ### Setup diff --git a/examples/android/Cargo.toml b/examples/android/Cargo.toml new file mode 100644 index 0000000000000..762974fd4866e --- /dev/null +++ b/examples/android/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "bevy_android_example" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +bevy = { path = "../../", features = [ + #"bevy_audio", TODO: https://github.com/bevyengine/bevy/issues/3648 + "bevy_winit", + "render", + #"vorbis", + "filesystem_watcher", + "png" +], default-features = false} + + +[package.metadata.android] +apk_label = "Bevy Example" +assets = "../../assets" +res = "assets/android-res" +icon = "@mipmap/ic_launcher" +build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"] +min_sdk_version = 16 +target_sdk_version = 29 diff --git a/examples/android/android.rs b/examples/android/android.rs deleted file mode 100644 index 006dd37486a04..0000000000000 --- a/examples/android/android.rs +++ /dev/null @@ -1,42 +0,0 @@ -use bevy::prelude::*; - -// the `bevy_main` proc_macro generates the required android boilerplate -#[bevy_main] -fn main() { - App::new() - .insert_resource(Msaa { samples: 2 }) - .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .run(); -} - -/// set up a simple 3D scene -fn setup( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - // plane - commands.spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), - material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), - ..Default::default() - }); - // cube - commands.spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), - material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), - transform: Transform::from_xyz(0.0, 0.5, 0.0), - ..Default::default() - }); - // light - commands.spawn_bundle(PointLightBundle { - transform: Transform::from_xyz(4.0, 8.0, 4.0), - ..Default::default() - }); - // camera - commands.spawn_bundle(PerspectiveCameraBundle { - transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), - ..Default::default() - }); -} diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs new file mode 100644 index 0000000000000..f6256386b9f0e --- /dev/null +++ b/examples/android/src/lib.rs @@ -0,0 +1,57 @@ +use bevy::{ + prelude::*, + render::options::{Backends, WgpuOptions}, + window::WindowMode, +}; + +// the `bevy_main` proc_macro generates the required ios boilerplate +#[bevy_main] +fn main() { + App::new() + .insert_resource(WindowDescriptor { + vsync: true, + resizable: false, + mode: WindowMode::BorderlessFullscreen, + ..Default::default() + }) + .insert_resource(WgpuOptions { + backends: Some(Backends::all()), + ..Default::default() + }) + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(animate_sprite_system) + .run(); +} + +fn animate_sprite_system( + time: Res