diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 14b0750e29fbc..fdac02fa75725 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -47,7 +47,7 @@ use crate::{ camera::CameraPlugin, mesh::MeshPlugin, render_resource::{PipelineCache, Shader, ShaderLoader}, - renderer::{render_system, RenderInstance}, + renderer::{render_system, DefaultApi, RenderApi, RenderInstance}, settings::WgpuSettings, view::{ViewPlugin, WindowRenderPlugin}, }; @@ -178,20 +178,26 @@ impl Plugin for RenderPlugin { .init_asset_loader::() .init_debug_asset_loader::(); - let mut system_state: SystemState>> = - SystemState::new(&mut app.world); - let primary_window = system_state.get(&app.world); + if !app.world.contains_resource::() { + app.world.insert_resource(RenderApi(Box::new(DefaultApi))); + } + + let mut system_state: SystemState<( + Query<&RawHandleWrapper, With>, + Res, + )> = SystemState::new(&mut app.world); + let (primary_window, render_api) = system_state.get(&app.world); if let Some(backends) = self.wgpu_settings.backends { - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + let instance = render_api.new_instance(wgpu::InstanceDescriptor { backends, dx12_shader_compiler: self.wgpu_settings.dx12_shader_compiler.clone(), }); let surface = primary_window.get_single().ok().map(|wrapper| unsafe { // SAFETY: Plugins should be set up on the main thread. let handle = wrapper.get_handle(); - instance - .create_surface(&handle) + render_api + .create_surface(&instance, &handle) .expect("Failed to create wgpu surface") }); @@ -202,6 +208,7 @@ impl Plugin for RenderPlugin { }; let (device, queue, adapter_info, render_adapter) = futures_lite::future::block_on(renderer::initialize_renderer( + &*render_api.0, &instance, &self.wgpu_settings, &request_adapter_options, diff --git a/crates/bevy_render/src/renderer/api.rs b/crates/bevy_render/src/renderer/api.rs new file mode 100644 index 0000000000000..b03a42331a038 --- /dev/null +++ b/crates/bevy_render/src/renderer/api.rs @@ -0,0 +1,87 @@ +use std::{path::Path, pin::Pin}; + +use bevy_window::ThreadLockedRawWindowHandleWrapper; +use futures_lite::Future; +use wgpu::{ + Adapter, CreateSurfaceError, Device, DeviceDescriptor, Instance, InstanceDescriptor, Queue, + RequestAdapterOptions, RequestDeviceError, Surface, +}; + +/// This trait is intended to be used for hooking into the [`Instance`], [`Surface`], [`Adapter`] +/// and [`Device`] creation process of the [`RenderPlugin`](crate::RenderPlugin). +/// +/// To do this, insert a [`RenderApi`](crate::RenderApi) resource before adding the +/// [`RenderPlugin`](crate::RenderPlugin). +pub trait Api: Send + Sync { + /// Creates a new wgpu [`Instance`]. + /// + /// Implement this if you need custom instance creation logic, + /// e.g. you want to enable additional Vulkan instance extensions. + /// + /// For the default implementation, see [`Instance::new()`]. + fn new_instance(&self, instance_desc: InstanceDescriptor) -> Instance { + Instance::new(instance_desc) + } + + /// Creates a [`Surface`] from a raw window handle for a given [`Instance`]. + /// + /// Implement this if you need custom creation logic. + /// + /// For the default implementation, see [`Instance::create_surface()`]. + /// + /// # Safety + /// + /// - `raw_window_handle` must be a valid object to create a surface upon. + /// - `raw_window_handle` must remain valid until after the returned [`Surface`] is + /// dropped. + unsafe fn create_surface( + &self, + instance: &Instance, + raw_window_handle: &ThreadLockedRawWindowHandleWrapper, + ) -> Result { + instance.create_surface(raw_window_handle) + } + + /// Retrieves an [`Adapter`] which matches the given [`RequestAdapterOptions`] + /// for a given [`Instance`]. + /// + /// Implement this if you have additional requirements on the adapter, + /// e.g. you need to make sure that a Device supports a specific extension or feature. + /// + /// For the default implementation, see [`Instance::request_adapter()`]. + fn request_adapter( + &self, + instance: &Instance, + options: &RequestAdapterOptions, + ) -> RequestAdapterFuture { + Box::pin(instance.request_adapter(options)) + } + + /// Requests a connection to a physical device, creating a logical [`Device`] + /// for a given [`Adapter`]. + /// + /// Implement this if you need custom device creation logic, + /// e.g. you want to enable additional Vulkan device extensions. + /// + /// For the default implementation, see [`Adapter::request_device()`]. + fn request_device( + &self, + adapter: &Adapter, + desc: &DeviceDescriptor, + trace_path: Option<&Path>, + ) -> RequestDeviceFuture { + Box::pin(adapter.request_device(desc, trace_path)) + } +} + +/// An implementation of [`Api`], using only default method implementations. +pub struct DefaultApi; + +impl Api for DefaultApi {} + +/// The [`Future`] returned by [`Api::request_adapter()`] +pub type RequestAdapterFuture = Pin>>>; + +/// The [`Future`] returned by [`Api::request_device()`] +pub type RequestDeviceFuture = + Pin>>>; diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index b47f73b85e1b1..05e5e9e3e0c68 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -1,6 +1,8 @@ +mod api; mod graph_runner; mod render_device; +pub use api::*; use bevy_derive::{Deref, DerefMut}; use bevy_utils::tracing::{error, info, info_span}; pub use graph_runner::*; @@ -106,6 +108,11 @@ pub struct RenderInstance(pub Instance); #[derive(Resource, Clone, Deref, DerefMut)] pub struct RenderAdapterInfo(pub AdapterInfo); +/// The handle to the api used for rendering. +/// See [`Api`] for more info. +#[derive(Resource, Deref, DerefMut)] +pub struct RenderApi(pub Box); + const GPU_NOT_FOUND_ERROR_MESSAGE: &str = if cfg!(target_os = "linux") { "Unable to find a GPU! Make sure you have installed required drivers! For extra information, see: https://github.com/bevyengine/bevy/blob/latest/docs/linux_dependencies.md" } else { @@ -115,12 +122,13 @@ const GPU_NOT_FOUND_ERROR_MESSAGE: &str = if cfg!(target_os = "linux") { /// Initializes the renderer by retrieving and preparing the GPU instance, device and queue /// for the specified backend. pub async fn initialize_renderer( + api: &dyn Api, instance: &Instance, options: &WgpuSettings, request_adapter_options: &RequestAdapterOptions<'_>, ) -> (RenderDevice, RenderQueue, RenderAdapterInfo, RenderAdapter) { - let adapter = instance - .request_adapter(request_adapter_options) + let adapter = api + .request_adapter(instance, request_adapter_options) .await .expect(GPU_NOT_FOUND_ERROR_MESSAGE); @@ -257,8 +265,9 @@ pub async fn initialize_renderer( }; } - let (device, queue) = adapter + let (device, queue) = api .request_device( + &adapter, &wgpu::DeviceDescriptor { label: options.device_label.as_ref().map(|a| a.as_ref()), features, diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 343e8d15510b7..5adfbaadc93b4 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::{RenderAdapter, RenderDevice, RenderInstance}, + renderer::{RenderAdapter, RenderApi, RenderDevice, RenderInstance}, Extract, ExtractSchedule, RenderApp, RenderSet, }; use bevy_app::{App, Plugin}; @@ -27,11 +27,13 @@ pub enum WindowSystem { impl Plugin for WindowRenderPlugin { fn build(&self, app: &mut App) { + let api = app.world.remove_resource::().unwrap(); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::() .init_non_send_resource::() + .insert_resource(api) .add_system_to_schedule(ExtractSchedule, extract_windows) .configure_set(WindowSystem::Prepare.in_set(RenderSet::Prepare)) .add_system(prepare_windows.in_set(WindowSystem::Prepare)); @@ -167,15 +169,18 @@ pub struct WindowSurfaces { /// another alternative is to try to use [`ANGLE`](https://github.com/gfx-rs/wgpu#angle) and /// [`Backends::GL`](crate::settings::Backends::GL) if your GPU/drivers support `OpenGL 4.3` / `OpenGL ES 3.0` or /// later. +#[allow(clippy::too_many_arguments)] pub fn prepare_windows( // By accessing a NonSend resource, we tell the scheduler to put this system on the main thread, // which is necessary for some OS s _marker: NonSend, mut windows: ResMut, mut window_surfaces: ResMut, + render_api: Res, render_device: Res, render_instance: Res, render_adapter: Res, + mut msaa: ResMut, ) { for window in windows.windows.values_mut() { @@ -186,8 +191,8 @@ pub fn prepare_windows( .or_insert_with(|| unsafe { // NOTE: On some OSes this MUST be called from the main thread. // As of wgpu 0.15, only failable if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. - let surface = render_instance - .create_surface(&window.handle.get_handle()) + let surface = render_api + .create_surface(&render_instance, &window.handle.get_handle()) .expect("Failed to create wgpu surface"); let caps = surface.get_capabilities(&render_adapter); let formats = caps.formats;