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