use crate::render::wgpu::sprite_texture_atlas::SpriteTextureAtlas;
use nalgebra_glm::Vec2;
use wgpu::util::DeviceExt;
use super::{
GlobalUniforms, SpriteInstance, SpritePass, SpritePipelineConfig, SpriteVertex,
create_sprite_pipeline, stencil_depth_state_normal, stencil_depth_state_test,
stencil_depth_state_write,
};
impl SpritePass {
pub fn new(
device: &wgpu::Device,
color_format: wgpu::TextureFormat,
queue: &wgpu::Queue,
) -> Self {
let max_instances = 1_000_000;
let mut atlas = SpriteTextureAtlas::new(
device,
crate::render::wgpu::sprite_texture_atlas::SPRITE_ATLAS_TOTAL_SLOTS,
crate::render::wgpu::sprite_texture_atlas::SPRITE_ATLAS_SLOT_SIZE,
);
let total_pixels = (atlas.atlas_size.0 * atlas.atlas_size.1 * 4) as usize;
let white_texture = vec![255u8; total_pixels];
queue.write_texture(
atlas.texture.as_image_copy(),
&white_texture,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(atlas.atlas_size.0 * 4),
rows_per_image: Some(atlas.atlas_size.1),
},
wgpu::Extent3d {
width: atlas.atlas_size.0,
height: atlas.atlas_size.1,
depth_or_array_layers: 1,
},
);
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Sprite Uniform Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(std::mem::size_of::<
GlobalUniforms,
>()
as u64),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: wgpu::BufferSize::new(std::mem::size_of::<
SpriteInstance,
>()
as u64),
},
count: None,
},
],
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Sprite Texture Bind Group Layout"),
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 background_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Sprite Background Bind Group Layout"),
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 standard_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Sprite Pipeline Layout"),
bind_group_layouts: &[
Some(&uniform_bind_group_layout),
Some(&texture_bind_group_layout),
],
immediate_size: 0,
});
let advanced_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Sprite Advanced Pipeline Layout"),
bind_group_layouts: &[
Some(&uniform_bind_group_layout),
Some(&texture_bind_group_layout),
Some(&background_bind_group_layout),
],
immediate_size: 0,
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Sprite Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../../../shaders/sprite.wgsl").into()),
});
let vertices = [
SpriteVertex {
offset: Vec2::new(-0.5, -0.5),
uv: Vec2::new(0.0, 1.0),
},
SpriteVertex {
offset: Vec2::new(0.5, -0.5),
uv: Vec2::new(1.0, 1.0),
},
SpriteVertex {
offset: Vec2::new(0.5, 0.5),
uv: Vec2::new(1.0, 0.0),
},
SpriteVertex {
offset: Vec2::new(-0.5, 0.5),
uv: Vec2::new(0.0, 0.0),
},
];
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Sprite Vertex Buffer"),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Sprite Index Buffer"),
contents: bytemuck::cast_slice(&indices),
usage: wgpu::BufferUsages::INDEX,
});
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 multiply_blend = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Dst,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
let screen_blend = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrc,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
let blend_states = [
wgpu::BlendState::ALPHA_BLENDING,
additive_blend,
multiply_blend,
screen_blend,
];
let blend_labels = ["Alpha", "Additive", "Multiply", "Screen"];
let pipelines_normal = std::array::from_fn(|blend_index| {
create_sprite_pipeline(
device,
&SpritePipelineConfig {
layout: &standard_pipeline_layout,
shader: &shader,
color_format,
blend_state: blend_states[blend_index],
depth_stencil: Some(stencil_depth_state_normal()),
color_write_mask: wgpu::ColorWrites::ALL,
label: &format!("Sprite Pipeline Normal ({})", blend_labels[blend_index]),
fragment_entry: "fs_main",
},
)
});
let pipelines_stencil_write = std::array::from_fn(|blend_index| {
create_sprite_pipeline(
device,
&SpritePipelineConfig {
layout: &standard_pipeline_layout,
shader: &shader,
color_format,
blend_state: blend_states[blend_index],
depth_stencil: Some(stencil_depth_state_write()),
color_write_mask: wgpu::ColorWrites::empty(),
label: &format!(
"Sprite Pipeline Stencil Write ({})",
blend_labels[blend_index]
),
fragment_entry: "fs_main",
},
)
});
let pipelines_stencil_test = std::array::from_fn(|blend_index| {
create_sprite_pipeline(
device,
&SpritePipelineConfig {
layout: &standard_pipeline_layout,
shader: &shader,
color_format,
blend_state: blend_states[blend_index],
depth_stencil: Some(stencil_depth_state_test()),
color_write_mask: wgpu::ColorWrites::ALL,
label: &format!(
"Sprite Pipeline Stencil Test ({})",
blend_labels[blend_index]
),
fragment_entry: "fs_main",
},
)
});
let pipeline_advanced_normal = create_sprite_pipeline(
device,
&SpritePipelineConfig {
layout: &advanced_pipeline_layout,
shader: &shader,
color_format,
blend_state: wgpu::BlendState::REPLACE,
depth_stencil: Some(stencil_depth_state_normal()),
color_write_mask: wgpu::ColorWrites::ALL,
label: "Sprite Pipeline Advanced Normal",
fragment_entry: "fs_main_advanced",
},
);
let pipeline_advanced_stencil_test = create_sprite_pipeline(
device,
&SpritePipelineConfig {
layout: &advanced_pipeline_layout,
shader: &shader,
color_format,
blend_state: wgpu::BlendState::REPLACE,
depth_stencil: Some(stencil_depth_state_test()),
color_write_mask: wgpu::ColorWrites::ALL,
label: "Sprite Pipeline Advanced Stencil Test",
fragment_entry: "fs_main_advanced",
},
);
let global_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Sprite Global Uniform Buffer"),
size: std::mem::size_of::<GlobalUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Sprite Instance Buffer"),
size: (std::mem::size_of::<SpriteInstance>() * max_instances) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Sprite Uniform Bind Group"),
layout: &uniform_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: global_uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: instance_buffer.as_entire_binding(),
},
],
});
let initial_stencil_size = (1u32, 1u32);
let stencil_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Sprite Stencil Texture"),
size: wgpu::Extent3d {
width: initial_stencil_size.0,
height: initial_stencil_size.1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth24PlusStencil8,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let stencil_view = stencil_texture.create_view(&wgpu::TextureViewDescriptor::default());
let initial_bg_size = (1u32, 1u32);
let background_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Sprite Background Texture"),
size: wgpu::Extent3d {
width: initial_bg_size.0,
height: initial_bg_size.1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba16Float,
usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let background_view =
background_texture.create_view(&wgpu::TextureViewDescriptor::default());
let background_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Sprite Background Sampler"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
crate::render::wgpu::sprite_shapes::upload_builtin_shapes(queue, &mut atlas);
let mut sprite_pass = Self {
pipelines_normal,
pipelines_stencil_write,
pipelines_stencil_test,
pipeline_advanced_normal,
pipeline_advanced_stencil_test,
global_uniform_buffer,
instance_buffer,
vertex_buffer,
index_buffer,
uniform_bind_group,
texture_bind_group_layout,
texture_bind_group: None,
background_bind_group_layout,
background_texture,
background_view,
background_sampler,
background_bind_group: None,
background_texture_size: initial_bg_size,
max_instances,
instance_data: Vec::new(),
blend_modes: Vec::new(),
stencil_modes: Vec::new(),
stencil_references: Vec::new(),
clip_rects: Vec::new(),
draw_batches: Vec::new(),
atlas,
stencil_texture,
stencil_view,
stencil_texture_size: initial_stencil_size,
cached_view_projection: None,
cached_surface_width: 0,
cached_surface_height: 0,
sort_indices: Vec::new(),
sorted_instances_scratch: Vec::new(),
sorted_blend_modes_scratch: Vec::new(),
sorted_stencil_modes_scratch: Vec::new(),
sorted_stencil_references_scratch: Vec::new(),
sorted_clip_rects_scratch: Vec::new(),
};
sprite_pass.update_texture_bind_group(device);
sprite_pass
}
}