bevy_render: Use RenderDevice to get limits/features and expose AdapterInfo (#3931)

# Objective

- `WgpuOptions` is mutated to be updated with the actual device limits and features, but this information is readily available to both the main and render worlds through the `RenderDevice` which has .limits() and .features() methods
- Information about the adapter in terms of its name, the backend in use, etc were not being exposed but have clear use cases for being used to take decisions about what rendering code to use. For example, if something works well on AMD GPUs but poorly on Intel GPUs. Or perhaps something works well in Vulkan but poorly in DX12.

## Solution

- Stop mutating `WgpuOptions `and don't insert the updated values into the main and render worlds
- Return `AdapterInfo` from `initialize_renderer` and insert it into the main and render worlds
- Use `RenderDevice` limits in the lighting code that was using `WgpuOptions.limits`.
- Renamed `WgpuOptions` to `WgpuSettings`
This commit is contained in:
Robert Swain 2022-02-16 21:17:37 +00:00
parent d3e526bfc0
commit 936468aa1e
8 changed files with 71 additions and 65 deletions

View File

@ -113,6 +113,10 @@ pub mod pbr {
#[cfg(feature = "bevy_render")] #[cfg(feature = "bevy_render")]
pub mod render { pub mod render {
//! Cameras, meshes, textures, shaders, and pipelines. //! Cameras, meshes, textures, shaders, and pipelines.
//! Use [`RenderDevice::features`](bevy_render::renderer::RenderDevice::features),
//! [`RenderDevice::limits`](bevy_render::renderer::RenderDevice::limits), and the
//! [`WgpuAdapterInfo`](bevy_render::render_resource::WgpuAdapterInfo) resource to
//! get runtime information about the actual adapter, backend, features, and limits.
pub use bevy_render::*; pub use bevy_render::*;
} }

View File

@ -15,7 +15,6 @@ use bevy_render::{
camera::{Camera, CameraProjection}, camera::{Camera, CameraProjection},
color::Color, color::Color,
mesh::Mesh, mesh::Mesh,
options::WgpuOptions,
render_asset::RenderAssets, render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{ render_phase::{
@ -607,7 +606,6 @@ pub fn prepare_lights(
directional_light_shadow_map: Res<ExtractedDirectionalLightShadowMap>, directional_light_shadow_map: Res<ExtractedDirectionalLightShadowMap>,
point_lights: Query<(Entity, &ExtractedPointLight)>, point_lights: Query<(Entity, &ExtractedPointLight)>,
directional_lights: Query<(Entity, &ExtractedDirectionalLight)>, directional_lights: Query<(Entity, &ExtractedDirectionalLight)>,
wgpu_options: Res<WgpuOptions>,
) { ) {
light_meta.view_gpu_lights.clear(); light_meta.view_gpu_lights.clear();
@ -697,9 +695,9 @@ pub fn prepare_lights(
TextureDescriptor { TextureDescriptor {
size: Extent3d { size: Extent3d {
width: (directional_light_shadow_map.size as u32) width: (directional_light_shadow_map.size as u32)
.min(wgpu_options.limits.max_texture_dimension_2d), .min(render_device.limits().max_texture_dimension_2d),
height: (directional_light_shadow_map.size as u32) height: (directional_light_shadow_map.size as u32)
.min(wgpu_options.limits.max_texture_dimension_2d), .min(render_device.limits().max_texture_dimension_2d),
depth_or_array_layers: DIRECTIONAL_SHADOW_LAYERS, depth_or_array_layers: DIRECTIONAL_SHADOW_LAYERS,
}, },
mip_level_count: 1, mip_level_count: 1,

View File

@ -1,7 +1,6 @@
pub mod camera; pub mod camera;
pub mod color; pub mod color;
pub mod mesh; pub mod mesh;
pub mod options;
pub mod primitives; pub mod primitives;
pub mod render_asset; pub mod render_asset;
pub mod render_component; pub mod render_component;
@ -9,6 +8,7 @@ pub mod render_graph;
pub mod render_phase; pub mod render_phase;
pub mod render_resource; pub mod render_resource;
pub mod renderer; pub mod renderer;
pub mod settings;
pub mod texture; pub mod texture;
pub mod view; pub mod view;
@ -108,9 +108,9 @@ struct ScratchRenderWorld(World);
impl Plugin for RenderPlugin { impl Plugin for RenderPlugin {
/// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app. /// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app.
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
let mut options = app let options = app
.world .world
.get_resource::<options::WgpuOptions>() .get_resource::<settings::WgpuSettings>()
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
@ -134,16 +134,14 @@ impl Plugin for RenderPlugin {
compatible_surface: surface.as_ref(), compatible_surface: surface.as_ref(),
..Default::default() ..Default::default()
}; };
let (device, queue) = futures_lite::future::block_on(renderer::initialize_renderer( let (device, queue, adapter_info) = futures_lite::future::block_on(
&instance, renderer::initialize_renderer(&instance, &options, &request_adapter_options),
&mut options, );
&request_adapter_options, debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
)); debug!("Configured wgpu adapter Features: {:#?}", device.features());
debug!("Configured wgpu adapter Limits: {:#?}", &options.limits);
debug!("Configured wgpu adapter Features: {:#?}", &options.features);
app.insert_resource(device.clone()) app.insert_resource(device.clone())
.insert_resource(queue.clone()) .insert_resource(queue.clone())
.insert_resource(options.clone()) .insert_resource(adapter_info.clone())
.init_resource::<ScratchRenderWorld>() .init_resource::<ScratchRenderWorld>()
.register_type::<Frustum>() .register_type::<Frustum>()
.register_type::<CubemapFrusta>(); .register_type::<CubemapFrusta>();
@ -171,7 +169,7 @@ impl Plugin for RenderPlugin {
.insert_resource(instance) .insert_resource(instance)
.insert_resource(device) .insert_resource(device)
.insert_resource(queue) .insert_resource(queue)
.insert_resource(options) .insert_resource(adapter_info)
.insert_resource(render_pipeline_cache) .insert_resource(render_pipeline_cache)
.insert_resource(asset_server) .insert_resource(asset_server)
.init_resource::<RenderGraph>(); .init_resource::<RenderGraph>();

View File

@ -22,23 +22,24 @@ pub use uniform_vec::*;
// TODO: decide where re-exports should go // TODO: decide where re-exports should go
pub use wgpu::{ pub use wgpu::{
util::BufferInitDescriptor, AddressMode, BindGroupDescriptor, BindGroupEntry, util::BufferInitDescriptor, AdapterInfo as WgpuAdapterInfo, AddressMode, BindGroupDescriptor,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType,
BlendFactor, BlendOperation, BlendState, BufferAddress, BufferBinding, BufferBindingType, BlendComponent, BlendFactor, BlendOperation, BlendState, BufferAddress, BufferBinding,
BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites, CommandEncoder, BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites,
CommandEncoderDescriptor, CompareFunction, ComputePassDescriptor, ComputePipelineDescriptor, CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePassDescriptor,
DepthBiasState, DepthStencilState, Extent3d, Face, Features as WgpuFeatures, FilterMode, ComputePipelineDescriptor, DepthBiasState, DepthStencilState, Extent3d, Face,
FragmentState as RawFragmentState, FrontFace, ImageCopyBuffer, ImageCopyBufferBase, Features as WgpuFeatures, FilterMode, FragmentState as RawFragmentState, FrontFace,
ImageCopyTexture, ImageCopyTextureBase, ImageDataLayout, ImageSubresourceRange, IndexFormat, ImageCopyBuffer, ImageCopyBufferBase, ImageCopyTexture, ImageCopyTextureBase, ImageDataLayout,
Limits as WgpuLimits, LoadOp, MapMode, MultisampleState, Operations, Origin3d, PipelineLayout, ImageSubresourceRange, IndexFormat, Limits as WgpuLimits, LoadOp, MapMode, MultisampleState,
PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, Operations, Origin3d, PipelineLayout, PipelineLayoutDescriptor, PolygonMode, PrimitiveState,
RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, PrimitiveTopology, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
RenderPipelineDescriptor as RawRenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, RenderPassDescriptor, RenderPipelineDescriptor as RawRenderPipelineDescriptor,
ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, StencilFaceState, SamplerBindingType, SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, ShaderSource,
StencilOperation, StencilState, StorageTextureAccess, TextureAspect, TextureDescriptor, ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess,
TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureViewDescriptor, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType,
TextureViewDimension, VertexAttribute, VertexBufferLayout as RawVertexBufferLayout, TextureUsages, TextureViewDescriptor, TextureViewDimension, VertexAttribute,
VertexFormat, VertexState as RawVertexState, VertexStepMode, VertexBufferLayout as RawVertexBufferLayout, VertexFormat, VertexState as RawVertexState,
VertexStepMode,
}; };
pub use bevy_crevice::*; pub use bevy_crevice::*;

View File

@ -6,13 +6,13 @@ pub use graph_runner::*;
pub use render_device::*; pub use render_device::*;
use crate::{ use crate::{
options::{WgpuOptions, WgpuOptionsPriority},
render_graph::RenderGraph, render_graph::RenderGraph,
settings::{WgpuSettings, WgpuSettingsPriority},
view::{ExtractedWindows, ViewTarget}, view::{ExtractedWindows, ViewTarget},
}; };
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use std::sync::Arc; use std::sync::Arc;
use wgpu::{CommandEncoder, Instance, Queue, RequestAdapterOptions}; use wgpu::{AdapterInfo, CommandEncoder, Instance, Queue, RequestAdapterOptions};
/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame. /// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame.
pub fn render_system(world: &mut World) { pub fn render_system(world: &mut World) {
@ -65,9 +65,9 @@ pub type RenderInstance = Instance;
/// for the specified backend. /// for the specified backend.
pub async fn initialize_renderer( pub async fn initialize_renderer(
instance: &Instance, instance: &Instance,
options: &mut WgpuOptions, options: &WgpuSettings,
request_adapter_options: &RequestAdapterOptions<'_>, request_adapter_options: &RequestAdapterOptions<'_>,
) -> (RenderDevice, RenderQueue) { ) -> (RenderDevice, RenderQueue, AdapterInfo) {
let adapter = instance let adapter = instance
.request_adapter(request_adapter_options) .request_adapter(request_adapter_options)
.await .await
@ -89,7 +89,7 @@ pub async fn initialize_renderer(
// Maybe get features and limits based on what is supported by the adapter/backend // Maybe get features and limits based on what is supported by the adapter/backend
let mut features = wgpu::Features::empty(); let mut features = wgpu::Features::empty();
let mut limits = options.limits.clone(); let mut limits = options.limits.clone();
if matches!(options.priority, WgpuOptionsPriority::Functionality) { if matches!(options.priority, WgpuSettingsPriority::Functionality) {
features = adapter.features() | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES; features = adapter.features() | wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES;
if adapter_info.device_type == wgpu::DeviceType::DiscreteGpu { if adapter_info.device_type == wgpu::DeviceType::DiscreteGpu {
// `MAPPABLE_PRIMARY_BUFFERS` can have a significant, negative performance impact for // `MAPPABLE_PRIMARY_BUFFERS` can have a significant, negative performance impact for
@ -106,7 +106,7 @@ pub async fn initialize_renderer(
features -= disabled_features; features -= disabled_features;
} }
// NOTE: |= is used here to ensure that any explicitly-enabled features are respected. // NOTE: |= is used here to ensure that any explicitly-enabled features are respected.
options.features |= features; features |= options.features;
// Enforce the limit constraints // Enforce the limit constraints
if let Some(constrained_limits) = options.constrained_limits.as_ref() { if let Some(constrained_limits) = options.constrained_limits.as_ref() {
@ -115,7 +115,7 @@ pub async fn initialize_renderer(
// specified max_limits. For 'min' limits, take the maximum instead. This is intended to // specified max_limits. For 'min' limits, take the maximum instead. This is intended to
// err on the side of being conservative. We can't claim 'higher' limits that are supported // err on the side of being conservative. We can't claim 'higher' limits that are supported
// but we can constrain to 'lower' limits. // but we can constrain to 'lower' limits.
options.limits = wgpu::Limits { limits = wgpu::Limits {
max_texture_dimension_1d: limits max_texture_dimension_1d: limits
.max_texture_dimension_1d .max_texture_dimension_1d
.min(constrained_limits.max_texture_dimension_1d), .min(constrained_limits.max_texture_dimension_1d),
@ -198,16 +198,14 @@ pub async fn initialize_renderer(
.max_compute_workgroups_per_dimension .max_compute_workgroups_per_dimension
.min(constrained_limits.max_compute_workgroups_per_dimension), .min(constrained_limits.max_compute_workgroups_per_dimension),
}; };
} else {
options.limits = limits;
} }
let (device, queue) = adapter let (device, queue) = adapter
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: options.device_label.as_ref().map(|a| a.as_ref()), label: options.device_label.as_ref().map(|a| a.as_ref()),
features: options.features, features,
limits: options.limits.clone(), limits,
}, },
trace_path, trace_path,
) )
@ -215,7 +213,7 @@ pub async fn initialize_renderer(
.unwrap(); .unwrap();
let device = Arc::new(device); let device = Arc::new(device);
let queue = Arc::new(queue); let queue = Arc::new(queue);
(RenderDevice::from(device), queue) (RenderDevice::from(device), queue, adapter_info)
} }
/// The context with all information required to interact with the GPU. /// The context with all information required to interact with the GPU.

View File

@ -2,32 +2,38 @@ use std::borrow::Cow;
pub use wgpu::{Backends, Features as WgpuFeatures, Limits as WgpuLimits, PowerPreference}; pub use wgpu::{Backends, Features as WgpuFeatures, Limits as WgpuLimits, PowerPreference};
/// Configures the priority used when automatically configuring the features/limits of `wgpu`.
#[derive(Clone)] #[derive(Clone)]
pub enum WgpuOptionsPriority { pub enum WgpuSettingsPriority {
/// WebGPU default features and limits
Compatibility, Compatibility,
/// The maximum supported features and limits of the adapter and backend
Functionality, Functionality,
/// WebGPU default limits plus additional constraints in order to be compatible with WebGL2
WebGL2, WebGL2,
} }
/// Provides configuration for renderer initialization. Use [`RenderDevice::features`](crate::renderer::RenderDevice::features),
/// [`RenderDevice::limits`](crate::renderer::RenderDevice::limits), and the [`WgpuAdapterInfo`](crate::render_resource::WgpuAdapterInfo)
/// resource to get runtime information about the actual adapter, backend, features, and limits.
#[derive(Clone)] #[derive(Clone)]
pub struct WgpuOptions { pub struct WgpuSettings {
pub device_label: Option<Cow<'static, str>>, pub device_label: Option<Cow<'static, str>>,
pub backends: Option<Backends>, pub backends: Option<Backends>,
pub power_preference: PowerPreference, pub power_preference: PowerPreference,
pub priority: WgpuOptionsPriority, pub priority: WgpuSettingsPriority,
/// The enabled features. Setting features will require them to be enabled when initializing /// The features to ensure are enabled regardless of what the adapter/backend supports.
/// the renderer. /// Setting these explicitly may cause renderer initialization to fail.
pub features: WgpuFeatures, pub features: WgpuFeatures,
/// The features to ensure are disabled regardless of what the adapter/backend supports /// The features to ensure are disabled regardless of what the adapter/backend supports
pub disabled_features: Option<WgpuFeatures>, pub disabled_features: Option<WgpuFeatures>,
/// The imposed limits. Updated based on adapter/backend limits when initializing the renderer /// The imposed limits.
/// if using WgpuOptionsPriority::Functionality
pub limits: WgpuLimits, pub limits: WgpuLimits,
/// The constraints on limits allowed regardless of what the adapter/backend supports /// The constraints on limits allowed regardless of what the adapter/backend supports
pub constrained_limits: Option<WgpuLimits>, pub constrained_limits: Option<WgpuLimits>,
} }
impl Default for WgpuOptions { impl Default for WgpuSettings {
fn default() -> Self { fn default() -> Self {
let default_backends = if cfg!(feature = "webgl") { let default_backends = if cfg!(feature = "webgl") {
Backends::GL Backends::GL
@ -37,9 +43,10 @@ impl Default for WgpuOptions {
let backends = Some(wgpu::util::backend_bits_from_env().unwrap_or(default_backends)); let backends = Some(wgpu::util::backend_bits_from_env().unwrap_or(default_backends));
let priority = options_priority_from_env().unwrap_or(WgpuOptionsPriority::Functionality); let priority = settings_priority_from_env().unwrap_or(WgpuSettingsPriority::Functionality);
let limits = if cfg!(feature = "webgl") || matches!(priority, WgpuOptionsPriority::WebGL2) { let limits = if cfg!(feature = "webgl") || matches!(priority, WgpuSettingsPriority::WebGL2)
{
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
#[allow(unused_mut)] #[allow(unused_mut)]
@ -65,17 +72,17 @@ impl Default for WgpuOptions {
} }
} }
/// Get a features/limits priority from the environment variable `WGPU_OPTIONS_PRIO` /// Get a features/limits priority from the environment variable `WGPU_SETTINGS_PRIO`
pub fn options_priority_from_env() -> Option<WgpuOptionsPriority> { pub fn settings_priority_from_env() -> Option<WgpuSettingsPriority> {
Some( Some(
match std::env::var("WGPU_OPTIONS_PRIO") match std::env::var("WGPU_SETTINGS_PRIO")
.as_deref() .as_deref()
.map(str::to_lowercase) .map(str::to_lowercase)
.as_deref() .as_deref()
{ {
Ok("compatibility") => WgpuOptionsPriority::Compatibility, Ok("compatibility") => WgpuSettingsPriority::Compatibility,
Ok("functionality") => WgpuOptionsPriority::Functionality, Ok("functionality") => WgpuSettingsPriority::Functionality,
Ok("webgl2") => WgpuOptionsPriority::WebGL2, Ok("webgl2") => WgpuSettingsPriority::WebGL2,
_ => return None, _ => return None,
}, },
) )

View File

@ -1,13 +1,13 @@
use bevy::{ use bevy::{
pbr::wireframe::{Wireframe, WireframeConfig, WireframePlugin}, pbr::wireframe::{Wireframe, WireframeConfig, WireframePlugin},
prelude::*, prelude::*,
render::{options::WgpuOptions, render_resource::WgpuFeatures}, render::{render_resource::WgpuFeatures, settings::WgpuSettings},
}; };
fn main() { fn main() {
App::new() App::new()
.insert_resource(Msaa { samples: 4 }) .insert_resource(Msaa { samples: 4 })
.insert_resource(WgpuOptions { .insert_resource(WgpuSettings {
features: WgpuFeatures::POLYGON_MODE_LINE, features: WgpuFeatures::POLYGON_MODE_LINE,
..Default::default() ..Default::default()
}) })

View File

@ -1,8 +1,8 @@
use bevy::{prelude::*, render::options::WgpuOptions}; use bevy::{prelude::*, render::settings::WgpuSettings};
fn main() { fn main() {
App::new() App::new()
.insert_resource(WgpuOptions { .insert_resource(WgpuSettings {
backends: None, backends: None,
..Default::default() ..Default::default()
}) })