Skip to content

Commit

Permalink
Add manual msaa resolve to TextureReshaper. Solves validation error.
Browse files Browse the repository at this point in the history
Previously a validation error was occurring due to passing a sampled
texture to the bind group without declaring that the texture was
multisampled in the fragment shader.

This adds some new multisampled shaders that correctly declare the
texture and do the multisampling resolve manually.

Dedicated shaders have been added for common sample counts with unrolled
loops to avoid having the shader deal with conditions based on uniform
data. A fallback shader is provided for all other sample counts.

Closes nannou-org#462.
  • Loading branch information
mitchmindtree committed Mar 2, 2020
1 parent f035233 commit 8f0239c
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 51 deletions.
3 changes: 1 addition & 2 deletions examples/capture_hi_res.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,11 @@ fn model(app: &App) -> Model {

// Create the texture reshaper.
let texture_view = texture.create_default_view();
let src_multisampled = texture.sample_count() > 1;
let dst_format = Frame::TEXTURE_FORMAT;
let texture_reshaper = wgpu::TextureReshaper::new(
device,
&texture_view,
src_multisampled,
sample_count,
sample_count,
dst_format,
);
Expand Down
4 changes: 2 additions & 2 deletions src/frame/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,12 +278,12 @@ impl RenderData {
) -> Self {
let intermediary_lin_srgba =
create_intermediary_lin_srgba(device, swap_chain_dims, msaa_samples);
let src_sample_count = 1;
let swap_chain_sample_count = 1;
let src_multisampled = false;
let texture_reshaper = wgpu::TextureReshaper::new(
device,
&intermediary_lin_srgba.texture_view,
src_multisampled,
src_sample_count,
swap_chain_sample_count,
swap_chain_format,
);
Expand Down
33 changes: 4 additions & 29 deletions src/wgpu/texture/capturer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ pub struct Rgba8AsyncMapping<'a> {
struct ConverterDataPair {
src_descriptor: wgpu::TextureDescriptor,
reshaper: wgpu::TextureReshaper,
resolved_src_texture: Option<wgpu::Texture>,
dst_texture: wgpu::Texture,
}

Expand Down Expand Up @@ -103,13 +102,6 @@ impl Capturer {
*converter_data_pair = create_converter_data_pair(device, src_texture);
}

// If the src is multisampled, add the resolve command.
if let Some(ref resolved_src_texture) = converter_data_pair.resolved_src_texture {
let src_view = src_texture.create_default_view();
let resolved_view = resolved_src_texture.create_default_view();
wgpu::resolve_texture(&src_view, &resolved_view, encoder);
}

// Encode the texture format conversion.
let dst_view = converter_data_pair.dst_texture.create_default_view();
converter_data_pair
Expand Down Expand Up @@ -252,38 +244,22 @@ fn create_converter_data_pair(
device: &wgpu::Device,
src_texture: &wgpu::Texture,
) -> ConverterDataPair {
// If the src is multisampled, it must be resolved first.
let resolved_src_texture = if src_texture.sample_count() > 1 {
let texture = wgpu::TextureBuilder::from(src_texture.descriptor_cloned())
.sample_count(1)
.usage(wgpu::TextureUsage::OUTPUT_ATTACHMENT | wgpu::TextureUsage::SAMPLED)
.build(device);
Some(texture)
} else {
None
};

// Create the destination format texture.
let dst_texture = wgpu::TextureBuilder::from(src_texture.descriptor_cloned())
.sample_count(1)
.format(Capturer::DST_FORMAT)
.usage(wgpu::TextureUsage::OUTPUT_ATTACHMENT | wgpu::TextureUsage::COPY_SRC)
.build(device);

// If we have a resolved texture, use it as the conversion src. Otherwise use `src_texture`.
let src_view = resolved_src_texture
.as_ref()
.map(|tex| tex.create_default_view())
.unwrap_or_else(|| src_texture.create_default_view());

// Create the converter.
let dst_format = dst_texture.format();
let src_multisampled = src_texture.sample_count() > 1;
let src_sample_count = src_texture.sample_count();
let src_view = src_texture.create_default_view();
let dst_sample_count = 1;
let dst_format = dst_texture.format();
let reshaper = wgpu::TextureReshaper::new(
device,
&src_view,
src_multisampled,
src_sample_count,
dst_sample_count,
dst_format,
);
Expand All @@ -294,7 +270,6 @@ fn create_converter_data_pair(
ConverterDataPair {
src_descriptor,
reshaper,
resolved_src_texture,
dst_texture,
}
}
108 changes: 90 additions & 18 deletions src/wgpu/texture/reshaper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,28 @@ pub struct Reshaper {
bind_group: wgpu::BindGroup,
render_pipeline: wgpu::RenderPipeline,
sampler: wgpu::Sampler,
uniform_buffer: Option<wgpu::Buffer>,
vertex_buffer: wgpu::Buffer,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
struct Vertex {
pub position: [f32; 2],
}

#[repr(C)]
#[derive(Copy, Clone)]
struct Uniforms {
sample_count: u32,
}

impl Reshaper {
/// Construct a new `Reshaper`.
pub fn new(
device: &wgpu::Device,
src_texture: &wgpu::TextureView,
src_multisampled: bool,
src_sample_count: u32,
dst_sample_count: u32,
dst_format: wgpu::TextureFormat,
) -> Self {
Expand All @@ -31,16 +44,23 @@ impl Reshaper {
let vs_spirv = wgpu::read_spirv(std::io::Cursor::new(&vs[..]))
.expect("failed to read hard-coded SPIRV");
let vs_mod = device.create_shader_module(&vs_spirv);
let fs = include_bytes!("shaders/frag.spv");
let fs_spirv = wgpu::read_spirv(std::io::Cursor::new(&fs[..]))
.expect("failed to read hard-coded SPIRV");
let fs = match src_sample_count {
1 => &include_bytes!("shaders/frag.spv")[..],
2 => &include_bytes!("shaders/frag_msaa2.spv")[..],
4 => &include_bytes!("shaders/frag_msaa4.spv")[..],
8 => &include_bytes!("shaders/frag_msaa8.spv")[..],
16 => &include_bytes!("shaders/frag_msaa16.spv")[..],
_ => &include_bytes!("shaders/frag_msaa.spv")[..],
};
let fs_spirv =
wgpu::read_spirv(std::io::Cursor::new(fs)).expect("failed to read hard-coded SPIRV");
let fs_mod = device.create_shader_module(&fs_spirv);

// Create the sampler for sampling from the source texture.
let sampler = wgpu::SamplerBuilder::new().build(device);

// Create the render pipeline.
let bind_group_layout = bind_group_layout(device, src_multisampled);
let bind_group_layout = bind_group_layout(device, src_sample_count);
let pipeline_layout = pipeline_layout(device, &bind_group_layout);
let render_pipeline = render_pipeline(
device,
Expand All @@ -51,8 +71,29 @@ impl Reshaper {
dst_format,
);

// Create the uniform buffer to pass the sample count if we don't have an unrolled resolve
// fragment shader for it.
let uniform_buffer = match unrolled_sample_count(src_sample_count) {
true => None,
false => {
let uniforms = Uniforms {
sample_count: src_sample_count,
};
let buffer = device
.create_buffer_mapped(1, wgpu::BufferUsage::UNIFORM)
.fill_from_slice(&[uniforms]);
Some(buffer)
}
};

// Create the bind group.
let bind_group = bind_group(device, &bind_group_layout, src_texture, &sampler);
let bind_group = bind_group(
device,
&bind_group_layout,
src_texture,
&sampler,
uniform_buffer.as_ref(),
);

// Create the vertex buffer.
let vertex_buffer = device
Expand All @@ -66,6 +107,7 @@ impl Reshaper {
bind_group,
render_pipeline,
sampler,
uniform_buffer,
vertex_buffer,
}
}
Expand All @@ -79,6 +121,7 @@ impl Reshaper {
) {
let vertex_range = 0..VERTICES.len() as u32;
let instance_range = 0..1;

let render_pass_desc = wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: dst_texture,
Expand All @@ -97,12 +140,6 @@ impl Reshaper {
}
}

#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
struct Vertex {
pub position: [f32; 2],
}

const VERTICES: [Vertex; 4] = [
Vertex {
position: [-1.0, -1.0],
Expand All @@ -118,6 +155,14 @@ const VERTICES: [Vertex; 4] = [
},
];

// We provide pre-prepared fragment shaders with unrolled resolves for common sample counts.
fn unrolled_sample_count(sample_count: u32) -> bool {
match sample_count {
1 | 2 | 4 | 8 | 16 => true,
_ => false,
}
}

fn vertex_attrs() -> [wgpu::VertexAttributeDescriptor; 1] {
[wgpu::VertexAttributeDescriptor {
format: wgpu::VertexFormat::Float2,
Expand All @@ -126,12 +171,12 @@ fn vertex_attrs() -> [wgpu::VertexAttributeDescriptor; 1] {
}]
}

fn bind_group_layout(device: &wgpu::Device, src_multisampled: bool) -> wgpu::BindGroupLayout {
fn bind_group_layout(device: &wgpu::Device, src_sample_count: u32) -> wgpu::BindGroupLayout {
let texture_binding = wgpu::BindGroupLayoutBinding {
binding: 0,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::SampledTexture {
multisampled: src_multisampled,
multisampled: src_sample_count > 1,
dimension: wgpu::TextureViewDimension::D2,
},
};
Expand All @@ -140,8 +185,21 @@ fn bind_group_layout(device: &wgpu::Device, src_multisampled: bool) -> wgpu::Bin
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler,
};
let bindings = &[texture_binding, sampler_binding];
let desc = wgpu::BindGroupLayoutDescriptor { bindings };
let uniforms_binding = match unrolled_sample_count(src_sample_count) {
true => None,
false => Some(wgpu::BindGroupLayoutBinding {
binding: 2,
visibility: wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::UniformBuffer { dynamic: false },
}),
};
let bindings = match uniforms_binding {
None => vec![texture_binding, sampler_binding],
Some(uniforms_binding) => vec![texture_binding, sampler_binding, uniforms_binding],
};
let desc = wgpu::BindGroupLayoutDescriptor {
bindings: &bindings,
};
device.create_bind_group_layout(&desc)
}

Expand All @@ -150,6 +208,7 @@ fn bind_group(
layout: &wgpu::BindGroupLayout,
texture: &wgpu::TextureView,
sampler: &wgpu::Sampler,
uniform_buffer: Option<&wgpu::Buffer>,
) -> wgpu::BindGroup {
let texture_binding = wgpu::Binding {
binding: 0,
Expand All @@ -159,8 +218,21 @@ fn bind_group(
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
};
let bindings = &[texture_binding, sampler_binding];
let desc = wgpu::BindGroupDescriptor { layout, bindings };
let uniforms_binding = uniform_buffer.map(|buffer| wgpu::Binding {
binding: 2,
resource: wgpu::BindingResource::Buffer {
buffer,
range: 0..std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
},
});
let bindings = match uniforms_binding {
None => vec![texture_binding, sampler_binding],
Some(uniforms_binding) => vec![texture_binding, sampler_binding, uniforms_binding],
};
let desc = wgpu::BindGroupDescriptor {
layout,
bindings: &bindings,
};
device.create_bind_group(&desc)
}

Expand Down
Binary file added src/wgpu/texture/reshaper/shaders/frag_msaa.spv
Binary file not shown.
Binary file not shown.
Binary file added src/wgpu/texture/reshaper/shaders/frag_msaa2.spv
Binary file not shown.
Binary file added src/wgpu/texture/reshaper/shaders/frag_msaa4.spv
Binary file not shown.
Binary file added src/wgpu/texture/reshaper/shaders/frag_msaa8.spv
Binary file not shown.
34 changes: 34 additions & 0 deletions src/wgpu/texture/reshaper/shaders/shader_msaa.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// NOTE: This shader requires being manually compiled to SPIR-V in order to
// avoid having downstream users require building shaderc and compiling the
// shader themselves. If you update this shader, be sure to also re-compile it
// and update `frag_msaa.spv`. You can do so using `glslangValidator` with the
// following command: `glslangValidator -V -o frag_msaa.spv shader_msaa.frag`

#version 450

layout(location = 0) in vec2 tex_coords;
layout(location = 0) out vec4 f_color;

layout(set = 0, binding = 0) uniform texture2DMS tex;
layout(set = 0, binding = 1) uniform sampler tex_sampler;
layout(set = 0, binding = 2) uniform Data {
uint sample_count;
} uniforms;

void main() {
// Get the integer tex coordinates.
ivec2 tex_size = textureSize(sampler2DMS(tex, tex_sampler));
int tex_x = int(tex_size.x * tex_coords.x);
int tex_y = int(tex_size.y * tex_coords.y);
ivec2 itex_coords = ivec2(tex_x, tex_y);

// Perform the resolve.
vec4 color = vec4(0);
for (int i = 0; i < uniforms.sample_count; i++) {
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, i);
}
color /= float(uniforms.sample_count);

// Assign the resolved color to the output.
f_color = color;
}
45 changes: 45 additions & 0 deletions src/wgpu/texture/reshaper/shaders/shader_msaa16.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// NOTE: This shader requires being manually compiled to SPIR-V in order to
// avoid having downstream users require building shaderc and compiling the
// shader themselves. If you update this shader, be sure to also re-compile it
// and update `frag_msaa16.spv`. You can do so using `glslangValidator` with
// the following command:
// `glslangValidator -V -o frag_msaa16.spv shader_msaa16.frag`

#version 450

layout(location = 0) in vec2 tex_coords;
layout(location = 0) out vec4 f_color;

layout(set = 0, binding = 0) uniform texture2DMS tex;
layout(set = 0, binding = 1) uniform sampler tex_sampler;

void main() {
// Get the integer tex coordinates.
ivec2 tex_size = textureSize(sampler2DMS(tex, tex_sampler));
int tex_x = int(tex_size.x * tex_coords.x);
int tex_y = int(tex_size.y * tex_coords.y);
ivec2 itex_coords = ivec2(tex_x, tex_y);

// Manually unroll the resolve. The less conditions the better!
vec4 color = vec4(0);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 0);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 1);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 2);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 3);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 4);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 5);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 6);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 7);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 8);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 9);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 10);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 11);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 12);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 13);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 14);
color += texelFetch(sampler2DMS(tex, tex_sampler), itex_coords, 15);
color *= 0.0625;

// Assign the resolved color to the output.
f_color = color;
}
Loading

0 comments on commit 8f0239c

Please sign in to comment.