2022-05-28 15:52:36 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use egui::mutex::RwLock;
|
2022-05-22 18:24:41 +00:00
|
|
|
use tracing::error;
|
|
|
|
use wgpu::{Adapter, Instance, Surface, TextureFormat};
|
|
|
|
|
2022-05-12 07:02:28 +00:00
|
|
|
use crate::renderer;
|
|
|
|
|
2022-05-28 15:52:36 +00:00
|
|
|
/// Access to the render state for egui, which can be useful in combination with
|
|
|
|
/// [`egui::PaintCallback`]s for custom rendering using WGPU.
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct RenderState {
|
|
|
|
pub device: Arc<wgpu::Device>,
|
|
|
|
pub queue: Arc<wgpu::Queue>,
|
|
|
|
pub target_format: TextureFormat,
|
|
|
|
pub egui_rpass: Arc<RwLock<renderer::RenderPass>>,
|
2022-05-12 07:02:28 +00:00
|
|
|
}
|
|
|
|
|
2022-05-22 18:24:41 +00:00
|
|
|
struct SurfaceState {
|
|
|
|
surface: Surface,
|
|
|
|
width: u32,
|
|
|
|
height: u32,
|
|
|
|
}
|
2022-05-12 07:02:28 +00:00
|
|
|
|
2022-05-22 18:24:41 +00:00
|
|
|
/// Everything you need to paint egui with [`wgpu`] on [`winit`].
|
|
|
|
///
|
|
|
|
/// Alternatively you can use [`crate::renderer`] directly.
|
|
|
|
pub struct Painter<'a> {
|
|
|
|
power_preference: wgpu::PowerPreference,
|
|
|
|
device_descriptor: wgpu::DeviceDescriptor<'a>,
|
|
|
|
present_mode: wgpu::PresentMode,
|
|
|
|
msaa_samples: u32,
|
|
|
|
|
|
|
|
instance: Instance,
|
|
|
|
adapter: Option<Adapter>,
|
|
|
|
render_state: Option<RenderState>,
|
|
|
|
surface_state: Option<SurfaceState>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Painter<'a> {
|
|
|
|
/// Manages [`wgpu`] state, including surface state, required to render egui.
|
|
|
|
///
|
|
|
|
/// Only the [`wgpu::Instance`] is initialized here. Device selection and the initialization
|
|
|
|
/// of render + surface state is deferred until the painter is given its first window target
|
|
|
|
/// via [`set_window()`](Self::set_window). (Ensuring that a device that's compatible with the
|
|
|
|
/// native window is chosen)
|
|
|
|
///
|
|
|
|
/// Before calling [`paint_and_update_textures()`](Self::paint_and_update_textures) a
|
|
|
|
/// [`wgpu::Surface`] must be initialized (and corresponding render state) by calling
|
|
|
|
/// [`set_window()`](Self::set_window) once you have
|
|
|
|
/// a [`winit::window::Window`] with a valid `.raw_window_handle()`
|
|
|
|
/// associated.
|
|
|
|
pub fn new(
|
|
|
|
backends: wgpu::Backends,
|
|
|
|
power_preference: wgpu::PowerPreference,
|
|
|
|
device_descriptor: wgpu::DeviceDescriptor<'a>,
|
|
|
|
present_mode: wgpu::PresentMode,
|
|
|
|
msaa_samples: u32,
|
|
|
|
) -> Self {
|
|
|
|
let instance = wgpu::Instance::new(backends);
|
2022-05-12 07:02:28 +00:00
|
|
|
|
|
|
|
Self {
|
2022-05-22 18:24:41 +00:00
|
|
|
power_preference,
|
|
|
|
device_descriptor,
|
|
|
|
present_mode,
|
|
|
|
msaa_samples,
|
|
|
|
|
|
|
|
instance,
|
|
|
|
adapter: None,
|
|
|
|
render_state: None,
|
|
|
|
surface_state: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-28 15:52:36 +00:00
|
|
|
/// Get the [`RenderState`].
|
|
|
|
///
|
|
|
|
/// Will return [`None`] if the render state has not been initialized yet.
|
|
|
|
pub fn get_render_state(&self) -> Option<RenderState> {
|
|
|
|
self.render_state.as_ref().cloned()
|
|
|
|
}
|
|
|
|
|
2022-05-22 18:24:41 +00:00
|
|
|
async fn init_render_state(
|
|
|
|
&self,
|
|
|
|
adapter: &Adapter,
|
|
|
|
target_format: TextureFormat,
|
|
|
|
) -> RenderState {
|
|
|
|
let (device, queue) =
|
|
|
|
pollster::block_on(adapter.request_device(&self.device_descriptor, None)).unwrap();
|
|
|
|
|
2022-05-28 15:52:36 +00:00
|
|
|
let rpass = renderer::RenderPass::new(&device, target_format, self.msaa_samples);
|
2022-05-22 18:24:41 +00:00
|
|
|
|
|
|
|
RenderState {
|
2022-05-28 15:52:36 +00:00
|
|
|
device: Arc::new(device),
|
|
|
|
queue: Arc::new(queue),
|
2022-05-22 18:24:41 +00:00
|
|
|
target_format,
|
2022-05-28 15:52:36 +00:00
|
|
|
egui_rpass: Arc::new(RwLock::new(rpass)),
|
2022-05-12 07:02:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 18:24:41 +00:00
|
|
|
// We want to defer the initialization of our render state until we have a surface
|
|
|
|
// so we can take its format into account.
|
|
|
|
//
|
|
|
|
// After we've initialized our render state once though we expect all future surfaces
|
|
|
|
// will have the same format and so this render state will remain valid.
|
|
|
|
fn ensure_render_state_for_surface(&mut self, surface: &Surface) {
|
|
|
|
self.adapter.get_or_insert_with(|| {
|
|
|
|
pollster::block_on(self.instance.request_adapter(&wgpu::RequestAdapterOptions {
|
|
|
|
power_preference: self.power_preference,
|
|
|
|
compatible_surface: Some(surface),
|
|
|
|
force_fallback_adapter: false,
|
|
|
|
}))
|
|
|
|
.unwrap()
|
|
|
|
});
|
|
|
|
|
|
|
|
if self.render_state.is_none() {
|
|
|
|
let adapter = self.adapter.as_ref().unwrap();
|
|
|
|
let swapchain_format = surface.get_preferred_format(adapter).unwrap();
|
|
|
|
|
|
|
|
let rs = pollster::block_on(self.init_render_state(adapter, swapchain_format));
|
|
|
|
self.render_state = Some(rs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn configure_surface(&mut self, width_in_pixels: u32, height_in_pixels: u32) {
|
|
|
|
let render_state = self
|
|
|
|
.render_state
|
|
|
|
.as_ref()
|
|
|
|
.expect("Render state should exist before surface configuration");
|
|
|
|
let format = render_state.target_format;
|
|
|
|
|
|
|
|
let config = wgpu::SurfaceConfiguration {
|
|
|
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
|
|
format,
|
|
|
|
width: width_in_pixels,
|
|
|
|
height: height_in_pixels,
|
|
|
|
present_mode: self.present_mode,
|
|
|
|
};
|
|
|
|
|
|
|
|
let surface_state = self
|
|
|
|
.surface_state
|
|
|
|
.as_mut()
|
|
|
|
.expect("Surface state should exist before surface configuration");
|
|
|
|
surface_state
|
|
|
|
.surface
|
|
|
|
.configure(&render_state.device, &config);
|
|
|
|
surface_state.width = width_in_pixels;
|
|
|
|
surface_state.height = height_in_pixels;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`]
|
|
|
|
///
|
|
|
|
/// This creates a [`wgpu::Surface`] for the given Window (as well as initializing render
|
|
|
|
/// state if needed) that is used for egui rendering.
|
|
|
|
///
|
|
|
|
/// This must be called before trying to render via
|
|
|
|
/// [`paint_and_update_textures`](Self::paint_and_update_textures)
|
|
|
|
///
|
|
|
|
/// # Portability
|
|
|
|
///
|
|
|
|
/// _In particular it's important to note that on Android a it's only possible to create
|
|
|
|
/// a window surface between `Resumed` and `Paused` lifecycle events, and Winit will panic on
|
|
|
|
/// attempts to query the raw window handle while paused._
|
|
|
|
///
|
|
|
|
/// On Android [`set_window`](Self::set_window) should be called with `Some(window)` for each
|
|
|
|
/// `Resumed` event and `None` for each `Paused` event. Currently, on all other platforms
|
|
|
|
/// [`set_window`](Self::set_window) may be called with `Some(window)` as soon as you have a
|
|
|
|
/// valid [`winit::window::Window`].
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// The raw Window handle associated with the given `window` must be a valid object to create a
|
|
|
|
/// surface upon and must remain valid for the lifetime of the created surface. (The surface may
|
|
|
|
/// be cleared by passing `None`).
|
|
|
|
pub unsafe fn set_window(&mut self, window: Option<&winit::window::Window>) {
|
|
|
|
match window {
|
|
|
|
Some(window) => {
|
|
|
|
let surface = self.instance.create_surface(&window);
|
|
|
|
|
|
|
|
self.ensure_render_state_for_surface(&surface);
|
|
|
|
|
|
|
|
let size = window.inner_size();
|
|
|
|
let width = size.width as u32;
|
|
|
|
let height = size.height as u32;
|
|
|
|
self.surface_state = Some(SurfaceState {
|
|
|
|
surface,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
});
|
|
|
|
self.configure_surface(width, height);
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
self.surface_state = None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the maximum texture dimension supported if known
|
|
|
|
///
|
|
|
|
/// This API will only return a known dimension after `set_window()` has been called
|
|
|
|
/// at least once, since the underlying device and render state are initialized lazily
|
|
|
|
/// once we have a window (that may determine the choice of adapter/device).
|
|
|
|
pub fn max_texture_side(&self) -> Option<usize> {
|
|
|
|
self.render_state
|
|
|
|
.as_ref()
|
|
|
|
.map(|rs| rs.device.limits().max_texture_dimension_2d as usize)
|
2022-05-12 07:02:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn on_window_resized(&mut self, width_in_pixels: u32, height_in_pixels: u32) {
|
2022-05-22 18:24:41 +00:00
|
|
|
if self.surface_state.is_some() {
|
|
|
|
self.configure_surface(width_in_pixels, height_in_pixels);
|
|
|
|
} else {
|
|
|
|
error!("Ignoring window resize notification with no surface created via Painter::set_window()");
|
|
|
|
}
|
2022-05-12 07:02:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn paint_and_update_textures(
|
|
|
|
&mut self,
|
|
|
|
pixels_per_point: f32,
|
|
|
|
clear_color: egui::Rgba,
|
|
|
|
clipped_primitives: &[egui::ClippedPrimitive],
|
|
|
|
textures_delta: &egui::TexturesDelta,
|
|
|
|
) {
|
2022-05-22 18:24:41 +00:00
|
|
|
let render_state = match self.render_state.as_mut() {
|
|
|
|
Some(rs) => rs,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
let surface_state = match self.surface_state.as_ref() {
|
|
|
|
Some(rs) => rs,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
let output_frame = match surface_state.surface.get_current_texture() {
|
2022-05-12 07:02:28 +00:00
|
|
|
Ok(frame) => frame,
|
|
|
|
Err(wgpu::SurfaceError::Outdated) => {
|
|
|
|
// This error occurs when the app is minimized on Windows.
|
|
|
|
// Silently return here to prevent spamming the console with:
|
|
|
|
// "The underlying surface has changed, and therefore the swap chain must be updated"
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
tracing::warn!("Dropped frame with error: {e}");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let output_view = output_frame
|
|
|
|
.texture
|
|
|
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
|
|
|
|
2022-05-22 18:24:41 +00:00
|
|
|
let mut encoder =
|
|
|
|
render_state
|
|
|
|
.device
|
|
|
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
|
|
label: Some("encoder"),
|
|
|
|
});
|
2022-05-12 07:02:28 +00:00
|
|
|
|
|
|
|
// Upload all resources for the GPU.
|
|
|
|
let screen_descriptor = renderer::ScreenDescriptor {
|
2022-05-22 18:24:41 +00:00
|
|
|
size_in_pixels: [surface_state.width, surface_state.height],
|
2022-05-12 07:02:28 +00:00
|
|
|
pixels_per_point,
|
|
|
|
};
|
|
|
|
|
2022-05-28 15:52:36 +00:00
|
|
|
{
|
|
|
|
let mut rpass = render_state.egui_rpass.write();
|
|
|
|
for (id, image_delta) in &textures_delta.set {
|
|
|
|
rpass.update_texture(&render_state.device, &render_state.queue, *id, image_delta);
|
|
|
|
}
|
|
|
|
|
|
|
|
rpass.update_buffers(
|
2022-05-22 18:24:41 +00:00
|
|
|
&render_state.device,
|
|
|
|
&render_state.queue,
|
2022-05-28 15:52:36 +00:00
|
|
|
clipped_primitives,
|
|
|
|
&screen_descriptor,
|
2022-05-22 18:24:41 +00:00
|
|
|
);
|
2022-05-12 07:02:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Record all render passes.
|
2022-05-28 15:52:36 +00:00
|
|
|
render_state.egui_rpass.read().execute(
|
2022-05-12 07:02:28 +00:00
|
|
|
&mut encoder,
|
|
|
|
&output_view,
|
|
|
|
clipped_primitives,
|
|
|
|
&screen_descriptor,
|
|
|
|
Some(wgpu::Color {
|
|
|
|
r: clear_color.r() as f64,
|
|
|
|
g: clear_color.g() as f64,
|
|
|
|
b: clear_color.b() as f64,
|
|
|
|
a: clear_color.a() as f64,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
2022-05-28 15:52:36 +00:00
|
|
|
{
|
|
|
|
let mut rpass = render_state.egui_rpass.write();
|
|
|
|
for id in &textures_delta.free {
|
|
|
|
rpass.free_texture(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 07:02:28 +00:00
|
|
|
// Submit the commands.
|
2022-05-22 18:24:41 +00:00
|
|
|
render_state.queue.submit(std::iter::once(encoder.finish()));
|
2022-05-12 07:02:28 +00:00
|
|
|
|
|
|
|
// Redraw egui
|
|
|
|
output_frame.present();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::unused_self)]
|
|
|
|
pub fn destroy(&mut self) {
|
2022-05-21 14:53:25 +00:00
|
|
|
// TODO(emilk): something here?
|
2022-05-12 07:02:28 +00:00
|
|
|
}
|
|
|
|
}
|