0030748: Visualization - Marker displayed in immediate layer ruins QT Quick view...
[occt.git] / src / OpenGl / OpenGl_TileSampler.cxx
index c9b9b88..c8bbd17 100644 (file)
 
 #include <OpenGl_Context.hxx>
 #include <OpenGl_TileSampler.hxx>
+#include <Graphic3d_RenderingParams.hxx>
 #include <TCollection_ExtendedString.hxx>
 
-namespace
-{
-  //! Scale factor for estimating visual error.
-  static const float THE_SCALE_FACTOR = 1.0f / 1e6f;
-}
+// define to debug algorithm values
+//#define RAY_TRACE_PRINT_DEBUG_INFO
 
 //=======================================================================
 //function : OpenGl_TileSampler
 //purpose  :
 //=======================================================================
 OpenGl_TileSampler::OpenGl_TileSampler()
-: mySample (0),
-  mySizeX  (0),
-  mySizeY  (0),
-  myTilesX (0),
-  myTilesY (0)
+: myLastSample (0),
+  myScaleFactor(1.0f),
+  myTileSize   (0),
+  myViewSize   (0, 0)
 {
   mySampler.initFaure();
 }
@@ -41,156 +38,292 @@ OpenGl_TileSampler::OpenGl_TileSampler()
 //function : GrabVarianceMap
 //purpose  :
 //=======================================================================
-void OpenGl_TileSampler::GrabVarianceMap (const Handle(OpenGl_Context)& theContext)
+void OpenGl_TileSampler::GrabVarianceMap (const Handle(OpenGl_Context)& theContext,
+                                          const Handle(OpenGl_Texture)& theTexture)
 {
-#if !defined(GL_ES_VERSION_2_0)
-  std::vector<GLint> aRawData (NbTiles(), 0);
+  if (theTexture.IsNull())
+  {
+    return;
+  }
 
-  theContext->core11fwd->glGetTexImage (GL_TEXTURE_2D, 0, GL_RED_INTEGER, GL_INT, &aRawData.front());
+  myVarianceRaw.Init (0);
+#if !defined(GL_ES_VERSION_2_0)
+  theTexture->Bind (theContext);
+  theContext->core11fwd->glPixelStorei (GL_PACK_ALIGNMENT,  1);
+  theContext->core11fwd->glPixelStorei (GL_PACK_ROW_LENGTH, 0);
+  theContext->core11fwd->glGetTexImage (GL_TEXTURE_2D, 0, GL_RED_INTEGER, GL_INT, myVarianceRaw.ChangeData());
   const GLenum anErr = theContext->core11fwd->glGetError();
+  theTexture->Unbind (theContext);
   if (anErr != GL_NO_ERROR)
   {
     theContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_MEDIUM,
                              "Error! Failed to fetch visual error map from the GPU");
+    return;
   }
-  else
+#else
+  // glGetTexImage() is unavailable on OpenGL ES, FBO + glReadPixels() can be used instead
+  (void )theContext;
+#endif
+
+  const float aFactor = 1.0f / myScaleFactor;
+  for (Standard_Size aColIter = 0; aColIter < myVarianceMap.SizeX; ++aColIter)
   {
-    for (int aTileIdx = 0, aNbTiles = NbTiles(); aTileIdx < aNbTiles; ++aTileIdx)
+    for (Standard_Size aRowIter = 0; aRowIter < myVarianceMap.SizeY; ++aRowIter)
     {
-      myVarianceMap[aTileIdx] = aRawData[aTileIdx] * THE_SCALE_FACTOR;
-    }
+      const int aRawValue = myVarianceRaw.Value (aRowIter, aColIter);
+      Standard_RangeError_Raise_if (aRawValue < 0, "Internal Error: signed integer overflow within OpenGl_TileSampler");
 
-    for (int aX = 0; aX < myTilesX; ++aX)
-    {
-      for (int aY = 0; aY < myTilesY; ++aY)
+      float& aTile = myVarianceMap.ChangeValue (aRowIter, aColIter);
+      aTile = aFactor * float(aRawValue);
+      aTile *= 1.0f / tileArea ((int )aColIter, (int )aRowIter); // average error over the tile
+      if (aRowIter != 0)
       {
-        ChangeTile (aX, aY) *= 1.0f / TileArea (aX, aY); // average error over the tile
-
-        if (aY > 0)
-        {
-          ChangeTile (aX, aY) += Tile (aX, aY - 1);
-        }
+        aTile += myVarianceMap.Value (aRowIter - 1, aColIter);
       }
     }
+  }
 
