diff --git a/src/OpenGl/OpenGl_Context.cxx b/src/OpenGl/OpenGl_Context.cxx
index 4111e96990..9ae0f0ec41 100644
--- a/src/OpenGl/OpenGl_Context.cxx
+++ b/src/OpenGl/OpenGl_Context.cxx
@@ -65,6 +65,7 @@ IMPLEMENT_STANDARD_RTTIEXT(OpenGl_Context,Standard_Transient)
 #endif
 
 #ifdef __EMSCRIPTEN__
+  #include <emscripten.h>
   #include <emscripten/html5.h>
 
   //! Check if WebGL extension is available and activate it
@@ -138,6 +139,7 @@ OpenGl_Context::OpenGl_Context (const Handle(OpenGl_Caps)& theCaps)
   core45     (NULL),
   core45back (NULL),
   caps   (!theCaps.IsNull() ? theCaps : new OpenGl_Caps()),
+  hasGetBufferData (Standard_False),
 #if defined(GL_ES_VERSION_2_0)
   hasHighp   (Standard_False),
   hasUintIndex(Standard_False),
@@ -1677,6 +1679,7 @@ void OpenGl_Context::init (const Standard_Boolean theIsCoreProfile)
   {
     core30    = (OpenGl_GlCore30*    )(&(*myFuncs));
     core30fwd = (OpenGl_GlCore30Fwd* )(&(*myFuncs));
+    hasGetBufferData = true;
   }
 
   // load OpenGL ES 3.1 new functions
@@ -2183,6 +2186,7 @@ void OpenGl_Context::init (const Standard_Boolean theIsCoreProfile)
       core15 = (OpenGl_GlCore15* )(&(*myFuncs));
     }
     core15fwd = (OpenGl_GlCore15Fwd* )(&(*myFuncs));
+    hasGetBufferData = true;
   }
   else
   {
@@ -4612,6 +4616,36 @@ bool OpenGl_Context::SetSampleAlphaToCoverage (bool theToEnable)
   return anOldValue;
 }
 
+// =======================================================================
+// function : GetBufferSubData
+// purpose  :
+// =======================================================================
+bool OpenGl_Context::GetBufferSubData (GLenum theTarget, GLintptr theOffset, GLsizeiptr theSize, void* theData)
+{
+  if (!hasGetBufferData)
+  {
+    return false;
+  }
+#ifdef __EMSCRIPTEN__
+  EM_ASM_(
+  {
+    Module.ctx.getBufferSubData($0, $1, HEAPU8.subarray($2, $2 + $3));
+  }, theTarget, theOffset, theData, theSize);
+  return true;
+#elif defined(GL_ES_VERSION_2_0)
+  if (void* aData = core30fwd->glMapBufferRange (theTarget, theOffset, theSize, GL_MAP_READ_BIT))
+  {
+    memcpy (theData, aData, theSize);
+    core30fwd->glUnmapBuffer (theTarget);
+    return true;
+  }
+  return false;
+#else
+  core15fwd->glGetBufferSubData (theTarget, theOffset, theSize, theData);
+  return true;
+#endif
+}
+
 // =======================================================================
 // function : DumpJson
 // purpose  :
diff --git a/src/OpenGl/OpenGl_Context.hxx b/src/OpenGl/OpenGl_Context.hxx
index fd16e49e20..aed802a306 100644
--- a/src/OpenGl/OpenGl_Context.hxx
+++ b/src/OpenGl/OpenGl_Context.hxx
@@ -987,6 +987,17 @@ public: //! @name methods to alter or retrieve current state
   //! Set line feater width.
   void SetLineFeather(Standard_ShortReal theValue) { myLineFeather = theValue; }
 
