use super::*;
impl ViewportGpuResources {
pub(crate) fn ensure_instanced_pipelines(&mut self, device: &wgpu::Device) {
if self.instance_bind_group_layout.is_some() {
return; }
let instance_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("instance_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
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::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
});
let instanced_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("mesh_instanced_shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/mesh_instanced.wgsl").into()),
});
let instanced_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("instanced_pipeline_layout"),
bind_group_layouts: &[&self.camera_bind_group_layout, &instance_bgl],
push_constant_ranges: &[],
});
let solid_instanced = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("solid_instanced_pipeline"),
layout: Some(&instanced_layout),
vertex: wgpu::VertexState {
module: &instanced_shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::buffer_layout()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &instanced_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: self.target_format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: Some(wgpu::Face::Back),
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: self.sample_count,
..Default::default()
},
multiview: None,
cache: None,
});
let transparent_instanced =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("transparent_instanced_pipeline"),
layout: Some(&instanced_layout),
vertex: wgpu::VertexState {
module: &instanced_shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::buffer_layout()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &instanced_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: self.target_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
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: self.sample_count,
..Default::default()
},
multiview: None,
cache: None,
});
let shadow_instanced_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("shadow_instanced_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../shaders/shadow_instanced.wgsl").into(),
),
});
let shadow_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("shadow_bgl_for_instanced"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let shadow_instanced_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("shadow_instanced_pipeline_layout"),
bind_group_layouts: &[&shadow_bgl, &instance_bgl],
push_constant_ranges: &[],
});
let shadow_instanced = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("shadow_instanced_pipeline"),
layout: Some(&shadow_instanced_layout),
vertex: wgpu::VertexState {
module: &shadow_instanced_shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::buffer_layout()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: None,
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: Some(wgpu::Face::Front),
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let cascade_bufs: [wgpu::Buffer; 4] = std::array::from_fn(|i| {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some(&format!("shadow_instanced_cascade_buf_{i}")),
size: 64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
})
});
let cascade_bgs: [wgpu::BindGroup; 4] = std::array::from_fn(|i| {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("shadow_instanced_cascade_bg_{i}")),
layout: &shadow_bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: cascade_bufs[i].as_entire_binding(),
}],
})
});
self.shadow_instanced_cascade_bufs = cascade_bufs.map(Some);
self.shadow_instanced_cascade_bgs = cascade_bgs.map(Some);
self.instance_bind_group_layout = Some(instance_bgl);
self.solid_instanced_pipeline = Some(solid_instanced);
self.transparent_instanced_pipeline = Some(transparent_instanced);
self.shadow_instanced_pipeline = Some(shadow_instanced);
}
pub(crate) fn upload_instance_data(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
data: &[InstanceData],
) {
if data.is_empty() {
return;
}
let _bgl = self
.instance_bind_group_layout
.as_ref()
.expect("ensure_instanced_pipelines must be called first");
let max_instances = (device.limits().max_storage_buffer_binding_size as usize)
/ std::mem::size_of::<InstanceData>();
let data = &data[..data.len().min(max_instances)];
let needed = data.len();
if needed > self.instance_storage_capacity {
let new_cap = (needed * 2).max(64).min(max_instances);
let buf_size = (new_cap * std::mem::size_of::<InstanceData>()) as u64;
self.instance_storage_buf = Some(device.create_buffer(&wgpu::BufferDescriptor {
label: Some("instance_storage_buf"),
size: buf_size,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
}));
self.instance_storage_capacity = new_cap;
self.instance_bind_groups.clear();
}
queue.write_buffer(
self.instance_storage_buf.as_ref().unwrap(),
0,
bytemuck::cast_slice(data),
);
}
pub(crate) fn get_instance_bind_group(
&mut self,
device: &wgpu::Device,
albedo_id: Option<u64>,
normal_map_id: Option<u64>,
ao_map_id: Option<u64>,
) -> Option<&wgpu::BindGroup> {
let key = (
albedo_id.unwrap_or(u64::MAX),
normal_map_id.unwrap_or(u64::MAX),
ao_map_id.unwrap_or(u64::MAX),
);
if !self.instance_bind_groups.contains_key(&key) {
let bgl = self.instance_bind_group_layout.as_ref()?;
let buf = self.instance_storage_buf.as_ref()?;
let albedo_view = match albedo_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_texture.view,
};
let normal_view = match normal_map_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_normal_map_view,
};
let ao_view = match ao_map_id {
Some(id) if (id as usize) < self.textures.len() => &self.textures[id as usize].view,
_ => &self.fallback_ao_map_view,
};
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("instance_tex_bg"),
layout: bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(albedo_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.material_sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(normal_view),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(ao_view),
},
],
});
self.instance_bind_groups.insert(key, bg);
}
self.instance_bind_groups.get(&key)
}
}