use super::*;
impl ViewportGpuResources {
pub(crate) fn ensure_sprite_pipelines(&mut self, device: &wgpu::Device) {
if self.sprite_bgl.is_some() {
return;
}
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("sprite_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let soft_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("sprite_soft_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
],
});
let soft_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("sprite_soft_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let fallback_tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("sprite_soft_fallback_tex"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let fallback_view = fallback_tex.create_view(&wgpu::TextureViewDescriptor {
aspect: wgpu::TextureAspect::DepthOnly,
..Default::default()
});
let fallback_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("sprite_soft_fallback_bg"),
layout: &soft_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&fallback_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&soft_sampler),
},
],
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("sprite_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!(concat!(env!("OUT_DIR"), "/sprite.wgsl")).into(),
),
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("sprite_pipeline_layout"),
bind_group_layouts: &[&self.camera_bind_group_layout, &bgl, &soft_bgl],
push_constant_ranges: &[],
});
let vert_attrs = [wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
}];
let vertex_buffers = [wgpu::VertexBufferLayout {
array_stride: 12,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &vert_attrs,
}];
let sample_count = self.sample_count;
let additive_blend = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
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 premultiplied_blend = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
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 make_sprite =
|fmt: wgpu::TextureFormat, depth_write: bool, blend: wgpu::BlendState, label: &str| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &vertex_buffers,
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: fmt,
blend: Some(blend),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: None,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: depth_write,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: sample_count,
..Default::default()
},
multiview: None,
cache: None,
})
};
let ldr = self.target_format;
let hdr = wgpu::TextureFormat::Rgba16Float;
let alpha = wgpu::BlendState::ALPHA_BLENDING;
self.sprite_bgl = Some(bgl);
self.sprite_soft_bgl = Some(soft_bgl);
self.sprite_soft_sampler = Some(soft_sampler);
self.sprite_soft_fallback_tex = Some(fallback_tex);
self.sprite_soft_fallback_bg = Some(fallback_bg);
self.sprite_pipeline = Some(DualPipeline {
ldr: make_sprite(ldr, false, alpha, "sprite_pipeline"),
hdr: make_sprite(hdr, false, alpha, "sprite_pipeline"),
});
self.sprite_pipeline_depth_write = Some(DualPipeline {
ldr: make_sprite(ldr, true, alpha, "sprite_pipeline_depth_write"),
hdr: make_sprite(hdr, true, alpha, "sprite_pipeline_depth_write"),
});
self.sprite_pipeline_additive = Some(DualPipeline {
ldr: make_sprite(ldr, false, additive_blend, "sprite_pipeline_additive"),
hdr: make_sprite(hdr, false, additive_blend, "sprite_pipeline_additive"),
});
self.sprite_pipeline_additive_depth_write = Some(DualPipeline {
ldr: make_sprite(
ldr,
true,
additive_blend,
"sprite_pipeline_additive_depth_write",
),
hdr: make_sprite(
hdr,
true,
additive_blend,
"sprite_pipeline_additive_depth_write",
),
});
self.sprite_pipeline_premultiplied = Some(DualPipeline {
ldr: make_sprite(
ldr,
false,
premultiplied_blend,
"sprite_pipeline_premultiplied",
),
hdr: make_sprite(
hdr,
false,
premultiplied_blend,
"sprite_pipeline_premultiplied",
),
});
self.sprite_pipeline_premultiplied_depth_write = Some(DualPipeline {
ldr: make_sprite(
ldr,
true,
premultiplied_blend,
"sprite_pipeline_premultiplied_depth_write",
),
hdr: make_sprite(
hdr,
true,
premultiplied_blend,
"sprite_pipeline_premultiplied_depth_write",
),
});
}
pub(crate) fn upload_sprite(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
item: &crate::renderer::SpriteItem,
) -> SpriteGpuData {
let count = item.positions.len() as u32;
let pos_bytes: Vec<u8> = item
.positions
.iter()
.flat_map(|p| bytemuck::bytes_of(p).iter().copied())
.collect();
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("sprite_vertex_buf"),
size: pos_bytes.len().max(12) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&vertex_buffer, 0, &pos_bytes);
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuSpriteInstance {
colour: [f32; 4],
size: f32,
rotation: f32,
_pad0: f32,
_pad1: f32,
uv_rect: [f32; 4],
}
let instances: Vec<GpuSpriteInstance> = (0..item.positions.len())
.map(|i| GpuSpriteInstance {
colour: if i < item.colours.len() {
item.colours[i]
} else {
item.default_colour
},
size: if i < item.sizes.len() {
item.sizes[i]
} else {
item.default_size
},
rotation: if i < item.rotations.len() {
item.rotations[i]
} else {
0.0
},
_pad0: 0.0,
_pad1: 0.0,
uv_rect: if i < item.uv_rects.len() {
item.uv_rects[i]
} else {
[0.0, 0.0, 1.0, 1.0]
},
})
.collect();
let instance_bytes = bytemuck::cast_slice(&instances);
let instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("sprite_instance_buf"),
size: instance_bytes.len().max(48) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&instance_buf, 0, instance_bytes);
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct SpriteUniformData {
model: [[f32; 4]; 4],
world_space: u32,
has_texture: u32,
soft_particle_distance: f32,
_pad0: u32,
}
let (texture_view, has_texture): (&wgpu::TextureView, u32) =
if let Some(id) = item.texture_id {
if let Some(tex) = self.textures.get(id as usize) {
(&tex.view, 1)
} else {
(&self.fallback_lut_view, 0)
}
} else {
(&self.fallback_lut_view, 0)
};
let uniform_data = SpriteUniformData {
model: item.model,
world_space: if item.size_mode == crate::renderer::SpriteSizeMode::WorldSpace {
1
} else {
0
},
has_texture,
soft_particle_distance: item
.soft_particle_distance
.filter(|d| *d > 0.0)
.unwrap_or(0.0),
_pad0: 0,
};
let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("sprite_uniform_buf"),
size: std::mem::size_of::<SpriteUniformData>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&uniform_buf, 0, bytemuck::bytes_of(&uniform_data));
let bgl = self
.sprite_bgl
.as_ref()
.expect("ensure_sprite_pipelines not called");
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("sprite_bind_group"),
layout: bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(texture_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.material_sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: instance_buf.as_entire_binding(),
},
],
});
SpriteGpuData {
vertex_buffer,
sprite_count: count,
bind_group,
depth_write: item.depth_write,
blend: item.blend,
wireframe: false,
_uniform_buf: uniform_buf,
_instance_buf: instance_buf,
}
}
pub(crate) fn ensure_sprite_outline_mask_pipeline(&mut self, device: &wgpu::Device) {
if self.sprite_outline_mask_pipeline.is_some() {
return;
}
let bgl = self
.sprite_bgl
.as_ref()
.expect("ensure_sprite_pipelines must be called first");
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("sprite_outline_mask_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!(concat!(env!("OUT_DIR"), "/sprite_outline_mask.wgsl")).into(),
),
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("sprite_outline_mask_pipeline_layout"),
bind_group_layouts: &[&self.camera_bind_group_layout, bgl],
push_constant_ranges: &[],
});
let vert_attrs = [wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
}];
let vertex_buffers = [wgpu::VertexBufferLayout {
array_stride: 12,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &vert_attrs,
}];
self.sprite_outline_mask_pipeline = Some(device.create_render_pipeline(
&wgpu::RenderPipelineDescriptor {
label: Some("sprite_outline_mask_pipeline"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &vertex_buffers,
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::R8Unorm,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: None,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
},
));
}
pub fn upload_sprite_set(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
item: &crate::renderer::SpriteItem,
) -> crate::resources::SpriteSetId {
self.ensure_sprite_pipelines(device);
let gpu = self.upload_sprite(device, queue, item);
self.sprite_set_store.insert(gpu)
}
pub fn drop_sprite_set(&mut self, id: crate::resources::SpriteSetId) -> bool {
self.sprite_set_store.remove(id)
}
pub fn replace_sprite_set(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
id: crate::resources::SpriteSetId,
item: &crate::renderer::SpriteItem,
) -> bool {
if !self.sprite_set_store.contains(id) {
return false;
}
self.ensure_sprite_pipelines(device);
let gpu = self.upload_sprite(device, queue, item);
self.sprite_set_store.replace(id, gpu)
}
pub fn begin_upload_sprite_set(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
item: crate::renderer::SpriteItem,
) -> crate::resources::JobId {
let slot = crate::resources::ResultSlot::<crate::resources::SpriteSetId>::new();
let slot_for_apply = slot.clone();
let device_for_apply = device.clone();
let queue_for_apply = queue.clone();
let id = {
let mut runner = self.jobs.lock().expect("upload job runner poisoned");
runner.submit_cpu(move |progress| {
progress.set(0.9);
Ok(crate::resources::upload_jobs::JobProduct::with_apply(Box::new(
move |resources: &mut ViewportGpuResources| {
let sid = resources.upload_sprite_set(
&device_for_apply,
&queue_for_apply,
&item,
);
slot_for_apply.set(sid);
},
)))
})
};
self.job_sprite_set_results
.lock()
.expect("sprite set result map poisoned")
.insert(id, slot);
id
}
pub fn upload_result_sprite_set(
&mut self,
id: crate::resources::JobId,
) -> crate::error::ViewportResult<crate::resources::SpriteSetId> {
let mut map = self
.job_sprite_set_results
.lock()
.expect("sprite set result map poisoned");
let slot = match map.get(&id) {
Some(s) => s.clone(),
None => {
return Err(crate::error::ViewportError::JobResultMissing {
reason: "unknown id or wrong upload type",
});
}
};
match slot.take() {
Some(sid) => {
map.remove(&id);
Ok(sid)
}
None => Err(crate::error::ViewportError::JobNotReady),
}
}
pub fn upload_sprite_instance_set(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
item: &crate::renderer::SpriteItem,
) -> crate::resources::SpriteInstanceSetId {
self.ensure_sprite_pipelines(device);
let gpu = self.upload_sprite(device, queue, item);
self.sprite_instance_set_store.insert(gpu)
}
pub fn drop_sprite_instance_set(
&mut self,
id: crate::resources::SpriteInstanceSetId,
) -> bool {
self.sprite_instance_set_store.remove(id)
}
pub fn replace_sprite_instance_set(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
id: crate::resources::SpriteInstanceSetId,
item: &crate::renderer::SpriteItem,
) -> bool {
if !self.sprite_instance_set_store.contains(id) {
return false;
}
self.ensure_sprite_pipelines(device);
let gpu = self.upload_sprite(device, queue, item);
self.sprite_instance_set_store.replace(id, gpu)
}
pub fn begin_upload_sprite_instance_set(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
item: crate::renderer::SpriteItem,
) -> crate::resources::JobId {
let slot = crate::resources::ResultSlot::<crate::resources::SpriteInstanceSetId>::new();
let slot_for_apply = slot.clone();
let device_for_apply = device.clone();
let queue_for_apply = queue.clone();
let id = {
let mut runner = self.jobs.lock().expect("upload job runner poisoned");
runner.submit_cpu(move |progress| {
progress.set(0.9);
Ok(crate::resources::upload_jobs::JobProduct::with_apply(Box::new(
move |resources: &mut ViewportGpuResources| {
let sid = resources.upload_sprite_instance_set(
&device_for_apply,
&queue_for_apply,
&item,
);
slot_for_apply.set(sid);
},
)))
})
};
self.job_sprite_instance_set_results
.lock()
.expect("sprite instance set result map poisoned")
.insert(id, slot);
id
}
pub fn upload_result_sprite_instance_set(
&mut self,
id: crate::resources::JobId,
) -> crate::error::ViewportResult<crate::resources::SpriteInstanceSetId> {
let mut map = self
.job_sprite_instance_set_results
.lock()
.expect("sprite instance set result map poisoned");
let slot = match map.get(&id) {
Some(s) => s.clone(),
None => {
return Err(crate::error::ViewportError::JobResultMissing {
reason: "unknown id or wrong upload type",
});
}
};
match slot.take() {
Some(sid) => {
map.remove(&id);
Ok(sid)
}
None => Err(crate::error::ViewportError::JobNotReady),
}
}
}