-    myMarginalMap.resize (myTilesX); // build marginal distribution
-    for (int aX = 0; aX < myTilesX; ++aX)
+  // build marginal distribution
+  for (Standard_Size aX = 0; aX < myVarianceMap.SizeX; ++aX)
+  {
+    myMarginalMap[aX] = myVarianceMap.Value (myVarianceMap.SizeY - 1, aX);
+    if (aX != 0)
     {
-      myMarginalMap[aX] = Tile (aX, myTilesY - 1);
-
-      if (aX > 0)
-        myMarginalMap[aX] += myMarginalMap[aX - 1];
+      myMarginalMap[aX] += myMarginalMap[aX - 1];
     }
   }
-#else
-  // glGetTexImage() is unavailable on OpenGL ES, FBO + glReadPixels() can be used instead
-  (void )theContext;
+
+#ifdef RAY_TRACE_PRINT_DEBUG_INFO
+  dumpMap (std::cerr, myVarianceRaw, "OpenGl_TileSampler, Variance map");
 #endif
 }
 
 //=======================================================================
-//function : Sample
+//function : dumpMap
 //purpose  :
 //=======================================================================
-void OpenGl_TileSampler::Sample (int& theOffsetX,
-                                 int& theOffsetY)
+void OpenGl_TileSampler::dumpMap (std::ostream& theStream,
+                                  const Image_PixMapTypedData<int>& theMap,
+                                  const char* theTitle) const
 {
-  int aX = 0;
-  int aY = 0;
+  theStream << theTitle << " " << theMap.SizeX << "x" << theMap.SizeY << " (tile " << myTileSize << "x" << myTileSize << ")" << ":\n";
+  for (Standard_Size aRowIter = 0; aRowIter < theMap.SizeY; ++aRowIter)
+  {
+    for (Standard_Size aColIter = 0; aColIter < theMap.SizeX; ++aColIter)
+    {
+      theStream << " [" << theMap.Value (aRowIter, aColIter) << "]";
+    }
+    theStream << "\n";
+  }
+}
 
-  const float aKsiX = mySampler.sample (0, mySample) * myMarginalMap.back();
-  for (; aX < myTilesX - 1; ++aX)
+//=======================================================================
+//function : nextTileToSample
+//purpose  :
+//=======================================================================
+Graphic3d_Vec2i OpenGl_TileSampler::nextTileToSample()
+{
+  Graphic3d_Vec2i aTile (0, 0);
+  const float aKsiX = mySampler.sample (0, myLastSample) * myMarginalMap.back();
+  for (; (size_t )aTile.x() < myMarginalMap.size() - 1; ++aTile.x())
   {
-    if (aKsiX <= myMarginalMap[aX])
+    if (aKsiX <= myMarginalMap[aTile.x()])
     {
       break;
     }
   }
 
-  const float aKsiY = mySampler.sample (1, mySample) * Tile (aX, myTilesY - 1);
-  for (; aY < myTilesY - 1; ++aY)
+  const float aKsiY = mySampler.sample (1, myLastSample) * myVarianceMap.Value (myVarianceMap.SizeY - 1, aTile.x());
+  for (; (size_t )aTile.y() < myVarianceMap.SizeY - 1; ++aTile.y())
   {
-    if (aKsiY <= Tile (aX, aY))
+    if (aKsiY <= myVarianceMap.Value (aTile.y(), aTile.x()))
     {
       break;
     }
   }
 
-  theOffsetX = aX * TileSize();
-  theOffsetY = aY * TileSize();
-
-  ++mySample;
+  ++myLastSample;
+  return aTile;
 }
 
 //=======================================================================
 //function : SetSize
 //purpose  :
 //=======================================================================
