From a2f4270b1dde579e401bab77a0de868963c7b255 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 16 Dec 2024 12:17:48 +0100 Subject: [PATCH 1/3] Improve graphics device capability detection --- crates/viewer/re_renderer/src/config.rs | 270 +++++++++++------- crates/viewer/re_renderer/src/context.rs | 83 ++---- .../re_renderer/src/draw_phases/outlines.rs | 8 +- .../src/draw_phases/picking_layer.rs | 2 +- .../re_renderer/src/renderer/compositor.rs | 4 +- .../re_renderer/src/renderer/debug_overlay.rs | 2 +- .../re_renderer/src/renderer/depth_cloud.rs | 2 +- .../viewer/re_renderer/src/renderer/lines.rs | 2 +- .../re_renderer/src/renderer/mesh_renderer.rs | 2 +- .../re_renderer/src/renderer/point_cloud.rs | 2 +- .../re_renderer/src/renderer/rectangles.rs | 2 +- crates/viewer/re_renderer/src/view_builder.rs | 2 +- .../viewer/re_renderer_examples/framework.rs | 21 +- crates/viewer/re_viewer/src/lib.rs | 21 +- .../src/gpu_bridge/image_to_gpu.rs | 4 +- 15 files changed, 225 insertions(+), 202 deletions(-) diff --git a/crates/viewer/re_renderer/src/config.rs b/crates/viewer/re_renderer/src/config.rs index 18318407b7be..1c43bf164c25 100644 --- a/crates/viewer/re_renderer/src/config.rs +++ b/crates/viewer/re_renderer/src/config.rs @@ -11,10 +11,11 @@ /// See also `global_bindings.wgsl` #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DeviceTier { - /// Limited feature support as provided by WebGL and native GLES2/OpenGL3(ish). + /// Limited feature support as provided by WebGL and typically only by GLES2/OpenGL3(ish). /// /// Note that we do not distinguish between WebGL & native GL here, /// instead, we go with the lowest common denominator. + /// In theory this path can also be hit on Vulkan & Metal drivers, but this is exceedingly rare. Gles = 0, /// Full support of WebGPU spec without additional feature requirements. @@ -26,6 +27,82 @@ pub enum DeviceTier { //HighEnd } +impl DeviceTier { + /// Whether the current device tier supports sampling from textures with a sample count higher than 1. + pub fn support_sampling_msaa_texture(&self) -> bool { + match self { + Self::Gles => false, + Self::FullWebGpuSupport => true, + } + } + + /// Whether the current device tier supports reading back depth textures. + /// + /// If this returns false, we first have to create a copy of the depth buffer by rendering depth to a different texture. + pub fn support_depth_readback(&self) -> bool { + match self { + Self::Gles => false, + Self::FullWebGpuSupport => true, + } + } + + pub fn support_bgra_textures(&self) -> bool { + match self { + // TODO(wgpu#3583): Incorrectly reported by wgpu right now. + // GLES2 does not support BGRA textures! + Self::Gles => false, + Self::FullWebGpuSupport => true, + } + } + + /// Downlevel features required by the given tier. + pub fn required_downlevel_capabilities(&self) -> wgpu::DownlevelCapabilities { + wgpu::DownlevelCapabilities { + flags: match self { + Self::Gles => wgpu::DownlevelFlags::empty(), + // Require fully WebGPU compliance for the native tier. + Self::FullWebGpuSupport => wgpu::DownlevelFlags::all(), + }, + limits: Default::default(), // unused so far both here and in wgpu as of writing. + + // Sm3 is missing a lot of features and has a even instruction count limits. + // Sm4 is missing storage images and other minor features. + // Sm5 is WebGPU compliant + shader_model: wgpu::ShaderModel::Sm4, + } + } + + /// Required features for the given device tier. + #[allow(clippy::unused_self)] + pub fn features(&self) -> wgpu::Features { + wgpu::Features::empty() + } + + /// Check whether the given downlevel caps are sufficient for this tier. + pub fn check_required_downlevel_capabilities( + &self, + downlevel_caps: &wgpu::DownlevelCapabilities, + ) -> Result<(), InsufficientDeviceCapabilities> { + let required_downlevel_caps_webgpu = self.required_downlevel_capabilities(); + if downlevel_caps.shader_model < required_downlevel_caps_webgpu.shader_model { + Err(InsufficientDeviceCapabilities::TooLowShaderModel { + required: required_downlevel_caps_webgpu.shader_model, + actual: downlevel_caps.shader_model, + }) + } else if !downlevel_caps + .flags + .contains(required_downlevel_caps_webgpu.flags) + { + Err(InsufficientDeviceCapabilities::MissingCapabilitiesFlags { + required: required_downlevel_caps_webgpu.flags, + actual: downlevel_caps.flags, + }) + } else { + Ok(()) + } + } +} + /// Type of Wgpu backend. /// /// Used in the rare cases where it's necessary to be aware of the api differences between @@ -44,17 +121,20 @@ pub enum WgpuBackendType { #[derive(thiserror::Error, Debug)] pub enum InsufficientDeviceCapabilities { - #[error("Adapter does not support the minimum shader model required. Supported is {actual:?} but required is {required:?}")] + #[error("Adapter does not support the minimum shader model required. Supported is {actual:?} but required is {required:?}.")] TooLowShaderModel { required: wgpu::ShaderModel, actual: wgpu::ShaderModel, }, - #[error("Adapter does not have all the required capability flags required. Supported are {actual:?} but required are {required:?}")] + #[error("Adapter does not have all the required capability flags required. Supported are {actual:?} but required are {required:?}.")] MissingCapabilitiesFlags { required: wgpu::DownlevelFlags, actual: wgpu::DownlevelFlags, }, + + #[error("Adapter does not support drawing to texture format {format:?}")] + CantDrawToTexture { format: wgpu::TextureFormat }, } /// Capabilities of a given device. @@ -85,47 +165,27 @@ pub struct DeviceCaps { } impl DeviceCaps { - /// Whether the current device tier supports sampling from textures with a sample count higher than 1. - pub fn support_sampling_msaa_texture(&self) -> bool { - match self.tier { - DeviceTier::Gles => false, - DeviceTier::FullWebGpuSupport => true, - } - } - - /// Whether the current device tier supports sampling from textures with a sample count higher than 1. - pub fn support_depth_readback(&self) -> bool { - match self.tier { - DeviceTier::Gles => false, - DeviceTier::FullWebGpuSupport => true, - } - } - - pub fn support_bgra_textures(&self) -> bool { - match self.tier { - // TODO(wgpu#3583): Incorrectly reported by wgpu right now. - // GLES2 does not support BGRA textures! - DeviceTier::Gles => false, - DeviceTier::FullWebGpuSupport => true, - } - } - - /// Picks the highest possible tier for a given adapter. + /// Picks the highest possible tier for a given adapter, but doesn't validate that all the capabilities needed are there. /// - /// Note that it is always possible to pick a lower tier! - pub fn from_adapter(adapter: &wgpu::Adapter) -> Self { - let backend = adapter.get_info().backend; - - let tier = match backend { - wgpu::Backend::Vulkan - | wgpu::Backend::Metal - | wgpu::Backend::Dx12 - | wgpu::Backend::BrowserWebGpu => DeviceTier::FullWebGpuSupport, - - wgpu::Backend::Gl | wgpu::Backend::Empty => DeviceTier::Gles, + /// This is really only needed for generating a device descriptor for [`Self::device_descriptor`]. + /// See also use of `egui_wgpu::WgpuSetup::CreateNew` + pub fn from_adapter_without_validation(adapter: &wgpu::Adapter) -> Self { + let downlevel_caps = adapter.get_downlevel_capabilities(); + + // Note that non-GL backend doesn't automatically mean we support all downlevel flags. + // (practically that's only the case for a handful of Vulkan/Metal devices and even so that's rare. + // Practically all issues are with GL) + let tier = if DeviceTier::FullWebGpuSupport + .check_required_downlevel_capabilities(&downlevel_caps) + .is_ok() + { + // We pass the WebGPU min-spec! + DeviceTier::FullWebGpuSupport + } else { + DeviceTier::Gles }; - let backend_type = match backend { + let backend_type = match adapter.get_info().backend { wgpu::Backend::Empty | wgpu::Backend::Vulkan | wgpu::Backend::Metal @@ -142,15 +202,80 @@ impl DeviceCaps { } } }; + let limits = adapter.limits(); Self { tier, - max_texture_dimension2d: adapter.limits().max_texture_dimension_2d, - max_buffer_size: adapter.limits().max_buffer_size, + max_texture_dimension2d: limits.max_texture_dimension_2d, + max_buffer_size: limits.max_buffer_size, backend_type, } } + /// Picks the highest possible tier for a given adapter. + /// + /// Note that it is always possible to pick a lower tier! + pub fn from_adapter(adapter: &wgpu::Adapter) -> Result { + let caps = Self::from_adapter_without_validation(adapter); + caps.tier + .check_required_downlevel_capabilities(&adapter.get_downlevel_capabilities())?; + + if caps.tier == DeviceTier::Gles { + // Check texture format support. If `WEBGPU_TEXTURE_FORMAT_SUPPORT` is enabled, we're generally fine. + // This is an implicit requirement for the WebGPU tier and above. + if !adapter + .get_downlevel_capabilities() + .flags + .contains(wgpu::DownlevelFlags::WEBGPU_TEXTURE_FORMAT_SUPPORT) + { + // Otherwise, make sure some basic formats are supported for drawing. + // This is far from an exhaustive list, but it's a good sanity check for formats that may be missing. + let formats_required_for_drawing = [ + crate::ViewBuilder::MAIN_TARGET_COLOR_FORMAT, + // R32f has previously observed being missing on old OpenGL drivers and was fixed by updating the driver. + // https://github.com/rerun-io/rerun/issues/8466 + // We use this as a fallback when depth readback is not support, but making this a general requirement + // seems wise as this is a great litmus test for potato drivers. + wgpu::TextureFormat::R32Float, + // The picking layer format is an integer texture. Might be slightly more challenging for some backends. + crate::PickingLayerProcessor::PICKING_LAYER_FORMAT, + ]; + + for format in formats_required_for_drawing { + if !adapter + .get_texture_format_features(format) + .allowed_usages + .contains(wgpu::TextureUsages::RENDER_ATTACHMENT) + { + return Err(InsufficientDeviceCapabilities::CantDrawToTexture { format }); + } + } + } + + // Alright, this should still basically work. + // This is really old though, so if we're not doing WebGL where this is kinda expected, let's issue a warning + // in order to let the user know that they might be in trouble. + // + // In the long run we'd like WebGPU to be our minspec! + // To learn more about the WebGPU minspec check: + // * https://github.com/gpuweb/gpuweb/issues/1069 + // * https://www.w3.org/TR/webgpu/#adapter-capability-guarantees + // * https://www.w3.org/TR/webgpu/#limits + // This is roughly everything post 2014, so still VERY generous. + // + // It's much more likely we end up in here because of... + // * older software rasterizer + // * old/missing driver + // * some VM/container setup with limited graphics capabilities. + // + // That's a lot of murky information, so let's keep the actual message crisp for now. + #[cfg(not(web))] + re_log::warn!("Running on a GPU/graphics driver with very limited abilitites. Consider updating your driver."); + }; + + Ok(caps) + } + /// Wgpu limits required by the given device tier. pub fn limits(&self) -> wgpu::Limits { wgpu::Limits { @@ -160,72 +285,15 @@ impl DeviceCaps { } } - /// Required features for the given device tier. - #[allow(clippy::unused_self)] - pub fn features(&self) -> wgpu::Features { - wgpu::Features::empty() - } - /// Device descriptor compatible with the given device tier. pub fn device_descriptor(&self) -> wgpu::DeviceDescriptor<'static> { wgpu::DeviceDescriptor { label: Some("re_renderer device"), - required_features: self.features(), + required_features: self.tier.features(), required_limits: self.limits(), memory_hints: Default::default(), } } - - /// Downlevel features required by the given tier. - pub fn required_downlevel_capabilities(&self) -> wgpu::DownlevelCapabilities { - wgpu::DownlevelCapabilities { - flags: match self.tier { - DeviceTier::Gles => wgpu::DownlevelFlags::empty(), - // Require fully WebGPU compliance for the native tier. - DeviceTier::FullWebGpuSupport => wgpu::DownlevelFlags::all(), - }, - limits: Default::default(), // unused so far both here and in wgpu - shader_model: wgpu::ShaderModel::Sm4, - } - } - - /// Checks if passed downlevel capabilities support the given device tier. - pub fn check_downlevel_capabilities( - &self, - capabilities: &wgpu::DownlevelCapabilities, - ) -> Result<(), InsufficientDeviceCapabilities> { - let wgpu::DownlevelCapabilities { - flags, - limits: _, - shader_model, - } = self.required_downlevel_capabilities(); - - if capabilities.shader_model < shader_model { - Err(InsufficientDeviceCapabilities::TooLowShaderModel { - required: shader_model, - actual: capabilities.shader_model, - }) - } else if !capabilities.flags.contains(flags) { - Err(InsufficientDeviceCapabilities::MissingCapabilitiesFlags { - required: flags, - actual: capabilities.flags, - }) - } else { - Ok(()) - } - } -} - -/// Startup configuration for a [`crate::RenderContext`] -/// -/// Contains any kind of configuration that doesn't change for the entire lifetime of a [`crate::RenderContext`]. -/// (flipside, if we do want to change any of these, the [`crate::RenderContext`] needs to be re-created) -pub struct RenderContextConfig { - /// The color format used by the eframe output buffer. - pub output_format_color: wgpu::TextureFormat, - - /// Hardware capabilities of the device. - pub device_caps: DeviceCaps, } /// Backends that are officially supported by `re_renderer`. diff --git a/crates/viewer/re_renderer/src/context.rs b/crates/viewer/re_renderer/src/context.rs index 53a38ec3e27c..3608a4a625f5 100644 --- a/crates/viewer/re_renderer/src/context.rs +++ b/crates/viewer/re_renderer/src/context.rs @@ -8,7 +8,7 @@ use type_map::concurrent::{self, TypeMap}; use crate::{ allocator::{CpuWriteGpuReadBelt, GpuReadbackBelt}, - config::{DeviceCaps, DeviceTier, RenderContextConfig}, + config::{DeviceCaps, DeviceTier}, error_handling::{ErrorTracker, WgpuErrorScope}, global_bindings::GlobalBindings, renderer::Renderer, @@ -23,37 +23,9 @@ const STARTUP_FRAME_IDX: u64 = u64::MAX; #[derive(thiserror::Error, Debug)] pub enum RenderContextError { #[error( - "The given device doesn't support the required limits for the given hardware caps {device_caps:?}.\n\ - Required: {required:?}\n\ - Actual: {actual:?}" + "The GPU/graphics driver is lacking some abilities: {0}.\nConsider updating the driver." )] - Limits { - device_caps: DeviceCaps, - required: Box, // boxed because of its size - actual: Box, // boxed because of its size - }, - - #[error( - "The given device doesn't support the required features for the given hardware caps {device_caps:?}.\n\ - Required: {required:?}\n\ - Actual: {actual:?}" - )] - Features { - device_caps: DeviceCaps, - required: wgpu::Features, - actual: wgpu::Features, - }, - - #[error( - "The given device doesn't support the required downlevel capabilities for the given hardware caps {device_caps:?}.\n\ - Required: {required:?}\n\ - Actual: {actual:?}" - )] - DownlevelCapabilities { - device_caps: DeviceCaps, - required: wgpu::DownlevelCapabilities, - actual: wgpu::DownlevelCapabilities, - }, + InsufficientDeviceCapabilities(#[from] crate::config::InsufficientDeviceCapabilities), } /// Any resource involving wgpu rendering which can be re-used across different scenes. @@ -62,7 +34,8 @@ pub struct RenderContext { pub device: Arc, pub queue: Arc, - pub config: RenderContextConfig, + device_caps: DeviceCaps, + output_format_color: wgpu::TextureFormat, /// Global bindings, always bound to 0 bind group slot zero. /// [`Renderer`] are not allowed to use bind group 0 themselves! @@ -154,10 +127,13 @@ impl RenderContext { adapter: &wgpu::Adapter, device: Arc, queue: Arc, - config: RenderContextConfig, + output_format_color: wgpu::TextureFormat, ) -> Result { re_tracing::profile_function!(); + // Validate capabilities of the device. + let device_caps = DeviceCaps::from_adapter(adapter)?; + let frame_index_for_uncaptured_errors = Arc::new(AtomicU64::new(STARTUP_FRAME_IDX)); // Make sure to catch all errors, never crash, and deduplicate reported errors. @@ -189,33 +165,6 @@ impl RenderContext { let mut gpu_resources = WgpuResourcePools::default(); let global_bindings = GlobalBindings::new(&gpu_resources, &device); - // Validate capabilities of the device. - if !config.device_caps.limits().check_limits(&device.limits()) { - return Err(RenderContextError::Limits { - required: Box::new(config.device_caps.limits()), - device_caps: config.device_caps, - actual: Box::new(device.limits()), - }); - } - if !device.features().contains(config.device_caps.features()) { - return Err(RenderContextError::Features { - required: config.device_caps.features(), - device_caps: config.device_caps, - actual: device.features(), - }); - } - if !adapter - .get_downlevel_capabilities() - .flags - .contains(config.device_caps.required_downlevel_capabilities().flags) - { - return Err(RenderContextError::DownlevelCapabilities { - required: config.device_caps.required_downlevel_capabilities(), - device_caps: config.device_caps, - actual: adapter.get_downlevel_capabilities(), - }); - } - let resolver = crate::new_recommended_file_resolver(); let texture_manager_2d = TextureManager2D::new(&device, &queue, &gpu_resources.textures); @@ -250,7 +199,8 @@ impl RenderContext { Ok(Self { device, queue, - config, + device_caps, + output_format_color, global_bindings, renderers: RwLock::new(Renderers { renderers: TypeMap::new(), @@ -286,7 +236,7 @@ impl RenderContext { // knowing that we're not _actually_ blocking. // // For more details check https://github.com/gfx-rs/wgpu/issues/3601 - if cfg!(target_arch = "wasm32") && self.config.device_caps.tier == DeviceTier::Gles { + if cfg!(target_arch = "wasm32") && self.device_caps.tier == DeviceTier::Gles { self.device.poll(wgpu::Maintain::Wait); return; } @@ -340,7 +290,7 @@ This means, either a call to RenderContext::before_submit was omitted, or the pr if let Some(top_level_error_scope) = self.active_frame.top_level_error_scope.take() { let frame_index_for_uncaptured_errors = self.frame_index_for_uncaptured_errors.clone(); self.top_level_error_tracker.handle_error_future( - self.config.device_caps.backend_type, + self.device_caps.backend_type, top_level_error_scope.end(), self.active_frame.frame_index, move |err_tracker, frame_index| { @@ -469,7 +419,12 @@ This means, either a call to RenderContext::before_submit was omitted, or the pr /// Returns the device's capabilities. pub fn device_caps(&self) -> &DeviceCaps { - &self.config.device_caps + &self.device_caps + } + + /// Returns the final output format for color (i.e. the surface's format). + pub fn output_format_color(&self) -> wgpu::TextureFormat { + self.output_format_color } } diff --git a/crates/viewer/re_renderer/src/draw_phases/outlines.rs b/crates/viewer/re_renderer/src/draw_phases/outlines.rs index 7e10b7bb7435..8cc8ffe55f25 100644 --- a/crates/viewer/re_renderer/src/draw_phases/outlines.rs +++ b/crates/viewer/re_renderer/src/draw_phases/outlines.rs @@ -45,7 +45,7 @@ use crate::{ allocator::create_and_fill_uniform_buffer_batch, - config::DeviceCaps, + config::DeviceTier, include_shader_module, renderer::screen_triangle_vertex_shader, view_builder::ViewBuilder, @@ -177,7 +177,7 @@ impl OutlineMaskProcessor { const VORONOI_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Float; /// Default MSAA state for the outline mask target. - pub fn mask_default_msaa_state(tier: &DeviceCaps) -> wgpu::MultisampleState { + pub fn mask_default_msaa_state(tier: DeviceTier) -> wgpu::MultisampleState { wgpu::MultisampleState { count: Self::mask_sample_count(tier), mask: !0, @@ -186,7 +186,7 @@ impl OutlineMaskProcessor { } /// Number of MSAA samples used for the outline mask target. - pub fn mask_sample_count(tier: &DeviceCaps) -> u32 { + pub fn mask_sample_count(tier: DeviceTier) -> u32 { if tier.support_sampling_msaa_texture() { // The MSAA shader variant deals with *exactly* 4 samples. // See `jumpflooding_step_msaa.wgsl`. @@ -208,7 +208,7 @@ impl OutlineMaskProcessor { // ------------- Textures ------------- let texture_pool = &ctx.gpu_resources.textures; - let mask_sample_count = Self::mask_sample_count(&ctx.config.device_caps); + let mask_sample_count = Self::mask_sample_count(ctx.device_caps().tier); let mask_texture_desc = crate::wgpu_resources::TextureDesc { label: format!("{instance_label}::mask_texture").into(), size: wgpu::Extent3d { diff --git a/crates/viewer/re_renderer/src/draw_phases/picking_layer.rs b/crates/viewer/re_renderer/src/draw_phases/picking_layer.rs index 3b9082e5359e..14042437b183 100644 --- a/crates/viewer/re_renderer/src/draw_phases/picking_layer.rs +++ b/crates/viewer/re_renderer/src/draw_phases/picking_layer.rs @@ -204,7 +204,7 @@ impl PickingLayerProcessor { }, ); - let direct_depth_readback = ctx.config.device_caps.support_depth_readback(); + let direct_depth_readback = ctx.device_caps().tier.support_depth_readback(); let picking_depth_target = ctx.gpu_resources.textures.alloc( &ctx.device, diff --git a/crates/viewer/re_renderer/src/renderer/compositor.rs b/crates/viewer/re_renderer/src/renderer/compositor.rs index 34b0e2691e94..2daedb84ffa2 100644 --- a/crates/viewer/re_renderer/src/renderer/compositor.rs +++ b/crates/viewer/re_renderer/src/renderer/compositor.rs @@ -171,7 +171,7 @@ impl Renderer for Compositor { .shader_modules .get_or_create(ctx, &include_shader_module!("../../shader/composite.wgsl")), vertex_buffers: smallvec![], - render_targets: smallvec![Some(ctx.config.output_format_color.into())], + render_targets: smallvec![Some(ctx.output_format_color().into())], primitive: wgpu::PrimitiveState::default(), depth_stencil: None, multisample: wgpu::MultisampleState::default(), @@ -186,7 +186,7 @@ impl Renderer for Compositor { ctx, &RenderPipelineDesc { render_targets: smallvec![Some(wgpu::ColorTargetState { - format: ctx.config.output_format_color, + format: ctx.output_format_color(), blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], diff --git a/crates/viewer/re_renderer/src/renderer/debug_overlay.rs b/crates/viewer/re_renderer/src/renderer/debug_overlay.rs index 5e4d81cecb19..ef7f3ae8a00c 100644 --- a/crates/viewer/re_renderer/src/renderer/debug_overlay.rs +++ b/crates/viewer/re_renderer/src/renderer/debug_overlay.rs @@ -207,7 +207,7 @@ impl Renderer for DebugOverlayRenderer { fragment_entrypoint: "main_fs".into(), fragment_handle: shader_module, vertex_buffers: smallvec![], - render_targets: smallvec![Some(ctx.config.output_format_color.into())], + render_targets: smallvec![Some(ctx.output_format_color().into())], primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleStrip, cull_mode: None, diff --git a/crates/viewer/re_renderer/src/renderer/depth_cloud.rs b/crates/viewer/re_renderer/src/renderer/depth_cloud.rs index a4ea7f470c3c..3bb09c3004f5 100644 --- a/crates/viewer/re_renderer/src/renderer/depth_cloud.rs +++ b/crates/viewer/re_renderer/src/renderer/depth_cloud.rs @@ -459,7 +459,7 @@ impl Renderer for DepthCloudRenderer { render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())], depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE, // Alpha to coverage doesn't work with the mask integer target. - multisample: OutlineMaskProcessor::mask_default_msaa_state(&ctx.config.device_caps), + multisample: OutlineMaskProcessor::mask_default_msaa_state(ctx.device_caps().tier), ..render_pipeline_desc_color }, ); diff --git a/crates/viewer/re_renderer/src/renderer/lines.rs b/crates/viewer/re_renderer/src/renderer/lines.rs index e42cf6f5d6a9..c10fec15aa32 100644 --- a/crates/viewer/re_renderer/src/renderer/lines.rs +++ b/crates/viewer/re_renderer/src/renderer/lines.rs @@ -711,7 +711,7 @@ impl Renderer for LineRenderer { }, depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE, // Alpha to coverage doesn't work with the mask integer target. - multisample: OutlineMaskProcessor::mask_default_msaa_state(&ctx.config.device_caps), + multisample: OutlineMaskProcessor::mask_default_msaa_state(ctx.device_caps().tier), }, ); diff --git a/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs b/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs index 553f8c3e7160..06fce1fac96f 100644 --- a/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs +++ b/crates/viewer/re_renderer/src/renderer/mesh_renderer.rs @@ -388,7 +388,7 @@ impl Renderer for MeshRenderer { fragment_entrypoint: "fs_main_outline_mask".into(), render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())], depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE, - multisample: OutlineMaskProcessor::mask_default_msaa_state(&ctx.config.device_caps), + multisample: OutlineMaskProcessor::mask_default_msaa_state(ctx.device_caps().tier), ..render_pipeline_shaded_desc }, ); diff --git a/crates/viewer/re_renderer/src/renderer/point_cloud.rs b/crates/viewer/re_renderer/src/renderer/point_cloud.rs index ca289ba92d25..a3360d76fc2b 100644 --- a/crates/viewer/re_renderer/src/renderer/point_cloud.rs +++ b/crates/viewer/re_renderer/src/renderer/point_cloud.rs @@ -572,7 +572,7 @@ impl Renderer for PointCloudRenderer { render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())], depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE, // Alpha to coverage doesn't work with the mask integer target. - multisample: OutlineMaskProcessor::mask_default_msaa_state(&ctx.config.device_caps), + multisample: OutlineMaskProcessor::mask_default_msaa_state(ctx.device_caps().tier), ..render_pipeline_desc_color }, ); diff --git a/crates/viewer/re_renderer/src/renderer/rectangles.rs b/crates/viewer/re_renderer/src/renderer/rectangles.rs index 595da865b018..621c23f1f65c 100644 --- a/crates/viewer/re_renderer/src/renderer/rectangles.rs +++ b/crates/viewer/re_renderer/src/renderer/rectangles.rs @@ -614,7 +614,7 @@ impl Renderer for RectangleRenderer { fragment_entrypoint: "fs_main_outline_mask".into(), render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())], depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE, - multisample: OutlineMaskProcessor::mask_default_msaa_state(&ctx.config.device_caps), + multisample: OutlineMaskProcessor::mask_default_msaa_state(ctx.device_caps().tier), ..render_pipeline_desc_color }), ); diff --git a/crates/viewer/re_renderer/src/view_builder.rs b/crates/viewer/re_renderer/src/view_builder.rs index e03871ded644..2455595e7e66 100644 --- a/crates/viewer/re_renderer/src/view_builder.rs +++ b/crates/viewer/re_renderer/src/view_builder.rs @@ -475,7 +475,7 @@ impl ViewBuilder { pixel_world_size_from_camera_distance, pixels_per_point: config.pixels_per_point, - device_tier: (ctx.config.device_caps.tier as u32).into(), + device_tier: (ctx.device_caps().tier as u32).into(), }; let frame_uniform_buffer = create_and_fill_uniform_buffer( ctx, diff --git a/crates/viewer/re_renderer_examples/framework.rs b/crates/viewer/re_renderer_examples/framework.rs index 924162f05323..7313834f9431 100644 --- a/crates/viewer/re_renderer_examples/framework.rs +++ b/crates/viewer/re_renderer_examples/framework.rs @@ -8,7 +8,7 @@ use anyhow::Context as _; use web_time::Instant; use re_renderer::{ - config::{supported_backends, DeviceCaps, RenderContextConfig}, + config::{supported_backends, DeviceCaps}, view_builder::ViewBuilder, RenderContext, }; @@ -130,8 +130,7 @@ impl Application { .await .context("failed to find an appropriate adapter")?; - let device_caps = DeviceCaps::from_adapter(&adapter); - device_caps.check_downlevel_capabilities(&adapter.get_downlevel_capabilities())?; + let device_caps = DeviceCaps::from_adapter(&adapter)?; let (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor { @@ -150,16 +149,8 @@ impl Application { let output_format_color = preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats); - let re_ctx = RenderContext::new( - &adapter, - device, - queue, - RenderContextConfig { - output_format_color, - device_caps, - }, - ) - .map_err(|err| anyhow::format_err!("{err}"))?; + let re_ctx = RenderContext::new(&adapter, device, queue, output_format_color) + .map_err(|err| anyhow::format_err!("{err}"))?; let example = E::new(&re_ctx); @@ -190,8 +181,8 @@ impl Application { // Use AutoNoVSync if you want to do quick perf checking. // Otherwise, use AutoVsync is much more pleasant to use - laptops don't heat up and desktop won't have annoying coil whine on trivial examples. present_mode: wgpu::PresentMode::AutoVsync, - format: self.re_ctx.config.output_format_color, - view_formats: vec![self.re_ctx.config.output_format_color], + format: self.re_ctx.output_format_color(), + view_formats: vec![self.re_ctx.output_format_color()], ..self .surface .get_default_config(&self.adapter, size.width, size.height) diff --git a/crates/viewer/re_viewer/src/lib.rs b/crates/viewer/re_viewer/src/lib.rs index 8e17962aa5bf..60524824134a 100644 --- a/crates/viewer/re_viewer/src/lib.rs +++ b/crates/viewer/re_viewer/src/lib.rs @@ -193,10 +193,20 @@ pub(crate) fn wgpu_options(force_wgpu_backend: Option) -> egui_wgpu::Wgp egui_wgpu::SurfaceErrorAction::SkipFrame } }), + // TODO(andreas): It would be great to use `egui_wgpu::WgpuSetup::Existing` and put the + // full control of adapter creation into the hands of `re_renderer`. + // However, we generally need to take into account the _surface_ as well: + // * this is a strict *requirement* when using WebGL + // * on OpenGL & Linux it _helps_ to know the surface because either Vulkan or OpenGL may not be happy with all surfaces + // + // TODO(andreas): Next better thing that we should aspire for is to allow rejecting adapters on native in egui. + // I.e. instead of always providing a device descriptor, we should allow it to fail for a given device. + // This rejection should happen with reason-message so it's tractable why a given adapter wasn't chosen. + // Which is obviously what we want to show when we're rejecting all adapters, but it would + // also be great to be able to show that information later on. wgpu_setup: egui_wgpu::WgpuSetup::CreateNew { - device_descriptor: std::sync::Arc::new(|adapter| re_renderer::config::DeviceCaps::from_adapter(adapter).device_descriptor()), + device_descriptor: std::sync::Arc::new(|adapter| re_renderer::config::DeviceCaps::from_adapter_without_validation(adapter).device_descriptor()), supported_backends: supported_graphics_backends(force_wgpu_backend), - // TODO(andreas): Use ..Default::default(), please patch egui to have this power_preference: wgpu::util::power_preference_from_env().unwrap_or(wgpu::PowerPreference::HighPerformance), }, ..Default::default() @@ -210,7 +220,7 @@ pub fn customize_eframe_and_setup_renderer( re_tracing::profile_function!(); if let Some(render_state) = &cc.wgpu_render_state { - use re_renderer::{config::RenderContextConfig, RenderContext}; + use re_renderer::RenderContext; let paint_callback_resources = &mut render_state.renderer.write().callback_resources; @@ -218,10 +228,7 @@ pub fn customize_eframe_and_setup_renderer( &render_state.adapter, render_state.device.clone(), render_state.queue.clone(), - RenderContextConfig { - output_format_color: render_state.target_format, - device_caps: re_renderer::config::DeviceCaps::from_adapter(&render_state.adapter), - }, + render_state.target_format, )?; paint_callback_resources.insert(render_ctx); diff --git a/crates/viewer/re_viewer_context/src/gpu_bridge/image_to_gpu.rs b/crates/viewer/re_viewer_context/src/gpu_bridge/image_to_gpu.rs index 2417df79808e..1c3b14347bc7 100644 --- a/crates/viewer/re_viewer_context/src/gpu_bridge/image_to_gpu.rs +++ b/crates/viewer/re_viewer_context/src/gpu_bridge/image_to_gpu.rs @@ -222,7 +222,9 @@ pub fn required_shader_decode( || color_model == ColorModel::BGRA { // U8 can be converted to RGBA without the shader's help since there's a format for it. - if image_format.datatype() == ChannelDatatype::U8 && device_caps.support_bgra_textures() { + if image_format.datatype() == ChannelDatatype::U8 + && device_caps.tier.support_bgra_textures() + { None } else { Some(ShaderDecoding::Bgr) From 379e5c09a6f046cfd16128886ef3ebf8b2d69914 Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Mon, 16 Dec 2024 12:27:33 +0100 Subject: [PATCH 2/3] link issue --- crates/viewer/re_viewer/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/viewer/re_viewer/src/lib.rs b/crates/viewer/re_viewer/src/lib.rs index 60524824134a..3b6bede7f1cd 100644 --- a/crates/viewer/re_viewer/src/lib.rs +++ b/crates/viewer/re_viewer/src/lib.rs @@ -193,13 +193,13 @@ pub(crate) fn wgpu_options(force_wgpu_backend: Option) -> egui_wgpu::Wgp egui_wgpu::SurfaceErrorAction::SkipFrame } }), - // TODO(andreas): It would be great to use `egui_wgpu::WgpuSetup::Existing` and put the + // TODO(#8475): It would be great to use `egui_wgpu::WgpuSetup::Existing` and put the // full control of adapter creation into the hands of `re_renderer`. // However, we generally need to take into account the _surface_ as well: // * this is a strict *requirement* when using WebGL // * on OpenGL & Linux it _helps_ to know the surface because either Vulkan or OpenGL may not be happy with all surfaces // - // TODO(andreas): Next better thing that we should aspire for is to allow rejecting adapters on native in egui. + // Next better thing that we should aspire for is to allow rejecting adapters on native in egui. // I.e. instead of always providing a device descriptor, we should allow it to fail for a given device. // This rejection should happen with reason-message so it's tractable why a given adapter wasn't chosen. // Which is obviously what we want to show when we're rejecting all adapters, but it would From 1589ae67fd74a4690661c6c120d69f78a1fc709f Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Tue, 17 Dec 2024 11:07:23 +0100 Subject: [PATCH 3/3] fix broken comment --- crates/viewer/re_renderer/src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/viewer/re_renderer/src/config.rs b/crates/viewer/re_renderer/src/config.rs index 1c43bf164c25..c2b8c4aa8071 100644 --- a/crates/viewer/re_renderer/src/config.rs +++ b/crates/viewer/re_renderer/src/config.rs @@ -65,7 +65,7 @@ impl DeviceTier { }, limits: Default::default(), // unused so far both here and in wgpu as of writing. - // Sm3 is missing a lot of features and has a even instruction count limits. + // Sm3 is missing a lot of features and even has an instruction count limit. // Sm4 is missing storage images and other minor features. // Sm5 is WebGPU compliant shader_model: wgpu::ShaderModel::Sm4, @@ -263,7 +263,7 @@ impl DeviceCaps { // * https://www.w3.org/TR/webgpu/#limits // This is roughly everything post 2014, so still VERY generous. // - // It's much more likely we end up in here because of... + // It's much more likely we end up in here because of… // * older software rasterizer // * old/missing driver // * some VM/container setup with limited graphics capabilities.