sketchbook_wgpu/wgpu/
device.rs

1use winit::window::Window;
2
3#[derive(Debug)]
4pub struct WgpuSurface {
5    // the order of these fields is important for unsafe code
6    // the surface must be dropped first, then the window
7    surface: wgpu::Surface,
8    window: Window,
9}
10
11impl WgpuSurface {
12    pub fn for_window(instance: &wgpu::Instance, window: Window) -> Self {
13        let surface = unsafe { instance.create_surface(&window) };
14
15        Self { surface, window }
16    }
17
18    pub fn best_adapter<F, B>(self, instance: &wgpu::Instance, compare: F) -> Result<WgpuAdapter, WgpuSurface>
19    where
20        B: Ord,
21        F: Fn(&wgpu::Adapter) -> B,
22    {
23        if let Some(adapter) = instance.enumerate_adapters(wgpu::Backends::all()).filter(|adapter| {
24            adapter.is_surface_supported(&self.surface)
25        }).max_by_key(compare) {
26            Ok(WgpuAdapter {
27                adapter,
28                surface: self,
29            })
30        } else {
31            Err(self)
32        }
33    }
34
35    pub fn prefer_low_power(adapter: &wgpu::Adapter) -> u8 {
36        match adapter.get_info().device_type {
37            wgpu::DeviceType::IntegratedGpu => 5,
38            wgpu::DeviceType::VirtualGpu => 4,
39            wgpu::DeviceType::DiscreteGpu => 3,
40            wgpu::DeviceType::Cpu => 2,
41            wgpu::DeviceType::Other => 1,
42        }
43    }
44
45    pub fn prefer_high_power(adapter: &wgpu::Adapter) -> u8 {
46        match adapter.get_info().device_type {
47            wgpu::DeviceType::DiscreteGpu => 5,
48            wgpu::DeviceType::VirtualGpu => 4,
49            wgpu::DeviceType::IntegratedGpu => 3,
50            wgpu::DeviceType::Cpu => 2,
51            wgpu::DeviceType::Other => 1,
52        }
53    }
54}
55
56#[derive(Debug)]
57pub struct WgpuAdapter {
58    pub(crate) adapter: wgpu::Adapter,
59    surface: WgpuSurface,
60}
61
62impl WgpuAdapter {
63    pub async fn create_device(self) -> Result<WgpuDevice, (WgpuAdapter, wgpu::RequestDeviceError)> {
64        let result = self.adapter.request_device(
65            &wgpu::DeviceDescriptor {
66                features: wgpu::Features::empty(),
67                limits: wgpu::Limits::default(),
68                label: None,
69            },
70            None
71        ).await;
72
73        match result {
74            Ok((device, queue)) => {
75                let size = self.surface.window.inner_size();
76                if size.width == 0 || size.height == 0 {
77                    panic!("Window size is 0. Cannot create device.");
78                }
79
80                let config = wgpu::SurfaceConfiguration {
81                    usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
82                    format: self.surface.surface.get_preferred_format(&self.adapter).unwrap(),
83                    width: size.width,
84                    height: size.height,
85                    present_mode: wgpu::PresentMode::Fifo,
86                };
87
88                self.surface.surface.configure(&device, &config);
89
90                Ok(WgpuDevice {
91                    adapter: self,
92                    device,
93                    queue,
94                    config,
95                })
96            }
97            Err(error) => Err((self, error))
98        }
99    }
100}
101
102#[derive(Debug)]
103pub struct WgpuDevice {
104    pub device: wgpu::Device,
105    pub queue: wgpu::Queue,
106    adapter: WgpuAdapter,
107    config: wgpu::SurfaceConfiguration,
108}
109
110impl WgpuDevice {
111    pub fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
112        self.config.width = size.width;
113        self.config.height = size.height;
114        self.adapter.surface.surface.configure(&self.device, &self.config);
115    }
116
117    pub fn request_redraw(&self) {
118        self.adapter.surface.window.request_redraw()
119    }
120
121    pub fn inner_size(&self) -> winit::dpi::PhysicalSize<u32> {
122        self.adapter.surface.window.inner_size()
123    }
124
125    pub fn scale_factor(&self) -> f64 {
126        self.adapter.surface.window.scale_factor()
127    }
128
129    pub fn redraw<F>(&mut self, render: F) -> Result<(), wgpu::SurfaceError>
130    where
131        F: FnOnce(&wgpu::TextureView, &mut wgpu::CommandEncoder)
132    {
133        match self.adapter.surface.surface.get_current_texture() {
134            Ok(output) => {
135                let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
136
137                let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
138                    label: Some("Render Encoder"),
139                });
140
141                render(&view, &mut encoder);
142
143                // submit will accept anything that implements IntoIter
144                self.queue.submit(std::iter::once(encoder.finish()));
145                output.present();
146
147                Ok(())
148            }   
149            // Reconfigure the surface if lost
150            Err(err @ wgpu::SurfaceError::Lost) => {
151                // self.resize(self.inner_size());
152                Err(err)
153            },
154            Err(err @ wgpu::SurfaceError::Outdated) => {
155                // self.resize(self.inner_size());
156                Err(err)
157            },
158            // The system is out of memory, we should probably quit
159            Err(err @ wgpu::SurfaceError::OutOfMemory) => {
160                eprintln!("Out of memory. Unable to create render buffer.");
161                Err(err)
162            },
163            // All other errors (Outdated, Timeout) should be resolved by the next frame
164            Err(e) => {
165                eprintln!("{:?}", e);
166                Err(e)
167            }
168        }
169    }
170
171    pub fn create_pipeline(&mut self, shader_source: &str, bind_group_layouts: &[&wgpu::BindGroupLayout], buffers: &[wgpu::VertexBufferLayout]) -> wgpu::RenderPipeline {
172        let shader = self.device.create_shader_module(&wgpu::ShaderModuleDescriptor {
173            label: Some("Shader"),
174            source: wgpu::ShaderSource::Wgsl(shader_source.into()),
175        });
176
177        let render_pipeline_layout = self.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
178            label: Some("Render Pipeline Layout"),
179            bind_group_layouts,
180            push_constant_ranges: &[],
181        });
182
183        let render_pipeline = self.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
184            label: Some("Render Pipeline"),
185            layout: Some(&render_pipeline_layout),
186            vertex: wgpu::VertexState {
187                module: &shader,
188                entry_point: "v_main",
189                buffers, 
190            },
191            fragment: Some(wgpu::FragmentState { 
192                module: &shader,
193                entry_point: "f_main",
194                targets: &[wgpu::ColorTargetState { 
195                    format: self.config.format,
196                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
197                    write_mask: wgpu::ColorWrites::ALL,
198                }],
199            }),
200            primitive: wgpu::PrimitiveState {
201                topology: wgpu::PrimitiveTopology::TriangleStrip,
202                strip_index_format: None,
203                front_face: wgpu::FrontFace::Cw,
204                cull_mode: Some(wgpu::Face::Back),
205                polygon_mode: wgpu::PolygonMode::Fill,
206                unclipped_depth: false,
207                conservative: false,
208            },
209            depth_stencil: None,
210            multisample: wgpu::MultisampleState {
211                count: 1,
212                mask: !0,
213                alpha_to_coverage_enabled: false,
214            },
215            multiview: None,
216        });
217
218        render_pipeline
219    }
220}