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    IceWhirlwind = 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    Transformation = 66,
121    FireGigasAsh = 67,
122    FireGigasWhirlwind = 68,
123    FireGigasOverheat = 69,
124    FireGigasExplosion = 70,
125    FirePillarIndicator = 71,
126    FirePillar = 72,
127    FireLowShockwave = 73,
128    PipeSmoke = 74,
129    TrainSmoke = 75,
130}
131
132impl ParticleMode {
133    pub fn into_uint(self) -> u32 { self as u32 }
134}
135
136#[repr(C)]
137#[derive(Copy, Clone, Debug, Zeroable, Pod)]
138pub struct Instance {
139    // created_at time, so we can calculate time relativity, needed for relative animation.
140    // can save 32 bits per instance, for particles that are not relatively animated.
141    inst_time: f32,
142
143    // The lifespan in seconds of the particle
144    inst_lifespan: f32,
145
146    // a seed value for randomness
147    // can save 32 bits per instance, for particles that don't need randomness/uniqueness.
148    inst_entropy: f32,
149
150    // modes should probably be seperate shaders, as a part of scaling and optimisation efforts.
151    // can save 32 bits per instance, and have cleaner tailor made code.
152    inst_mode: i32,
153
154    // A direction for particles to move in
155    inst_dir: [f32; 3],
156
157    // a triangle is: f32 x 3 x 3 x 1  = 288 bits
158    // a quad is:     f32 x 3 x 3 x 2  = 576 bits
159    // a cube is:     f32 x 3 x 3 x 12 = 3456 bits
160    // this vec is:   f32 x 3 x 1 x 1  = 96 bits (per instance!)
161    // consider using a throw-away mesh and
162    // positioning the vertex verticies instead,
163    // if we have:
164    // - a triangle mesh, and 3 or more instances.
165    // - a quad mesh, and 6 or more instances.
166    // - a cube mesh, and 36 or more instances.
167    inst_pos: [f32; 3],
168}
169
170impl Instance {
171    pub fn new(
172        inst_time: f64,
173        lifespan: f32,
174        inst_mode: ParticleMode,
175        inst_pos: Vec3<f32>,
176    ) -> Self {
177        use rand::Rng;
178        Self {
179            inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
180            inst_lifespan: lifespan,
181            inst_entropy: rand::thread_rng().gen(),
182            inst_mode: inst_mode as i32,
183            inst_pos: inst_pos.into_array(),
184            inst_dir: [0.0, 0.0, 0.0],
185        }
186    }
187
188    pub fn new_directed(
189        inst_time: f64,
190        lifespan: f32,
191        inst_mode: ParticleMode,
192        inst_pos: Vec3<f32>,
193        inst_pos2: Vec3<f32>,
194    ) -> Self {
195        use rand::Rng;
196        Self {
197            inst_time: (inst_time % super::TIME_OVERFLOW) as f32,
198            inst_lifespan: lifespan,
199            inst_entropy: rand::thread_rng().gen(),
200            inst_mode: inst_mode as i32,
201            inst_pos: inst_pos.into_array(),
202            inst_dir: (inst_pos2 - inst_pos).into_array(),
203        }
204    }
205
206    fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
207        const ATTRIBUTES: [wgpu::VertexAttribute; 6] = wgpu::vertex_attr_array![2 => Float32, 3 => Float32, 4 => Float32, 5 => Sint32, 6 => Float32x3, 7 => Float32x3];
208        wgpu::VertexBufferLayout {
209            array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
210            step_mode: wgpu::VertexStepMode::Instance,
211            attributes: &ATTRIBUTES,
212        }
213    }
214}
215
216impl Default for Instance {
217    fn default() -> Self { Self::new(0.0, 0.0, ParticleMode::CampfireSmoke, Vec3::zero()) }
218}
219
220pub struct ParticlePipeline {
221    pub pipeline: wgpu::RenderPipeline,
222}
223
224impl ParticlePipeline {
225    pub fn new(
226        device: &wgpu::Device,
227        vs_module: &wgpu::ShaderModule,
228        fs_module: &wgpu::ShaderModule,
229        global_layout: &GlobalsLayouts,
230        aa_mode: AaMode,
231        format: wgpu::TextureFormat,
232    ) -> Self {
233        common_base::span!(_guard, "ParticlePipeline::new");
234        let render_pipeline_layout =
235            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
236                label: Some("Particle pipeline layout"),
237                push_constant_ranges: &[],
238                bind_group_layouts: &[&global_layout.globals, &global_layout.shadow_textures],
239            });
240
241        let samples = aa_mode.samples();
242
243        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
244            label: Some("Particle pipeline"),
245            layout: Some(&render_pipeline_layout),
246            vertex: wgpu::VertexState {
247                module: vs_module,
248                entry_point: "main",
249                buffers: &[Vertex::desc(), Instance::desc()],
250            },
251            primitive: wgpu::PrimitiveState {
252                topology: wgpu::PrimitiveTopology::TriangleList,
253                strip_index_format: None,
254                front_face: wgpu::FrontFace::Ccw,
255                cull_mode: Some(wgpu::Face::Back),
256                unclipped_depth: false,
257                polygon_mode: wgpu::PolygonMode::Fill,
258                conservative: false,
259            },
260            depth_stencil: Some(wgpu::DepthStencilState {
261                format: wgpu::TextureFormat::Depth32Float,
262                depth_write_enabled: true,
263                depth_compare: wgpu::CompareFunction::GreaterEqual,
264                stencil: wgpu::StencilState {
265                    front: wgpu::StencilFaceState::IGNORE,
266                    back: wgpu::StencilFaceState::IGNORE,
267                    read_mask: !0,
268                    write_mask: 0,
269                },
270                bias: wgpu::DepthBiasState {
271                    constant: 0,
272                    slope_scale: 0.0,
273                    clamp: 0.0,
274                },
275            }),
276            multisample: wgpu::MultisampleState {
277                count: samples,
278                mask: !0,
279                alpha_to_coverage_enabled: false,
280            },
281            fragment: Some(wgpu::FragmentState {
282                module: fs_module,
283                entry_point: "main",
284                targets: &[
285                    Some(wgpu::ColorTargetState {
286                        format,
287                        blend: Some(wgpu::BlendState {
288                            color: wgpu::BlendComponent {
289                                src_factor: wgpu::BlendFactor::SrcAlpha,
290                                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
291                                operation: wgpu::BlendOperation::Add,
292                            },
293                            alpha: wgpu::BlendComponent {
294                                src_factor: wgpu::BlendFactor::One,
295                                dst_factor: wgpu::BlendFactor::One,
296                                operation: wgpu::BlendOperation::Add,
297                            },
298                        }),
299                        write_mask: wgpu::ColorWrites::ALL,
300                    }),
301                    Some(wgpu::ColorTargetState {
302                        format: wgpu::TextureFormat::Rgba8Uint,
303                        blend: None,
304                        write_mask: wgpu::ColorWrites::ALL,
305                    }),
306                ],
307            }),
308            multiview: None,
309        });
310
311        Self {
312            pipeline: render_pipeline,
313        }
314    }
315}