+  //! Wrapper over glGetBufferSubData(), implemented as:
+  //! - OpenGL 1.5+ (desktop) via glGetBufferSubData();
+  //! - OpenGL ES 3.0+ via glMapBufferRange();
+  //! - WebGL 2.0+ via gl.getBufferSubData().
+  //! @param theTarget [in] target buffer to map
+  //! @param theOffset [in] offset to the beginning of sub-data
+  //! @param theSize   [in] number of bytes to read
+  //! @param theData  [out] destination pointer to fill
+  //! @return FALSE if functionality is unavailable
+  Standard_EXPORT bool GetBufferSubData (GLenum theTarget, GLintptr theOffset, GLsizeiptr theSize, void* theData);
+
   //! Return Graphics Driver's vendor.
   const TCollection_AsciiString& Vendor() const { return myVendor; }
 
@@ -1044,6 +1055,7 @@ public: //! @name core profiles
 
 public: //! @name extensions
 
+  Standard_Boolean       hasGetBufferData;   //!< flag indicating if GetBufferSubData() is supported
   Standard_Boolean       hasHighp;           //!< highp in GLSL ES fragment shader is supported
   Standard_Boolean       hasUintIndex;       //!< GLuint for index buffer is supported (always available on desktop; on OpenGL ES - since 3.0 or as extension GL_OES_element_index_uint)
   Standard_Boolean       hasTexRGBA8;        //!< always available on desktop; on OpenGL ES - since 3.0 or as extension GL_OES_rgb8_rgba8
diff --git a/src/OpenGl/OpenGl_GlCore30.hxx b/src/OpenGl/OpenGl_GlCore30.hxx
index cede88fad2..f9cb9ad8b9 100644
--- a/src/OpenGl/OpenGl_GlCore30.hxx
+++ b/src/OpenGl/OpenGl_GlCore30.hxx
@@ -132,6 +132,18 @@ public: //! @name OpenGL 3.0 additives to 2.1
   using theBaseClass_t::glVertexAttribI4ubv;
   using theBaseClass_t::glVertexAttribI4usv;
 #endif
+
+#if defined(GL_ES_VERSION_2_0)
+  // the following functions from OpenGL 1.5 have been added only in OpenGL ES 3.0
+  using theBaseClass_t::glGenQueries;
+  using theBaseClass_t::glDeleteQueries;
+  using theBaseClass_t::glIsQuery;
+  using theBaseClass_t::glBeginQuery;
+  using theBaseClass_t::glEndQuery;
+  using theBaseClass_t::glGetQueryiv;
+  using theBaseClass_t::glGetQueryObjectuiv;
+  using theBaseClass_t::glUnmapBuffer;
+#endif
 };
 
 //! OpenGL 3.0 core based on 2.1 version.
diff --git a/src/OpenGl/OpenGl_VertexBuffer.cxx b/src/OpenGl/OpenGl_VertexBuffer.cxx
index 1727817753..35b3621f99 100644
--- a/src/OpenGl/OpenGl_VertexBuffer.cxx
+++ b/src/OpenGl/OpenGl_VertexBuffer.cxx
@@ -160,6 +160,33 @@ bool OpenGl_VertexBuffer::subData (const Handle(OpenGl_Context)& theGlCtx,
   return isDone;
 }
 
+// =======================================================================
+// function : getSubData
+// purpose  :
+// =======================================================================
+bool OpenGl_VertexBuffer::getSubData (const Handle(OpenGl_Context)& theGlCtx,
+                                      const GLsizei theElemFrom,
+                                      const GLsizei theElemsNb,
+                                      void*         theData,
+                                      const GLenum  theDataType)
+{
+  if (!IsValid() || myDataType != theDataType
+   || theElemFrom < 0 || ((theElemFrom + theElemsNb) > myElemsNb)
+   || !theGlCtx->hasGetBufferData)
+  {
+    return false;
+  }
+
+  Bind (theGlCtx);
+  const size_t  aDataSize = sizeOfGlType (theDataType);
+  const GLintptr anOffset = GLintptr (theElemFrom) * GLintptr  (myComponentsNb) * aDataSize;
+  const GLsizeiptr  aSize = GLsizeiptr(theElemsNb) * GLsizeiptr(myComponentsNb) * aDataSize;
+  bool isDone = theGlCtx->GetBufferSubData (GetTarget(), anOffset, aSize, theData);
+  isDone = isDone && (glGetError() == GL_NO_ERROR);
+  Unbind (theGlCtx);
+  return isDone;
+}
+
 // =======================================================================
 // function : BindVertexAttrib
 // purpose  :
