# Patch: egui-wgpu 0.34.2 - runtime MSAA (used by TCAD Viewer) # Against crates.io egui-wgpu 0.34.2, src/ only. # # Apply inside an extracted egui-wgpu-0.34.2 tree: # patch -p1 < egui-wgpu-0.34.2-runtime-msaa.patch # Then point Cargo [patch.crates-io] at that tree, or vendor it. # diff --git a/egui-wgpu-0.34.2/src/lib.rs b/egui-wgpu-0.34.2/src/lib.rs index 64e3551..009e2f7 100644 --- a/egui-wgpu-0.34.2/src/lib.rs +++ b/egui-wgpu-0.34.2/src/lib.rs @@ -21,9 +21,12 @@ pub use wgpu; /// Low-level painting of [`egui`](https://github.com/emilk/egui) on [`wgpu`]. mod renderer; +mod runtime_msaa; + mod setup; pub use renderer::*; +pub use runtime_msaa::request_msaa; pub use setup::{ EguiDisplayHandle, NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting, diff --git a/egui-wgpu-0.34.2/src/renderer.rs b/egui-wgpu-0.34.2/src/renderer.rs index e55f758..94876c3 100644 --- a/egui-wgpu-0.34.2/src/renderer.rs +++ b/egui-wgpu-0.34.2/src/renderer.rs @@ -235,6 +235,9 @@ impl Default for RendererOptions { /// Renderer for a egui based GUI. pub struct Renderer { pipeline: wgpu::RenderPipeline, + shader_module: wgpu::ShaderModule, + pipeline_layout: wgpu::PipelineLayout, + output_color_format: wgpu::TextureFormat, index_buffer: SlicedBuffer, vertex_buffer: SlicedBuffer, @@ -259,6 +262,83 @@ pub struct Renderer { pub callback_resources: CallbackResources, } +fn create_egui_render_pipeline( + device: &wgpu::Device, + pipeline_layout: &wgpu::PipelineLayout, + module: &wgpu::ShaderModule, + output_color_format: wgpu::TextureFormat, + options: &RendererOptions, +) -> wgpu::RenderPipeline { + let depth_stencil = options.depth_stencil_format.map(|format| wgpu::DepthStencilState { + format, + depth_write_enabled: Some(false), + depth_compare: Some(wgpu::CompareFunction::Always), + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }); + + profiling::scope!("create_render_pipeline"); + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("egui_pipeline"), + layout: Some(pipeline_layout), + vertex: wgpu::VertexState { + entry_point: Some("vs_main"), + module, + buffers: &[wgpu::VertexBufferLayout { + array_stride: 5 * 4, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32], + }], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + unclipped_depth: false, + conservative: false, + cull_mode: None, + front_face: wgpu::FrontFace::default(), + polygon_mode: wgpu::PolygonMode::default(), + strip_index_format: None, + }, + depth_stencil, + multisample: wgpu::MultisampleState { + alpha_to_coverage_enabled: false, + count: options.msaa_samples.max(1), + mask: !0, + }, + fragment: Some(wgpu::FragmentState { + module, + entry_point: Some(if output_color_format.is_srgb() { + log::warn!( + "Detected a linear (sRGBA aware) framebuffer {output_color_format:?}. egui prefers Rgba8Unorm or Bgra8Unorm" + ); + "fs_main_linear_framebuffer" + } else { + "fs_main_gamma_framebuffer" + }), + targets: &[Some(wgpu::ColorTargetState { + format: output_color_format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::OneMinusDstAlpha, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + multiview_mask: None, + cache: None, + }) +} + impl Renderer { /// Creates a renderer for a egui UI. /// @@ -357,81 +437,13 @@ impl Renderer { immediate_size: 0, }); - let depth_stencil = options - .depth_stencil_format - .map(|format| wgpu::DepthStencilState { - format, - depth_write_enabled: Some(false), - depth_compare: Some(wgpu::CompareFunction::Always), - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }); - - let pipeline = { - profiling::scope!("create_render_pipeline"); - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("egui_pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - entry_point: Some("vs_main"), - module: &module, - buffers: &[wgpu::VertexBufferLayout { - array_stride: 5 * 4, - step_mode: wgpu::VertexStepMode::Vertex, - // 0: vec2 position - // 1: vec2 texture coordinates - // 2: uint color - attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32], - }], - compilation_options: wgpu::PipelineCompilationOptions::default() - }, - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - unclipped_depth: false, - conservative: false, - cull_mode: None, - front_face: wgpu::FrontFace::default(), - polygon_mode: wgpu::PolygonMode::default(), - strip_index_format: None, - }, - depth_stencil, - multisample: wgpu::MultisampleState { - alpha_to_coverage_enabled: false, - count: options.msaa_samples.max(1), - mask: !0, - }, - - fragment: Some(wgpu::FragmentState { - module: &module, - entry_point: Some(if output_color_format.is_srgb() { - log::warn!("Detected a linear (sRGBA aware) framebuffer {output_color_format:?}. egui prefers Rgba8Unorm or Bgra8Unorm"); - "fs_main_linear_framebuffer" - } else { - "fs_main_gamma_framebuffer" // this is what we prefer - }), - targets: &[Some(wgpu::ColorTargetState { - format: output_color_format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::OneMinusDstAlpha, - dst_factor: wgpu::BlendFactor::One, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default() - }), - multiview_mask: None, - cache: None, - } - ) - }; + let pipeline = create_egui_render_pipeline( + device, + &pipeline_layout, + &module, + output_color_format, + &options, + ); const VERTEX_BUFFER_START_CAPACITY: wgpu::BufferAddress = (std::mem::size_of::() * 1024) as _; @@ -440,6 +452,9 @@ impl Renderer { Self { pipeline, + shader_module: module, + pipeline_layout, + output_color_format, vertex_buffer: SlicedBuffer { buffer: create_vertex_buffer(device, VERTEX_BUFFER_START_CAPACITY), slices: Vec::with_capacity(64), @@ -463,6 +478,22 @@ impl Renderer { } } + /// Recreate only the render pipeline when MSAA changes (keeps font/UI textures). + pub fn set_msaa_samples(&mut self, device: &wgpu::Device, msaa_samples: u32) { + let msaa_samples = msaa_samples.max(1); + if self.options.msaa_samples == msaa_samples { + return; + } + self.options.msaa_samples = msaa_samples; + self.pipeline = create_egui_render_pipeline( + device, + &self.pipeline_layout, + &self.shader_module, + self.output_color_format, + &self.options, + ); + } + /// Executes the egui renderer onto an existing wgpu renderpass. /// /// Note that the lifetime of `render_pass` is `'static` which requires a call to [`wgpu::RenderPass::forget_lifetime`]. diff --git a/egui-wgpu-0.34.2/src/runtime_msaa.rs b/egui-wgpu-0.34.2/src/runtime_msaa.rs new file mode 100644 index 0000000..1439921 --- /dev/null +++ b/egui-wgpu-0.34.2/src/runtime_msaa.rs @@ -0,0 +1,22 @@ +//! Pending MSAA changes applied at the start of the next Painter frame. + +use std::sync::atomic::{AtomicU32, Ordering}; + +/// Bit 31 = pending flag; low bits = sample count (1 or 4). +static PENDING_MSAA: AtomicU32 = AtomicU32::new(0); +const PENDING_FLAG: u32 = 1 << 31; + +/// Request MSAA sample count for all Painter instances on the next paint. +pub fn request_msaa(samples: u32) { + let samples = samples.max(1); + PENDING_MSAA.store(samples | PENDING_FLAG, Ordering::Release); +} + +pub(crate) fn take_pending_msaa() -> Option { + let v = PENDING_MSAA.swap(0, Ordering::AcqRel); + if v & PENDING_FLAG != 0 { + Some(v & !PENDING_FLAG) + } else { + None + } +} diff --git a/egui-wgpu-0.34.2/src/winit.rs b/egui-wgpu-0.34.2/src/winit.rs index 3f6adfc..e2c12ca 100644 --- a/egui-wgpu-0.34.2/src/winit.rs +++ b/egui-wgpu-0.34.2/src/winit.rs @@ -384,6 +384,30 @@ impl Painter { state.resizing = resizing; } + /// Change MSAA sample count and recreate depth/MSAA attachments for this viewport. + pub fn apply_msaa_samples(&mut self, viewport_id: ViewportId, samples: u32) { + let samples = samples.max(1); + if self.options.msaa_samples == samples { + return; + } + self.options.msaa_samples = samples; + if samples <= 1 { + self.msaa_texture_view.remove(&viewport_id); + } + if let Some(state) = self.surfaces.get(&viewport_id) { + if let (Some(w), Some(h)) = ( + NonZeroU32::new(state.width.max(1)), + NonZeroU32::new(state.height.max(1)), + ) { + self.resize_and_generate_depth_texture_view_and_msaa_view( + viewport_id, + w, + h, + ); + } + } + } + pub fn on_window_resized( &mut self, viewport_id: ViewportId, @@ -422,6 +446,10 @@ impl Painter { ) -> f32 { profiling::function_scope!(); + if let Some(samples) = crate::runtime_msaa::take_pending_msaa() { + self.apply_msaa_samples(viewport_id, samples); + } + /// Guard to ensure that commands are always submitted to the renderer queue /// so that calls to [`write_buffer()`](https://docs.rs/wgpu/latest/wgpu/struct.Queue.html#method.write_buffer) /// are completed even if we take a codepath which doesn't submit commands and avoids