/**************************************************************************** ** ** Copyright (C) 1993-2009 NVIDIA Corporation. ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "Qt3DSImportLibPrecompile.h" #include "Qt3DSImportMesh.h" #include "foundation/Qt3DSMemoryBuffer.h" #include "Qt3DSImportContainers.h" using namespace qt3dsimp; // Disable mesh optimization. TODO: After removing NvTriStrip need // to implement mesh optimization. //#define DISABLE_MESH_OPTIMIZATION 1 // It turns out we can't enable vertex remapping because it would break // mesh morphing. #define DISABLE_VERTEX_REMAP 1 namespace { template NVConstDataRef toRefBuf(QT3DSU8 *bufData, QT3DSU32 off, QT3DSU32 size) { QT3DS_ASSERT(size % sizeof(TDataType) == 0); if (size) return NVConstDataRef((TDataType *)(bufData + off), size / sizeof(TDataType)); return NVConstDataRef(); } struct DynamicVBuf { QT3DSU32 m_Stride; ImportArray m_VertexBufferEntries; MemoryBuffer m_VertexData; void clear() { m_Stride = 0; m_VertexBufferEntries.clear(); m_VertexData.clear(); } }; struct DynamicIndexBuf { NVRenderComponentTypes::Enum m_CompType; MemoryBuffer m_IndexData; DynamicIndexBuf() {} void clear() { m_IndexData.clear(); } }; struct SubsetDesc { QT3DSU32 m_Count; QT3DSU32 m_Offset; NVBounds3 m_Bounds; QString m_Name; SubsetDesc(QT3DSU32 c, QT3DSU32 off) : m_Count(c) , m_Offset(off) { } SubsetDesc() : m_Count(0) , m_Offset(0) { } }; QT3DSU32 GetAlignedOffset(QT3DSU32 offset, QT3DSU32 align) { QT3DSU32 leftover = offset % align; if (leftover) return offset + (align - leftover); return offset; } class MeshBuilderImpl : public MeshBuilder { DynamicVBuf m_VertexBuffer; DynamicIndexBuf m_IndexBuffer; ImportArray m_Joints; ImportArray m_MeshSubsetDescs; NVRenderDrawMode::Enum m_DrawMode; NVRenderWinding::Enum m_Winding; MemoryBuffer m_RemappedVertexData; MemoryBuffer m_NewIndexBuffer; ImportArray m_MeshBuffer; public: MeshBuilderImpl() { Reset(); } virtual ~MeshBuilderImpl() { Reset(); } void Release() override { delete this; } void Reset() override { m_VertexBuffer.clear(); m_IndexBuffer.clear(); m_Joints.clear(); m_MeshSubsetDescs.clear(); m_DrawMode = NVRenderDrawMode::Triangles; m_Winding = NVRenderWinding::CounterClockwise; m_MeshBuffer.clear(); } void SetDrawParameters(NVRenderDrawMode::Enum drawMode, NVRenderWinding::Enum winding) override { m_DrawMode = drawMode; m_Winding = winding; } // Somewhat burly method to interleave the data as tightly as possible // while taking alignment into account. bool SetVertexBuffer(NVConstDataRef entries) override { QT3DSU32 currentOffset = 0; QT3DSU32 bufferAlignment = 0; QT3DSU32 numItems = 0; bool retval = true; QT3DSIMP_FOREACH(idx, entries.size()) { const MeshBuilderVBufEntry &entry(entries[idx]); // Ignore entries with no data. if (entry.m_Data.begin() == NULL || entry.m_Data.size() == 0) continue; QT3DSU32 alignment = NVRenderComponentTypes::getSizeofType(entry.m_ComponentType); bufferAlignment = qMax(bufferAlignment, alignment); QT3DSU32 byteSize = alignment * entry.m_NumComponents; if (entry.m_Data.size() % alignment != 0) { QT3DS_ASSERT(false); retval = false; } QT3DSU32 localNumItems = entry.m_Data.size() / byteSize; if (numItems == 0) { numItems = localNumItems; } else if (numItems != localNumItems) { QT3DS_ASSERT(false); retval = false; numItems = qMin(numItems, localNumItems); } // Lots of platforms can't handle non-aligned data. // so ensure we are aligned. currentOffset = GetAlignedOffset(currentOffset, alignment); NVRenderVertexBufferEntry vbufEntry(entry.m_Name, entry.m_ComponentType, entry.m_NumComponents, currentOffset); m_VertexBuffer.m_VertexBufferEntries.push_back(vbufEntry); currentOffset += byteSize; } m_VertexBuffer.m_Stride = GetAlignedOffset(currentOffset, bufferAlignment); // Packed interleave the data QT3DSIMP_FOREACH(idx, numItems) { QT3DSU32 dataOffset = 0; QT3DSIMP_FOREACH(entryIdx, entries.size()) { const MeshBuilderVBufEntry &entry(entries[entryIdx]); // Ignore entries with no data. if (entry.m_Data.begin() == NULL || entry.m_Data.size() == 0) continue; QT3DSU32 alignment = NVRenderComponentTypes::getSizeofType(entry.m_ComponentType); QT3DSU32 byteSize = alignment * entry.m_NumComponents; QT3DSU32 offset = byteSize * idx; QT3DSU32 newOffset = GetAlignedOffset(dataOffset, alignment); if (newOffset != dataOffset) m_VertexBuffer.m_VertexData.writeZeros(newOffset - dataOffset); m_VertexBuffer.m_VertexData.write(entry.m_Data.begin() + offset, byteSize); dataOffset = newOffset + byteSize; } QT3DS_ASSERT(dataOffset == m_VertexBuffer.m_Stride); } return retval; } void SetVertexBuffer(NVConstDataRef entries, QT3DSU32 stride, NVConstDataRef data) override { QT3DSIMP_FOREACH(idx, (QT3DSU32)entries.size()) { m_VertexBuffer.m_VertexBufferEntries.push_back(entries[idx]); } m_VertexBuffer.m_VertexData.write(data.begin(), data.size()); if (stride == 0) { // Calculate the stride of the buffer using the vbuf entries QT3DSIMP_FOREACH(idx, entries.size()) { const NVRenderVertexBufferEntry &entry(entries[idx]); stride = qMax(stride, entry.m_FirstItemOffset + (entry.m_NumComponents * NVRenderComponentTypes::getSizeofType( entry.m_ComponentType))); } } m_VertexBuffer.m_Stride = stride; } void SetIndexBuffer(NVConstDataRef data, NVRenderComponentTypes::Enum comp) override { m_IndexBuffer.m_CompType = comp; m_IndexBuffer.m_IndexData.write(data.begin(), data.size()); } void AddJoint(QT3DSI32 jointID, QT3DSI32 parentID, const QT3DSF32 *invBindPose, const QT3DSF32 *localToGlobalBoneSpace) override { m_Joints.push_back(Joint(jointID, parentID, invBindPose, localToGlobalBoneSpace)); } SubsetDesc CreateSubset(const wchar_t *inName, QT3DSU32 count, QT3DSU32 offset) { if (inName == NULL) inName = L""; SubsetDesc retval(count, offset); retval.m_Name = QString::fromWCharArray(inName); return retval; } // indexBuffer QT3DS_MAX_U32 means no index buffer. // count of QT3DS_MAX_U32 means use all available items // offset means exactly what you would think. Offset is in item size, not bytes. void AddMeshSubset(const wchar_t *inName, QT3DSU32 count, QT3DSU32 offset, QT3DSU32 boundsPositionEntryIndex) override { SubsetDesc retval = CreateSubset(inName, count, offset); if (boundsPositionEntryIndex != QT3DS_MAX_U32) { retval.m_Bounds = Mesh::CalculateSubsetBounds( m_VertexBuffer.m_VertexBufferEntries[boundsPositionEntryIndex], m_VertexBuffer.m_VertexData, m_VertexBuffer.m_Stride, m_IndexBuffer.m_IndexData, m_IndexBuffer.m_CompType, count, offset); } m_MeshSubsetDescs.push_back(retval); } void AddMeshSubset(const wchar_t *inName, QT3DSU32 count, QT3DSU32 offset, const NVBounds3 &inBounds) override { SubsetDesc retval = CreateSubset(inName, count, offset); retval.m_Bounds = inBounds; m_MeshSubsetDescs.push_back(retval); } #ifndef DISABLE_MESH_OPTIMIZATION void DeletePrimitiveGroup(PrimitiveGroup *&inGroup) { if (inGroup) delete[] inGroup; inGroup = NULL; } #endif // We connect sub meshes which habe the same material void ConnectSubMeshes() override { if (m_MeshSubsetDescs.size() < 2) { // nothing to do return; } QT3DSU32 matDuplicates = 0; // as a pre-step we check if we have duplicate material at all for (QT3DSU32 i = 0, subsetEnd = m_MeshSubsetDescs.size(); i < subsetEnd && !matDuplicates; ++i) { SubsetDesc ¤tSubset = m_MeshSubsetDescs[i]; for (QT3DSU32 j = 0, subsetEnd = m_MeshSubsetDescs.size(); j < subsetEnd; ++j) { SubsetDesc &theSubset = m_MeshSubsetDescs[j]; if (i == j) continue; if (currentSubset.m_Name == theSubset.m_Name) { matDuplicates++; break; // found a duplicate bail out } } } // did we find some duplicates? if (matDuplicates) { ImportArray newMeshSubsetDescs; ImportArray::iterator theIter; QString curMatName; m_NewIndexBuffer.clear(); for (theIter = m_MeshSubsetDescs.begin(); theIter != m_MeshSubsetDescs.end(); ++theIter) { bool bProcessed = false; for (ImportArray::iterator iter = newMeshSubsetDescs.begin(); iter != newMeshSubsetDescs.end(); ++iter) { if (theIter->m_Name == iter->m_Name) { bProcessed = true; break; } } if (bProcessed) continue; curMatName = theIter->m_Name; QT3DSU32 theIndexCompSize = NVRenderComponentTypes::getSizeofType(m_IndexBuffer.m_CompType); // get pointer to indices QT3DSU8 *theIndices = (m_IndexBuffer.m_IndexData.begin()) + (theIter->m_Offset * theIndexCompSize); // write new offset theIter->m_Offset = m_NewIndexBuffer.size() / theIndexCompSize; // store indices m_NewIndexBuffer.write(theIndices, theIter->m_Count * theIndexCompSize); for (QT3DSU32 j = 0, subsetEnd = m_MeshSubsetDescs.size(); j < subsetEnd; ++j) { if (theIter == &m_MeshSubsetDescs[j]) continue; SubsetDesc &theSubset = m_MeshSubsetDescs[j]; if (curMatName == theSubset.m_Name) { // get pointer to indices QT3DSU8 *theIndices = (m_IndexBuffer.m_IndexData.begin()) + (theSubset.m_Offset * theIndexCompSize); // store indices m_NewIndexBuffer.write(theIndices, theSubset.m_Count * theIndexCompSize); // increment indices count theIter->m_Count += theSubset.m_Count; } } newMeshSubsetDescs.push_back(*theIter); } m_MeshSubsetDescs.clear(); m_MeshSubsetDescs = newMeshSubsetDescs; m_IndexBuffer.m_IndexData.clear(); m_IndexBuffer.m_IndexData.write(m_NewIndexBuffer.begin(), m_NewIndexBuffer.size()); // compute new bounding box for (theIter = m_MeshSubsetDescs.begin(); theIter != m_MeshSubsetDescs.end(); ++theIter) { theIter->m_Bounds = Mesh::CalculateSubsetBounds( m_VertexBuffer.m_VertexBufferEntries[0], m_VertexBuffer.m_VertexData, m_VertexBuffer.m_Stride, m_IndexBuffer.m_IndexData, m_IndexBuffer.m_CompType, theIter->m_Count, theIter->m_Offset); } } } // Here is the NVTriStrip magic. void OptimizeMesh() override { if (NVRenderComponentTypes::getSizeofType(m_IndexBuffer.m_CompType) != 2) { // we currently re-arrange unsigned int indices. // this is because NvTriStrip only supports short indices QT3DS_ASSERT(NVRenderComponentTypes::getSizeofType(m_IndexBuffer.m_CompType) == 4); return; } #ifndef DISABLE_MESH_OPTIMIZATION SetCacheSize(CACHESIZE_GEFORCE3); SetStitchStrips(true); SetMinStripSize(0); // Create the optimized list indices SetListsOnly(true); // Optimize the indices using NvTriStrip // First, nv-tri-strip all of the indexes. They shouldn't be interleaved, meaning // there is an assumption here that mesh subset1 doesn't uses indexes from mesh subset 2. // They may share vertexes, however, which means that we need to be careful when remapping // them. // Have to go subset by subset in order for the tri-strip to avoid stepping on subsets. m_NewIndexBuffer.clear(); for (QT3DSU32 subsetIdx = 0, subsetEnd = m_MeshSubsetDescs.size(); subsetIdx < subsetEnd; ++subsetIdx) { SubsetDesc &theSubset = m_MeshSubsetDescs[subsetIdx]; QT3DSU16 *theIndices = reinterpret_cast(m_IndexBuffer.m_IndexData.begin()) + theSubset.m_Offset; QT3DSU32 theIndexCount = theSubset.m_Count; QT3DSU16 theNumGroups = 0; PrimitiveGroup *thePrimitiveGroupsList = NULL; theSubset.m_Offset = m_NewIndexBuffer.size() / sizeof(QT3DSU16); theSubset.m_Count = 0; // We don't support larger vertex buffers. That requires splitting the buffer, // an operation we haven't implemented (yet). if (GenerateStrips(theIndices, theIndexCount, &thePrimitiveGroupsList, &theNumGroups)) { if (theNumGroups) { QT3DS_ASSERT(theNumGroups == 1); PrimitiveGroup &srcGroup(thePrimitiveGroupsList[0]); QT3DS_ASSERT(srcGroup.type == PT_LIST); m_NewIndexBuffer.write(srcGroup.indices, srcGroup.numIndices); theSubset.m_Count = srcGroup.numIndices; } } DeletePrimitiveGroup(thePrimitiveGroupsList); } m_IndexBuffer.m_IndexData.clear(); m_IndexBuffer.m_IndexData.write(m_NewIndexBuffer.begin(), m_NewIndexBuffer.size()); #ifndef DISABLE_VERTEX_REMAP // This operation does not go subset by subset // by rather remaps the entire vertex buffer in one shot // once all of the index buffers are setup. QT3DSU16 *theIndices = reinterpret_cast(m_IndexBuffer.m_IndexData.begin()); QT3DSU32 theIndexCount = m_IndexBuffer.m_IndexData.size() / sizeof(QT3DSU16); // Setup input to the remap system QT3DSU16 theNumGroups = 1; PrimitiveGroup thePrimitiveGroup; thePrimitiveGroup.type = PT_LIST; thePrimitiveGroup.numIndices = theIndexCount; thePrimitiveGroup.indices = new QT3DSU16[theIndexCount]; memCopy(thePrimitiveGroup.indices, theIndices, theIndexCount * sizeof(QT3DSU16)); PrimitiveGroup *theRemappedGroup = NULL; QT3DSU32 vertBufByteSize = m_VertexBuffer.m_VertexData.size(); QT3DSU32 numVertexIndices = vertBufByteSize / m_VertexBuffer.m_Stride; QT3DS_ASSERT(numVertexIndices < QT3DS_MAX_U16); QT3DSU32 vertBufStride = m_VertexBuffer.m_Stride; // This remaps the vertexes RemapIndices(&thePrimitiveGroup, theNumGroups, static_cast(numVertexIndices), &theRemappedGroup); m_RemappedVertexData.reserve(vertBufByteSize); const QT3DSU8 *srcVertexPtr(m_VertexBuffer.m_VertexData.begin()); QT3DSU8 *dstVertexPtr(m_RemappedVertexData.begin()); QT3DS_ASSERT(theNumGroups == 1); for (QT3DSU32 theGroupIdx = 0; theGroupIdx < 1; ++theGroupIdx) { PrimitiveGroup &srcGroup(thePrimitiveGroup); PrimitiveGroup &dstGroup(theRemappedGroup[theGroupIdx]); QT3DS_ASSERT(srcGroup.type == PT_LIST); for (QT3DSU32 theIndexIdx = 0; theIndexIdx < srcGroup.numIndices; ++theIndexIdx) { QT3DSU16 srcIndex = srcGroup.indices[theIndexIdx]; QT3DSU16 dstIndex = dstGroup.indices[theIndexIdx]; QT3DS_ASSERT(dstIndex * m_VertexBuffer.m_Stride < vertBufByteSize); QT3DS_ASSERT(srcIndex * m_VertexBuffer.m_Stride < vertBufByteSize); // Maybe add in a check to see if we possibly already copied this information // That would of course be only an optimization. memCopy(dstVertexPtr + dstIndex * vertBufStride, srcVertexPtr + srcIndex * vertBufStride, vertBufStride); theIndices[theIndexIdx] = dstIndex; } memCopy(m_VertexBuffer.m_VertexData.begin(), m_RemappedVertexData.begin(), vertBufByteSize); } DeletePrimitiveGroup(theRemappedGroup); #endif // DISABLE_VERTEX_REMAP #endif // DISABLE_MESH_OPTIMIZATION } template static void Assign(QT3DSU8 *inBaseAddress, QT3DSU8 *inDataAddress, SOffsetDataRef &inBuffer, const TDataType *inDestData, QT3DSU32 inDestSize) { inBuffer.m_Offset = (QT3DSU32)(inDataAddress - inBaseAddress); inBuffer.m_Size = inDestSize; memCopy(inDataAddress, inDestData, inDestSize * sizeof(TDataType)); } template static void Assign(QT3DSU8 *inBaseAddress, QT3DSU8 *inDataAddress, SOffsetDataRef &inBuffer, QT3DSU32 inDestSize) { inBuffer.m_Offset = (QT3DSU32)(inDataAddress - inBaseAddress); inBuffer.m_Size = inDestSize; } // Return the current mesh. This is only good for this function call, item may change or be // released // due to any further function calls. Mesh &GetMesh() override { QT3DSU32 meshSize = sizeof(Mesh); QT3DSU32 alignment = sizeof(void *); QT3DSU32 vertDataSize = GetAlignedOffset(m_VertexBuffer.m_VertexData.size(), alignment); meshSize += vertDataSize; QT3DSU32 entrySize = m_VertexBuffer.m_VertexBufferEntries.size() * sizeof(qt3ds::render::NVRenderVertexBufferEntry); meshSize += entrySize; QT3DSU32 entryNameSize = 0; for (QT3DSU32 idx = 0, end = m_VertexBuffer.m_VertexBufferEntries.size(); idx < end; ++idx) { const qt3ds::render::NVRenderVertexBufferEntry &theEntry( m_VertexBuffer.m_VertexBufferEntries[idx]); const char *entryName = theEntry.m_Name; if (entryName == NULL) entryName = ""; entryNameSize += (QT3DSU32)(strlen(theEntry.m_Name)) + 1; } entryNameSize = GetAlignedOffset(entryNameSize, alignment); meshSize += entryNameSize; QT3DSU32 indexBufferSize = GetAlignedOffset(m_IndexBuffer.m_IndexData.size(), alignment); meshSize += indexBufferSize; QT3DSU32 subsetSize = m_MeshSubsetDescs.size() * sizeof(MeshSubset); QT3DSU32 nameSize = 0; for (QT3DSU32 idx = 0, end = m_MeshSubsetDescs.size(); idx < end; ++idx) { if (!m_MeshSubsetDescs[idx].m_Name.isEmpty()) nameSize += m_MeshSubsetDescs[idx].m_Name.size() + 1; } nameSize *= sizeof(char16_t); nameSize = GetAlignedOffset(nameSize, alignment); meshSize += subsetSize + nameSize; QT3DSU32 jointsSize = m_Joints.size() * sizeof(qt3dsimp::Joint); meshSize += jointsSize; m_MeshBuffer.resize(meshSize); QT3DSU8 *baseAddress = m_MeshBuffer.data(); Mesh *retval = reinterpret_cast(baseAddress); retval->m_DrawMode = m_DrawMode; retval->m_Winding = m_Winding; QT3DSU8 *vertBufferData = baseAddress + sizeof(Mesh); QT3DSU8 *vertEntryData = vertBufferData + vertDataSize; QT3DSU8 *vertEntryNameData = vertEntryData + entrySize; QT3DSU8 *indexBufferData = vertEntryNameData + entryNameSize; QT3DSU8 *subsetBufferData = indexBufferData + indexBufferSize; QT3DSU8 *nameBufferData = subsetBufferData + subsetSize; QT3DSU8 *jointBufferData = nameBufferData + nameSize; retval->m_VertexBuffer.m_Stride = m_VertexBuffer.m_Stride; Assign(baseAddress, vertBufferData, retval->m_VertexBuffer.m_Data, m_VertexBuffer.m_VertexData.begin(), m_VertexBuffer.m_VertexData.size()); retval->m_VertexBuffer.m_Entries.m_Size = m_VertexBuffer.m_VertexBufferEntries.size(); retval->m_VertexBuffer.m_Entries.m_Offset = (QT3DSU32)(vertEntryData - baseAddress); for (QT3DSU32 idx = 0, end = m_VertexBuffer.m_VertexBufferEntries.size(); idx < end; ++idx) { const qt3ds::render::NVRenderVertexBufferEntry &theEntry( m_VertexBuffer.m_VertexBufferEntries[idx]); MeshVertexBufferEntry &theDestEntry( retval->m_VertexBuffer.m_Entries.index(baseAddress, idx)); theDestEntry.m_ComponentType = theEntry.m_ComponentType; theDestEntry.m_FirstItemOffset = theEntry.m_FirstItemOffset; theDestEntry.m_NumComponents = theEntry.m_NumComponents; const char *targetName = theEntry.m_Name; if (targetName == NULL) targetName = ""; QT3DSU32 entryNameLen = (QT3DSU32)(strlen(targetName)) + 1; theDestEntry.m_NameOffset = (QT3DSU32)(vertEntryNameData - baseAddress); memCopy(vertEntryNameData, theEntry.m_Name, entryNameLen); vertEntryNameData += entryNameLen; } retval->m_IndexBuffer.m_ComponentType = m_IndexBuffer.m_CompType; Assign(baseAddress, indexBufferData, retval->m_IndexBuffer.m_Data, m_IndexBuffer.m_IndexData.begin(), m_IndexBuffer.m_IndexData.size()); Assign(baseAddress, subsetBufferData, retval->m_Subsets, m_MeshSubsetDescs.size()); for (QT3DSU32 idx = 0, end = m_MeshSubsetDescs.size(); idx < end; ++idx) { SubsetDesc &theDesc = m_MeshSubsetDescs[idx]; MeshSubset &theSubset = reinterpret_cast(subsetBufferData)[idx]; theSubset.m_Bounds = theDesc.m_Bounds; theSubset.m_Count = theDesc.m_Count; theSubset.m_Offset = theDesc.m_Offset; if (!theDesc.m_Name.isEmpty()) { theSubset.m_Name.m_Size = theDesc.m_Name.size() + 1; theSubset.m_Name.m_Offset = (QT3DSU32)(nameBufferData - baseAddress); std::transform(theDesc.m_Name.begin(), theDesc.m_Name.end(), reinterpret_cast(nameBufferData), [](QChar c) { return static_cast(c.unicode()); }); reinterpret_cast(nameBufferData)[theDesc.m_Name.size()] = 0; nameBufferData += (theDesc.m_Name.size() + 1) * sizeof(char16_t); } else { theSubset.m_Name.m_Size = 0; theSubset.m_Name.m_Offset = 0; } } Assign(baseAddress, jointBufferData, retval->m_Joints, m_Joints.data(), m_Joints.size()); return *retval; } }; } // Uses new/delete. MeshBuilder &MeshBuilder::CreateMeshBuilder() { return *(new MeshBuilderImpl()); }