Skip to main content

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