/* ENSnano, a 3d graphical application for DNA nanostructures. Copyright (C) 2021 Nicolas Levy and Nicolas Schabanel 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 . */ 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, /// A possible updates to the instances to be drawn. Must be taken into account before drawing /// next frame new_instances: Option>>, /// 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, } pub enum CircleKind { FullCircle, RotationWidget, } impl CircleDrawer { pub fn new( device: Rc, queue: Rc, 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>) { 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"), }) } }