0030476: Visualization, Path Tracing - Adaptive Screen Sampling leads to unstable...
[occt.git] / src / OpenGl / OpenGl_TileSampler.cxx
1 // Created on: 2016-06-16
2 // Created by: Denis BOGOLEPOV & Danila ULYANOV
3 // Copyright (c) 2016 OPEN CASCADE SAS
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 #include <OpenGl_Context.hxx>
17 #include <OpenGl_TileSampler.hxx>
18 #include <Graphic3d_RenderingParams.hxx>
19 #include <TCollection_ExtendedString.hxx>
20
21 // define to debug algorithm values
22 //#define RAY_TRACE_PRINT_DEBUG_INFO
23
24 //=======================================================================
25 //function : OpenGl_TileSampler
26 //purpose  :
27 //=======================================================================
28 OpenGl_TileSampler::OpenGl_TileSampler()
29 : myLastSample (0),
30   myScaleFactor(1.0f),
31   myTileSize   (0),
32   myViewSize   (0, 0)
33 {
34   mySampler.initFaure();
35 }
36
37 //=======================================================================
38 //function : GrabVarianceMap
39 //purpose  :
40 //=======================================================================
41 void OpenGl_TileSampler::GrabVarianceMap (const Handle(OpenGl_Context)& theContext,
42                                           const Handle(OpenGl_Texture)& theTexture)
43 {
44   if (theTexture.IsNull())
45   {
46     return;
47   }
48
49   myVarianceRaw.Init (0);
50 #if !defined(GL_ES_VERSION_2_0)
51   theTexture->Bind (theContext);
52   theContext->core11fwd->glPixelStorei (GL_PACK_ALIGNMENT,  1);
53   theContext->core11fwd->glPixelStorei (GL_PACK_ROW_LENGTH, 0);
54   theContext->core11fwd->glGetTexImage (GL_TEXTURE_2D, 0, GL_RED_INTEGER, GL_INT, myVarianceRaw.ChangeData());
55   const GLenum anErr = theContext->core11fwd->glGetError();
56   theTexture->Unbind (theContext);
57   if (anErr != GL_NO_ERROR)
58   {
59     theContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_MEDIUM,
60                              "Error! Failed to fetch visual error map from the GPU");
61     return;
62   }
63 #else
64   // glGetTexImage() is unavailable on OpenGL ES, FBO + glReadPixels() can be used instead
65   (void )theContext;
66 #endif
67
68   const float aFactor = 1.0f / myScaleFactor;
69   for (Standard_Size aColIter = 0; aColIter < myVarianceMap.SizeX; ++aColIter)
70   {
71     for (Standard_Size aRowIter = 0; aRowIter < myVarianceMap.SizeY; ++aRowIter)
72     {
73       const int aRawValue = myVarianceRaw.Value (aRowIter, aColIter);
74       Standard_RangeError_Raise_if (aRawValue < 0, "Internal Error: signed integer overflow within OpenGl_TileSampler");
75
76       float& aTile = myVarianceMap.ChangeValue (aRowIter, aColIter);
77       aTile = aFactor * float(aRawValue);
78       aTile *= 1.0f / tileArea ((int )aColIter, (int )aRowIter); // average error over the tile
79       if (aRowIter != 0)
80       {
81         aTile += myVarianceMap.Value (aRowIter - 1, aColIter);
82       }
83     }
84   }
85
86   // build marginal distribution
87   for (Standard_Size aX = 0; aX < myVarianceMap.SizeX; ++aX)
88   {
89     myMarginalMap[aX] = myVarianceMap.Value (myVarianceMap.SizeY - 1, aX);
90     if (aX != 0)
91     {
92       myMarginalMap[aX] += myMarginalMap[aX - 1];
93     }
94   }
95
96 #ifdef RAY_TRACE_PRINT_DEBUG_INFO
97   dumpMap (std::cerr, myVarianceRaw, "OpenGl_TileSampler, Variance map");
98 #endif
99 }
100
101 //=======================================================================
102 //function : dumpMap
103 //purpose  :
104 //=======================================================================
105 void OpenGl_TileSampler::dumpMap (std::ostream& theStream,
106                                   const Image_PixMapTypedData<int>& theMap,
107                                   const char* theTitle) const
108 {
109   theStream << theTitle << " " << theMap.SizeX << "x" << theMap.SizeY << " (tile " << myTileSize << "x" << myTileSize << ")" << ":\n";
110   for (Standard_Size aRowIter = 0; aRowIter < theMap.SizeY; ++aRowIter)
111   {
112     for (Standard_Size aColIter = 0; aColIter < theMap.SizeX; ++aColIter)
113     {
114       theStream << " [" << theMap.Value (aRowIter, aColIter) << "]";
115     }
116     theStream << "\n";
117   }
118 }
119
120 //=======================================================================
121 //function : nextTileToSample
122 //purpose  :
123 //=======================================================================
124 Graphic3d_Vec2i OpenGl_TileSampler::nextTileToSample()
125 {
126   Graphic3d_Vec2i aTile (0, 0);
127   const float aKsiX = mySampler.sample (0, myLastSample) * myMarginalMap.back();
128   for (; (size_t )aTile.x() < myMarginalMap.size() - 1; ++aTile.x())
129   {
130     if (aKsiX <= myMarginalMap[aTile.x()])
131     {
132       break;
133     }
134   }
135
136   const float aKsiY = mySampler.sample (1, myLastSample) * myVarianceMap.Value (myVarianceMap.SizeY - 1, aTile.x());
137   for (; (size_t )aTile.y() < myVarianceMap.SizeY - 1; ++aTile.y())
138   {
139     if (aKsiY <= myVarianceMap.Value (aTile.y(), aTile.x()))
140     {
141       break;
142     }
143   }
144
145   ++myLastSample;
146   return aTile;
147 }
148
149 //=======================================================================
150 //function : SetSize
151 //purpose  :
152 //=======================================================================
153 void OpenGl_TileSampler::SetSize (const Graphic3d_RenderingParams& theParams,
154                                   const Graphic3d_Vec2i& theSize)
155 {
156   if (theSize.x() <= 0
157    || theSize.y() <= 0)
158   {
159     return;
160   }
161
162   myViewSize = theSize;
163
164   const int aTileSize = Max (theParams.RayTracingTileSize, 1);
165   const int aNbTilesX = Max (1, static_cast<int> (ceilf (static_cast<float> (theSize.x()) / aTileSize)));
166   const int aNbTilesY = Max (1, static_cast<int> (ceilf (static_cast<float> (theSize.y()) / aTileSize)));
167   if (myTileSize != aTileSize
168    || (int )myTiles.SizeX != aNbTilesX
169    || (int )myTiles.SizeY != aNbTilesY)
170   {
171     myTileSize = aTileSize;
172     myScaleFactor = 1.0e6f * (1024.0f / float(myTileSize * myTileSize));
173
174     Handle(NCollection_BaseAllocator) anAlloc = NCollection_BaseAllocator::CommonBaseAllocator();
175     myTiles.SetTopDown (true);
176     myTiles.Init (anAlloc, aNbTilesX, aNbTilesY);
177     myTiles.Init (1);
178
179     myTileSamples.SetTopDown (true);
180     myTileSamples.Init (myTiles.Allocator(), aNbTilesX, aNbTilesY);
181     myTileSamples.Init (1);
182
183     myVarianceMap.SetTopDown (true);
184     myVarianceMap.Init (myTiles.Allocator(), myTiles.SizeX, myTiles.SizeY);
185     myVarianceMap.Init (0.0f);
186
187     myVarianceRaw.SetTopDown (true);
188     myVarianceRaw.Init (myTiles.Allocator(), myTiles.SizeX, myTiles.SizeY);
189     myVarianceRaw.Init (0);
190
191     myOffsets.SetTopDown (true);
192     myOffsets.Init (myTiles.Allocator(), myTiles.SizeX, myTiles.SizeY);
193     myOffsets.Init (Graphic3d_Vec2i (-1, -1));
194
195     myMarginalMap.resize (myTiles.SizeX);
196     myMarginalMap.assign (myMarginalMap.size(), 0.0f);
197   }
198
199   // calculate a size of compact offsets texture optimal for rendering reduced number of tiles
200   Standard_Integer aNbShunkTilesX = (int )myTiles.SizeX, aNbShunkTilesY = (int )myTiles.SizeY;
201   if (theParams.NbRayTracingTiles > 0)
202   {
203     aNbShunkTilesX = 8;
204     aNbShunkTilesY = 8;
205     for (Standard_Integer anIdx = 0; aNbShunkTilesX * aNbShunkTilesY < theParams.NbRayTracingTiles; ++anIdx)
206     {
207       (anIdx % 2 == 0 ? aNbShunkTilesX : aNbShunkTilesY) <<= 1;
208     }
209   }
210   if ((int )myOffsetsShrunk.SizeX != aNbShunkTilesX
211    || (int )myOffsetsShrunk.SizeY != aNbShunkTilesY)
212   {
213     myOffsetsShrunk.SetTopDown (true);
214     myOffsetsShrunk.Init (myTiles.Allocator(), aNbShunkTilesX, aNbShunkTilesY);
215     myOffsetsShrunk.Init (Graphic3d_Vec2i (-1, -1));
216   }
217 }
218
219 //=======================================================================
220 //function : upload
221 //purpose  :
222 //=======================================================================
223 bool OpenGl_TileSampler::upload (const Handle(OpenGl_Context)& theContext,
224                                  const Handle(OpenGl_Texture)& theSamplesTexture,
225                                  const Handle(OpenGl_Texture)& theOffsetsTexture,
226                                  const bool theAdaptive)
227 {
228   if (myTiles.IsEmpty())
229   {
230     return false;
231   }
232
233   // Fill in myTiles map with a number of passes (samples) per tile.
234   // By default, all tiles receive 1 sample, but basing on visual error level (myVarianceMap),
235   // this amount is re-distributed from tiles having smallest error take 0 samples to tiles having larger error.
236   // This redistribution is smoothed by Halton sampler.
237   //
238   // myOffsets map is filled as redirection of currently rendered tile to another one
239   // so that tiles having smallest error level have 0 tiles redirected from,
240   // while tiles with great error level might be rendered more than 1.
241   // This map is used within single-pass rendering method requiring atomic float operation support from hardware.
242   myTiles.Init (0);
243   Image_PixMapTypedData<Graphic3d_Vec2i>& anOffsets = theAdaptive ? myOffsetsShrunk : myOffsets;
244   anOffsets.Init (Graphic3d_Vec2i (-1, -1));
245   for (Standard_Size aRowIter = 0; aRowIter < anOffsets.SizeY; ++aRowIter)
246   {
247     for (Standard_Size aColIter = 0; aColIter < anOffsets.SizeX; ++aColIter)
248     {
249       Graphic3d_Vec2i& aRedirectTile = anOffsets.ChangeValue (aRowIter, aColIter);
250       aRedirectTile = theAdaptive ? nextTileToSample() : Graphic3d_Vec2i ((int )aColIter, (int )aRowIter);
251       myTiles.ChangeValue (aRedirectTile.y(), aRedirectTile.x()) += 1;
252     }
253   }
254
255 #ifdef RAY_TRACE_PRINT_DEBUG_INFO
256   dumpMap (std::cerr, myTiles, "OpenGl_TileSampler, Samples");
257 #endif
258
259   // Fill in myTileSamples map from myTiles with an actual number of Samples per Tile as multiple of Tile Area
260   // (e.g. tile that should be rendered ones will have amount of samples equal to its are 4x4=16).
261   // This map is used for discarding tile fragments having <=0 of samples left within multi-pass rendering.
262   myTileSamples.Init (0);
263   for (Standard_Size aRowIter = 0; aRowIter < myTiles.SizeY; ++aRowIter)
264   {
265     for (Standard_Size aColIter = 0; aColIter < myTiles.SizeX; ++aColIter)
266     {
267       myTileSamples.ChangeValue (aRowIter, aColIter) = tileArea ((int )aColIter, (int )aRowIter) * myTiles.Value (aRowIter, aColIter);
268     }
269   }
270
271   bool hasErrors = false;
272
273   if (!theSamplesTexture.IsNull())
274   {
275     theSamplesTexture->Bind (theContext);
276     theContext->core11fwd->glPixelStorei (GL_UNPACK_ALIGNMENT,  1);
277   #if !defined(GL_ES_VERSION_2_0)
278     theContext->core11fwd->glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
279   #endif
280     if (theSamplesTexture->SizeX() == (int )myTileSamples.SizeX
281      && theSamplesTexture->SizeY() == (int )myTileSamples.SizeY)
282     {
283       theContext->core11fwd->glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, (int )myTileSamples.SizeX, (int )myTileSamples.SizeY, GL_RED_INTEGER, GL_INT, myTileSamples.Data());
284       if (theContext->core11fwd->glGetError() != GL_NO_ERROR)
285       {
286         hasErrors = true;
287         theContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_MEDIUM,
288                                  "Error! Failed to upload tile samples map on the GPU");
289       }
290     }
291     else
292     {
293       hasErrors = true;
294     }
295     theSamplesTexture->Unbind (theContext);
296   }
297
298   if (!theOffsetsTexture.IsNull())
299   {
300     if (theOffsetsTexture->SizeX() != (int )anOffsets.SizeX
301     ||  theOffsetsTexture->SizeY() != (int )anOffsets.SizeY
302     || !theOffsetsTexture->IsValid())
303     {
304       theOffsetsTexture->Release (theContext.operator->());
305       if (!theOffsetsTexture->Init (theContext, GL_RG32I, GL_RG_INTEGER, GL_INT,
306                                     (int )anOffsets.SizeX, (int )anOffsets.SizeY, Graphic3d_TOT_2D))
307       {
308         hasErrors = true;
309       }
310     }
311     if (theOffsetsTexture->IsValid())
312     {
313       theOffsetsTexture->Bind (theContext);
314       theContext->core11fwd->glPixelStorei (GL_UNPACK_ALIGNMENT,  1);
315     #if !defined(GL_ES_VERSION_2_0)
316       theContext->core11fwd->glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
317     #endif
318       theContext->core11fwd->glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, (int )anOffsets.SizeX, (int )anOffsets.SizeY, GL_RG_INTEGER, GL_INT, anOffsets.Data());
319       if (theContext->core11fwd->glGetError() != GL_NO_ERROR)
320       {
321         hasErrors = true;
322         theContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_MEDIUM,
323                                  "Error! Failed to upload tile offset map on the GPU");
324       }
325       theOffsetsTexture->Unbind (theContext);
326     }
327   }
328   return !hasErrors;
329 }