in Kits/DirectXTK12/Src/ModelLoadCMO.cpp [336:969]
std::unique_ptr<Model> DirectX::Model::CreateFromCMO(
ID3D12Device* device,
const uint8_t* meshData, size_t dataSize,
ModelLoaderFlags flags,
size_t* animsOffset)
{
if (animsOffset)
{
*animsOffset = 0;
}
if (!InitOnceExecuteOnce(&g_InitOnce, InitializeDecl, nullptr, nullptr))
throw std::system_error(std::error_code(static_cast<int>(GetLastError()), std::system_category()), "InitOnceExecuteOnce");
if (!device || !meshData)
throw std::invalid_argument("Device and meshData cannot be null");
// Meshes
auto nMesh = reinterpret_cast<const uint32_t*>(meshData);
size_t usedSize = sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (!*nMesh)
throw std::runtime_error("No meshes found");
std::map<std::wstring, int> textureDictionary;
std::vector<ModelMaterialInfo> modelmats;
auto model = std::make_unique<Model>();
model->meshes.reserve(*nMesh);
uint32_t partCount = 0;
for (size_t meshIndex = 0; meshIndex < *nMesh; ++meshIndex)
{
// Mesh name
auto nName = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
auto meshName = reinterpret_cast<const wchar_t*>(meshData + usedSize);
usedSize += sizeof(wchar_t)*(*nName);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
auto mesh = std::make_shared<ModelMesh>();
mesh->name.assign(meshName, *nName);
// Materials
auto nMats = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
std::vector<MaterialRecordCMO> materials;
materials.reserve(*nMats);
size_t baseMaterialIndex = modelmats.size();
for (size_t j = 0; j < *nMats; ++j)
{
MaterialRecordCMO m;
m.materialIndex = static_cast<uint32_t>(baseMaterialIndex + j);
// Material name
nName = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
auto matName = reinterpret_cast<const wchar_t*>(meshData + usedSize);
usedSize += sizeof(wchar_t)*(*nName);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
m.name.assign(matName, *nName);
// Material settings
auto matSetting = reinterpret_cast<const VSD3DStarter::Material*>(meshData + usedSize);
usedSize += sizeof(VSD3DStarter::Material);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
m.pMaterial = matSetting;
// Pixel shader name
nName = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
auto psName = reinterpret_cast<const wchar_t*>(meshData + usedSize);
usedSize += sizeof(wchar_t)*(*nName);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
m.pixelShader.assign(psName, *nName);
for (size_t t = 0; t < VSD3DStarter::MAX_TEXTURE; ++t)
{
nName = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
auto txtName = reinterpret_cast<const wchar_t*>(meshData + usedSize);
usedSize += sizeof(wchar_t)*(*nName);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
m.texture[t].assign(txtName, *nName);
}
materials.emplace_back(m);
}
assert(materials.size() == *nMats);
if (materials.empty())
{
// Add default material if none defined
MaterialRecordCMO m;
m.materialIndex = static_cast<uint32_t>(baseMaterialIndex);
m.pMaterial = &VSD3DStarter::s_defMaterial;
m.name = L"Default";
materials.emplace_back(m);
}
// Skeletal data?
const uint8_t* bSkeleton = meshData + usedSize;
usedSize += sizeof(uint8_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
// Submeshes
auto nSubmesh = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (!*nSubmesh)
throw std::runtime_error("No submeshes found\n");
auto subMesh = reinterpret_cast<const VSD3DStarter::SubMesh*>(meshData + usedSize);
usedSize += sizeof(VSD3DStarter::SubMesh) * (*nSubmesh);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
// Index buffers
auto nIBs = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (!*nIBs)
throw std::runtime_error("No index buffers found\n");
struct IBData
{
size_t nIndices;
const uint16_t* ptr;
};
std::vector<IBData> ibData;
ibData.reserve(*nIBs);
std::vector<SharedGraphicsResource> ibs;
ibs.resize(*nIBs);
for (size_t j = 0; j < *nIBs; ++j)
{
auto nIndexes = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (!*nIndexes)
throw std::runtime_error("Empty index buffer found\n");
const uint64_t sizeInBytes = uint64_t(*(nIndexes)) * sizeof(uint16_t);
if (sizeInBytes > UINT32_MAX)
throw std::runtime_error("IB too large");
if (!(flags & ModelLoader_AllowLargeModels))
{
if (sizeInBytes > (D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u))
throw std::runtime_error("IB too large for DirectX 12");
}
auto ibBytes = static_cast<size_t>(sizeInBytes);
auto indexes = reinterpret_cast<const uint16_t*>(meshData + usedSize);
usedSize += ibBytes;
if (dataSize < usedSize)
throw std::runtime_error("End of file");
IBData ib;
ib.nIndices = *nIndexes;
ib.ptr = indexes;
ibData.emplace_back(ib);
ibs[j] = GraphicsMemory::Get(device).Allocate(ibBytes);
memcpy(ibs[j].Memory(), indexes, ibBytes);
}
assert(ibData.size() == *nIBs);
assert(ibs.size() == *nIBs);
// Vertex buffers
auto nVBs = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (!*nVBs)
throw std::runtime_error("No vertex buffers found\n");
struct VBData
{
size_t nVerts;
const VertexPositionNormalTangentColorTexture* ptr;
const VSD3DStarter::SkinningVertex* skinPtr;
};
std::vector<VBData> vbData;
vbData.reserve(*nVBs);
for (size_t j = 0; j < *nVBs; ++j)
{
auto nVerts = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (!*nVerts)
throw std::runtime_error("Empty vertex buffer found\n");
const size_t vbBytes = sizeof(VertexPositionNormalTangentColorTexture) * (*(nVerts));
auto verts = reinterpret_cast<const VertexPositionNormalTangentColorTexture*>(meshData + usedSize);
usedSize += vbBytes;
if (dataSize < usedSize)
throw std::runtime_error("End of file");
VBData vb;
vb.nVerts = *nVerts;
vb.ptr = verts;
vb.skinPtr = nullptr;
vbData.emplace_back(vb);
}
assert(vbData.size() == *nVBs);
// Skinning vertex buffers
auto nSkinVBs = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (*nSkinVBs)
{
if (*nSkinVBs != *nVBs)
throw std::runtime_error("Number of VBs not equal to number of skin VBs");
for (size_t j = 0; j < *nSkinVBs; ++j)
{
auto nVerts = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (!*nVerts)
throw std::runtime_error("Empty skinning vertex buffer found\n");
if (vbData[j].nVerts != *nVerts)
throw std::runtime_error("Mismatched number of verts for skin VBs");
const size_t vbBytes = sizeof(VSD3DStarter::SkinningVertex) * (*(nVerts));
auto verts = reinterpret_cast<const VSD3DStarter::SkinningVertex*>(meshData + usedSize);
usedSize += vbBytes;
if (dataSize < usedSize)
throw std::runtime_error("End of file");
vbData[j].skinPtr = verts;
}
}
// Extents
auto extents = reinterpret_cast<const VSD3DStarter::MeshExtents*>(meshData + usedSize);
usedSize += sizeof(VSD3DStarter::MeshExtents);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
mesh->boundingSphere.Center.x = extents->CenterX;
mesh->boundingSphere.Center.y = extents->CenterY;
mesh->boundingSphere.Center.z = extents->CenterZ;
mesh->boundingSphere.Radius = extents->Radius;
XMVECTOR min = XMVectorSet(extents->MinX, extents->MinY, extents->MinZ, 0.f);
XMVECTOR max = XMVectorSet(extents->MaxX, extents->MaxY, extents->MaxZ, 0.f);
BoundingBox::CreateFromPoints(mesh->boundingBox, min, max);
// Load model bones (if present and requested)
if (*bSkeleton && (flags & ModelLoader_IncludeBones))
{
// Bones
auto nBones = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (!*nBones)
throw std::runtime_error("Animation bone data is missing\n");
ModelBone::Collection bones;
bones.resize(*nBones);
auto transforms = ModelBone::MakeArray(*nBones);
auto invTransforms = ModelBone::MakeArray(*nBones);
for (uint32_t j = 0; j < *nBones; ++j)
{
// Bone name
nName = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
auto boneName = reinterpret_cast<const wchar_t*>(meshData + usedSize);
usedSize += sizeof(wchar_t) * (*nName);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
bones[j].name = boneName;
// Bone settings
auto cmobones = reinterpret_cast<const VSD3DStarter::Bone*>(meshData + usedSize);
usedSize += sizeof(VSD3DStarter::Bone);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
transforms[j] = XMLoadFloat4x4(&cmobones->LocalTransform);
invTransforms[j] = XMLoadFloat4x4(&cmobones->InvBindPos);
if (cmobones->ParentIndex < 0)
{
if (!j)
continue;
// Add as a sibling of the root bone
uint32_t index = 0;
for (size_t visited = 0;; ++visited)
{
if (visited >= *nBones)
throw std::runtime_error("Skeleton bones form an invalid graph");
uint32_t sibling = bones[index].siblingIndex;
if (sibling == ModelBone::c_Invalid)
{
bones[index].siblingIndex = j;
break;
}
if (sibling >= *nBones)
throw std::runtime_error("Skeleton bones corrupt");
index = sibling;
}
}
else if (static_cast<uint32_t>(cmobones->ParentIndex) >= *nBones)
{
throw std::runtime_error("Skeleton bones corrupt");
}
else
{
if (!j)
throw std::runtime_error("First bone must be root!");
auto index = static_cast<uint32_t>(cmobones->ParentIndex);
bones[j].parentIndex = index;
// Add as the only child of the parent
if (bones[index].childIndex == ModelBone::c_Invalid)
{
bones[index].childIndex = j;
}
else
{
// Otherwise add as a sibling of the parent's other children
index = bones[index].childIndex;
for (size_t visited = 0;; ++visited)
{
if (visited >= *nBones)
throw std::runtime_error("Skeleton bones form an invalid graph");
uint32_t sibling = bones[index].siblingIndex;
if (sibling == ModelBone::c_Invalid)
{
bones[index].siblingIndex = j;
break;
}
if (sibling >= *nBones)
throw std::runtime_error("Skeleton bones corrupt");
index = sibling;
}
}
}
}
std::swap(model->bones, bones);
std::swap(model->boneMatrices, transforms);
std::swap(model->invBindPoseMatrices, invTransforms);
// Animation Clips
if (animsOffset)
{
// Optional return for offset to start of animation clips in the CMO.
size_t offset = usedSize;
auto nClips = reinterpret_cast<const uint32_t*>(meshData + usedSize);
usedSize += sizeof(uint32_t);
if (dataSize < usedSize)
throw std::runtime_error("End of file");
if (*nClips > 0)
{
*animsOffset = offset;
}
}
}
bool enableSkinning = (*nSkinVBs) != 0 && !(flags & ModelLoader_DisableSkinning);
// Build vertex buffers
std::vector<SharedGraphicsResource> vbs;
vbs.resize(*nVBs);
const size_t stride = enableSkinning ? sizeof(VertexPositionNormalTangentColorTextureSkinning)
: sizeof(VertexPositionNormalTangentColorTexture);
for (size_t j = 0; j < *nVBs; ++j)
{
const size_t nVerts = vbData[j].nVerts;
const uint64_t sizeInBytes = uint64_t(stride) * uint64_t(nVerts);
if (sizeInBytes > UINT32_MAX)
throw std::runtime_error("VB too large");
if (!(flags & ModelLoader_AllowLargeModels))
{
if (sizeInBytes > uint64_t(D3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u))
throw std::runtime_error("VB too large for DirectX 12");
}
const size_t bytes = static_cast<size_t>(sizeInBytes);
{
auto temp = std::make_unique<uint8_t[]>(bytes + (sizeof(uint32_t) * nVerts));
auto visited = reinterpret_cast<uint32_t*>(temp.get() + bytes);
memset(visited, 0xff, sizeof(uint32_t) * nVerts);
assert(vbData[j].ptr != nullptr);
if (enableSkinning)
{
// Combine CMO multi-stream data into a single stream
auto skinptr = vbData[j].skinPtr;
assert(skinptr != nullptr);
uint8_t* ptr = temp.get();
auto sptr = vbData[j].ptr;
for (size_t v = 0; v < nVerts; ++v)
{
*reinterpret_cast<VertexPositionNormalTangentColorTexture*>(ptr) = sptr[v];
auto skinv = reinterpret_cast<VertexPositionNormalTangentColorTextureSkinning*>(ptr);
skinv->SetBlendIndices(*reinterpret_cast<const XMUINT4*>(skinptr[v].boneIndex));
skinv->SetBlendWeights(*reinterpret_cast<const XMFLOAT4*>(skinptr[v].boneWeight));
ptr += stride;
}
}
else
{
memcpy(temp.get(), vbData[j].ptr, bytes);
}
{
// Need to fix up VB tex coords for UV transform which is not supported by basic effects
for (size_t k = 0; k < *nSubmesh; ++k)
{
auto& sm = subMesh[k];
if (sm.VertexBufferIndex != j)
continue;
if ((sm.IndexBufferIndex >= *nIBs)
|| (sm.MaterialIndex >= materials.size()))
throw std::out_of_range("Invalid submesh found\n");
XMMATRIX uvTransform = XMLoadFloat4x4(&materials[sm.MaterialIndex].pMaterial->UVTransform);
auto ib = ibData[sm.IndexBufferIndex].ptr;
const size_t count = ibData[sm.IndexBufferIndex].nIndices;
for (size_t q = 0; q < count; ++q)
{
size_t v = ib[q];
if (v >= nVerts)
throw std::out_of_range("Invalid index found\n");
auto verts = reinterpret_cast<VertexPositionNormalTangentColorTexture*>(temp.get() + (v * stride));
if (visited[v] == uint32_t(-1))
{
visited[v] = sm.MaterialIndex;
XMVECTOR t = XMLoadFloat2(&verts->textureCoordinate);
t = XMVectorSelect(g_XMIdentityR3, t, g_XMSelect1110);
t = XMVector4Transform(t, uvTransform);
XMStoreFloat2(&verts->textureCoordinate, t);
}
else if (visited[v] != sm.MaterialIndex)
{
#ifdef _DEBUG
XMMATRIX uv2 = XMLoadFloat4x4(&materials[visited[v]].pMaterial->UVTransform);
if (XMVector4NotEqual(uvTransform.r[0], uv2.r[0])
|| XMVector4NotEqual(uvTransform.r[1], uv2.r[1])
|| XMVector4NotEqual(uvTransform.r[2], uv2.r[2])
|| XMVector4NotEqual(uvTransform.r[3], uv2.r[3]))
{
DebugTrace("WARNING: %ls - mismatched UV transforms for the same vertex; texture coordinates may not be correct\n", mesh->name.c_str());
}
#endif
}
}
}
}
vbs[j] = GraphicsMemory::Get(device).Allocate(bytes);
memcpy(vbs[j].Memory(), temp.get(), bytes);
}
}
assert(vbs.size() == *nVBs);
// Create model materials
bool srgb = (flags & ModelLoader_MaterialColorsSRGB) != 0;
for (size_t j = 0; j < materials.size(); ++j)
{
auto& m = materials[j];
ModelMaterialInfo info;
info.name = m.name.c_str();
info.specularPower = m.pMaterial->SpecularPower;
info.perVertexColor = true;
info.enableSkinning = enableSkinning;
info.alphaValue = m.pMaterial->Diffuse.w;
info.ambientColor = GetMaterialColor(m.pMaterial->Ambient.x, m.pMaterial->Ambient.y, m.pMaterial->Ambient.z, srgb);
info.diffuseColor = GetMaterialColor(m.pMaterial->Diffuse.x, m.pMaterial->Diffuse.y, m.pMaterial->Diffuse.z, srgb);
info.specularColor = GetMaterialColor(m.pMaterial->Specular.x, m.pMaterial->Specular.y, m.pMaterial->Specular.z, srgb);
info.emissiveColor = GetMaterialColor(m.pMaterial->Emissive.x, m.pMaterial->Emissive.y, m.pMaterial->Emissive.z, srgb);
info.diffuseTextureIndex = GetUniqueTextureIndex(m.texture[0].c_str(), textureDictionary);
info.samplerIndex = (info.diffuseTextureIndex == -1) ? -1 : static_cast<int>(CommonStates::SamplerIndex::AnisotropicWrap);
modelmats.emplace_back(info);
}
// Build mesh parts
for (size_t j = 0; j < *nSubmesh; ++j)
{
auto& sm = subMesh[j];
if ((sm.IndexBufferIndex >= *nIBs)
|| (sm.VertexBufferIndex >= *nVBs)
|| (sm.MaterialIndex >= materials.size()))
throw std::out_of_range("Invalid submesh found\n");
auto& mat = materials[sm.MaterialIndex];
auto part = new ModelMeshPart(partCount++);
part->indexCount = sm.PrimCount * 3;
part->materialIndex = mat.materialIndex;
part->startIndex = sm.StartIndex;
part->vertexStride = static_cast<UINT>(stride);
part->indexBuffer = ibs[sm.IndexBufferIndex];
part->indexBufferSize = static_cast<uint32_t>(ibs[sm.IndexBufferIndex].Size());
part->vertexBuffer = vbs[sm.VertexBufferIndex];
part->vertexBufferSize = static_cast<uint32_t>(vbs[sm.VertexBufferIndex].Size());
part->vbDecl = enableSkinning ? g_vbdeclSkinning : g_vbdecl;
if (mat.pMaterial->Diffuse.w < 1)
{
mesh->alphaMeshParts.emplace_back(part);
}
else
{
mesh->opaqueMeshParts.emplace_back(part);
}
}
model->meshes.emplace_back(mesh);
}
// Copy the materials and texture names into contiguous arrays
model->materials = std::move(modelmats);
model->textureNames.resize(textureDictionary.size());
for (auto texture = std::cbegin(textureDictionary); texture != std::cend(textureDictionary); ++texture)
{
model->textureNames[static_cast<size_t>(texture->second)] = texture->first;
}
return model;
}