mirror of
https://git.dev.opencascade.org/repos/occt.git
synced 2025-08-14 13:30:48 +03:00
0030476: Visualization, Path Tracing - Adaptive Screen Sampling leads to unstable results
OpenGl_View::runPathtrace() has been extended with alternative multi-pass Adaptive Screen Sampling mode, not relying on atomic floating point operations. Although atomic operations on floats allows single-pass rendering, such operations leads to instability in case of different calculation order. Atomic float operations are also currently supported only by single GPU vendor. Fixed GLSL compilation on Intel drivers (follow ARB_shader_image_load_store specs rather than EXT_shader_image_load_store). Graphic3d_RenderingParams::AdaptiveScreenSamplingAtomic option has been added to activate 1-pass Adaptive Screen Sampling mode when supported by hardware. vfps command has been extended with -duration argument allowing to limit command execution time. vactivate command has been extended with -noUpdate argument.
This commit is contained in:
@@ -1136,10 +1136,14 @@ TCollection_AsciiString OpenGl_View::generateShaderPrefix (const Handle(OpenGl_C
|
||||
|
||||
if (myRaytraceParameters.AdaptiveScreenSampling) // adaptive screen sampling requested
|
||||
{
|
||||
// to activate the feature we need OpenGL 4.4 and GL_NV_shader_atomic_float extension
|
||||
if (theGlContext->IsGlGreaterEqual (4, 4) && theGlContext->CheckExtension ("GL_NV_shader_atomic_float"))
|
||||
if (theGlContext->IsGlGreaterEqual (4, 4))
|
||||
{
|
||||
aPrefixString += TCollection_AsciiString ("\n#define ADAPTIVE_SAMPLING");
|
||||
if (myRaytraceParameters.AdaptiveScreenSamplingAtomic
|
||||
&& theGlContext->CheckExtension ("GL_NV_shader_atomic_float"))
|
||||
{
|
||||
aPrefixString += TCollection_AsciiString ("\n#define ADAPTIVE_SAMPLING_ATOMIC");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1387,9 +1391,11 @@ Standard_Boolean OpenGl_View::initRaytraceResources (const Standard_Integer theS
|
||||
aToRebuildShaders = Standard_True;
|
||||
}
|
||||
|
||||
if (myRenderParams.AdaptiveScreenSampling != myRaytraceParameters.AdaptiveScreenSampling)
|
||||
if (myRenderParams.AdaptiveScreenSampling != myRaytraceParameters.AdaptiveScreenSampling
|
||||
|| myRenderParams.AdaptiveScreenSamplingAtomic != myRaytraceParameters.AdaptiveScreenSamplingAtomic)
|
||||
{
|
||||
myRaytraceParameters.AdaptiveScreenSampling = myRenderParams.AdaptiveScreenSampling;
|
||||
myRaytraceParameters.AdaptiveScreenSampling = myRenderParams.AdaptiveScreenSampling;
|
||||
myRaytraceParameters.AdaptiveScreenSamplingAtomic = myRenderParams.AdaptiveScreenSamplingAtomic;
|
||||
if (myRenderParams.AdaptiveScreenSampling) // adaptive sampling was requested
|
||||
{
|
||||
if (!theGlContext->HasRayTracingAdaptiveSampling())
|
||||
@@ -1397,7 +1403,15 @@ Standard_Boolean OpenGl_View::initRaytraceResources (const Standard_Integer theS
|
||||
// disable the feature if it is not supported
|
||||
myRaytraceParameters.AdaptiveScreenSampling = myRenderParams.AdaptiveScreenSampling = Standard_False;
|
||||
theGlContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PORTABILITY, 0, GL_DEBUG_SEVERITY_LOW,
|
||||
"Adaptive sampling not supported (OpenGL 4.4 or GL_NV_shader_atomic_float is missing)");
|
||||
"Adaptive sampling is not supported (OpenGL 4.4 is missing)");
|
||||
}
|
||||
else if (myRaytraceParameters.AdaptiveScreenSamplingAtomic
|
||||
&& !theGlContext->HasRayTracingAdaptiveSamplingAtomic())
|
||||
{
|
||||
// disable the feature if it is not supported
|
||||
myRaytraceParameters.AdaptiveScreenSamplingAtomic = myRenderParams.AdaptiveScreenSamplingAtomic = Standard_False;
|
||||
theGlContext->PushMessage (GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PORTABILITY, 0, GL_DEBUG_SEVERITY_LOW,
|
||||
"Atomic adaptive sampling is not supported (GL_NV_shader_atomic_float is missing)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1726,6 +1740,8 @@ Standard_Boolean OpenGl_View::initRaytraceResources (const Standard_Integer theS
|
||||
|
||||
myUniformLocations[anIndex][OpenGl_RT_uRenderImage] =
|
||||
aShaderProgram->GetUniformLocation (theGlContext, "uRenderImage");
|
||||
myUniformLocations[anIndex][OpenGl_RT_uTilesImage] =
|
||||
aShaderProgram->GetUniformLocation (theGlContext, "uTilesImage");
|
||||
myUniformLocations[anIndex][OpenGl_RT_uOffsetImage] =
|
||||
aShaderProgram->GetUniformLocation (theGlContext, "uOffsetImage");
|
||||
myUniformLocations[anIndex][OpenGl_RT_uTileSize] =
|
||||
@@ -1814,6 +1830,8 @@ void OpenGl_View::releaseRaytraceResources (const Handle(OpenGl_Context)& theGlC
|
||||
nullifyResource (theGlContext, myRaytraceTileOffsetsTexture[1]);
|
||||
nullifyResource (theGlContext, myRaytraceVisualErrorTexture[0]);
|
||||
nullifyResource (theGlContext, myRaytraceVisualErrorTexture[1]);
|
||||
nullifyResource (theGlContext, myRaytraceTileSamplesTexture[0]);
|
||||
nullifyResource (theGlContext, myRaytraceTileSamplesTexture[1]);
|
||||
|
||||
nullifyResource (theGlContext, mySceneNodeInfoTexture);
|
||||
nullifyResource (theGlContext, mySceneMinPointTexture);
|
||||
@@ -1874,6 +1892,7 @@ Standard_Boolean OpenGl_View::updateRaytraceBuffers (const Standard_Integer
|
||||
{
|
||||
myRaytraceOutputTexture[aViewIter] = new OpenGl_Texture();
|
||||
myRaytraceVisualErrorTexture[aViewIter] = new OpenGl_Texture();
|
||||
myRaytraceTileSamplesTexture[aViewIter] = new OpenGl_Texture();
|
||||
myRaytraceTileOffsetsTexture[aViewIter] = new OpenGl_Texture();
|
||||
}
|
||||
|
||||
@@ -1895,7 +1914,15 @@ Standard_Boolean OpenGl_View::updateRaytraceBuffers (const Standard_Integer
|
||||
&& myRaytraceVisualErrorTexture[aViewIter]->SizeX() == myTileSampler.NbTilesX()
|
||||
&& myRaytraceVisualErrorTexture[aViewIter]->SizeY() == myTileSampler.NbTilesY())
|
||||
{
|
||||
continue;
|
||||
if (myRaytraceParameters.AdaptiveScreenSamplingAtomic)
|
||||
{
|
||||
continue; // offsets texture is dynamically resized
|
||||
}
|
||||
else if (myRaytraceTileSamplesTexture[aViewIter]->SizeX() == myTileSampler.NbTilesX()
|
||||
&& myRaytraceTileSamplesTexture[aViewIter]->SizeY() == myTileSampler.NbTilesY())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
myAccumFrames = 0;
|
||||
@@ -1913,8 +1940,14 @@ Standard_Boolean OpenGl_View::updateRaytraceBuffers (const Standard_Integer
|
||||
|
||||
// workaround for some NVIDIA drivers
|
||||
myRaytraceVisualErrorTexture[aViewIter]->Release (theGlContext.operator->());
|
||||
myRaytraceTileSamplesTexture[aViewIter]->Release (theGlContext.operator->());
|
||||
myRaytraceVisualErrorTexture[aViewIter]->Init (theGlContext, GL_R32I, GL_RED_INTEGER, GL_INT,
|
||||
myTileSampler.NbTilesX(), myTileSampler.NbTilesY(), Graphic3d_TOT_2D);
|
||||
if (!myRaytraceParameters.AdaptiveScreenSamplingAtomic)
|
||||
{
|
||||
myRaytraceTileSamplesTexture[aViewIter]->Init (theGlContext, GL_R32I, GL_RED_INTEGER, GL_INT,
|
||||
myTileSampler.NbTilesX(), myTileSampler.NbTilesY(), Graphic3d_TOT_2D);
|
||||
}
|
||||
}
|
||||
else // non-adaptive mode
|
||||
{
|
||||
@@ -2684,8 +2717,16 @@ void OpenGl_View::bindRaytraceTextures (const Handle(OpenGl_Context)& theGlConte
|
||||
myRaytraceOutputTexture[theStereoView]->TextureId(), 0, GL_TRUE, 0, GL_READ_WRITE, GL_R32F);
|
||||
theGlContext->core42->glBindImageTexture (OpenGl_RT_VisualErrorImage,
|
||||
myRaytraceVisualErrorTexture[theStereoView]->TextureId(), 0, GL_TRUE, 0, GL_READ_WRITE, GL_R32I);
|
||||
theGlContext->core42->glBindImageTexture (OpenGl_RT_TileOffsetsImage,
|
||||
myRaytraceTileOffsetsTexture[theStereoView]->TextureId(), 0, GL_TRUE, 0, GL_READ_ONLY, GL_RG32I);
|
||||
if (myRaytraceParameters.AdaptiveScreenSamplingAtomic)
|
||||
{
|
||||
theGlContext->core42->glBindImageTexture (OpenGl_RT_TileOffsetsImage,
|
||||
myRaytraceTileOffsetsTexture[theStereoView]->TextureId(), 0, GL_TRUE, 0, GL_READ_ONLY, GL_RG32I);
|
||||
}
|
||||
else
|
||||
{
|
||||
theGlContext->core42->glBindImageTexture (OpenGl_RT_TileSamplesImage,
|
||||
myRaytraceTileSamplesTexture[theStereoView]->TextureId(), 0, GL_TRUE, 0, GL_READ_WRITE, GL_R32I);
|
||||
}
|
||||
#else
|
||||
(void )theStereoView;
|
||||
#endif
|
||||
@@ -2911,7 +2952,14 @@ Standard_Boolean OpenGl_View::runPathtrace (const Standard_Integer
|
||||
myTileSampler.Reset(); // reset tile sampler to its initial state
|
||||
|
||||
// Adaptive sampling is starting at the second frame
|
||||
myTileSampler.UploadOffsets (theGlContext, myRaytraceTileOffsetsTexture[aFBOIdx], false);
|
||||
if (myRaytraceParameters.AdaptiveScreenSamplingAtomic)
|
||||
{
|
||||
myTileSampler.UploadOffsets (theGlContext, myRaytraceTileOffsetsTexture[aFBOIdx], false);
|
||||
}
|
||||
else
|
||||
{
|
||||
myTileSampler.UploadSamples (theGlContext, myRaytraceTileSamplesTexture[aFBOIdx], false);
|
||||
}
|
||||
|
||||
#if !defined(GL_ES_VERSION_2_0)
|
||||
theGlContext->core44->glClearTexImage (myRaytraceOutputTexture[aFBOIdx]->TextureId(), 0, GL_RED, GL_FLOAT, NULL);
|
||||
@@ -2932,24 +2980,19 @@ Standard_Boolean OpenGl_View::runPathtrace (const Standard_Integer
|
||||
// Set frame accumulation weight
|
||||
myRaytraceProgram->SetUniform (theGlContext, myUniformLocations[0][OpenGl_RT_uAccumSamples], myAccumFrames);
|
||||
|
||||
// Set random number generator seed
|
||||
if (myAccumFrames == 0)
|
||||
{
|
||||
myRNG.SetSeed(); // start RNG from beginning
|
||||
}
|
||||
myRaytraceProgram->SetUniform (theGlContext, myUniformLocations[0][OpenGl_RT_uFrameRndSeed], static_cast<Standard_Integer> (myRNG.NextInt() >> 2));
|
||||
|
||||
// Set image uniforms for render program
|
||||
if (myRaytraceParameters.AdaptiveScreenSampling)
|
||||
{
|
||||
myRaytraceProgram->SetUniform (theGlContext, myUniformLocations[0][OpenGl_RT_uRenderImage], OpenGl_RT_OutputImage);
|
||||
myRaytraceProgram->SetUniform (theGlContext, myUniformLocations[0][OpenGl_RT_uTilesImage], OpenGl_RT_TileSamplesImage);
|
||||
myRaytraceProgram->SetUniform (theGlContext, myUniformLocations[0][OpenGl_RT_uOffsetImage], OpenGl_RT_TileOffsetsImage);
|
||||
myRaytraceProgram->SetUniform (theGlContext, myUniformLocations[0][OpenGl_RT_uTileSize], myTileSampler.TileSize());
|
||||
}
|
||||
|
||||
const Handle(OpenGl_FrameBuffer)& aRenderImageFramebuffer = myAccumFrames % 2 ? myRaytraceFBO1[aFBOIdx] : myRaytraceFBO2[aFBOIdx];
|
||||
aRenderImageFramebuffer->BindBuffer (theGlContext);
|
||||
if (myRaytraceParameters.AdaptiveScreenSampling)
|
||||
if (myRaytraceParameters.AdaptiveScreenSampling
|
||||
&& myRaytraceParameters.AdaptiveScreenSamplingAtomic)
|
||||
{
|
||||
// extend viewport here, so that tiles at boundaries (cut tile size by target rendering viewport)
|
||||
// redirected to inner tiles (full tile size) are drawn entirely
|
||||
@@ -2959,11 +3002,41 @@ Standard_Boolean OpenGl_View::runPathtrace (const Standard_Integer
|
||||
|
||||
// Generate for the given RNG seed
|
||||
glDisable (GL_DEPTH_TEST);
|
||||
theGlContext->core20fwd->glDrawArrays (GL_TRIANGLES, 0, 6);
|
||||
|
||||
// Adaptive Screen Sampling computes the same overall amount of samples per frame redraw as normal Path Tracing,
|
||||
// but distributes them unequally across pixels (grouped in tiles), so that some pixels do not receive new samples at all.
|
||||
//
|
||||
// Offsets map (redirecting currently rendered tile to another tile) allows performing Adaptive Screen Sampling in single pass,
|
||||
// but current implementation relies on atomic float operations (AdaptiveScreenSamplingAtomic) for this.
|
||||
// So that when atomic floats are not supported by GPU, multi-pass rendering is used instead.
|
||||
//
|
||||
// Single-pass rendering is more optimal due to smaller amount of draw calls,
|
||||
// memory synchronization barriers, discarding most of the fragments and bad parallelization in case of very small amount of tiles requiring more samples.
|
||||
// However, atomic operations on float values still produces different result (close, but not bit exact) making non-regression testing not robust.
|
||||
// It should be possible following single-pass rendering approach but using extra accumulation buffer and resolving pass as possible improvement.
|
||||
const int aNbPasses = myRaytraceParameters.AdaptiveScreenSampling
|
||||
&& !myRaytraceParameters.AdaptiveScreenSamplingAtomic
|
||||
? myTileSampler.MaxTileSamples()
|
||||
: 1;
|
||||
if (myAccumFrames == 0)
|
||||
{
|
||||
myRNG.SetSeed(); // start RNG from beginning
|
||||
}
|
||||
for (int aPassIter = 0; aPassIter < aNbPasses; ++aPassIter)
|
||||
{
|
||||
myRaytraceProgram->SetUniform (theGlContext, myUniformLocations[0][OpenGl_RT_uFrameRndSeed], static_cast<Standard_Integer> (myRNG.NextInt() >> 2));
|
||||
theGlContext->core20fwd->glDrawArrays (GL_TRIANGLES, 0, 6);
|
||||
if (myRaytraceParameters.AdaptiveScreenSampling)
|
||||
{
|
||||
#if !defined(GL_ES_VERSION_2_0)
|
||||
theGlContext->core44->glMemoryBarrier (GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
aRenderImageFramebuffer->UnbindBuffer (theGlContext);
|
||||
|
||||
if (myRaytraceParameters.AdaptiveScreenSampling)
|
||||
if (myRaytraceParameters.AdaptiveScreenSampling
|
||||
&& myRaytraceParameters.AdaptiveScreenSamplingAtomic)
|
||||
{
|
||||
glViewport (0, 0, theSizeX, theSizeY);
|
||||
}
|
||||
@@ -3026,7 +3099,14 @@ Standard_Boolean OpenGl_View::runPathtraceOut (const Graphic3d_Camera::Projectio
|
||||
{
|
||||
// Download visual error map from the GPU and build adjusted tile offsets for optimal image sampling
|
||||
myTileSampler.GrabVarianceMap (theGlContext, myRaytraceVisualErrorTexture[aFBOIdx]);
|
||||
myTileSampler.UploadOffsets (theGlContext, myRaytraceTileOffsetsTexture[aFBOIdx], myAccumFrames != 0);
|
||||
if (myRaytraceParameters.AdaptiveScreenSamplingAtomic)
|
||||
{
|
||||
myTileSampler.UploadOffsets (theGlContext, myRaytraceTileOffsetsTexture[aFBOIdx], myAccumFrames != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
myTileSampler.UploadSamples (theGlContext, myRaytraceTileSamplesTexture[aFBOIdx], myAccumFrames != 0);
|
||||
}
|
||||
}
|
||||
|
||||
unbindRaytraceTextures (theGlContext);
|
||||
|
Reference in New Issue
Block a user