Skip to content

Commit 086e6a4

Browse files
committed
Make winit an opt-in feature of egui-wgpu
1 parent cb57b07 commit 086e6a4

File tree

5 files changed

+166
-152
lines changed

5 files changed

+166
-152
lines changed

eframe/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ tracing = "0.1"
6464

6565
# optional:
6666
egui_glow = { version = "0.18.0", path = "../egui_glow", optional = true, default-features = false }
67-
egui-wgpu = { version = "0.18.0", path = "../egui-wgpu", optional = true, default-features = false }
67+
egui-wgpu = { version = "0.18.0", path = "../egui-wgpu", optional = true, features = ["winit"] }
6868
glow = { version = "0.11", optional = true }
6969
ron = { version = "0.7", optional = true }
7070
serde = { version = "1", optional = true, features = ["derive"] }

eframe/src/native/run.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ pub fn run_wgpu(
211211
.unwrap();
212212

213213
// SAFETY: `window` must outlive `painter`.
214-
let mut painter = unsafe { egui_wgpu::Painter::new(&window) };
214+
#[allow(unsafe_code)]
215+
let mut painter = unsafe { egui_wgpu::winit::Painter::new(&window) };
215216

216217
let mut integration = epi_integration::EpiIntegration::new(
217218
painter.max_texture_side(),

egui-wgpu/Cargo.toml

+9-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ keywords = ["wgpu", "egui", "gui", "gamedev"]
1818
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
1919

2020

21+
[features]
22+
# Make it easy to create bindings for winit.
23+
winit = ["dep:pollster", "dep:winit"]
24+
25+
2126
[dependencies]
2227
egui = { version = "0.18.1", path = "../egui", default-features = false, features = [
2328
"bytemuck",
2429
] }
2530

2631
bytemuck = "1.7"
27-
pollster = "0.2"
2832
tracing = "0.1"
2933
wgpu = { version = "0.12", features = ["webgl"] }
30-
winit = "0.26"
34+
35+
# Optional:
36+
pollster = { version = "0.2", optional = true }
37+
winit = { version = "0.26", optional = true }

egui-wgpu/src/lib.rs

+4-148
Original file line numberDiff line numberDiff line change
@@ -2,153 +2,9 @@
22
33
#![allow(unsafe_code)]
44

5+
/// Low-level painting of [`egui`] on [`wgpu`].
56
pub mod renderer;
67

7-
/// Everything you need to paint egui with [`wgpu`] on winit.
8-
///
9-
/// Alternatively you can use [`crate::renderer`] directly.
10-
pub struct Painter {
11-
device: wgpu::Device,
12-
queue: wgpu::Queue,
13-
surface_config: wgpu::SurfaceConfiguration,
14-
surface: wgpu::Surface,
15-
egui_rpass: renderer::RenderPass,
16-
}
17-
18-
impl Painter {
19-
/// Creates a [`wgpu`] surface for the given window, and things required to render egui onto it.
20-
///
21-
/// SAFETY: The given window MUST outlive [`Painter`].
22-
pub unsafe fn new(window: &'window winit::window::Window) -> Self {
23-
let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY | wgpu::Backends::GL);
24-
let surface = unsafe { instance.create_surface(&window) };
25-
26-
// WGPU 0.11+ support force fallback (if HW implementation not supported), set it to true or false (optional).
27-
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
28-
power_preference: wgpu::PowerPreference::HighPerformance,
29-
compatible_surface: Some(&surface),
30-
force_fallback_adapter: false,
31-
}))
32-
.unwrap();
33-
34-
let (device, queue) = pollster::block_on(adapter.request_device(
35-
&wgpu::DeviceDescriptor {
36-
features: wgpu::Features::default(),
37-
limits: wgpu::Limits::default(),
38-
label: None,
39-
},
40-
None,
41-
))
42-
.unwrap();
43-
44-
let size = window.inner_size();
45-
let surface_format = surface.get_preferred_format(&adapter).unwrap();
46-
let surface_config = wgpu::SurfaceConfiguration {
47-
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
48-
format: surface_format,
49-
width: size.width as u32,
50-
height: size.height as u32,
51-
present_mode: wgpu::PresentMode::Fifo, // TODO: make vsync configurable
52-
};
53-
surface.configure(&device, &surface_config);
54-
55-
let egui_rpass = renderer::RenderPass::new(&device, surface_format, 1);
56-
57-
Self {
58-
device,
59-
queue,
60-
surface_config,
61-
surface,
62-
egui_rpass,
63-
}
64-
}
65-
66-
pub fn max_texture_side(&self) -> usize {
67-
self.device.limits().max_texture_dimension_2d as usize
68-
}
69-
70-
pub fn on_window_resized(&mut self, width: u32, height: u32) {
71-
self.surface_config.width = width;
72-
self.surface_config.height = height;
73-
self.surface.configure(&self.device, &self.surface_config);
74-
}
75-
76-
pub fn paint_and_update_textures(
77-
&mut self,
78-
pixels_per_point: f32,
79-
clear_color: egui::Rgba,
80-
clipped_primitives: &[egui::ClippedPrimitive],
81-
textures_delta: &egui::TexturesDelta,
82-
) {
83-
let output_frame = match self.surface.get_current_texture() {
84-
Ok(frame) => frame,
85-
Err(wgpu::SurfaceError::Outdated) => {
86-
// This error occurs when the app is minimized on Windows.
87-
// Silently return here to prevent spamming the console with:
88-
// "The underlying surface has changed, and therefore the swap chain must be updated"
89-
return;
90-
}
91-
Err(e) => {
92-
tracing::warn!("Dropped frame with error: {e}");
93-
return;
94-
}
95-
};
96-
let output_view = output_frame
97-
.texture
98-
.create_view(&wgpu::TextureViewDescriptor::default());
99-
100-
let mut encoder = self
101-
.device
102-
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
103-
label: Some("encoder"),
104-
});
105-
106-
// Upload all resources for the GPU.
107-
let screen_descriptor = renderer::ScreenDescriptor {
108-
size_in_pixels: [self.surface_config.width, self.surface_config.height],
109-
pixels_per_point,
110-
};
111-
112-
for (id, image_delta) in &textures_delta.set {
113-
self.egui_rpass
114-
.update_texture(&self.device, &self.queue, *id, image_delta);
115-
}
116-
for id in &textures_delta.free {
117-
self.egui_rpass.free_texture(id);
118-
}
119-
120-
self.egui_rpass.update_buffers(
121-
&self.device,
122-
&self.queue,
123-
clipped_primitives,
124-
&screen_descriptor,
125-
);
126-
127-
// Record all render passes.
128-
self.egui_rpass
129-
.execute(
130-
&mut encoder,
131-
&output_view,
132-
clipped_primitives,
133-
&screen_descriptor,
134-
Some(wgpu::Color {
135-
r: clear_color.r() as f64,
136-
g: clear_color.g() as f64,
137-
b: clear_color.b() as f64,
138-
a: clear_color.a() as f64,
139-
}),
140-
)
141-
.unwrap();
142-
143-
// Submit the commands.
144-
self.queue.submit(std::iter::once(encoder.finish()));
145-
146-
// Redraw egui
147-
output_frame.present();
148-
}
149-
150-
#[allow(clippy::unused_self)]
151-
pub fn destroy(&mut self) {
152-
// TODO: something here?
153-
}
154-
}
8+
/// Module for painting [`egui`] with [`wgpu`] on [`winit`].
9+
#[cfg(feature = "winit")]
10+
pub mod winit;

