]> OCCT Git - occt.git/commitdiff
0032979: Data Exchange, RWGltf_CafWriter - support multi-threaded Draco compression
authorichesnok <ichesnok@opencascade.com>
Tue, 26 Jul 2022 11:06:58 +0000 (14:06 +0300)
committersmoskvin <smoskvin@opencascade.com>
Fri, 12 Aug 2022 16:04:03 +0000 (19:04 +0300)
'MultiThread' field was added to structure RWGltf_DracoParameters for using multithreading.
Class CafWriter_DracoEncodingFunctor was added for multithreaded compression.

src/RWGltf/RWGltf_CafWriter.cxx
src/RWGltf/RWGltf_CafWriter.hxx
src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx
tests/de_mesh/gltf_write/bull_parallel [new file with mode: 0644]

index b5776fd88282bcfbc07d050a8d2c3c00d4679b9b..e6bcdbefc0c58d52ba11784b7416a1533e2dd580 100644 (file)
@@ -21,6 +21,7 @@
 #include <NCollection_DataMap.hxx>
 #include <OSD_FileSystem.hxx>
 #include <OSD_File.hxx>
+#include <OSD_Parallel.hxx>
 #include <OSD_Path.hxx>
 #include <OSD_Timer.hxx>
 #include <RWGltf_GltfAccessorLayout.hxx>
@@ -170,6 +171,72 @@ namespace
 #endif
 }
 
+#ifdef HAVE_DRACO
+//! Functor for parallel execution of encoding meshes to Draco buffers.
+class DracoEncodingFunctor
+{
+public:
+
+  DracoEncodingFunctor (const Message_ProgressRange& theProgress,
+                        draco::Encoder& theDracoEncoder,
+                        const std::vector<std::shared_ptr<RWGltf_CafWriter::Mesh>>& theMeshes,
+                        std::vector<std::shared_ptr<draco::EncoderBuffer>>& theEncoderBuffers)
+  : myProgress(theProgress, "Draco compression", Max(1, int(theMeshes.size()))),
+    myDracoEncoder(&theDracoEncoder),
+    myRanges(0, int(theMeshes.size()) - 1),
+    myMeshes(&theMeshes),
+    myEncoderBuffers(&theEncoderBuffers)
+  { 
+    for (int anIndex = 0; anIndex != int(theMeshes.size()); ++anIndex)
+    {
+      myRanges.SetValue(anIndex, myProgress.Next());
+    }
+  }
+
+  void operator () (int theMeshIndex) const
+  {
+    const std::shared_ptr<RWGltf_CafWriter::Mesh>& aCurrentMesh = myMeshes->at(theMeshIndex);
+    if (aCurrentMesh->NodesVec.empty())
+    {
+      return;
+    }
+
+    Message_ProgressScope aScope(myRanges[theMeshIndex], NULL, 1);
+
+    draco::Mesh aMesh;
+    writeNodesToDracoMesh (aMesh, aCurrentMesh->NodesVec);
+
+    if (!aCurrentMesh->NormalsVec.empty())
+    {
+      writeNormalsToDracoMesh (aMesh, aCurrentMesh->NormalsVec);
+    }
+
+    if (!aCurrentMesh->TexCoordsVec.empty())
+    {
+      writeTexCoordsToDracoMesh (aMesh, aCurrentMesh->TexCoordsVec);
+    }
+
+    writeIndicesToDracoMesh (aMesh, aCurrentMesh->IndicesVec);
+
+    std::shared_ptr<draco::EncoderBuffer> anEncoderBuffer = std::make_shared<draco::EncoderBuffer>();
+    draco::Status aStatus = myDracoEncoder->EncodeMeshToBuffer (aMesh, anEncoderBuffer.get());
+    if (aStatus.ok())
+    {
+      myEncoderBuffers->at(theMeshIndex) = anEncoderBuffer;
+    }
+
+    aScope.Next();
+  }
+
+private:
+  Message_ProgressScope   myProgress;
+  draco::Encoder*         myDracoEncoder;
+  NCollection_Array1<Message_ProgressRange>                   myRanges;
+  const std::vector<std::shared_ptr<RWGltf_CafWriter::Mesh>>* myMeshes;
+  std::vector<std::shared_ptr<draco::EncoderBuffer>>*         myEncoderBuffers;
+};
+#endif
+
 //================================================================
 // Function : Constructor
 // Purpose  :
