1use std::{borrow::Cow, collections::HashMap, fmt::Display};
2
3use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
4use wgpu::util::DeviceExt;
5use zintl_render::mesh::Mesh;
6use zintl_render_math::Mat4;
7use zintl_render_math::{
8 PhysicalPixelsFPoint, PhysicalPixelsPoint, PhysicalPixelsSize, TexturePoint, Viewport,
9};
10
11pub trait WindowHandle: HasWindowHandle + HasDisplayHandle + Sync + Send {}
12
13impl<T: HasWindowHandle + HasDisplayHandle + Sync + Send> WindowHandle for T {}
14
15#[repr(C)]
16#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
17pub struct Uniforms {
18 pub ortho: Mat4,
19}
20
21#[repr(C)]
22#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
23pub struct DevicePoint {
24 pub x: f32,
25 pub y: f32,
26}
27
28impl From<PhysicalPixelsFPoint> for DevicePoint {
29 #[inline]
30 fn from(point: PhysicalPixelsFPoint) -> Self {
31 Self {
32 x: point.x.value(),
33 y: point.y.value(),
34 }
35 }
36}
37
38impl From<PhysicalPixelsPoint> for DevicePoint {
39 #[inline]
40 fn from(point: PhysicalPixelsPoint) -> Self {
41 Self {
42 x: point.x.value() as f32,
43 y: point.y.value() as f32,
44 }
45 }
46}
47
48#[repr(C)]
49#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
50pub struct DeviceVertex {
51 pub position: DevicePoint,
52 pub tex_coords: TexturePoint,
53}
54
55impl DeviceVertex {
56 pub fn from_vertex(
57 vertex: &zintl_render::mesh::Vertex,
58 texture_size: PhysicalPixelsSize,
59 ) -> Self {
60 Self {
61 position: vertex.position.into(),
62 tex_coords: TexturePoint::from_physical_point(vertex.tex_coords, texture_size).unwrap(),
64 }
65 }
66}
67
68#[repr(C)]
69#[derive(Clone, Debug, Default)]
70pub struct DeviceMesh {
71 pub vertices: Vec<DeviceVertex>,
72 pub indices: Vec<u32>,
73 pub texture_id: Option<usize>,
74}
75
76impl DeviceMesh {
77 pub fn from_mesh(mesh: Mesh, texture_size: PhysicalPixelsSize) -> Self {
78 let vertices = mesh
79 .vertices
80 .into_iter()
81 .map(|v| DeviceVertex::from_vertex(&v, texture_size))
82 .collect();
83 let indices = mesh.indices;
84 let texture_id = mesh.texture_id;
85
86 DeviceMesh {
87 vertices,
88 indices,
89 texture_id,
90 }
91 }
92}
93
94#[allow(dead_code)]
95#[derive(Debug, Clone)]
96pub struct Texture {
97 native_texture: wgpu::Texture,
98 bind_group: wgpu::BindGroup,
99 size: PhysicalPixelsSize,
100}
101
102const FILL_RECT_SHADER_SRC: &str = include_str!("./shaders/fill_rect.wgsl");
103
104pub type TextureId = usize;
105
106#[derive(Debug)]
108pub struct WgpuApplication<'a> {
109 surface: wgpu::Surface<'a>,
110 device: wgpu::Device,
111 queue: wgpu::Queue,
112 config: wgpu::SurfaceConfiguration,
113 viewport: Viewport,
114 render_pipeline: wgpu::RenderPipeline,
115 textures: HashMap<TextureId, Texture>,
116 uniform_buffer: wgpu::Buffer,
117 uniform_bind_group: wgpu::BindGroup,
118 texture_bind_group_layout: wgpu::BindGroupLayout,
119}
120
121#[derive(Clone, Debug)]
123pub enum WgpuApplicationError {
124 CreateSurfaceError,
125 AdapterRequestDeviceError(wgpu::RequestAdapterError),
126 CreateDeviceError,
127}
128
129impl Display for WgpuApplicationError {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 match self {
132 WgpuApplicationError::CreateSurfaceError => {
133 write!(f, "Failed to create surface")
134 }
135 WgpuApplicationError::AdapterRequestDeviceError(err) => {
136 write!(f, "Failed to find an appropriate adapter: {:?}", err)
137 }
138 WgpuApplicationError::CreateDeviceError => {
139 write!(f, "Failed to create device")
140 }
141 }
142 }
143}
144
145pub type WgpuApplicationResult<T> = Result<T, WgpuApplicationError>;
147
148impl<'a> WgpuApplication<'a> {
149 async fn init_adapter(
150 instance: &wgpu::Instance,
151 surface: &wgpu::Surface<'a>,
152 ) -> WgpuApplicationResult<wgpu::Adapter> {
153 match instance
154 .request_adapter(&wgpu::RequestAdapterOptions {
155 power_preference: wgpu::PowerPreference::default(),
156 force_fallback_adapter: false,
157 compatible_surface: Some(surface),
158 })
159 .await
160 {
161 Ok(a) => Ok(a),
162 Err(err) => Err(WgpuApplicationError::AdapterRequestDeviceError(err)),
163 }
164 }
165
166 fn create_ortho_matrix(viewport: Viewport) -> Mat4 {
167 cgmath::ortho(
168 0.,
169 viewport.device_width.value() as f32,
170 viewport.device_height.value() as f32,
171 0.,
172 -1.,
173 1.,
174 )
175 .into()
176 }
177
178 fn create_vertex_buffer(device: &wgpu::Device, vertices: &[DeviceVertex]) -> wgpu::Buffer {
179 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
180 label: Some("Vertex Buffer"),
181 contents: bytemuck::cast_slice(vertices),
182 usage: wgpu::BufferUsages::VERTEX,
183 })
184 }
185
186 fn create_index_buffer(device: &wgpu::Device, indices: &[u32]) -> wgpu::Buffer {
187 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
188 label: Some("Index Buffer"),
189 contents: bytemuck::cast_slice(indices),
190 usage: wgpu::BufferUsages::INDEX,
191 })
192 }
193
194 fn create_uniform_buffer(device: &wgpu::Device, uniforms: &Uniforms) -> wgpu::Buffer {
195 device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
196 label: Some("Uniform Buffer"),
197 contents: bytemuck::cast_slice(&[*uniforms]),
198 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
199 })
200 }
201
202 async fn init(
203 instance: wgpu::Instance,
204 surface: wgpu::Surface<'a>,
205 viewport: Viewport,
206 ) -> WgpuApplicationResult<Self> {
207 let adapter = Self::init_adapter(&instance, &surface).await?;
208
209 let (device, queue) = match adapter
210 .request_device(&wgpu::DeviceDescriptor {
211 label: None,
212 required_features: wgpu::Features::empty(),
213 required_limits: wgpu::Limits::downlevel_webgl2_defaults()
214 .using_resolution(adapter.limits()),
215 memory_hints: wgpu::MemoryHints::MemoryUsage,
216 trace: wgpu::Trace::Off,
217 })
218 .await
219 {
220 Ok(r) => r,
221 Err(..) => return Err(WgpuApplicationError::CreateDeviceError),
222 };
223
224 let ortho_matrix = Self::create_ortho_matrix(viewport);
226 let uniform_buffer = Self::create_uniform_buffer(
227 &device,
228 &Uniforms {
229 ortho: ortho_matrix.into(),
230 },
231 );
232 let uniform_bind_group_layout =
233 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
234 entries: &[wgpu::BindGroupLayoutEntry {
235 binding: 0,
236 visibility: wgpu::ShaderStages::VERTEX,
237 ty: wgpu::BindingType::Buffer {
238 ty: wgpu::BufferBindingType::Uniform,
239 has_dynamic_offset: false,
240 min_binding_size: None,
241 },
242 count: None,
243 }],
244 label: Some("uniform_bind_group_layout"),
245 });
246 let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
247 layout: &uniform_bind_group_layout,
248 entries: &[wgpu::BindGroupEntry {
249 binding: 0,
250 resource: uniform_buffer.as_entire_binding(),
251 }],
252 label: Some("uniform_bind_group"),
253 });
254
255 let texture_bind_group_layout =
256 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
257 entries: &[
258 wgpu::BindGroupLayoutEntry {
260 binding: 0,
261 visibility: wgpu::ShaderStages::FRAGMENT,
262 ty: wgpu::BindingType::Texture {
263 multisampled: false,
264 view_dimension: wgpu::TextureViewDimension::D2,
265 sample_type: wgpu::TextureSampleType::Float { filterable: true },
266 },
267 count: None,
268 },
269 wgpu::BindGroupLayoutEntry {
271 binding: 1,
272 visibility: wgpu::ShaderStages::FRAGMENT,
273 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
276 count: None,
277 },
278 ],
279 label: Some("texture_bind_group_layout"),
280 });
281
282 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
283 label: None,
284 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(FILL_RECT_SHADER_SRC)),
285 });
286
287 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
288 label: None,
289 bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
290 push_constant_ranges: &[],
291 });
292
293 let swapchain_capabilities = surface.get_capabilities(&adapter);
294 let swapchain_format = swapchain_capabilities.formats[0];
295
296 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
297 label: None,
298 layout: Some(&pipeline_layout),
299 vertex: wgpu::VertexState {
300 module: &shader,
301 entry_point: Some("vs_main"),
302 compilation_options: Default::default(),
303 buffers: &[wgpu::VertexBufferLayout {
304 array_stride: std::mem::size_of::<DeviceVertex>() as wgpu::BufferAddress,
305 step_mode: wgpu::VertexStepMode::Vertex,
306 attributes: &[
307 wgpu::VertexAttribute {
309 format: wgpu::VertexFormat::Float32x2,
310 offset: 0,
311 shader_location: 0, },
313 wgpu::VertexAttribute {
315 format: wgpu::VertexFormat::Float32x2,
316 offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
317 shader_location: 1,
318 },
319 ],
320 }],
321 },
322 primitive: wgpu::PrimitiveState::default(),
323 depth_stencil: None,
324 multisample: wgpu::MultisampleState::default(),
325 fragment: Some(wgpu::FragmentState {
326 module: &shader,
327 entry_point: Some("fs_main"),
328 compilation_options: Default::default(),
329 targets: &[Some(wgpu::ColorTargetState {
330 format: swapchain_format,
331 blend: Some(wgpu::BlendState {
332 color: wgpu::BlendComponent {
333 src_factor: wgpu::BlendFactor::Src,
334 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
335 operation: wgpu::BlendOperation::Add,
336 },
337 alpha: wgpu::BlendComponent {
338 src_factor: wgpu::BlendFactor::One,
339 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
340 operation: wgpu::BlendOperation::Add,
341 },
342 }),
343 write_mask: wgpu::ColorWrites::ALL,
344 })],
345 }),
346 multiview: None,
347 cache: None,
348 });
349
350 let config = surface
351 .get_default_config(
352 &adapter,
353 viewport.device_width.value(),
354 viewport.device_height.value(),
355 )
356 .unwrap();
357 surface.configure(&device, &config);
358
359 Ok(WgpuApplication {
360 surface,
361 device,
362 queue,
363 config,
364 viewport,
365 render_pipeline,
366 uniform_buffer,
367 uniform_bind_group,
368 textures: HashMap::new(),
369 texture_bind_group_layout,
370 })
371 }
372
373 pub async fn from_window_handle<T: WindowHandle + 'a>(
374 window_handle: T,
375 viewport: Viewport,
376 ) -> WgpuApplicationResult<Self> {
377 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
378 backends: wgpu::Backends::all(),
379 ..Default::default()
380 });
381 let surface_target = wgpu::SurfaceTarget::Window(Box::new(window_handle));
382 let surface = match instance.create_surface(surface_target) {
383 Ok(s) => s,
384 Err(..) => return Err(WgpuApplicationError::CreateSurfaceError),
385 };
386
387 Self::init(instance, surface, viewport).await
388 }
389
390 fn draw_mesh(&mut self, mesh: Mesh, rpass: &mut wgpu::RenderPass<'_>) {
391 if mesh.vertices.is_empty() && mesh.indices.is_empty() {
392 return;
393 }
394 let device_mesh = if let Some(texture_id) = mesh.texture_id {
395 let texture = self.textures.get(&texture_id).expect("Texture not found");
396
397 DeviceMesh::from_mesh(mesh.clone(), texture.size)
398 } else {
399 DeviceMesh::from_mesh(mesh.clone(), PhysicalPixelsSize::new(1.into(), 1.into()))
400 };
401 self.draw_device_mesh(device_mesh, rpass);
402
403 for child in mesh.children {
404 self.draw_mesh(child, rpass);
405 }
406 }
407
408 fn draw_device_mesh(&mut self, mesh: DeviceMesh, rpass: &mut wgpu::RenderPass<'_>) {
410 if !mesh.vertices.is_empty() || !mesh.indices.is_empty() {
411 if let Some(texture_id) = mesh.texture_id {
412 let texture = self.textures.get(&texture_id).expect("Texture not found");
413
414 rpass.set_bind_group(1, &texture.bind_group, &[]);
415 }
416 let vertex_buffer = Self::create_vertex_buffer(&self.device, &mesh.vertices);
417 let index_buffer = Self::create_index_buffer(&self.device, &mesh.indices);
418 rpass.set_vertex_buffer(0, vertex_buffer.slice(..));
419 rpass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
420 rpass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
421 }
422 }
423
424 pub fn draw(&mut self, meshes: Vec<Mesh>) {
425 let frame = self
426 .surface
427 .get_current_texture()
428 .expect("Failed to acquire next swap chain texture");
429 let view = frame
430 .texture
431 .create_view(&wgpu::TextureViewDescriptor::default());
432 let mut encoder = self
433 .device
434 .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
435 {
436 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
437 label: None,
438 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
439 view: &view,
440 resolve_target: None,
441 ops: wgpu::Operations {
442 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
443 store: wgpu::StoreOp::Store,
444 },
445 })],
446 depth_stencil_attachment: None,
447 timestamp_writes: None,
448 occlusion_query_set: None,
449 });
450
451 rpass.set_pipeline(&self.render_pipeline);
452 rpass.set_bind_group(0, &self.uniform_bind_group, &[]);
453
454 for mesh in meshes {
455 self.draw_mesh(mesh, &mut rpass);
456 }
457 }
458
459 self.queue.submit(Some(encoder.finish()));
460 frame.present();
461 }
462
463 pub fn resize(&mut self, new_viewport: Viewport) {
464 if self.viewport != new_viewport {
465 self.viewport = new_viewport;
466 self.reconfigure_surface_size();
467 }
468 }
469
470 fn reconfigure_surface_size(&mut self) {
471 self.config.width = self.viewport.device_width.value();
472 self.config.height = self.viewport.device_height.value();
473 let ortho = Self::create_ortho_matrix(self.viewport);
474 let uniforms = Uniforms {
475 ortho: ortho.into(),
476 };
477 self.queue
478 .write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
479 self.surface.configure(&self.device, &self.config);
480 }
481
482 pub fn register_texture(&mut self, pixels: Vec<u8>, size: PhysicalPixelsSize) -> usize {
483 let id = self.textures.len();
484 self.register_texture_with_id(id, pixels, size)
485 }
486
487 pub fn register_texture_with_id(
488 &mut self,
489 id: usize,
490 pixels: Vec<u8>,
491 size: PhysicalPixelsSize,
492 ) -> usize {
493 let texture_size = wgpu::Extent3d {
494 width: size.width.value(),
495 height: size.height.value(),
496 depth_or_array_layers: 1,
497 };
498 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
499 size: texture_size,
500 mip_level_count: 1,
501 sample_count: 1,
502 dimension: wgpu::TextureDimension::D2,
503 format: wgpu::TextureFormat::Rgba8UnormSrgb,
504 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
505 label: Some(format!("texture_{}", id).as_str()),
506 view_formats: &[],
508 });
509
510 self.queue.write_texture(
511 wgpu::TexelCopyTextureInfo {
512 texture: &texture,
513 mip_level: 0,
514 origin: wgpu::Origin3d::ZERO,
515 aspect: wgpu::TextureAspect::All,
516 },
517 &pixels,
518 wgpu::TexelCopyBufferLayout {
519 offset: 0,
520 bytes_per_row: Some(4 * size.width.value()),
521 rows_per_image: Some(size.height.value()),
522 },
523 texture_size,
524 );
525
526 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
527
528 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
530 address_mode_u: wgpu::AddressMode::ClampToEdge,
531 address_mode_v: wgpu::AddressMode::ClampToEdge,
532 address_mode_w: wgpu::AddressMode::ClampToEdge,
533 mag_filter: wgpu::FilterMode::Linear,
534 min_filter: wgpu::FilterMode::Linear,
535 mipmap_filter: wgpu::FilterMode::Linear,
536 ..Default::default()
537 });
538
539 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
540 layout: &self.texture_bind_group_layout,
541 entries: &[
542 wgpu::BindGroupEntry {
543 binding: 0,
544 resource: wgpu::BindingResource::TextureView(&texture_view),
545 },
546 wgpu::BindGroupEntry {
547 binding: 1,
548 resource: wgpu::BindingResource::Sampler(&sampler),
549 },
550 ],
551 label: Some(format!("bind_group_{}", id).as_str()),
552 });
553
554 let texture = Texture {
555 native_texture: texture,
556 bind_group,
557 size,
558 };
559
560 self.textures.insert(id, texture);
561 id
562 }
563
564 }