Skip to content

Commit 7a86925

Browse files
cartJMS55
andauthored
Opt-in raw vulkan initialization with hooks (#20565)
# Objective Currently registering additional required Vulkan features requires either hard-coding them into `bevy_render` (see the current [DLSS proposal](#19864)), or forcing the plugin to take full manual control over wgpu initialization. Neither is an acceptable or scalable option for a modular engine like Bevy. ## Solution * Add a new `raw_vulkan_init` Cargo feature, that when enabled switches to wgpu's raw Vulkan init path, which accepts callbacks that allow checking and requiring additional Vulkan features. * Add a new `WgpuRawVulkanInitSettings` resource, which provides wgpu Vulkan Instance and Device init callbacks, which can be used to detect and register vulkan features. * These callbacks can register arbitrary features in the new `AdditionalVulkanFeatures` resource, which is inserted into the RenderApp at the same time that the RenderDevice is. This enables plugins to register initialization callbacks, which must happen _before_ RenderPlugin. They can then feed off of `AdditionalVulkanFeatures`, after the renderer is initialized, to detect if a given feature is supported. Due to the current lifecycles, this is best accomplished with either: * A separate "init plugin" that is registered before RenderPlugin, and a "logic plugin" that does everything else. This should be used if the plugin logic needs `Plugin::build()` access to render device state, which needs to be registered _after_ RenderPlugin to have access. The proposed DLSS feature needs this pattern. * A single "init plugin" that is registered before RenderPlugin and does everything. Use this pattern if you can as it is simpler. With deferred plugin init, we could remove the need for this split. --------- Co-authored-by: JMS55 <[email protected]>
1 parent 6daa1b8 commit 7a86925

File tree

8 files changed

+325
-114
lines changed

8 files changed

+325
-114
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"]
327327
# Statically linked DXC shader compiler for DirectX 12
328328
statically-linked-dxc = ["bevy_internal/statically-linked-dxc"]
329329

330+
# Forces the wgpu instance to be initialized using the raw Vulkan HAL, enabling additional configuration
331+
raw_vulkan_init = ["bevy_internal/raw_vulkan_init"]
332+
330333
# Tracing support, saving a file in Chrome Tracing format
331334
trace_chrome = ["trace", "bevy_internal/trace_chrome"]
332335

crates/bevy_internal/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ spirv_shader_passthrough = ["bevy_render/spirv_shader_passthrough"]
6262
# TODO: When wgpu switches to DirectX 12 instead of Vulkan by default on windows, make this a default feature
6363
statically-linked-dxc = ["bevy_render/statically-linked-dxc"]
6464

65+
# Forces the wgpu instance to be initialized using the raw Vulkan HAL, enabling additional configuration
66+
raw_vulkan_init = ["bevy_render/raw_vulkan_init"]
67+
6568
# Include tonemapping LUT KTX2 files.
6669
tonemapping_luts = ["bevy_core_pipeline/tonemapping_luts"]
6770
# Include Bluenoise texture for environment map generation.

crates/bevy_render/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ spirv_shader_passthrough = ["wgpu/spirv"]
3232
# TODO: When wgpu switches to DirectX 12 instead of Vulkan by default on windows, make this a default feature
3333
statically-linked-dxc = ["wgpu/static-dxc"]
3434

35+
# Forces the wgpu instance to be initialized using the raw Vulkan HAL, enabling additional configuration
36+
raw_vulkan_init = ["wgpu/vulkan"]
37+
3538
trace = ["profiling"]
3639
tracing-tracy = ["dep:tracy-client"]
3740
ci_limits = []

crates/bevy_render/src/lib.rs

Lines changed: 30 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ use crate::{
8585
mesh::{MeshPlugin, MorphPlugin, RenderMesh},
8686
render_asset::prepare_assets,
8787
render_resource::{init_empty_bind_group_layout, PipelineCache},
88-
renderer::{render_system, RenderAdapterInfo, RenderInstance},
88+
renderer::{render_system, RenderAdapterInfo},
8989
settings::RenderCreation,
9090
storage::StoragePlugin,
9191
texture::TexturePlugin,
@@ -114,7 +114,6 @@ use render_asset::{
114114
use settings::RenderResources;
115115
use std::sync::Mutex;
116116
use sync_world::{despawn_temporary_render_entities, entity_sync_system, SyncWorldPlugin};
117-
use tracing::debug;
118117
pub use wgpu_wrapper::WgpuWrapper;
119118

120119
/// Contains the default Bevy rendering backend based on wgpu.
@@ -329,76 +328,29 @@ impl Plugin for RenderPlugin {
329328
.single(app.world())
330329
.ok()
331330
.cloned();
331+
332332
let settings = render_creation.clone();
333+
334+
#[cfg(feature = "raw_vulkan_init")]
335+
let raw_vulkan_init_settings = app
336+
.world_mut()
337+
.get_resource::<renderer::raw_vulkan_init::RawVulkanInitSettings>()
338+
.cloned()
339+
.unwrap_or_default();
340+
333341
let async_renderer = async move {
334-
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
342+
let render_resources = renderer::initialize_renderer(
335343
backends,
336-
flags: settings.instance_flags,
337-
memory_budget_thresholds: settings.instance_memory_budget_thresholds,
338-
backend_options: wgpu::BackendOptions {
339-
gl: wgpu::GlBackendOptions {
340-
gles_minor_version: settings.gles3_minor_version,
341-
fence_behavior: wgpu::GlFenceBehavior::Normal,
342-
},
343-
dx12: wgpu::Dx12BackendOptions {
344-
shader_compiler: settings.dx12_shader_compiler.clone(),
345-
},
346-
noop: wgpu::NoopBackendOptions { enable: false },
347-
},
348-
});
349-
350-
let surface = primary_window.and_then(|wrapper| {
351-
let maybe_handle = wrapper.0.lock().expect(
352-
"Couldn't get the window handle in time for renderer initialization",
353-
);
354-
if let Some(wrapper) = maybe_handle.as_ref() {
355-
// SAFETY: Plugins should be set up on the main thread.
356-
let handle = unsafe { wrapper.get_handle() };
357-
Some(
358-
instance
359-
.create_surface(handle)
360-
.expect("Failed to create wgpu surface"),
361-
)
362-
} else {
363-
None
364-
}
365-
});
366-
367-
let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER")
368-
.map_or(settings.force_fallback_adapter, |v| {
369-
!(v.is_empty() || v == "0" || v == "false")
370-
});
371-
372-
let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME")
373-
.as_deref()
374-
.map_or(settings.adapter_name.clone(), |x| Some(x.to_lowercase()));
375-
376-
let request_adapter_options = wgpu::RequestAdapterOptions {
377-
power_preference: settings.power_preference,
378-
compatible_surface: surface.as_ref(),
379-
force_fallback_adapter,
380-
};
381-
382-
let (device, queue, adapter_info, render_adapter) =
383-
renderer::initialize_renderer(
384-
&instance,
385-
&settings,
386-
&request_adapter_options,
387-
desired_adapter_name,
388-
)
389-
.await;
390-
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
391-
debug!("Configured wgpu adapter Features: {:#?}", device.features());
392-
let mut future_render_resources_inner =
393-
future_render_resources_wrapper.lock().unwrap();
394-
*future_render_resources_inner = Some(RenderResources(
395-
device,
396-
queue,
397-
adapter_info,
398-
render_adapter,
399-
RenderInstance(Arc::new(WgpuWrapper::new(instance))),
400-
));
344+
primary_window,
345+
&settings,
346+
#[cfg(feature = "raw_vulkan_init")]
347+
raw_vulkan_init_settings,
348+
)
349+
.await;
350+
351+
*future_render_resources_wrapper.lock().unwrap() = Some(render_resources);
401352
};
353+
402354
// In wasm, spawn a task and detach it for execution
403355
#[cfg(target_arch = "wasm32")]
404356
bevy_tasks::IoTaskPool::get()
@@ -461,8 +413,9 @@ impl Plugin for RenderPlugin {
461413
if let Some(future_render_resources) =
462414
app.world_mut().remove_resource::<FutureRenderResources>()
463415
{
464-
let RenderResources(device, queue, adapter_info, render_adapter, instance) =
465-
future_render_resources.0.lock().unwrap().take().unwrap();
416+
let render_resources = future_render_resources.0.lock().unwrap().take().unwrap();
417+
let RenderResources(device, queue, adapter_info, render_adapter, instance, ..) =
418+
render_resources;
466419

467420
let compressed_image_format_support = CompressedImageFormatSupport(
468421
CompressedImageFormats::from_features(device.features()),
@@ -476,6 +429,13 @@ impl Plugin for RenderPlugin {
476429

477430
let render_app = app.sub_app_mut(RenderApp);
478431

432+
#[cfg(feature = "raw_vulkan_init")]
433+
{
434+
let additional_vulkan_features: renderer::raw_vulkan_init::AdditionalVulkanFeatures =
435+
render_resources.5;
436+
render_app.insert_resource(additional_vulkan_features);
437+
}
438+
479439
render_app
480440
.insert_resource(instance)
481441
.insert_resource(PipelineCache::new(

crates/bevy_render/src/renderer/mod.rs

Lines changed: 125 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
mod graph_runner;
2+
#[cfg(feature = "raw_vulkan_init")]
3+
pub mod raw_vulkan_init;
24
mod render_device;
35

4-
use crate::WgpuWrapper;
5-
use bevy_derive::{Deref, DerefMut};
6-
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
7-
use bevy_tasks::ComputeTaskPool;
86
pub use graph_runner::*;
97
pub use render_device::*;
10-
use tracing::{debug, error, info, info_span, warn};
118

129
use crate::{
1310
diagnostic::{internal::DiagnosticsRecorder, RecordDiagnostics},
1411
render_graph::RenderGraph,
1512
render_phase::TrackedRenderPass,
1613
render_resource::RenderPassDescriptor,
17-
settings::{WgpuSettings, WgpuSettingsPriority},
14+
settings::{RenderResources, WgpuSettings, WgpuSettingsPriority},
1815
view::{ExtractedWindows, ViewTarget},
16+
WgpuWrapper,
1917
};
2018
use alloc::sync::Arc;
19+
use bevy_derive::{Deref, DerefMut};
2120
use bevy_ecs::{prelude::*, system::SystemState};
2221
use bevy_platform::time::Instant;
2322
use bevy_time::TimeSender;
23+
use bevy_window::RawHandleWrapperHolder;
24+
use tracing::{debug, error, info, info_span, warn};
2425
use wgpu::{
25-
Adapter, AdapterInfo, CommandBuffer, CommandEncoder, DeviceType, Instance, Queue,
26+
Adapter, AdapterInfo, Backends, CommandBuffer, CommandEncoder, DeviceType, Instance, Queue,
2627
RequestAdapterOptions, Trace,
2728
};
2829

@@ -171,15 +172,76 @@ fn find_adapter_by_name(
171172
/// Initializes the renderer by retrieving and preparing the GPU instance, device and queue
172173
/// for the specified backend.
173174
pub async fn initialize_renderer(
174-
instance: &Instance,
175+
backends: Backends,
176+
primary_window: Option<RawHandleWrapperHolder>,
175177
options: &WgpuSettings,
176-
request_adapter_options: &RequestAdapterOptions<'_, '_>,
177-
desired_adapter_name: Option<String>,
178-
) -> (RenderDevice, RenderQueue, RenderAdapterInfo, RenderAdapter) {
178+
#[cfg(feature = "raw_vulkan_init")]
179+
raw_vulkan_init_settings: raw_vulkan_init::RawVulkanInitSettings,
180+
) -> RenderResources {
181+
let instance_descriptor = wgpu::InstanceDescriptor {
182+
backends,
183+
flags: options.instance_flags,
184+
memory_budget_thresholds: options.instance_memory_budget_thresholds,
185+
backend_options: wgpu::BackendOptions {
186+
gl: wgpu::GlBackendOptions {
187+
gles_minor_version: options.gles3_minor_version,
188+
fence_behavior: wgpu::GlFenceBehavior::Normal,
189+
},
190+
dx12: wgpu::Dx12BackendOptions {
191+
shader_compiler: options.dx12_shader_compiler.clone(),
192+
},
193+
noop: wgpu::NoopBackendOptions { enable: false },
194+
},
195+
};
196+
197+
#[cfg(not(feature = "raw_vulkan_init"))]
198+
let instance = Instance::new(&instance_descriptor);
199+
#[cfg(feature = "raw_vulkan_init")]
200+
let mut additional_vulkan_features = raw_vulkan_init::AdditionalVulkanFeatures::default();
201+
#[cfg(feature = "raw_vulkan_init")]
202+
let instance = raw_vulkan_init::create_raw_vulkan_instance(
203+
&instance_descriptor,
204+
&raw_vulkan_init_settings,
205+
&mut additional_vulkan_features,
206+
);
207+
208+
let surface = primary_window.and_then(|wrapper| {
209+
let maybe_handle = wrapper
210+
.0
211+
.lock()
212+
.expect("Couldn't get the window handle in time for renderer initialization");
213+
if let Some(wrapper) = maybe_handle.as_ref() {
214+
// SAFETY: Plugins should be set up on the main thread.
215+
let handle = unsafe { wrapper.get_handle() };
216+
Some(
217+
instance
218+
.create_surface(handle)
219+
.expect("Failed to create wgpu surface"),
220+
)
221+
} else {
222+
None
223+
}
224+
});
225+
226+
let force_fallback_adapter = std::env::var("WGPU_FORCE_FALLBACK_ADAPTER")
227+
.map_or(options.force_fallback_adapter, |v| {
228+
!(v.is_empty() || v == "0" || v == "false")
229+
});
230+
231+
let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME")
232+
.as_deref()
233+
.map_or(options.adapter_name.clone(), |x| Some(x.to_lowercase()));
234+
235+
let request_adapter_options = RequestAdapterOptions {
236+
power_preference: options.power_preference,
237+
compatible_surface: surface.as_ref(),
238+
force_fallback_adapter,
239+
};
240+
179241
#[cfg(not(target_family = "wasm"))]
180242
let mut selected_adapter = desired_adapter_name.and_then(|adapter_name| {
181243
find_adapter_by_name(
182-
instance,
244+
&instance,
183245
options,
184246
request_adapter_options.compatible_surface,
185247
&adapter_name,
@@ -198,7 +260,10 @@ pub async fn initialize_renderer(
198260
"Searching for adapter with options: {:?}",
199261
request_adapter_options
200262
);
201-
selected_adapter = instance.request_adapter(request_adapter_options).await.ok();
263+
selected_adapter = instance
264+
.request_adapter(&request_adapter_options)
265+
.await
266+
.ok();
202267
}
203268

204269
let adapter = selected_adapter.expect(GPU_NOT_FOUND_ERROR_MESSAGE);
@@ -364,24 +429,39 @@ pub async fn initialize_renderer(
364429
};
365430
}
366431

367-
let (device, queue) = adapter
368-
.request_device(&wgpu::DeviceDescriptor {
369-
label: options.device_label.as_ref().map(AsRef::as_ref),
370-
required_features: features,
371-
required_limits: limits,
372-
memory_hints: options.memory_hints.clone(),
373-
// See https://github.com/gfx-rs/wgpu/issues/5974
374-
trace: Trace::Off,
375-
})
376-
.await
377-
.unwrap();
378-
let queue = Arc::new(WgpuWrapper::new(queue));
379-
let adapter = Arc::new(WgpuWrapper::new(adapter));
380-
(
432+
let device_descriptor = wgpu::DeviceDescriptor {
433+
label: options.device_label.as_ref().map(AsRef::as_ref),
434+
required_features: features,
435+
required_limits: limits,
436+
memory_hints: options.memory_hints.clone(),
437+
// See https://github.com/gfx-rs/wgpu/issues/5974
438+
trace: Trace::Off,
439+
};
440+
441+
#[cfg(not(feature = "raw_vulkan_init"))]
442+
let (device, queue) = adapter.request_device(&device_descriptor).await.unwrap();
443+
444+
#[cfg(feature = "raw_vulkan_init")]
445+
let (device, queue) = raw_vulkan_init::create_raw_device(
446+
&adapter,
447+
&device_descriptor,
448+
&raw_vulkan_init_settings,
449+
&mut additional_vulkan_features,
450+
)
451+
.await
452+
.unwrap();
453+
454+
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
455+
debug!("Configured wgpu adapter Features: {:#?}", device.features());
456+
457+
RenderResources(
381458
RenderDevice::from(device),
382-
RenderQueue(queue),
459+
RenderQueue(Arc::new(WgpuWrapper::new(queue))),
383460
RenderAdapterInfo(WgpuWrapper::new(adapter_info)),
384-
RenderAdapter(adapter),
461+
RenderAdapter(Arc::new(WgpuWrapper::new(adapter))),
462+
RenderInstance(Arc::new(WgpuWrapper::new(instance))),
463+
#[cfg(feature = "raw_vulkan_init")]
464+
additional_vulkan_features,
385465
)
386466
}
387467

@@ -499,22 +579,24 @@ impl<'w> RenderContext<'w> {
499579

500580
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
501581
{
502-
let mut task_based_command_buffers = ComputeTaskPool::get().scope(|task_pool| {
503-
for (i, queued_command_buffer) in self.command_buffer_queue.into_iter().enumerate()
504-
{
505-
match queued_command_buffer {
506-
QueuedCommandBuffer::Ready(command_buffer) => {
507-
command_buffers.push((i, command_buffer));
508-
}
509-
QueuedCommandBuffer::Task(command_buffer_generation_task) => {
510-
let render_device = self.render_device.clone();
511-
task_pool.spawn(async move {
512-
(i, command_buffer_generation_task(render_device))
513-
});
582+
let mut task_based_command_buffers =
583+
bevy_tasks::ComputeTaskPool::get().scope(|task_pool| {
584+
for (i, queued_command_buffer) in
585+
self.command_buffer_queue.into_iter().enumerate()
586+
{
587+
match queued_command_buffer {
588+
QueuedCommandBuffer::Ready(command_buffer) => {
589+
command_buffers.push((i, command_buffer));
590+
}
591+
QueuedCommandBuffer::Task(command_buffer_generation_task) => {
592+
let render_device = self.render_device.clone();
593+
task_pool.spawn(async move {
594+
(i, command_buffer_generation_task(render_device))
595+
});
596+
}
514597
}
515598
}
516-
}
517-
});
599+
});
518600
command_buffers.append(&mut task_based_command_buffers);
519601
}
520602

0 commit comments

Comments
 (0)