-void OpenGl_TileSampler::SetSize (const int theSizeX,
-                                  const int theSizeY)
+void OpenGl_TileSampler::SetSize (const Graphic3d_RenderingParams& theParams,
+                                  const Graphic3d_Vec2i& theSize)
 {
-  if (mySizeX == theSizeX
-   && mySizeY == theSizeY)
+  if (theSize.x() <= 0
+   || theSize.y() <= 0)
   {
     return;
   }
 
-  mySizeX = theSizeX;
-  mySizeY = theSizeY;
+  myViewSize = theSize;
+
+  const int aTileSize = Max (theParams.RayTracingTileSize, 1);
+  const int aNbTilesX = Max (1, static_cast<int> (ceilf (static_cast<float> (theSize.x()) / aTileSize)));
+  const int aNbTilesY = Max (1, static_cast<int> (ceilf (static_cast<float> (theSize.y()) / aTileSize)));
+  if (myTileSize != aTileSize
+   || (int )myTiles.SizeX != aNbTilesX
+   || (int )myTiles.SizeY != aNbTilesY)
+  {
+    myTileSize = aTileSize;
+    myScaleFactor = 1.0e6f * (1024.0f / float(myTileSize * myTileSize));
+
+    Handle(NCollection_BaseAllocator) anAlloc = NCollection_BaseAllocator::CommonBaseAllocator();
+    myTiles.SetTopDown (true);
+    myTiles.Init (anAlloc, aNbTilesX, aNbTilesY);
+    myTiles.Init (1);
+
+    myTileSamples.SetTopDown (true);
+    myTileSamples.Init (myTiles.Allocator(), aNbTilesX, aNbTilesY);
+    myTileSamples.Init (1);
 
-  myTilesX = static_cast<int> (ceilf (static_cast<float> (mySizeX) / TileSize()));
-  myTilesY = static_cast<int> (ceilf (static_cast<float> (mySizeY) / TileSize()));
+    myVarianceMap.SetTopDown (true);
+    myVarianceMap.Init (myTiles.Allocator(), myTiles.SizeX, myTiles.SizeY);
+    myVarianceMap.Init (0.0f);
 
-  myVarianceMap.resize (myTilesX * myTilesY);
+    myVarianceRaw.SetTopDown (true);
+    myVarianceRaw.Init (myTiles.Allocator(), myTiles.SizeX, myTiles.SizeY);
+    myVarianceRaw.Init (0);
+
+    myOffsets.SetTopDown (true);
+    myOffsets.Init (myTiles.Allocator(), myTiles.SizeX, myTiles.SizeY);
+    myOffsets.Init (Graphic3d_Vec2i (-1, -1));
+
+    myMarginalMap.resize (myTiles.SizeX);
+    myMarginalMap.assign (myMarginalMap.size(), 0.0f);
+  }
+
+  // calculate a size of compact offsets texture optimal for rendering reduced number of tiles
+  Standard_Integer aNbShunkTilesX = (int )myTiles.SizeX, aNbShunkTilesY = (int )myTiles.SizeY;
+  if (theParams.NbRayTracingTiles > 0)
+  {
+    aNbShunkTilesX = 8;
+    aNbShunkTilesY = 8;
+    for (Standard_Integer anIdx = 0; aNbShunkTilesX * aNbShunkTilesY < theParams.NbRayTracingTiles; ++anIdx)
+    {
+      (anIdx % 2 == 0 ? aNbShunkTilesX : aNbShunkTilesY) <<= 1;
+    }
+  }
+  if ((int )myOffsetsShrunk.SizeX != aNbShunkTilesX
+   || (int )myOffsetsShrunk.SizeY != aNbShunkTilesY)
+  {
+    myOffsetsShrunk.SetTopDown (true);
+    myOffsetsShrunk.Init (myTiles.Allocator(), aNbShunkTilesX, aNbShunkTilesY);
+    myOffsetsShrunk.Init (Graphic3d_Vec2i (-1, -1));
+  }
 }
 
 //=======================================================================
