]> OCCT Git - occt.git/commitdiff
0032114: Visualization, TKOpenGl - broken PBR LUT on OpenGL ES 2.0
authorkgv <kgv@opencascade.com>
Tue, 2 Feb 2021 23:50:55 +0000 (02:50 +0300)
committerbugmaster <bugmaster@opencascade.com>
Wed, 3 Feb 2021 15:40:34 +0000 (18:40 +0300)
Added image formats Image_Format_RGF_half/Image_Format_RGBAF_half
with manual conversion between 32-bit float and 16-bit half-float values.

PBR LUT table is now converted into GL_HALF_FLOAT_OES data format in case of OpenGL ES 2.0.

src/Image/Image_Format.hxx
src/Image/Image_PixMap.cxx
src/Image/Image_PixMap.hxx
src/Media/Media_Frame.cxx
src/OpenGl/OpenGl_Context.cxx
src/OpenGl/OpenGl_FrameBuffer.cxx
src/OpenGl/OpenGl_TextureFormat.cxx
src/OpenGl/OpenGl_View.cxx

index 28bfcf4d697c3780745a93c68427587af70c064b..6c94daa3da6f03e4dcb13ad48f630593971398c0 100644 (file)
@@ -33,7 +33,9 @@ enum Image_Format
   Image_Format_BGRF,        //!< same as RGBF but with different components order
   Image_Format_RGBAF,       //!< 4 floats (16-bytes) RGBA image plane
   Image_Format_BGRAF,       //!< same as RGBAF but with different components order
+  Image_Format_RGF_half,    //!< 2 half-floats (4-bytes) RG image plane
+  Image_Format_RGBAF_half,  //!< 4 half-floats (8-bytes) RGBA image plane
 };
-enum { Image_Format_NB = Image_Format_BGRAF + 1 };
+enum { Image_Format_NB = Image_Format_RGBAF_half + 1 };
 
 #endif // _Image_Format_HeaderFile
index 15d2fad63f6e9a76a1691c6cb9db599fb0bcdcf7..02d7bdad7d7ab2579a122e43ba33f4c0c78c1f3d 100644 (file)
@@ -62,6 +62,8 @@ namespace
     ImageFormatInfo(BGRF,    3, sizeof(float) * 3),
     ImageFormatInfo(RGBAF,   4, sizeof(float) * 4),
     ImageFormatInfo(BGRAF,   4, sizeof(float) * 4),
+    ImageFormatInfo(RGF_half,   2, sizeof(uint16_t) * 2),
+    ImageFormatInfo(RGBAF_half, 4, sizeof(uint16_t) * 4),
     CompressedImageFormatInfo(RGB_S3TC_DXT1,  3, 1), // DXT1 uses circa half a byte per pixel (64 bits per 4x4 block)
     CompressedImageFormatInfo(RGBA_S3TC_DXT1, 4, 1),
     CompressedImageFormatInfo(RGBA_S3TC_DXT3, 4, 1), // DXT3/5 uses circa 1 byte per pixel (128 bits per 4x4 block)
@@ -294,6 +296,17 @@ Quantity_ColorRGBA Image_PixMap::PixelColor (const Standard_Integer theX,
       const Image_ColorBGRF& aPixel = Value<Image_ColorBGRF> (theY, theX);
       return Quantity_ColorRGBA (NCollection_Vec4<float> (aPixel.r(), aPixel.g(), aPixel.b(), 1.0f)); // opaque
     }
