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