1use std::collections::HashMap;
2
3use crate::{
4 app::{App, SystemStage},
5 assets::{handle::Handle, storage::RawAssetHandle},
6 ecs::{
7 plugin::Plugin,
8 system::{Commands, Local, Query, Res, ResMut},
9 system_condition::{ResourceExists, RunIfExt},
10 },
11};
12
13use super::{
14 backend::WGPUBackend,
15 binding::{BindGroupLayout, BindGroupLayoutBuilder, BindingKind},
16 buffer::Buffer,
17 buffers::{BindGroup, BindGroupBuilder, BufferBuilder},
18 flags::ShaderStages,
19 layout::GlobalLayoutPool,
20 material::Material,
21 player::AnimationPlayer,
22 skinned_mesh::SkinnedMesh,
23};
24
25struct SkinningBatch {
28 matrices: Buffer, _info: Buffer, bind_group: BindGroup,
31 joint_count: u32,
32 capacity: u32,
33}
34
35impl SkinningBatch {
36 fn new(backend: &WGPUBackend, layout: &BindGroupLayout, joint_count: u32, capacity: u32) -> Self {
37 let matrices = BufferBuilder::empty((capacity as u64) * (joint_count as u64) * 64)
38 .with_label("pebble_skinning:joint_matrices")
39 .with_storage()
40 .build(backend);
41 let info = BufferBuilder::with_data(bytemuck::cast_slice(&[joint_count, 0u32, 0u32, 0u32]))
42 .with_label("pebble_skinning:info")
43 .with_uniform()
44 .build(backend);
45 let bind_group = BindGroupBuilder::new(layout)
46 .with_buffer(&matrices)
47 .with_buffer(&info)
48 .build(backend);
49 Self { matrices, _info: info, bind_group, joint_count, capacity }
50 }
51}
52
53pub struct SkinnedBatchRenderer {
67 layout: BindGroupLayout,
68 batches: HashMap<(RawAssetHandle, RawAssetHandle), SkinningBatch>,
69}
70
71impl SkinnedBatchRenderer {
72 fn new(backend: &WGPUBackend) -> Self {
73 let layout = BindGroupLayoutBuilder::new()
74 .with_label("pebble_skinning")
75 .with_entry("joint_matrices", 0, BindingKind::storage_buffer_read_only(ShaderStages::VERTEX))
76 .with_entry("skin_info", 1, BindingKind::uniform_buffer(ShaderStages::VERTEX))
77 .build(backend);
78 Self { layout, batches: HashMap::new() }
79 }
80
81 pub fn bind_group(&self, material: RawAssetHandle, mesh: RawAssetHandle) -> Option<&BindGroup> {
84 self.batches.get(&(material, mesh)).map(|b| &b.bind_group)
85 }
86
87 fn prepare(&mut self, key: (RawAssetHandle, RawAssetHandle), joint_count: u32, needed: u32, backend: &WGPUBackend) {
88 let layout = self.layout.clone();
89 let batch = self.batches.entry(key).or_insert_with(|| {
90 SkinningBatch::new(backend, &layout, joint_count, needed.max(256))
91 });
92 if needed > batch.capacity {
93 *batch = SkinningBatch::new(backend, &layout, batch.joint_count, (batch.capacity * 2).max(needed));
94 }
95 }
96}
97
98pub struct SkinnedBatchUnit {
102 pub material: RawAssetHandle,
103 pub mesh: RawAssetHandle,
104 pub instance_count: u32,
105}
106
107#[derive(Default)]
117pub struct SkinnedBatchStorage {
118 pub batches: Vec<SkinnedBatchUnit>,
119}
120
121fn init_skinned_batching(
124 mut commands: Commands,
125 backend: Res<WGPUBackend>,
126 mut pool: ResMut<GlobalLayoutPool>,
127) -> Option<()> {
128 let renderer = SkinnedBatchRenderer::new(&backend);
129 pool.register("pebble_skinning", renderer.layout.clone());
130 commands.insert_resource(renderer);
131 commands.insert_resource(SkinnedBatchStorage::default());
132 Some(())
133}
134
135struct GroupData {
136 joint_count: u32,
137 matrices: Vec<glam::Mat4>, count: u32,
139}
140
141fn batch_skinned_entities(
142 backend: Res<WGPUBackend>,
143 mut renderer: ResMut<SkinnedBatchRenderer>,
144 mut storage: ResMut<SkinnedBatchStorage>,
145 mut query: Query<(&Handle<Material>, &Handle<SkinnedMesh>, &AnimationPlayer)>,
146 mut groups: Local<HashMap<(RawAssetHandle, RawAssetHandle), GroupData>>,
147) {
148 storage.batches.clear();
149 groups.clear();
150
151 for (mat, mesh, player) in query.iter() {
152 let entry = groups.entry((mat.id, mesh.id)).or_insert_with(|| GroupData {
153 joint_count: player.joint_count() as u32,
154 matrices: Vec::new(),
155 count: 0,
156 });
157 entry.matrices.extend(player.compute_matrices());
158 entry.count += 1;
159 }
160
161 for ((mat_id, mesh_id), group) in groups.iter() {
162 renderer.prepare((*mat_id, *mesh_id), group.joint_count, group.count, &backend);
163 let batch = renderer.batches.get(&(*mat_id, *mesh_id)).unwrap();
164 batch.matrices.write(bytemuck::cast_slice(&group.matrices));
165 storage.batches.push(SkinnedBatchUnit {
166 material: *mat_id,
167 mesh: *mesh_id,
168 instance_count: group.count,
169 });
170 }
171}
172
173pub struct SkinnedBatchingPlugin;
180
181impl Plugin for SkinnedBatchingPlugin {
182 fn build(&self, app: &mut App) {
183 app.add_system(
184 SystemStage::Startup,
185 init_skinned_batching.run_if::<ResourceExists<WGPUBackend>>(),
186 );
187 app.add_system(
193 SystemStage::PreRender,
194 batch_skinned_entities.run_if::<ResourceExists<WGPUBackend>>(),
195 );
196 }
197}