diff --git a/src/OpenGl/OpenGl_VertexBuffer.hxx b/src/OpenGl/OpenGl_VertexBuffer.hxx
index 72f72327b1..65c13890ee 100644
--- a/src/OpenGl/OpenGl_VertexBuffer.hxx
+++ b/src/OpenGl/OpenGl_VertexBuffer.hxx
@@ -154,6 +154,20 @@ public:
     return subData (theGlCtx, theElemFrom, theElemsNb, theData, GL_FLOAT);
   }
 
+  //! Read back buffer sub-range.
+  //! Notice that VBO will be unbound after this call.
+  //! Function reads portion of data from this VBO using glGetBufferSubData().
+  //! @param theElemFrom [in] element id from which replace buffer data (>=0);
+  //! @param theElemsNb  [in] elements count (theElemFrom + theElemsNb <= GetElemsNb());
+  //! @param theData    [out] destination pointer to GLfloat data.
+  bool GetSubData (const Handle(OpenGl_Context)& theGlCtx,
+                   const GLsizei theElemFrom,
+                   const GLsizei theElemsNb,
+                   GLfloat* theData)
+  {
+    return getSubData (theGlCtx, theElemFrom, theElemsNb, theData, GL_FLOAT);
+  }
+
   //! Notice that VBO will be unbound after this call.
   //! Function replaces portion of data within this VBO using glBufferSubData().
   //! The VBO should be initialized before call.
@@ -168,6 +182,20 @@ public:
     return subData (theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_INT);
   }
 
+  //! Read back buffer sub-range.
+  //! Notice that VBO will be unbound after this call.
+  //! Function reads portion of data from this VBO using glGetBufferSubData().
+  //! @param theElemFrom [in] element id from which replace buffer data (>=0);
+  //! @param theElemsNb  [in] elements count (theElemFrom + theElemsNb <= GetElemsNb());
+  //! @param theData    [out] destination pointer to GLuint data.
+  bool GetSubData (const Handle(OpenGl_Context)& theGlCtx,
+                   const GLsizei theElemFrom,
+                   const GLsizei theElemsNb,
+                   GLuint* theData)
+  {
+    return getSubData (theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_INT);
+  }
+
   //! Notice that VBO will be unbound after this call.
   //! Function replaces portion of data within this VBO using glBufferSubData().
   //! The VBO should be initialized before call.
@@ -182,6 +210,20 @@ public:
     return subData (theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_SHORT);
   }
 
+  //! Read back buffer sub-range.
+  //! Notice that VBO will be unbound after this call.
+  //! Function reads portion of data from this VBO using glGetBufferSubData().
+  //! @param theElemFrom [in] element id from which replace buffer data (>=0);
+  //! @param theElemsNb  [in] elements count (theElemFrom + theElemsNb <= GetElemsNb());
+  //! @param theData    [out] destination pointer to GLushort data.
+  bool GetSubData (const Handle(OpenGl_Context)& theGlCtx,
+                   const GLsizei theElemFrom,
+                   const GLsizei theElemsNb,
+                   GLushort* theData)
+  {
+    return getSubData (theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_SHORT);
+  }
+
   //! Notice that VBO will be unbound after this call.
   //! Function replaces portion of data within this VBO using glBufferSubData().
   //! The VBO should be initialized before call.
@@ -196,6 +238,20 @@ public:
     return subData (theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_BYTE);
   }
 
