1use std::iter;
11
12use bytemuck::{Pod, Zeroable};
13use wgpu::util::DeviceExt;
14
15use winit::{
16 event::{ElementState, KeyEvent, WindowEvent},
17 keyboard::{Key, NamedKey},
18};
19
20#[repr(C)]
21#[derive(Clone, Copy, Pod, Zeroable)]
22struct Vertex {
23 _pos: [f32; 2],
24 _color: [f32; 4],
25}
26
27struct Example {
28 bundle: wgpu::RenderBundle,
29 shader: wgpu::ShaderModule,
30 pipeline_layout: wgpu::PipelineLayout,
31 multisampled_framebuffer: wgpu::TextureView,
32 vertex_buffer: wgpu::Buffer,
33 vertex_count: u32,
34 sample_count: u32,
35 rebuild_bundle: bool,
36 config: wgpu::SurfaceConfiguration,
37 max_sample_count: u32,
38}
39
40impl Example {
41 fn create_bundle(
42 device: &wgpu::Device,
43 config: &wgpu::SurfaceConfiguration,
44 shader: &wgpu::ShaderModule,
45 pipeline_layout: &wgpu::PipelineLayout,
46 sample_count: u32,
47 vertex_buffer: &wgpu::Buffer,
48 vertex_count: u32,
49 ) -> wgpu::RenderBundle {
50 log::info!("sample_count: {sample_count}");
51 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
52 label: None,
53 layout: Some(pipeline_layout),
54 vertex: wgpu::VertexState {
55 module: shader,
56 entry_point: Some("vs_main"),
57 compilation_options: Default::default(),
58 buffers: &[wgpu::VertexBufferLayout {
59 array_stride: size_of::<Vertex>() as wgpu::BufferAddress,
60 step_mode: wgpu::VertexStepMode::Vertex,
61 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x4],
62 }],
63 },
64 fragment: Some(wgpu::FragmentState {
65 module: shader,
66 entry_point: Some("fs_main"),
67 compilation_options: Default::default(),
68 targets: &[Some(config.view_formats[0].into())],
69 }),
70 primitive: wgpu::PrimitiveState {
71 topology: wgpu::PrimitiveTopology::LineList,
72 front_face: wgpu::FrontFace::Ccw,
73 ..Default::default()
74 },
75 depth_stencil: None,
76 multisample: wgpu::MultisampleState {
77 count: sample_count,
78 ..Default::default()
79 },
80 multiview: None,
81 cache: None,
82 });
83 let mut encoder =
84 device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
85 label: None,
86 color_formats: &[Some(config.view_formats[0])],
87 depth_stencil: None,
88 sample_count,
89 multiview: None,
90 });
91 encoder.set_pipeline(&pipeline);
92 encoder.set_vertex_buffer(0, vertex_buffer.slice(..));
93 encoder.draw(0..vertex_count, 0..1);
94 encoder.finish(&wgpu::RenderBundleDescriptor {
95 label: Some("main"),
96 })
97 }
98
99 fn create_multisampled_framebuffer(
100 device: &wgpu::Device,
101 config: &wgpu::SurfaceConfiguration,
102 sample_count: u32,
103 ) -> wgpu::TextureView {
104 let multisampled_texture_extent = wgpu::Extent3d {
105 width: config.width,
106 height: config.height,
107 depth_or_array_layers: 1,
108 };
109 let multisampled_frame_descriptor = &wgpu::TextureDescriptor {
110 size: multisampled_texture_extent,
111 mip_level_count: 1,
112 sample_count,
113 dimension: wgpu::TextureDimension::D2,
114 format: config.view_formats[0],
115 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
116 label: None,
117 view_formats: &[],
118 };
119
120 device
121 .create_texture(multisampled_frame_descriptor)
122 .create_view(&wgpu::TextureViewDescriptor::default())
123 }
124}
125
126impl crate::framework::Example for Example {
127 fn optional_features() -> wgpu::Features {
128 wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
129 }
130
131 fn init(
132 config: &wgpu::SurfaceConfiguration,
133 _adapter: &wgpu::Adapter,
134 device: &wgpu::Device,
135 _queue: &wgpu::Queue,
136 ) -> Self {
137 log::info!("Press left/right arrow keys to change sample_count.");
138
139 let sample_flags = _adapter
140 .get_texture_format_features(config.view_formats[0])
141 .flags;
142
143 let max_sample_count = {
144 if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X16) {
145 16
146 } else if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X8) {
147 8
148 } else if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X4) {
149 4
150 } else if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X2) {
151 2
152 } else {
153 1
154 }
155 };
156
157 let sample_count = max_sample_count;
158
159 let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
160
161 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
162 label: None,
163 bind_group_layouts: &[],
164 push_constant_ranges: &[],
165 });
166
167 let multisampled_framebuffer =
168 Example::create_multisampled_framebuffer(device, config, sample_count);
169
170 let mut vertex_data = vec![];
171
172 let max = 50;
173 for i in 0..max {
174 let percent = i as f32 / max as f32;
175 let (sin, cos) = (percent * 2.0 * std::f32::consts::PI).sin_cos();
176 vertex_data.push(Vertex {
177 _pos: [0.0, 0.0],
178 _color: [1.0, -sin, cos, 1.0],
179 });
180 vertex_data.push(Vertex {
181 _pos: [1.0 * cos, 1.0 * sin],
182 _color: [sin, -cos, 1.0, 1.0],
183 });
184 }
185
186 let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
187 label: Some("Vertex Buffer"),
188 contents: bytemuck::cast_slice(&vertex_data),
189 usage: wgpu::BufferUsages::VERTEX,
190 });
191 let vertex_count = vertex_data.len() as u32;
192
193 let bundle = Example::create_bundle(
194 device,
195 config,
196 &shader,
197 &pipeline_layout,
198 sample_count,
199 &vertex_buffer,
200 vertex_count,
201 );
202
203 Example {
204 bundle,
205 shader,
206 pipeline_layout,
207 multisampled_framebuffer,
208 vertex_buffer,
209 vertex_count,
210 sample_count,
211 max_sample_count,
212 rebuild_bundle: false,
213 config: config.clone(),
214 }
215 }
216
217 #[expect(clippy::single_match)]
218 fn update(&mut self, event: winit::event::WindowEvent) {
219 match event {
220 WindowEvent::KeyboardInput {
221 event:
222 KeyEvent {
223 logical_key,
224 state: ElementState::Pressed,
225 ..
226 },
227 ..
228 } => match logical_key {
229 Key::Named(NamedKey::ArrowLeft) => {
232 if self.sample_count == self.max_sample_count {
233 self.sample_count = 1;
234 self.rebuild_bundle = true;
235 }
236 }
237 Key::Named(NamedKey::ArrowRight) => {
238 if self.sample_count == 1 {
239 self.sample_count = self.max_sample_count;
240 self.rebuild_bundle = true;
241 }
242 }
243 _ => {}
244 },
245 _ => {}
246 }
247 }
248
249 fn resize(
250 &mut self,
251 config: &wgpu::SurfaceConfiguration,
252 device: &wgpu::Device,
253 _queue: &wgpu::Queue,
254 ) {
255 self.config = config.clone();
256 self.multisampled_framebuffer =
257 Example::create_multisampled_framebuffer(device, config, self.sample_count);
258 }
259
260 fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
261 if self.rebuild_bundle {
262 self.bundle = Example::create_bundle(
263 device,
264 &self.config,
265 &self.shader,
266 &self.pipeline_layout,
267 self.sample_count,
268 &self.vertex_buffer,
269 self.vertex_count,
270 );
271 self.multisampled_framebuffer =
272 Example::create_multisampled_framebuffer(device, &self.config, self.sample_count);
273 self.rebuild_bundle = false;
274 }
275
276 let mut encoder =
277 device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
278 {
279 let rpass_color_attachment = if self.sample_count == 1 {
280 wgpu::RenderPassColorAttachment {
281 view,
282 depth_slice: None,
283 resolve_target: None,
284 ops: wgpu::Operations {
285 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
286 store: wgpu::StoreOp::Store,
287 },
288 }
289 } else {
290 wgpu::RenderPassColorAttachment {
291 view: &self.multisampled_framebuffer,
292 depth_slice: None,
293 resolve_target: Some(view),
294 ops: wgpu::Operations {
295 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
296 store: wgpu::StoreOp::Discard,
299 },
300 }
301 };
302
303 encoder
304 .begin_render_pass(&wgpu::RenderPassDescriptor {
305 label: None,
306 color_attachments: &[Some(rpass_color_attachment)],
307 depth_stencil_attachment: None,
308 timestamp_writes: None,
309 occlusion_query_set: None,
310 })
311 .execute_bundles(iter::once(&self.bundle));
312 }
313
314 queue.submit(iter::once(encoder.finish()));
315 }
316}
317
318pub fn main() {
319 crate::framework::run::<Example>("msaa-line");
320}
321
322#[cfg(test)]
323#[wgpu_test::gpu_test]
324pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
325 name: "msaa-line",
326 image_path: "/examples/features/src/msaa_line/screenshot.png",
327 width: 1024,
328 height: 768,
329 optional_features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
330 base_test_parameters: wgpu_test::TestParameters::default(),
331 comparisons: &[
334 wgpu_test::ComparisonType::Mean(0.065),
335 wgpu_test::ComparisonType::Percentile {
336 percentile: 0.5,
337 threshold: 0.29,
338 },
339 ],
340 _phantom: std::marker::PhantomData::<Example>,
341};