メモ:wgpu-rs と lyon を使って描画する

以前にこんなのを書いたけど、

けっきょく、nannou の挙動がわからないので勉強がてら素の wgpu と lyon を使ってみるか〜となってしまったのでそのメモ(vulkan のドライバまわり?の謎のエラーの切り分けのために、詳しめのエラーを出してくれる開発版 wgpu を使いたかった、という実利的な理由もあります)。 wgpu-rs も lyon も執筆時点の GitHub 最新版を使っているので、ここに書くことはすぐに風化していく知識です。

CullModeNone

wgpu::RasterizationStateDescriptorcull_mode は、 描画しない vertex の向きを指定するオプション。 learn-wgpu だと wgpu::CullMode::Back を使ってるけど、 lyon を使う時にこれを指定してしまうと何も表示されない。 2D だと表も裏もないので culling されないようにするのが正しいっぽい(Front でも動くけど)。

index buffer は padding する

lyon で tessellation をやるとき、

        let mut geometry: VertexBuffers<Vertex, u16> = VertexBuffers::new();

        let tolerance = 0.0001;

        let mut stroke_tess = StrokeTessellator::new();
        stroke_tess
            .tessellate_path(
                &path,
                &StrokeOptions::tolerance(tolerance).with_line_width(0.13),
                &mut BuffersBuilder::new(&mut geometry, |vertex: tessellation::StrokeVertex| {
                    Vertex {
                        position: vertex.position().to_array(),
                    }
                }),
            )
            .unwrap();

みたいなことをするけど、このとき、 geometry には vertex とその index が書き込まれる。が、そのまま使うとたぶんこういうエラーになる。

thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `2`,
 right: `0`: Buffers that are mapped at creation have to be aligned to COPY_BUFFER_ALIGNMENT', <::std::macros::panic macros>:5:6

これは、以下の制限が少し前に入ったから。

device.create_buffer_with_data() を使う場合、データ長が wgpu::COPY_BUFFER_ALIGNMENT(具体的には 4)の倍数になっている必要がある。 が、 vertex は多くの場合三角形なので、 index data は 3 の倍数になる。

ということで、こんな感じで自分で padding する必要がある。 render_pass.draw_indexed() には元の範囲を指定する必要があるので、 extend() する前に range を記録しておく。

        let stroke_range = 0..(geometry.indices.len() as u32);
        geometry.indices.extend(std::iter::repeat(0).take(
            wgpu::COPY_BUFFER_ALIGNMENT as usize
                - geometry.indices.len() % wgpu::COPY_BUFFER_ALIGNMENT as usize,
        ));

MSAA

TextureView をつくってそれを wgpu::RenderPassColorAttachmentDescriptorattachment に指定し、 frame.output.viewresolve_target に移す。基本は公式レポジトリの exampleを見れば書かれている。

--- a/src/main.rs
+++ b/src/main.rs
@@ -12,6 +12,8 @@ use winit::{
 
 use futures::executor::block_on;
 
+const SAMPLE_COUNT: u32 = 4;
+
 // The vertex type that we will use to represent a point on our triangle.
 #[repr(C)]
 #[derive(Clone, Copy)]
@@ -35,6 +37,7 @@ struct State {
     // index_count: usize,
     // bind_group: wgpu::BindGroup,
     render_pipeline: wgpu::RenderPipeline,
+    multisampled_framebuffer: wgpu::TextureView,
     // blur_render_pipeline: wgpu::RenderPipeline,
     geometry: VertexBuffers<Vertex, u16>,
     stroke_range: std::ops::Range<u32>,
@@ -120,6 +123,16 @@ impl State {
         // Load shader modules.
         let vs_mod = device.create_shader_module(wgpu::include_spirv!("shaders/shader.vert.spv"));
         let fs_mod = device.create_shader_module(wgpu::include_spirv!("shaders/shader.frag.spv"));
+
+        // MSAA
+        let multisampled_texture_extent = wgpu::Extent3d {
+            width: sc_desc.width,
+            height: sc_desc.height,
+            depth: 1,
+        };
+
+        let multisampled_framebuffer = create_multisampled_framebuffer(&device, &sc_desc);
+
         let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
             layout: &pipeline_layout,
             vertex_stage: wgpu::ProgrammableStageDescriptor {
@@ -153,7 +166,7 @@ impl State {
                     attributes: &wgpu::vertex_attr_array![0 => Float2],
                 }],
             },
-            sample_count: 1,
+            sample_count: SAMPLE_COUNT,
             sample_mask: !0,
             alpha_to_coverage_enabled: false,
         });
@@ -177,6 +190,7 @@ impl State {
             // index_count,
             // bind_group,
             render_pipeline,
+            multisampled_framebuffer,
             geometry,
             stroke_range,
             size,
@@ -189,6 +203,8 @@ impl State {
         self.sc_desc.width = new_size.width;
         self.sc_desc.height = new_size.height;
         self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
+
+        self.update_multisampled_framebuffer();
     }
 
     fn input(&mut self, event: &WindowEvent) -> bool {
@@ -227,8 +243,8 @@ impl State {
         {
             let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                 color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
-                    attachment: &frame.output.view,
-                    resolve_target: None,
+                    attachment: &self.multisampled_framebuffer,
+                    resolve_target: Some(&frame.output.view),
                     ops: wgpu::Operations {
                         load: wgpu::LoadOp::Clear(wgpu::Color {
                             r: 0.1,
@@ -262,6 +278,35 @@ impl State {
 
         &self.queue.submit(Some(encoder.finish()));
     }
+
+    fn update_multisampled_framebuffer(&mut self) {
+        self.multisampled_framebuffer =
+            create_multisampled_framebuffer(&self.device, &self.sc_desc);
+    }
+}
+
+fn create_multisampled_framebuffer(
+    device: &wgpu::Device,
+    sc_desc: &wgpu::SwapChainDescriptor,
+) -> wgpu::TextureView {
+    let multisampled_texture_extent = wgpu::Extent3d {
+        width: sc_desc.width,
+        height: sc_desc.height,
+        depth: 1,
+    };
+    let multisampled_frame_descriptor = &wgpu::TextureDescriptor {
+        size: multisampled_texture_extent,
+        mip_level_count: 1,
+        sample_count: SAMPLE_COUNT,
+        dimension: wgpu::TextureDimension::D2,
+        format: sc_desc.format,
+        usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
+        label: None,
+    };
+
+    device
+        .create_texture(multisampled_frame_descriptor)
+        .create_default_view()
 }
 
 // main() is derived from sotrh/learn-wgpu

結果

f:id:yutannihilation:20200719005522p:plain