@ -1,72 +1,203 @@
use tracing ::error ;
use wgpu ::{ Adapter , Instance , Surface , TextureFormat } ;
use crate ::renderer ;
/// Everything you need to paint egui with [`wgpu`] on [`winit`].
///
/// Alternatively you can use [`crate::renderer`] directly.
pub struct Painter {
struct RenderState {
device : wgpu ::Device ,
queue : wgpu ::Queue ,
surface_config : wgpu ::SurfaceConfiguration ,
surface : wgpu ::Surface ,
target_format : TextureFormat ,
egui_rpass : renderer ::RenderPass ,
}
impl Painter {
/// Creates a [`wgpu`] surface for the given window, and things required to render egui onto it.
///
/// # Safety
/// The given `window` must outlive the returned [`Painter`].
pub unsafe fn new ( window : & winit ::window ::Window , msaa_samples : u32 ) -> Self {
let instance = wgpu ::Instance ::new ( wgpu ::Backends ::PRIMARY | wgpu ::Backends ::GL ) ;
let surface = instance . create_surface ( & window ) ;
let adapter = pollster ::block_on ( instance . request_adapter ( & wgpu ::RequestAdapterOptions {
power_preference : wgpu ::PowerPreference ::HighPerformance ,
compatible_surface : Some ( & surface ) ,
force_fallback_adapter : false ,
} ) )
. unwrap ( ) ;
let ( device , queue ) = pollster ::block_on ( adapter . request_device (
& wgpu ::DeviceDescriptor {
features : wgpu ::Features ::default ( ) ,
limits : wgpu ::Limits ::default ( ) ,
label : None ,
} ,
None ,
) )
. unwrap ( ) ;
let size = window . inner_size ( ) ;
let surface_format = surface . get_preferred_format ( & adapter ) . unwrap ( ) ;
let surface_config = wgpu ::SurfaceConfiguration {
usage : wgpu ::TextureUsages ::RENDER_ATTACHMENT ,
format : surface_format ,
width : size . width as u32 ,
height : size . height as u32 ,
present_mode : wgpu ::PresentMode ::Fifo , // TODO(emilk): make vsync configurable
} ;
surface . configure ( & device , & surface_config ) ;
struct SurfaceState {
surface : Surface ,
width : u32 ,
height : u32 ,
}
let egui_rpass = renderer ::RenderPass ::new ( & device , surface_format , msaa_samples ) ;
/// 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 ) ;
Self {
power_preference ,
device_descriptor ,
present_mode ,
msaa_samples ,
instance ,
adapter : None ,
render_state : None ,
surface_state : None ,
}
}
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 ( ) ;
let egui_rpass = renderer ::RenderPass ::new ( & device , target_format , self . msaa_samples ) ;
RenderState {
device ,
queue ,
surface_config ,
surface ,
target_format ,
egui_rpass ,
}
}
pub fn max_texture_side ( & self ) -> usize {
self . device . limits ( ) . max_texture_dimension_2d as usize
// 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 )
}
pub fn on_window_resized ( & mut self , width_in_pixels : u32 , height_in_pixels : u32 ) {
self . surface_config . width = width_in_pixels ;
self . surface_config . height = height_in_pixels ;
self . surface . configure ( & self . device , & self . surface_config ) ;
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()" ) ;
}
}
pub fn paint_and_update_textures (
@ -76,7 +207,16 @@ impl Painter {
clipped_primitives : & [ egui ::ClippedPrimitive ] ,
textures_delta : & egui ::TexturesDelta ,
) {
let output_frame = match self . surface . get_current_texture ( ) {
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 ( ) {
Ok ( frame ) = > frame ,
Err ( wgpu ::SurfaceError ::Outdated ) = > {
// This error occurs when the app is minimized on Windows.
@ -93,35 +233,40 @@ impl Painter {
. texture
. create_view ( & wgpu ::TextureViewDescriptor ::default ( ) ) ;
let mut encoder = self
. device
. create_command_encoder ( & wgpu ::CommandEncoderDescriptor {
label : Some ( "encoder" ) ,
} ) ;
let mut encoder =
render_state
. device
. create_command_encoder ( & wgpu ::CommandEncoderDescriptor {
label : Some ( "encoder" ) ,
} ) ;
// Upload all resources for the GPU.
let screen_descriptor = renderer ::ScreenDescriptor {
size_in_pixels : [ self . surface_config . width , self . surface_config . height ] ,
size_in_pixels : [ surface_state . width , surface_state . height ] ,
pixels_per_point ,
} ;
for ( id , image_delta ) in & textures_delta . set {
self . egui_rpass
. update_texture ( & self . device , & self . queue , * id , image_delta ) ;
render_state . egui_rpass . update_texture (
& render_state . device ,
& render_state . queue ,
* id ,
image_delta ,
) ;
}
for id in & textures_delta . free {
self . egui_rpass . free_texture ( id ) ;
render_state . egui_rpass . free_texture ( id ) ;
}
self . egui_rpass . update_buffers (
& self . device ,
& self . queue ,
render_state . egui_rpass . update_buffers (
& render_state . device ,
& render_state . queue ,
clipped_primitives ,
& screen_descriptor ,
) ;
// Record all render passes.
self . egui_rpass . execute (
render_state . egui_rpass . execute (
& mut encoder ,
& output_view ,
clipped_primitives ,
@ -135,7 +280,7 @@ impl Painter {
) ;
// Submit the commands.
self . queue . submit ( std ::iter ::once ( encoder . finish ( ) ) ) ;
render_state . queue . submit ( std ::iter ::once ( encoder . finish ( ) ) ) ;
// Redraw egui
output_frame . present ( ) ;