ellipsoid/
graphics.rs

1use std::{fmt::Display, marker::PhantomData, num::NonZeroU32, path::Path};
2
3use glam::{Vec2, Vec3};
4
5use strum::{EnumIter, IntoEnumIterator};
6use winit::window::Window;
7
8mod gtransform;
9mod shape;
10
11pub use gtransform::GTransform;
12pub use shape::Shape;
13
14mod color;
15pub use color::Color;
16
17const VERTEX_BUFFER_INIT_SIZE: wgpu::BufferAddress =
18    1000 * std::mem::size_of::<VertexRaw>() as wgpu::BufferAddress;
19const INDEX_BUFFER_INIT_SIZE: wgpu::BufferAddress =
20    300 * std::mem::size_of::<u32>() as wgpu::BufferAddress;
21
22pub trait Textures: IntoEnumIterator + Display + Default + Into<u32> + Copy {
23    fn name(&self) -> String {
24        self.to_string()
25    }
26    fn extension(&self) -> image::ImageFormat {
27        image::ImageFormat::Png
28    }
29}
30
31pub type Geometry<T> = (Vec<Vertex<T>>, Vec<u32>);
32
33#[derive(Copy, Clone, Debug, Default, PartialEq)]
34pub struct Vertex<T: Textures> {
35    position: Vec3,
36    texture: T,
37    texture_coords: Vec2,
38    color: Color,
39}
40
41impl<T: Textures> Into<Vertex<T>> for (Vec3, Vec2) {
42    fn into(self) -> Vertex<T> {
43        Vertex {
44            position: self.0,
45            texture: T::default(),
46            texture_coords: self.1,
47            color: Color::WHITE,
48        }
49    }
50}
51
52#[repr(C)]
53#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Default)]
54pub struct VertexRaw {
55    position: [f32; 3],
56    texture_index: u32,
57    texture_coords: [f32; 2],
58    color: [f32; 4],
59}
60
61impl VertexRaw {
62    const ATTRIBS: [wgpu::VertexAttribute; 4] =
63        wgpu::vertex_attr_array![0 => Float32x3, 1 => Uint32, 2 => Float32x2, 3 => Float32x4];
64
65    fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
66        use std::mem;
67
68        wgpu::VertexBufferLayout {
69            array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
70            step_mode: wgpu::VertexStepMode::Vertex,
71            attributes: &Self::ATTRIBS,
72        }
73    }
74}
75
76impl<T: Textures> Into<VertexRaw> for Vertex<T> {
77    fn into(self) -> VertexRaw {
78        VertexRaw {
79            position: [self.position.x, self.position.y, self.position.z],
80            texture_index: self.texture.into(),
81            texture_coords: [self.texture_coords.x, self.texture_coords.y],
82            color: self.color.into(),
83        }
84    }
85}
86
87fn align<T: Default + Clone>(v: &mut Vec<T>) {
88    let len = v.len();
89    let rem = len % 4;
90    if rem > 0 {
91        v.extend(std::iter::repeat(T::default()).take(4 - rem));
92    }
93}
94
95pub struct Graphics<T: Textures> {
96    pub size: winit::dpi::PhysicalSize<u32>,
97    pub egui_platform: egui_winit_platform::Platform,
98    surface: wgpu::Surface,
99    device: wgpu::Device,
100    queue: wgpu::Queue,
101    config: wgpu::SurfaceConfiguration,
102    render_pipeline: wgpu::RenderPipeline,
103    vertex_buffer: wgpu::Buffer,
104    index_buffer: wgpu::Buffer,
105    num_indices: u32,
106    window: Window,
107    egui_rpass: egui_wgpu_backend::RenderPass,
108    start_time: chrono::NaiveTime,
109    vertices: Vec<Vertex<T>>,
110    indices: Vec<u32>,
111    texture_views: Vec<wgpu::TextureView>,
112    textures_bind_group: wgpu::BindGroup,
113    depth_texture: wgpu::Texture,
114    depth_texture_view: wgpu::TextureView,
115}
116
117impl<T: Textures> Graphics<T> {
118    pub async fn new(window: Window) -> Self {
119        let size = window.inner_size();
120
121        // The instance is a handle to our GPU
122        // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
123        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
124            backends: wgpu::Backends::all(),
125            dx12_shader_compiler: Default::default(),
126        });
127
128        // # Safety
129        //
130        // The surface needs to live as long as the window that created it.
131        // State owns the window so this should be safe.
132        let surface = unsafe { instance.create_surface(&window) }.unwrap();
133
134        let adapter = instance
135            .request_adapter(&wgpu::RequestAdapterOptions {
136                power_preference: wgpu::PowerPreference::default(),
137                compatible_surface: Some(&surface),
138                force_fallback_adapter: false,
139            })
140            .await
141            .unwrap();
142        let (device, queue) = adapter
143            .request_device(
144                &wgpu::DeviceDescriptor {
145                    label: None,
146                    features: wgpu::Features::TEXTURE_BINDING_ARRAY | wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
147                    // WebGL doesn't support all of wgpu's features, so if
148                    // we're building for the web we'll have to disable some.
149                    limits: if cfg!(target_arch = "wasm32") {
150                        wgpu::Limits::downlevel_webgl2_defaults()
151                    } else {
152                        wgpu::Limits::default()
153                    },
154                },
155                None, // Trace path
156            )
157            .await
158            .unwrap();
159
160        let surface_caps = surface.get_capabilities(&adapter);
161        // Shader code in this tutorial assumes an Srgb surface texture. Using a different
162        // one will result all the colors comming out darker. If you want to support non
163        // Srgb surfaces, you'll need to account for that when drawing to the frame.
164        let surface_format = surface_caps
165            .formats
166            .iter()
167            .copied()
168            .filter(|f| f.describe().srgb)
169            .next()
170            .unwrap_or(surface_caps.formats[0]);
171        let config = wgpu::SurfaceConfiguration {
172            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
173            format: surface_format,
174            width: size.width,
175            height: size.height,
176            present_mode: surface_caps.present_modes[0],
177            alpha_mode: surface_caps.alpha_modes[0],
178            view_formats: vec![],
179        };
180        surface.configure(&device, &config);
181
182        let texture_views = T::iter()
183            .enumerate()
184            .map(|(i, texture)| {
185                let mut ext = None;
186                for new_ext in texture.extension().extensions_str() {
187                    let path = Path::new("assets/textures").join(format!(
188                        "{}.{}",
189                        texture.name(),
190                        new_ext
191                    ));
192                    if path.exists() {
193                        ext = Some(new_ext);
194                        break;
195                    }
196                }
197                let Some(ext) = ext else {
198                    panic!("Texture {} not found", texture);
199                };
200
201                let path = Path::new("assets/textures").join(format!("{}.{}", texture.name(), ext));
202
203                let image_bytes = std::fs::read(path).unwrap();
204                let diffuse_image =
205                    image::load_from_memory_with_format(&image_bytes, texture.extension()).unwrap();
206
207                use image::GenericImageView;
208                let dimensions = diffuse_image.dimensions();
209
210                let texture_size = wgpu::Extent3d {
211                    width: dimensions.0,
212                    height: dimensions.1,
213                    depth_or_array_layers: 1,
214                };
215
216                let format = wgpu::TextureFormat::Rgba8UnormSrgb;
217
218                let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
219                    size: texture_size,
220                    mip_level_count: 1,
221                    sample_count: 1,
222                    dimension: wgpu::TextureDimension::D2,
223                    format,
224                    usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
225                    label: Some(format!("diffuse_texture_{}", i).as_str()),
226                    view_formats: &[],
227                });
228
229                let diffuse_rgba = diffuse_image.to_rgba8().into_raw();
230
231                let bytes_per_pixel = format.describe().block_size as u32;
232                let bytes_per_row = dimensions.0 * bytes_per_pixel;
233
234                queue.write_texture(
235                    wgpu::ImageCopyTexture {
236                        texture: &diffuse_texture,
237                        mip_level: 0,
238                        origin: wgpu::Origin3d::ZERO,
239                        aspect: wgpu::TextureAspect::All,
240                    },
241                    &diffuse_rgba,
242                    wgpu::ImageDataLayout {
243                        offset: 0,
244                        bytes_per_row: std::num::NonZeroU32::new(bytes_per_row),
245                        rows_per_image: std::num::NonZeroU32::new(dimensions.1),
246                    },
247                    texture_size,
248                );
249
250                diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default())
251            })
252            .collect::<Vec<_>>();
253
254        let texture_bind_group_layout =
255            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
256                label: Some("texture_bind_group_layout"),
257                entries: &[
258                    wgpu::BindGroupLayoutEntry {
259                        binding: 0,
260                        visibility: wgpu::ShaderStages::FRAGMENT,
261                        ty: wgpu::BindingType::Texture {
262                            multisampled: false,
263                            view_dimension: wgpu::TextureViewDimension::D2,
264                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
265                        },
266                        count: NonZeroU32::new(texture_views.len() as u32),
267                    },
268                    wgpu::BindGroupLayoutEntry {
269                        binding: 1,
270                        visibility: wgpu::ShaderStages::FRAGMENT,
271                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
272                        count: None,
273                    },
274                ],
275            });
276
277        let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
278
279        let texture_views_ref = texture_views.iter().collect::<Vec<_>>();
280        let textures_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
281            entries: &[
282                wgpu::BindGroupEntry {
283                    binding: 0,
284                    resource: wgpu::BindingResource::TextureViewArray(&texture_views_ref),
285                },
286                wgpu::BindGroupEntry {
287                    binding: 1,
288                    resource: wgpu::BindingResource::Sampler(&sampler),
289                },
290            ],
291            layout: &texture_bind_group_layout,
292            label: Some("texture_bind_group"),
293        });
294
295        let render_pipeline_layout =
296            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
297                label: Some("Render Pipeline Layout"),
298                bind_group_layouts: &[&texture_bind_group_layout],
299                push_constant_ranges: &[],
300            });
301
302        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
303            label: Some("Shader"),
304            source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
305        });
306
307
308        let depth_texture = device.create_texture(&wgpu::TextureDescriptor {
309            label: Some("depth_texture"),
310            size: wgpu::Extent3d {
311                width: config.width,
312                height: config.height,
313                depth_or_array_layers: 1,
314            },
315            mip_level_count: 1,
316            sample_count: 1,
317            dimension: wgpu::TextureDimension::D2,
318            format: wgpu::TextureFormat::Depth24PlusStencil8,
319            view_formats: &[],
320            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
321        });
322
323        let depth_texture_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
324
325        let depth_stencil_state = wgpu::DepthStencilState {
326            format: wgpu::TextureFormat::Depth24PlusStencil8,
327            depth_write_enabled: true,
328            depth_compare: wgpu::CompareFunction::Less,
329            stencil: wgpu::StencilState::default(),
330            bias: wgpu::DepthBiasState::default(),
331        };        
332
333        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
334            label: Some("Render Pipeline"),
335            layout: Some(&render_pipeline_layout),
336            vertex: wgpu::VertexState {
337                module: &shader,
338                entry_point: "vs_main",
339                buffers: &[VertexRaw::desc()],
340            },
341            fragment: Some(wgpu::FragmentState {
342                module: &shader,
343                entry_point: "fs_main",
344                targets: &[Some(wgpu::ColorTargetState {
345                    format: config.format,
346                    blend: Some(wgpu::BlendState {
347                        color: wgpu::BlendComponent {
348                            src_factor: wgpu::BlendFactor::SrcAlpha,
349                            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
350                            operation: wgpu::BlendOperation::Add,
351                        },
352                        alpha: wgpu::BlendComponent {
353                            src_factor: wgpu::BlendFactor::SrcAlpha,
354                            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
355                            operation: wgpu::BlendOperation::Add,
356                        },
357                    }),
358                    write_mask: wgpu::ColorWrites::ALL,
359                })],
360            }),
361            primitive: wgpu::PrimitiveState {
362                topology: wgpu::PrimitiveTopology::TriangleList,
363                strip_index_format: None,
364                front_face: wgpu::FrontFace::Ccw,
365                cull_mode: Some(wgpu::Face::Back),
366                // Setting this to anything other than Fill requires Features::POLYGON_MODE_LINE
367                // or Features::POLYGON_MODE_POINT
368                polygon_mode: wgpu::PolygonMode::Fill,
369                // Requires Features::DEPTH_CLIP_CONTROL
370                unclipped_depth: false,
371                // Requires Features::CONSERVATIVE_RASTERIZATION
372                conservative: false,
373            },
374            depth_stencil: Some(depth_stencil_state),
375            multisample: wgpu::MultisampleState {
376                count: 1,
377                mask: !0,
378                alpha_to_coverage_enabled: false,
379            },
380            // If the pipeline will be used with a multiview render pass, this
381            // indicates how many array layers the attachments will have.
382            multiview: None,
383        });
384
385        let vertex_buffer_desc = wgpu::BufferDescriptor {
386            label: Some("vertex_buffer"),
387            size: VERTEX_BUFFER_INIT_SIZE,
388            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
389            mapped_at_creation: false,
390        };
391
392        let vertex_buffer = device.create_buffer(&vertex_buffer_desc);
393
394        let index_buffer_desc = wgpu::BufferDescriptor {
395            label: Some("index_buffer"),
396            size: INDEX_BUFFER_INIT_SIZE,
397            usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
398            mapped_at_creation: false,
399        };
400
401        let index_buffer = device.create_buffer(&index_buffer_desc);
402
403        let num_indices = 0;
404
405        let egui_platform =
406            egui_winit_platform::Platform::new(egui_winit_platform::PlatformDescriptor {
407                physical_width: size.width as u32,
408                physical_height: size.height as u32,
409                scale_factor: window.scale_factor(),
410                font_definitions: egui::FontDefinitions::default(),
411                style: Default::default(),
412            });
413
414        let egui_rpass = egui_wgpu_backend::RenderPass::new(&device, surface_format, 1);
415
416        Self {
417            surface,
418            device,
419            queue,
420            config,
421            size,
422            render_pipeline,
423            vertex_buffer,
424            index_buffer,
425            num_indices,
426            window,
427            egui_platform,
428            egui_rpass,
429            start_time: chrono::Local::now().time(),
430            vertices: vec![],
431            indices: vec![],
432            textures_bind_group,
433            texture_views,
434            depth_texture,
435            depth_texture_view
436        }
437    }
438
439    pub fn add_geometry(&mut self, geometry: Geometry<T>) {
440        let index_offset = self.vertices.len() as u32;
441
442        let (vertices, indices) = geometry;
443
444        self.vertices.extend(vertices);
445        self.indices
446            .extend(indices.into_iter().map(|i| i + index_offset));
447    }
448
449    pub fn window(&self) -> &Window {
450        &self.window
451    }
452
453    pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
454        if new_size.width == 0 || new_size.height == 0 {
455            return;
456        }
457        self.size = new_size;
458        self.config.width = new_size.width;
459        self.config.height = new_size.height;
460        self.surface.configure(&self.device, &self.config);
461
462        self.depth_texture = self.device.create_texture(&wgpu::TextureDescriptor {
463            label: Some("depth_texture"),
464            size: wgpu::Extent3d {
465                width: self.config.width,
466                height: self.config.height,
467                depth_or_array_layers: 1,
468            },
469            mip_level_count: 1,
470            sample_count: 1,
471            dimension: wgpu::TextureDimension::D2,
472            format: wgpu::TextureFormat::Depth24PlusStencil8,
473            view_formats: &[],
474            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
475        });
476
477        self.depth_texture_view = self.depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
478    }
479
480    pub fn handle_raw_event(&mut self, event: &winit::event::Event<()>) {
481        self.egui_platform.handle_event(event);
482    }
483
484    pub fn update(&mut self) {
485        self.egui_platform.update_time(
486            (chrono::Local::now().time() - self.start_time).num_milliseconds() as f64 / 1000.0,
487        );
488        self.egui_platform.begin_frame();
489
490        self.num_indices = self.indices.len() as u32;
491
492        let mut vertices_raw = std::mem::take(&mut self.vertices)
493            .into_iter()
494            .map(|x| x.into())
495            .collect::<Vec<VertexRaw>>();
496
497        align(&mut self.indices);
498        align(&mut vertices_raw);
499
500        if self.vertex_buffer.size()
501            < (vertices_raw.len() * std::mem::size_of::<VertexRaw>()) as u64
502        {
503            let mut new_size = self.vertex_buffer.size();
504            while new_size < (vertices_raw.len() * std::mem::size_of::<VertexRaw>()) as u64 {
505                new_size *= 2;
506            }
507            self.vertex_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
508                label: Some("vertex_buffer"),
509                size: new_size,
510                usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
511                mapped_at_creation: false,
512            });
513        }
514
515        if self.index_buffer.size() < (self.indices.len() * std::mem::size_of::<u32>()) as u64 {
516            let mut new_size = self.index_buffer.size();
517            while new_size < (self.indices.len() * std::mem::size_of::<u32>()) as u64 {
518                new_size *= 2;
519            }
520            self.index_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
521                label: Some("index_buffer"),
522                size: new_size,
523                usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
524                mapped_at_creation: false,
525            });
526        }
527
528        self.queue
529            .write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertices_raw));
530        self.queue
531            .write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&self.indices));
532
533        self.indices.clear();
534    }
535
536    pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
537        let output = self.surface.get_current_texture()?;
538        let view = output
539            .texture
540            .create_view(&wgpu::TextureViewDescriptor::default());
541
542        let full_output = self.egui_platform.end_frame(Some(&self.window));
543        let paint_jobs = self.egui_platform.context().tessellate(full_output.shapes);
544
545        let mut encoder = self
546            .device
547            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
548                label: Some("Render Encoder"),
549            });
550
551        {
552
553            let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
554                label: Some("Render Pass"),
555                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
556                    view: &view,
557                    resolve_target: None,
558                    ops: wgpu::Operations {
559                        load: wgpu::LoadOp::Clear(wgpu::Color {
560                            r: 0.1,
561                            g: 0.1,
562                            b: 0.1,
563                            a: 1.0,
564                        }),
565                        store: true,
566                    },
567                })],
568                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
569                    view: &self.depth_texture_view,
570                    depth_ops: Some(wgpu::Operations {
571                        load: wgpu::LoadOp::Clear(1.0),
572                        store: true,
573                    }),
574                    stencil_ops: None,
575                }),
576            });
577
578            render_pass.set_pipeline(&self.render_pipeline);
579
580            render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
581            render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
582
583            render_pass.set_bind_group(0, &self.textures_bind_group, &[]);
584
585            render_pass.draw_indexed(0..self.num_indices, 0, 0..1);
586        }
587
588        let tdelta = full_output.textures_delta;
589        self.egui_rpass
590            .add_textures(&self.device, &self.queue, &tdelta)
591            .expect("Failed to add textures");
592        let screen_descriptor = egui_wgpu_backend::ScreenDescriptor {
593            physical_width: self.size.width,
594            physical_height: self.size.height,
595            scale_factor: self.window.scale_factor() as f32,
596        };
597        self.egui_rpass
598            .update_buffers(&self.device, &self.queue, &paint_jobs, &screen_descriptor);
599
600        self.egui_rpass
601            .execute(&mut encoder, &view, &paint_jobs, &screen_descriptor, None)
602            .unwrap();
603
604        self.queue.submit(std::iter::once(encoder.finish()));
605        output.present();
606
607        self.egui_rpass
608            .remove_textures(tdelta)
609            .expect("Failed to remove textures");
610
611        Ok(())
612    }
613}