+    case Image_Format_RGF_half:
+    {
+      const NCollection_Vec2<uint16_t>& aPixel = Value<NCollection_Vec2<uint16_t>> (theY, theX);
+      return Quantity_ColorRGBA (NCollection_Vec4<float> (ConvertFromHalfFloat (aPixel.x()), ConvertFromHalfFloat (aPixel.y()), 0.0f, 1.0f));
+    }
+    case Image_Format_RGBAF_half:
+    {
+      const NCollection_Vec4<uint16_t>& aPixel = Value<NCollection_Vec4<uint16_t>> (theY, theX);
+      return Quantity_ColorRGBA (NCollection_Vec4<float> (ConvertFromHalfFloat (aPixel.r()), ConvertFromHalfFloat (aPixel.g()),
+                                                          ConvertFromHalfFloat (aPixel.b()), ConvertFromHalfFloat (aPixel.a())));
+    }
     case Image_Format_RGBA:
     {
       const Image_ColorRGBA& aPixel = Value<Image_ColorRGBA> (theY, theX);
@@ -440,6 +453,22 @@ void Image_PixMap::SetPixelColor (const Standard_Integer theX,
       aPixel.b() = aColor.b();
       return;
     }
+    case Image_Format_RGF_half:
+    {
+      NCollection_Vec2<uint16_t>& aPixel = ChangeValue<NCollection_Vec2<uint16_t>> (theY, theX);
+      aPixel.x() = ConvertToHalfFloat (aColor.r());
+      aPixel.y() = ConvertToHalfFloat (aColor.g());
+      return;
+    }
+    case Image_Format_RGBAF_half:
+    {
+      NCollection_Vec4<uint16_t>& aPixel = ChangeValue<NCollection_Vec4<uint16_t>> (theY, theX);
+      aPixel.r() = ConvertToHalfFloat (aColor.r());
+      aPixel.g() = ConvertToHalfFloat (aColor.g());
+      aPixel.b() = ConvertToHalfFloat (aColor.b());
+      aPixel.a() = ConvertToHalfFloat (aColor.a());
+      return;
+    }
     case Image_Format_RGBA:
     {
       Image_ColorRGBA& aPixel = ChangeValue<Image_ColorRGBA> (theY, theX);
index a043f8894a7c87f0e276a3d7107045375fe111c1..35e263ad549e23674cad0e60c728f2cdf986b986 100644 (file)
@@ -303,6 +303,36 @@ public: //! @name low-level API for batch-processing (pixels reading / compariso
     return myData.ChangeValue (theRow, theCol);
   }
 
+public:
+
+  //! Convert 16-bit half-float value into 32-bit float (simple conversion).
+  static float ConvertFromHalfFloat (const uint16_t theHalf)
+  {
+    union FloatUint32 { float Float32; uint32_t UInt32; };
+
+    const uint32_t e = (theHalf & 0x7C00) >> 10; // exponent
+    const uint32_t m = (theHalf & 0x03FF) << 13; // mantissa
+    FloatUint32 mf, aRes;
+    mf.Float32 = (float )m;
+    const uint32_t v = mf.UInt32 >> 23; // evil log2 bit hack to count leading zeros in denormalized format
+    aRes.UInt32 = (theHalf & 0x8000)<<16 | (e != 0) * ((e + 112) << 23 | m) | ((e == 0) & (m != 0)) * ((v - 37) << 23 | ((m << (150 - v)) & 0x007FE000)); // sign : normalized : denormalized
+    return aRes.Float32;
+  }
+
+  //! Convert 32-bit float value into IEEE-754 16-bit floating-point format without infinity:
+  //! 1-5-10, exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits.
+  static uint16_t ConvertToHalfFloat (const float theFloat)
+  {
+    union FloatUint32 { float Float32; uint32_t UInt32; };
+    FloatUint32 anInput;
+    anInput.Float32 = theFloat;
+    const uint32_t b = anInput.UInt32 + 0x00001000; // round-to-nearest-even: add last bit after truncated mantissa
+    const uint32_t e = (b & 0x7F800000) >> 23; // exponent
+    const uint32_t m =  b & 0x007FFFFF; // mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding
+    return (uint16_t)((b & 0x80000000) >> 16 | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13)
+         | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) | (e > 143) * 0x7FFF); // sign : normalized : denormalized : saturate
+  }
+
 protected:
 
   Image_PixMapData myData;      //!< data buffer
index f0ad43e27c7c51b3c6ec896abdfee1a28bcdf241..94d8843c529b97a060de132573d295299ad2528a 100644 (file)
@@ -97,6 +97,8 @@ int Media_Frame::FormatOcct2FFmpeg (Image_Format theFormat)
     case Image_Format_RGBF:
     case Image_Format_BGRAF:
     case Image_Format_BGRF:
