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