wgpu_examples/cube/
mod.rs

1use bytemuck::{Pod, Zeroable};
2use std::f32::consts;
3use wgpu::util::DeviceExt;
4
5#[repr(C)]
6#[derive(Clone, Copy, Pod, Zeroable)]
7struct Vertex {
8    _pos: [f32; 4],
9    _tex_coord: [f32; 2],
10}
11
12fn vertex(pos: [i8; 3], tc: [i8; 2]) -> Vertex {
13    Vertex {
14        _pos: [pos[0] as f32, pos[1] as f32, pos[2] as f32, 1.0],
15        _tex_coord: [tc[0] as f32, tc[1] as f32],
16    }
17}
18
19fn create_vertices() -> (Vec<Vertex>, Vec<u16>) {
20    let vertex_data = [
21        // top (0, 0, 1)
22        vertex([-1, -1, 1], [0, 0]),
23        vertex([1, -1, 1], [1, 0]),
24        vertex([1, 1, 1], [1, 1]),
25        vertex([-1, 1, 1], [0, 1]),
26        // bottom (0, 0, -1)
27        vertex([-1, 1, -1], [1, 0]),
28        vertex([1, 1, -1], [0, 0]),
29        vertex([1, -1, -1], [0, 1]),
30        vertex([-1, -1, -1], [1, 1]),
31        // right (1, 0, 0)
32        vertex([1, -1, -1], [0, 0]),
33        vertex([1, 1, -1], [1, 0]),
34        vertex([1, 1, 1], [1, 1]),
35        vertex([1, -1, 1], [0, 1]),
36        // left (-1, 0, 0)
37        vertex([-1, -1, 1], [1, 0]),
38        vertex([-1, 1, 1], [0, 0]),
39        vertex([-1, 1, -1], [0, 1]),
40        vertex([-1, -1, -1], [1, 1]),
41        // front (0, 1, 0)
42        vertex([1, 1, -1], [1, 0]),
43        vertex([-1, 1, -1], [0, 0]),
44        vertex([-1, 1, 1], [0, 1]),
45        vertex([1, 1, 1], [1, 1]),
46        // back (0, -1, 0)
47        vertex([1, -1, 1], [0, 0]),
48        vertex([-1, -1, 1], [1, 0]),
49        vertex([-1, -1, -1], [1, 1]),
50        vertex([1, -1, -1], [0, 1]),
51    ];
52
53    let index_data: &[u16] = &[
54        0, 1, 2, 2, 3, 0, // top
55        4, 5, 6, 6, 7, 4, // bottom
56        8, 9, 10, 10, 11, 8, // right
57        12, 13, 14, 14, 15, 12, // left
58        16, 17, 18, 18, 19, 16, // front
59        20, 21, 22, 22, 23, 20, // back
60    ];
61
62    (vertex_data.to_vec(), index_data.to_vec())
63}
64
65fn create_texels(size: usize) -> Vec<u8> {
66    (0..size * size)
67        .map(|id| {
68            // get high five for recognizing this ;)
69            let cx = 3.0 * (id % size) as f32 / (size - 1) as f32 - 2.0;
70            let cy = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0;
71            let (mut x, mut y, mut count) = (cx, cy, 0);
72            while count < 0xFF && x * x + y * y < 4.0 {
73                let old_x = x;
74                x = x * x - y * y + cx;
75                y = 2.0 * old_x * y + cy;
76                count += 1;
77            }
78            count
79        })
80        .collect()
81}
82
83struct Example {
84    vertex_buf: wgpu::Buffer,
85    index_buf: wgpu::Buffer,
86    index_count: usize,
87    bind_group: wgpu::BindGroup,
88    uniform_buf: wgpu::Buffer,
89    pipeline: wgpu::RenderPipeline,
90    pipeline_wire: Option<wgpu::RenderPipeline>,
91}
92
93impl Example {
94    fn generate_matrix(aspect_ratio: f32) -> glam::Mat4 {
95        let projection = glam::Mat4::perspective_rh(consts::FRAC_PI_4, aspect_ratio, 1.0, 10.0);
96        let view = glam::Mat4::look_at_rh(
97            glam::Vec3::new(1.5f32, -5.0, 3.0),
98            glam::Vec3::ZERO,
99            glam::Vec3::Z,
100        );
101        projection * view
102    }
103}
104
105impl crate::framework::Example for Example {
106    fn optional_features() -> wgpu::Features {
107        wgpu::Features::POLYGON_MODE_LINE
108    }
109
110    fn init(
111        config: &wgpu::SurfaceConfiguration,
112        _adapter: &wgpu::Adapter,
113        device: &wgpu::Device,
114        queue: &wgpu::Queue,
115    ) -> Self {
116        // Create the vertex and index buffers
117        let vertex_size = size_of::<Vertex>();
118        let (vertex_data, index_data) = create_vertices();
119
120        let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
121            label: Some("Vertex Buffer"),
122            contents: bytemuck::cast_slice(&vertex_data),
123            usage: wgpu::BufferUsages::VERTEX,
124        });
125
126        let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
127            label: Some("Index Buffer"),
128            contents: bytemuck::cast_slice(&index_data),
129            usage: wgpu::BufferUsages::INDEX,
130        });
131
132        // Create pipeline layout
133        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
134            label: None,
135            entries: &[
136                wgpu::BindGroupLayoutEntry {
137                    binding: 0,
138                    visibility: wgpu::ShaderStages::VERTEX,
139                    ty: wgpu::BindingType::Buffer {
140                        ty: wgpu::BufferBindingType::Uniform,
141                        has_dynamic_offset: false,
142                        min_binding_size: wgpu::BufferSize::new(64),
143                    },
144                    count: None,
145                },
146                wgpu::BindGroupLayoutEntry {
147                    binding: 1,
148                    visibility: wgpu::ShaderStages::FRAGMENT,
149                    ty: wgpu::BindingType::Texture {
150                        multisampled: false,
151                        sample_type: wgpu::TextureSampleType::Uint,
152                        view_dimension: wgpu::TextureViewDimension::D2,
153                    },
154                    count: None,
155                },
156            ],
157        });
158        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
159            label: None,
160            bind_group_layouts: &[&bind_group_layout],
161            push_constant_ranges: &[],
162        });
163
164        // Create the texture
165        let size = 256u32;
166        let texels = create_texels(size as usize);
167        let texture_extent = wgpu::Extent3d {
168            width: size,
169            height: size,
170            depth_or_array_layers: 1,
171        };
172        let texture = device.create_texture(&wgpu::TextureDescriptor {
173            label: None,
174            size: texture_extent,
175            mip_level_count: 1,
176            sample_count: 1,
177            dimension: wgpu::TextureDimension::D2,
178            format: wgpu::TextureFormat::R8Uint,
179            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
180            view_formats: &[],
181        });
182        let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
183        queue.write_texture(
184            texture.as_image_copy(),
185            &texels,
186            wgpu::TexelCopyBufferLayout {
187                offset: 0,
188                bytes_per_row: Some(size),
189                rows_per_image: None,
190            },
191            texture_extent,
192        );
193
194        // Create other resources
195        let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32);
196        let mx_ref: &[f32; 16] = mx_total.as_ref();
197        let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
198            label: Some("Uniform Buffer"),
199            contents: bytemuck::cast_slice(mx_ref),
200            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
201        });
202
203        // Create bind group
204        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
205            layout: &bind_group_layout,
206            entries: &[
207                wgpu::BindGroupEntry {
208                    binding: 0,
209                    resource: uniform_buf.as_entire_binding(),
210                },
211                wgpu::BindGroupEntry {
212                    binding: 1,
213                    resource: wgpu::BindingResource::TextureView(&texture_view),
214                },
215            ],
216            label: None,
217        });
218
219        let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
220
221        let vertex_buffers = [wgpu::VertexBufferLayout {
222            array_stride: vertex_size as wgpu::BufferAddress,
223            step_mode: wgpu::VertexStepMode::Vertex,
224            attributes: &[
225                wgpu::VertexAttribute {
226                    format: wgpu::VertexFormat::Float32x4,
227                    offset: 0,
228                    shader_location: 0,
229                },
230                wgpu::VertexAttribute {
231                    format: wgpu::VertexFormat::Float32x2,
232                    offset: 4 * 4,
233                    shader_location: 1,
234                },
235            ],
236        }];
237
238        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
239            label: None,
240            layout: Some(&pipeline_layout),
241            vertex: wgpu::VertexState {
242                module: &shader,
243                entry_point: Some("vs_main"),
244                compilation_options: Default::default(),
245                buffers: &vertex_buffers,
246            },
247            fragment: Some(wgpu::FragmentState {
248                module: &shader,
249                entry_point: Some("fs_main"),
250                compilation_options: Default::default(),
251                targets: &[Some(config.view_formats[0].into())],
252            }),
253            primitive: wgpu::PrimitiveState {
254                cull_mode: Some(wgpu::Face::Back),
255                ..Default::default()
256            },
257            depth_stencil: None,
258            multisample: wgpu::MultisampleState::default(),
259            multiview: None,
260            cache: None,
261        });
262
263        let pipeline_wire = if device
264            .features()
265            .contains(wgpu::Features::POLYGON_MODE_LINE)
266        {
267            let pipeline_wire = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
268                label: None,
269                layout: Some(&pipeline_layout),
270                vertex: wgpu::VertexState {
271                    module: &shader,
272                    entry_point: Some("vs_main"),
273                    compilation_options: Default::default(),
274                    buffers: &vertex_buffers,
275                },
276                fragment: Some(wgpu::FragmentState {
277                    module: &shader,
278                    entry_point: Some("fs_wire"),
279                    compilation_options: Default::default(),
280                    targets: &[Some(wgpu::ColorTargetState {
281                        format: config.view_formats[0],
282                        blend: Some(wgpu::BlendState {
283                            color: wgpu::BlendComponent {
284                                operation: wgpu::BlendOperation::Add,
285                                src_factor: wgpu::BlendFactor::SrcAlpha,
286                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
287                            },
288                            alpha: wgpu::BlendComponent::REPLACE,
289                        }),
290                        write_mask: wgpu::ColorWrites::ALL,
291                    })],
292                }),
293                primitive: wgpu::PrimitiveState {
294                    front_face: wgpu::FrontFace::Ccw,
295                    cull_mode: Some(wgpu::Face::Back),
296                    polygon_mode: wgpu::PolygonMode::Line,
297                    ..Default::default()
298                },
299                depth_stencil: None,
300                multisample: wgpu::MultisampleState::default(),
301                multiview: None,
302                cache: None,
303            });
304            Some(pipeline_wire)
305        } else {
306            None
307        };
308
309        // Done
310        Example {
311            vertex_buf,
312            index_buf,
313            index_count: index_data.len(),
314            bind_group,
315            uniform_buf,
316            pipeline,
317            pipeline_wire,
318        }
319    }
320
321    fn update(&mut self, _event: winit::event::WindowEvent) {
322        //empty
323    }
324
325    fn resize(
326        &mut self,
327        config: &wgpu::SurfaceConfiguration,
328        _device: &wgpu::Device,
329        queue: &wgpu::Queue,
330    ) {
331        let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32);
332        let mx_ref: &[f32; 16] = mx_total.as_ref();
333        queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref));
334    }
335
336    fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
337        let mut encoder =
338            device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
339        {
340            let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
341                label: None,
342                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
343                    view,
344                    depth_slice: None,
345                    resolve_target: None,
346                    ops: wgpu::Operations {
347                        load: wgpu::LoadOp::Clear(wgpu::Color {
348                            r: 0.1,
349                            g: 0.2,
350                            b: 0.3,
351                            a: 1.0,
352                        }),
353                        store: wgpu::StoreOp::Store,
354                    },
355                })],
356                depth_stencil_attachment: None,
357                timestamp_writes: None,
358                occlusion_query_set: None,
359            });
360            rpass.push_debug_group("Prepare data for draw.");
361            rpass.set_pipeline(&self.pipeline);
362            rpass.set_bind_group(0, &self.bind_group, &[]);
363            rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint16);
364            rpass.set_vertex_buffer(0, self.vertex_buf.slice(..));
365            rpass.pop_debug_group();
366            rpass.insert_debug_marker("Draw!");
367            rpass.draw_indexed(0..self.index_count as u32, 0, 0..1);
368            if let Some(ref pipe) = self.pipeline_wire {
369                rpass.set_pipeline(pipe);
370                rpass.draw_indexed(0..self.index_count as u32, 0, 0..1);
371            }
372        }
373
374        queue.submit(Some(encoder.finish()));
375    }
376}
377
378pub fn main() {
379    crate::framework::run::<Example>("cube");
380}
381
382#[cfg(test)]
383#[wgpu_test::gpu_test]
384pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
385    name: "cube",
386    // Generated on 1080ti on Vk/Windows
387    image_path: "/examples/features/src/cube/screenshot.png",
388    width: 1024,
389    height: 768,
390    optional_features: wgpu::Features::default(),
391    base_test_parameters: wgpu_test::TestParameters::default(),
392    comparisons: &[
393        wgpu_test::ComparisonType::Mean(0.04), // Bounded by Intel 630 on Vk/Windows
394    ],
395    _phantom: std::marker::PhantomData::<Example>,
396};
397
398#[cfg(test)]
399#[wgpu_test::gpu_test]
400pub static TEST_LINES: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
401    name: "cube-lines",
402    // Generated on 1080ti on Vk/Windows
403    image_path: "/examples/features/src/cube/screenshot-lines.png",
404    width: 1024,
405    height: 768,
406    optional_features: wgpu::Features::POLYGON_MODE_LINE,
407    base_test_parameters: wgpu_test::TestParameters::default(),
408    // We're looking for tiny changes here, so we focus on a spike in the 95th percentile.
409    comparisons: &[
410        wgpu_test::ComparisonType::Mean(0.05), // Bounded by Intel 630 on Vk/Windows
411        wgpu_test::ComparisonType::Percentile {
412            percentile: 0.95,
413            threshold: 0.36,
414        }, // Bounded by 1080ti on DX12
415    ],
416    _phantom: std::marker::PhantomData::<Example>,
417};