wgpu_examples/texture_arrays/
mod.rs

1use bytemuck::{Pod, Zeroable};
2use std::num::{NonZeroU32, NonZeroU64};
3use wgpu::util::DeviceExt;
4
5#[repr(C)]
6#[derive(Clone, Copy, Pod, Zeroable)]
7struct Vertex {
8    _pos: [f32; 2],
9    _tex_coord: [f32; 2],
10    _index: u32,
11}
12
13fn vertex(pos: [i8; 2], tc: [i8; 2], index: i8) -> Vertex {
14    Vertex {
15        _pos: [pos[0] as f32, pos[1] as f32],
16        _tex_coord: [tc[0] as f32, tc[1] as f32],
17        _index: index as u32,
18    }
19}
20
21fn create_vertices() -> Vec<Vertex> {
22    vec![
23        // left rectangle
24        vertex([-1, -1], [0, 1], 0),
25        vertex([-1, 1], [0, 0], 0),
26        vertex([0, 1], [1, 0], 0),
27        vertex([0, -1], [1, 1], 0),
28        // right rectangle
29        vertex([0, -1], [0, 1], 1),
30        vertex([0, 1], [0, 0], 1),
31        vertex([1, 1], [1, 0], 1),
32        vertex([1, -1], [1, 1], 1),
33    ]
34}
35
36fn create_indices() -> Vec<u16> {
37    vec![
38        // Left rectangle
39        0, 1, 2, // 1st
40        2, 0, 3, // 2nd
41        // Right rectangle
42        4, 5, 6, // 1st
43        6, 4, 7, // 2nd
44    ]
45}
46
47#[derive(Copy, Clone)]
48enum Color {
49    Red,
50    Green,
51    Blue,
52    White,
53}
54
55fn create_texture_data(color: Color) -> [u8; 4] {
56    match color {
57        Color::Red => [255, 0, 0, 255],
58        Color::Green => [0, 255, 0, 255],
59        Color::Blue => [0, 0, 255, 255],
60        Color::White => [255, 255, 255, 255],
61    }
62}
63
64struct Example {
65    pipeline: wgpu::RenderPipeline,
66    bind_group: wgpu::BindGroup,
67    uniform_bind_group: wgpu::BindGroup,
68    vertex_buffer: wgpu::Buffer,
69    index_buffer: wgpu::Buffer,
70    index_format: wgpu::IndexFormat,
71    uniform_workaround: bool,
72}
73
74impl crate::framework::Example for Example {
75    fn optional_features() -> wgpu::Features {
76        wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
77    }
78    fn required_features() -> wgpu::Features {
79        wgpu::Features::TEXTURE_BINDING_ARRAY
80    }
81    fn required_limits() -> wgpu::Limits {
82        wgpu::Limits {
83            max_binding_array_elements_per_shader_stage: 6,
84            max_binding_array_sampler_elements_per_shader_stage: 2,
85            ..wgpu::Limits::downlevel_defaults()
86        }
87    }
88    fn init(
89        config: &wgpu::SurfaceConfiguration,
90        _adapter: &wgpu::Adapter,
91        device: &wgpu::Device,
92        queue: &wgpu::Queue,
93    ) -> Self {
94        let mut uniform_workaround = false;
95        let base_shader_module = device.create_shader_module(wgpu::include_wgsl!("indexing.wgsl"));
96        let env_override = match std::env::var("WGPU_TEXTURE_ARRAY_STYLE") {
97            Ok(value) => match &*value.to_lowercase() {
98                "nonuniform" | "non_uniform" => Some(true),
99                "uniform" => Some(false),
100                _ => None,
101            },
102            Err(_) => None,
103        };
104        let fragment_entry_point = match (device.features(), env_override) {
105            (_, Some(false)) => {
106                uniform_workaround = true;
107                "uniform_main"
108            }
109            (_, Some(true)) => "non_uniform_main",
110            (f, _)
111                if f.contains(
112                    wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
113                ) =>
114            {
115                "non_uniform_main"
116            }
117            _ => {
118                uniform_workaround = true;
119                "uniform_main"
120            }
121        };
122        let non_uniform_shader_module;
123        // TODO: Because naga's capabilities are evaluated on validate, not on write, we cannot make a shader module with unsupported
124        // capabilities even if we don't use it. So for now put it in a separate module.
125        let fragment_shader_module = if !uniform_workaround {
126            non_uniform_shader_module =
127                device.create_shader_module(wgpu::include_wgsl!("non_uniform_indexing.wgsl"));
128            &non_uniform_shader_module
129        } else {
130            &base_shader_module
131        };
132
133        println!("Using fragment entry point '{fragment_entry_point}'");
134
135        let vertex_size = size_of::<Vertex>();
136        let vertex_data = create_vertices();
137        let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
138            label: Some("Vertex Buffer"),
139            contents: bytemuck::cast_slice(&vertex_data),
140            usage: wgpu::BufferUsages::VERTEX,
141        });
142
143        let index_data = create_indices();
144        let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
145            label: Some("Index Buffer"),
146            contents: bytemuck::cast_slice(&index_data),
147            usage: wgpu::BufferUsages::INDEX,
148        });
149
150        let mut texture_index_buffer_contents = vec![0u32; 128];
151        texture_index_buffer_contents[0] = 0;
152        texture_index_buffer_contents[64] = 1;
153        let texture_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
154            label: Some("Index Buffer"),
155            contents: bytemuck::cast_slice(&texture_index_buffer_contents),
156            usage: wgpu::BufferUsages::UNIFORM,
157        });
158
159        let red_texture_data = create_texture_data(Color::Red);
160        let green_texture_data = create_texture_data(Color::Green);
161        let blue_texture_data = create_texture_data(Color::Blue);
162        let white_texture_data = create_texture_data(Color::White);
163
164        let texture_descriptor = wgpu::TextureDescriptor {
165            size: wgpu::Extent3d::default(),
166            mip_level_count: 1,
167            sample_count: 1,
168            dimension: wgpu::TextureDimension::D2,
169            format: wgpu::TextureFormat::Rgba8UnormSrgb,
170            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
171            label: None,
172            view_formats: &[],
173        };
174        let red_texture = device.create_texture(&wgpu::TextureDescriptor {
175            label: Some("red"),
176            view_formats: &[],
177            ..texture_descriptor
178        });
179        let green_texture = device.create_texture(&wgpu::TextureDescriptor {
180            label: Some("green"),
181            view_formats: &[],
182            ..texture_descriptor
183        });
184        let blue_texture = device.create_texture(&wgpu::TextureDescriptor {
185            label: Some("blue"),
186            view_formats: &[],
187            ..texture_descriptor
188        });
189        let white_texture = device.create_texture(&wgpu::TextureDescriptor {
190            label: Some("white"),
191            view_formats: &[],
192            ..texture_descriptor
193        });
194
195        let red_texture_view = red_texture.create_view(&wgpu::TextureViewDescriptor::default());
196        let green_texture_view = green_texture.create_view(&wgpu::TextureViewDescriptor::default());
197        let blue_texture_view = blue_texture.create_view(&wgpu::TextureViewDescriptor::default());
198        let white_texture_view = white_texture.create_view(&wgpu::TextureViewDescriptor::default());
199
200        queue.write_texture(
201            red_texture.as_image_copy(),
202            &red_texture_data,
203            wgpu::TexelCopyBufferLayout {
204                offset: 0,
205                bytes_per_row: Some(4),
206                rows_per_image: None,
207            },
208            wgpu::Extent3d::default(),
209        );
210        queue.write_texture(
211            green_texture.as_image_copy(),
212            &green_texture_data,
213            wgpu::TexelCopyBufferLayout {
214                offset: 0,
215                bytes_per_row: Some(4),
216                rows_per_image: None,
217            },
218            wgpu::Extent3d::default(),
219        );
220        queue.write_texture(
221            blue_texture.as_image_copy(),
222            &blue_texture_data,
223            wgpu::TexelCopyBufferLayout {
224                offset: 0,
225                bytes_per_row: Some(4),
226                rows_per_image: None,
227            },
228            wgpu::Extent3d::default(),
229        );
230        queue.write_texture(
231            white_texture.as_image_copy(),
232            &white_texture_data,
233            wgpu::TexelCopyBufferLayout {
234                offset: 0,
235                bytes_per_row: Some(4),
236                rows_per_image: None,
237            },
238            wgpu::Extent3d::default(),
239        );
240
241        let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());
242
243        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
244            label: Some("bind group layout"),
245            entries: &[
246                wgpu::BindGroupLayoutEntry {
247                    binding: 0,
248                    visibility: wgpu::ShaderStages::FRAGMENT,
249                    ty: wgpu::BindingType::Texture {
250                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
251                        view_dimension: wgpu::TextureViewDimension::D2,
252                        multisampled: false,
253                    },
254                    count: NonZeroU32::new(2),
255                },
256                wgpu::BindGroupLayoutEntry {
257                    binding: 1,
258                    visibility: wgpu::ShaderStages::FRAGMENT,
259                    ty: wgpu::BindingType::Texture {
260                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
261                        view_dimension: wgpu::TextureViewDimension::D2,
262                        multisampled: false,
263                    },
264                    count: NonZeroU32::new(2),
265                },
266                wgpu::BindGroupLayoutEntry {
267                    binding: 2,
268                    visibility: wgpu::ShaderStages::FRAGMENT,
269                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
270                    count: NonZeroU32::new(2),
271                },
272            ],
273        });
274
275        let uniform_bind_group_layout =
276            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
277                label: Some("uniform bind group layout"),
278                entries: &[wgpu::BindGroupLayoutEntry {
279                    binding: 0,
280                    visibility: wgpu::ShaderStages::FRAGMENT,
281                    ty: wgpu::BindingType::Buffer {
282                        ty: wgpu::BufferBindingType::Uniform,
283                        has_dynamic_offset: true,
284                        min_binding_size: Some(NonZeroU64::new(4).unwrap()),
285                    },
286                    count: None,
287                }],
288            });
289
290        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
291            entries: &[
292                wgpu::BindGroupEntry {
293                    binding: 0,
294                    resource: wgpu::BindingResource::TextureViewArray(&[
295                        &red_texture_view,
296                        &green_texture_view,
297                    ]),
298                },
299                wgpu::BindGroupEntry {
300                    binding: 1,
301                    resource: wgpu::BindingResource::TextureViewArray(&[
302                        &blue_texture_view,
303                        &white_texture_view,
304                    ]),
305                },
306                wgpu::BindGroupEntry {
307                    binding: 2,
308                    resource: wgpu::BindingResource::SamplerArray(&[&sampler, &sampler]),
309                },
310            ],
311            layout: &bind_group_layout,
312            label: Some("bind group"),
313        });
314
315        let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
316            entries: &[wgpu::BindGroupEntry {
317                binding: 0,
318                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
319                    buffer: &texture_index_buffer,
320                    offset: 0,
321                    size: Some(NonZeroU64::new(4).unwrap()),
322                }),
323            }],
324            layout: &uniform_bind_group_layout,
325            label: Some("uniform bind group"),
326        });
327
328        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
329            label: Some("main"),
330            bind_group_layouts: &[&bind_group_layout, &uniform_bind_group_layout],
331            push_constant_ranges: &[],
332        });
333
334        let index_format = wgpu::IndexFormat::Uint16;
335
336        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
337            label: None,
338            layout: Some(&pipeline_layout),
339            vertex: wgpu::VertexState {
340                module: &base_shader_module,
341                entry_point: Some("vert_main"),
342                compilation_options: Default::default(),
343                buffers: &[wgpu::VertexBufferLayout {
344                    array_stride: vertex_size as wgpu::BufferAddress,
345                    step_mode: wgpu::VertexStepMode::Vertex,
346                    attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Sint32],
347                }],
348            },
349            fragment: Some(wgpu::FragmentState {
350                module: fragment_shader_module,
351                entry_point: Some(fragment_entry_point),
352                compilation_options: Default::default(),
353                targets: &[Some(config.view_formats[0].into())],
354            }),
355            primitive: wgpu::PrimitiveState {
356                front_face: wgpu::FrontFace::Ccw,
357                ..Default::default()
358            },
359            depth_stencil: None,
360            multisample: wgpu::MultisampleState::default(),
361            multiview: None,
362            cache: None
363        });
364
365        Self {
366            pipeline,
367            bind_group,
368            uniform_bind_group,
369            vertex_buffer,
370            index_buffer,
371            index_format,
372            uniform_workaround,
373        }
374    }
375    fn resize(
376        &mut self,
377        _sc_desc: &wgpu::SurfaceConfiguration,
378        _device: &wgpu::Device,
379        _queue: &wgpu::Queue,
380    ) {
381        // noop
382    }
383    fn update(&mut self, _event: winit::event::WindowEvent) {
384        // noop
385    }
386    fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
387        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
388            label: Some("primary"),
389        });
390
391        let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
392            label: None,
393            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
394                view,
395                depth_slice: None,
396                resolve_target: None,
397                ops: wgpu::Operations {
398                    load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
399                    store: wgpu::StoreOp::Store,
400                },
401            })],
402            depth_stencil_attachment: None,
403            timestamp_writes: None,
404            occlusion_query_set: None,
405        });
406
407        rpass.set_pipeline(&self.pipeline);
408        rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
409        rpass.set_index_buffer(self.index_buffer.slice(..), self.index_format);
410        if self.uniform_workaround {
411            rpass.set_bind_group(0, &self.bind_group, &[]);
412            rpass.set_bind_group(1, &self.uniform_bind_group, &[0]);
413            rpass.draw_indexed(0..6, 0, 0..1);
414            rpass.set_bind_group(1, &self.uniform_bind_group, &[256]);
415            rpass.draw_indexed(6..12, 0, 0..1);
416        } else {
417            rpass.set_bind_group(0, &self.bind_group, &[]);
418            rpass.set_bind_group(1, &self.uniform_bind_group, &[0]);
419            rpass.draw_indexed(0..12, 0, 0..1);
420        }
421
422        drop(rpass);
423
424        queue.submit(Some(encoder.finish()));
425    }
426}
427
428pub fn main() {
429    crate::framework::run::<Example>("texture-arrays");
430}
431
432#[cfg(test)]
433#[wgpu_test::gpu_test]
434pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
435    name: "texture-arrays",
436    image_path: "/examples/features/src/texture_arrays/screenshot.png",
437    width: 1024,
438    height: 768,
439    optional_features: wgpu::Features::empty(),
440    base_test_parameters: wgpu_test::TestParameters::default(),
441    comparisons: &[wgpu_test::ComparisonType::Mean(0.0)],
442    _phantom: std::marker::PhantomData::<Example>,
443};
444
445#[cfg(test)]
446#[wgpu_test::gpu_test]
447pub static TEST_UNIFORM: crate::framework::ExampleTestParams =
448    crate::framework::ExampleTestParams {
449        name: "texture-arrays-uniform",
450        image_path: "/examples/features/src/texture_arrays/screenshot.png",
451        width: 1024,
452        height: 768,
453        optional_features: wgpu::Features::empty(),
454        base_test_parameters: wgpu_test::TestParameters::default(),
455        comparisons: &[wgpu_test::ComparisonType::Mean(0.0)],
456        _phantom: std::marker::PhantomData::<Example>,
457    };
458
459#[cfg(test)]
460#[wgpu_test::gpu_test]
461pub static TEST_NON_UNIFORM: crate::framework::ExampleTestParams =
462    crate::framework::ExampleTestParams {
463        name: "texture-arrays-non-uniform",
464        image_path: "/examples/features/src/texture_arrays/screenshot.png",
465        width: 1024,
466        height: 768,
467        optional_features:
468            wgpu::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
469        base_test_parameters: wgpu_test::TestParameters::default(),
470        comparisons: &[wgpu_test::ComparisonType::Mean(0.0)],
471        _phantom: std::marker::PhantomData::<Example>,
472    };