use crate::ecs::camera::queries::query_active_camera_matrices;
use crate::ecs::sprite_particles::components::EmitterShape2D;
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use freecs::Entity;
use nalgebra_glm::{Mat4, Vec2};
use std::collections::HashMap;
use wgpu::util::DeviceExt;
const MAX_SPRITE_PARTICLES: u32 = 500_000;
const MAX_SPRITE_EMITTERS: u32 = 256;
const WORKGROUP_SIZE: u32 = 256;
struct EmitterSpawnState {
accumulated_spawn: f32,
has_fired: bool,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuSpriteParticle {
position: [f32; 4],
velocity: [f32; 4],
color: [f32; 4],
size_lifetime: [f32; 4],
emitter_data: [f32; 4],
physics: [f32; 4],
color_start: [f32; 4],
color_end: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuSpriteEmitterData {
position_anchor: [f32; 4],
velocity_min: [f32; 4],
velocity_max: [f32; 4],
lifetime_range: [f32; 4],
size_start: [f32; 4],
size_end: [f32; 4],
gravity_drag: [f32; 4],
rotation_range: [f32; 4],
rotation_speed_range: [f32; 4],
color_start: [f32; 4],
color_end: [f32; 4],
uv_min_max: [f32; 4],
texture_depth: [f32; 4],
shape_params: [f32; 4],
spawn_count: u32,
shape_type: u32,
blend_mode: u32,
_padding: u32,
}
impl Default for GpuSpriteEmitterData {
fn default() -> Self {
Self {
position_anchor: [0.0; 4],
velocity_min: [0.0; 4],
velocity_max: [0.0; 4],
lifetime_range: [1.0, 2.0, 0.0, 0.0],
size_start: [8.0, 8.0, 0.0, 0.0],
size_end: [2.0, 2.0, 0.0, 0.0],
gravity_drag: [0.0, -200.0, 0.1, 0.0],
rotation_range: [0.0; 4],
rotation_speed_range: [0.0; 4],
color_start: [1.0, 1.0, 1.0, 1.0],
color_end: [1.0, 1.0, 1.0, 0.0],
uv_min_max: [0.0, 0.0, 1.0, 1.0],
texture_depth: [0.0; 4],
shape_params: [0.0; 4],
spawn_count: 0,
shape_type: 0,
blend_mode: 0,
_padding: 0,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct SimParams {
delta_time: f32,
time: f32,
max_particles: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct DrawIndirect {
vertex_count: u32,
instance_count: u32,
first_vertex: u32,
first_instance: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct SpriteParticleUniforms {
view_projection: Mat4,
screen_size: Vec2,
atlas_slots_per_row: f32,
atlas_slot_uv_size: f32,
}
pub struct SpriteParticlePass {
_particle_buffer: wgpu::Buffer,
emitter_buffer: wgpu::Buffer,
params_buffer: wgpu::Buffer,
_free_indices_buffer: wgpu::Buffer,
_free_count_buffer: wgpu::Buffer,
_alive_indices_buffer: wgpu::Buffer,
_alive_count_buffer: wgpu::Buffer,
draw_indirect_buffer: wgpu::Buffer,
uniform_buffer: wgpu::Buffer,
update_pipeline: wgpu::ComputePipeline,
spawn_pipeline: wgpu::ComputePipeline,
reset_pipeline: wgpu::ComputePipeline,
render_pipeline_alpha: wgpu::RenderPipeline,
render_pipeline_additive: wgpu::RenderPipeline,
_compute_bind_group_layout: wgpu::BindGroupLayout,
compute_bind_group: wgpu::BindGroup,
_render_bind_group_layout: wgpu::BindGroupLayout,
render_bind_group: wgpu::BindGroup,
texture_bind_group_layout: wgpu::BindGroupLayout,
texture_bind_group: Option<wgpu::BindGroup>,
max_particles: u32,
emitter_data: Vec<GpuSpriteEmitterData>,
time: f32,
delta_time: f32,
emitter_spawn_states: HashMap<Entity, EmitterSpawnState>,
cached_view_projection: Option<Mat4>,
use_additive_blending: bool,
}
impl SpriteParticlePass {
pub fn new(device: &wgpu::Device, color_format: wgpu::TextureFormat) -> Self {
let max_particles = MAX_SPRITE_PARTICLES;
let particle_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Sprite Particle Buffer"),
size: (std::mem::size_of::<GpuSpriteParticle>() * max_particles as usize) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let emitter_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Sprite Emitter Buffer"),
size: (std::mem::size_of::<GpuSpriteEmitterData>() * MAX_SPRITE_EMITTERS as usize)
as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let params_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Sprite Particle Sim Params"),
size: std::mem::size_of::<SimParams>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut free_indices: Vec<u32> = (0..max_particles).collect();
free_indices.reverse();
let free_indices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Sprite Particle Free Indices"),
contents: bytemuck::cast_slice(&free_indices),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let free_count_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Sprite Particle Free Count"),
contents: bytemuck::cast_slice(&[max_particles]),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let alive_indices_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Sprite Particle Alive Indices"),
size: (std::mem::size_of::<u32>() * max_particles as usize) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let alive_count_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Sprite Particle Alive Count"),
contents: bytemuck::cast_slice(&[0u32]),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let draw_indirect_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Sprite Particle Draw Indirect"),
contents: bytemuck::cast_slice(&[DrawIndirect {
vertex_count: 6,
instance_count: 0,
first_vertex: 0,
first_instance: 0,
}]),
usage: wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::INDIRECT
| wgpu::BufferUsages::COPY_DST,
});
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Sprite Particle Uniforms"),
size: std::mem::size_of::<SpriteParticleUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let compute_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Sprite Particle Compute BGL"),
entries: &[
bgl_storage_rw(0, wgpu::ShaderStages::COMPUTE),
bgl_storage_ro(1, wgpu::ShaderStages::COMPUTE),
bgl_uniform(2, wgpu::ShaderStages::COMPUTE),
bgl_storage_rw(3, wgpu::ShaderStages::COMPUTE),
bgl_storage_rw(4, wgpu::ShaderStages::COMPUTE),
bgl_storage_rw(5, wgpu::ShaderStages::COMPUTE),
bgl_storage_rw(6, wgpu::ShaderStages::COMPUTE),
bgl_storage_rw(7, wgpu::ShaderStages::COMPUTE),
],
});
let compute_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Sprite Particle Compute BG"),
layout: &compute_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: particle_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: emitter_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: params_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: free_indices_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: free_count_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: alive_indices_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 6,
resource: alive_count_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 7,
resource: draw_indirect_buffer.as_entire_binding(),
},
],
});
let render_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Sprite Particle Render BGL"),
entries: &[
bgl_uniform(0, wgpu::ShaderStages::VERTEX),
bgl_storage_ro(1, wgpu::ShaderStages::VERTEX),
bgl_storage_ro(2, wgpu::ShaderStages::VERTEX),
],
});
let render_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Sprite Particle Render BG"),
layout: &render_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: particle_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: alive_indices_buffer.as_entire_binding(),
},
],
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Sprite Particle Texture BGL"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let compute_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Sprite Particle Compute Shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../../shaders/sprite_particle_update.wgsl").into(),
),
});
let render_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Sprite Particle Render Shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../../shaders/sprite_particle_render.wgsl").into(),
),
});
let compute_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Sprite Particle Compute PL"),
bind_group_layouts: &[&compute_bind_group_layout],
push_constant_ranges: &[],
});
let update_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Sprite Particle Update"),
layout: Some(&compute_pipeline_layout),
module: &compute_shader,
entry_point: Some("update"),
compilation_options: Default::default(),
cache: None,
});
let spawn_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Sprite Particle Spawn"),
layout: Some(&compute_pipeline_layout),
module: &compute_shader,
entry_point: Some("spawn"),
compilation_options: Default::default(),
cache: None,
});
let reset_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Sprite Particle Reset"),
layout: Some(&compute_pipeline_layout),
module: &compute_shader,
entry_point: Some("reset_counters"),
compilation_options: Default::default(),
cache: None,
});
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Sprite Particle Render PL"),
bind_group_layouts: &[&render_bind_group_layout, &texture_bind_group_layout],
push_constant_ranges: &[],
});
let render_pipeline_alpha = create_sprite_particle_pipeline(
device,
&render_pipeline_layout,
&render_shader,
color_format,
wgpu::BlendState::ALPHA_BLENDING,
"fs_main",
"Sprite Particle Pipeline (Alpha)",
);
let additive_blend = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
};
let render_pipeline_additive = create_sprite_particle_pipeline(
device,
&render_pipeline_layout,
&render_shader,
color_format,
additive_blend,
"fs_main_additive",
"Sprite Particle Pipeline (Additive)",
);
Self {
_particle_buffer: particle_buffer,
emitter_buffer,
params_buffer,
_free_indices_buffer: free_indices_buffer,
_free_count_buffer: free_count_buffer,
_alive_indices_buffer: alive_indices_buffer,
_alive_count_buffer: alive_count_buffer,
draw_indirect_buffer,
uniform_buffer,
update_pipeline,
spawn_pipeline,
reset_pipeline,
render_pipeline_alpha,
render_pipeline_additive,
_compute_bind_group_layout: compute_bind_group_layout,
compute_bind_group,
_render_bind_group_layout: render_bind_group_layout,
render_bind_group,
texture_bind_group_layout,
texture_bind_group: None,
max_particles,
emitter_data: Vec::new(),
time: 0.0,
delta_time: 0.0,
emitter_spawn_states: HashMap::new(),
cached_view_projection: None,
use_additive_blending: true,
}
}
pub fn set_atlas_bind_group(
&mut self,
device: &wgpu::Device,
atlas_view: &wgpu::TextureView,
atlas_sampler: &wgpu::Sampler,
) {
self.texture_bind_group = Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Sprite Particle Texture BG"),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(atlas_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(atlas_sampler),
},
],
}));
}
fn update_emitters(&mut self, world: &World) {
self.emitter_data.clear();
let emitter_entities: Vec<_> = world
.query_entities(crate::ecs::SPRITE_PARTICLE_EMITTER)
.collect();
for entity in &emitter_entities {
if self.emitter_data.len() >= MAX_SPRITE_EMITTERS as usize {
break;
}
let Some(emitter) = world.get_sprite_particle_emitter(*entity) else {
continue;
};
if !emitter.enabled {
self.emitter_spawn_states.remove(entity);
continue;
}
if let Some(visibility) = world.get_visibility(*entity)
&& !visibility.visible
{
continue;
}
let render_layer = world
.get_render_layer(*entity)
.map(|layer| layer.0)
.unwrap_or(crate::ecs::render_layer::components::RenderLayer::WORLD);
let should_render = match render_layer {
crate::ecs::render_layer::components::RenderLayer::WORLD => {
world.resources.graphics.render_layer_world_enabled
}
crate::ecs::render_layer::components::RenderLayer::OVERLAY => {
world.resources.graphics.render_layer_overlay_enabled
}
_ => true,
};
if !should_render {
continue;
}
let state = self
.emitter_spawn_states
.entry(*entity)
.or_insert(EmitterSpawnState {
accumulated_spawn: 0.0,
has_fired: false,
});
if emitter.one_shot && state.has_fired {
continue;
}
if emitter.burst_count > 0 && !state.has_fired {
state.accumulated_spawn += emitter.burst_count as f32;
state.has_fired = true;
} else if emitter.spawn_rate > 0.0 {
state.accumulated_spawn += emitter.spawn_rate * self.delta_time;
}
let spawn_count = state.accumulated_spawn.floor().min(256.0) as u32;
state.accumulated_spawn -= spawn_count as f32;
if spawn_count == 0 {
continue;
}
let (shape_type, shape_params) = match emitter.shape {
EmitterShape2D::Point => (0u32, [0.0f32; 4]),
EmitterShape2D::Circle { radius } => (1u32, [radius, 0.0, 0.0, 0.0]),
EmitterShape2D::Rectangle { half_extents } => {
(2u32, [half_extents.x, half_extents.y, 0.0, 0.0])
}
};
let blend_mode_id = match emitter.blend_mode {
crate::ecs::sprite::components::SpriteBlendMode::Alpha => 0u32,
crate::ecs::sprite::components::SpriteBlendMode::Additive => 1u32,
crate::ecs::sprite::components::SpriteBlendMode::Multiply => 2u32,
crate::ecs::sprite::components::SpriteBlendMode::Screen => 3u32,
_ => 0u32,
};
let gpu_emitter = GpuSpriteEmitterData {
position_anchor: [emitter.anchor.x, emitter.anchor.y, 0.0, 0.0],
velocity_min: [emitter.velocity_min.x, emitter.velocity_min.y, 0.0, 0.0],
velocity_max: [emitter.velocity_max.x, emitter.velocity_max.y, 0.0, 0.0],
lifetime_range: [emitter.lifetime_min, emitter.lifetime_max, 0.0, 0.0],
size_start: [emitter.size_start.x, emitter.size_start.y, 0.0, 0.0],
size_end: [emitter.size_end.x, emitter.size_end.y, 0.0, 0.0],
gravity_drag: [emitter.gravity.x, emitter.gravity.y, emitter.drag, 0.0],
rotation_range: [emitter.rotation_min, emitter.rotation_max, 0.0, 0.0],
rotation_speed_range: [
emitter.rotation_speed_min,
emitter.rotation_speed_max,
0.0,
0.0,
],
color_start: emitter.color.start,
color_end: emitter.color.end,
uv_min_max: [
emitter.uv_min.x,
emitter.uv_min.y,
emitter.uv_max.x,
emitter.uv_max.y,
],
texture_depth: [emitter.texture_index as f32, emitter.depth, 0.0, 0.0],
shape_params,
spawn_count,
shape_type,
blend_mode: blend_mode_id,
_padding: 0,
};
self.emitter_data.push(gpu_emitter);
}
self.emitter_spawn_states
.retain(|entity, _| emitter_entities.contains(entity));
}
}
impl PassNode<World> for SpriteParticlePass {
fn name(&self) -> &str {
"sprite_particle_pass"
}
fn reads(&self) -> Vec<&str> {
vec![]
}
fn writes(&self) -> Vec<&str> {
vec![]
}
fn reads_writes(&self) -> Vec<&str> {
vec!["color"]
}
fn prepare(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, world: &World) {
self.delta_time = world.resources.window.timing.delta_time;
self.time += self.delta_time;
self.update_emitters(world);
if !self.emitter_data.is_empty() {
queue.write_buffer(
&self.emitter_buffer,
0,
bytemuck::cast_slice(&self.emitter_data),
);
}
let params = SimParams {
delta_time: self.delta_time,
time: self.time,
max_particles: self.max_particles,
_padding: 0,
};
queue.write_buffer(&self.params_buffer, 0, bytemuck::cast_slice(&[params]));
let camera_matrices = query_active_camera_matrices(world);
let (view_projection, screen_size) =
if let Some((w, h)) = world.resources.window.cached_viewport_size {
let width = w as f32;
let height = h as f32;
let vp = if let Some(ref matrices) = camera_matrices {
matrices.projection * matrices.view
} else {
nalgebra_glm::ortho_rh_zo(0.0, width, 0.0, height, -1.0, 1.0)
};
(vp, Vec2::new(width, height))
} else {
(
nalgebra_glm::ortho_rh_zo(0.0, 1.0, 0.0, 1.0, -1.0, 1.0),
Vec2::new(1.0, 1.0),
)
};
self.cached_view_projection = Some(view_projection);
let atlas_slots_per_row =
(crate::render::wgpu::sprite_texture_atlas::SPRITE_ATLAS_TOTAL_SLOTS as f32)
.sqrt()
.ceil();
let uniforms = SpriteParticleUniforms {
view_projection,
screen_size,
atlas_slots_per_row,
atlas_slot_uv_size: 1.0 / atlas_slots_per_row,
};
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
}
fn execute<'r, 'e>(
&mut self,
context: PassExecutionContext<'r, 'e, World>,
) -> crate::render::wgpu::rendergraph::Result<
Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
> {
{
let mut compute_pass =
context
.encoder
.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Sprite Particle Reset"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.reset_pipeline);
compute_pass.set_bind_group(0, &self.compute_bind_group, &[]);
compute_pass.dispatch_workgroups(1, 1, 1);
}
{
let mut compute_pass =
context
.encoder
.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Sprite Particle Update"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.update_pipeline);
compute_pass.set_bind_group(0, &self.compute_bind_group, &[]);
let workgroups = self.max_particles.div_ceil(WORKGROUP_SIZE);
compute_pass.dispatch_workgroups(workgroups, 1, 1);
}
if !self.emitter_data.is_empty() {
let mut compute_pass =
context
.encoder
.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Sprite Particle Spawn"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.spawn_pipeline);
compute_pass.set_bind_group(0, &self.compute_bind_group, &[]);
compute_pass.dispatch_workgroups(self.emitter_data.len() as u32, 1, 1);
}
if let Some(ref texture_bind_group) = self.texture_bind_group {
let color_view = context.get_texture_view("color")?;
let mut render_pass = context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Sprite Particle Render"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
let pipeline = if self.use_additive_blending {
&self.render_pipeline_additive
} else {
&self.render_pipeline_alpha
};
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, &self.render_bind_group, &[]);
render_pass.set_bind_group(1, texture_bind_group, &[]);
render_pass.draw_indirect(&self.draw_indirect_buffer, 0);
}
Ok(vec![])
}
}
fn bgl_storage_rw(binding: u32, visibility: wgpu::ShaderStages) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
fn bgl_storage_ro(binding: u32, visibility: wgpu::ShaderStages) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
fn bgl_uniform(binding: u32, visibility: wgpu::ShaderStages) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
}
fn create_sprite_particle_pipeline(
device: &wgpu::Device,
layout: &wgpu::PipelineLayout,
shader: &wgpu::ShaderModule,
color_format: wgpu::TextureFormat,
blend_state: wgpu::BlendState,
fragment_entry: &str,
label: &str,
) -> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(layout),
vertex: wgpu::VertexState {
module: shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: shader,
entry_point: Some(fragment_entry),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(blend_state),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
})
}