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 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
124 backends: wgpu::Backends::all(),
125 dx12_shader_compiler: Default::default(),
126 });
127
128 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 limits: if cfg!(target_arch = "wasm32") {
150 wgpu::Limits::downlevel_webgl2_defaults()
151 } else {
152 wgpu::Limits::default()
153 },
154 },
155 None, )
157 .await
158 .unwrap();
159
160 let surface_caps = surface.get_capabilities(&adapter);
161 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 polygon_mode: wgpu::PolygonMode::Fill,
369 unclipped_depth: false,
371 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 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}