use crate::resources::*;
#[derive(Default)]
pub(crate) struct InstancingResources {
pub(crate) bind_group_layout: Option<wgpu::BindGroupLayout>,
pub(crate) storage_buf: Option<wgpu::Buffer>,
pub(crate) storage_capacity: usize,
pub(crate) bind_groups: std::collections::HashMap<(u64, u64, u64), wgpu::BindGroup>,
pub(crate) solid_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) solid_two_sided_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) transparent_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) shadow_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) shadow_two_sided_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) shadow_cascade_bufs: [Option<wgpu::Buffer>; 4],
pub(crate) shadow_cascade_bgs: [Option<wgpu::BindGroup>; 4],
pub(crate) hdr_solid_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) hdr_solid_two_sided_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) hdr_transparent_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) hdr_additive_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) hdr_premultiplied_pipeline: Option<wgpu::RenderPipeline>,
}
#[derive(Default)]
pub(crate) struct CullResources {
pub(crate) aabb_buf: Option<wgpu::Buffer>,
pub(crate) aabb_capacity: usize,
pub(crate) batch_meta_buf: Option<wgpu::Buffer>,
pub(crate) batch_meta_capacity: usize,
pub(crate) bind_group_layout: Option<wgpu::BindGroupLayout>,
pub(crate) hdr_solid_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) hdr_solid_two_sided_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) oit_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) shadow_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) shadow_two_sided_pipeline: Option<wgpu::RenderPipeline>,
pub(crate) shadow_bgl: Option<wgpu::BindGroupLayout>,
}
impl DeviceResources {
pub(crate) fn ensure_instanced_pipelines(&mut self, device: &wgpu::Device) {
if self.instancing.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 = {
let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced.wgsl"));
let composed = crate::resources::mesh_sidecar::registry::compose_shader(
base,
&self.deform.registrations,
);
crate::resources::builders::wgsl_module(device, "mesh_instanced_shader", composed)
};
let instanced_layout = crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
device,
"instanced_pipeline_layout",
&self.camera_bind_group_layout,
&instance_bgl,
self.deform
.enabled
.then_some(&self.deform.bind_group_layout),
);
let ldr_inst = crate::resources::mesh::mesh_pipelines::build_ldr_instanced_mesh_pipelines(
device,
&instanced_layout,
&instanced_shader,
self.target_format,
self.sample_count,
);
let solid_instanced = ldr_inst.solid;
let solid_two_sided_instanced = ldr_inst.solid_two_sided;
let transparent_instanced = ldr_inst.transparent;
let shadow_instanced_shader = crate::resources::builders::wgsl_module(
device,
"shadow_instanced_shader",
crate::resources::builders::wgsl_source!("shadow_instanced"),
);
let shadow_bgl = crate::resources::builders::uniform_bgl(
device,
"shadow_bgl_for_instanced",
wgpu::ShaderStages::VERTEX,
);
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 make_shadow_instanced =
|label: &str, cull_mode: Option<wgpu::Face>, bias: wgpu::DepthBiasState| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
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,
..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,
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
})
};
let shadow_instanced = make_shadow_instanced(
"shadow_instanced_pipeline",
Some(wgpu::Face::Front),
crate::resources::mesh::mesh_pipelines::CSM_SHADOW_BIAS,
);
let shadow_instanced_two_sided = make_shadow_instanced(
"shadow_instanced_two_sided_pipeline",
None,
crate::resources::mesh::mesh_pipelines::CSM_SHADOW_BIAS_TWO_SIDED,
);
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.instancing.shadow_cascade_bufs = cascade_bufs.map(Some);
self.instancing.shadow_cascade_bgs = cascade_bgs.map(Some);
self.instancing.bind_group_layout = Some(instance_bgl);
self.instancing.solid_pipeline = Some(solid_instanced);
self.instancing.solid_two_sided_pipeline = Some(solid_two_sided_instanced);
self.instancing.transparent_pipeline = Some(transparent_instanced);
self.instancing.shadow_pipeline = Some(shadow_instanced);
self.instancing.shadow_two_sided_pipeline = Some(shadow_instanced_two_sided);
}
pub(crate) fn ensure_hdr_instanced_pipelines(&mut self, device: &wgpu::Device) {
if self.instancing.hdr_solid_pipeline.is_some() {
return;
}
let Some(ref instance_bgl) = self.instancing.bind_group_layout else {
return;
};
let inst_shader = {
let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced.wgsl"));
let composed = crate::resources::mesh_sidecar::registry::compose_shader(
base,
&self.deform.registrations,
);
crate::resources::builders::wgsl_module(device, "mesh_instanced_shader_hdr", composed)
};
let inst_layout = crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
device,
"hdr_instanced_pipeline_layout",
&self.camera_bind_group_layout,
instance_bgl,
self.deform
.enabled
.then_some(&self.deform.bind_group_layout),
);
let hdr_inst = crate::resources::mesh::mesh_pipelines::build_hdr_instanced_mesh_pipelines(
device,
&inst_layout,
&inst_shader,
);
self.instancing.hdr_solid_pipeline = Some(hdr_inst.solid);
self.instancing.hdr_solid_two_sided_pipeline = Some(hdr_inst.solid_two_sided);
self.instancing.hdr_transparent_pipeline = Some(hdr_inst.transparent);
self.instancing.hdr_additive_pipeline = Some(hdr_inst.additive);
self.instancing.hdr_premultiplied_pipeline = Some(hdr_inst.premultiplied);
}
pub(crate) fn ensure_oit_instanced_pipeline(&mut self, device: &wgpu::Device) {
if self.oit.instanced_pipeline.is_some() {
return;
}
let Some(ref instance_bgl) = self.instancing.bind_group_layout else {
return;
};
let instanced_oit_shader = {
let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced_oit.wgsl"));
let composed = crate::resources::mesh_sidecar::registry::compose_shader(
base,
&self.deform.registrations,
);
crate::resources::builders::wgsl_module(device, "mesh_instanced_oit_shader", composed)
};
let instanced_oit_layout =
crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
device,
"oit_instanced_pipeline_layout",
&self.camera_bind_group_layout,
instance_bgl,
self.deform
.enabled
.then_some(&self.deform.bind_group_layout),
);
let pipeline = crate::resources::mesh::mesh_pipelines::build_oit_instanced_pipeline(
device,
&instanced_oit_layout,
&instanced_oit_shader,
"oit_instanced_pipeline",
"vs_main",
);
self.oit.instanced_pipeline = Some(pipeline);
}
pub(crate) fn upload_instance_data(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
data: &[InstanceData],
) {
if data.is_empty() {
return;
}
let _bgl = self
.instancing
.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.instancing.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.instancing.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.instancing.storage_capacity = new_cap;
self.instancing.bind_groups.clear();
}
queue.write_buffer(
self.instancing.storage_buf.as_ref().unwrap(),
0,
bytemuck::cast_slice(data),
);
}
pub(crate) fn upload_cull_inputs(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
aabbs: &[crate::resources::types::InstanceAabb],
metas: &[crate::resources::types::BatchMeta],
) {
let max_instances = (device.limits().max_storage_buffer_binding_size as usize)
/ std::mem::size_of::<crate::resources::types::InstanceAabb>();
let aabbs = &aabbs[..aabbs.len().min(max_instances)];
if aabbs.len() > self.cull.aabb_capacity {
let new_cap = (aabbs.len() * 2).max(64).min(max_instances);
self.cull.aabb_buf = Some(device.create_buffer(&wgpu::BufferDescriptor {
label: Some("instance_aabb_buf"),
size: (new_cap * std::mem::size_of::<crate::resources::types::InstanceAabb>())
as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
}));
self.cull.aabb_capacity = new_cap;
}
if !aabbs.is_empty() {
queue.write_buffer(
self.cull.aabb_buf.as_ref().unwrap(),
0,
bytemuck::cast_slice(aabbs),
);
}
let max_batches = (device.limits().max_storage_buffer_binding_size as usize)
/ std::mem::size_of::<crate::resources::types::BatchMeta>();
let metas = &metas[..metas.len().min(max_batches)];
let batch_count = metas.len();
if batch_count > self.cull.batch_meta_capacity {
let new_cap = (batch_count * 2).max(16).min(max_batches);
let meta_size =
(new_cap * std::mem::size_of::<crate::resources::types::BatchMeta>()) as u64;
self.cull.batch_meta_buf = Some(device.create_buffer(&wgpu::BufferDescriptor {
label: Some("batch_meta_buf"),
size: meta_size,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
}));
self.cull.batch_meta_capacity = new_cap;
}
if !metas.is_empty() {
queue.write_buffer(
self.cull.batch_meta_buf.as_ref().unwrap(),
0,
bytemuck::cast_slice(metas),
);
}
}
pub(crate) fn ensure_cull_instance_pipelines(&mut self, device: &wgpu::Device) {
if self.cull.bind_group_layout.is_some() {
return;
}
let Some(ref _instance_bgl) = self.instancing.bind_group_layout else {
return; };
let cull_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("instance_cull_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,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
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 instanced_shader = {
let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced.wgsl"));
let composed = crate::resources::mesh_sidecar::registry::compose_shader(
base,
&self.deform.registrations,
);
crate::resources::builders::wgsl_module(device, "mesh_instanced_shader_cull", composed)
};
let inst_cull_layout = crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
device,
"hdr_instanced_cull_pipeline_layout",
&self.camera_bind_group_layout,
&cull_bgl,
self.deform
.enabled
.then_some(&self.deform.bind_group_layout),
);
let hdr_solid_cull =
crate::resources::mesh::mesh_pipelines::build_hdr_instanced_cull_pipeline(
device,
&inst_cull_layout,
&instanced_shader,
);
let hdr_solid_cull_two_sided =
crate::resources::mesh::mesh_pipelines::build_hdr_instanced_cull_two_sided_pipeline(
device,
&inst_cull_layout,
&instanced_shader,
);
let oit_shader = {
let base = include_str!(concat!(env!("OUT_DIR"), "/mesh_instanced_oit.wgsl"));
let composed = crate::resources::mesh_sidecar::registry::compose_shader(
base,
&self.deform.registrations,
);
crate::resources::builders::wgsl_module(
device,
"mesh_instanced_oit_shader_cull",
composed,
)
};
let oit_cull_layout = crate::resources::mesh::mesh_pipelines::instanced_pipeline_layout(
device,
"oit_instanced_cull_pipeline_layout",
&self.camera_bind_group_layout,
&cull_bgl,
self.deform
.enabled
.then_some(&self.deform.bind_group_layout),
);
let oit_cull = crate::resources::mesh::mesh_pipelines::build_oit_instanced_pipeline(
device,
&oit_cull_layout,
&oit_shader,
"oit_instanced_cull_pipeline",
"vs_main_cull",
);
self.cull.bind_group_layout = Some(cull_bgl);
self.cull.hdr_solid_pipeline = Some(hdr_solid_cull);
self.cull.hdr_solid_two_sided_pipeline = Some(hdr_solid_cull_two_sided);
self.cull.oit_pipeline = Some(oit_cull);
let shadow_cull_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("shadow_cull_instance_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
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,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
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 shadow_bgl_for_cull = crate::resources::builders::uniform_bgl(
device,
"shadow_bgl_for_cull",
wgpu::ShaderStages::VERTEX,
);
let shadow_cull_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("shadow_instanced_cull_pipeline_layout"),
bind_group_layouts: &[&shadow_bgl_for_cull, &shadow_cull_bgl],
push_constant_ranges: &[],
});
let shadow_cull_shader = crate::resources::builders::wgsl_module(
device,
"shadow_instanced_cull_shader",
crate::resources::builders::wgsl_source!("shadow_instanced"),
);
let make_shadow_cull =
|label: &str, cull_mode: Option<wgpu::Face>, bias: wgpu::DepthBiasState| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&shadow_cull_layout),
vertex: wgpu::VertexState {
module: &shadow_cull_shader,
entry_point: Some("vs_shadow_cull"),
buffers: &[Vertex::buffer_layout()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: None,
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode,
..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,
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
})
};
let shadow_instanced_cull = make_shadow_cull(
"shadow_instanced_cull_pipeline",
Some(wgpu::Face::Front),
crate::resources::mesh::mesh_pipelines::CSM_SHADOW_BIAS,
);
let shadow_instanced_cull_two_sided = make_shadow_cull(
"shadow_instanced_cull_two_sided_pipeline",
None,
crate::resources::mesh::mesh_pipelines::CSM_SHADOW_BIAS_TWO_SIDED,
);
self.cull.shadow_pipeline = Some(shadow_instanced_cull);
self.cull.shadow_two_sided_pipeline = Some(shadow_instanced_cull_two_sided);
self.cull.shadow_bgl = Some(shadow_cull_bgl);
}
pub(crate) fn get_shadow_cull_instance_bind_group<'a>(
&self,
shadow_cull: &'a mut crate::resources::ShadowCullState,
device: &wgpu::Device,
cascade_idx: usize,
) -> Option<&'a wgpu::BindGroup> {
if shadow_cull.shadow_cull_instance_bgs[cascade_idx].is_none() {
let bgl = self.cull.shadow_bgl.as_ref()?;
let inst_buf = self.instancing.storage_buf.as_ref()?;
let vis_buf = shadow_cull.shadow_vis_bufs[cascade_idx].as_ref()?;
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("shadow_cull_instance_bg_{cascade_idx}")),
layout: bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: inst_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: vis_buf.as_entire_binding(),
},
],
});
shadow_cull.shadow_cull_instance_bgs[cascade_idx] = Some(bg);
}
shadow_cull.shadow_cull_instance_bgs[cascade_idx].as_ref()
}
pub(crate) fn get_instance_cull_bind_group<'a>(
&self,
cull_state: &'a mut crate::resources::ViewportCullState,
device: &wgpu::Device,
albedo_id: Option<crate::resources::TextureId>,
normal_map_id: Option<crate::resources::TextureId>,
ao_map_id: Option<crate::resources::TextureId>,
) -> Option<&'a wgpu::BindGroup> {
let key = (
albedo_id.map(|t| t.raw()).unwrap_or(u64::MAX),
normal_map_id.map(|t| t.raw()).unwrap_or(u64::MAX),
ao_map_id.map(|t| t.raw()).unwrap_or(u64::MAX),
);
if !cull_state.instance_cull_bind_groups.contains_key(&key) {
let bgl = self.cull.bind_group_layout.as_ref()?;
let inst_buf = self.instancing.storage_buf.as_ref()?;
let vis_buf = cull_state.visibility_index_buf.as_ref()?;
let albedo_view = match albedo_id {
Some(id) if self.content.textures.get(id).is_some() => {
&self.content.textures.get(id).unwrap().view
}
_ => &self.fallback_texture.view,
};
let normal_view = match normal_map_id {
Some(id) if self.content.textures.get(id).is_some() => {
&self.content.textures.get(id).unwrap().view
}
_ => &self.fallback_normal_map_view,
};
let ao_view = match ao_map_id {
Some(id) if self.content.textures.get(id).is_some() => {
&self.content.textures.get(id).unwrap().view
}
_ => &self.fallback_ao_map_view,
};
let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("instance_cull_tex_bg"),
layout: bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: inst_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),
},
wgpu::BindGroupEntry {
binding: 5,
resource: vis_buf.as_entire_binding(),
},
],
});
cull_state.instance_cull_bind_groups.insert(key, bg);
}
cull_state.instance_cull_bind_groups.get(&key)
}
pub(crate) fn get_instance_bind_group(
&mut self,
device: &wgpu::Device,
albedo_id: Option<crate::resources::TextureId>,
normal_map_id: Option<crate::resources::TextureId>,
ao_map_id: Option<crate::resources::TextureId>,
) -> Option<&wgpu::BindGroup> {
let key = (
albedo_id.map(|t| t.raw()).unwrap_or(u64::MAX),
normal_map_id.map(|t| t.raw()).unwrap_or(u64::MAX),
ao_map_id.map(|t| t.raw()).unwrap_or(u64::MAX),
);
if !self.instancing.bind_groups.contains_key(&key) {
let bgl = self.instancing.bind_group_layout.as_ref()?;
let buf = self.instancing.storage_buf.as_ref()?;
let albedo_view = match albedo_id {
Some(id) if self.content.textures.get(id).is_some() => {
&self.content.textures.get(id).unwrap().view
}
_ => &self.fallback_texture.view,
};
let normal_view = match normal_map_id {
Some(id) if self.content.textures.get(id).is_some() => {
&self.content.textures.get(id).unwrap().view
}
_ => &self.fallback_normal_map_view,
};
let ao_view = match ao_map_id {
Some(id) if self.content.textures.get(id).is_some() => {
&self.content.textures.get(id).unwrap().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.instancing.bind_groups.insert(key, bg);
}
self.instancing.bind_groups.get(&key)
}
pub(crate) fn upload_mesh_instance(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
item: &crate::renderer::MeshInstanceItem,
) -> Option<crate::resources::types::MeshInstanceGpuData> {
self.upload_mesh_instance_from(device, queue, item, item.mesh_id, None)
}
pub(crate) fn upload_mesh_instance_from(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
item: &crate::renderer::MeshInstanceItem,
mesh_id: crate::resources::mesh::mesh_store::MeshId,
indices: Option<&[u32]>,
) -> Option<crate::resources::types::MeshInstanceGpuData> {
let instance_count = match indices {
Some(idx) => idx.len() as u32,
None => item.transforms.len() as u32,
};
if instance_count == 0 {
return None;
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuInstanceData {
model: [[f32; 4]; 4],
colour: [f32; 4],
selected: u32,
wireframe: u32,
ambient: f32,
diffuse: f32,
specular: f32,
shininess: f32,
has_texture: u32,
use_pbr: u32,
metallic: f32,
roughness: f32,
has_normal_map: u32,
has_ao_map: u32,
unlit: u32,
receive_shadows: u32,
use_flat: u32,
_pad_inst1: u32,
uv_transform: [f32; 4],
ao_range: [f32; 2],
_pad_ao_range: [f32; 2],
}
const _: () = assert!(std::mem::size_of::<GpuInstanceData>() == 176);
let has_texture = if item
.texture_id
.is_some_and(|id| self.content.textures.get(id).is_some())
{
1u32
} else {
0u32
};
let build = |i: usize| -> GpuInstanceData {
GpuInstanceData {
model: item.transforms[i],
colour: item.colours.get(i).copied().unwrap_or([1.0, 1.0, 1.0, 1.0]),
selected: 0,
wireframe: 0,
ambient: 1.0,
diffuse: 0.0,
specular: 0.0,
shininess: 0.0,
has_texture,
use_pbr: 0,
metallic: 0.0,
roughness: 0.0,
has_normal_map: 0,
has_ao_map: 0,
unlit: 1,
receive_shadows: 0,
use_flat: 1,
_pad_inst1: 0,
uv_transform: [0.0, 0.0, 1.0, 1.0],
ao_range: [0.0, 1.0],
_pad_ao_range: [0.0, 0.0],
}
};
let instances: Vec<GpuInstanceData> = match indices {
Some(idx) => idx.iter().map(|&i| build(i as usize)).collect(),
None => (0..item.transforms.len()).map(build).collect(),
};
let instance_bytes = bytemuck::cast_slice(&instances);
let instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("mesh_instance_buf"),
size: instance_bytes
.len()
.max(std::mem::size_of::<GpuInstanceData>()) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&instance_buf, 0, instance_bytes);
let bgl = self.instancing.bind_group_layout.as_ref()?;
let albedo_view = match item.texture_id {
Some(id) if self.content.textures.get(id).is_some() => {
&self.content.textures.get(id).unwrap().view
}
_ => &self.fallback_texture.view,
};
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("mesh_instance_bg"),
layout: bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: instance_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(&self.fallback_normal_map_view),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(&self.fallback_ao_map_view),
},
],
});
Some(crate::resources::types::MeshInstanceGpuData {
mesh_id,
instance_count,
bind_group,
blend: item.blend,
_instance_buf: instance_buf,
})
}
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct ObjectUniform {
pub(crate) model: [[f32; 4]; 4], pub(crate) colour: [f32; 4], pub(crate) selected: u32, pub(crate) wireframe: u32, pub(crate) ambient: f32, pub(crate) diffuse: f32, pub(crate) specular: f32, pub(crate) shininess: f32, pub(crate) has_texture: u32, pub(crate) use_pbr: u32, pub(crate) metallic: f32, pub(crate) roughness: f32, pub(crate) has_normal_map: u32, pub(crate) has_ao_map: u32, pub(crate) has_attribute: u32, pub(crate) scalar_min: f32, pub(crate) scalar_max: f32, pub(crate) receive_shadows: u32, pub(crate) nan_colour: [f32; 4], pub(crate) use_nan_colour: u32, pub(crate) use_matcap: u32, pub(crate) matcap_blendable: u32, pub(crate) unlit: u32, pub(crate) use_face_colour: u32, pub(crate) uv_vis_mode: u32, pub(crate) uv_vis_scale: f32, pub(crate) backface_policy: u32, pub(crate) backface_colour: [f32; 4], pub(crate) has_warp: u32, pub(crate) warp_scale: f32, pub(crate) has_position_override: u32, pub(crate) has_normal_override: u32, pub(crate) emissive: [f32; 3], pub(crate) use_flat: u32, pub(crate) alpha_mode: u32, pub(crate) alpha_cutoff: f32, pub(crate) has_metallic_roughness_tex: u32, pub(crate) has_emissive_tex: u32, pub(crate) uv_transform: [f32; 4], pub(crate) deform_flags: u32, pub(crate) _pad_after_deform: u32, pub(crate) ao_range: [f32; 2], pub(crate) metallic_range: [f32; 2], pub(crate) roughness_range: [f32; 2], }
const _: () = assert!(std::mem::size_of::<ObjectUniform>() == 304);
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct InstanceData {
pub(crate) model: [[f32; 4]; 4], pub(crate) colour: [f32; 4], pub(crate) selected: u32, pub(crate) wireframe: u32, pub(crate) ambient: f32, pub(crate) diffuse: f32, pub(crate) specular: f32, pub(crate) shininess: f32, pub(crate) has_texture: u32, pub(crate) use_pbr: u32, pub(crate) metallic: f32, pub(crate) roughness: f32, pub(crate) has_normal_map: u32, pub(crate) has_ao_map: u32, pub(crate) unlit: u32, pub(crate) receive_shadows: u32, pub(crate) use_flat: u32, pub(crate) _pad_inst: u32, pub(crate) uv_transform: [f32; 4], pub(crate) ao_range: [f32; 2], pub(crate) _pad_ao_range: [f32; 2], }
const _: () = assert!(std::mem::size_of::<InstanceData>() == 176);
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct PickInstance {
pub(crate) model_c0: [f32; 4],
pub(crate) model_c1: [f32; 4],
pub(crate) model_c2: [f32; 4],
pub(crate) model_c3: [f32; 4],
pub(crate) object_id: u32,
pub(crate) _pad: [u32; 3],
}
const _: () = assert!(std::mem::size_of::<PickInstance>() == 80);
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct InstanceAabb {
pub(crate) min: [f32; 3],
pub(crate) batch_index: u32,
pub(crate) max: [f32; 3],
pub(crate) cast_shadows: u32,
}
const _: () = assert!(std::mem::size_of::<InstanceAabb>() == 32);
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct BatchMeta {
pub index_count: u32,
pub first_index: u32,
pub instance_offset: u32,
pub instance_count: u32,
pub vis_offset: u32,
pub is_transparent: u32,
pub _pad: [u32; 2],
}
const _: () = assert!(std::mem::size_of::<BatchMeta>() == 32);