use bytemuck::{Pod, Zeroable};
use std::{f32::consts, mem::size_of};
use wgpu::util::DeviceExt;
const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
const MIP_LEVEL_COUNT: u32 = 10;
const MIP_PASS_COUNT: u32 = MIP_LEVEL_COUNT - 1;
const QUERY_FEATURES: wgpu::Features = {
wgpu::Features::TIMESTAMP_QUERY
.union(wgpu::Features::TIMESTAMP_QUERY_INSIDE_PASSES)
.union(wgpu::Features::PIPELINE_STATISTICS_QUERY)
};
fn create_texels(size: usize, cx: f32, cy: f32) -> Vec<u8> {
use std::iter;
(0..size * size)
.flat_map(|id| {
let mut x = 4.0 * (id % size) as f32 / (size - 1) as f32 - 2.0;
let mut y = 2.0 * (id / size) as f32 / (size - 1) as f32 - 1.0;
let mut count = 0;
while count < 0xFF && x * x + y * y < 4.0 {
let old_x = x;
x = x * x - y * y + cx;
y = 2.0 * old_x * y + cy;
count += 1;
}
iter::once(0xFF - (count * 2) as u8)
.chain(iter::once(0xFF - (count * 5) as u8))
.chain(iter::once(0xFF - (count * 13) as u8))
.chain(iter::once(u8::MAX))
})
.collect()
}
struct QuerySets {
timestamp: wgpu::QuerySet,
timestamp_period: f32,
pipeline_statistics: wgpu::QuerySet,
data_buffer: wgpu::Buffer,
mapping_buffer: wgpu::Buffer,
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct TimestampData {
start: u64,
end: u64,
}
type TimestampQueries = [TimestampData; MIP_PASS_COUNT as usize];
type PipelineStatisticsQueries = [u64; MIP_PASS_COUNT as usize];
fn pipeline_statistics_offset() -> wgpu::BufferAddress {
(size_of::<TimestampQueries>() as wgpu::BufferAddress).max(wgpu::QUERY_RESOLVE_BUFFER_ALIGNMENT)
}
struct Example {
bind_group: wgpu::BindGroup,
uniform_buf: wgpu::Buffer,
draw_pipeline: wgpu::RenderPipeline,
}
impl Example {
fn generate_matrix(aspect_ratio: f32) -> glam::Mat4 {
let projection = glam::Mat4::perspective_rh(consts::FRAC_PI_4, aspect_ratio, 1.0, 1000.0);
let view = glam::Mat4::look_at_rh(
glam::Vec3::new(0f32, 0.0, 10.0),
glam::Vec3::new(0f32, 50.0, 0.0),
glam::Vec3::Z,
);
projection * view
}
fn generate_mipmaps(
encoder: &mut wgpu::CommandEncoder,
device: &wgpu::Device,
texture: &wgpu::Texture,
query_sets: &Option<QuerySets>,
mip_count: u32,
) {
let shader = device.create_shader_module(wgpu::include_wgsl!("blit.wgsl"));
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("blit"),
layout: None,
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(TEXTURE_FORMAT.into())],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let bind_group_layout = pipeline.get_bind_group_layout(0);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("mip"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let views = (0..mip_count)
.map(|mip| {
texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("mip"),
format: None,
dimension: None,
usage: None,
aspect: wgpu::TextureAspect::All,
base_mip_level: mip,
mip_level_count: Some(1),
base_array_layer: 0,
array_layer_count: None,
})
})
.collect::<Vec<_>>();
for target_mip in 1..mip_count as usize {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&views[target_mip - 1]),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
label: None,
});
let pipeline_query_index_base = target_mip as u32 - 1;
let timestamp_query_index_base = (target_mip as u32 - 1) * 2;
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &views[target_mip],
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
if let Some(ref query_sets) = query_sets {
rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base);
rpass.begin_pipeline_statistics_query(
&query_sets.pipeline_statistics,
pipeline_query_index_base,
);
}
rpass.set_pipeline(&pipeline);
rpass.set_bind_group(0, &bind_group, &[]);
rpass.draw(0..3, 0..1);
if let Some(ref query_sets) = query_sets {
rpass.write_timestamp(&query_sets.timestamp, timestamp_query_index_base + 1);
rpass.end_pipeline_statistics_query();
}
}
if let Some(ref query_sets) = query_sets {
let timestamp_query_count = MIP_PASS_COUNT * 2;
encoder.resolve_query_set(
&query_sets.timestamp,
0..timestamp_query_count,
&query_sets.data_buffer,
0,
);
encoder.resolve_query_set(
&query_sets.pipeline_statistics,
0..MIP_PASS_COUNT,
&query_sets.data_buffer,
pipeline_statistics_offset(),
);
}
}
}
impl crate::framework::Example for Example {
fn optional_features() -> wgpu::Features {
QUERY_FEATURES
}
fn init(
config: &wgpu::SurfaceConfiguration,
_adapter: &wgpu::Adapter,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> Self {
let mut init_encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
let size = 1 << MIP_PASS_COUNT;
let texels = create_texels(size as usize, -0.8, 0.156);
let texture_extent = wgpu::Extent3d {
width: size,
height: size,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
size: texture_extent,
mip_level_count: MIP_LEVEL_COUNT,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: TEXTURE_FORMAT,
usage: wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::COPY_DST,
label: None,
view_formats: &[],
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let temp_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Temporary Buffer"),
contents: texels.as_slice(),
usage: wgpu::BufferUsages::COPY_SRC,
});
init_encoder.copy_buffer_to_texture(
wgpu::TexelCopyBufferInfo {
buffer: &temp_buf,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * size),
rows_per_image: None,
},
},
texture.as_image_copy(),
texture_extent,
);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: None,
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32);
let mx_ref: &[f32; 16] = mx_total.as_ref();
let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Uniform Buffer"),
contents: bytemuck::cast_slice(mx_ref),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let shader = device.create_shader_module(wgpu::include_wgsl!("draw.wgsl"));
let draw_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("draw"),
layout: None,
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[],
},
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::TriangleStrip,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let bind_group_layout = draw_pipeline.get_bind_group_layout(0);
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&texture_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
label: None,
});
let query_sets = if device.features().contains(QUERY_FEATURES) {
let mip_passes = MIP_LEVEL_COUNT - 1;
let timestamp = device.create_query_set(&wgpu::QuerySetDescriptor {
label: None,
count: mip_passes * 2,
ty: wgpu::QueryType::Timestamp,
});
let timestamp_period = queue.get_timestamp_period();
let pipeline_statistics = device.create_query_set(&wgpu::QuerySetDescriptor {
label: None,
count: mip_passes,
ty: wgpu::QueryType::PipelineStatistics(
wgpu::PipelineStatisticsTypes::FRAGMENT_SHADER_INVOCATIONS,
),
});
let buffer_size = pipeline_statistics_offset()
+ size_of::<PipelineStatisticsQueries>() as wgpu::BufferAddress;
let data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("query buffer"),
size: buffer_size,
usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let mapping_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("query buffer"),
size: buffer_size,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
Some(QuerySets {
timestamp,
timestamp_period,
pipeline_statistics,
data_buffer,
mapping_buffer,
})
} else {
None
};
Self::generate_mipmaps(
&mut init_encoder,
device,
&texture,
&query_sets,
MIP_LEVEL_COUNT,
);
if let Some(ref query_sets) = query_sets {
init_encoder.copy_buffer_to_buffer(
&query_sets.data_buffer,
0,
&query_sets.mapping_buffer,
0,
query_sets.data_buffer.size(),
);
}
queue.submit(Some(init_encoder.finish()));
if let Some(ref query_sets) = query_sets {
query_sets
.mapping_buffer
.slice(..)
.map_async(wgpu::MapMode::Read, |_| ());
device.poll(wgpu::Maintain::wait()).panic_on_timeout();
let timestamp_view = query_sets
.mapping_buffer
.slice(..size_of::<TimestampQueries>() as wgpu::BufferAddress)
.get_mapped_range();
let pipeline_stats_view = query_sets
.mapping_buffer
.slice(pipeline_statistics_offset()..)
.get_mapped_range();
let timestamp_data: &TimestampQueries = bytemuck::from_bytes(×tamp_view);
let pipeline_stats_data: &PipelineStatisticsQueries =
bytemuck::from_bytes(&pipeline_stats_view);
for (idx, (timestamp, pipeline)) in timestamp_data
.iter()
.zip(pipeline_stats_data.iter())
.enumerate()
{
let nanoseconds =
(timestamp.end - timestamp.start) as f32 * query_sets.timestamp_period;
let microseconds = nanoseconds / 1000.0;
println!(
"Generating mip level {} took {:.3} μs and called the fragment shader {} times",
idx + 1,
microseconds,
pipeline
);
}
}
Example {
bind_group,
uniform_buf,
draw_pipeline,
}
}
fn update(&mut self, _event: winit::event::WindowEvent) {
}
fn resize(
&mut self,
config: &wgpu::SurfaceConfiguration,
_device: &wgpu::Device,
queue: &wgpu::Queue,
) {
let mx_total = Self::generate_matrix(config.width as f32 / config.height as f32);
let mx_ref: &[f32; 16] = mx_total.as_ref();
queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(mx_ref));
}
fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let clear_color = wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
};
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(clear_color),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&self.draw_pipeline);
rpass.set_bind_group(0, &self.bind_group, &[]);
rpass.draw(0..4, 0..1);
}
queue.submit(Some(encoder.finish()));
}
}
pub fn main() {
crate::framework::run::<Example>("mipmap");
}
#[cfg(test)]
#[wgpu_test::gpu_test]
static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
name: "mipmap",
image_path: "/examples/src/mipmap/screenshot.png",
width: 1024,
height: 768,
optional_features: wgpu::Features::default(),
base_test_parameters: wgpu_test::TestParameters::default(),
comparisons: &[wgpu_test::ComparisonType::Mean(0.02)],
_phantom: std::marker::PhantomData::<Example>,
};
#[cfg(test)]
#[wgpu_test::gpu_test]
static TEST_QUERY: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
name: "mipmap-query",
image_path: "/examples/src/mipmap/screenshot_query.png",
width: 1024,
height: 768,
optional_features: QUERY_FEATURES,
base_test_parameters: wgpu_test::TestParameters::default(),
comparisons: &[wgpu_test::ComparisonType::Mean(0.025)],
_phantom: std::marker::PhantomData::<Example>,
};