Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Api trait for hooking into Instance and Device creation #7700

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -178,20 +178,26 @@ impl Plugin for RenderPlugin {
.init_asset_loader::<ShaderLoader>()
.init_debug_asset_loader::<ShaderLoader>();

let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
SystemState::new(&mut app.world);
let primary_window = system_state.get(&app.world);
if !app.world.contains_resource::<RenderApi>() {
app.world.insert_resource(RenderApi(Box::new(DefaultApi)));
}

let mut system_state: SystemState<(
Query<&RawHandleWrapper, With<PrimaryWindow>>,
Res<RenderApi>,
)> = 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")
});

Expand All @@ -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,
Expand Down
87 changes: 87 additions & 0 deletions crates/bevy_render/src/renderer/api.rs
Original file line number Diff line number Diff line change
@@ -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<Surface, CreateSurfaceError> {
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<Box<dyn Future<Output = Option<Adapter>>>>;

/// The [`Future`] returned by [`Api::request_device()`]
pub type RequestDeviceFuture =
Pin<Box<dyn Future<Output = Result<(Device, Queue), RequestDeviceError>>>>;
15 changes: 12 additions & 3 deletions crates/bevy_render/src/renderer/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -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<dyn Api>);

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 {
Expand All @@ -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);

Expand Down Expand Up @@ -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,
Expand Down
11 changes: 8 additions & 3 deletions crates/bevy_render/src/view/window.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -27,11 +27,13 @@ pub enum WindowSystem {

impl Plugin for WindowRenderPlugin {
fn build(&self, app: &mut App) {
let api = app.world.remove_resource::<RenderApi>().unwrap();
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ExtractedWindows>()
.init_resource::<WindowSurfaces>()
.init_non_send_resource::<NonSendMarker>()
.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));
Expand Down Expand Up @@ -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<NonSendMarker>,
mut windows: ResMut<ExtractedWindows>,
mut window_surfaces: ResMut<WindowSurfaces>,
render_api: Res<RenderApi>,
render_device: Res<RenderDevice>,
render_instance: Res<RenderInstance>,
render_adapter: Res<RenderAdapter>,

mut msaa: ResMut<Msaa>,
) {
for window in windows.windows.values_mut() {
Expand All @@ -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;
Expand Down