1use crate::ScreenDescriptor;
2use epaint::emath::NumExt;
3use epaint::{Primitive, Vertex};
4use std::borrow::Cow;
5use std::collections::HashMap;
6use std::num::NonZeroU64;
7use std::ops::Range;
8use wgpu::util::DeviceExt;
9
10#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
12#[repr(C)]
13struct UniformBuffer {
14 screen_size_in_points: [f32; 2],
15 _padding: [u32; 2],
18}
19
20impl PartialEq for UniformBuffer {
21 fn eq(&self, other: &Self) -> bool {
22 self.screen_size_in_points == other.screen_size_in_points
23 }
24}
25
26struct SlicedBuffer {
27 buffer: wgpu::Buffer,
28 slices: Vec<Range<usize>>,
29 capacity: wgpu::BufferAddress,
30}
31
32pub struct RadiantRenderer {
33 pipeline: wgpu::RenderPipeline,
34
35 index_buffer: SlicedBuffer,
36 vertex_buffer: SlicedBuffer,
37
38 uniform_buffer: wgpu::Buffer,
39 previous_uniform_buffer_content: UniformBuffer,
40 uniform_bind_group: wgpu::BindGroup,
41 texture_bind_group_layout: wgpu::BindGroupLayout,
42
43 textures: HashMap<epaint::TextureId, (Option<wgpu::Texture>, wgpu::BindGroup)>,
47 samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>,
49}
50
51impl RadiantRenderer {
52 pub fn new(
53 device: &wgpu::Device,
54 output_color_format: wgpu::TextureFormat,
55 output_depth_format: Option<wgpu::TextureFormat>,
56 msaa_samples: u32,
57 ) -> Self {
58 let module = device.create_shader_module(wgpu::include_wgsl!("egui.wgsl"));
59
60 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
61 label: Some("egui_uniform_buffer"),
62 contents: bytemuck::cast_slice(&[UniformBuffer {
63 screen_size_in_points: [0.0, 0.0],
64 _padding: Default::default(),
65 }]),
66 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
67 });
68
69 let uniform_bind_group_layout =
70 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
71 label: Some("egui_uniform_bind_group_layout"),
72 entries: &[wgpu::BindGroupLayoutEntry {
73 binding: 0,
74 visibility: wgpu::ShaderStages::VERTEX,
75 ty: wgpu::BindingType::Buffer {
76 has_dynamic_offset: false,
77 min_binding_size: NonZeroU64::new(std::mem::size_of::<UniformBuffer>() as _),
78 ty: wgpu::BufferBindingType::Uniform,
79 },
80 count: None,
81 }],
82 });
83
84 let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
85 label: Some("egui_uniform_bind_group"),
86 layout: &uniform_bind_group_layout,
87 entries: &[wgpu::BindGroupEntry {
88 binding: 0,
89 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
90 buffer: &uniform_buffer,
91 offset: 0,
92 size: None,
93 }),
94 }],
95 });
96
97 let texture_bind_group_layout =
98 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
99 label: Some("egui_texture_bind_group_layout"),
100 entries: &[
101 wgpu::BindGroupLayoutEntry {
102 binding: 0,
103 visibility: wgpu::ShaderStages::FRAGMENT,
104 ty: wgpu::BindingType::Texture {
105 multisampled: false,
106 sample_type: wgpu::TextureSampleType::Float { filterable: true },
107 view_dimension: wgpu::TextureViewDimension::D2,
108 },
109 count: None,
110 },
111 wgpu::BindGroupLayoutEntry {
112 binding: 1,
113 visibility: wgpu::ShaderStages::FRAGMENT,
114 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
115 count: None,
116 },
117 ],
118 });
119
120 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
121 label: Some("egui_pipeline_layout"),
122 bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
123 push_constant_ranges: &[],
124 });
125
126 let depth_stencil = output_depth_format.map(|format| wgpu::DepthStencilState {
127 format,
128 depth_write_enabled: false,
129 depth_compare: wgpu::CompareFunction::Always,
130 stencil: wgpu::StencilState::default(),
131 bias: wgpu::DepthBiasState::default(),
132 });
133
134 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
135 label: Some("egui_pipeline"),
136 layout: Some(&pipeline_layout),
137 vertex: wgpu::VertexState {
138 entry_point: "vs_main",
139 module: &module,
140 buffers: &[wgpu::VertexBufferLayout {
141 array_stride: 5 * 4,
142 step_mode: wgpu::VertexStepMode::Vertex,
143 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32],
147 }],
148 },
149 primitive: wgpu::PrimitiveState {
150 topology: wgpu::PrimitiveTopology::TriangleList,
151 unclipped_depth: false,
152 conservative: false,
153 cull_mode: None,
154 front_face: wgpu::FrontFace::default(),
155 polygon_mode: wgpu::PolygonMode::default(),
156 strip_index_format: None,
157 },
158 depth_stencil,
159 multisample: wgpu::MultisampleState {
160 alpha_to_coverage_enabled: false,
161 count: msaa_samples,
162 mask: !0,
163 },
164
165 fragment: Some(wgpu::FragmentState {
166 module: &module,
167 entry_point: if output_color_format.is_srgb() {
168 log::warn!("Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm", output_color_format);
169 "fs_main_linear_framebuffer"
170 } else {
171 "fs_main_gamma_framebuffer" },
173 targets: &[Some(wgpu::ColorTargetState {
174 format: output_color_format,
175 blend: Some(wgpu::BlendState {
176 color: wgpu::BlendComponent {
177 src_factor: wgpu::BlendFactor::One,
178 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
179 operation: wgpu::BlendOperation::Add,
180 },
181 alpha: wgpu::BlendComponent {
182 src_factor: wgpu::BlendFactor::OneMinusDstAlpha,
183 dst_factor: wgpu::BlendFactor::One,
184 operation: wgpu::BlendOperation::Add,
185 },
186 }),
187 write_mask: wgpu::ColorWrites::ALL,
188 })],
189 }),
190 multiview: None,
191 });
192
193 const VERTEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
194 (std::mem::size_of::<Vertex>() * 1024) as _;
195 const INDEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
196 (std::mem::size_of::<u32>() * 1024 * 3) as _;
197
198 Self {
199 pipeline,
200 vertex_buffer: SlicedBuffer {
201 buffer: create_vertex_buffer(device, VERTEX_BUFFER_START_CAPACITY),
202 slices: Vec::with_capacity(64),
203 capacity: VERTEX_BUFFER_START_CAPACITY,
204 },
205 index_buffer: SlicedBuffer {
206 buffer: create_index_buffer(device, INDEX_BUFFER_START_CAPACITY),
207 slices: Vec::with_capacity(64),
208 capacity: INDEX_BUFFER_START_CAPACITY,
209 },
210 uniform_buffer,
211 previous_uniform_buffer_content: UniformBuffer {
213 screen_size_in_points: [0.0, 0.0],
214 _padding: [0, 0],
215 },
216 uniform_bind_group,
217 texture_bind_group_layout,
218 textures: HashMap::default(),
219 samplers: HashMap::default(),
221 }
222 }
223
224 pub fn update_buffers(
225 &mut self,
226 device: &wgpu::Device,
227 queue: &wgpu::Queue,
228 screen_descriptor: &ScreenDescriptor,
229 paint_jobs: &[epaint::ClippedPrimitive],
230 ) {
231 let screen_size_in_points = screen_descriptor.screen_size_in_points();
232
233 let uniform_buffer_content = UniformBuffer {
234 screen_size_in_points,
235 _padding: Default::default(),
236 };
237 if uniform_buffer_content != self.previous_uniform_buffer_content {
238 queue.write_buffer(
240 &self.uniform_buffer,
241 0,
242 bytemuck::cast_slice(&[uniform_buffer_content]),
243 );
244 self.previous_uniform_buffer_content = uniform_buffer_content;
245 }
246
247 let (vertex_count, index_count) = {
248 paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {
250 match &clipped_primitive.primitive {
251 Primitive::Mesh(mesh) => {
252 (acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len())
253 }
254 Primitive::Callback(_callback) => {
255 (0, 0)
262 }
263 }
264 })
265 };
266
267 if index_count > 0 {
268 self.index_buffer.slices.clear();
271 let required_index_buffer_size = (std::mem::size_of::<u32>() * index_count) as u64;
272 if self.index_buffer.capacity < required_index_buffer_size {
273 self.index_buffer.capacity =
275 (self.index_buffer.capacity * 2).at_least(required_index_buffer_size);
276 self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity);
277 }
278
279 let mut index_buffer_staging = queue
280 .write_buffer_with(
281 &self.index_buffer.buffer,
282 0,
283 NonZeroU64::new(required_index_buffer_size).unwrap(),
284 )
285 .expect("Failed to create staging buffer for index data");
286 let mut index_offset = 0;
287 for epaint::ClippedPrimitive { primitive, .. } in paint_jobs {
288 match primitive {
289 Primitive::Mesh(mesh) => {
290 let size = mesh.indices.len() * std::mem::size_of::<u32>();
291 let slice = index_offset..(size + index_offset);
292 index_buffer_staging[slice.clone()]
293 .copy_from_slice(bytemuck::cast_slice(&mesh.indices));
294 self.index_buffer.slices.push(slice);
295 index_offset += size;
296 }
297 Primitive::Callback(_) => {}
298 }
299 }
300 }
301 if vertex_count > 0 {
302 self.vertex_buffer.slices.clear();
305 let required_vertex_buffer_size = (std::mem::size_of::<Vertex>() * vertex_count) as u64;
306 if self.vertex_buffer.capacity < required_vertex_buffer_size {
307 self.vertex_buffer.capacity =
309 (self.vertex_buffer.capacity * 2).at_least(required_vertex_buffer_size);
310 self.vertex_buffer.buffer =
311 create_vertex_buffer(device, self.vertex_buffer.capacity);
312 }
313
314 let mut vertex_buffer_staging = queue
315 .write_buffer_with(
316 &self.vertex_buffer.buffer,
317 0,
318 NonZeroU64::new(required_vertex_buffer_size).unwrap(),
319 )
320 .expect("Failed to create staging buffer for vertex data");
321 let mut vertex_offset = 0;
322 for epaint::ClippedPrimitive { primitive, .. } in paint_jobs {
323 match primitive {
324 Primitive::Mesh(mesh) => {
325 let size = mesh.vertices.len() * std::mem::size_of::<Vertex>();
326 let slice = vertex_offset..(size + vertex_offset);
327 vertex_buffer_staging[slice.clone()]
328 .copy_from_slice(bytemuck::cast_slice(&mesh.vertices));
329 self.vertex_buffer.slices.push(slice);
330 vertex_offset += size;
331 }
332 Primitive::Callback(_) => {}
333 }
334 }
335 }
336 }
337
338 pub fn update_texture(
339 &mut self,
340 device: &wgpu::Device,
341 queue: &wgpu::Queue,
342 id: epaint::TextureId,
343 image_delta: &epaint::ImageDelta,
344 ) {
345 let width = image_delta.image.width() as u32;
348 let height = image_delta.image.height() as u32;
349
350 let size = wgpu::Extent3d {
351 width,
352 height,
353 depth_or_array_layers: 1,
354 };
355
356 let data_color32 = match &image_delta.image {
357 epaint::ImageData::Color(image) => {
358 assert_eq!(
359 width as usize * height as usize,
360 image.pixels.len(),
361 "Mismatch between texture size and texel count"
362 );
363 Cow::Borrowed(&image.pixels)
364 }
365 epaint::ImageData::Font(image) => {
366 assert_eq!(
367 width as usize * height as usize,
368 image.pixels.len(),
369 "Mismatch between texture size and texel count"
370 );
371 Cow::Owned(image.srgba_pixels(None).collect::<Vec<_>>())
372 }
373 };
374 let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
375
376 let queue_write_data_to_texture = |texture, origin| {
377 queue.write_texture(
378 wgpu::ImageCopyTexture {
379 texture,
380 mip_level: 0,
381 origin,
382 aspect: wgpu::TextureAspect::All,
383 },
384 data_bytes,
385 wgpu::ImageDataLayout {
386 offset: 0,
387 bytes_per_row: Some(4 * width),
388 rows_per_image: Some(height),
389 },
390 size,
391 );
392 };
393
394 if let Some(pos) = image_delta.pos {
395 let (texture, _bind_group) = self
397 .textures
398 .get(&id)
399 .expect("Tried to update a texture that has not been allocated yet.");
400 let origin = wgpu::Origin3d {
401 x: pos[0] as u32,
402 y: pos[1] as u32,
403 z: 0,
404 };
405 queue_write_data_to_texture(
406 texture.as_ref().expect("Tried to update user texture."),
407 origin,
408 );
409 } else {
410 let label_str = format!("egui_texid_{id:?}");
413 let label = Some(label_str.as_str());
414 let texture = device.create_texture(&wgpu::TextureDescriptor {
415 label,
416 size,
417 mip_level_count: 1,
418 sample_count: 1,
419 dimension: wgpu::TextureDimension::D2,
420 format: wgpu::TextureFormat::Rgba8UnormSrgb, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
422 view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
423 });
424 let sampler = self
425 .samplers
426 .entry(image_delta.options)
427 .or_insert_with(|| create_sampler(image_delta.options, device));
428 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
429 label,
430 layout: &self.texture_bind_group_layout,
431 entries: &[
432 wgpu::BindGroupEntry {
433 binding: 0,
434 resource: wgpu::BindingResource::TextureView(
435 &texture.create_view(&wgpu::TextureViewDescriptor::default()),
436 ),
437 },
438 wgpu::BindGroupEntry {
439 binding: 1,
440 resource: wgpu::BindingResource::Sampler(sampler),
441 },
442 ],
443 });
444 let origin = wgpu::Origin3d::ZERO;
445 queue_write_data_to_texture(&texture, origin);
446 self.textures.insert(id, (Some(texture), bind_group));
447 };
448 }
449
450 pub fn render<'a>(
451 &'a self,
452 render_pass: &mut wgpu::RenderPass<'a>,
453 screen_descriptor: &ScreenDescriptor,
454 paint_jobs: &'a [epaint::ClippedPrimitive],
455 ) {
456 let pixels_per_point = screen_descriptor.pixels_per_point;
457 let size_in_pixels = screen_descriptor.size_in_pixels;
458
459 let mut needs_reset = true;
462
463 let mut index_buffer_slices = self.index_buffer.slices.iter();
464 let mut vertex_buffer_slices = self.vertex_buffer.slices.iter();
465
466 for epaint::ClippedPrimitive {
467 clip_rect,
468 primitive,
469 } in paint_jobs
470 {
471 if needs_reset {
472 render_pass.set_viewport(
473 0.0,
474 0.0,
475 size_in_pixels[0] as f32,
476 size_in_pixels[1] as f32,
477 0.0,
478 1.0,
479 );
480 render_pass.set_pipeline(&self.pipeline);
481 render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
482 needs_reset = false;
483 }
484
485 {
486 let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels);
487
488 if rect.width == 0 || rect.height == 0 {
489 if let Primitive::Mesh(_) = primitive {
491 index_buffer_slices.next().unwrap();
493 vertex_buffer_slices.next().unwrap();
494 }
495 continue;
496 }
497
498 render_pass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height);
499 }
500
501 match primitive {
502 Primitive::Mesh(mesh) => {
503 let index_buffer_slice = index_buffer_slices.next().unwrap();
504 let vertex_buffer_slice = vertex_buffer_slices.next().unwrap();
505
506 if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) {
507 render_pass.set_bind_group(1, bind_group, &[]);
508 render_pass.set_index_buffer(
509 self.index_buffer.buffer.slice(
510 index_buffer_slice.start as u64..index_buffer_slice.end as u64,
511 ),
512 wgpu::IndexFormat::Uint32,
513 );
514 render_pass.set_vertex_buffer(
515 0,
516 self.vertex_buffer.buffer.slice(
517 vertex_buffer_slice.start as u64..vertex_buffer_slice.end as u64,
518 ),
519 );
520 render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
521 } else {
522 log::warn!("Missing texture: {:?}", mesh.texture_id);
523 }
524 }
525 Primitive::Callback(_callback) => {
526 }
571 }
572 }
573
574 render_pass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);
575 }
576}
577
578fn create_sampler(
579 options: epaint::textures::TextureOptions,
580 device: &wgpu::Device,
581) -> wgpu::Sampler {
582 let mag_filter = match options.magnification {
583 epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
584 epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
585 };
586 let min_filter = match options.minification {
587 epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
588 epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
589 };
590 device.create_sampler(&wgpu::SamplerDescriptor {
591 label: Some(&format!(
592 "egui sampler (mag: {mag_filter:?}, min {min_filter:?})"
593 )),
594 mag_filter,
595 min_filter,
596 ..Default::default()
597 })
598}
599
600fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
601 device.create_buffer(&wgpu::BufferDescriptor {
603 label: Some("egui_vertex_buffer"),
604 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
605 size,
606 mapped_at_creation: false,
607 })
608}
609
610fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
611 device.create_buffer(&wgpu::BufferDescriptor {
613 label: Some("egui_index_buffer"),
614 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
615 size,
616 mapped_at_creation: false,
617 })
618}
619struct ScissorRect {
621 x: u32,
622 y: u32,
623 width: u32,
624 height: u32,
625}
626
627impl ScissorRect {
628 fn new(clip_rect: &epaint::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self {
629 let clip_min_x = pixels_per_point * clip_rect.min.x;
631 let clip_min_y = pixels_per_point * clip_rect.min.y;
632 let clip_max_x = pixels_per_point * clip_rect.max.x;
633 let clip_max_y = pixels_per_point * clip_rect.max.y;
634
635 let clip_min_x = clip_min_x.round() as u32;
637 let clip_min_y = clip_min_y.round() as u32;
638 let clip_max_x = clip_max_x.round() as u32;
639 let clip_max_y = clip_max_y.round() as u32;
640
641 let clip_min_x = clip_min_x.clamp(0, target_size[0]);
643 let clip_min_y = clip_min_y.clamp(0, target_size[1]);
644 let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]);
645 let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]);
646
647 Self {
648 x: clip_min_x,
649 y: clip_min_y,
650 width: clip_max_x - clip_min_x,
651 height: clip_max_y - clip_min_y,
652 }
653 }
654}