egui-wgpu/src/winit.rs

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use crate::renderer;
2+
3+
/// Everything you need to paint egui with [`wgpu`] on [`winit`].
4+
///
5+
/// Alternatively you can use [`crate::renderer`] directly.
6+
pub struct Painter {
7+
device: wgpu::Device,
8+
queue: wgpu::Queue,
9+
surface_config: wgpu::SurfaceConfiguration,
10+
surface: wgpu::Surface,
11+
egui_rpass: renderer::RenderPass,
12+
}
13+
14+
impl Painter {
15+
/// Creates a [`wgpu`] surface for the given window, and things required to render egui onto it.
16+
///
17+
/// SAFETY: The given window MUST outlive [`Painter`].
18+
pub unsafe fn new(window: &winit::window::Window) -> Self {
19+
let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY | wgpu::Backends::GL);
20+
let surface = instance.create_surface(&window);
21+
22+
// WGPU 0.11+ support force fallback (if HW implementation not supported), set it to true or false (optional).
23+
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
24+
power_preference: wgpu::PowerPreference::HighPerformance,
25+
compatible_surface: Some(&surface),
26+
force_fallback_adapter: false,
27+
}))
28+
.unwrap();
29+
30+
let (device, queue) = pollster::block_on(adapter.request_device(
31+
&wgpu::DeviceDescriptor {
32+
features: wgpu::Features::default(),
33+
limits: wgpu::Limits::default(),
34+
label: None,
35+
},
36+
None,
37+
))
38+
.unwrap();
39+
40+
let size = window.inner_size();
41+
let surface_format = surface.get_preferred_format(&adapter).unwrap();
42+
let surface_config = wgpu::SurfaceConfiguration {
43+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
44+
format: surface_format,
45+
width: size.width as u32,
46+
height: size.height as u32,
47+
present_mode: wgpu::PresentMode::Fifo, // TODO: make vsync configurable
48+
};
49+
surface.configure(&device, &surface_config);
50+
51+
let egui_rpass = renderer::RenderPass::new(&device, surface_format, 1);
52+
53+
Self {
54+
device,
55+
queue,
56+
surface_config,
57+
surface,
58+
egui_rpass,
59+
}
60+
}
61+
62+
pub fn max_texture_side(&self) -> usize {
63+
self.device.limits().max_texture_dimension_2d as usize
64+
}
65+
66+
pub fn on_window_resized(&mut self, width: u32, height: u32) {
67+
self.surface_config.width = width;
68+
self.surface_config.height = height;
69+
self.surface.configure(&self.device, &self.surface_config);
70+
}
71+
72+
pub fn paint_and_update_textures(
73+
&mut self,
74+
pixels_per_point: f32,
75+
clear_color: egui::Rgba,
76+
clipped_primitives: &[egui::ClippedPrimitive],
77+
textures_delta: &egui::TexturesDelta,
78+
) {
79+
let output_frame = match self.surface.get_current_texture() {
80+
Ok(frame) => frame,
81+
Err(wgpu::SurfaceError::Outdated) => {
82+
// This error occurs when the app is minimized on Windows.
83+
// Silently return here to prevent spamming the console with:
84+
// "The underlying surface has changed, and therefore the swap chain must be updated"
85+
return;
86+
}
87+
Err(e) => {
88+
tracing::warn!("Dropped frame with error: {e}");
89+
return;
90+
}
91+
};
92+
let output_view = output_frame
93+
.texture
94+
.create_view(&wgpu::TextureViewDescriptor::default());
95+
96+
let mut encoder = self
97+
.device
98+
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
99+
label: Some("encoder"),
100+
});
101+
102+
// Upload all resources for the GPU.
103+
let screen_descriptor = renderer::ScreenDescriptor {
104+
size_in_pixels: [self.surface_config.width, self.surface_config.height],
105+
pixels_per_point,
106+
};
107+
108+
for (id, image_delta) in &textures_delta.set {
109+
self.egui_rpass
110+
.update_texture(&self.device, &self.queue, *id, image_delta);
111+
}
112+
for id in &textures_delta.free {
113+
self.egui_rpass.free_texture(id);
114+
}
115+
116+
self.egui_rpass.update_buffers(
117+
&self.device,
118+
&self.queue,
119+
clipped_primitives,
120+
&screen_descriptor,
121+
);
122+
123+
// Record all render passes.
124+
self.egui_rpass
125+
.execute(
126+
&mut encoder,
127+
&output_view,
128+
clipped_primitives,
129+
&screen_descriptor,
130+
Some(wgpu::Color {
131+
r: clear_color.r() as f64,
132+
g: clear_color.g() as f64,
133+
b: clear_color.b() as f64,
134+
a: clear_color.a() as f64,
135+
}),
136+
)
137+
.unwrap();
138+
139+
// Submit the commands.
140+
self.queue.submit(std::iter::once(encoder.finish()));
141+
142+
// Redraw egui
143+
output_frame.present();
144+
}
145+
146+
#[allow(clippy::unused_self)]
147+
pub fn destroy(&mut self) {
148+
// TODO: something here?
149+
}
150+
}

0 commit comments

Comments
 (0)