//-------------------------------------------------------------------------------------- // File: ModelLoadCMO.cpp // // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // // http://go.microsoft.com/fwlink/?LinkId=248929 //-------------------------------------------------------------------------------------- #include "pch.h" #include "Model.h" #include "DirectXHelpers.h" #include "Effects.h" #include "VertexTypes.h" #include "BinaryReader.h" #include "PlatformHelpers.h" using namespace DirectX; using Microsoft::WRL::ComPtr; #include "CMO.h" static_assert(sizeof(VertexPositionNormalTangentColorTexture) == sizeof(VSD3DStarter::VertexPositionNormalTangentColorTexture), "mismatch with CMO vertex type"); namespace { //---------------------------------------------------------------------------------- struct MaterialRecordCMO { const VSD3DStarter::Material* pMaterial; std::wstring name; std::wstring pixelShader; std::wstring texture[VSD3DStarter::MAX_TEXTURE]; std::shared_ptr effect; ComPtr il; MaterialRecordCMO() noexcept : pMaterial(nullptr), texture{} {} }; // Helper for creating a D3D input layout. void CreateCMOInputLayout(_In_ ID3D11Device* device, _In_ IEffect* effect, _Outptr_ ID3D11InputLayout** pInputLayout, bool skinning) { if (skinning) { ThrowIfFailed( CreateInputLayoutFromEffect(device, effect, pInputLayout) ); } else { ThrowIfFailed( CreateInputLayoutFromEffect(device, effect, pInputLayout) ); } assert(pInputLayout != nullptr && *pInputLayout != nullptr); _Analysis_assume_(pInputLayout != nullptr && *pInputLayout != nullptr); SetDebugObjectName(*pInputLayout, "ModelCMO"); } // Shared VB input element description INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT; std::shared_ptr g_vbdecl; std::shared_ptr g_vbdeclSkinning; BOOL CALLBACK InitializeDecl(PINIT_ONCE initOnce, PVOID Parameter, PVOID *lpContext) { UNREFERENCED_PARAMETER(initOnce); UNREFERENCED_PARAMETER(Parameter); UNREFERENCED_PARAMETER(lpContext); g_vbdecl = std::make_shared( VertexPositionNormalTangentColorTexture::InputElements, VertexPositionNormalTangentColorTexture::InputElements + VertexPositionNormalTangentColorTexture::InputElementCount); g_vbdeclSkinning = std::make_shared( VertexPositionNormalTangentColorTextureSkinning::InputElements, VertexPositionNormalTangentColorTextureSkinning::InputElements + VertexPositionNormalTangentColorTextureSkinning::InputElementCount); return TRUE; } inline XMFLOAT3 GetMaterialColor(float r, float g, float b, bool srgb) { if (srgb) { XMVECTOR v = XMVectorSet(r, g, b, 1.f); v = XMColorSRGBToRGB(v); XMFLOAT3 result; XMStoreFloat3(&result, v); return result; } else { return XMFLOAT3(r, g, b); } } } //====================================================================================== // Model Loader //====================================================================================== _Use_decl_annotations_ std::unique_ptr DirectX::Model::CreateFromCMO( ID3D11Device* device, const uint8_t* meshData, size_t dataSize, IEffectFactory& fxFactory, 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(GetLastError()), std::system_category()), "InitOnceExecuteOnce"); if (!device || !meshData) throw std::invalid_argument("Device and meshData cannot be null"); auto fxFactoryDGSL = dynamic_cast(&fxFactory); // Meshes auto nMesh = reinterpret_cast(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"); auto model = std::make_unique(); for (size_t meshIndex = 0; meshIndex < *nMesh; ++meshIndex) { // Mesh name auto nName = reinterpret_cast(meshData + usedSize); usedSize += sizeof(uint32_t); if (dataSize < usedSize) throw std::runtime_error("End of file"); auto meshName = reinterpret_cast(static_cast(meshData + usedSize)); // [CodeQL.SM02986]: The cast here is intentional. usedSize += sizeof(wchar_t)*(*nName); if (dataSize < usedSize) throw std::runtime_error("End of file"); auto mesh = std::make_shared(); mesh->name.assign(meshName, *nName); mesh->ccw = (flags & ModelLoader_CounterClockwise) != 0; mesh->pmalpha = (flags & ModelLoader_PremultipledAlpha) != 0; // Materials auto nMats = reinterpret_cast(meshData + usedSize); usedSize += sizeof(uint32_t); if (dataSize < usedSize) throw std::runtime_error("End of file"); std::vector materials; materials.reserve(*nMats); for (size_t j = 0; j < *nMats; ++j) { MaterialRecordCMO m; // Material name nName = reinterpret_cast(meshData + usedSize); usedSize += sizeof(uint32_t); if (dataSize < usedSize) throw std::runtime_error("End of file"); auto matName = reinterpret_cast(static_cast(meshData + usedSize)); // [CodeQL.SM02986]: The cast here is intentional. 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(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(meshData + usedSize); usedSize += sizeof(uint32_t); if (dataSize < usedSize) throw std::runtime_error("End of file"); auto psName = reinterpret_cast(static_cast(meshData + usedSize)); // [CodeQL.SM02986]: The cast here is intentional. 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(meshData + usedSize); usedSize += sizeof(uint32_t); if (dataSize < usedSize) throw std::runtime_error("End of file"); auto txtName = reinterpret_cast(static_cast(meshData + usedSize)); // [CodeQL.SM02986]: The cast here is intentional. 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.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(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(meshData + usedSize); usedSize += sizeof(VSD3DStarter::SubMesh) * (*nSubmesh); if (dataSize < usedSize) throw std::runtime_error("End of file"); // Index buffers auto nIBs = reinterpret_cast(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.reserve(*nIBs); std::vector> ibs; ibs.resize(*nIBs); for (size_t j = 0; j < *nIBs; ++j) { auto nIndexes = reinterpret_cast(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 > (D3D11_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u)) throw std::runtime_error("IB too large for DirectX 11"); } auto const ibBytes = static_cast(sizeInBytes); auto indexes = reinterpret_cast(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); D3D11_BUFFER_DESC desc = {}; desc.Usage = D3D11_USAGE_DEFAULT; desc.ByteWidth = static_cast(ibBytes); desc.BindFlags = D3D11_BIND_INDEX_BUFFER; D3D11_SUBRESOURCE_DATA initData = { indexes, 0, 0 }; ThrowIfFailed( device->CreateBuffer(&desc, &initData, &ibs[j]) ); SetDebugObjectName(ibs[j].Get(), "ModelCMO"); } assert(ibData.size() == *nIBs); assert(ibs.size() == *nIBs); // Vertex buffers auto nVBs = reinterpret_cast(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.reserve(*nVBs); for (size_t j = 0; j < *nVBs; ++j) { auto nVerts = reinterpret_cast(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(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(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(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(meshData + usedSize); usedSize += vbBytes; if (dataSize < usedSize) throw std::runtime_error("End of file"); vbData[j].skinPtr = verts; } } // Extents auto extents = reinterpret_cast(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; const XMVECTOR min = XMVectorSet(extents->MinX, extents->MinY, extents->MinZ, 0.f); const 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(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(meshData + usedSize); usedSize += sizeof(uint32_t); if (dataSize < usedSize) throw std::runtime_error("End of file"); auto boneName = reinterpret_cast(static_cast(meshData + usedSize)); // [CodeQL.SM02986]: The cast here is intentional. 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(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"); const 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(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(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"); const 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(meshData + usedSize); usedSize += sizeof(uint32_t); if (dataSize < usedSize) throw std::runtime_error("End of file"); if (*nClips > 0) { *animsOffset = offset; } } } const bool enableSkinning = (*nSkinVBs) != 0 && !(flags & ModelLoader_DisableSkinning); // Build vertex buffers std::vector> 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(D3D11_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_A_TERM * 1024u * 1024u)) throw std::runtime_error("VB too large for DirectX 11"); } const size_t bytes = static_cast(sizeInBytes); D3D11_BUFFER_DESC desc = {}; desc.Usage = D3D11_USAGE_DEFAULT; desc.ByteWidth = static_cast(bytes); desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; if (fxFactoryDGSL && !enableSkinning) { // Can use CMO vertex data directly D3D11_SUBRESOURCE_DATA initData = { vbData[j].ptr, 0, 0 }; ThrowIfFailed( device->CreateBuffer(&desc, &initData, &vbs[j]) ); } else { auto temp = std::make_unique(bytes + (sizeof(uint32_t) * nVerts)); auto visited = reinterpret_cast(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(ptr) = sptr[v]; auto skinv = reinterpret_cast(ptr); skinv->SetBlendIndices(*reinterpret_cast(skinptr[v].boneIndex)); skinv->SetBlendWeights(*reinterpret_cast(skinptr[v].boneWeight)); ptr += stride; } } else { memcpy(temp.get(), vbData[j].ptr, bytes); } if (!fxFactoryDGSL) { // 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"); const 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(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 const 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 } } } } // Create vertex buffer from temporary buffer D3D11_SUBRESOURCE_DATA initData = { temp.get(), 0, 0 }; ThrowIfFailed( device->CreateBuffer(&desc, &initData, &vbs[j]) ); } SetDebugObjectName(vbs[j].Get(), "ModelCMO"); } assert(vbs.size() == *nVBs); // Create Effects const bool srgb = (flags & ModelLoader_MaterialColorsSRGB) != 0; for (size_t j = 0; j < materials.size(); ++j) { auto& m = materials[j]; if (fxFactoryDGSL) { DGSLEffectFactory::DGSLEffectInfo info; info.name = m.name.c_str(); info.specularPower = m.pMaterial->SpecularPower; info.perVertexColor = true; info.enableSkinning = enableSkinning; info.alpha = 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.diffuseTexture = m.texture[0].empty() ? nullptr : m.texture[0].c_str(); info.specularTexture = m.texture[1].empty() ? nullptr : m.texture[1].c_str(); info.normalTexture = m.texture[2].empty() ? nullptr : m.texture[2].c_str(); info.emissiveTexture = m.texture[3].empty() ? nullptr : m.texture[3].c_str(); info.pixelShader = m.pixelShader.c_str(); constexpr int offset = DGSLEffectFactory::DGSLEffectInfo::BaseTextureOffset; for (int i = 0; i < (DGSLEffect::MaxTextures - offset); ++i) { info.textures[i] = m.texture[i + offset].empty() ? nullptr : m.texture[i + offset].c_str(); } m.effect = fxFactoryDGSL->CreateDGSLEffect(info, nullptr); auto dgslEffect = static_cast(m.effect.get()); dgslEffect->SetUVTransform(XMLoadFloat4x4(&m.pMaterial->UVTransform)); } else { EffectFactory::EffectInfo info; info.name = m.name.c_str(); info.specularPower = m.pMaterial->SpecularPower; info.perVertexColor = true; info.enableSkinning = enableSkinning; info.alpha = 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.diffuseTexture = m.texture[0].c_str(); m.effect = fxFactory.CreateEffect(info, nullptr); } CreateCMOInputLayout(device, m.effect.get(), &m.il, enableSkinning); } // 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(); if (mat.pMaterial->Diffuse.w < 1) part->isAlpha = true; part->indexCount = sm.PrimCount * 3; part->startIndex = sm.StartIndex; part->vertexStride = static_cast(stride); part->inputLayout = mat.il; part->indexBuffer = ibs[sm.IndexBufferIndex]; part->vertexBuffer = vbs[sm.VertexBufferIndex]; part->effect = mat.effect; part->vbDecl = enableSkinning ? g_vbdeclSkinning : g_vbdecl; mesh->meshParts.emplace_back(part); } model->meshes.emplace_back(mesh); } return model; } //-------------------------------------------------------------------------------------- _Use_decl_annotations_ std::unique_ptr DirectX::Model::CreateFromCMO( ID3D11Device* device, const wchar_t* szFileName, IEffectFactory& fxFactory, ModelLoaderFlags flags, size_t* animsOffset) { if (animsOffset) { *animsOffset = 0; } size_t dataSize = 0; std::unique_ptr data; HRESULT hr = BinaryReader::ReadEntireFile(szFileName, data, &dataSize); if (FAILED(hr)) { DebugTrace("ERROR: CreateFromCMO failed (%08X) loading '%ls'\n", static_cast(hr), szFileName); throw std::runtime_error("CreateFromCMO"); } auto model = CreateFromCMO(device, data.get(), dataSize, fxFactory, flags, animsOffset); model->name = szFileName; return model; } //-------------------------------------------------------------------------------------- // Adapters for /Zc:wchar_t- clients #if defined(_MSC_VER) && !defined(_NATIVE_WCHAR_T_DEFINED) _Use_decl_annotations_ std::unique_ptr Model::CreateFromCMO( ID3D11Device* device, const __wchar_t* szFileName, IEffectFactory& fxFactory, ModelLoaderFlags flags, size_t* animsOffset) { return Model::CreateFromCMO(device, reinterpret_cast(szFileName), fxFactory, flags, animsOffset); } #endif