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