veloren_voxygen/render/pipelines/
particle.rs

1use super::super::{AaMode, GlobalsLayouts, Vertex as VertexTrait};
2use bytemuck::{Pod, Zeroable};
3use std::mem;
4use vek::*;
5
6#[repr(C)]
7#[derive(Copy, Clone, Debug, Zeroable, Pod)]
8pub struct Vertex {
9    pub pos: [f32; 3],
10    // ____BBBBBBBBGGGGGGGGRRRRRRRR
11    // col: u32 = "v_col",
12    // ...AANNN
13    // A = AO
14    // N = Normal
15    norm_ao: u32,
16}
17
18impl Vertex {
19    #[expect(clippy::collapsible_else_if)]
20    pub fn new(pos: Vec3<f32>, norm: Vec3<f32>) -> Self {
21        #[expect(clippy::bool_to_int_with_if)]
22        let norm_bits = if norm.x != 0.0 {
23            if norm.x < 0.0 { 0 } else { 1 }
24        } else if norm.y != 0.0 {
25            if norm.y < 0.0 { 2 } else { 3 }
26        } else {
27            if norm.z < 0.0 { 4 } else { 5 }
28        };
29
30        Self {
31            pos: pos.into_array(),
32            norm_ao: norm_bits,
33        }
34    }
35
36    fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
37        const ATTRIBUTES: [wgpu::VertexAttribute; 2] =
38            wgpu::vertex_attr_array![0 => Float32x3, 1 => Uint32];
39        wgpu::VertexBufferLayout {
40            array_stride: Self::STRIDE,
41            step_mode: wgpu::VertexStepMode::Vertex,
42            attributes: &ATTRIBUTES,
43        }
44    }
45}
46
47impl VertexTrait for Vertex {
48    const QUADS_INDEX: Option<wgpu::IndexFormat> = Some(wgpu::IndexFormat::Uint16);
49    const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
50}
51
52#[derive(Copy, Clone)]
53pub enum ParticleMode {
54    CampfireSmoke = 0,
55    CampfireFire = 1,
56    GunPowderSpark = 2,
57    Shrapnel = 3,
58    FireworkBlue = 4,
59    FireworkGreen = 5,
60    FireworkPurple = 6,
61    FireworkRed = 7,
62    FireworkWhite = 8,
63    FireworkYellow = 9,
64    Leaf = 10,
65    Firefly = 11,
66    Bee = 12,
67    GroundShockwave = 13,
68    EnergyHealing = 14,
69    EnergyNature = 15,
70    FlameThrower = 16,
71    FireShockwave = 17,
72    FireBowl = 18,
73    Snow = 19,
74    Explosion = 20,
75    Ice = 21,
76    LifestealBeam = 22,
77    CultistFlame = 23,
78    StaticSmoke = 24,
79    Blood = 25,
80    Enraged = 26,
81    BigShrapnel = 27,
82    Laser = 28,
83    Bubbles = 29,
84    Water = 30,
85    IceSpikes = 31,
86    Drip = 32,
87    Tornado = 33,
88    Death = 34,
89    EnergyBuffing = 35,
90    WebStrand = 36,
91    BlackSmoke = 37,
92    Lightning = 38,
93    Steam = 39,
94    BarrelOrgan = 40,
95    PotionSickness = 41,
96    GigaSnow = 42,
97    CyclopsCharge = 43,
98    SnowStorm = 44,
99    PortalFizz = 45,
100    Ink = 46,
101    Whirlwind = 47,
102    FieryBurst = 48,
103    FieryBurstVortex = 49,
104    FieryBurstSparks = 50,
105    FieryBurstAsh = 51,
106    FieryTornado = 52,
107    PhoenixCloud = 53,
108    FieryDropletTrace = 54,
109    EnergyPhoenix = 55,
110    PhoenixBeam = 56,
111    PhoenixBuildUpAim = 57,
112    ClayShrapnel = 58,
113    Airflow = 59,
114    Spore = 60,
115    SurpriseEgg = 61,
116    FlameTornado = 62,
117    Poison = 63,
118    WaterFoam = 64,
119    EngineJet = 65,
120}
121
122impl ParticleMode {
123    pub fn into_uint(self) -> u32 { self as u32 }
124}
125
126#[repr(C)]
127#[derive(Copy, Clone, Debug, Zeroable, Pod)]
128pub struct Instance {
129    // created_at time, so we can calculate time relativity, needed for relative animation.
130    // can save 32 bits per instance, for particles that are not relatively animated.
131    inst_time: f32,
132
133    // The lifespan in seconds of the particle
134    inst_lifespan: f32,
135
136    // a seed value for randomness
137    // can save 32 bits per instance, for particles that don't need randomness/uniqueness.
138    inst_entropy: f32,
139
140    // modes should probably be seperate shaders, as a part of scaling and optimisation efforts.
141    // can save 32 bits per instance, and have cleaner tailor made code.
142    inst_mode: i32,
143
144    // A direction for particles to move in
145    inst_dir: [f32; 3],
146
147    // a triangle is: f32 x 3 x 3 x 1  = 288 bits
148    // a quad is:     f32 x 3 x 3 x 2  = 576 bits
149    // a cube is:     f32 x 3 x 3 x 12 = 3456 bits
150    // this vec is:   f32 x 3 x 1 x 1  = 96 bits (per instance!)
151    // consider using a throw-away mesh and
152    // positioning the vertex verticies instead,
153    // if we have:
154    // - a triangle mesh, and 3 or more instances.
155    // - a quad mesh, and 6 or more instances.
156    // - a cube mesh, and 36 or more instances.
157    inst_pos: [f32; 3],
158}
159
160impl Instance {
161    pub fn new(
162        inst_time: f64,
163        lifespan: f32,
164        inst_mode: ParticleMode,
165        inst_pos: Vec3<f32>,
166    ) -> Self {
167        use rand::Rng;
168        Self {
169            inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
170            inst_lifespan: lifespan,
171            inst_entropy: rand::thread_rng().gen(),
172            inst_mode: inst_mode as i32,
173            inst_pos: inst_pos.into_array(),
174            inst_dir: [0.0, 0.0, 0.0],
175        }
176    }
177
178    pub fn new_directed(
179        inst_time: f64,
180        lifespan: f32,
181        inst_mode: ParticleMode,
182        inst_pos: Vec3<f32>,
183        inst_pos2: Vec3<f32>,
184    ) -> Self {
185        use rand::Rng;
186        Self {
187            inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
188            inst_lifespan: lifespan,
189            inst_entropy: rand::thread_rng().gen(),
190            inst_mode: inst_mode as i32,
191            inst_pos: inst_pos.into_array(),
192            inst_dir: (inst_pos2 - inst_pos).into_array(),
193        }
194    }
195
196    fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
197        const ATTRIBUTES: [wgpu::VertexAttribute; 6] = wgpu::vertex_attr_array![2 => Float32, 3 => Float32, 4 => Float32, 5 => Sint32, 6 => Float32x3, 7 => Float32x3];
198        wgpu::VertexBufferLayout {
199            array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
200            step_mode: wgpu::VertexStepMode::Instance,
201            attributes: &ATTRIBUTES,
202        }
203    }
204}
205
206impl Default for Instance {
207    fn default() -> Self { Self::new(0.0, 0.0, ParticleMode::CampfireSmoke, Vec3::zero()) }
208}
209
210pub struct ParticlePipeline {
211    pub pipeline: wgpu::RenderPipeline,
212}
213
214impl ParticlePipeline {
215    pub fn new(
216        device: &wgpu::Device,
217        vs_module: &wgpu::ShaderModule,
218        fs_module: &wgpu::ShaderModule,
219        global_layout: &GlobalsLayouts,
220        aa_mode: AaMode,
221        format: wgpu::TextureFormat,
222    ) -> Self {
223        common_base::span!(_guard, "ParticlePipeline::new");
224        let render_pipeline_layout =
225            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
226                label: Some("Particle pipeline layout"),
227                push_constant_ranges: &[],
228                bind_group_layouts: &[&global_layout.globals, &global_layout.shadow_textures],
229            });
230
231        let samples = aa_mode.samples();
232
233        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
234            label: Some("Particle pipeline"),
235            layout: Some(&render_pipeline_layout),
236            vertex: wgpu::VertexState {
237                module: vs_module,
238                entry_point: "main",
239                buffers: &[Vertex::desc(), Instance::desc()],
240            },
241            primitive: wgpu::PrimitiveState {
242                topology: wgpu::PrimitiveTopology::TriangleList,
243                strip_index_format: None,
244                front_face: wgpu::FrontFace::Ccw,
245                cull_mode: Some(wgpu::Face::Back),
246                unclipped_depth: false,
247                polygon_mode: wgpu::PolygonMode::Fill,
248                conservative: false,
249            },
250            depth_stencil: Some(wgpu::DepthStencilState {
251                format: wgpu::TextureFormat::Depth32Float,
252                depth_write_enabled: true,
253                depth_compare: wgpu::CompareFunction::GreaterEqual,
254                stencil: wgpu::StencilState {
255                    front: wgpu::StencilFaceState::IGNORE,
256                    back: wgpu::StencilFaceState::IGNORE,
257                    read_mask: !0,
258                    write_mask: 0,
259                },
260                bias: wgpu::DepthBiasState {
261                    constant: 0,
262                    slope_scale: 0.0,
263                    clamp: 0.0,
264                },
265            }),
266            multisample: wgpu::MultisampleState {
267                count: samples,
268                mask: !0,
269                alpha_to_coverage_enabled: false,
270            },
271            fragment: Some(wgpu::FragmentState {
272                module: fs_module,
273                entry_point: "main",
274                targets: &[
275                    Some(wgpu::ColorTargetState {
276                        format,
277                        blend: Some(wgpu::BlendState {
278                            color: wgpu::BlendComponent {
279                                src_factor: wgpu::BlendFactor::SrcAlpha,
280                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
281                                operation: wgpu::BlendOperation::Add,
282                            },
283                            alpha: wgpu::BlendComponent {
284                                src_factor: wgpu::BlendFactor::One,
285                                dst_factor: wgpu::BlendFactor::One,
286                                operation: wgpu::BlendOperation::Add,
287                            },
288                        }),
289                        write_mask: wgpu::ColorWrites::ALL,
290                    }),
291                    Some(wgpu::ColorTargetState {
292                        format: wgpu::TextureFormat::Rgba8Uint,
293                        blend: None,
294                        write_mask: wgpu::ColorWrites::ALL,
295                    }),
296                ],
297            }),
298            multiview: None,
299        });
300
301        Self {
302            pipeline: render_pipeline,
303        }
304    }
305}