use std::{iter, mem::size_of};
use bytemuck::{Pod, Zeroable};
use wgpu::util::DeviceExt;
use winit::{
event::{ElementState, KeyEvent, WindowEvent},
keyboard::{Key, NamedKey},
};
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct Vertex {
_pos: [f32; 2],
_color: [f32; 4],
}
struct Example {
bundle: wgpu::RenderBundle,
shader: wgpu::ShaderModule,
pipeline_layout: wgpu::PipelineLayout,
multisampled_framebuffer: wgpu::TextureView,
vertex_buffer: wgpu::Buffer,
vertex_count: u32,
sample_count: u32,
rebuild_bundle: bool,
config: wgpu::SurfaceConfiguration,
max_sample_count: u32,
}
impl Example {
fn create_bundle(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
shader: &wgpu::ShaderModule,
pipeline_layout: &wgpu::PipelineLayout,
sample_count: u32,
vertex_buffer: &wgpu::Buffer,
vertex_count: u32,
) -> wgpu::RenderBundle {
log::info!("sample_count: {}", sample_count);
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(pipeline_layout),
vertex: wgpu::VertexState {
module: shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x4],
}],
},
fragment: Some(wgpu::FragmentState {
module: shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(config.view_formats[0].into())],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::LineList,
front_face: wgpu::FrontFace::Ccw,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: sample_count,
..Default::default()
},
multiview: None,
cache: None,
});
let mut encoder =
device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
label: None,
color_formats: &[Some(config.view_formats[0])],
depth_stencil: None,
sample_count,
multiview: None,
});
encoder.set_pipeline(&pipeline);
encoder.set_vertex_buffer(0, vertex_buffer.slice(..));
encoder.draw(0..vertex_count, 0..1);
encoder.finish(&wgpu::RenderBundleDescriptor {
label: Some("main"),
})
}
fn create_multisampled_framebuffer(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
sample_count: u32,
) -> wgpu::TextureView {
let multisampled_texture_extent = wgpu::Extent3d {
width: config.width,
height: config.height,
depth_or_array_layers: 1,
};
let multisampled_frame_descriptor = &wgpu::TextureDescriptor {
size: multisampled_texture_extent,
mip_level_count: 1,
sample_count,
dimension: wgpu::TextureDimension::D2,
format: config.view_formats[0],
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
label: None,
view_formats: &[],
};
device
.create_texture(multisampled_frame_descriptor)
.create_view(&wgpu::TextureViewDescriptor::default())
}
}
impl crate::framework::Example for Example {
fn optional_features() -> wgpu::Features {
wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
}
fn init(
config: &wgpu::SurfaceConfiguration,
_adapter: &wgpu::Adapter,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> Self {
log::info!("Press left/right arrow keys to change sample_count.");
let sample_flags = _adapter
.get_texture_format_features(config.view_formats[0])
.flags;
let max_sample_count = {
if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X16) {
16
} else if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X8) {
8
} else if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X4) {
4
} else if sample_flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_X2) {
2
} else {
1
}
};
let sample_count = max_sample_count;
let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let multisampled_framebuffer =
Example::create_multisampled_framebuffer(device, config, sample_count);
let mut vertex_data = vec![];
let max = 50;
for i in 0..max {
let percent = i as f32 / max as f32;
let (sin, cos) = (percent * 2.0 * std::f32::consts::PI).sin_cos();
vertex_data.push(Vertex {
_pos: [0.0, 0.0],
_color: [1.0, -sin, cos, 1.0],
});
vertex_data.push(Vertex {
_pos: [1.0 * cos, 1.0 * sin],
_color: [sin, -cos, 1.0, 1.0],
});
}
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(&vertex_data),
usage: wgpu::BufferUsages::VERTEX,
});
let vertex_count = vertex_data.len() as u32;
let bundle = Example::create_bundle(
device,
config,
&shader,
&pipeline_layout,
sample_count,
&vertex_buffer,
vertex_count,
);
Example {
bundle,
shader,
pipeline_layout,
multisampled_framebuffer,
vertex_buffer,
vertex_count,
sample_count,
max_sample_count,
rebuild_bundle: false,
config: config.clone(),
}
}
#[allow(clippy::single_match)]
fn update(&mut self, event: winit::event::WindowEvent) {
match event {
WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key,
state: ElementState::Pressed,
..
},
..
} => match logical_key {
Key::Named(NamedKey::ArrowLeft) => {
if self.sample_count == self.max_sample_count {
self.sample_count = 1;
self.rebuild_bundle = true;
}
}
Key::Named(NamedKey::ArrowRight) => {
if self.sample_count == 1 {
self.sample_count = self.max_sample_count;
self.rebuild_bundle = true;
}
}
_ => {}
},
_ => {}
}
}
fn resize(
&mut self,
config: &wgpu::SurfaceConfiguration,
device: &wgpu::Device,
_queue: &wgpu::Queue,
) {
self.config = config.clone();
self.multisampled_framebuffer =
Example::create_multisampled_framebuffer(device, config, self.sample_count);
}
fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
if self.rebuild_bundle {
self.bundle = Example::create_bundle(
device,
&self.config,
&self.shader,
&self.pipeline_layout,
self.sample_count,
&self.vertex_buffer,
self.vertex_count,
);
self.multisampled_framebuffer =
Example::create_multisampled_framebuffer(device, &self.config, self.sample_count);
self.rebuild_bundle = false;
}
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let rpass_color_attachment = if self.sample_count == 1 {
wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
}
} else {
wgpu::RenderPassColorAttachment {
view: &self.multisampled_framebuffer,
resolve_target: Some(view),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Discard,
},
}
};
encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(rpass_color_attachment)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
})
.execute_bundles(iter::once(&self.bundle));
}
queue.submit(iter::once(encoder.finish()));
}
}
pub fn main() {
crate::framework::run::<Example>("msaa-line");
}
#[cfg(test)]
#[wgpu_test::gpu_test]
static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
name: "msaa-line",
image_path: "/examples/src/msaa_line/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
base_test_parameters: wgpu_test::TestParameters::default(),
comparisons: &[
wgpu_test::ComparisonType::Mean(0.065),
wgpu_test::ComparisonType::Percentile {
percentile: 0.5,
threshold: 0.29,
},
],
_phantom: std::marker::PhantomData::<Example>,
};