+  //! Read back buffer sub-range.
+  //! Notice that VBO will be unbound after this call.
+  //! Function reads portion of data from this VBO using glGetBufferSubData().
+  //! @param theElemFrom [in] element id from which replace buffer data (>=0);
+  //! @param theElemsNb  [in] elements count (theElemFrom + theElemsNb <= GetElemsNb());
+  //! @param theData    [out] destination pointer to GLubyte data.
+  bool GetSubData (const Handle(OpenGl_Context)& theGlCtx,
+                   const GLsizei theElemFrom,
+                   const GLsizei theElemsNb,
+                   GLubyte* theData)
+  {
+    return getSubData (theGlCtx, theElemFrom, theElemsNb, theData, GL_UNSIGNED_BYTE);
+  }
+
   //! Bind this VBO to active GLSL program.
   Standard_EXPORT void BindVertexAttrib (const Handle(OpenGl_Context)& theGlCtx,
                                          const GLuint                  theAttribLoc) const;
@@ -286,6 +342,13 @@ public: //! @name advanced methods
                                         const void*    theData,
                                         const GLenum   theDataType);
 
+  //! Read back buffer sub-range.
+  Standard_EXPORT virtual bool getSubData (const Handle(OpenGl_Context)& theGlCtx,
+                                           const GLsizei theElemFrom,
+                                           const GLsizei theElemsNb,
+                                           void*         theData,
+                                           const GLenum  theDataType);
+
   //! Setup array pointer - either for active GLSL program OpenGl_Context::ActiveProgram()
   //! or for FFP using bindFixed() when no program bound.
   static void bindAttribute (const Handle(OpenGl_Context)&   theGlCtx,
diff --git a/src/OpenGl/OpenGl_VertexBufferCompat.cxx b/src/OpenGl/OpenGl_VertexBufferCompat.cxx
index 1b11c3b26f..2ba9a1d1b4 100644
--- a/src/OpenGl/OpenGl_VertexBufferCompat.cxx
+++ b/src/OpenGl/OpenGl_VertexBufferCompat.cxx
@@ -175,3 +175,27 @@ bool OpenGl_VertexBufferCompat::subData (const Handle(OpenGl_Context)& ,
   memcpy (myData->ChangeData() + anOffset, theData, aNbBytes);
   return true;
 }
+
+// =======================================================================
+// function : getSubData
+// purpose  :
+// =======================================================================
+bool OpenGl_VertexBufferCompat::getSubData (const Handle(OpenGl_Context)& ,
+                                            const GLsizei theElemFrom,
+                                            const GLsizei theElemsNb,
+                                            void* theData,
+                                            const GLenum  theDataType)
+{
+  if (!IsValid() || myDataType != theDataType
+   || theElemFrom < 0 || ((theElemFrom + theElemsNb) > myElemsNb)
+   || theData == NULL)
+  {
+    return false;
+  }
+
+  const size_t aDataSize = sizeOfGlType (theDataType);
+  const size_t anOffset  = size_t(theElemFrom) * size_t(myComponentsNb) * aDataSize;
+  const size_t aNbBytes  = size_t(theElemsNb)  * size_t(myComponentsNb) * aDataSize;
+  memcpy (theData, myData->Data() + anOffset, aNbBytes);
+  return true;
+}
diff --git a/src/OpenGl/OpenGl_VertexBufferCompat.hxx b/src/OpenGl/OpenGl_VertexBufferCompat.hxx
index 331dbe6ebb..f7c395702b 100644
--- a/src/OpenGl/OpenGl_VertexBufferCompat.hxx
+++ b/src/OpenGl/OpenGl_VertexBufferCompat.hxx
@@ -82,6 +82,13 @@ public: //! @name advanced methods
                                         const void*    theData,
                                         const GLenum   theDataType) Standard_OVERRIDE;
 
+  //! Read back buffer sub-range.
+  Standard_EXPORT virtual bool getSubData (const Handle(OpenGl_Context)& theGlCtx,
+                                           const GLsizei theElemFrom,
+                                           const GLsizei theElemsNb,
+                                           void* theData,
+                                           const GLenum  theDataType) Standard_OVERRIDE;
+
 protected:
 
   Handle(NCollection_Buffer) myData; //!< buffer data