MeshResource.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. // Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
  2. // All rights reserved.
  3. // Code licensed under the BSD License.
  4. // http://www.anki3d.org/LICENSE
  5. #include <AnKi/Resource/MeshResource.h>
  6. #include <AnKi/Resource/ResourceManager.h>
  7. #include <AnKi/Resource/MeshBinaryLoader.h>
  8. #include <AnKi/Resource/AsyncLoader.h>
  9. #include <AnKi/Util/Functions.h>
  10. #include <AnKi/Util/Filesystem.h>
  11. #include <AnKi/Core/App.h>
  12. #include <AnKi/Physics/PhysicsWorld.h>
  13. namespace anki {
  14. class MeshResource::LoadContext
  15. {
  16. public:
  17. MeshResourcePtr m_mesh;
  18. MeshBinaryLoader m_loader;
  19. LoadContext(MeshResource* mesh)
  20. : m_mesh(mesh)
  21. , m_loader(&ResourceMemoryPool::getSingleton())
  22. {
  23. }
  24. };
  25. /// Mesh upload async task.
  26. class MeshResource::LoadTask : public AsyncLoaderTask
  27. {
  28. public:
  29. MeshResource::LoadContext m_ctx;
  30. LoadTask(MeshResource* mesh)
  31. : m_ctx(mesh)
  32. {
  33. }
  34. Error operator()([[maybe_unused]] AsyncLoaderTaskContext& ctx) final
  35. {
  36. return m_ctx.m_mesh->loadAsync(m_ctx.m_loader);
  37. }
  38. static BaseMemoryPool& getMemoryPool()
  39. {
  40. return ResourceMemoryPool::getSingleton();
  41. }
  42. };
  43. MeshResource::MeshResource()
  44. {
  45. }
  46. MeshResource::~MeshResource()
  47. {
  48. for(Lod& lod : m_lods)
  49. {
  50. UnifiedGeometryBuffer::getSingleton().deferredFree(lod.m_indexBufferAllocationToken);
  51. for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
  52. {
  53. UnifiedGeometryBuffer::getSingleton().deferredFree(lod.m_vertexBuffersAllocationToken[stream]);
  54. }
  55. }
  56. }
  57. Error MeshResource::load(const ResourceFilename& filename, Bool async)
  58. {
  59. UniquePtr<LoadTask> task;
  60. LoadContext* ctx;
  61. LoadContext localCtx(this);
  62. String basename;
  63. getFilepathFilename(filename, basename);
  64. const Bool rayTracingEnabled = GrManager::getSingleton().getDeviceCapabilities().m_rayTracingEnabled;
  65. if(async)
  66. {
  67. task.reset(ResourceManager::getSingleton().getAsyncLoader().newTask<LoadTask>(this));
  68. ctx = &task->m_ctx;
  69. }
  70. else
  71. {
  72. task.reset(nullptr);
  73. ctx = &localCtx;
  74. }
  75. // Open file
  76. MeshBinaryLoader& loader = ctx->m_loader;
  77. ANKI_CHECK(loader.load(filename));
  78. const MeshBinaryHeader& header = loader.getHeader();
  79. // Misc
  80. m_indexType = header.m_indexType;
  81. m_aabb.setMin(header.m_boundingVolume.m_aabbMin);
  82. m_aabb.setMax(header.m_boundingVolume.m_aabbMax);
  83. m_positionsScale = header.m_vertexAttributes[VertexStreamId::kPosition].m_scale[0];
  84. m_positionsTranslation = Vec3(&header.m_vertexAttributes[VertexStreamId::kPosition].m_translation[0]);
  85. m_isConvex = !!(loader.getHeader().m_flags & MeshBinaryFlag::kConvex);
  86. // Submeshes
  87. m_subMeshes.resize(header.m_subMeshCount);
  88. for(U32 i = 0; i < m_subMeshes.getSize(); ++i)
  89. {
  90. for(U32 lod = 0; lod < header.m_lodCount; ++lod)
  91. {
  92. m_subMeshes[i].m_firstIndices[lod] = loader.getSubMeshes()[i].m_lods[lod].m_firstIndex;
  93. m_subMeshes[i].m_indexCounts[lod] = loader.getSubMeshes()[i].m_lods[lod].m_indexCount;
  94. m_subMeshes[i].m_firstMeshlet[lod] = loader.getSubMeshes()[i].m_lods[lod].m_firstMeshlet;
  95. m_subMeshes[i].m_meshletCounts[lod] = loader.getSubMeshes()[i].m_lods[lod].m_meshletCount;
  96. }
  97. m_subMeshes[i].m_aabb.setMin(loader.getSubMeshes()[i].m_boundingVolume.m_aabbMin);
  98. m_subMeshes[i].m_aabb.setMax(loader.getSubMeshes()[i].m_boundingVolume.m_aabbMax);
  99. }
  100. // LODs
  101. m_lods.resize(header.m_lodCount);
  102. for(I32 l = I32(header.m_lodCount - 1); l >= 0; --l)
  103. {
  104. Lod& lod = m_lods[l];
  105. // Index stuff
  106. lod.m_indexCount = header.m_indexCounts[l];
  107. ANKI_ASSERT((lod.m_indexCount % 3) == 0 && "Expecting triangles");
  108. const PtrSize indexBufferSize = PtrSize(lod.m_indexCount) * getIndexSize(m_indexType);
  109. lod.m_indexBufferAllocationToken = UnifiedGeometryBuffer::getSingleton().allocate(indexBufferSize, getIndexSize(m_indexType));
  110. // Vertex stuff
  111. lod.m_vertexCount = header.m_vertexCounts[l];
  112. for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
  113. {
  114. if(header.m_vertexAttributes[stream].m_format == Format::kNone)
  115. {
  116. continue;
  117. }
  118. m_presentVertStreams |= VertexStreamMask(1 << stream);
  119. lod.m_vertexBuffersAllocationToken[stream] =
  120. UnifiedGeometryBuffer::getSingleton().allocateFormat(kMeshRelatedVertexStreamFormats[stream], lod.m_vertexCount);
  121. }
  122. // Meshlet
  123. if(GrManager::getSingleton().getDeviceCapabilities().m_meshShaders || g_meshletRenderingCVar)
  124. {
  125. const PtrSize meshletIndicesSize = header.m_meshletPrimitiveCounts[l] * sizeof(U8Vec4);
  126. lod.m_meshletIndices = UnifiedGeometryBuffer::getSingleton().allocate(meshletIndicesSize, sizeof(U8Vec4));
  127. const PtrSize meshletBoundingVolumesSize = header.m_meshletCounts[l] * sizeof(MeshletBoundingVolume);
  128. lod.m_meshletBoundingVolumes = UnifiedGeometryBuffer::getSingleton().allocate(meshletBoundingVolumesSize, sizeof(MeshletBoundingVolume));
  129. const PtrSize meshletGeomDescriptorsSize = header.m_meshletCounts[l] * sizeof(MeshletGeometryDescriptor);
  130. lod.m_meshletGeometryDescriptors =
  131. UnifiedGeometryBuffer::getSingleton().allocate(meshletGeomDescriptorsSize, sizeof(MeshletGeometryDescriptor));
  132. lod.m_meshletCount = header.m_meshletCounts[l];
  133. }
  134. }
  135. // BLAS
  136. if(rayTracingEnabled)
  137. {
  138. for(U32 submeshIdx = 0; submeshIdx < m_subMeshes.getSize(); ++submeshIdx)
  139. {
  140. SubMesh& subMesh = m_subMeshes[submeshIdx];
  141. for(U32 lodIdx = 0; lodIdx < header.m_lodCount; ++lodIdx)
  142. {
  143. Lod& lod = m_lods[lodIdx];
  144. AccelerationStructureInitInfo inf(ResourceString().sprintf("%s_%s", "BLAS", basename.cstr()));
  145. inf.m_type = AccelerationStructureType::kBottomLevel;
  146. inf.m_bottomLevel.m_indexBuffer = BufferView(lod.m_indexBufferAllocationToken)
  147. .incrementOffset(getIndexSize(m_indexType) * subMesh.m_firstIndices[lodIdx])
  148. .setRange(getIndexSize(m_indexType) * subMesh.m_indexCounts[lodIdx]);
  149. inf.m_bottomLevel.m_indexCount = subMesh.m_indexCounts[lodIdx];
  150. inf.m_bottomLevel.m_indexType = m_indexType;
  151. inf.m_bottomLevel.m_positionBuffer = lod.m_vertexBuffersAllocationToken[VertexStreamId::kPosition];
  152. inf.m_bottomLevel.m_positionStride = getFormatInfo(kMeshRelatedVertexStreamFormats[VertexStreamId::kPosition]).m_texelSize;
  153. inf.m_bottomLevel.m_positionsFormat = kMeshRelatedVertexStreamFormats[VertexStreamId::kPosition];
  154. inf.m_bottomLevel.m_positionCount = lod.m_vertexCount;
  155. subMesh.m_blas[lodIdx] = GrManager::getSingleton().newAccelerationStructure(inf);
  156. }
  157. }
  158. }
  159. // Clear the buffers
  160. if(async)
  161. {
  162. CommandBufferInitInfo cmdbinit("MeshResourceClear");
  163. cmdbinit.m_flags = CommandBufferFlag::kSmallBatch | CommandBufferFlag::kGeneralWork;
  164. CommandBufferPtr cmdb = GrManager::getSingleton().newCommandBuffer(cmdbinit);
  165. for(const Lod& lod : m_lods)
  166. {
  167. cmdb->zeroBuffer(lod.m_indexBufferAllocationToken.getCompleteBufferView());
  168. for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
  169. {
  170. if(header.m_vertexAttributes[stream].m_format != Format::kNone)
  171. {
  172. cmdb->zeroBuffer(lod.m_vertexBuffersAllocationToken[stream].getCompleteBufferView());
  173. }
  174. }
  175. if(lod.m_meshletIndices.isValid())
  176. {
  177. cmdb->zeroBuffer(lod.m_meshletIndices);
  178. cmdb->zeroBuffer(lod.m_meshletBoundingVolumes);
  179. cmdb->zeroBuffer(lod.m_meshletGeometryDescriptors);
  180. }
  181. }
  182. const BufferBarrierInfo barrier = {UnifiedGeometryBuffer::getSingleton().getBufferView(), BufferUsageBit::kCopyDestination,
  183. BufferUsageBit::kVertexOrIndex};
  184. cmdb->setPipelineBarrier({}, {&barrier, 1}, {});
  185. cmdb->endRecording();
  186. GrManager::getSingleton().submit(cmdb.get());
  187. }
  188. // Submit the loading task
  189. if(async)
  190. {
  191. ResourceManager::getSingleton().getAsyncLoader().submitTask(task.get());
  192. LoadTask* pTask;
  193. task.moveAndReset(pTask);
  194. }
  195. else
  196. {
  197. ANKI_CHECK(loadAsync(loader));
  198. }
  199. return Error::kNone;
  200. }
  201. Error MeshResource::loadAsync(MeshBinaryLoader& loader) const
  202. {
  203. GrManager& gr = GrManager::getSingleton();
  204. TransferGpuAllocator& transferAlloc = ResourceManager::getSingleton().getTransferGpuAllocator();
  205. Array<TransferGpuAllocatorHandle, kMaxLodCount*(U32(VertexStreamId::kMeshRelatedCount) + 1 + 3)> handles;
  206. U32 handleCount = 0;
  207. Buffer* unifiedGeometryBuffer = &UnifiedGeometryBuffer::getSingleton().getBuffer();
  208. const BufferUsageBit unifiedGeometryBufferNonTransferUsage = unifiedGeometryBuffer->getBufferUsage() ^ BufferUsageBit::kCopyDestination;
  209. CommandBufferInitInfo cmdbinit;
  210. cmdbinit.m_flags = CommandBufferFlag::kSmallBatch | CommandBufferFlag::kGeneralWork;
  211. CommandBufferPtr cmdb = gr.newCommandBuffer(cmdbinit);
  212. // Set transfer to transfer barrier because of the clear that happened while sync loading
  213. const BufferBarrierInfo barrier = {UnifiedGeometryBuffer::getSingleton().getBufferView(), unifiedGeometryBufferNonTransferUsage,
  214. BufferUsageBit::kCopyDestination};
  215. cmdb->setPipelineBarrier({}, {&barrier, 1}, {});
  216. // Upload index and vertex buffers
  217. for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
  218. {
  219. const Lod& lod = m_lods[lodIdx];
  220. // Upload index buffer
  221. {
  222. TransferGpuAllocatorHandle& handle = handles[handleCount++];
  223. ANKI_CHECK(transferAlloc.allocate(lod.m_indexBufferAllocationToken.getAllocatedSize(), handle));
  224. void* data = handle.getMappedMemory();
  225. ANKI_ASSERT(data);
  226. ANKI_CHECK(loader.storeIndexBuffer(lodIdx, data, handle.getRange()));
  227. cmdb->copyBufferToBuffer(handle, lod.m_indexBufferAllocationToken);
  228. }
  229. // Upload vert buffers
  230. for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
  231. {
  232. if(!(m_presentVertStreams & VertexStreamMask(1 << stream)))
  233. {
  234. continue;
  235. }
  236. TransferGpuAllocatorHandle& handle = handles[handleCount++];
  237. ANKI_CHECK(transferAlloc.allocate(lod.m_vertexBuffersAllocationToken[stream].getAllocatedSize(), handle));
  238. U8* data = static_cast<U8*>(handle.getMappedMemory());
  239. ANKI_ASSERT(data);
  240. // Load to staging
  241. ANKI_CHECK(loader.storeVertexBuffer(lodIdx, U32(stream), data, handle.getRange()));
  242. // Copy
  243. cmdb->copyBufferToBuffer(handle, lod.m_vertexBuffersAllocationToken[stream]);
  244. }
  245. if(lod.m_meshletBoundingVolumes.isValid())
  246. {
  247. // Indices
  248. TransferGpuAllocatorHandle& handle = handles[handleCount++];
  249. const PtrSize primitivesSize = lod.m_meshletIndices.getAllocatedSize();
  250. ANKI_CHECK(transferAlloc.allocate(primitivesSize, handle));
  251. ANKI_CHECK(loader.storeMeshletIndicesBuffer(lodIdx, handle.getMappedMemory(), primitivesSize));
  252. cmdb->copyBufferToBuffer(handle, lod.m_meshletIndices);
  253. // Meshlets
  254. ResourceDynamicArray<MeshBinaryMeshlet> binaryMeshlets;
  255. binaryMeshlets.resize(loader.getHeader().m_meshletCounts[lodIdx]);
  256. ANKI_CHECK(loader.storeMeshletBuffer(lodIdx, WeakArray(binaryMeshlets)));
  257. TransferGpuAllocatorHandle& handle2 = handles[handleCount++];
  258. ANKI_CHECK(transferAlloc.allocate(lod.m_meshletBoundingVolumes.getAllocatedSize(), handle2));
  259. WeakArray<MeshletBoundingVolume> outMeshletBoundingVolumes(static_cast<MeshletBoundingVolume*>(handle2.getMappedMemory()),
  260. loader.getHeader().m_meshletCounts[lodIdx]);
  261. TransferGpuAllocatorHandle& handle3 = handles[handleCount++];
  262. ANKI_CHECK(transferAlloc.allocate(lod.m_meshletGeometryDescriptors.getAllocatedSize(), handle3));
  263. WeakArray<MeshletGeometryDescriptor> outMeshletGeomDescriptors(static_cast<MeshletGeometryDescriptor*>(handle3.getMappedMemory()),
  264. loader.getHeader().m_meshletCounts[lodIdx]);
  265. for(U32 i = 0; i < binaryMeshlets.getSize(); ++i)
  266. {
  267. const MeshBinaryMeshlet& inMeshlet = binaryMeshlets[i];
  268. MeshletGeometryDescriptor& outMeshletGeom = outMeshletGeomDescriptors[i];
  269. MeshletBoundingVolume& outMeshletBoundingVolume = outMeshletBoundingVolumes[i];
  270. outMeshletBoundingVolume = {};
  271. outMeshletGeom = {};
  272. for(VertexStreamId stream : EnumIterable(VertexStreamId::kMeshRelatedFirst, VertexStreamId::kMeshRelatedCount))
  273. {
  274. if(!(m_presentVertStreams & VertexStreamMask(1u << stream)))
  275. {
  276. continue;
  277. }
  278. outMeshletGeom.m_vertexOffsets[U32(stream)] =
  279. lod.m_vertexBuffersAllocationToken[stream].getOffset() / getFormatInfo(kMeshRelatedVertexStreamFormats[stream]).m_texelSize
  280. + inMeshlet.m_firstVertex;
  281. }
  282. outMeshletGeom.m_firstPrimitive =
  283. lod.m_meshletIndices.getOffset() / getFormatInfo(kMeshletPrimitiveFormat).m_texelSize + inMeshlet.m_firstPrimitive;
  284. outMeshletGeom.m_primitiveCount_R16_Uint_vertexCount_R16_Uint = (inMeshlet.m_primitiveCount << 16u) | inMeshlet.m_vertexCount;
  285. outMeshletGeom.m_positionTranslation = m_positionsTranslation;
  286. outMeshletGeom.m_positionScale = m_positionsScale;
  287. outMeshletBoundingVolume.m_aabbMin = inMeshlet.m_boundingVolume.m_aabbMin;
  288. outMeshletBoundingVolume.m_aabbMax = inMeshlet.m_boundingVolume.m_aabbMax;
  289. outMeshletBoundingVolume.m_coneDirection_R8G8B8_Snorm_cosHalfAngle_R8_Snorm =
  290. packSnorm4x8(Vec4(inMeshlet.m_coneDirection, cos(inMeshlet.m_coneAngle / 2.0f)));
  291. outMeshletBoundingVolume.m_coneApex = inMeshlet.m_coneApex;
  292. outMeshletBoundingVolume.m_sphereRadius =
  293. ((outMeshletBoundingVolume.m_aabbMin + outMeshletBoundingVolume.m_aabbMax) / 2.0f - outMeshletBoundingVolume.m_aabbMax).length();
  294. outMeshletBoundingVolume.m_primitiveCount = inMeshlet.m_primitiveCount;
  295. }
  296. cmdb->copyBufferToBuffer(handle2, lod.m_meshletBoundingVolumes);
  297. cmdb->copyBufferToBuffer(handle3, lod.m_meshletGeometryDescriptors);
  298. }
  299. }
  300. if(gr.getDeviceCapabilities().m_rayTracingEnabled)
  301. {
  302. // Build BLASes
  303. for(U32 submeshIdx = 0; submeshIdx < m_subMeshes.getSize(); ++submeshIdx)
  304. {
  305. const SubMesh& submesh = m_subMeshes[submeshIdx];
  306. // Set the barriers
  307. BufferBarrierInfo bufferBarrier;
  308. bufferBarrier.m_bufferView = UnifiedGeometryBuffer::getSingleton().getBufferView();
  309. bufferBarrier.m_previousUsage = BufferUsageBit::kCopyDestination;
  310. bufferBarrier.m_nextUsage = unifiedGeometryBufferNonTransferUsage;
  311. Array<AccelerationStructureBarrierInfo, kMaxLodCount> asBarriers;
  312. for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
  313. {
  314. asBarriers[lodIdx].m_as = submesh.m_blas[lodIdx].get();
  315. asBarriers[lodIdx].m_previousUsage = AccelerationStructureUsageBit::kNone;
  316. asBarriers[lodIdx].m_nextUsage = AccelerationStructureUsageBit::kBuild;
  317. }
  318. cmdb->setPipelineBarrier({}, {&bufferBarrier, 1}, {&asBarriers[0], m_lods.getSize()});
  319. // Build BLASes
  320. for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
  321. {
  322. // TODO find a temp buffer
  323. BufferInitInfo buffInit("BLAS scratch");
  324. buffInit.m_size = submesh.m_blas[lodIdx]->getBuildScratchBufferSize();
  325. buffInit.m_usage = BufferUsageBit::kAccelerationStructureBuildScratch;
  326. BufferPtr scratchBuff = GrManager::getSingleton().newBuffer(buffInit);
  327. cmdb->buildAccelerationStructure(submesh.m_blas[lodIdx].get(), BufferView(scratchBuff.get()));
  328. }
  329. // Barriers again
  330. for(U32 lodIdx = 0; lodIdx < m_lods.getSize(); ++lodIdx)
  331. {
  332. asBarriers[lodIdx].m_as = submesh.m_blas[lodIdx].get();
  333. asBarriers[lodIdx].m_previousUsage = AccelerationStructureUsageBit::kBuild;
  334. asBarriers[lodIdx].m_nextUsage = AccelerationStructureUsageBit::kAllRead;
  335. }
  336. cmdb->setPipelineBarrier({}, {}, {&asBarriers[0], m_lods.getSize()});
  337. }
  338. }
  339. else
  340. {
  341. // Only set a barrier
  342. BufferBarrierInfo bufferBarrier;
  343. bufferBarrier.m_bufferView = UnifiedGeometryBuffer::getSingleton().getBufferView();
  344. bufferBarrier.m_previousUsage = BufferUsageBit::kCopyDestination;
  345. bufferBarrier.m_nextUsage = unifiedGeometryBufferNonTransferUsage;
  346. cmdb->setPipelineBarrier({}, {&bufferBarrier, 1}, {});
  347. }
  348. // Finalize
  349. FencePtr fence;
  350. cmdb->endRecording();
  351. GrManager::getSingleton().submit(cmdb.get(), {}, &fence);
  352. for(U32 i = 0; i < handleCount; ++i)
  353. {
  354. transferAlloc.release(handles[i], fence);
  355. }
  356. return Error::kNone;
  357. }
  358. Error MeshResource::getOrCreateCollisionShape(Bool wantStatic, U32 lod, PhysicsCollisionShapePtr& out) const
  359. {
  360. lod = min<U32>(lod, getLodCount() - 1);
  361. Bool isConvex = m_isConvex;
  362. if(!isConvex && !wantStatic)
  363. {
  364. ANKI_RESOURCE_LOGW("Requesting a non-static non-convex collision shape is not supported. Will create a convex hull instead");
  365. isConvex = true;
  366. }
  367. const Lod& l = m_lods[lod];
  368. LockGuard lock(l.m_collisionShapeMtx);
  369. if(!l.m_collisionShapes[isConvex])
  370. {
  371. MeshBinaryLoader loader(&ResourceMemoryPool::getSingleton());
  372. ANKI_CHECK(loader.load(getFilename()));
  373. ResourceDynamicArray<Vec3> positions;
  374. ResourceDynamicArray<U32> indices;
  375. ANKI_CHECK(loader.storeIndicesAndPosition(lod, indices, positions));
  376. if(isConvex)
  377. {
  378. l.m_collisionShapes[isConvex] = PhysicsWorld::getSingleton().newConvexHullShape(positions);
  379. }
  380. else
  381. {
  382. l.m_collisionShapes[isConvex] = PhysicsWorld::getSingleton().newStaticMeshShape(positions, indices);
  383. }
  384. }
  385. out = l.m_collisionShapes[isConvex];
  386. return Error::kNone;
  387. }
  388. } // end namespace anki