Raw File
/*
ENSnano, a 3d graphical application for DNA nanostructures.
    Copyright (C) 2021  Nicolas Levy <nicolaspierrelevy@gmail.com> and Nicolas Schabanel <nicolas.schabanel@ens-lyon.fr>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
use iced_wgpu::wgpu;
use std::rc::Rc;
use ultraviolet::Vec2;
use wgpu::{include_spirv, BindGroupLayout, Device, Queue, RenderPass, RenderPipeline};

use crate::consts::*;
use crate::utils::bindgroup_manager::DynamicBindGroup;
use crate::utils::texture::Texture;

#[repr(C)]
#[derive(Clone, Copy)]
pub struct CircleInstance {
    pub center: Vec2,
    pub radius: f32,
    pub angle: f32,
    pub z_index: i32,
    color: u32,
}

impl CircleInstance {
    pub fn new(center: Vec2, radius: f32, z_index: i32, color: u32) -> Self {
        Self {
            center,
            radius,
            angle: 0.,
            z_index,
            color,
        }
    }

    pub fn set_radius(&mut self, radius: f32) {
        self.radius = radius;
    }

    pub fn set_color(&mut self, color: u32) {
        self.color = color
    }

    #[allow(dead_code)]
    pub fn angle(self, angle: f32) -> Self {
        Self { angle, ..self }
    }

    pub fn in_rectangle(&self, c1: &Vec2, c2: &Vec2) -> bool {
        let min_x = c1.x.min(c2.x);
        let max_x = c1.x.max(c2.x);
        let min_y = c1.y.min(c2.y);
        let max_y = c1.y.max(c2.y);

        (self.center.x >= min_x
            && self.center.x <= max_x
            && self.center.y >= min_y
            && self.center.y <= max_y)
            || (self.center - Vec2::new(min_x, min_y)).mag() <= self.radius
            || (self.center - Vec2::new(min_x, max_y)).mag() <= self.radius
            || (self.center - Vec2::new(max_x, min_y)).mag() <= self.radius
            || (self.center - Vec2::new(max_x, max_y)).mag() <= self.radius
    }
}

unsafe impl bytemuck::Zeroable for CircleInstance {}
unsafe impl bytemuck::Pod for CircleInstance {}

pub struct CircleDrawer {
    device: Rc<Device>,
    /// A possible updates to the instances to be drawn. Must be taken into account before drawing
    /// next frame
    new_instances: Option<Rc<Vec<CircleInstance>>>,
    /// The number of instance to draw.
    number_instances: usize,
    /// The data sent the the GPU
    instances_bg: DynamicBindGroup,
    /// The pipeline created by `self`
    pipeline: Option<RenderPipeline>,
}

pub enum CircleKind {
    FullCircle,
    RotationWidget,
}

impl CircleDrawer {
    pub fn new(
        device: Rc<Device>,
        queue: Rc<Queue>,
        globals_layout: &BindGroupLayout,
        circle_kind: CircleKind,
    ) -> Self {
        let instances_bg = DynamicBindGroup::new(device.clone(), queue.clone());

        let mut ret = Self {
            device,
            new_instances: None,
            number_instances: 0,
            pipeline: None,
            instances_bg,
        };
        let pipeline = ret.create_pipeline(globals_layout, circle_kind);
        ret.pipeline = Some(pipeline);
        ret
    }

    pub fn draw<'a>(&'a mut self, render_pass: &mut RenderPass<'a>) {
        self.update_instances();
        render_pass.set_pipeline(self.pipeline.as_ref().unwrap());
        render_pass.set_bind_group(1, self.instances_bg.get_bindgroup(), &[]);
        render_pass.draw(0..4, 0..self.number_instances as u32);
    }

    pub fn new_instances(&mut self, instances: Rc<Vec<CircleInstance>>) {
        self.new_instances = Some(instances)
    }

    fn update_instances(&mut self) {
        if let Some(ref instances) = self.new_instances {
            self.number_instances = instances.len();
            let instances_data: Vec<_> = instances.iter().cloned().collect();
            self.instances_bg.update(instances_data.as_slice());
        }
    }

    /// Create a render pipepline. This function is meant to be called once, before drawing for the
    /// first time.
    fn create_pipeline(
        &self,
        globals_layout: &BindGroupLayout,
        circle_kind: CircleKind,
    ) -> RenderPipeline {
        let vertex_module = self
            .device
            .create_shader_module(&include_spirv!("circle.vert.spv"));

        let fragment_module = match circle_kind {
            CircleKind::FullCircle => self
                .device
                .create_shader_module(&include_spirv!("circle.frag.spv")),
            CircleKind::RotationWidget => self
                .device
                .create_shader_module(&include_spirv!("rotation_widget.frag.spv")),
        };

        let render_pipeline_layout =
            self.device
                .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                    bind_group_layouts: &[globals_layout, &self.instances_bg.get_layout()],
                    push_constant_ranges: &[],
                    label: Some("render_pipeline_layout"),
                });

        let format = wgpu::TextureFormat::Bgra8UnormSrgb;

        let color_blend = wgpu::BlendState {
            src_factor: wgpu::BlendFactor::SrcAlpha,
            dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
            operation: wgpu::BlendOperation::Add,
        };

        let alpha_blend = wgpu::BlendState {
            src_factor: wgpu::BlendFactor::One,
            dst_factor: wgpu::BlendFactor::One,
            operation: wgpu::BlendOperation::Add,
        };
        let targets = &[wgpu::ColorTargetState {
            format,
            color_blend,
            alpha_blend,
            write_mask: wgpu::ColorWrite::ALL,
        }];

        let primitive = wgpu::PrimitiveState {
            topology: wgpu::PrimitiveTopology::TriangleStrip,
            strip_index_format: Some(wgpu::IndexFormat::Uint16),
            front_face: wgpu::FrontFace::Ccw,
            cull_mode: wgpu::CullMode::None,
            ..Default::default()
        };

        self.device
            .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
                layout: Some(&render_pipeline_layout),
                vertex: wgpu::VertexState {
                    module: &vertex_module,
                    entry_point: "main",
                    buffers: &[],
                },
                fragment: Some(wgpu::FragmentState {
                    module: &fragment_module,
                    entry_point: "main",
                    targets,
                }),
                primitive,
                depth_stencil: Some(wgpu::DepthStencilState {
                    format: Texture::DEPTH_FORMAT,
                    depth_write_enabled: true,
                    depth_compare: wgpu::CompareFunction::Less,
                    stencil: Default::default(),
                    bias: Default::default(),
                    clamp_depth: false,
                }),
                multisample: wgpu::MultisampleState {
                    count: SAMPLE_COUNT,
                    mask: !0,
                    alpha_to_coverage_enabled: false,
                },
                label: Some("render pipeline"),
            })
    }
}
back to top