diff --git a/crates/bevy_render/src/options.rs b/crates/bevy_render/src/options.rs index 39ef493b9eb8c..c071ed99c58af 100644 --- a/crates/bevy_render/src/options.rs +++ b/crates/bevy_render/src/options.rs @@ -15,8 +15,16 @@ pub struct WgpuOptions { pub backends: Option, pub power_preference: PowerPreference, pub priority: WgpuOptionsPriority, + /// The enabled features. Setting features will require them to be enabled when initializing + /// the renderer. pub features: WgpuFeatures, + /// The features to ensure are disabled regardless of what the adapter/backend supports + pub disabled_features: Option, + /// The imposed limits. Updated based on adapter/backend limits when initializing the renderer + /// if using WgpuOptionsPriority::Functionality pub limits: WgpuLimits, + /// The constraints on limits allowed regardless of what the adapter/backend supports + pub constrained_limits: Option, } impl Default for WgpuOptions { @@ -50,7 +58,9 @@ impl Default for WgpuOptions { power_preference: PowerPreference::HighPerformance, priority, features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + disabled_features: None, limits, + constrained_limits: None, } } } diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 9908cfd476126..77cbd4e067288 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -86,9 +86,11 @@ pub async fn initialize_renderer( #[cfg(not(feature = "wgpu_trace"))] let trace_path = None; + // Maybe get features and limits based on what is supported by the adapter/backend + let mut features = wgpu::Features::empty(); + let mut limits = options.limits.clone(); if matches!(options.priority, WgpuOptionsPriority::Functionality) { - let mut 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 { // `MAPPABLE_PRIMARY_BUFFERS` can have a significant, negative performance impact for // discrete GPUs due to having to transfer data across the PCI-E bus and so it @@ -96,8 +98,108 @@ pub async fn initialize_renderer( // integrated GPUs. features -= wgpu::Features::MAPPABLE_PRIMARY_BUFFERS; } - options.features = features; - options.limits = adapter.limits(); + limits = adapter.limits(); + } + + // Enforce the disabled features + if let Some(disabled_features) = options.disabled_features { + features -= disabled_features; + } + // NOTE: |= is used here to ensure that any explicitly-enabled features are respected. + options.features |= features; + + // Enforce the limit constraints + if let Some(constrained_limits) = options.constrained_limits.as_ref() { + // NOTE: Respect the configured limits as an 'upper bound'. This means for 'max' limits, we + // take the minimum of the calculated limits according to the adapter/backend and the + // 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 + // but we can constrain to 'lower' limits. + options.limits = wgpu::Limits { + max_texture_dimension_1d: limits + .max_texture_dimension_1d + .min(constrained_limits.max_texture_dimension_1d), + max_texture_dimension_2d: limits + .max_texture_dimension_2d + .min(constrained_limits.max_texture_dimension_2d), + max_texture_dimension_3d: limits + .max_texture_dimension_3d + .min(constrained_limits.max_texture_dimension_3d), + max_texture_array_layers: limits + .max_texture_array_layers + .min(constrained_limits.max_texture_array_layers), + max_bind_groups: limits + .max_bind_groups + .min(constrained_limits.max_bind_groups), + max_dynamic_uniform_buffers_per_pipeline_layout: limits + .max_dynamic_uniform_buffers_per_pipeline_layout + .min(constrained_limits.max_dynamic_uniform_buffers_per_pipeline_layout), + max_dynamic_storage_buffers_per_pipeline_layout: limits + .max_dynamic_storage_buffers_per_pipeline_layout + .min(constrained_limits.max_dynamic_storage_buffers_per_pipeline_layout), + max_sampled_textures_per_shader_stage: limits + .max_sampled_textures_per_shader_stage + .min(constrained_limits.max_sampled_textures_per_shader_stage), + max_samplers_per_shader_stage: limits + .max_samplers_per_shader_stage + .min(constrained_limits.max_samplers_per_shader_stage), + max_storage_buffers_per_shader_stage: limits + .max_storage_buffers_per_shader_stage + .min(constrained_limits.max_storage_buffers_per_shader_stage), + max_storage_textures_per_shader_stage: limits + .max_storage_textures_per_shader_stage + .min(constrained_limits.max_storage_textures_per_shader_stage), + max_uniform_buffers_per_shader_stage: limits + .max_uniform_buffers_per_shader_stage + .min(constrained_limits.max_uniform_buffers_per_shader_stage), + max_uniform_buffer_binding_size: limits + .max_uniform_buffer_binding_size + .min(constrained_limits.max_uniform_buffer_binding_size), + max_storage_buffer_binding_size: limits + .max_storage_buffer_binding_size + .min(constrained_limits.max_storage_buffer_binding_size), + max_vertex_buffers: limits + .max_vertex_buffers + .min(constrained_limits.max_vertex_buffers), + max_vertex_attributes: limits + .max_vertex_attributes + .min(constrained_limits.max_vertex_attributes), + max_vertex_buffer_array_stride: limits + .max_vertex_buffer_array_stride + .min(constrained_limits.max_vertex_buffer_array_stride), + max_push_constant_size: limits + .max_push_constant_size + .min(constrained_limits.max_push_constant_size), + min_uniform_buffer_offset_alignment: limits + .min_uniform_buffer_offset_alignment + .max(constrained_limits.min_uniform_buffer_offset_alignment), + min_storage_buffer_offset_alignment: limits + .min_storage_buffer_offset_alignment + .max(constrained_limits.min_storage_buffer_offset_alignment), + max_inter_stage_shader_components: limits + .max_inter_stage_shader_components + .min(constrained_limits.max_inter_stage_shader_components), + max_compute_workgroup_storage_size: limits + .max_compute_workgroup_storage_size + .min(constrained_limits.max_compute_workgroup_storage_size), + max_compute_invocations_per_workgroup: limits + .max_compute_invocations_per_workgroup + .min(constrained_limits.max_compute_invocations_per_workgroup), + max_compute_workgroup_size_x: limits + .max_compute_workgroup_size_x + .min(constrained_limits.max_compute_workgroup_size_x), + max_compute_workgroup_size_y: limits + .max_compute_workgroup_size_y + .min(constrained_limits.max_compute_workgroup_size_y), + max_compute_workgroup_size_z: limits + .max_compute_workgroup_size_z + .min(constrained_limits.max_compute_workgroup_size_z), + max_compute_workgroups_per_dimension: limits + .max_compute_workgroups_per_dimension + .min(constrained_limits.max_compute_workgroups_per_dimension), + }; + } else { + options.limits = limits; } let (device, queue) = adapter