tx2_core/
renderer.rs

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}