-//function : Upload
+//function : upload
 //purpose  :
 //=======================================================================
-void OpenGl_TileSampler::Upload (const Handle(OpenGl_Context)& theContext,
-                                 const Handle(OpenGl_Texture)& theTexture,
-                                 const int                     theNbTilesX,
-                                 const int                     theNbTilesY,
-                                 const bool                    theAdaptive)
+bool OpenGl_TileSampler::upload (const Handle(OpenGl_Context)& theContext,
+                                 const Handle(OpenGl_Texture)& theSamplesTexture,
+                                 const Handle(OpenGl_Texture)& theOffsetsTexture,
+                                 const bool theAdaptive)
 {
-  if (theTexture.IsNull())
+  if (myTiles.IsEmpty())
   {
-    return;
+    return false;
   }
 
-  const int aNbTilesX = theAdaptive ? theNbTilesX : myTilesX;
-  const int aNbTilesY = theAdaptive ? theNbTilesY : myTilesY;
+  // Fill in myTiles map with a number of passes (samples) per tile.
+  // By default, all tiles receive 1 sample, but basing on visual error level (myVarianceMap),
+  // this amount is re-distributed from tiles having smallest error take 0 samples to tiles having larger error.
+  // This redistribution is smoothed by Halton sampler.
+  //
+  // myOffsets map is filled as redirection of currently rendered tile to another one
+  // so that tiles having smallest error level have 0 tiles redirected from,
+  // while tiles with great error level might be rendered more than 1.
+  // This map is used within single-pass rendering method requiring atomic float operation support from hardware.
+  myTiles.Init (0);
+  Image_PixMapTypedData<Graphic3d_Vec2i>& anOffsets = theAdaptive ? myOffsetsShrunk : myOffsets;
+  anOffsets.Init (Graphic3d_Vec2i (-1, -1));
+  for (Standard_Size aRowIter = 0; aRowIter < anOffsets.SizeY; ++aRowIter)
+  {
+    for (Standard_Size aColIter = 0; aColIter < anOffsets.SizeX; ++aColIter)
+    {
+      Graphic3d_Vec2i& aRedirectTile = anOffsets.ChangeValue (aRowIter, aColIter);
+      aRedirectTile = theAdaptive ? nextTileToSample() : Graphic3d_Vec2i ((int )aColIter, (int )aRowIter);
+      myTiles.ChangeValue (aRedirectTile.y(), aRedirectTile.x()) += 1;
+    }
+  }
+
+#ifdef RAY_TRACE_PRINT_DEBUG_INFO
+  dumpMap (std::cerr, myTiles, "OpenGl_TileSampler, Samples");
+#endif
 
-  Standard_ASSERT_RAISE (aNbTilesX * aNbTilesY > 0,
-    "Error! Number of sampling tiles should be positive");
+  // Fill in myTileSamples map from myTiles with an actual number of Samples per Tile as multiple of Tile Area
+  // (e.g. tile that should be rendered ones will have amount of samples equal to its are 4x4=16).
+  // This map is used for discarding tile fragments having <=0 of samples left within multi-pass rendering.
+  myTileSamples.Init (0);
+  for (Standard_Size aRowIter = 0; aRowIter < myTiles.SizeY; ++aRowIter)
+  {
+    for (Standard_Size aColIter = 0; aColIter < myTiles.SizeX; ++aColIter)
+    {
+      myTileSamples.ChangeValue (aRowIter, aColIter) = tileArea ((int )aColIter, (int )aRowIter) * myTiles.Value (aRowIter, aColIter);
+    }
+  }
 
-  std::vector<GLint> aData (aNbTilesX * aNbTilesY * 2);
+  bool hasErrors = false;
 
-  for (int aX = 0; aX < aNbTilesX; ++aX)
+  if (!theSamplesTexture.IsNull())
   {
-    for (int aY = 0; aY < aNbTilesY; ++aY)
+    theSamplesTexture->Bind (theContext);
+    theContext->core11fwd->glPixelStorei (GL_UNPACK_ALIGNMENT,  1);
+  #if !defined(GL_ES_VERSION_2_0)
+    theContext->core11fwd->glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
+  #endif
+    if (theSamplesTexture->SizeX() == (int )myTileSamples.SizeX
+     && theSamplesTexture->SizeY() == (int )myTileSamples.SizeY)
     {
-      if (!theAdaptive)
-      {
-        aData[(aY * aNbTilesX + aX) * 2 + 0] = aX * TileSize();
-        aData[(aY * aNbTilesX + aX) * 2 + 1] = aY * TileSize();
-      }
-      else
+      theContext->core11fwd->glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, (int )myTileSamples.SizeX, (int )myTileSamples.SizeY, GL_RED_INTEGER, GL_INT, myTileSamples.Data());
+      if (theContext->core11fwd->glGetError() != GL_NO_ERROR)
       {
-        Sample (aData[(aY * aNbTilesX + aX) * 2 + 0],
-                aData[(aY * aNbTilesX + aX) * 2 + 1]);
+        hasErrors = true;
+        theContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_MEDIUM,
+                                 "Error! Failed to upload tile samples map on the GPU");
       }
     }
