2022-05-12 07:02:28 +00:00
#![ allow(unsafe_code) ]
2022-10-05 18:14:18 +00:00
use std ::num ::NonZeroU64 ;
2022-10-14 08:53:19 +00:00
use std ::ops ::Range ;
2022-05-12 07:02:28 +00:00
use std ::{ borrow ::Cow , collections ::HashMap , num ::NonZeroU32 } ;
2022-10-14 08:53:19 +00:00
use egui ::epaint ::Vertex ;
use egui ::NumExt ;
2022-09-13 07:32:05 +00:00
use egui ::{ epaint ::Primitive , PaintCallbackInfo } ;
2022-08-03 07:26:16 +00:00
use type_map ::concurrent ::TypeMap ;
2022-05-12 07:02:28 +00:00
use wgpu ;
use wgpu ::util ::DeviceExt as _ ;
2022-05-28 15:52:36 +00:00
/// A callback function that can be used to compose an [`egui::PaintCallback`] for custom WGPU
/// rendering.
///
/// The callback is composed of two functions: `prepare` and `paint`.
///
/// `prepare` is called every frame before `paint`, and can use the passed-in [`wgpu::Device`] and
/// [`wgpu::Buffer`] to allocate or modify GPU resources such as buffers.
2022-10-12 12:27:24 +00:00
/// Additionally, a [`wgpu::CommandEncoder`] is provided in order to allow creation of
/// custom [`wgpu::RenderPass`]/[`wgpu::ComputePass`] or perform buffer/texture copies
/// which may serve as preparation to the final `paint`.
/// (This allows reusing the same [`wgpu::CommandEncoder`] for all callbacks and egui rendering itself)
2022-05-28 15:52:36 +00:00
///
2022-10-12 12:27:24 +00:00
/// `paint` is called after `prepare` and is given access to the [`wgpu::RenderPass`] so that it
/// can issue draw commands into the same [`wgpu::RenderPass`] that is used for all other egui elements.
2022-05-28 15:52:36 +00:00
///
/// The final argument of both the `prepare` and `paint` callbacks is a the
2022-09-07 12:20:21 +00:00
/// [`paint_callback_resources`][crate::renderer::Renderer::paint_callback_resources].
2022-05-28 15:52:36 +00:00
/// `paint_callback_resources` has the same lifetime as the Egui render pass, so it can be used to
/// store buffers, pipelines, and other information that needs to be accessed during the render
/// pass.
///
/// # Example
///
2022-10-05 18:14:18 +00:00
/// See the [`custom3d_wgpu`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
2022-05-28 15:52:36 +00:00
pub struct CallbackFn {
prepare : Box < PrepareCallback > ,
paint : Box < PaintCallback > ,
}
2022-10-12 12:27:24 +00:00
type PrepareCallback =
dyn Fn ( & wgpu ::Device , & wgpu ::Queue , & mut wgpu ::CommandEncoder , & mut TypeMap ) + Sync + Send ;
2022-08-02 15:26:33 +00:00
2022-05-28 15:52:36 +00:00
type PaintCallback =
dyn for < ' a , ' b > Fn ( PaintCallbackInfo , & ' a mut wgpu ::RenderPass < ' b > , & ' b TypeMap ) + Sync + Send ;
impl Default for CallbackFn {
fn default ( ) -> Self {
CallbackFn {
2022-10-12 12:27:24 +00:00
prepare : Box ::new ( | _ , _ , _ , _ | ( ) ) ,
2022-05-28 15:52:36 +00:00
paint : Box ::new ( | _ , _ , _ | ( ) ) ,
}
}
}
impl CallbackFn {
pub fn new ( ) -> Self {
Self ::default ( )
}
/// Set the prepare callback
pub fn prepare < F > ( mut self , prepare : F ) -> Self
where
2022-10-12 12:27:24 +00:00
F : Fn ( & wgpu ::Device , & wgpu ::Queue , & mut wgpu ::CommandEncoder , & mut TypeMap )
+ Sync
+ Send
+ 'static ,
2022-05-28 15:52:36 +00:00
{
self . prepare = Box ::new ( prepare ) as _ ;
self
}
/// Set the paint callback
pub fn paint < F > ( mut self , paint : F ) -> Self
where
F : for < ' a , ' b > Fn ( PaintCallbackInfo , & ' a mut wgpu ::RenderPass < ' b > , & ' b TypeMap )
+ Sync
+ Send
+ 'static ,
{
self . paint = Box ::new ( paint ) as _ ;
self
}
}
2022-05-12 07:02:28 +00:00
/// Information about the screen used for rendering.
pub struct ScreenDescriptor {
/// Size of the window in physical pixels.
pub size_in_pixels : [ u32 ; 2 ] ,
/// HiDPI scale factor (pixels per point).
pub pixels_per_point : f32 ,
}
impl ScreenDescriptor {
/// size in "logical" points
fn screen_size_in_points ( & self ) -> [ f32 ; 2 ] {
[
self . size_in_pixels [ 0 ] as f32 / self . pixels_per_point ,
self . size_in_pixels [ 1 ] as f32 / self . pixels_per_point ,
]
}
}
/// Uniform buffer used when rendering.
#[ derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable) ]
#[ repr(C) ]
struct UniformBuffer {
screen_size_in_points : [ f32 ; 2 ] ,
2022-05-28 16:41:01 +00:00
// Uniform buffers need to be at least 16 bytes in WebGL.
// See https://github.com/gfx-rs/wgpu/issues/2072
_padding : [ u32 ; 2 ] ,
2022-05-12 07:02:28 +00:00
}
2022-10-14 08:53:19 +00:00
struct SlicedBuffer {
2022-05-12 07:02:28 +00:00
buffer : wgpu ::Buffer ,
2022-10-14 08:53:19 +00:00
slices : Vec < Range < wgpu ::BufferAddress > > ,
capacity : wgpu ::BufferAddress ,
2022-05-12 07:02:28 +00:00
}
2022-09-07 12:20:21 +00:00
/// Renderer for a egui based GUI.
pub struct Renderer {
pipeline : wgpu ::RenderPipeline ,
2022-10-14 08:53:19 +00:00
index_buffer : SlicedBuffer ,
vertex_buffer : SlicedBuffer ,
uniform_buffer : wgpu ::Buffer ,
2022-05-12 07:02:28 +00:00
uniform_bind_group : wgpu ::BindGroup ,
texture_bind_group_layout : wgpu ::BindGroupLayout ,
2022-05-22 15:32:54 +00:00
/// Map of egui texture IDs to textures and their associated bindgroups (texture view +
/// sampler). The texture may be None if the TextureId is just a handle to a user-provided
/// sampler.
textures : HashMap < egui ::TextureId , ( Option < wgpu ::Texture > , wgpu ::BindGroup ) > ,
next_user_texture_id : u64 ,
2022-05-28 15:52:36 +00:00
/// Storage for use by [`egui::PaintCallback`]'s that need to store resources such as render
/// pipelines that must have the lifetime of the renderpass.
2022-08-03 07:26:16 +00:00
pub paint_callback_resources : TypeMap ,
2022-05-12 07:02:28 +00:00
}
2022-09-07 12:20:21 +00:00
impl Renderer {
/// Creates a renderer for a egui UI.
2022-05-12 07:02:28 +00:00
///
2022-10-12 12:27:24 +00:00
/// `output_color_format` should preferably be [`wgpu::TextureFormat::Rgba8Unorm`] or
2022-09-24 15:53:11 +00:00
/// [`wgpu::TextureFormat::Bgra8Unorm`], i.e. in gamma-space.
2022-05-12 07:02:28 +00:00
pub fn new (
device : & wgpu ::Device ,
2022-10-12 12:27:24 +00:00
output_color_format : wgpu ::TextureFormat ,
output_depth_format : Option < wgpu ::TextureFormat > ,
2022-05-12 07:02:28 +00:00
msaa_samples : u32 ,
) -> Self {
let shader = wgpu ::ShaderModuleDescriptor {
2022-10-05 18:14:18 +00:00
label : Some ( " egui " ) ,
2022-05-12 07:02:28 +00:00
source : wgpu ::ShaderSource ::Wgsl ( Cow ::Borrowed ( include_str! ( " egui.wgsl " ) ) ) ,
} ;
2022-07-03 13:43:39 +00:00
let module = device . create_shader_module ( shader ) ;
2022-05-12 07:02:28 +00:00
let uniform_buffer = device . create_buffer_init ( & wgpu ::util ::BufferInitDescriptor {
label : Some ( " egui_uniform_buffer " ) ,
contents : bytemuck ::cast_slice ( & [ UniformBuffer {
screen_size_in_points : [ 0.0 , 0.0 ] ,
2022-05-28 16:41:01 +00:00
_padding : Default ::default ( ) ,
2022-05-12 07:02:28 +00:00
} ] ) ,
usage : wgpu ::BufferUsages ::UNIFORM | wgpu ::BufferUsages ::COPY_DST ,
} ) ;
let uniform_bind_group_layout =
device . create_bind_group_layout ( & wgpu ::BindGroupLayoutDescriptor {
label : Some ( " egui_uniform_bind_group_layout " ) ,
entries : & [ wgpu ::BindGroupLayoutEntry {
binding : 0 ,
visibility : wgpu ::ShaderStages ::VERTEX ,
ty : wgpu ::BindingType ::Buffer {
has_dynamic_offset : false ,
2022-10-05 18:14:18 +00:00
min_binding_size : NonZeroU64 ::new ( std ::mem ::size_of ::< UniformBuffer > ( ) as _ ) ,
2022-05-12 07:02:28 +00:00
ty : wgpu ::BufferBindingType ::Uniform ,
} ,
count : None ,
} ] ,
} ) ;
let uniform_bind_group = device . create_bind_group ( & wgpu ::BindGroupDescriptor {
label : Some ( " egui_uniform_bind_group " ) ,
layout : & uniform_bind_group_layout ,
entries : & [ wgpu ::BindGroupEntry {
binding : 0 ,
resource : wgpu ::BindingResource ::Buffer ( wgpu ::BufferBinding {
2022-10-14 08:53:19 +00:00
buffer : & uniform_buffer ,
2022-05-12 07:02:28 +00:00
offset : 0 ,
size : None ,
} ) ,
} ] ,
} ) ;
let texture_bind_group_layout =
device . create_bind_group_layout ( & wgpu ::BindGroupLayoutDescriptor {
label : Some ( " egui_texture_bind_group_layout " ) ,
entries : & [
wgpu ::BindGroupLayoutEntry {
binding : 0 ,
visibility : wgpu ::ShaderStages ::FRAGMENT ,
ty : wgpu ::BindingType ::Texture {
multisampled : false ,
sample_type : wgpu ::TextureSampleType ::Float { filterable : true } ,
view_dimension : wgpu ::TextureViewDimension ::D2 ,
} ,
count : None ,
} ,
wgpu ::BindGroupLayoutEntry {
binding : 1 ,
visibility : wgpu ::ShaderStages ::FRAGMENT ,
ty : wgpu ::BindingType ::Sampler ( wgpu ::SamplerBindingType ::Filtering ) ,
count : None ,
} ,
] ,
} ) ;
let pipeline_layout = device . create_pipeline_layout ( & wgpu ::PipelineLayoutDescriptor {
label : Some ( " egui_pipeline_layout " ) ,
bind_group_layouts : & [ & uniform_bind_group_layout , & texture_bind_group_layout ] ,
push_constant_ranges : & [ ] ,
} ) ;
2022-10-12 12:27:24 +00:00
let depth_stencil = output_depth_format . map ( | format | wgpu ::DepthStencilState {
format ,
2022-09-06 12:37:07 +00:00
depth_write_enabled : false ,
depth_compare : wgpu ::CompareFunction ::Always ,
stencil : wgpu ::StencilState ::default ( ) ,
bias : wgpu ::DepthBiasState ::default ( ) ,
} ) ;
2022-09-07 12:20:21 +00:00
let pipeline = device . create_render_pipeline ( & wgpu ::RenderPipelineDescriptor {
2022-05-12 07:02:28 +00:00
label : Some ( " egui_pipeline " ) ,
layout : Some ( & pipeline_layout ) ,
vertex : wgpu ::VertexState {
2022-09-24 15:53:11 +00:00
entry_point : " vs_main " ,
2022-05-12 07:02:28 +00:00
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 ] ,
} ] ,
} ,
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 ,
} ,
2022-09-06 12:37:07 +00:00
depth_stencil ,
2022-05-12 07:02:28 +00:00
multisample : wgpu ::MultisampleState {
alpha_to_coverage_enabled : false ,
count : msaa_samples ,
mask : ! 0 ,
} ,
fragment : Some ( wgpu ::FragmentState {
module : & module ,
2022-10-12 12:27:24 +00:00
entry_point : if output_color_format . describe ( ) . srgb {
tracing ::warn! ( " Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm " , output_color_format ) ;
2022-09-24 15:53:11 +00:00
" fs_main_linear_framebuffer "
} else {
" fs_main_gamma_framebuffer " // this is what we prefer
} ,
2022-07-03 13:43:39 +00:00
targets : & [ Some ( wgpu ::ColorTargetState {
2022-10-12 12:27:24 +00:00
format : output_color_format ,
2022-05-12 07:02:28 +00:00
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 ,
2022-07-03 13:43:39 +00:00
} ) ] ,
2022-05-12 07:02:28 +00:00
} ) ,
multiview : None ,
} ) ;
2022-10-14 08:53:19 +00:00
const VERTEX_BUFFER_START_CAPACITY : wgpu ::BufferAddress =
( std ::mem ::size_of ::< Vertex > ( ) * 1024 ) as _ ;
const INDEX_BUFFER_START_CAPACITY : wgpu ::BufferAddress =
( std ::mem ::size_of ::< u32 > ( ) * 1024 * 3 ) as _ ;
2022-05-12 07:02:28 +00:00
Self {
2022-09-07 12:20:21 +00:00
pipeline ,
2022-10-14 08:53:19 +00:00
vertex_buffer : SlicedBuffer {
buffer : create_vertex_buffer ( device , VERTEX_BUFFER_START_CAPACITY ) ,
slices : Vec ::with_capacity ( 64 ) ,
capacity : VERTEX_BUFFER_START_CAPACITY ,
} ,
index_buffer : SlicedBuffer {
buffer : create_index_buffer ( device , INDEX_BUFFER_START_CAPACITY ) ,
slices : Vec ::with_capacity ( 64 ) ,
capacity : INDEX_BUFFER_START_CAPACITY ,
} ,
2022-05-12 07:02:28 +00:00
uniform_buffer ,
uniform_bind_group ,
texture_bind_group_layout ,
textures : HashMap ::new ( ) ,
2022-05-22 15:32:54 +00:00
next_user_texture_id : 0 ,
2022-05-28 15:52:36 +00:00
paint_callback_resources : TypeMap ::default ( ) ,
2022-05-12 07:02:28 +00:00
}
}
2022-09-07 12:20:21 +00:00
/// Executes the egui renderer onto an existing wgpu renderpass.
2022-10-12 12:27:24 +00:00
pub fn render < ' rp > (
2022-09-07 12:20:21 +00:00
& ' rp self ,
render_pass : & mut wgpu ::RenderPass < ' rp > ,
2022-05-12 07:02:28 +00:00
paint_jobs : & [ egui ::epaint ::ClippedPrimitive ] ,
screen_descriptor : & ScreenDescriptor ,
) {
let pixels_per_point = screen_descriptor . pixels_per_point ;
let size_in_pixels = screen_descriptor . size_in_pixels ;
2022-09-07 12:20:21 +00:00
// Whether or not we need to reset the render pass because a paint callback has just
2022-05-28 15:52:36 +00:00
// run.
let mut needs_reset = true ;
2022-10-14 08:53:19 +00:00
let mut index_buffer_slices = self . index_buffer . slices . iter ( ) ;
let mut vertex_buffer_slices = self . vertex_buffer . slices . iter ( ) ;
2022-06-02 20:45:54 +00:00
for egui ::ClippedPrimitive {
clip_rect ,
primitive ,
} in paint_jobs
2022-05-12 07:02:28 +00:00
{
2022-05-28 15:52:36 +00:00
if needs_reset {
2022-09-07 12:20:21 +00:00
render_pass . set_viewport (
2022-05-28 15:52:36 +00:00
0.0 ,
0.0 ,
size_in_pixels [ 0 ] as f32 ,
size_in_pixels [ 1 ] as f32 ,
0.0 ,
1.0 ,
) ;
2022-09-07 12:20:21 +00:00
render_pass . set_pipeline ( & self . pipeline ) ;
render_pass . set_bind_group ( 0 , & self . uniform_bind_group , & [ ] ) ;
2022-05-28 15:52:36 +00:00
needs_reset = false ;
}
2022-07-28 22:06:08 +00:00
{
let rect = ScissorRect ::new ( clip_rect , pixels_per_point , size_in_pixels ) ;
if rect . width = = 0 | | rect . height = = 0 {
2022-09-13 07:32:05 +00:00
// Skip rendering zero-sized clip areas.
2022-07-28 22:06:08 +00:00
if let Primitive ::Mesh ( _ ) = primitive {
2022-09-13 07:32:05 +00:00
// If this is a mesh, we need to advance the index and vertex buffer iterators:
2022-10-14 08:53:19 +00:00
index_buffer_slices . next ( ) . unwrap ( ) ;
vertex_buffer_slices . next ( ) . unwrap ( ) ;
2022-07-28 22:06:08 +00:00
}
continue ;
2022-06-09 13:59:43 +00:00
}
2022-05-12 07:02:28 +00:00
2022-09-07 12:20:21 +00:00
render_pass . set_scissor_rect ( rect . x , rect . y , rect . width , rect . height ) ;
2022-07-28 22:06:08 +00:00
}
2022-05-28 15:52:36 +00:00
2022-05-12 07:02:28 +00:00
match primitive {
Primitive ::Mesh ( mesh ) = > {
2022-10-14 08:53:19 +00:00
let index_buffer_slice = index_buffer_slices . next ( ) . unwrap ( ) ;
let vertex_buffer_slice = vertex_buffer_slices . next ( ) . unwrap ( ) ;
2022-06-02 20:45:54 +00:00
2022-06-09 13:59:43 +00:00
if let Some ( ( _texture , bind_group ) ) = self . textures . get ( & mesh . texture_id ) {
2022-09-07 12:20:21 +00:00
render_pass . set_bind_group ( 1 , bind_group , & [ ] ) ;
render_pass . set_index_buffer (
2022-10-14 08:53:19 +00:00
self . index_buffer . buffer . slice ( index_buffer_slice . clone ( ) ) ,
2022-05-12 07:02:28 +00:00
wgpu ::IndexFormat ::Uint32 ,
) ;
2022-10-14 08:53:19 +00:00
render_pass . set_vertex_buffer (
0 ,
self . vertex_buffer . buffer . slice ( vertex_buffer_slice . clone ( ) ) ,
) ;
2022-09-07 12:20:21 +00:00
render_pass . draw_indexed ( 0 .. mesh . indices . len ( ) as u32 , 0 , 0 .. 1 ) ;
2022-05-12 07:02:28 +00:00
} else {
tracing ::warn! ( " Missing texture: {:?} " , mesh . texture_id ) ;
}
}
2022-05-28 15:52:36 +00:00
Primitive ::Callback ( callback ) = > {
let cbfn = if let Some ( c ) = callback . callback . downcast_ref ::< CallbackFn > ( ) {
c
} else {
// We already warned in the `prepare` callback
continue ;
} ;
if callback . rect . is_positive ( ) {
needs_reset = true ;
2022-07-28 22:06:08 +00:00
{
// Set the viewport rect
// Transform callback rect to physical pixels:
let rect_min_x = pixels_per_point * callback . rect . min . x ;
let rect_min_y = pixels_per_point * callback . rect . min . y ;
let rect_max_x = pixels_per_point * callback . rect . max . x ;
let rect_max_y = pixels_per_point * callback . rect . max . y ;
let rect_min_x = rect_min_x . round ( ) ;
let rect_min_y = rect_min_y . round ( ) ;
let rect_max_x = rect_max_x . round ( ) ;
let rect_max_y = rect_max_y . round ( ) ;
2022-09-07 12:20:21 +00:00
render_pass . set_viewport (
2022-07-28 22:06:08 +00:00
rect_min_x ,
rect_min_y ,
rect_max_x - rect_min_x ,
rect_max_y - rect_min_y ,
0.0 ,
1.0 ,
) ;
2022-05-28 15:52:36 +00:00
}
( cbfn . paint ) (
PaintCallbackInfo {
viewport : callback . rect ,
clip_rect : * clip_rect ,
pixels_per_point ,
screen_size_px : size_in_pixels ,
} ,
2022-09-07 12:20:21 +00:00
render_pass ,
2022-05-28 15:52:36 +00:00
& self . paint_callback_resources ,
) ;
}
2022-05-12 07:02:28 +00:00
}
}
}
2022-07-26 23:09:19 +00:00
2022-09-07 12:20:21 +00:00
render_pass . set_scissor_rect ( 0 , 0 , size_in_pixels [ 0 ] , size_in_pixels [ 1 ] ) ;
2022-05-12 07:02:28 +00:00
}
2022-09-07 12:20:21 +00:00
/// Should be called before `render()`.
2022-05-12 07:02:28 +00:00
pub fn update_texture (
& mut self ,
device : & wgpu ::Device ,
queue : & wgpu ::Queue ,
id : egui ::TextureId ,
image_delta : & egui ::epaint ::ImageDelta ,
) {
let width = image_delta . image . width ( ) as u32 ;
let height = image_delta . image . height ( ) as u32 ;
let size = wgpu ::Extent3d {
width ,
height ,
depth_or_array_layers : 1 ,
} ;
let data_color32 = match & image_delta . image {
egui ::ImageData ::Color ( image ) = > {
assert_eq! (
width as usize * height as usize ,
image . pixels . len ( ) ,
" Mismatch between texture size and texel count "
) ;
Cow ::Borrowed ( & image . pixels )
}
egui ::ImageData ::Font ( image ) = > {
assert_eq! (
width as usize * height as usize ,
image . pixels . len ( ) ,
" Mismatch between texture size and texel count "
) ;
2022-09-24 15:53:11 +00:00
Cow ::Owned ( image . srgba_pixels ( None ) . collect ::< Vec < _ > > ( ) )
2022-05-12 07:02:28 +00:00
}
} ;
let data_bytes : & [ u8 ] = bytemuck ::cast_slice ( data_color32 . as_slice ( ) ) ;
let queue_write_data_to_texture = | texture , origin | {
queue . write_texture (
wgpu ::ImageCopyTexture {
texture ,
mip_level : 0 ,
origin ,
aspect : wgpu ::TextureAspect ::All ,
} ,
data_bytes ,
wgpu ::ImageDataLayout {
offset : 0 ,
bytes_per_row : NonZeroU32 ::new ( 4 * width ) ,
rows_per_image : NonZeroU32 ::new ( height ) ,
} ,
size ,
) ;
} ;
if let Some ( pos ) = image_delta . pos {
// update the existing texture
let ( texture , _bind_group ) = self
. textures
. get ( & id )
. expect ( " Tried to update a texture that has not been allocated yet. " ) ;
let origin = wgpu ::Origin3d {
x : pos [ 0 ] as u32 ,
y : pos [ 1 ] as u32 ,
z : 0 ,
} ;
2022-05-22 15:32:54 +00:00
queue_write_data_to_texture (
texture . as_ref ( ) . expect ( " Tried to update user texture. " ) ,
origin ,
) ;
2022-05-12 07:02:28 +00:00
} else {
2022-10-05 18:14:18 +00:00
// TODO(Wumpf): Create only a new texture if we need to
2022-05-12 07:02:28 +00:00
// allocate a new texture
2022-10-05 18:14:18 +00:00
// Use same label for all resources associated with this texture id (no point in retyping the type)
let label_str = format! ( " egui_texid_ {:?} " , id ) ;
let label = Some ( label_str . as_str ( ) ) ;
2022-05-12 07:02:28 +00:00
let texture = device . create_texture ( & wgpu ::TextureDescriptor {
2022-10-05 18:14:18 +00:00
label ,
2022-05-12 07:02:28 +00:00
size ,
mip_level_count : 1 ,
sample_count : 1 ,
dimension : wgpu ::TextureDimension ::D2 ,
2022-09-24 15:53:11 +00:00
format : wgpu ::TextureFormat ::Rgba8UnormSrgb , // TODO(emilk): handle WebGL1 where this is not always supported!
2022-05-12 07:02:28 +00:00
usage : wgpu ::TextureUsages ::TEXTURE_BINDING | wgpu ::TextureUsages ::COPY_DST ,
} ) ;
2022-05-22 15:40:56 +00:00
let filter = match image_delta . filter {
egui ::TextureFilter ::Nearest = > wgpu ::FilterMode ::Nearest ,
egui ::TextureFilter ::Linear = > wgpu ::FilterMode ::Linear ,
} ;
2022-10-05 18:14:18 +00:00
// TODO(Wumpf): Reuse this sampler.
2022-05-12 07:02:28 +00:00
let sampler = device . create_sampler ( & wgpu ::SamplerDescriptor {
2022-10-05 18:14:18 +00:00
label ,
2022-05-22 15:40:56 +00:00
mag_filter : filter ,
min_filter : filter ,
2022-05-12 07:02:28 +00:00
.. Default ::default ( )
} ) ;
let bind_group = device . create_bind_group ( & wgpu ::BindGroupDescriptor {
2022-10-05 18:14:18 +00:00
label ,
2022-05-12 07:02:28 +00:00
layout : & self . texture_bind_group_layout ,
entries : & [
wgpu ::BindGroupEntry {
binding : 0 ,
resource : wgpu ::BindingResource ::TextureView (
& texture . create_view ( & wgpu ::TextureViewDescriptor ::default ( ) ) ,
) ,
} ,
wgpu ::BindGroupEntry {
binding : 1 ,
resource : wgpu ::BindingResource ::Sampler ( & sampler ) ,
} ,
] ,
} ) ;
let origin = wgpu ::Origin3d ::ZERO ;
queue_write_data_to_texture ( & texture , origin ) ;
2022-05-22 15:32:54 +00:00
self . textures . insert ( id , ( Some ( texture ) , bind_group ) ) ;
2022-05-12 07:02:28 +00:00
} ;
}
pub fn free_texture ( & mut self , id : & egui ::TextureId ) {
self . textures . remove ( id ) ;
}
2022-06-02 20:45:54 +00:00
/// Get the WGPU texture and bind group associated to a texture that has been allocated by egui.
///
/// This could be used by custom paint hooks to render images that have been added through with
/// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html)
/// or [`egui::Context::load_texture`].
2022-08-08 10:21:53 +00:00
pub fn texture (
2022-06-02 20:45:54 +00:00
& self ,
id : & egui ::TextureId ,
) -> Option < & ( Option < wgpu ::Texture > , wgpu ::BindGroup ) > {
self . textures . get ( id )
}
2022-05-22 15:32:54 +00:00
/// Registers a `wgpu::Texture` with a `egui::TextureId`.
///
/// This enables the application to reference the texture inside an image ui element.
/// This effectively enables off-screen rendering inside the egui UI. Texture must have
/// the texture format `TextureFormat::Rgba8UnormSrgb` and
/// Texture usage `TextureUsage::SAMPLED`.
pub fn register_native_texture (
& mut self ,
device : & wgpu ::Device ,
texture : & wgpu ::TextureView ,
texture_filter : wgpu ::FilterMode ,
) -> egui ::TextureId {
self . register_native_texture_with_sampler_options (
device ,
texture ,
wgpu ::SamplerDescriptor {
2022-10-05 18:14:18 +00:00
label : Some ( format! ( " egui_user_image_ {} " , self . next_user_texture_id ) . as_str ( ) ) ,
2022-05-22 15:32:54 +00:00
mag_filter : texture_filter ,
min_filter : texture_filter ,
.. Default ::default ( )
} ,
)
}
2022-08-29 12:09:34 +00:00
/// Registers a `wgpu::Texture` with an existing `egui::TextureId`.
///
/// This enables applications to reuse `TextureId`s.
pub fn update_egui_texture_from_wgpu_texture (
& mut self ,
device : & wgpu ::Device ,
texture : & wgpu ::TextureView ,
texture_filter : wgpu ::FilterMode ,
id : egui ::TextureId ,
) {
self . update_egui_texture_from_wgpu_texture_with_sampler_options (
device ,
texture ,
wgpu ::SamplerDescriptor {
2022-10-05 18:14:18 +00:00
label : Some ( format! ( " egui_user_image_ {} " , self . next_user_texture_id ) . as_str ( ) ) ,
2022-08-29 12:09:34 +00:00
mag_filter : texture_filter ,
min_filter : texture_filter ,
.. Default ::default ( )
} ,
id ,
) ;
}
2022-05-22 15:32:54 +00:00
/// Registers a `wgpu::Texture` with a `egui::TextureId` while also accepting custom
/// `wgpu::SamplerDescriptor` options.
///
/// This allows applications to specify individual minification/magnification filters as well as
/// custom mipmap and tiling options.
///
/// The `Texture` must have the format `TextureFormat::Rgba8UnormSrgb` and usage
/// `TextureUsage::SAMPLED`. Any compare function supplied in the `SamplerDescriptor` will be
/// ignored.
2022-05-22 15:40:56 +00:00
#[ allow(clippy::needless_pass_by_value) ] // false positive
2022-05-22 15:32:54 +00:00
pub fn register_native_texture_with_sampler_options (
& mut self ,
device : & wgpu ::Device ,
texture : & wgpu ::TextureView ,
2022-05-22 15:40:56 +00:00
sampler_descriptor : wgpu ::SamplerDescriptor < '_ > ,
2022-05-22 15:32:54 +00:00
) -> egui ::TextureId {
let sampler = device . create_sampler ( & wgpu ::SamplerDescriptor {
compare : None ,
.. sampler_descriptor
} ) ;
let bind_group = device . create_bind_group ( & wgpu ::BindGroupDescriptor {
2022-10-05 18:14:18 +00:00
label : Some ( format! ( " egui_user_image_ {} " , self . next_user_texture_id ) . as_str ( ) ) ,
2022-05-22 15:32:54 +00:00
layout : & self . texture_bind_group_layout ,
entries : & [
wgpu ::BindGroupEntry {
binding : 0 ,
resource : wgpu ::BindingResource ::TextureView ( texture ) ,
} ,
wgpu ::BindGroupEntry {
binding : 1 ,
resource : wgpu ::BindingResource ::Sampler ( & sampler ) ,
} ,
] ,
} ) ;
let id = egui ::TextureId ::User ( self . next_user_texture_id ) ;
self . textures . insert ( id , ( None , bind_group ) ) ;
self . next_user_texture_id + = 1 ;
id
}
2022-08-29 12:09:34 +00:00
/// Registers a `wgpu::Texture` with an existing `egui::TextureId` while also accepting custom
/// `wgpu::SamplerDescriptor` options.
///
/// This allows applications to reuse `TextureId`s created with custom sampler options.
#[ allow(clippy::needless_pass_by_value) ] // false positive
pub fn update_egui_texture_from_wgpu_texture_with_sampler_options (
& mut self ,
device : & wgpu ::Device ,
texture : & wgpu ::TextureView ,
sampler_descriptor : wgpu ::SamplerDescriptor < '_ > ,
id : egui ::TextureId ,
) {
let ( _user_texture , user_texture_binding ) = self
. textures
. get_mut ( & id )
. expect ( " Tried to update a texture that has not been allocated yet. " ) ;
let sampler = device . create_sampler ( & wgpu ::SamplerDescriptor {
compare : None ,
.. sampler_descriptor
} ) ;
let bind_group = device . create_bind_group ( & wgpu ::BindGroupDescriptor {
2022-10-05 18:14:18 +00:00
label : Some ( format! ( " egui_user_image_ {} " , self . next_user_texture_id ) . as_str ( ) ) ,
2022-08-29 12:09:34 +00:00
layout : & self . texture_bind_group_layout ,
entries : & [
wgpu ::BindGroupEntry {
binding : 0 ,
resource : wgpu ::BindingResource ::TextureView ( texture ) ,
} ,
wgpu ::BindGroupEntry {
binding : 1 ,
resource : wgpu ::BindingResource ::Sampler ( & sampler ) ,
} ,
] ,
} ) ;
* user_texture_binding = bind_group ;
}
2022-09-07 12:20:21 +00:00
/// Uploads the uniform, vertex and index data used by the renderer.
/// Should be called before `render()`.
2022-05-12 07:02:28 +00:00
pub fn update_buffers (
& mut self ,
device : & wgpu ::Device ,
queue : & wgpu ::Queue ,
2022-10-12 12:27:24 +00:00
encoder : & mut wgpu ::CommandEncoder ,
2022-05-12 07:02:28 +00:00
paint_jobs : & [ egui ::epaint ::ClippedPrimitive ] ,
screen_descriptor : & ScreenDescriptor ,
) {
let screen_size_in_points = screen_descriptor . screen_size_in_points ( ) ;
2022-10-14 08:53:19 +00:00
// Update uniform buffer
queue . write_buffer (
& self . uniform_buffer ,
2022-05-12 07:02:28 +00:00
0 ,
bytemuck ::cast_slice ( & [ UniformBuffer {
screen_size_in_points ,
2022-05-28 16:41:01 +00:00
_padding : Default ::default ( ) ,
2022-05-12 07:02:28 +00:00
} ] ) ,
) ;
2022-10-14 08:53:19 +00:00
// Determine how many vertices & indices need to be rendered.
let ( vertex_count , index_count ) =
paint_jobs . iter ( ) . fold ( ( 0 , 0 ) , | acc , clipped_primitive | {
match & clipped_primitive . primitive {
Primitive ::Mesh ( mesh ) = > {
( acc . 0 + mesh . vertices . len ( ) , acc . 1 + mesh . indices . len ( ) )
}
Primitive ::Callback ( _ ) = > acc ,
}
} ) ;
// Resize index buffer if needed.
{
self . index_buffer . slices . clear ( ) ;
let required_size = ( std ::mem ::size_of ::< u32 > ( ) * index_count ) as u64 ;
if self . index_buffer . capacity < required_size {
self . index_buffer . capacity =
( self . index_buffer . capacity * 2 ) . at_least ( required_size ) ;
self . index_buffer . buffer = create_index_buffer ( device , self . index_buffer . capacity ) ;
}
}
// Resize vertex buffer if needed.
{
self . vertex_buffer . slices . clear ( ) ;
let required_size = ( std ::mem ::size_of ::< Vertex > ( ) * vertex_count ) as u64 ;
if self . vertex_buffer . capacity < required_size {
self . vertex_buffer . capacity =
( self . vertex_buffer . capacity * 2 ) . at_least ( required_size ) ;
self . vertex_buffer . buffer =
create_vertex_buffer ( device , self . vertex_buffer . capacity ) ;
}
}
// Upload index & vertex data and call user callbacks
2022-06-02 20:45:54 +00:00
for egui ::ClippedPrimitive { primitive , .. } in paint_jobs . iter ( ) {
2022-05-12 07:02:28 +00:00
match primitive {
Primitive ::Mesh ( mesh ) = > {
2022-10-14 08:53:19 +00:00
{
let index_offset = self . index_buffer . slices . last ( ) . unwrap_or ( & ( 0 .. 0 ) ) . end ;
let data = bytemuck ::cast_slice ( & mesh . indices ) ;
queue . write_buffer ( & self . index_buffer . buffer , index_offset , data ) ;
self . index_buffer
. slices
. push ( index_offset .. ( data . len ( ) as wgpu ::BufferAddress + index_offset ) ) ;
2022-05-12 07:02:28 +00:00
}
2022-10-14 08:53:19 +00:00
{
let vertex_offset = self . vertex_buffer . slices . last ( ) . unwrap_or ( & ( 0 .. 0 ) ) . end ;
let data = bytemuck ::cast_slice ( & mesh . vertices ) ;
queue . write_buffer ( & self . vertex_buffer . buffer , vertex_offset , data ) ;
self . vertex_buffer . slices . push (
vertex_offset .. ( data . len ( ) as wgpu ::BufferAddress + vertex_offset ) ,
) ;
2022-05-12 07:02:28 +00:00
}
}
2022-05-28 15:52:36 +00:00
Primitive ::Callback ( callback ) = > {
let cbfn = if let Some ( c ) = callback . callback . downcast_ref ::< CallbackFn > ( ) {
c
} else {
2022-09-25 14:43:01 +00:00
tracing ::warn! ( " Unknown paint callback: expected `egui_wgpu::CallbackFn` " ) ;
2022-05-28 15:52:36 +00:00
continue ;
} ;
2022-10-12 12:27:24 +00:00
( cbfn . prepare ) ( device , queue , encoder , & mut self . paint_callback_resources ) ;
2022-05-12 07:02:28 +00:00
}
}
}
}
2022-10-14 08:53:19 +00:00
}
2022-05-12 07:02:28 +00:00
2022-10-14 08:53:19 +00:00
fn create_vertex_buffer ( device : & wgpu ::Device , size : u64 ) -> wgpu ::Buffer {
device . create_buffer ( & wgpu ::BufferDescriptor {
label : Some ( " egui_vertex_buffer " ) ,
usage : wgpu ::BufferUsages ::VERTEX | wgpu ::BufferUsages ::COPY_DST ,
size ,
mapped_at_creation : false ,
} )
}
2022-05-12 07:02:28 +00:00
2022-10-14 08:53:19 +00:00
fn create_index_buffer ( device : & wgpu ::Device , size : u64 ) -> wgpu ::Buffer {
device . create_buffer ( & wgpu ::BufferDescriptor {
label : Some ( " egui_index_buffer " ) ,
usage : wgpu ::BufferUsages ::INDEX | wgpu ::BufferUsages ::COPY_DST ,
size ,
mapped_at_creation : false ,
} )
2022-05-12 07:02:28 +00:00
}
2022-05-28 15:52:36 +00:00
2022-07-28 22:06:08 +00:00
/// A Rect in physical pixel space, used for setting cliipping rectangles.
struct ScissorRect {
2022-05-28 15:52:36 +00:00
x : u32 ,
y : u32 ,
width : u32 ,
height : u32 ,
}
2022-07-28 22:06:08 +00:00
impl ScissorRect {
fn new ( clip_rect : & egui ::Rect , pixels_per_point : f32 , target_size : [ u32 ; 2 ] ) -> Self {
// Transform clip rect to physical pixels:
let clip_min_x = pixels_per_point * clip_rect . min . x ;
let clip_min_y = pixels_per_point * clip_rect . min . y ;
let clip_max_x = pixels_per_point * clip_rect . max . x ;
let clip_max_y = pixels_per_point * clip_rect . max . y ;
// Round to integer:
let clip_min_x = clip_min_x . round ( ) as u32 ;
let clip_min_y = clip_min_y . round ( ) as u32 ;
let clip_max_x = clip_max_x . round ( ) as u32 ;
let clip_max_y = clip_max_y . round ( ) as u32 ;
// Clamp:
let clip_min_x = clip_min_x . clamp ( 0 , target_size [ 0 ] ) ;
let clip_min_y = clip_min_y . clamp ( 0 , target_size [ 1 ] ) ;
let clip_max_x = clip_max_x . clamp ( clip_min_x , target_size [ 0 ] ) ;
let clip_max_y = clip_max_y . clamp ( clip_min_y , target_size [ 1 ] ) ;
2022-09-13 07:32:05 +00:00
Self {
2022-07-28 22:06:08 +00:00
x : clip_min_x ,
y : clip_min_y ,
2022-09-13 07:32:05 +00:00
width : clip_max_x - clip_min_x ,
height : clip_max_y - clip_min_y ,
2022-07-28 22:06:08 +00:00
}
2022-05-28 15:52:36 +00:00
}
}
2022-08-03 07:26:16 +00:00
#[ test ]
2022-09-07 12:20:21 +00:00
fn renderer_impl_send_sync ( ) {
2022-08-03 07:26:16 +00:00
fn assert_send_sync < T : Send + Sync > ( ) { }
2022-09-07 12:20:21 +00:00
assert_send_sync ::< Renderer > ( ) ;
2022-08-03 07:26:16 +00:00
}