1use core::{num::NonZeroU64, ops::Range};
2use std::borrow::Cow;
3
4use ahash::HashMap;
5use bytemuck::Zeroable as _;
6use epaint::{PaintCallbackInfo, Primitive, Vertex, emath::NumExt as _};
7
8use wgpu::util::DeviceExt as _;
9
10#[cfg(not(all(
12 target_arch = "wasm32",
13 not(feature = "fragile-send-sync-non-atomic-wasm"),
14)))]
15pub type CallbackResources = type_map::concurrent::TypeMap;
17#[cfg(all(
18 target_arch = "wasm32",
19 not(feature = "fragile-send-sync-non-atomic-wasm"),
20))]
21pub type CallbackResources = type_map::TypeMap;
23
24pub struct Callback(Box<dyn CallbackTrait>);
30
31impl Callback {
32 pub fn new_paint_callback(
34 rect: epaint::emath::Rect,
35 callback: impl CallbackTrait + 'static,
36 ) -> epaint::PaintCallback {
37 epaint::PaintCallback {
38 rect,
39 callback: std::sync::Arc::new(Self(Box::new(callback))),
40 }
41 }
42}
43
44pub trait CallbackTrait: Send + Sync {
89 fn prepare(
90 &self,
91 _device: &wgpu::Device,
92 _queue: &wgpu::Queue,
93 _screen_descriptor: &ScreenDescriptor,
94 _egui_encoder: &mut wgpu::CommandEncoder,
95 _callback_resources: &mut CallbackResources,
96 ) -> Vec<wgpu::CommandBuffer> {
97 Vec::new()
98 }
99
100 fn finish_prepare(
102 &self,
103 _device: &wgpu::Device,
104 _queue: &wgpu::Queue,
105 _egui_encoder: &mut wgpu::CommandEncoder,
106 _callback_resources: &mut CallbackResources,
107 ) -> Vec<wgpu::CommandBuffer> {
108 Vec::new()
109 }
110
111 fn paint(
116 &self,
117 info: PaintCallbackInfo,
118 render_pass: &mut wgpu::RenderPass<'static>,
119 callback_resources: &CallbackResources,
120 );
121}
122
123pub struct ScreenDescriptor {
125 pub size_in_pixels: [u32; 2],
127
128 pub pixels_per_point: f32,
130}
131
132impl ScreenDescriptor {
133 fn screen_size_in_points(&self) -> [f32; 2] {
135 [
136 self.size_in_pixels[0] as f32 / self.pixels_per_point,
137 self.size_in_pixels[1] as f32 / self.pixels_per_point,
138 ]
139 }
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
144#[repr(C)]
145struct UniformBuffer {
146 screen_size_in_points: [f32; 2],
147 dithering: u32,
148
149 predictable_texture_filtering: u32,
153}
154
155struct SlicedBuffer {
156 buffer: wgpu::Buffer,
157 slices: Vec<Range<usize>>,
158 capacity: wgpu::BufferAddress,
159}
160
161pub struct Texture {
162 pub texture: Option<wgpu::Texture>,
164
165 pub bind_group: wgpu::BindGroup,
167
168 pub options: Option<epaint::textures::TextureOptions>,
171}
172
173#[derive(Clone, Copy, Debug)]
175pub struct RendererOptions {
176 pub msaa_samples: u32,
186
187 pub depth_stencil_format: Option<wgpu::TextureFormat>,
192
193 pub dithering: bool,
201
202 pub predictable_texture_filtering: bool,
211}
212
213impl RendererOptions {
214 pub const PREDICTABLE: Self = Self {
218 msaa_samples: 1,
219 depth_stencil_format: None,
220 dithering: false,
221 predictable_texture_filtering: true,
222 };
223}
224
225impl Default for RendererOptions {
226 fn default() -> Self {
227 Self {
228 msaa_samples: 0,
229 depth_stencil_format: None,
230 dithering: true,
231 predictable_texture_filtering: false,
232 }
233 }
234}
235
236pub struct Renderer {
238 pipeline: wgpu::RenderPipeline,
239
240 index_buffer: SlicedBuffer,
241 vertex_buffer: SlicedBuffer,
242
243 uniform_buffer: wgpu::Buffer,
244 previous_uniform_buffer_content: UniformBuffer,
245 uniform_bind_group: wgpu::BindGroup,
246 texture_bind_group_layout: wgpu::BindGroupLayout,
247
248 texture_flag_buffers: [wgpu::Buffer; NUM_TEXTURE_FLAGS],
252
253 textures: HashMap<epaint::TextureId, Texture>,
257 next_user_texture_id: u64,
258 samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>,
259
260 options: RendererOptions,
261
262 pub callback_resources: CallbackResources,
266}
267
268impl Renderer {
269 pub fn new(
274 device: &wgpu::Device,
275 output_color_format: wgpu::TextureFormat,
276 options: RendererOptions,
277 ) -> Self {
278 profiling::function_scope!();
279
280 let shader = wgpu::ShaderModuleDescriptor {
281 label: Some("egui"),
282 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))),
283 };
284 let module = {
285 profiling::scope!("create_shader_module");
286 device.create_shader_module(shader)
287 };
288
289 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
290 label: Some("egui_uniform_buffer"),
291 contents: bytemuck::cast_slice(&[UniformBuffer {
292 screen_size_in_points: [0.0, 0.0],
293 dithering: u32::from(options.dithering),
294 predictable_texture_filtering: u32::from(options.predictable_texture_filtering),
295 }]),
296 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
297 });
298
299 let uniform_bind_group_layout = {
300 profiling::scope!("create_bind_group_layout");
301 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
302 label: Some("egui_uniform_bind_group_layout"),
303 entries: &[wgpu::BindGroupLayoutEntry {
304 binding: 0,
305 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
306 ty: wgpu::BindingType::Buffer {
307 has_dynamic_offset: false,
308 min_binding_size: NonZeroU64::new(
309 core::mem::size_of::<UniformBuffer>() as _
310 ),
311 ty: wgpu::BufferBindingType::Uniform,
312 },
313 count: None,
314 }],
315 })
316 };
317
318 let uniform_bind_group = {
319 profiling::scope!("create_bind_group");
320 device.create_bind_group(&wgpu::BindGroupDescriptor {
321 label: Some("egui_uniform_bind_group"),
322 layout: &uniform_bind_group_layout,
323 entries: &[wgpu::BindGroupEntry {
324 binding: 0,
325 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
326 buffer: &uniform_buffer,
327 offset: 0,
328 size: None,
329 }),
330 }],
331 })
332 };
333
334 let texture_bind_group_layout = {
335 profiling::scope!("create_bind_group_layout");
336 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
337 label: Some("egui_texture_bind_group_layout"),
338 entries: &[
339 wgpu::BindGroupLayoutEntry {
340 binding: 0,
341 visibility: wgpu::ShaderStages::FRAGMENT,
342 ty: wgpu::BindingType::Texture {
343 multisampled: false,
344 sample_type: wgpu::TextureSampleType::Float { filterable: true },
345 view_dimension: wgpu::TextureViewDimension::D2,
346 },
347 count: None,
348 },
349 wgpu::BindGroupLayoutEntry {
350 binding: 1,
351 visibility: wgpu::ShaderStages::FRAGMENT,
352 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
353 count: None,
354 },
355 wgpu::BindGroupLayoutEntry {
356 binding: 2,
357 visibility: wgpu::ShaderStages::FRAGMENT,
358 ty: wgpu::BindingType::Buffer {
359 has_dynamic_offset: false,
360 min_binding_size: NonZeroU64::new(
361 (core::mem::size_of::<u32>() * 4) as _,
362 ),
363 ty: wgpu::BufferBindingType::Uniform,
364 },
365 count: None,
366 },
367 ],
368 })
369 };
370
371 let texture_flag_buffers = core::array::from_fn::<_, NUM_TEXTURE_FLAGS, _>(|flag| {
372 let flag = flag as u32;
373 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
374 label: Some(&format!("egui_texture_flags_{flag}")),
375 contents: bytemuck::bytes_of(&[flag, 0, 0, 0]),
376 usage: wgpu::BufferUsages::UNIFORM,
377 })
378 });
379
380 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
381 label: Some("egui_pipeline_layout"),
382 bind_group_layouts: &[
383 Some(&uniform_bind_group_layout),
384 Some(&texture_bind_group_layout),
385 ],
386 immediate_size: 0,
387 });
388
389 let depth_stencil = options
390 .depth_stencil_format
391 .map(|format| wgpu::DepthStencilState {
392 format,
393 depth_write_enabled: Some(false),
394 depth_compare: Some(wgpu::CompareFunction::Always),
395 stencil: wgpu::StencilState::default(),
396 bias: wgpu::DepthBiasState::default(),
397 });
398
399 let pipeline = {
400 profiling::scope!("create_render_pipeline");
401 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
402 label: Some("egui_pipeline"),
403 layout: Some(&pipeline_layout),
404 vertex: wgpu::VertexState {
405 entry_point: Some("vs_main"),
406 module: &module,
407 buffers: &[Some(wgpu::VertexBufferLayout {
408 array_stride: 5 * 4,
409 step_mode: wgpu::VertexStepMode::Vertex,
410 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32],
414 })],
415 compilation_options: wgpu::PipelineCompilationOptions::default()
416 },
417 primitive: wgpu::PrimitiveState {
418 topology: wgpu::PrimitiveTopology::TriangleList,
419 unclipped_depth: false,
420 conservative: false,
421 cull_mode: None,
422 front_face: wgpu::FrontFace::default(),
423 polygon_mode: wgpu::PolygonMode::default(),
424 strip_index_format: None,
425 },
426 depth_stencil,
427 multisample: wgpu::MultisampleState {
428 alpha_to_coverage_enabled: false,
429 count: options.msaa_samples.max(1),
430 mask: !0,
431 },
432
433 fragment: Some(wgpu::FragmentState {
434 module: &module,
435 entry_point: Some(if output_color_format.is_srgb() {
436 log::warn!("Detected a linear (sRGBA aware) framebuffer {output_color_format:?}. egui prefers Rgba8Unorm or Bgra8Unorm");
437 "fs_main_linear_framebuffer"
438 } else {
439 "fs_main_gamma_framebuffer" }),
441 targets: &[Some(wgpu::ColorTargetState {
442 format: output_color_format,
443 blend: Some(wgpu::BlendState {
444 color: wgpu::BlendComponent {
445 src_factor: wgpu::BlendFactor::One,
446 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
447 operation: wgpu::BlendOperation::Add,
448 },
449 alpha: wgpu::BlendComponent {
450 src_factor: wgpu::BlendFactor::OneMinusDstAlpha,
451 dst_factor: wgpu::BlendFactor::One,
452 operation: wgpu::BlendOperation::Add,
453 },
454 }),
455 write_mask: wgpu::ColorWrites::ALL,
456 })],
457 compilation_options: wgpu::PipelineCompilationOptions::default()
458 }),
459 multiview_mask: None,
460 cache: None,
461 }
462 )
463 };
464
465 const VERTEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
466 (core::mem::size_of::<Vertex>() * 1024) as _;
467 const INDEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
468 (core::mem::size_of::<u32>() * 1024 * 3) as _;
469
470 Self {
471 pipeline,
472 vertex_buffer: SlicedBuffer {
473 buffer: create_vertex_buffer(device, VERTEX_BUFFER_START_CAPACITY),
474 slices: Vec::with_capacity(64),
475 capacity: VERTEX_BUFFER_START_CAPACITY,
476 },
477 index_buffer: SlicedBuffer {
478 buffer: create_index_buffer(device, INDEX_BUFFER_START_CAPACITY),
479 slices: Vec::with_capacity(64),
480 capacity: INDEX_BUFFER_START_CAPACITY,
481 },
482 uniform_buffer,
483 previous_uniform_buffer_content: UniformBuffer::zeroed(),
485 uniform_bind_group,
486 texture_bind_group_layout,
487 texture_flag_buffers,
488 textures: HashMap::default(),
489 next_user_texture_id: 0,
490 samplers: HashMap::default(),
491 options,
492 callback_resources: CallbackResources::default(),
493 }
494 }
495
496 pub fn render(
507 &self,
508 render_pass: &mut wgpu::RenderPass<'static>,
509 paint_jobs: &[epaint::ClippedPrimitive],
510 screen_descriptor: &ScreenDescriptor,
511 ) {
512 profiling::function_scope!();
513
514 let pixels_per_point = screen_descriptor.pixels_per_point;
515 let size_in_pixels = screen_descriptor.size_in_pixels;
516
517 let mut needs_reset = true;
520
521 let mut index_buffer_slices = self.index_buffer.slices.iter();
522 let mut vertex_buffer_slices = self.vertex_buffer.slices.iter();
523
524 for epaint::ClippedPrimitive {
525 clip_rect,
526 primitive,
527 } in paint_jobs
528 {
529 if needs_reset {
530 render_pass.set_viewport(
531 0.0,
532 0.0,
533 size_in_pixels[0] as f32,
534 size_in_pixels[1] as f32,
535 0.0,
536 1.0,
537 );
538 render_pass.set_pipeline(&self.pipeline);
539 render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
540 needs_reset = false;
541 }
542
543 {
544 let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels);
545
546 if rect.width == 0 || rect.height == 0 {
547 if let Primitive::Mesh(_) = primitive {
549 index_buffer_slices
551 .next()
552 .expect("You must call .update_buffers() before .render()");
553 vertex_buffer_slices
554 .next()
555 .expect("You must call .update_buffers() before .render()");
556 }
557 continue;
558 }
559
560 render_pass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height);
561 }
562
563 match primitive {
564 Primitive::Mesh(mesh) => {
565 let index_buffer_slice = index_buffer_slices
566 .next()
567 .expect("You must call .update_buffers() before .render()");
568 let vertex_buffer_slice = vertex_buffer_slices
569 .next()
570 .expect("You must call .update_buffers() before .render()");
571
572 if let Some(Texture { bind_group, .. }) = self.textures.get(&mesh.texture_id) {
573 render_pass.set_bind_group(1, bind_group, &[]);
574 render_pass.set_index_buffer(
575 self.index_buffer.buffer.slice(
576 index_buffer_slice.start as u64..index_buffer_slice.end as u64,
577 ),
578 wgpu::IndexFormat::Uint32,
579 );
580 render_pass.set_vertex_buffer(
581 0,
582 self.vertex_buffer.buffer.slice(
583 vertex_buffer_slice.start as u64..vertex_buffer_slice.end as u64,
584 ),
585 );
586 render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
587 } else {
588 log::warn!("Missing texture: {:?}", mesh.texture_id);
589 }
590 }
591 Primitive::Callback(callback) => {
592 let Some(cbfn) = callback.callback.downcast_ref::<Callback>() else {
593 continue;
595 };
596
597 let info = PaintCallbackInfo {
598 viewport: callback.rect,
599 clip_rect: *clip_rect,
600 pixels_per_point,
601 screen_size_px: size_in_pixels,
602 };
603
604 let viewport_px = info.viewport_in_pixels();
605 if viewport_px.width_px > 0 && viewport_px.height_px > 0 {
606 profiling::scope!("callback");
607
608 needs_reset = true;
609
610 render_pass.set_viewport(
619 viewport_px.left_px as f32,
620 viewport_px.top_px as f32,
621 viewport_px.width_px as f32,
622 viewport_px.height_px as f32,
623 0.0,
624 1.0,
625 );
626
627 cbfn.0.paint(info, render_pass, &self.callback_resources);
628 }
629 }
630 }
631 }
632
633 render_pass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);
634 }
635
636 pub fn update_texture(
638 &mut self,
639 device: &wgpu::Device,
640 queue: &wgpu::Queue,
641 id: epaint::TextureId,
642 image_delta: &epaint::ImageDelta,
643 ) {
644 profiling::function_scope!();
645
646 let width = image_delta.image.width() as u32;
647 let height = image_delta.image.height() as u32;
648
649 let size = wgpu::Extent3d {
650 width,
651 height,
652 depth_or_array_layers: 1,
653 };
654
655 let data_color32 = match &image_delta.image {
656 epaint::ImageData::Color(image) => {
657 assert_eq!(
658 width as usize * height as usize,
659 image.pixels.len(),
660 "Mismatch between texture size and texel count"
661 );
662 Cow::Borrowed(&image.pixels)
663 }
664 };
665 let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
666
667 let queue_write_data_to_texture = |texture, origin| {
668 profiling::scope!("write_texture");
669 queue.write_texture(
670 wgpu::TexelCopyTextureInfo {
671 texture,
672 mip_level: 0,
673 origin,
674 aspect: wgpu::TextureAspect::All,
675 },
676 data_bytes,
677 wgpu::TexelCopyBufferLayout {
678 offset: 0,
679 bytes_per_row: Some(4 * width),
680 rows_per_image: Some(height),
681 },
682 size,
683 );
684 };
685
686 let label_str = format!("egui_texid_{id:?}");
688 let label = Some(label_str.as_str());
689
690 let (texture, origin, bind_group) = if let Some(pos) = image_delta.pos {
691 let Texture {
693 texture,
694 bind_group,
695 options,
696 } = self
697 .textures
698 .remove(&id)
699 .expect("Tried to update a texture that has not been allocated yet.");
700 let texture = texture.expect("Tried to update user texture.");
701 let options = options.expect("Tried to update user texture.");
702 let origin = wgpu::Origin3d {
703 x: pos[0] as u32,
704 y: pos[1] as u32,
705 z: 0,
706 };
707
708 (
709 texture,
710 origin,
711 if image_delta.options == options {
714 Some(bind_group)
715 } else {
716 None
717 },
718 )
719 } else {
720 let texture = {
722 profiling::scope!("create_texture");
723 device.create_texture(&wgpu::TextureDescriptor {
724 label,
725 size,
726 mip_level_count: 1,
727 sample_count: 1,
728 dimension: wgpu::TextureDimension::D2,
729 format: wgpu::TextureFormat::Rgba8Unorm,
730 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
731 view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
732 })
733 };
734 let origin = wgpu::Origin3d::ZERO;
735 (texture, origin, None)
736 };
737
738 let bind_group = bind_group.unwrap_or_else(|| {
739 let nearest =
740 image_delta.options.magnification == epaint::textures::TextureFilter::Nearest;
741 let wrap_mode = wrap_mode_flag(image_delta.options.wrap_mode);
742 let sampler = self
743 .samplers
744 .entry(image_delta.options)
745 .or_insert_with(|| create_sampler(image_delta.options, device));
746 device.create_bind_group(&wgpu::BindGroupDescriptor {
747 label,
748 layout: &self.texture_bind_group_layout,
749 entries: &[
750 wgpu::BindGroupEntry {
751 binding: 0,
752 resource: wgpu::BindingResource::TextureView(
753 &texture.create_view(&wgpu::TextureViewDescriptor::default()),
754 ),
755 },
756 wgpu::BindGroupEntry {
757 binding: 1,
758 resource: wgpu::BindingResource::Sampler(sampler),
759 },
760 wgpu::BindGroupEntry {
761 binding: 2,
762 resource: self.texture_flag_buffers[texture_flags(nearest, wrap_mode)]
763 .as_entire_binding(),
764 },
765 ],
766 })
767 });
768
769 queue_write_data_to_texture(&texture, origin);
770
771 debug_assert!(
774 image_delta.pos.is_some() || [texture.width(), texture.height()] == [width, height],
775 "egui texture {id:?}: GPU texture is {}x{} but full delta is {width}x{height}",
776 texture.width(),
777 texture.height(),
778 );
779
780 self.textures.insert(
781 id,
782 Texture {
783 texture: Some(texture),
784 bind_group,
785 options: Some(image_delta.options),
786 },
787 );
788 }
789
790 pub fn free_texture(&mut self, id: &epaint::TextureId) {
791 if let Some(texture) = self.textures.remove(id).and_then(|t| t.texture) {
792 texture.destroy();
793 }
794 }
795
796 pub fn texture(&self, id: &epaint::TextureId) -> Option<&Texture> {
801 self.textures.get(id)
802 }
803
804 pub fn register_native_texture(
810 &mut self,
811 device: &wgpu::Device,
812 texture: &wgpu::TextureView,
813 texture_filter: wgpu::FilterMode,
814 ) -> epaint::TextureId {
815 self.register_native_texture_with_sampler_options(
816 device,
817 texture,
818 wgpu::SamplerDescriptor {
819 label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
820 mag_filter: texture_filter,
821 min_filter: texture_filter,
822 ..Default::default()
823 },
824 )
825 }
826
827 pub fn update_egui_texture_from_wgpu_texture(
831 &mut self,
832 device: &wgpu::Device,
833 texture: &wgpu::TextureView,
834 texture_filter: wgpu::FilterMode,
835 id: epaint::TextureId,
836 ) {
837 self.update_egui_texture_from_wgpu_texture_with_sampler_options(
838 device,
839 texture,
840 wgpu::SamplerDescriptor {
841 label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
842 mag_filter: texture_filter,
843 min_filter: texture_filter,
844 ..Default::default()
845 },
846 id,
847 );
848 }
849
850 #[expect(clippy::needless_pass_by_value)] pub fn register_native_texture_with_sampler_options(
860 &mut self,
861 device: &wgpu::Device,
862 texture: &wgpu::TextureView,
863 sampler_descriptor: wgpu::SamplerDescriptor<'_>,
864 ) -> epaint::TextureId {
865 profiling::function_scope!();
866
867 let nearest = sampler_descriptor.mag_filter == wgpu::FilterMode::Nearest;
868 let wrap_mode = address_mode_wrap_flag(sampler_descriptor.address_mode_u);
869 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
870 compare: None,
871 ..sampler_descriptor
872 });
873
874 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
875 label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
876 layout: &self.texture_bind_group_layout,
877 entries: &[
878 wgpu::BindGroupEntry {
879 binding: 0,
880 resource: wgpu::BindingResource::TextureView(texture),
881 },
882 wgpu::BindGroupEntry {
883 binding: 1,
884 resource: wgpu::BindingResource::Sampler(&sampler),
885 },
886 wgpu::BindGroupEntry {
887 binding: 2,
888 resource: self.texture_flag_buffers[texture_flags(nearest, wrap_mode)]
889 .as_entire_binding(),
890 },
891 ],
892 });
893
894 let id = epaint::TextureId::User(self.next_user_texture_id);
895 self.textures.insert(
896 id,
897 Texture {
898 texture: None,
899 bind_group,
900 options: None,
901 },
902 );
903 self.next_user_texture_id += 1;
904
905 id
906 }
907
908 #[expect(clippy::needless_pass_by_value)] pub fn update_egui_texture_from_wgpu_texture_with_sampler_options(
914 &mut self,
915 device: &wgpu::Device,
916 texture: &wgpu::TextureView,
917 sampler_descriptor: wgpu::SamplerDescriptor<'_>,
918 id: epaint::TextureId,
919 ) {
920 profiling::function_scope!();
921
922 let Texture {
923 bind_group: user_texture_binding,
924 ..
925 } = self
926 .textures
927 .get_mut(&id)
928 .expect("Tried to update a texture that has not been allocated yet.");
929
930 let nearest = sampler_descriptor.mag_filter == wgpu::FilterMode::Nearest;
931 let wrap_mode = address_mode_wrap_flag(sampler_descriptor.address_mode_u);
932 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
933 compare: None,
934 ..sampler_descriptor
935 });
936
937 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
938 label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
939 layout: &self.texture_bind_group_layout,
940 entries: &[
941 wgpu::BindGroupEntry {
942 binding: 0,
943 resource: wgpu::BindingResource::TextureView(texture),
944 },
945 wgpu::BindGroupEntry {
946 binding: 1,
947 resource: wgpu::BindingResource::Sampler(&sampler),
948 },
949 wgpu::BindGroupEntry {
950 binding: 2,
951 resource: self.texture_flag_buffers[texture_flags(nearest, wrap_mode)]
952 .as_entire_binding(),
953 },
954 ],
955 });
956
957 *user_texture_binding = bind_group;
958 }
959
960 pub fn update_buffers(
965 &mut self,
966 device: &wgpu::Device,
967 queue: &wgpu::Queue,
968 encoder: &mut wgpu::CommandEncoder,
969 paint_jobs: &[epaint::ClippedPrimitive],
970 screen_descriptor: &ScreenDescriptor,
971 ) -> Vec<wgpu::CommandBuffer> {
972 profiling::function_scope!();
973
974 let screen_size_in_points = screen_descriptor.screen_size_in_points();
975
976 let uniform_buffer_content = UniformBuffer {
977 screen_size_in_points,
978 dithering: u32::from(self.options.dithering),
979 predictable_texture_filtering: u32::from(self.options.predictable_texture_filtering),
980 };
981 if uniform_buffer_content != self.previous_uniform_buffer_content {
982 profiling::scope!("update uniforms");
983 queue.write_buffer(
984 &self.uniform_buffer,
985 0,
986 bytemuck::cast_slice(&[uniform_buffer_content]),
987 );
988 self.previous_uniform_buffer_content = uniform_buffer_content;
989 }
990
991 let mut callbacks = Vec::new();
993 let (vertex_count, index_count) = {
994 profiling::scope!("count_vertices_indices");
995 paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {
996 match &clipped_primitive.primitive {
997 Primitive::Mesh(mesh) => {
998 (acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len())
999 }
1000 Primitive::Callback(callback) => {
1001 if let Some(c) = callback.callback.downcast_ref::<Callback>() {
1002 callbacks.push(c.0.as_ref());
1003 } else {
1004 log::warn!("Unknown paint callback: expected `egui_wgpu::Callback`");
1005 }
1006 acc
1007 }
1008 }
1009 })
1010 };
1011
1012 if index_count > 0 {
1013 profiling::scope!("indices", index_count.to_string().as_str());
1014
1015 self.index_buffer.slices.clear();
1016
1017 let required_index_buffer_size = (core::mem::size_of::<u32>() * index_count) as u64;
1018 if self.index_buffer.capacity < required_index_buffer_size {
1019 self.index_buffer.capacity =
1021 (self.index_buffer.capacity * 2).at_least(required_index_buffer_size);
1022 self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity);
1023 }
1024
1025 let index_buffer_staging = queue.write_buffer_with(
1026 &self.index_buffer.buffer,
1027 0,
1028 #[expect(clippy::unwrap_used)] NonZeroU64::new(required_index_buffer_size).unwrap(),
1030 );
1031
1032 let Some(mut index_buffer_staging) = index_buffer_staging else {
1033 panic!(
1034 "Failed to create staging buffer for index data. Index count: {index_count}. Required index buffer size: {required_index_buffer_size}. Actual size {} and capacity: {} (bytes)",
1035 self.index_buffer.buffer.size(),
1036 self.index_buffer.capacity
1037 );
1038 };
1039
1040 let mut index_offset = 0;
1041 for epaint::ClippedPrimitive { primitive, .. } in paint_jobs {
1042 match primitive {
1043 Primitive::Mesh(mesh) => {
1044 let size = mesh.indices.len() * core::mem::size_of::<u32>();
1045 let slice = index_offset..(size + index_offset);
1046 index_buffer_staging
1047 .slice(slice.clone())
1048 .copy_from_slice(bytemuck::cast_slice(&mesh.indices));
1049 self.index_buffer.slices.push(slice);
1050 index_offset += size;
1051 }
1052 Primitive::Callback(_) => {}
1053 }
1054 }
1055 }
1056 if vertex_count > 0 {
1057 profiling::scope!("vertices", vertex_count.to_string().as_str());
1058
1059 self.vertex_buffer.slices.clear();
1060
1061 let required_vertex_buffer_size =
1062 (core::mem::size_of::<Vertex>() * vertex_count) as u64;
1063 if self.vertex_buffer.capacity < required_vertex_buffer_size {
1064 self.vertex_buffer.capacity =
1066 (self.vertex_buffer.capacity * 2).at_least(required_vertex_buffer_size);
1067 self.vertex_buffer.buffer =
1068 create_vertex_buffer(device, self.vertex_buffer.capacity);
1069 }
1070
1071 let vertex_buffer_staging = queue.write_buffer_with(
1072 &self.vertex_buffer.buffer,
1073 0,
1074 #[expect(clippy::unwrap_used)] NonZeroU64::new(required_vertex_buffer_size).unwrap(),
1076 );
1077
1078 let Some(mut vertex_buffer_staging) = vertex_buffer_staging else {
1079 panic!(
1080 "Failed to create staging buffer for vertex data. Vertex count: {vertex_count}. Required vertex buffer size: {required_vertex_buffer_size}. Actual size {} and capacity: {} (bytes)",
1081 self.vertex_buffer.buffer.size(),
1082 self.vertex_buffer.capacity
1083 );
1084 };
1085
1086 let mut vertex_offset = 0;
1087 for epaint::ClippedPrimitive { primitive, .. } in paint_jobs {
1088 match primitive {
1089 Primitive::Mesh(mesh) => {
1090 let size = mesh.vertices.len() * core::mem::size_of::<Vertex>();
1091 let slice = vertex_offset..(size + vertex_offset);
1092 vertex_buffer_staging
1093 .slice(slice.clone())
1094 .copy_from_slice(bytemuck::cast_slice(&mesh.vertices));
1095 self.vertex_buffer.slices.push(slice);
1096 vertex_offset += size;
1097 }
1098 Primitive::Callback(_) => {}
1099 }
1100 }
1101 }
1102
1103 let mut user_cmd_bufs = Vec::new();
1104 {
1105 profiling::scope!("prepare callbacks");
1106 for callback in &callbacks {
1107 user_cmd_bufs.extend(callback.prepare(
1108 device,
1109 queue,
1110 screen_descriptor,
1111 encoder,
1112 &mut self.callback_resources,
1113 ));
1114 }
1115 }
1116 {
1117 profiling::scope!("finish prepare callbacks");
1118 for callback in &callbacks {
1119 user_cmd_bufs.extend(callback.finish_prepare(
1120 device,
1121 queue,
1122 encoder,
1123 &mut self.callback_resources,
1124 ));
1125 }
1126 }
1127
1128 user_cmd_bufs
1129 }
1130}
1131
1132const TEX_FLAG_NEAREST: u32 = 1;
1136
1137const WRAP_MODE_CLAMP_TO_EDGE: u32 = 0;
1141const WRAP_MODE_REPEAT: u32 = 1;
1142const WRAP_MODE_MIRRORED_REPEAT: u32 = 2;
1143
1144const NUM_TEXTURE_FLAGS: usize = 6;
1146
1147fn wrap_mode_flag(wrap_mode: epaint::textures::TextureWrapMode) -> u32 {
1148 match wrap_mode {
1149 epaint::textures::TextureWrapMode::ClampToEdge => WRAP_MODE_CLAMP_TO_EDGE,
1150 epaint::textures::TextureWrapMode::Repeat => WRAP_MODE_REPEAT,
1151 epaint::textures::TextureWrapMode::MirroredRepeat => WRAP_MODE_MIRRORED_REPEAT,
1152 }
1153}
1154
1155fn address_mode_wrap_flag(address_mode: wgpu::AddressMode) -> u32 {
1156 match address_mode {
1157 wgpu::AddressMode::Repeat => WRAP_MODE_REPEAT,
1158 wgpu::AddressMode::MirrorRepeat => WRAP_MODE_MIRRORED_REPEAT,
1159 wgpu::AddressMode::ClampToEdge | wgpu::AddressMode::ClampToBorder => {
1160 WRAP_MODE_CLAMP_TO_EDGE
1161 }
1162 }
1163}
1164
1165fn texture_flags(nearest: bool, wrap_mode: u32) -> usize {
1171 let nearest = if nearest { TEX_FLAG_NEAREST } else { 0 };
1172 (nearest | (wrap_mode << 1)) as usize
1173}
1174
1175fn create_sampler(
1176 options: epaint::textures::TextureOptions,
1177 device: &wgpu::Device,
1178) -> wgpu::Sampler {
1179 let mag_filter = match options.magnification {
1180 epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
1181 epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
1182 };
1183 let min_filter = match options.minification {
1184 epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
1185 epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
1186 };
1187 let address_mode = match options.wrap_mode {
1188 epaint::textures::TextureWrapMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
1189 epaint::textures::TextureWrapMode::Repeat => wgpu::AddressMode::Repeat,
1190 epaint::textures::TextureWrapMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,
1191 };
1192 device.create_sampler(&wgpu::SamplerDescriptor {
1193 label: Some(&format!(
1194 "egui sampler (mag: {mag_filter:?}, min {min_filter:?})"
1195 )),
1196 mag_filter,
1197 min_filter,
1198 address_mode_u: address_mode,
1199 address_mode_v: address_mode,
1200 ..Default::default()
1201 })
1202}
1203
1204fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
1205 profiling::function_scope!();
1206 device.create_buffer(&wgpu::BufferDescriptor {
1207 label: Some("egui_vertex_buffer"),
1208 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1209 size,
1210 mapped_at_creation: false,
1211 })
1212}
1213
1214fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
1215 profiling::function_scope!();
1216 device.create_buffer(&wgpu::BufferDescriptor {
1217 label: Some("egui_index_buffer"),
1218 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
1219 size,
1220 mapped_at_creation: false,
1221 })
1222}
1223
1224struct ScissorRect {
1226 x: u32,
1227 y: u32,
1228 width: u32,
1229 height: u32,
1230}
1231
1232impl ScissorRect {
1233 fn new(clip_rect: &epaint::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self {
1234 let clip_min_x = pixels_per_point * clip_rect.min.x;
1236 let clip_min_y = pixels_per_point * clip_rect.min.y;
1237 let clip_max_x = pixels_per_point * clip_rect.max.x;
1238 let clip_max_y = pixels_per_point * clip_rect.max.y;
1239
1240 let clip_min_x = clip_min_x.round() as u32;
1242 let clip_min_y = clip_min_y.round() as u32;
1243 let clip_max_x = clip_max_x.round() as u32;
1244 let clip_max_y = clip_max_y.round() as u32;
1245
1246 let clip_min_x = clip_min_x.clamp(0, target_size[0]);
1248 let clip_min_y = clip_min_y.clamp(0, target_size[1]);
1249 let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]);
1250 let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]);
1251
1252 Self {
1253 x: clip_min_x,
1254 y: clip_min_y,
1255 width: clip_max_x - clip_min_x,
1256 height: clip_max_y - clip_min_y,
1257 }
1258 }
1259}
1260
1261#[cfg(any(
1266 not(target_arch = "wasm32"),
1267 all(
1268 feature = "fragile-send-sync-non-atomic-wasm",
1269 not(target_feature = "atomics")
1270 ),
1271))]
1272const _: () = {
1273 const fn assert_send_sync<T: Send + Sync>() {}
1274 assert_send_sync::<Renderer>();
1275};