+    else
+    {
+      hasErrors = true;
+    }
+    theSamplesTexture->Unbind (theContext);
   }
 
-  theTexture->Bind (theContext);
-
-  theContext->core11fwd->glTexImage2D (GL_TEXTURE_2D, 0, GL_RG32I, aNbTilesX, aNbTilesY, 0, GL_RG_INTEGER, GL_UNSIGNED_INT, &aData.front());
-
-  if (theContext->core11fwd->glGetError() != GL_NO_ERROR)
+  if (!theOffsetsTexture.IsNull())
   {
-    theContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_MEDIUM,
-                             "Error! Failed to upload tile offset map on the GPU");
+    if (theOffsetsTexture->SizeX() != (int )anOffsets.SizeX
+    ||  theOffsetsTexture->SizeY() != (int )anOffsets.SizeY
+    || !theOffsetsTexture->IsValid())
+    {
+      theOffsetsTexture->Release (theContext.operator->());
+      if (!theOffsetsTexture->Init (theContext, GL_RG32I, GL_RG_INTEGER, GL_INT,
+                                    (int )anOffsets.SizeX, (int )anOffsets.SizeY, Graphic3d_TOT_2D))
+      {
+        hasErrors = true;
+      }
+    }
+    if (theOffsetsTexture->IsValid())
+    {
+      theOffsetsTexture->Bind (theContext);
+      theContext->core11fwd->glPixelStorei (GL_UNPACK_ALIGNMENT,  1);
+    #if !defined(GL_ES_VERSION_2_0)
+      theContext->core11fwd->glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
+    #endif
+      theContext->core11fwd->glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, (int )anOffsets.SizeX, (int )anOffsets.SizeY, GL_RG_INTEGER, GL_INT, anOffsets.Data());
+      if (theContext->core11fwd->glGetError() != GL_NO_ERROR)
+      {
+        hasErrors = true;
+        theContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_MEDIUM,
+                                 "Error! Failed to upload tile offset map on the GPU");
+      }
+      theOffsetsTexture->Unbind (theContext);
+    }
   }
+  return !hasErrors;
 }