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