@@ -185,7 +252,8 @@ RWGltf_CafWriter::RWGltf_CafWriter (const TCollection_AsciiString& theFile,
   myToEmbedTexturesInGlb (true),
   myToMergeFaces (false),
   myToSplitIndices16 (false),
-  myBinDataLen64  (0)
+  myBinDataLen64  (0),
+  myToParallel (false)
 {
   myCSTrsf.SetOutputLengthUnit (1.0); // meters
   myCSTrsf.SetOutputCoordinateSystem (RWMesh_CoordinateSystem_glTF);
@@ -537,6 +605,8 @@ bool RWGltf_CafWriter::writeBinData (const Handle(TDocStd_Document)& theDocument
   myBinDataMap.Clear();
   myBinDataLen64 = 0;
 
+  Message_ProgressScope aScope(theProgress, "Write binary data", myDracoParameters.DracoCompression ? 2 : 1);
+
   const Handle(OSD_FileSystem)& aFileSystem = OSD_FileSystem::DefaultFileSystem();
   std::shared_ptr<std::ostream> aBinFile = aFileSystem->OpenOStream (myBinFileNameFull, std::ios::out | std::ios::binary);
   if (aBinFile.get() == NULL
@@ -546,7 +616,7 @@ bool RWGltf_CafWriter::writeBinData (const Handle(TDocStd_Document)& theDocument
     return false;
   }
 
-  Message_ProgressScope aPSentryBin (theProgress, "Binary data", 4);
+  Message_ProgressScope aPSentryBin (aScope.Next(), "Binary data", 4);
   const RWGltf_GltfArrayType anArrTypes[4] =
   {
     RWGltf_GltfArrayType_Position,
@@ -797,7 +867,6 @@ bool RWGltf_CafWriter::writeBinData (const Handle(TDocStd_Document)& theDocument
 #ifdef HAVE_DRACO
     OSD_Timer aDracoTimer;
     aDracoTimer.Start();
-    int aBuffId = 0;
     draco::Encoder aDracoEncoder;
     aDracoEncoder.SetAttributeQuantization (draco::GeometryAttribute::POSITION,  myDracoParameters.QuantizePositionBits);
     aDracoEncoder.SetAttributeQuantization (draco::GeometryAttribute::NORMAL,    myDracoParameters.QuantizeNormalBits);
@@ -805,38 +874,23 @@ bool RWGltf_CafWriter::writeBinData (const Handle(TDocStd_Document)& theDocument
     aDracoEncoder.SetAttributeQuantization (draco::GeometryAttribute::COLOR,     myDracoParameters.QuantizeColorBits);
     aDracoEncoder.SetAttributeQuantization (draco::GeometryAttribute::GENERIC,   myDracoParameters.QuantizeGenericBits);
     aDracoEncoder.SetSpeedOptions (myDracoParameters.CompressionLevel, myDracoParameters.CompressionLevel);
-    for (size_t aMeshInd = 0; aMeshInd != aMeshes.size(); ++aMeshInd)
-    {
-      const std::shared_ptr<RWGltf_CafWriter::Mesh>& aCurrentMesh = aMeshes[aMeshInd];
-      if (aCurrentMesh->NodesVec.empty())
-      {
-        continue;
-      }
 
-      draco::Mesh aDracoMesh;
-      writeNodesToDracoMesh (aDracoMesh, aCurrentMesh->NodesVec);
-      if (!aCurrentMesh->NormalsVec.empty())
-      {
-        writeNormalsToDracoMesh (aDracoMesh, aCurrentMesh->NormalsVec);
-      }
-      if (!aCurrentMesh->TexCoordsVec.empty())
-      {
-        writeTexCoordsToDracoMesh (aDracoMesh, aCurrentMesh->TexCoordsVec);
-      }
-      writeIndicesToDracoMesh (aDracoMesh, aCurrentMesh->IndicesVec);
+    std::vector<std::shared_ptr<draco::EncoderBuffer>> anEncoderBuffers(aMeshes.size());
+    DracoEncodingFunctor aFunctor (aScope.Next(), aDracoEncoder, aMeshes, anEncoderBuffers);
+    OSD_Parallel::For (0, int(aMeshes.size()), aFunctor, !myToParallel);
 
-      draco::EncoderBuffer anEncoderBuff;
-      draco::Status aStatus = aDracoEncoder.EncodeMeshToBuffer (aDracoMesh, &anEncoderBuff);
-      if (!aStatus.ok())
+    for (size_t aBuffInd = 0; aBuffInd != anEncoderBuffers.size(); ++aBuffInd)
+    {
+      if (anEncoderBuffers.at(aBuffInd).get() == nullptr)
       {
-        Message::SendFail (TCollection_AsciiString("Error: mesh cannot be encoded in draco buffer."));
+        Message::SendFail(TCollection_AsciiString("Error: mesh not encoded in draco buffer."));
         return false;
       }
-
       RWGltf_GltfBufferView aBuffViewDraco;
-      aBuffViewDraco.Id = aBuffId++;
+      aBuffViewDraco.Id = (int)aBuffInd;
       aBuffViewDraco.ByteOffset = aBinFile->tellp();
-      aBinFile->write (anEncoderBuff.data(), std::streamsize(anEncoderBuff.size()));
+      const draco::EncoderBuffer& anEncoderBuff = *anEncoderBuffers.at(aBuffInd);
+      aBinFile->write(anEncoderBuff.data(), std::streamsize(anEncoderBuff.size()));
       if (!aBinFile->good())
       {
         Message::SendFail (TCollection_AsciiString("File '") + myBinFileNameFull + "' cannot be written");
index be1d8a344d34c8679bda7cd8a0e8c05de6f2ad25..be5d05f6375393ced1dc7b842ebea1b127f95904 100644 (file)
@@ -125,6 +125,12 @@ public:
   //! May reduce binary data size thanks to smaller triangle indexes.
   void SetSplitIndices16 (bool theToSplit) { myToSplitIndices16 = theToSplit; }
 
+  //! Return TRUE if multithreaded optimizations are allowed; FALSE by default.
+  bool ToParallel() const { return myToParallel; }
+
+  //! Setup multithreaded execution.
+  void SetParallel (bool theToParallel) { myToParallel = theToParallel; }
+
   //! Return Draco parameters
   const RWGltf_DracoParameters& CompressionParameters() const { return myDracoParameters; }
 
@@ -397,6 +403,7 @@ protected:
   int64_t                                       myBinDataLen64;      //!< length of binary file
 
   std::vector<RWGltf_GltfBufferView>            myBuffViewsDraco;    //!< vector of buffers view with compression data
+  Standard_Boolean                              myToParallel;        //!< flag to use multithreading; FALSE by default
   RWGltf_DracoParameters                        myDracoParameters;   //!< Draco parameters
 };
 
index f0c2c917e004ea3e3f1d8e938b3b821ba678010e..62aaad6e2efaf1cc2033a73f72fdc2fcfa0829df 100644 (file)
@@ -383,6 +383,7 @@ static Standard_Integer WriteGltf (Draw_Interpretor& theDI,
   RWMesh_CoordinateSystem aSystemCoordSys = RWMesh_CoordinateSystem_Zup;
   bool toForceUVExport = false, toEmbedTexturesInGlb = true;
   bool toMergeFaces = false, toSplitIndices16 = false;
+  bool isParallel = false;
   RWMesh_NameFormat aNodeNameFormat = RWMesh_NameFormat_InstanceOrProduct;
   RWMesh_NameFormat aMeshNameFormat = RWMesh_NameFormat_Product;
   RWGltf_DracoParameters aDracoParameters;
@@ -556,6 +557,10 @@ static Standard_Integer WriteGltf (Draw_Interpretor& theDI,
     {
       aDracoParameters.UnifiedQuantization = Draw::ParseOnOffIterator(theNbArgs, theArgVec, anArgIter);
     }
+    else if (anArgCase == "-parallel")
+    {
+      isParallel = Draw::ParseOnOffIterator(theNbArgs, theArgVec, anArgIter);
+    }
     else
     {
       Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'";
@@ -587,6 +592,7 @@ static Standard_Integer WriteGltf (Draw_Interpretor& theDI,
   aWriter.SetToEmbedTexturesInGlb (toEmbedTexturesInGlb);
   aWriter.SetMergeFaces (toMergeFaces);
   aWriter.SetSplitIndices16 (toSplitIndices16);
+  aWriter.SetParallel(isParallel);
   aWriter.SetCompressionParameters(aDracoParameters);
   aWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit (aScaleFactorM);
   aWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem (aSystemCoordSys);
@@ -2450,7 +2456,7 @@ void  XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands)
                    "\n\t\t:            [-meshNameFormat {empty|product|instance|instOrProd|prodOrInst|prodAndInst|verbose}]=product"
                    "\n\t\t:            [-draco]=0 [-compressionLevel {0-10}]=7 [-quantizePositionBits Value]=14 [-quantizeNormalBits Value]=10"
                    "\n\t\t:            [-quantizeTexcoordBits Value]=12 [-quantizeColorBits Value]=8 [-quantizeGenericBits Value]=12"
-                   "\n\t\t:            [-unifiedQuantization]=0"
+                   "\n\t\t:            [-unifiedQuantization]=0 [-parallel]=0"
                    "\n\t\t: Write XDE document into glTF file."
                    "\n\t\t:   -trsfFormat       preferred transformation format"
                    "\n\t\t:   -systemCoordSys   system coordinate system; Zup when not specified"
@@ -2460,7 +2466,7 @@ void  XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands)
                    "\n\t\t:   -texturesSeparate write textures to separate files"
                    "\n\t\t:   -nodeNameFormat   name format for Nodes"
                    "\n\t\t:   -meshNameFormat   name format for Meshes"
-                   "\n\t\t:   -draco use Draco  compression 3D geometric meshes"
+                   "\n\t\t:   -draco            use Draco compression 3D geometric meshes"
                    "\n\t\t:   -compressionLevel draco compression level [0-10] (by default 7), a value of 0 will apply sequential encoding and preserve face order"
                    "\n\t\t:   -quantizePositionBits quantization bits for position attribute when using Draco compression (by default 14)"
                    "\n\t\t:   -quantizeNormalBits   quantization bits for normal attribute when using Draco compression (by default 10)"
@@ -2468,7 +2474,8 @@ void  XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands)
                    "\n\t\t:   -quantizeColorBits    quantization bits for color attribute when using Draco compression (by default 8)"
                    "\n\t\t:   -quantizeGenericBits  quantization bits for skinning attribute (joint indices and joint weights)"
                    "\n                        and custom attributes when using Draco compression (by default 12)"
-                   "\n\t\t:   -unifiedQuantization  quantization is applied on each primitive separately if this option is false",
+                   "\n\t\t:   -unifiedQuantization  quantization is applied on each primitive separately if this option is false"
+                   "\n\t\t:   -parallel             use multithreading for Draco compression",
                    __FILE__, WriteGltf, g);
   theCommands.Add ("writegltf",
                    "writegltf shape file",
diff --git a/tests/de_mesh/gltf_write/bull_parallel b/tests/de_mesh/gltf_write/bull_parallel
new file mode 100644 (file)
index 0000000..5e3967b
--- /dev/null
@@ -0,0 +1,13 @@
+puts "========"
+puts "0032867: Data Exchange - Implement Draco compression for writing glTF"
+puts "Test case exporting model into glb (binary glTF) file."
+puts "========"
+
+Close D0 -silent
+ReadGltf D0 [locate_data_file bug32867_bull.glb]
+
+set aGltfFile1 "${imagedir}/${casename}_tmp1.glb"
+
+WriteGltf D0 "$aGltfFile1" -draco on -parallel
+
+ReadGltf D "$aGltfFile1"