Create `egui_wgpu::RendererOptions` (#7601)

This commit is contained in:
Emil Ernerfeldt 2025-10-07 17:06:23 +02:00 committed by GitHub
parent 56b1def064
commit 9cb4e6a54e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 103 additions and 66 deletions

View File

@ -186,13 +186,15 @@ impl<'app> WgpuWinitApp<'app> {
let mut painter = pollster::block_on(egui_wgpu::winit::Painter::new( let mut painter = pollster::block_on(egui_wgpu::winit::Painter::new(
egui_ctx.clone(), egui_ctx.clone(),
self.native_options.wgpu_options.clone(), self.native_options.wgpu_options.clone(),
self.native_options.multisampling.max(1) as _,
egui_wgpu::depth_format_from_bits(
self.native_options.depth_buffer,
self.native_options.stencil_buffer,
),
self.native_options.viewport.transparent.unwrap_or(false), self.native_options.viewport.transparent.unwrap_or(false),
self.native_options.dithering, egui_wgpu::RendererOptions {
msaa_samples: self.native_options.multisampling as _,
depth_stencil_format: egui_wgpu::depth_format_from_bits(
self.native_options.depth_buffer,
self.native_options.stencil_buffer,
),
dithering: self.native_options.dithering,
},
)); ));
let window = Arc::new(window); let window = Arc::new(window);

View File

@ -14,7 +14,7 @@ pub(crate) struct WebPainterWgpu {
surface_configuration: wgpu::SurfaceConfiguration, surface_configuration: wgpu::SurfaceConfiguration,
render_state: Option<RenderState>, render_state: Option<RenderState>,
on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>, on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
depth_format: Option<wgpu::TextureFormat>, depth_stencil_format: Option<wgpu::TextureFormat>,
depth_texture_view: Option<wgpu::TextureView>, depth_texture_view: Option<wgpu::TextureView>,
screen_capture_state: Option<CaptureState>, screen_capture_state: Option<CaptureState>,
capture_tx: CaptureSender, capture_tx: CaptureSender,
@ -35,7 +35,7 @@ impl WebPainterWgpu {
height_in_pixels: u32, height_in_pixels: u32,
) -> Option<wgpu::TextureView> { ) -> Option<wgpu::TextureView> {
let device = &render_state.device; let device = &render_state.device;
self.depth_format.map(|depth_format| { self.depth_stencil_format.map(|depth_stencil_format| {
device device
.create_texture(&wgpu::TextureDescriptor { .create_texture(&wgpu::TextureDescriptor {
label: Some("egui_depth_texture"), label: Some("egui_depth_texture"),
@ -47,9 +47,9 @@ impl WebPainterWgpu {
mip_level_count: 1, mip_level_count: 1,
sample_count: 1, sample_count: 1,
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: depth_format, format: depth_stencil_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[depth_format], view_formats: &[depth_stencil_format],
}) })
.create_view(&wgpu::TextureViewDescriptor::default()) .create_view(&wgpu::TextureViewDescriptor::default())
}) })
@ -68,15 +68,17 @@ impl WebPainterWgpu {
.create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
.map_err(|err| format!("failed to create wgpu surface: {err}"))?; .map_err(|err| format!("failed to create wgpu surface: {err}"))?;
let depth_format = egui_wgpu::depth_format_from_bits(options.depth_buffer, 0); let depth_stencil_format = egui_wgpu::depth_format_from_bits(options.depth_buffer, 0);
let render_state = RenderState::create( let render_state = RenderState::create(
&options.wgpu_options, &options.wgpu_options,
&instance, &instance,
Some(&surface), Some(&surface),
depth_format, egui_wgpu::RendererOptions {
1, dithering: options.dithering,
options.dithering, depth_stencil_format,
..Default::default()
},
) )
.await .await
.map_err(|err| err.to_string())?; .map_err(|err| err.to_string())?;
@ -101,7 +103,7 @@ impl WebPainterWgpu {
render_state: Some(render_state), render_state: Some(render_state),
surface, surface,
surface_configuration, surface_configuration,
depth_format, depth_stencil_format,
depth_texture_view: None, depth_texture_view: None,
on_surface_error: options.wgpu_options.on_surface_error.clone(), on_surface_error: options.wgpu_options.on_surface_error.clone(),
screen_capture_state: None, screen_capture_state: None,

View File

@ -173,9 +173,7 @@ impl RenderState {
config: &WgpuConfiguration, config: &WgpuConfiguration,
instance: &wgpu::Instance, instance: &wgpu::Instance,
compatible_surface: Option<&wgpu::Surface<'static>>, compatible_surface: Option<&wgpu::Surface<'static>>,
depth_format: Option<wgpu::TextureFormat>, options: RendererOptions,
msaa_samples: u32,
dithering: bool,
) -> Result<Self, WgpuError> { ) -> Result<Self, WgpuError> {
profiling::scope!("RenderState::create"); // async yield give bad names using `profile_function` profiling::scope!("RenderState::create"); // async yield give bad names using `profile_function`
@ -244,13 +242,7 @@ impl RenderState {
}; };
let target_format = crate::preferred_framebuffer_format(&surface_formats)?; let target_format = crate::preferred_framebuffer_format(&surface_formats)?;
let renderer = Renderer::new( let renderer = Renderer::new(&device, target_format, options);
&device,
target_format,
depth_format,
msaa_samples,
dithering,
);
// On wasm, depending on feature flags, wgpu objects may or may not implement sync. // On wasm, depending on feature flags, wgpu objects may or may not implement sync.
// It doesn't make sense to switch to Rc for that special usecase, so simply disable the lint. // It doesn't make sense to switch to Rc for that special usecase, so simply disable the lint.

View File

@ -3,6 +3,7 @@
use std::{borrow::Cow, num::NonZeroU64, ops::Range}; use std::{borrow::Cow, num::NonZeroU64, ops::Range};
use ahash::HashMap; use ahash::HashMap;
use bytemuck::Zeroable as _;
use epaint::{PaintCallbackInfo, Primitive, Vertex, emath::NumExt as _}; use epaint::{PaintCallbackInfo, Primitive, Vertex, emath::NumExt as _};
use wgpu::util::DeviceExt as _; use wgpu::util::DeviceExt as _;
@ -175,6 +176,57 @@ pub struct Texture {
pub options: Option<epaint::textures::TextureOptions>, pub options: Option<epaint::textures::TextureOptions>,
} }
/// Ways to configure [`Renderer`] during creation.
#[derive(Clone, Copy, Debug)]
pub struct RendererOptions {
/// Set the level of the multisampling anti-aliasing (MSAA).
///
/// Must be a power-of-two. Higher = more smooth 3D.
///
/// A value of `0` or `1` turns it off (default).
///
/// `egui` already performs anti-aliasing via "feathering"
/// (controlled by [`egui::epaint::TessellationOptions`]),
/// but if you are embedding 3D in egui you may want to turn on multisampling.
pub msaa_samples: u32,
/// What format to use for the depth and stencil buffers,
/// e.g. [`wgpu::TextureFormat::Depth32FloatStencil8`].
///
/// egui doesn't need depth/stencil, so the default value is `None` (no depth or stancil buffers).
pub depth_stencil_format: Option<wgpu::TextureFormat>,
/// Controls whether to apply dithering to minimize banding artifacts.
///
/// Dithering assumes an sRGB output and thus will apply noise to any input value that lies between
/// two 8bit values after applying the sRGB OETF function, i.e. if it's not a whole 8bit value in "gamma space".
/// This means that only inputs from texture interpolation and vertex colors should be affected in practice.
///
/// Defaults to true.
pub dithering: bool,
}
impl RendererOptions {
/// Set options that produce the most predicatable output.
///
/// Useful for image snapshot tests.
pub const PREDICTABLE: Self = Self {
msaa_samples: 1,
depth_stencil_format: None,
dithering: false,
};
}
impl Default for RendererOptions {
fn default() -> Self {
Self {
msaa_samples: 0,
depth_stencil_format: None,
dithering: true,
}
}
}
/// Renderer for a egui based GUI. /// Renderer for a egui based GUI.
pub struct Renderer { pub struct Renderer {
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,
@ -194,7 +246,7 @@ pub struct Renderer {
next_user_texture_id: u64, next_user_texture_id: u64,
samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>, samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>,
dithering: bool, options: RendererOptions,
/// Storage for resources shared with all invocations of [`CallbackTrait`]'s methods. /// Storage for resources shared with all invocations of [`CallbackTrait`]'s methods.
/// ///
@ -210,9 +262,7 @@ impl Renderer {
pub fn new( pub fn new(
device: &wgpu::Device, device: &wgpu::Device,
output_color_format: wgpu::TextureFormat, output_color_format: wgpu::TextureFormat,
output_depth_format: Option<wgpu::TextureFormat>, options: RendererOptions,
msaa_samples: u32,
dithering: bool,
) -> Self { ) -> Self {
profiling::function_scope!(); profiling::function_scope!();
@ -229,7 +279,7 @@ impl Renderer {
label: Some("egui_uniform_buffer"), label: Some("egui_uniform_buffer"),
contents: bytemuck::cast_slice(&[UniformBuffer { contents: bytemuck::cast_slice(&[UniformBuffer {
screen_size_in_points: [0.0, 0.0], screen_size_in_points: [0.0, 0.0],
dithering: u32::from(dithering), dithering: u32::from(options.dithering),
_padding: Default::default(), _padding: Default::default(),
}]), }]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
@ -299,13 +349,15 @@ impl Renderer {
push_constant_ranges: &[], push_constant_ranges: &[],
}); });
let depth_stencil = output_depth_format.map(|format| wgpu::DepthStencilState { let depth_stencil = options
format, .depth_stencil_format
depth_write_enabled: false, .map(|format| wgpu::DepthStencilState {
depth_compare: wgpu::CompareFunction::Always, format,
stencil: wgpu::StencilState::default(), depth_write_enabled: false,
bias: wgpu::DepthBiasState::default(), depth_compare: wgpu::CompareFunction::Always,
}); stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
});
let pipeline = { let pipeline = {
profiling::scope!("create_render_pipeline"); profiling::scope!("create_render_pipeline");
@ -337,7 +389,7 @@ impl Renderer {
depth_stencil, depth_stencil,
multisample: wgpu::MultisampleState { multisample: wgpu::MultisampleState {
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
count: msaa_samples, count: options.msaa_samples.max(1),
mask: !0, mask: !0,
}, },
@ -392,17 +444,13 @@ impl Renderer {
}, },
uniform_buffer, uniform_buffer,
// Buffers on wgpu are zero initialized, so this is indeed its current state! // Buffers on wgpu are zero initialized, so this is indeed its current state!
previous_uniform_buffer_content: UniformBuffer { previous_uniform_buffer_content: UniformBuffer::zeroed(),
screen_size_in_points: [0.0, 0.0],
dithering: 0,
_padding: 0,
},
uniform_bind_group, uniform_bind_group,
texture_bind_group_layout, texture_bind_group_layout,
textures: HashMap::default(), textures: HashMap::default(),
next_user_texture_id: 0, next_user_texture_id: 0,
samplers: HashMap::default(), samplers: HashMap::default(),
dithering, options,
callback_resources: CallbackResources::default(), callback_resources: CallbackResources::default(),
} }
} }
@ -846,7 +894,7 @@ impl Renderer {
let uniform_buffer_content = UniformBuffer { let uniform_buffer_content = UniformBuffer {
screen_size_in_points, screen_size_in_points,
dithering: u32::from(self.dithering), dithering: u32::from(self.options.dithering),
_padding: Default::default(), _padding: Default::default(),
}; };
if uniform_buffer_content != self.previous_uniform_buffer_content { if uniform_buffer_content != self.previous_uniform_buffer_content {

View File

@ -1,8 +1,11 @@
#![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_errors_doc)]
#![allow(clippy::undocumented_unsafe_blocks)] #![allow(clippy::undocumented_unsafe_blocks)]
use crate::capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel};
use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer}; use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer};
use crate::{
RendererOptions,
capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel},
};
use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet}; use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet};
use std::{num::NonZeroU32, sync::Arc}; use std::{num::NonZeroU32, sync::Arc};
@ -21,10 +24,8 @@ struct SurfaceState {
pub struct Painter { pub struct Painter {
context: Context, context: Context,
configuration: WgpuConfiguration, configuration: WgpuConfiguration,
msaa_samples: u32, options: RendererOptions,
support_transparent_backbuffer: bool, support_transparent_backbuffer: bool,
dithering: bool,
depth_format: Option<wgpu::TextureFormat>,
screen_capture_state: Option<CaptureState>, screen_capture_state: Option<CaptureState>,
instance: wgpu::Instance, instance: wgpu::Instance,
@ -54,10 +55,8 @@ impl Painter {
pub async fn new( pub async fn new(
context: Context, context: Context,
configuration: WgpuConfiguration, configuration: WgpuConfiguration,
msaa_samples: u32,
depth_format: Option<wgpu::TextureFormat>,
support_transparent_backbuffer: bool, support_transparent_backbuffer: bool,
dithering: bool, options: RendererOptions,
) -> Self { ) -> Self {
let (capture_tx, capture_rx) = capture_channel(); let (capture_tx, capture_rx) = capture_channel();
let instance = configuration.wgpu_setup.new_instance().await; let instance = configuration.wgpu_setup.new_instance().await;
@ -65,10 +64,8 @@ impl Painter {
Self { Self {
context, context,
configuration, configuration,
msaa_samples, options,
support_transparent_backbuffer, support_transparent_backbuffer,
dithering,
depth_format,
screen_capture_state: None, screen_capture_state: None,
instance, instance,
@ -204,9 +201,7 @@ impl Painter {
&self.configuration, &self.configuration,
&self.instance, &self.instance,
Some(&surface), Some(&surface),
self.depth_format, self.options,
self.msaa_samples,
self.dithering,
) )
.await?; .await?;
self.render_state.get_or_insert(render_state) self.render_state.get_or_insert(render_state)
@ -279,7 +274,7 @@ impl Painter {
Self::configure_surface(surface_state, render_state, &self.configuration); Self::configure_surface(surface_state, render_state, &self.configuration);
if let Some(depth_format) = self.depth_format { if let Some(depth_format) = self.options.depth_stencil_format {
self.depth_texture_view.insert( self.depth_texture_view.insert(
viewport_id, viewport_id,
render_state render_state
@ -292,7 +287,7 @@ impl Painter {
depth_or_array_layers: 1, depth_or_array_layers: 1,
}, },
mip_level_count: 1, mip_level_count: 1,
sample_count: self.msaa_samples, sample_count: self.options.msaa_samples.max(1),
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: depth_format, format: depth_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT usage: wgpu::TextureUsages::RENDER_ATTACHMENT
@ -303,7 +298,7 @@ impl Painter {
); );
} }
if let Some(render_state) = (self.msaa_samples > 1) if let Some(render_state) = (self.options.msaa_samples > 1)
.then_some(self.render_state.as_ref()) .then_some(self.render_state.as_ref())
.flatten() .flatten()
{ {
@ -320,7 +315,7 @@ impl Painter {
depth_or_array_layers: 1, depth_or_array_layers: 1,
}, },
mip_level_count: 1, mip_level_count: 1,
sample_count: self.msaa_samples, sample_count: self.options.msaa_samples.max(1),
dimension: wgpu::TextureDimension::D2, dimension: wgpu::TextureDimension::D2,
format: texture_format, format: texture_format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
@ -450,7 +445,7 @@ impl Painter {
}; };
let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default()); let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default());
let (view, resolve_target) = (self.msaa_samples > 1) let (view, resolve_target) = (self.options.msaa_samples > 1)
.then_some(self.msaa_texture_view.get(&viewport_id)) .then_some(self.msaa_texture_view.get(&viewport_id))
.flatten() .flatten()
.map_or((&target_view, None), |texture_view| { .map_or((&target_view, None), |texture_view| {

View File

@ -67,9 +67,7 @@ pub fn create_render_state(setup: WgpuSetup) -> egui_wgpu::RenderState {
}, },
&instance, &instance,
None, None,
None, egui_wgpu::RendererOptions::PREDICTABLE,
1,
false,
)) ))
.expect("Failed to create render state") .expect("Failed to create render state")
} }