12#include <unordered_map>
18#include "spdlog/spdlog.h"
36 static_assert(
sizeof(
PackedVertex) == 56,
"PackedVertex must be 56 bytes (14 floats)");
55 static_assert(
sizeof(
DynamicBatchVertex) == 60,
"DynamicBatchVertex must be 60 bytes (15 floats)");
63 _groups[group.
id] = group;
69 for (
auto it = _batches.begin(); it != _batches.end(); ) {
70 if ((*it)->batchGroupId == groupId) {
72 for (
auto* mi : (*it)->origMeshInstances) {
75 it = _batches.erase(it);
80 _groups.erase(groupId);
85 auto it = _groups.find(groupId);
86 return it != _groups.end() ? &it->second :
nullptr;
102 bool operator==(
const MaterialKey& o)
const {
return groupId == o.groupId && material == o.material; }
104 struct MaterialKeyHash {
105 size_t operator()(
const MaterialKey& k)
const {
106 auto h1 = std::hash<int>{}(k.groupId);
107 auto h2 = std::hash<void*>{}(
static_cast<void*
>(k.material));
108 return h1 ^ (h2 << 1);
112 std::unordered_map<MaterialKey, std::vector<MeshInstance*>, MaterialKeyHash> groups;
115 if (!rc || !rc->enabled())
continue;
117 const int groupId = rc->batchGroupId();
118 if (groupId < 0)
continue;
119 if (_groups.find(groupId) == _groups.end())
continue;
121 for (
auto* mi : rc->meshInstances()) {
122 if (!mi || !mi->mesh() || !mi->mesh()->getVertexBuffer())
continue;
124 MaterialKey key{groupId, mi->material()};
125 groups[key].push_back(mi);
131 int totalOrigMeshInstances = 0;
132 for (
auto& [key, meshInstances] : groups) {
133 if (meshInstances.size() < 2)
continue;
137 std::unique_ptr<Batch> batch;
138 if (group && group->dynamic) {
139 batch = createDynamicBatch(meshInstances, key.groupId);
141 batch = createBatch(meshInstances, key.groupId);
144 totalOrigMeshInstances +=
static_cast<int>(meshInstances.size());
148 if (scene && scene->
layers()) {
150 if (group && !group->layers.empty()) {
151 for (
int layerId : group->layers) {
152 auto layer = scene->
layers()->getLayerById(layerId);
154 layer->addMeshInstances({batch->meshInstance.get()});
159 auto worldLayer = scene->
layers()->getLayerById(1);
161 worldLayer->addMeshInstances({batch->meshInstance.get()});
166 _batches.push_back(std::move(batch));
170 if (batchCount > 0) {
171 spdlog::info(
"[BatchManager] Created {} batches from {} mesh instances",
172 batchCount, totalOrigMeshInstances);
181 for (
auto& batch : _batches) {
183 if (scene && scene->
layers() && batch->meshInstance) {
185 if (group && !group->layers.empty()) {
186 for (
int layerId : group->layers) {
187 auto layer = scene->
layers()->getLayerById(layerId);
189 layer->removeMeshInstances({batch->meshInstance.get()});
193 auto worldLayer = scene->
layers()->getLayerById(1);
195 worldLayer->removeMeshInstances({batch->meshInstance.get()});
201 for (
auto* mi : batch->origMeshInstances) {
202 mi->setVisible(
true);
211 std::unique_ptr<Batch> BatchManager::createBatch(
212 const std::vector<MeshInstance*>& meshInstances,
int batchGroupId)
214 if (meshInstances.empty() || !_device)
return nullptr;
217 int totalVertices = 0;
218 int totalIndices = 0;
219 for (
auto* mi : meshInstances) {
220 auto vb = mi->mesh()->getVertexBuffer();
221 auto ib = mi->mesh()->getIndexBuffer();
224 totalVertices += vb->numVertices();
226 totalIndices += ib->numIndices();
229 totalIndices += vb->numVertices();
233 if (totalVertices == 0)
return nullptr;
236 std::vector<PackedVertex> mergedVertices;
237 mergedVertices.reserve(totalVertices);
239 std::vector<uint32_t> mergedIndices;
240 mergedIndices.reserve(totalIndices);
242 BoundingBox mergedAabb;
243 bool aabbInitialized =
false;
245 uint32_t vertexOffset = 0;
248 for (
auto* mi : meshInstances) {
249 auto vb = mi->mesh()->getVertexBuffer();
250 auto ib = mi->mesh()->getIndexBuffer();
251 if (!vb || vb->storage().empty())
continue;
253 const int vertCount = vb->numVertices();
254 const auto* srcVerts =
reinterpret_cast<const PackedVertex*
>(vb->storage().data());
259 worldMatrix = mi->node()->worldTransform();
265 Matrix4 normalMatrix = worldMatrix.inverse().transpose();
268 for (
int i = 0; i < vertCount; i++) {
269 PackedVertex v = srcVerts[i];
272 Vector3 pos = worldMatrix.transformPoint(Vector3(v.px, v.py, v.pz));
273 v.px = pos.getX(); v.py = pos.getY(); v.pz = pos.getZ();
276 float nnx = normalMatrix.getElement(0, 0) * v.nx +
277 normalMatrix.getElement(1, 0) * v.ny +
278 normalMatrix.getElement(2, 0) * v.nz;
279 float nny = normalMatrix.getElement(0, 1) * v.nx +
280 normalMatrix.getElement(1, 1) * v.ny +
281 normalMatrix.getElement(2, 1) * v.nz;
282 float nnz = normalMatrix.getElement(0, 2) * v.nx +
283 normalMatrix.getElement(1, 2) * v.ny +
284 normalMatrix.getElement(2, 2) * v.nz;
285 Vector3 transformedNormal = Vector3(nnx, nny, nnz).normalized();
286 v.nx = transformedNormal.getX(); v.ny = transformedNormal.getY(); v.nz = transformedNormal.getZ();
289 float ttx = normalMatrix.getElement(0, 0) * v.tx +
290 normalMatrix.getElement(1, 0) * v.ty +
291 normalMatrix.getElement(2, 0) * v.tz;
292 float tty = normalMatrix.getElement(0, 1) * v.tx +
293 normalMatrix.getElement(1, 1) * v.ty +
294 normalMatrix.getElement(2, 1) * v.tz;
295 float ttz = normalMatrix.getElement(0, 2) * v.tx +
296 normalMatrix.getElement(1, 2) * v.ty +
297 normalMatrix.getElement(2, 2) * v.tz;
298 Vector3 transformedTangent = Vector3(ttx, tty, ttz).normalized();
299 v.tx = transformedTangent.getX(); v.ty = transformedTangent.getY(); v.tz = transformedTangent.getZ();
303 mergedVertices.push_back(v);
307 if (ib && !ib->storage().empty()) {
308 const int idxCount = ib->numIndices();
309 const auto* idxData = ib->storage().data();
312 const auto* idx16 =
reinterpret_cast<const uint16_t*
>(idxData);
313 for (
int i = 0; i < idxCount; i++) {
314 mergedIndices.push_back(
static_cast<uint32_t
>(idx16[i]) + vertexOffset);
317 const auto* idx32 =
reinterpret_cast<const uint32_t*
>(idxData);
318 for (
int i = 0; i < idxCount; i++) {
319 mergedIndices.push_back(idx32[i] + vertexOffset);
323 for (
int i = 0; i < idxCount; i++) {
324 mergedIndices.push_back(
static_cast<uint32_t
>(idxData[i]) + vertexOffset);
329 for (
int i = 0; i < vertCount; i++) {
330 mergedIndices.push_back(vertexOffset +
static_cast<uint32_t
>(i));
335 BoundingBox instanceAabb = mi->aabb();
336 if (!aabbInitialized) {
337 mergedAabb = instanceAabb;
338 aabbInitialized =
true;
340 mergedAabb.add(instanceAabb);
343 vertexOffset +=
static_cast<uint32_t
>(vertCount);
347 const int mergedVertCount =
static_cast<int>(mergedVertices.size());
348 const int mergedIdxCount =
static_cast<int>(mergedIndices.size());
351 auto vertFormat = meshInstances[0]->mesh()->getVertexBuffer()->format();
352 VertexBufferOptions vbOpts;
353 vbOpts.data.resize(mergedVertCount *
sizeof(PackedVertex));
354 std::memcpy(vbOpts.data.data(), mergedVertices.data(), vbOpts.data.size());
356 auto mergedVB = _device->createVertexBuffer(vertFormat, mergedVertCount, vbOpts);
358 spdlog::warn(
"[BatchManager] Failed to create merged vertex buffer ({} verts)", mergedVertCount);
364 std::vector<uint8_t> idxData(mergedIdxCount *
sizeof(uint32_t));
365 std::memcpy(idxData.data(), mergedIndices.data(), idxData.size());
367 auto mergedIB = _device->createIndexBuffer(
INDEXFORMAT_UINT32, mergedIdxCount, idxData);
369 spdlog::warn(
"[BatchManager] Failed to create merged index buffer ({} indices)", mergedIdxCount);
374 auto mergedMesh = std::make_shared<Mesh>();
375 mergedMesh->setVertexBuffer(mergedVB);
376 mergedMesh->setIndexBuffer(mergedIB);
377 mergedMesh->setAabb(mergedAabb);
382 prim.count = mergedIdxCount;
384 mergedMesh->setPrimitive(prim);
387 auto batch = std::make_unique<Batch>();
388 batch->batchGroupId = batchGroupId;
389 batch->mesh = mergedMesh;
390 batch->vertexBuffer = mergedVB;
391 batch->indexBuffer = mergedIB;
394 batch->node.setPosition(Vector3(0, 0, 0));
396 Material* sharedMaterial = meshInstances[0]->material();
397 batch->meshInstance = std::make_unique<MeshInstance>(
398 mergedMesh.get(), sharedMaterial, &batch->node);
401 batch->meshInstance->setCastShadow(meshInstances[0]->castShadow());
402 batch->meshInstance->setReceiveShadow(meshInstances[0]->receiveShadow());
405 for (
auto* mi : meshInstances) {
406 mi->setVisible(
false);
407 batch->origMeshInstances.push_back(mi);
417 for (
auto& batch : _batches) {
418 if (!batch->dynamic)
continue;
419 if (batch->skinBatchInstance) {
420 batch->skinBatchInstance->updateMatrices();
422 batch->updateBoundingBox();
430 std::unique_ptr<Batch> BatchManager::createDynamicBatch(
431 const std::vector<MeshInstance*>& meshInstances,
int batchGroupId)
433 if (meshInstances.empty() || !_device)
return nullptr;
436 int totalVertices = 0;
437 int totalIndices = 0;
438 for (
auto* mi : meshInstances) {
439 auto vb = mi->mesh()->getVertexBuffer();
440 auto ib = mi->mesh()->getIndexBuffer();
443 totalVertices += vb->numVertices();
445 totalIndices += ib->numIndices();
447 totalIndices += vb->numVertices();
451 if (totalVertices == 0)
return nullptr;
454 std::vector<DynamicBatchVertex> mergedVertices;
455 mergedVertices.reserve(totalVertices);
457 std::vector<uint32_t> mergedIndices;
458 mergedIndices.reserve(totalIndices);
461 std::vector<GraphNode*> boneNodes;
462 boneNodes.reserve(meshInstances.size());
464 uint32_t vertexOffset = 0;
467 for (
int instIdx = 0; instIdx < static_cast<int>(meshInstances.size()); ++instIdx) {
468 auto* mi = meshInstances[instIdx];
469 auto vb = mi->mesh()->getVertexBuffer();
470 auto ib = mi->mesh()->getIndexBuffer();
471 if (!vb || vb->storage().empty())
continue;
473 const int vertCount = vb->numVertices();
474 const auto* srcVerts =
reinterpret_cast<const PackedVertex*
>(vb->storage().data());
477 for (
int i = 0; i < vertCount; i++) {
478 const PackedVertex& sv = srcVerts[i];
479 DynamicBatchVertex dv;
480 dv.px = sv.px; dv.py = sv.py; dv.pz = sv.pz;
481 dv.nx = sv.nx; dv.ny = sv.ny; dv.nz = sv.nz;
482 dv.u = sv.u; dv.v = sv.v;
483 dv.tx = sv.tx; dv.ty = sv.ty; dv.tz = sv.tz; dv.tw = sv.tw;
484 dv.u1 = sv.u1; dv.v1 = sv.v1;
485 dv.boneIndex =
static_cast<float>(instIdx);
486 mergedVertices.push_back(dv);
490 if (ib && !ib->storage().empty()) {
491 const int idxCount = ib->numIndices();
492 const auto* idxData = ib->storage().data();
495 const auto* idx16 =
reinterpret_cast<const uint16_t*
>(idxData);
496 for (
int i = 0; i < idxCount; i++) {
497 mergedIndices.push_back(
static_cast<uint32_t
>(idx16[i]) + vertexOffset);
500 const auto* idx32 =
reinterpret_cast<const uint32_t*
>(idxData);
501 for (
int i = 0; i < idxCount; i++) {
502 mergedIndices.push_back(idx32[i] + vertexOffset);
505 for (
int i = 0; i < idxCount; i++) {
506 mergedIndices.push_back(
static_cast<uint32_t
>(idxData[i]) + vertexOffset);
510 for (
int i = 0; i < vertCount; i++) {
511 mergedIndices.push_back(vertexOffset +
static_cast<uint32_t
>(i));
516 boneNodes.push_back(mi->node());
518 vertexOffset +=
static_cast<uint32_t
>(vertCount);
522 const int mergedVertCount =
static_cast<int>(mergedVertices.size());
523 const int mergedIdxCount =
static_cast<int>(mergedIndices.size());
526 auto dynamicFormat = std::make_shared<VertexFormat>(
527 static_cast<int>(
sizeof(DynamicBatchVertex)));
529 VertexBufferOptions vbOpts;
530 vbOpts.data.resize(mergedVertCount *
sizeof(DynamicBatchVertex));
531 std::memcpy(vbOpts.data.data(), mergedVertices.data(), vbOpts.data.size());
533 auto mergedVB = _device->createVertexBuffer(dynamicFormat, mergedVertCount, vbOpts);
535 spdlog::warn(
"[BatchManager] Failed to create dynamic batch vertex buffer ({} verts)", mergedVertCount);
541 std::vector<uint8_t> idxData(mergedIdxCount *
sizeof(uint32_t));
542 std::memcpy(idxData.data(), mergedIndices.data(), idxData.size());
544 auto mergedIB = _device->createIndexBuffer(
INDEXFORMAT_UINT32, mergedIdxCount, idxData);
546 spdlog::warn(
"[BatchManager] Failed to create dynamic batch index buffer ({} indices)", mergedIdxCount);
551 auto mergedMesh = std::make_shared<Mesh>();
552 mergedMesh->setVertexBuffer(mergedVB);
553 mergedMesh->setIndexBuffer(mergedIB);
558 prim.count = mergedIdxCount;
560 mergedMesh->setPrimitive(prim);
563 auto batch = std::make_unique<Batch>();
564 batch->batchGroupId = batchGroupId;
565 batch->dynamic =
true;
566 batch->mesh = mergedMesh;
567 batch->vertexBuffer = mergedVB;
568 batch->indexBuffer = mergedIB;
571 batch->node.setPosition(Vector3(0, 0, 0));
573 Material* sharedMaterial = meshInstances[0]->material();
574 batch->meshInstance = std::make_unique<MeshInstance>(
575 mergedMesh.get(), sharedMaterial, &batch->node);
578 batch->meshInstance->setDynamicBatch(
true);
581 batch->meshInstance->setCastShadow(meshInstances[0]->castShadow());
582 batch->meshInstance->setReceiveShadow(meshInstances[0]->receiveShadow());
585 batch->skinBatchInstance = std::make_unique<SkinBatchInstance>(std::move(boneNodes));
586 batch->meshInstance->setSkinBatchInstance(batch->skinBatchInstance.get());
589 batch->skinBatchInstance->updateMatrices();
590 batch->updateBoundingBox();
593 for (
auto* mi : meshInstances) {
594 mi->setVisible(
false);
595 batch->origMeshInstances.push_back(mi);
598 spdlog::debug(
"[BatchManager] Dynamic batch: {} instances, {} verts, {} indices, {} bones",
599 meshInstances.size(), mergedVertCount, mergedIdxCount, boneNodes.size());
BatchManager(GraphicsDevice *device)
const BatchGroup * getGroupById(int groupId) const
void prepare(Scene *scene=nullptr)
void addGroup(const BatchGroup &group)
void removeGroup(int groupId)
void destroy(Scene *scene=nullptr)
Abstract GPU interface for resource creation, state management, and draw submission.
Base class for GPU materials — owns uniform data, texture bindings, blend/depth state,...
static const std::vector< RenderComponent * > & instances()
Container for the scene graph, lighting environment, fog, skybox, and layer composition.
const std::shared_ptr< LayerComposition > & layers() const
static Matrix4 identity()