From 38b6c84e7761c049b17d178deb9c866386a53946 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector0193@gmail.com>
Date: Wed, 23 Oct 2019 01:21:23 +0200
Subject: Implement basic image rendering in `iced_wgpu`

---
 wgpu/src/image.rs | 438 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 438 insertions(+)
 create mode 100644 wgpu/src/image.rs

(limited to 'wgpu/src/image.rs')

diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
new file mode 100644
index 00000000..18889faf
--- /dev/null
+++ b/wgpu/src/image.rs
@@ -0,0 +1,438 @@
+use crate::Transformation;
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::mem;
+use std::rc::Rc;
+
+pub struct Pipeline {
+    cache: RefCell<HashMap<String, Memory>>,
+
+    pipeline: wgpu::RenderPipeline,
+    transform: wgpu::Buffer,
+    vertices: wgpu::Buffer,
+    indices: wgpu::Buffer,
+    instances: wgpu::Buffer,
+    constants: wgpu::BindGroup,
+    texture_layout: wgpu::BindGroupLayout,
+}
+
+impl Pipeline {
+    pub fn new(device: &wgpu::Device) -> Self {
+        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+            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::Linear,
+            lod_min_clamp: -100.0,
+            lod_max_clamp: 100.0,
+            compare_function: wgpu::CompareFunction::Always,
+        });
+
+        let constant_layout =
+            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+                bindings: &[
+                    wgpu::BindGroupLayoutBinding {
+                        binding: 0,
+                        visibility: wgpu::ShaderStage::VERTEX,
+                        ty: wgpu::BindingType::UniformBuffer { dynamic: false },
+                    },
+                    wgpu::BindGroupLayoutBinding {
+                        binding: 1,
+                        visibility: wgpu::ShaderStage::FRAGMENT,
+                        ty: wgpu::BindingType::Sampler,
+                    },
+                ],
+            });
+
+        let matrix: [f32; 16] = Transformation::identity().into();
+
+        let transform_buffer = device
+            .create_buffer_mapped(
+                16,
+                wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
+            )
+            .fill_from_slice(&matrix[..]);
+
+        let constant_bind_group =
+            device.create_bind_group(&wgpu::BindGroupDescriptor {
+                layout: &constant_layout,
+                bindings: &[
+                    wgpu::Binding {
+                        binding: 0,
+                        resource: wgpu::BindingResource::Buffer {
+                            buffer: &transform_buffer,
+                            range: 0..64,
+                        },
+                    },
+                    wgpu::Binding {
+                        binding: 1,
+                        resource: wgpu::BindingResource::Sampler(&sampler),
+                    },
+                ],
+            });
+
+        let texture_layout =
+            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+                bindings: &[wgpu::BindGroupLayoutBinding {
+                    binding: 0,
+                    visibility: wgpu::ShaderStage::FRAGMENT,
+                    ty: wgpu::BindingType::SampledTexture {
+                        multisampled: false,
+                        dimension: wgpu::TextureViewDimension::D2,
+                    },
+                }],
+            });
+
+        let layout =
+            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+                bind_group_layouts: &[&constant_layout, &texture_layout],
+            });
+
+        let vs = include_bytes!("shader/image.vert.spv");
+        let vs_module = device.create_shader_module(
+            &wgpu::read_spirv(std::io::Cursor::new(&vs[..]))
+                .expect("Read image vertex shader as SPIR-V"),
+        );
+
+        let fs = include_bytes!("shader/image.frag.spv");
+        let fs_module = device.create_shader_module(
+            &wgpu::read_spirv(std::io::Cursor::new(&fs[..]))
+                .expect("Read image fragment shader as SPIR-V"),
+        );
+
+        let pipeline =
+            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+                layout: &layout,
+                vertex_stage: wgpu::ProgrammableStageDescriptor {
+                    module: &vs_module,
+                    entry_point: "main",
+                },
+                fragment_stage: Some(wgpu::ProgrammableStageDescriptor {
+                    module: &fs_module,
+                    entry_point: "main",
+                }),
+                rasterization_state: Some(wgpu::RasterizationStateDescriptor {
+                    front_face: wgpu::FrontFace::Cw,
+                    cull_mode: wgpu::CullMode::None,
+                    depth_bias: 0,
+                    depth_bias_slope_scale: 0.0,
+                    depth_bias_clamp: 0.0,
+                }),
+                primitive_topology: wgpu::PrimitiveTopology::TriangleList,
+                color_states: &[wgpu::ColorStateDescriptor {
+                    format: wgpu::TextureFormat::Bgra8UnormSrgb,
+                    color_blend: wgpu::BlendDescriptor {
+                        src_factor: wgpu::BlendFactor::SrcAlpha,
+                        dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+                        operation: wgpu::BlendOperation::Add,
+                    },
+                    alpha_blend: wgpu::BlendDescriptor {
+                        src_factor: wgpu::BlendFactor::One,
+                        dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+                        operation: wgpu::BlendOperation::Add,
+                    },
+                    write_mask: wgpu::ColorWrite::ALL,
+                }],
+                depth_stencil_state: None,
+                index_format: wgpu::IndexFormat::Uint16,
+                vertex_buffers: &[
+                    wgpu::VertexBufferDescriptor {
+                        stride: mem::size_of::<Vertex>() as u64,
+                        step_mode: wgpu::InputStepMode::Vertex,
+                        attributes: &[wgpu::VertexAttributeDescriptor {
+                            shader_location: 0,
+                            format: wgpu::VertexFormat::Float2,
+                            offset: 0,
+                        }],
+                    },
+                    wgpu::VertexBufferDescriptor {
+                        stride: mem::size_of::<Instance>() as u64,
+                        step_mode: wgpu::InputStepMode::Instance,
+                        attributes: &[
+                            wgpu::VertexAttributeDescriptor {
+                                shader_location: 1,
+                                format: wgpu::VertexFormat::Float2,
+                                offset: 0,
+                            },
+                            wgpu::VertexAttributeDescriptor {
+                                shader_location: 2,
+                                format: wgpu::VertexFormat::Float2,
+                                offset: 4 * 2,
+                            },
+                        ],
+                    },
+                ],
+                sample_count: 1,
+                sample_mask: !0,
+                alpha_to_coverage_enabled: false,
+            });
+
+        let vertices = device
+            .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX)
+            .fill_from_slice(&QUAD_VERTS);
+
+        let indices = device
+            .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX)
+            .fill_from_slice(&QUAD_INDICES);
+
+        let instances = device.create_buffer(&wgpu::BufferDescriptor {
+            size: mem::size_of::<Instance>() as u64,
+            usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST,
+        });
+
+        Pipeline {
+            cache: RefCell::new(HashMap::new()),
+
+            pipeline,
+            transform: transform_buffer,
+            vertices,
+            indices,
+            instances,
+            constants: constant_bind_group,
+            texture_layout,
+        }
+    }
+
+    pub fn dimensions(&self, path: &str) -> (u32, u32) {
+        self.load(path);
+
+        self.cache.borrow().get(path).unwrap().dimensions()
+    }
+
+    fn load(&self, path: &str) {
+        if !self.cache.borrow().contains_key(path) {
+            let image = image::open(path).expect("Load image").to_bgra();
+
+            self.cache
+                .borrow_mut()
+                .insert(path.to_string(), Memory::Host { image });
+        }
+    }
+
+    pub fn draw(
+        &mut self,
+        device: &mut wgpu::Device,
+        encoder: &mut wgpu::CommandEncoder,
+        instances: &[Image],
+        transformation: Transformation,
+        target: &wgpu::TextureView,
+    ) {
+        let matrix: [f32; 16] = transformation.into();
+
+        let transform_buffer = device
+            .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC)
+            .fill_from_slice(&matrix[..]);
+
+        encoder.copy_buffer_to_buffer(
+            &transform_buffer,
+            0,
+            &self.transform,
+            0,
+            16 * 4,
+        );
+
+        // TODO: Batch draw calls using a texture atlas
+        // Guillotière[1] by @nical can help us a lot here.
+        //
+        // [1]: https://github.com/nical/guillotiere
+        for image in instances {
+            self.load(&image.path);
+
+            let texture = self
+                .cache
+                .borrow_mut()
+                .get_mut(&image.path)
+                .unwrap()
+                .upload(device, encoder, &self.texture_layout);
+
+            let instance_buffer = device
+                .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC)
+                .fill_from_slice(&[Instance {
+                    position: image.position,
+                    scale: image.scale,
+                }]);
+
+            encoder.copy_buffer_to_buffer(
+                &instance_buffer,
+                0,
+                &self.instances,
+                0,
+                mem::size_of::<Image>() as u64,
+            );
+
+            {
+                let mut render_pass =
+                    encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                        color_attachments: &[
+                            wgpu::RenderPassColorAttachmentDescriptor {
+                                attachment: target,
+                                resolve_target: None,
+                                load_op: wgpu::LoadOp::Load,
+                                store_op: wgpu::StoreOp::Store,
+                                clear_color: wgpu::Color {
+                                    r: 0.0,
+                                    g: 0.0,
+                                    b: 0.0,
+                                    a: 0.0,
+                                },
+                            },
+                        ],
+                        depth_stencil_attachment: None,
+                    });
+
+                render_pass.set_pipeline(&self.pipeline);
+                render_pass.set_bind_group(0, &self.constants, &[]);
+                render_pass.set_bind_group(1, &texture, &[]);
+                render_pass.set_index_buffer(&self.indices, 0);
+                render_pass.set_vertex_buffers(
+                    0,
+                    &[(&self.vertices, 0), (&self.instances, 0)],
+                );
+
+                render_pass.draw_indexed(
+                    0..QUAD_INDICES.len() as u32,
+                    0,
+                    0..1 as u32,
+                );
+            }
+        }
+    }
+}
+
+enum Memory {
+    Host {
+        image: image::ImageBuffer<image::Bgra<u8>, Vec<u8>>,
+    },
+    Device {
+        bind_group: Rc<wgpu::BindGroup>,
+        width: u32,
+        height: u32,
+    },
+}
+
+impl Memory {
+    fn dimensions(&self) -> (u32, u32) {
+        match self {
+            Memory::Host { image } => image.dimensions(),
+            Memory::Device { width, height, .. } => (*width, *height),
+        }
+    }
+
+    fn upload(
+        &mut self,
+        device: &wgpu::Device,
+        encoder: &mut wgpu::CommandEncoder,
+        texture_layout: &wgpu::BindGroupLayout,
+    ) -> Rc<wgpu::BindGroup> {
+        match self {
+            Memory::Host { image } => {
+                let (width, height) = image.dimensions();
+
+                let extent = wgpu::Extent3d {
+                    width,
+                    height,
+                    depth: 1,
+                };
+
+                let texture = device.create_texture(&wgpu::TextureDescriptor {
+                    size: extent,
+                    array_layer_count: 1,
+                    mip_level_count: 1,
+                    sample_count: 1,
+                    dimension: wgpu::TextureDimension::D2,
+                    format: wgpu::TextureFormat::Bgra8UnormSrgb,
+                    usage: wgpu::TextureUsage::COPY_DST
+                        | wgpu::TextureUsage::SAMPLED,
+                });
+
+                let slice = image.clone().into_raw();
+
+                let temp_buf = device
+                    .create_buffer_mapped(
+                        slice.len(),
+                        wgpu::BufferUsage::COPY_SRC,
+                    )
+                    .fill_from_slice(&slice[..]);
+
+                encoder.copy_buffer_to_texture(
+                    wgpu::BufferCopyView {
+                        buffer: &temp_buf,
+                        offset: 0,
+                        row_pitch: 4 * width as u32,
+                        image_height: height as u32,
+                    },
+                    wgpu::TextureCopyView {
+                        texture: &texture,
+                        array_layer: 0,
+                        mip_level: 0,
+                        origin: wgpu::Origin3d {
+                            x: 0.0,
+                            y: 0.0,
+                            z: 0.0,
+                        },
+                    },
+                    extent,
+                );
+
+                let bind_group =
+                    device.create_bind_group(&wgpu::BindGroupDescriptor {
+                        layout: texture_layout,
+                        bindings: &[wgpu::Binding {
+                            binding: 0,
+                            resource: wgpu::BindingResource::TextureView(
+                                &texture.create_default_view(),
+                            ),
+                        }],
+                    });
+
+                let bind_group = Rc::new(bind_group);
+
+                *self = Memory::Device {
+                    bind_group: bind_group.clone(),
+                    width,
+                    height,
+                };
+
+                bind_group
+            }
+            Memory::Device { bind_group, .. } => bind_group.clone(),
+        }
+    }
+}
+
+pub struct Image {
+    pub path: String,
+    pub position: [f32; 2],
+    pub scale: [f32; 2],
+}
+
+#[derive(Clone, Copy)]
+pub struct Vertex {
+    _position: [f32; 2],
+}
+
+const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
+
+const QUAD_VERTS: [Vertex; 4] = [
+    Vertex {
+        _position: [0.0, 0.0],
+    },
+    Vertex {
+        _position: [1.0, 0.0],
+    },
+    Vertex {
+        _position: [1.0, 1.0],
+    },
+    Vertex {
+        _position: [0.0, 1.0],
+    },
+];
+
+#[derive(Clone, Copy)]
+struct Instance {
+    position: [f32; 2],
+    scale: [f32; 2],
+}
-- 
cgit