mirror of
https://github.com/gfx-rs/wgpu.git
synced 2025-12-08 21:26:17 +00:00
Co-authored-by: Andreas Reich <r_andreas2@web.de> Co-authored-by: Magnus <85136135+SupaMaggie70Incorporated@users.noreply.github.com>
343 lines
12 KiB
Rust
343 lines
12 KiB
Rust
//! The parts of this example enabling MSAA are:
|
|
//! * The render pipeline is created with a sample_count > 1.
|
|
//! * A new texture with a sample_count > 1 is created and set as the color_attachment instead of the swapchain.
|
|
//! * The swapchain is now specified as a resolve_target.
|
|
//!
|
|
//! The parts of this example enabling LineList are:
|
|
//! * Set the primitive_topology to PrimitiveTopology::LineList.
|
|
//! * Vertices and Indices describe the two points that make up a line.
|
|
|
|
use std::iter;
|
|
|
|
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_mask: 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 | wgpu::TextureUsages::TRANSIENT,
|
|
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(),
|
|
}
|
|
}
|
|
|
|
#[expect(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 {
|
|
// TODO: Switch back to full scans of possible options when we expose
|
|
// supported sample counts to the user.
|
|
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,
|
|
depth_slice: None,
|
|
resolve_target: None,
|
|
ops: wgpu::Operations {
|
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
|
store: wgpu::StoreOp::Store,
|
|
},
|
|
}
|
|
} else {
|
|
wgpu::RenderPassColorAttachment {
|
|
view: &self.multisampled_framebuffer,
|
|
depth_slice: None,
|
|
resolve_target: Some(view),
|
|
ops: wgpu::Operations {
|
|
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
|
|
// Storing pre-resolve MSAA data is unnecessary if it isn't used later.
|
|
// On tile-based GPU, avoid store can reduce your app's memory footprint.
|
|
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,
|
|
multiview_mask: 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]
|
|
pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
|
|
name: "msaa-line",
|
|
image_path: "/examples/features/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(),
|
|
// There's a lot of natural variance so we check the weighted median too to differentiate
|
|
// real failures from variance.
|
|
comparisons: &[
|
|
wgpu_test::ComparisonType::Mean(0.065),
|
|
wgpu_test::ComparisonType::Percentile {
|
|
percentile: 0.5,
|
|
threshold: 0.29,
|
|
},
|
|
],
|
|
_phantom: std::marker::PhantomData::<Example>,
|
|
};
|