+    case Image_Format_RGF_half:
+    case Image_Format_RGBAF_half:
     case Image_Format_UNKNOWN:
       return AV_PIX_FMT_NONE; // unsupported
   }
index 6147eaf0e7297fd373db9a8b1cb664c03cd73967..79e87cc18a20eefdf60d1e0c60eb5b707d32c284 100644 (file)
@@ -3389,9 +3389,17 @@ void OpenGl_Context::init (const Standard_Boolean theIsCoreProfile)
     mySupportedFormats->Add (Image_Format_AlphaF);
     mySupportedFormats->Add (Image_Format_RGBF);
     mySupportedFormats->Add (Image_Format_RGBAF);
+    if (hasHalfFloatBuffer)
+    {
+      mySupportedFormats->Add (Image_Format_RGBAF_half);
+    }
     if (arbTexRG)
     {
       mySupportedFormats->Add (Image_Format_RGF);
+      if (hasHalfFloatBuffer)
+      {
+        mySupportedFormats->Add (Image_Format_RGF_half);
+      }
     }
     if (extBgra)
     {
@@ -3442,7 +3450,8 @@ void OpenGl_Context::init (const Standard_Boolean theIsCoreProfile)
           && arbTexFloat
           && (IsGlGreaterEqual (3, 0)
         #if defined(GL_ES_VERSION_2_0)
-          || true // || CheckExtension ("GL_EXT_shader_texture_lod") fallback is used when extension is unavailable
+           || hasHighp
+        // || CheckExtension ("GL_EXT_shader_texture_lod") fallback is used when extension is unavailable
         #else
           || (IsGlGreaterEqual (2, 1) && CheckExtension ("GL_EXT_gpu_shader4"))
         #endif
index 0246700a945b4ce7614f94b2c94d505eb0f0a821..50d47ef364ee419c55812eca1ce5c49d620f3535 100644 (file)
@@ -922,9 +922,14 @@ Standard_Boolean OpenGl_FrameBuffer::BufferDump (const Handle(OpenGl_Context)& t
       aFormat = GL_RGBA;
       aType   = GL_FLOAT;
       break;
+    case Image_Format_RGBAF_half:
+      aFormat = GL_RGBA;
+      aType   = GL_HALF_FLOAT;
+      break;
     case Image_Format_Alpha:
     case Image_Format_AlphaF:
       return Standard_False; // GL_ALPHA is no more supported in core context
+    case Image_Format_RGF_half:
     case Image_Format_UNKNOWN:
       return Standard_False;
   }
index ec07a2c2d2b7ad7b5da3a481e81384e81f0f27e4..be055ae72d2b87a887f091d3a7ec540959453e8f 100644 (file)
@@ -217,6 +217,34 @@ OpenGl_TextureFormat OpenGl_TextureFormat::FindFormat (const Handle(OpenGl_Conte
       return OpenGl_TextureFormat();
     #endif
     }
+    case Image_Format_RGF_half:
+    {
+      aFormat.SetNbComponents (2);
+      aFormat.SetInternalFormat (GL_RG16F);
+      aFormat.SetPixelFormat (GL_RG);
+      aFormat.SetDataType (GL_HALF_FLOAT);
+      if (theCtx->hasHalfFloatBuffer == OpenGl_FeatureInExtensions)
+      {
+      #if defined(GL_ES_VERSION_2_0)
+        aFormat.SetDataType (GL_HALF_FLOAT_OES);
+      #endif
+      }
+      return aFormat;
+    }
+    case Image_Format_RGBAF_half:
+    {
+      aFormat.SetNbComponents (4);
+      aFormat.SetInternalFormat (GL_RGBA16F);
+      aFormat.SetPixelFormat (GL_RGBA);
+      aFormat.SetDataType (GL_HALF_FLOAT);
+      if (theCtx->hasHalfFloatBuffer == OpenGl_FeatureInExtensions)
+      {
+      #if defined(GL_ES_VERSION_2_0)
+        aFormat.SetDataType (GL_HALF_FLOAT_OES);
+      #endif
+      }
+      return aFormat;
+    }
     case Image_Format_RGBA:
     {
       aFormat.SetNbComponents (4);
@@ -442,7 +470,7 @@ OpenGl_TextureFormat OpenGl_TextureFormat::FindSizedFormat (const Handle(OpenGl_
       aFormat.SetInternalFormat (theSizedFormat);
       aFormat.SetPixelFormat (GL_RGBA);
       aFormat.SetDataType (GL_HALF_FLOAT);
-      aFormat.SetImageFormat (Image_Format_RGBAF);
+      aFormat.SetImageFormat (Image_Format_RGBAF_half);
       if (theCtx->hasHalfFloatBuffer == OpenGl_FeatureInExtensions)
       {
       #if defined(GL_ES_VERSION_2_0)
@@ -470,6 +498,23 @@ OpenGl_TextureFormat OpenGl_TextureFormat::FindSizedFormat (const Handle(OpenGl_
       }
       return aFormat;
     }
+    case GL_RG16F:
+    {
+      aFormat.SetNbComponents (2);
+      aFormat.SetInternalFormat (theSizedFormat);
+      aFormat.SetPixelFormat (GL_RG);
+      aFormat.SetDataType (GL_HALF_FLOAT);
+      aFormat.SetImageFormat (Image_Format_RGF_half);
+      if (theCtx->hasHalfFloatBuffer == OpenGl_FeatureInExtensions)
+      {
+      #if defined(GL_ES_VERSION_2_0)
+        aFormat.SetDataType (GL_HALF_FLOAT_OES);
+      #else
+        aFormat.SetDataType (GL_FLOAT);
+      #endif
+      }
+      return aFormat;
+    }
     case GL_SRGB8_ALPHA8:
     case GL_RGBA8:
     case GL_RGBA:
index 4d58b9eb7bc458c9f102943e90d0a10794b5c78d..57af07f5b1c6b968a65f83929af5ce92866174b1 100644 (file)
@@ -1188,41 +1188,70 @@ bool OpenGl_View::prepareFrameBuffers (Graphic3d_Camera::Projection& theProj)
         static const TCollection_AsciiString THE_SHARED_ENV_LUT_KEY("EnvLUT");
         if (!aCtx->GetResource (THE_SHARED_ENV_LUT_KEY, anEnvLUT))
         {
-          Handle(Graphic3d_TextureParams) aParams = new Graphic3d_TextureParams();
-          aParams->SetFilter (Graphic3d_TOTF_BILINEAR);
-          aParams->SetRepeat (Standard_False);
-          aParams->SetTextureUnit (aCtx->PBREnvLUTTexUnit());
-          anEnvLUT = new OpenGl_Texture(THE_SHARED_ENV_LUT_KEY, aParams);
-          Handle(Image_PixMap) aPixMap = new Image_PixMap();
+          bool toConvertHalfFloat = false;
+        #if defined(GL_ES_VERSION_2_0)
+          // GL_RG32F is not texture-filterable format in OpenGL ES without OES_texture_float_linear extension.
+          // GL_RG16F is texture-filterable since OpenGL ES 3.0 or OpenGL ES 2.0 + OES_texture_half_float_linear.
+          // OpenGL ES 3.0 allows initialization of GL_RG16F from 32-bit float data, but OpenGL ES 2.0 + OES_texture_half_float does not.
+          // Note that it is expected that GL_RG16F has enough precision for this table, so that it can be used also on desktop OpenGL.
+          const bool hasHalfFloat = aCtx->IsGlGreaterEqual (3, 0) || aCtx->CheckExtension ("GL_OES_texture_half_float_linear");
+          toConvertHalfFloat = !aCtx->IsGlGreaterEqual (3, 0) && hasHalfFloat;
+        #endif
+          Image_Format anImgFormat = Image_Format_UNKNOWN;
           if (aCtx->arbTexRG)
+          {
+            anImgFormat = toConvertHalfFloat ? Image_Format_RGF_half : Image_Format_RGF;
+          }
+          else
+          {
+            anImgFormat = toConvertHalfFloat ? Image_Format_RGBAF_half : Image_Format_RGBAF;
+          }
+
+          Handle(Image_PixMap) aPixMap = new Image_PixMap();
+          if (anImgFormat == Image_Format_RGF)
           {
             aPixMap->InitWrapper (Image_Format_RGF, (Standard_Byte*)Textures_EnvLUT, Textures_EnvLUTSize, Textures_EnvLUTSize);
           }
           else
           {
+            aPixMap->InitZero (anImgFormat, Textures_EnvLUTSize, Textures_EnvLUTSize);
             Image_PixMap aPixMapRG;
             aPixMapRG.InitWrapper (Image_Format_RGF, (Standard_Byte*)Textures_EnvLUT, Textures_EnvLUTSize, Textures_EnvLUTSize);
-            aPixMap->InitZero (Image_Format_RGBAF, Textures_EnvLUTSize, Textures_EnvLUTSize);
             for (Standard_Size aRowIter = 0; aRowIter < aPixMapRG.SizeY(); ++aRowIter)
             {
               for (Standard_Size aColIter = 0; aColIter < aPixMapRG.SizeX(); ++aColIter)
               {
                 const Image_ColorRGF& aPixelRG = aPixMapRG.Value<Image_ColorRGF> (aRowIter, aColIter);
-                Image_ColorRGBAF& aPixelRGBA = aPixMap->ChangeValue<Image_ColorRGBAF> (aRowIter, aColIter);
-                aPixelRGBA.r() = aPixelRG.r();
-                aPixelRGBA.g() = aPixelRG.g();
+                if (toConvertHalfFloat)
+                {
+                  NCollection_Vec2<uint16_t>& aPixelRGBA = aPixMap->ChangeValue<NCollection_Vec2<uint16_t>> (aRowIter, aColIter);
+                  aPixelRGBA.x() = Image_PixMap::ConvertToHalfFloat (aPixelRG.r());
+                  aPixelRGBA.y() = Image_PixMap::ConvertToHalfFloat (aPixelRG.g());
+                }
+                else
+                {
+                  Image_ColorRGBAF& aPixelRGBA = aPixMap->ChangeValue<Image_ColorRGBAF> (aRowIter, aColIter);
+                  aPixelRGBA.r() = aPixelRG.r();
+                  aPixelRGBA.g() = aPixelRG.g();
+                }
               }
             }
           }
 
           OpenGl_TextureFormat aTexFormat = OpenGl_TextureFormat::FindFormat (aCtx, aPixMap->Format(), false);
         #if defined(GL_ES_VERSION_2_0)
-          // GL_RG32F is not texture-filterable format on OpenGL ES without OES_texture_float_linear extension.
-          // GL_RG16F is texture-filterable since OpenGL ES 3.0 and can be initialized from 32-bit floats.
-          // Note that it is expected that GL_RG16F has enough precision for this table, so that it can be used also on desktop OpenGL.
-          //if (!aCtx->hasTexFloatLinear)
-          aTexFormat.SetInternalFormat (aCtx->arbTexRG ? GL_RG16F : GL_RGBA16F);
+          if (aTexFormat.IsValid()
+           && hasHalfFloat)
+          {
+            aTexFormat.SetInternalFormat (aCtx->arbTexRG ? GL_RG16F : GL_RGBA16F);
+          }
         #endif
+
+          Handle(Graphic3d_TextureParams) aParams = new Graphic3d_TextureParams();
+          aParams->SetFilter (Graphic3d_TOTF_BILINEAR);
+          aParams->SetRepeat (Standard_False);
+          aParams->SetTextureUnit (aCtx->PBREnvLUTTexUnit());
+          anEnvLUT = new OpenGl_Texture(THE_SHARED_ENV_LUT_KEY, aParams);
           if (!aTexFormat.IsValid()
            || !anEnvLUT->Init (aCtx, aTexFormat, Graphic3d_Vec2i((Standard_Integer)Textures_EnvLUTSize), Graphic3d_TOT_2D, aPixMap.get()))
           {