1use winit::window::Window;
2use wgpu::util::DeviceExt;
3
4#[repr(C)]
5#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
6struct Vertex {
7 position: [f32; 3],
8 tex_coords: [f32; 2],
9}
10
11const VERTICES: &[Vertex] = &[
12 Vertex { position: [-1.0, 1.0, 0.0], tex_coords: [0.0, 0.0] },
13 Vertex { position: [-1.0, -1.0, 0.0], tex_coords: [0.0, 1.0] },
14 Vertex { position: [1.0, -1.0, 0.0], tex_coords: [1.0, 1.0] },
15 Vertex { position: [1.0, 1.0, 0.0], tex_coords: [1.0, 0.0] },
16];
17
18const INDICES: &[u16] = &[
19 0, 1, 2,
20 0, 2, 3,
21];
22
23const SHADER: &str = r#"
24struct VertexInput {
25 @location(0) position: vec3<f32>,
26 @location(1) tex_coords: vec2<f32>,
27}
28
29struct VertexOutput {
30 @builtin(position) clip_position: vec4<f32>,
31 @location(0) tex_coords: vec2<f32>,
32}
33
34@vertex
35fn vs_main(model: VertexInput) -> VertexOutput {
36 var out: VertexOutput;
37 out.tex_coords = model.tex_coords;
38 out.clip_position = vec4<f32>(model.position, 1.0);
39 return out;
40}
41
42@group(0) @binding(0)
43var t_diffuse: texture_2d<f32>;
44@group(0) @binding(1)
45var s_diffuse: sampler;
46
47@fragment
48fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
49 return textureSample(t_diffuse, s_diffuse, in.tex_coords);
50}
51"#;
52
53pub struct Renderer {
54 surface: wgpu::Surface<'static>,
55 device: wgpu::Device,
56 queue: wgpu::Queue,
57 config: wgpu::SurfaceConfiguration,
58 pub size: winit::dpi::PhysicalSize<u32>,
59 render_pipeline: wgpu::RenderPipeline,
60 vertex_buffer: wgpu::Buffer,
61 index_buffer: wgpu::Buffer,
62 num_indices: u32,
63 diffuse_bind_group: Option<wgpu::BindGroup>,
64 bind_group_layout: wgpu::BindGroupLayout,
65}
66
67impl Renderer {
68 pub async fn new(window: &Window) -> Self {
69 let size = window.inner_size();
70
71 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
72 backends: wgpu::Backends::all(),
73 ..Default::default()
74 });
75
76 let surface = unsafe {
77 let surface = instance.create_surface(window).unwrap();
78 std::mem::transmute::<wgpu::Surface<'_>, wgpu::Surface<'static>>(surface)
79 };
80
81 let adapter = instance.request_adapter(
82 &wgpu::RequestAdapterOptions {
83 power_preference: wgpu::PowerPreference::default(),
84 compatible_surface: Some(&surface),
85 force_fallback_adapter: false,
86 },
87 ).await.unwrap();
88
89 let (device, queue) = adapter.request_device(
90 &wgpu::DeviceDescriptor {
91 required_features: wgpu::Features::empty(),
92 required_limits: wgpu::Limits::default(),
93 label: None,
94 },
95 None,
96 ).await.unwrap();
97
98 let surface_caps = surface.get_capabilities(&adapter);
99 let surface_format = surface_caps.formats.iter()
100 .copied()
101 .filter(|f| f.is_srgb())
102 .next()
103 .unwrap_or(surface_caps.formats[0]);
104
105 let config = wgpu::SurfaceConfiguration {
106 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
107 format: surface_format,
108 width: size.width,
109 height: size.height,
110 present_mode: surface_caps.present_modes[0],
111 alpha_mode: surface_caps.alpha_modes[0],
112 view_formats: vec![],
113 desired_maximum_frame_latency: 2,
114 };
115
116 surface.configure(&device, &config);
117
118 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
119 label: Some("Shader"),
120 source: wgpu::ShaderSource::Wgsl(SHADER.into()),
121 });
122
123 let texture_bind_group_layout =
124 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
125 entries: &[
126 wgpu::BindGroupLayoutEntry {
127 binding: 0,
128 visibility: wgpu::ShaderStages::FRAGMENT,
129 ty: wgpu::BindingType::Texture {
130 multisampled: false,
131 view_dimension: wgpu::TextureViewDimension::D2,
132 sample_type: wgpu::TextureSampleType::Float { filterable: true },
133 },
134 count: None,
135 },
136 wgpu::BindGroupLayoutEntry {
137 binding: 1,
138 visibility: wgpu::ShaderStages::FRAGMENT,
139 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
140 count: None,
141 },
142 ],
143 label: Some("texture_bind_group_layout"),
144 });
145
146 let render_pipeline_layout =
147 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
148 label: Some("Render Pipeline Layout"),
149 bind_group_layouts: &[&texture_bind_group_layout],
150 push_constant_ranges: &[],
151 });
152
153 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
154 label: Some("Render Pipeline"),
155 layout: Some(&render_pipeline_layout),
156 vertex: wgpu::VertexState {
157 module: &shader,
158 entry_point: "vs_main",
159 buffers: &[wgpu::VertexBufferLayout {
160 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
161 step_mode: wgpu::VertexStepMode::Vertex,
162 attributes: &[
163 wgpu::VertexAttribute {
164 offset: 0,
165 shader_location: 0,
166 format: wgpu::VertexFormat::Float32x3,
167 },
168 wgpu::VertexAttribute {
169 offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
170 shader_location: 1,
171 format: wgpu::VertexFormat::Float32x2,
172 },
173 ],
174 }],
175 },
176 fragment: Some(wgpu::FragmentState {
177 module: &shader,
178 entry_point: "fs_main",
179 targets: &[Some(wgpu::ColorTargetState {
180 format: config.format,
181 blend: Some(wgpu::BlendState::REPLACE),
182 write_mask: wgpu::ColorWrites::ALL,
183 })],
184 }),
185 primitive: wgpu::PrimitiveState {
186 topology: wgpu::PrimitiveTopology::TriangleList,
187 strip_index_format: None,
188 front_face: wgpu::FrontFace::Ccw,
189 cull_mode: Some(wgpu::Face::Back),
190 polygon_mode: wgpu::PolygonMode::Fill,
191 unclipped_depth: false,
192 conservative: false,
193 },
194 depth_stencil: None,
195 multisample: wgpu::MultisampleState {
196 count: 1,
197 mask: !0,
198 alpha_to_coverage_enabled: false,
199 },
200 multiview: None,
201 });
202
203 let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
204 label: Some("Vertex Buffer"),
205 contents: bytemuck::cast_slice(VERTICES),
206 usage: wgpu::BufferUsages::VERTEX,
207 });
208
209 let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
210 label: Some("Index Buffer"),
211 contents: bytemuck::cast_slice(INDICES),
212 usage: wgpu::BufferUsages::INDEX,
213 });
214
215 Self {
216 surface,
217 device,
218 queue,
219 config,
220 size,
221 render_pipeline,
222 vertex_buffer,
223 index_buffer,
224 num_indices: INDICES.len() as u32,
225 diffuse_bind_group: None,
226 bind_group_layout: texture_bind_group_layout,
227 }
228 }
229
230 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
231 if new_size.width > 0 && new_size.height > 0 {
232 self.size = new_size;
233 self.config.width = new_size.width;
234 self.config.height = new_size.height;
235 self.surface.configure(&self.device, &self.config);
236 }
237 }
238
239 pub fn set_image(&mut self, width: u32, height: u32, data: &[u8]) {
240 let texture_size = wgpu::Extent3d {
241 width,
242 height,
243 depth_or_array_layers: 1,
244 };
245
246 let texture = self.device.create_texture(&wgpu::TextureDescriptor {
247 size: texture_size,
248 mip_level_count: 1,
249 sample_count: 1,
250 dimension: wgpu::TextureDimension::D2,
251 format: wgpu::TextureFormat::Rgba8UnormSrgb,
252 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
253 label: Some("diffuse_texture"),
254 view_formats: &[],
255 });
256
257 self.queue.write_texture(
258 wgpu::ImageCopyTexture {
259 texture: &texture,
260 mip_level: 0,
261 origin: wgpu::Origin3d::ZERO,
262 aspect: wgpu::TextureAspect::All,
263 },
264 data,
265 wgpu::ImageDataLayout {
266 offset: 0,
267 bytes_per_row: Some(4 * width),
268 rows_per_image: Some(height),
269 },
270 texture_size,
271 );
272
273 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
274 let sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
275 address_mode_u: wgpu::AddressMode::ClampToEdge,
276 address_mode_v: wgpu::AddressMode::ClampToEdge,
277 address_mode_w: wgpu::AddressMode::ClampToEdge,
278 mag_filter: wgpu::FilterMode::Linear,
279 min_filter: wgpu::FilterMode::Nearest,
280 mipmap_filter: wgpu::FilterMode::Nearest,
281 ..Default::default()
282 });
283
284 let diffuse_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
285 layout: &self.bind_group_layout,
286 entries: &[
287 wgpu::BindGroupEntry {
288 binding: 0,
289 resource: wgpu::BindingResource::TextureView(&texture_view),
290 },
291 wgpu::BindGroupEntry {
292 binding: 1,
293 resource: wgpu::BindingResource::Sampler(&sampler),
294 },
295 ],
296 label: Some("diffuse_bind_group"),
297 });
298
299 self.diffuse_bind_group = Some(diffuse_bind_group);
300 }
301
302 pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
303 let output = self.surface.get_current_texture()?;
304 let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
305
306 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
307 label: Some("Render Encoder"),
308 });
309
310 {
311 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
312 label: Some("Render Pass"),
313 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
314 view: &view,
315 resolve_target: None,
316 ops: wgpu::Operations {
317 load: wgpu::LoadOp::Clear(wgpu::Color {
318 r: 0.1,
319 g: 0.2,
320 b: 0.3,
321 a: 1.0,
322 }),
323 store: wgpu::StoreOp::Store,
324 },
325 })],
326 depth_stencil_attachment: None,
327 timestamp_writes: None,
328 occlusion_query_set: None,
329 });
330
331 if let Some(bind_group) = &self.diffuse_bind_group {
332 render_pass.set_pipeline(&self.render_pipeline);
333 render_pass.set_bind_group(0, bind_group, &[]);
334 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
335 render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
336 render_pass.draw_indexed(0..self.num_indices, 0, 0..1);
337 }
338 }
339
340 self.queue.submit(std::iter::once(encoder.finish()));
341 output.present();
342
343 Ok(())
344 }
345}