Merge "Cleaned up some of the circular dependency between GLES::Context & GLES::Graph...
[platform/core/uifw/dali-adaptor.git] / dali / internal / graphics / gles-impl / gles-context.cpp
index 077d2c6..b7e2b5e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "gles-context.h"
 #include <dali/integration-api/adaptor-framework/render-surface-interface.h>
+#include <dali/integration-api/debug.h>
 #include <dali/integration-api/gl-abstraction.h>
 #include <dali/integration-api/gl-defines.h>
 #include <dali/internal/graphics/common/graphics-interface.h>
+#include <dali/public-api/math/math-utils.h>
 
 #include "egl-graphics-controller.h"
 #include "gles-graphics-buffer.h"
 #include "gles-graphics-program.h"
 #include "gles-graphics-render-pass.h"
 #include "gles-graphics-render-target.h"
+#include "gles-texture-dependency-checker.h"
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <map>
+#include <unordered_map>
 
 namespace Dali::Graphics::GLES
 {
 struct Context::Impl
 {
-  Impl(EglGraphicsController& controller)
-  : mController(controller)
+  explicit Impl(EglGraphicsController& controller, Integration::GlAbstraction* gl)
+  : mController(controller),
+    mGL(gl)
   {
   }
 
   ~Impl() = default;
 
   /**
+   * Binds (and creates) VAO
+   *
+   * VAO is fixed per program so it has to be created only once assuming
+   * that VertexInputState has been set correctly for the pipeline.
+   *
+   */
+  void BindProgramVAO(const GLES::ProgramImpl* program, const VertexInputState& vertexInputState)
+  {
+    // Calculate attributes location hash unordered.
+    std::size_t hash = 0;
+    for(const auto& attr : vertexInputState.attributes)
+    {
+      // Make unordered hash value by location.
+      // Note : This hash function varified for locations only under < 20.
+      std::size_t salt = attr.location + 1;
+      hash += salt << (sizeof(std::size_t) * 6);
+      salt *= salt;
+      salt ^= attr.location;
+      hash += salt << (sizeof(std::size_t) * 4);
+      salt *= salt;
+      hash += salt;
+    }
+
+    auto* gl = GetGL();
+    if(!gl) // early out if no gl
+    {
+      return;
+    }
+
+    if(DALI_UNLIKELY(!mDiscardedVAOList.empty()))
+    {
+      gl->DeleteVertexArrays(static_cast<Dali::GLsizei>(mDiscardedVAOList.size()), mDiscardedVAOList.data());
+      mDiscardedVAOList.clear();
+    }
+
+    auto iter = mProgramVAOMap.find(program);
+    if(iter != mProgramVAOMap.end())
+    {
+      auto attributeIter = iter->second.find(hash);
+      if(attributeIter != iter->second.end())
+      {
+        if(mProgramVAOCurrentState != attributeIter->second)
+        {
+          mProgramVAOCurrentState = attributeIter->second;
+          gl->BindVertexArray(attributeIter->second);
+
+          // Binding VAO seems to reset the index buffer binding so the cache must be reset
+          mGlStateCache.mBoundElementArrayBufferId = 0;
+        }
+        return;
+      }
+    }
+
+    uint32_t vao;
+    gl->GenVertexArrays(1, &vao);
+    gl->BindVertexArray(vao);
+
+    // Binding VAO seems to reset the index buffer binding so the cache must be reset
+    mGlStateCache.mBoundElementArrayBufferId = 0;
+
+    mProgramVAOMap[program][hash] = vao;
+    for(const auto& attr : vertexInputState.attributes)
+    {
+      gl->EnableVertexAttribArray(attr.location);
+    }
+
+    mProgramVAOCurrentState = vao;
+  }
+
+  /**
    * Sets the initial GL state.
    */
   void InitializeGlState()
   {
-    auto& gl = *mController.GetGL();
+    auto* gl = GetGL();
+    if(gl)
+    {
+      mGlStateCache.mClearColorSet        = false;
+      mGlStateCache.mColorMask            = true;
+      mGlStateCache.mStencilMask          = 0xFF;
+      mGlStateCache.mBlendEnabled         = false;
+      mGlStateCache.mDepthBufferEnabled   = false;
+      mGlStateCache.mDepthMaskEnabled     = false;
+      mGlStateCache.mScissorTestEnabled   = false;
+      mGlStateCache.mStencilBufferEnabled = false;
 
-    mGlStateCache.mClearColorSet        = false;
-    mGlStateCache.mColorMask            = true;
-    mGlStateCache.mStencilMask          = 0xFF;
-    mGlStateCache.mBlendEnabled         = false;
-    mGlStateCache.mDepthBufferEnabled   = false;
-    mGlStateCache.mDepthMaskEnabled     = false;
-    mGlStateCache.mScissorTestEnabled   = false;
-    mGlStateCache.mStencilBufferEnabled = false;
+      gl->Disable(GL_DITHER);
 
-    gl.Disable(GL_DITHER);
+      mGlStateCache.mBoundArrayBufferId        = 0;
+      mGlStateCache.mBoundElementArrayBufferId = 0;
+      mGlStateCache.mActiveTextureUnit         = 0;
 
-    mGlStateCache.mBoundArrayBufferId        = 0;
-    mGlStateCache.mBoundElementArrayBufferId = 0;
-    mGlStateCache.mActiveTextureUnit         = 0;
+      mGlStateCache.mBlendFuncSeparateSrcRGB   = BlendFactor::ONE;
+      mGlStateCache.mBlendFuncSeparateDstRGB   = BlendFactor::ZERO;
+      mGlStateCache.mBlendFuncSeparateSrcAlpha = BlendFactor::ONE;
+      mGlStateCache.mBlendFuncSeparateDstAlpha = BlendFactor::ZERO;
 
-    mGlStateCache.mBlendFuncSeparateSrcRGB   = BlendFactor::ONE;
-    mGlStateCache.mBlendFuncSeparateDstRGB   = BlendFactor::ZERO;
-    mGlStateCache.mBlendFuncSeparateSrcAlpha = BlendFactor::ONE;
-    mGlStateCache.mBlendFuncSeparateDstAlpha = BlendFactor::ZERO;
+      // initial state is GL_FUNC_ADD for both RGB and Alpha blend modes
+      mGlStateCache.mBlendEquationSeparateModeRGB   = BlendOp::ADD;
+      mGlStateCache.mBlendEquationSeparateModeAlpha = BlendOp::ADD;
 
-    // initial state is GL_FUNC_ADD for both RGB and Alpha blend modes
-    mGlStateCache.mBlendEquationSeparateModeRGB   = BlendOp::ADD;
-    mGlStateCache.mBlendEquationSeparateModeAlpha = BlendOp::ADD;
+      mGlStateCache.mCullFaceMode = CullMode::NONE; // By default cullface is disabled, front face is set to CCW and cull face is set to back
 
-    mGlStateCache.mCullFaceMode = CullMode::NONE; //By default cullface is disabled, front face is set to CCW and cull face is set to back
+      // Initialze vertex attribute cache
+      memset(&mGlStateCache.mVertexAttributeCachedState, 0, sizeof(mGlStateCache.mVertexAttributeCachedState));
+      memset(&mGlStateCache.mVertexAttributeCurrentState, 0, sizeof(mGlStateCache.mVertexAttributeCurrentState));
 
-    //Initialze vertex attribute cache
-    memset(&mGlStateCache.mVertexAttributeCachedState, 0, sizeof(mGlStateCache.mVertexAttributeCachedState));
-    memset(&mGlStateCache.mVertexAttributeCurrentState, 0, sizeof(mGlStateCache.mVertexAttributeCurrentState));
+      // Initialize bound 2d texture cache
+      memset(&mGlStateCache.mBoundTextureId, 0, sizeof(mGlStateCache.mBoundTextureId));
 
-    //Initialize bound 2d texture cache
-    memset(&mGlStateCache.mBoundTextureId, 0, sizeof(mGlStateCache.mBoundTextureId));
+      mGlStateCache.mFrameBufferStateCache.Reset();
 
-    mGlStateCache.mFrameBufferStateCache.Reset();
+      GLint maxTextures;
+      gl->GetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextures);
+      DALI_LOG_RELEASE_INFO("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d\n", maxTextures);
+    }
   }
 
   /**
@@ -87,23 +172,25 @@ struct Context::Impl
    */
   void FlushVertexAttributeLocations()
   {
-    auto& gl = *mController.GetGL();
-
-    for(unsigned int i = 0; i < MAX_ATTRIBUTE_CACHE_SIZE; ++i)
+    auto* gl = GetGL();
+    if(gl)
     {
-      // see if the cached state is different to the actual state
-      if(mGlStateCache.mVertexAttributeCurrentState[i] != mGlStateCache.mVertexAttributeCachedState[i])
+      for(unsigned int i = 0; i < MAX_ATTRIBUTE_CACHE_SIZE; ++i)
       {
-        // it's different so make the change to the driver and update the cached state
-        mGlStateCache.mVertexAttributeCurrentState[i] = mGlStateCache.mVertexAttributeCachedState[i];
-
-        if(mGlStateCache.mVertexAttributeCurrentState[i])
-        {
-          gl.EnableVertexAttribArray(i);
-        }
-        else
+        // see if the cached state is different to the actual state
+        if(mGlStateCache.mVertexAttributeCurrentState[i] != mGlStateCache.mVertexAttributeCachedState[i])
         {
-          gl.DisableVertexAttribArray(i);
+          // it's different so make the change to the driver and update the cached state
+          mGlStateCache.mVertexAttributeCurrentState[i] = mGlStateCache.mVertexAttributeCachedState[i];
+
+          if(mGlStateCache.mVertexAttributeCurrentState[i])
+          {
+            gl->EnableVertexAttribArray(i);
+          }
+          else
+          {
+            gl->DisableVertexAttribArray(i);
+          }
         }
       }
     }
@@ -117,29 +204,41 @@ struct Context::Impl
    */
   void SetVertexAttributeLocation(unsigned int location, bool state)
   {
-    auto& gl = *mController.GetGL();
-
-    if(location >= MAX_ATTRIBUTE_CACHE_SIZE)
+    auto* gl = GetGL();
+    if(gl)
     {
-      // not cached, make the gl call through context
-      if(state)
+      if(location >= MAX_ATTRIBUTE_CACHE_SIZE)
       {
-        gl.EnableVertexAttribArray(location);
+        // not cached, make the gl call through context
+        if(state)
+        {
+          gl->EnableVertexAttribArray(location);
+        }
+        else
+        {
+          gl->DisableVertexAttribArray(location);
+        }
       }
       else
       {
-        gl.DisableVertexAttribArray(location);
+        // set the cached state, it will be set at the next draw call
+        // if it's different from the current driver state
+        mGlStateCache.mVertexAttributeCachedState[location] = state;
       }
     }
-    else
-    {
-      // set the cached state, it will be set at the next draw call
-      // if it's different from the current driver state
-      mGlStateCache.mVertexAttributeCachedState[location] = state;
-    }
   }
 
-  EglGraphicsController& mController;
+  /**
+   * Get the pointer to the GL implementation
+   * @return The GL implementation, nullptr if the context has not been created or shutting down
+   */
+  [[nodiscard]] inline Integration::GlAbstraction* GetGL() const
+  {
+    return mGlContextCreated ? mGL : nullptr;
+  }
+
+  EglGraphicsController&      mController;
+  Integration::GlAbstraction* mGL{nullptr};
 
   const GLES::PipelineImpl* mCurrentPipeline{nullptr}; ///< Currently bound pipeline
   const GLES::PipelineImpl* mNewPipeline{nullptr};     ///< New pipeline to be set on flush
@@ -165,21 +264,46 @@ struct Context::Impl
   const GLES::RenderTarget* mCurrentRenderTarget{nullptr};
   const GLES::RenderPass*   mCurrentRenderPass{nullptr};
 
-  GLStateCache mGlStateCache{}; ///< GL status cache
+  // Each context must have own VAOs as they cannot be shared
+  std::unordered_map<const GLES::ProgramImpl*, std::map<std::size_t, uint32_t>> mProgramVAOMap;              ///< GL program-VAO map
+  uint32_t                                                                      mProgramVAOCurrentState{0u}; ///< Currently bound VAO
+  GLStateCache                                                                  mGlStateCache{};             ///< GL status cache
+  std::vector<Dali::GLuint>                                                     mDiscardedVAOList{};
+
+  bool mGlContextCreated{false};    ///< True if the OpenGL context has been created
+  bool mVertexBuffersChanged{true}; ///< True if BindVertexBuffers changed any buffer bindings
+
+  EGLContext mNativeDrawContext{0u}; ///< Native rendering EGL context compatible with window context
 
-  bool mGlContextCreated{false}; ///< True if the OpenGL context has been created
+  EGLSurface mCacheDrawReadSurface{0u};    ///< cached 'read' surface
+  EGLSurface mCacheDrawWriteSurface{0u};   ///< cached 'write' surface
+  EGLContext mCacheEGLGraphicsContext{0u}; ///< cached window context
 };
 
-Context::Context(EglGraphicsController& controller)
+Context::Context(EglGraphicsController& controller, Integration::GlAbstraction* glAbstraction)
 {
-  mImpl = std::make_unique<Impl>(controller);
+  mImpl = std::make_unique<Impl>(controller, glAbstraction);
 }
 
-Context::~Context() = default;
+Context::~Context()
+{
+  // Destroy native rendering context if one exists
+  if(mImpl->mNativeDrawContext)
+  {
+    eglDestroyContext(eglGetCurrentDisplay(), mImpl->mNativeDrawContext);
+    mImpl->mNativeDrawContext = EGL_NO_CONTEXT;
+  }
+}
 
-void Context::Flush(bool reset, const GLES::DrawCallDescriptor& drawCall)
+void Context::Flush(bool reset, const GLES::DrawCallDescriptor& drawCall, GLES::TextureDependencyChecker& dependencyChecker)
 {
-  auto& gl = *mImpl->mController.GetGL();
+  auto* gl = mImpl->GetGL();
+  if(!gl) // Early out if no gl
+  {
+    return;
+  }
+
+  static const bool hasGLES3(mImpl->mController.GetGLESVersion() >= GLESVersion::GLES_30);
 
   // early out if neither current nor new pipelines are set
   // this behaviour may be valid so no assert
@@ -199,11 +323,23 @@ void Context::Flush(bool reset, const GLES::DrawCallDescriptor& drawCall)
     newProgram = static_cast<const GLES::Program*>(mImpl->mNewPipeline->GetCreateInfo().programState->program);
   }
 
+  if(!currentProgram && !newProgram)
+  {
+    // Early out if we have no program for this pipeline.
+    DALI_LOG_ERROR("No program defined for pipeline\n");
+    return;
+  }
+
+  // If this draw uses a different pipeline _AND_ the pipeline has a different GL Program,
+  // Then bind the new program. Ensure vertex atrributes are set.
+
+  bool programChanged = false;
   if(mImpl->mNewPipeline && mImpl->mCurrentPipeline != mImpl->mNewPipeline)
   {
     if(!currentProgram || currentProgram->GetImplementation()->GetGlProgram() != newProgram->GetImplementation()->GetGlProgram())
     {
       mImpl->mNewPipeline->Bind(newProgram->GetImplementation()->GetGlProgram());
+      programChanged = true;
     }
 
     // Blend state
@@ -220,59 +356,117 @@ void Context::Flush(bool reset, const GLES::DrawCallDescriptor& drawCall)
   // Map binding# to sampler location
   const auto& reflection = !newProgram ? currentProgram->GetReflection() : newProgram->GetReflection();
   const auto& samplers   = reflection.GetSamplers();
+
+  uint32_t currentSampler = 0;
+  uint32_t currentElement = 0;
+
+  // @warning Assume that binding.binding is strictly linear in the same order as mCurrentTextureBindings
+  // elements. This avoids having to sort the bindings.
   for(const auto& binding : mImpl->mCurrentTextureBindings)
   {
+    if(currentSampler >= samplers.size())
+    {
+      // Don't bind more textures than there are active samplers.
+      break;
+    }
+
     auto texture = const_cast<GLES::Texture*>(static_cast<const GLES::Texture*>(binding.texture));
 
     // Texture may not have been initialized yet...(tbm_surface timing issue?)
     if(!texture->GetGLTexture())
     {
-      // Attempt to reinitialize
-      // @todo need to put this somewhere else where it isn't const.
-      // Maybe post it back on end of initialize queue if initialization fails?
       texture->InitializeResource();
     }
 
+    // Warning, this may cause glWaitSync to occur on the GPU.
+    dependencyChecker.CheckNeedsSync(this, texture);
     texture->Bind(binding);
+    texture->Prepare();
 
-    texture->Prepare(); // @todo also non-const.
-
-    if(binding.binding < samplers.size()) // binding maps to texture unit. (texture bindings should also be in binding order)
+    if(programChanged)
     {
-      // Offset is set to the lexical offset within the frag shader, map it to the texture unit
-      // @todo Explicitly set the texture unit through the graphics interface
-      gl.Uniform1i(samplers[binding.binding].location, samplers[binding.binding].offset);
+      // @warning Assume that location of array elements is sequential.
+      // @warning GL does not guarantee this, but in practice, it is.
+      gl->Uniform1i(samplers[currentSampler].location + currentElement,
+                    samplers[currentSampler].offset + currentElement);
+      ++currentElement;
+      if(currentElement >= samplers[currentSampler].elementCount)
+      {
+        ++currentSampler;
+        currentElement = 0;
+      }
     }
   }
 
-  // for each attribute bind vertices
-  const auto& pipelineState = mImpl->mNewPipeline ? mImpl->mNewPipeline->GetCreateInfo() : mImpl->mCurrentPipeline->GetCreateInfo();
-  const auto& vi            = pipelineState.vertexInputState;
-  for(const auto& attr : vi->attributes)
+  const auto& pipelineState    = mImpl->mNewPipeline ? mImpl->mNewPipeline->GetCreateInfo() : mImpl->mCurrentPipeline->GetCreateInfo();
+  const auto& vertexInputState = pipelineState.vertexInputState;
+
+  // for each attribute bind vertices, unless the pipeline+buffer is the same
+  if(programChanged || mImpl->mVertexBuffersChanged)
   {
-    // Enable location
-    mImpl->SetVertexAttributeLocation(attr.location, true);
+    if(hasGLES3)
+    {
+      mImpl->BindProgramVAO(static_cast<const GLES::Program*>(pipelineState.programState->program)->GetImplementation(), *vertexInputState);
+    }
+
+    for(const auto& attr : vertexInputState->attributes)
+    {
+      // Enable location
+      if(!hasGLES3)
+      {
+        mImpl->SetVertexAttributeLocation(attr.location, true);
+      }
 
-    const auto& bufferSlot    = mImpl->mCurrentVertexBufferBindings[attr.binding];
-    const auto& bufferBinding = vi->bufferBindings[attr.binding];
+      const auto& bufferSlot    = mImpl->mCurrentVertexBufferBindings[attr.binding];
+      const auto& bufferBinding = vertexInputState->bufferBindings[attr.binding];
 
-    auto glesBuffer = bufferSlot.buffer->GetGLBuffer();
+      auto glesBuffer = bufferSlot.buffer->GetGLBuffer();
 
-    // Bind buffer
-    BindBuffer(GL_ARRAY_BUFFER, glesBuffer);
+      BindBuffer(GL_ARRAY_BUFFER, glesBuffer); // Cached
 
-    gl.VertexAttribPointer(attr.location,
-                           GLVertexFormat(attr.format).size,
-                           GLVertexFormat(attr.format).format,
-                           GL_FALSE,
-                           bufferBinding.stride,
-                           reinterpret_cast<void*>(attr.offset));
+      if(attr.format == VertexInputFormat::FLOAT ||
+         attr.format == VertexInputFormat::FVECTOR2 ||
+         attr.format == VertexInputFormat::FVECTOR3 ||
+         attr.format == VertexInputFormat::FVECTOR4)
+      {
+        gl->VertexAttribPointer(attr.location, // Not cached...
+                                GLVertexFormat(attr.format).size,
+                                GLVertexFormat(attr.format).format,
+                                GL_FALSE,
+                                bufferBinding.stride,
+                                reinterpret_cast<void*>(attr.offset));
+      }
+      else
+      {
+        gl->VertexAttribIPointer(attr.location,
+                                 GLVertexFormat(attr.format).size,
+                                 GLVertexFormat(attr.format).format,
+                                 bufferBinding.stride,
+                                 reinterpret_cast<void*>(attr.offset));
+      }
+
+      if(hasGLES3)
+      {
+        switch(bufferBinding.inputRate)
+        {
+          case Graphics::VertexInputRate::PER_VERTEX:
+          {
+            gl->VertexAttribDivisor(attr.location, 0);
+            break;
+          }
+          case Graphics::VertexInputRate::PER_INSTANCE:
+          {
+            //@todo Get actual instance rate...
+            gl->VertexAttribDivisor(attr.location, 1);
+            break;
+          }
+        }
+      }
+    }
   }
 
   // Resolve topology
-  const auto& ia = mImpl->mNewPipeline->GetCreateInfo().inputAssemblyState;
-
-  // Bind uniforms
+  const auto& ia = pipelineState.inputAssemblyState;
 
   // Resolve draw call
   switch(drawCall.type)
@@ -282,11 +476,25 @@ void Context::Flush(bool reset, const GLES::DrawCallDescriptor& drawCall)
       mImpl->mGlStateCache.mFrameBufferStateCache.DrawOperation(mImpl->mGlStateCache.mColorMask,
                                                                 mImpl->mGlStateCache.DepthBufferWriteEnabled(),
                                                                 mImpl->mGlStateCache.StencilBufferWriteEnabled());
-      mImpl->FlushVertexAttributeLocations();
+      // For GLES3+ we use VAO, for GLES2 internal cache
+      if(!hasGLES3)
+      {
+        mImpl->FlushVertexAttributeLocations();
+      }
 
-      gl.DrawArrays(GLESTopology(ia->topology),
-                    drawCall.draw.firstVertex,
-                    drawCall.draw.vertexCount);
+      if(drawCall.draw.instanceCount == 0)
+      {
+        gl->DrawArrays(GLESTopology(ia->topology),
+                       drawCall.draw.firstVertex,
+                       drawCall.draw.vertexCount);
+      }
+      else
+      {
+        gl->DrawArraysInstanced(GLESTopology(ia->topology),
+                                drawCall.draw.firstVertex,
+                                drawCall.draw.vertexCount,
+                                drawCall.draw.instanceCount);
+      }
       break;
     }
     case DrawCallDescriptor::Type::DRAW_INDEXED:
@@ -297,13 +505,29 @@ void Context::Flush(bool reset, const GLES::DrawCallDescriptor& drawCall)
       mImpl->mGlStateCache.mFrameBufferStateCache.DrawOperation(mImpl->mGlStateCache.mColorMask,
                                                                 mImpl->mGlStateCache.DepthBufferWriteEnabled(),
                                                                 mImpl->mGlStateCache.StencilBufferWriteEnabled());
-      mImpl->FlushVertexAttributeLocations();
+
+      // For GLES3+ we use VAO, for GLES2 internal cache
+      if(!hasGLES3)
+      {
+        mImpl->FlushVertexAttributeLocations();
+      }
 
       auto indexBufferFormat = GLIndexFormat(binding.format).format;
-      gl.DrawElements(GLESTopology(ia->topology),
-                      drawCall.drawIndexed.indexCount,
-                      indexBufferFormat,
-                      reinterpret_cast<void*>(binding.offset));
+      if(drawCall.drawIndexed.instanceCount == 0)
+      {
+        gl->DrawElements(GLESTopology(ia->topology),
+                         drawCall.drawIndexed.indexCount,
+                         indexBufferFormat,
+                         reinterpret_cast<void*>(binding.offset));
+      }
+      else
+      {
+        gl->DrawElementsInstanced(GLESTopology(ia->topology),
+                                  drawCall.drawIndexed.indexCount,
+                                  indexBufferFormat,
+                                  reinterpret_cast<void*>(binding.offset),
+                                  drawCall.drawIndexed.instanceCount);
+      }
       break;
     }
     case DrawCallDescriptor::Type::DRAW_INDEXED_INDIRECT:
@@ -322,11 +546,13 @@ void Context::Flush(bool reset, const GLES::DrawCallDescriptor& drawCall)
   }
 }
 
-void Context::BindTextures(const std::vector<Graphics::TextureBinding>& bindings)
+void Context::BindTextures(const Graphics::TextureBinding* bindings, uint32_t count)
 {
   // for each texture allocate slot
-  for(const auto& binding : bindings)
+  for(auto i = 0u; i < count; ++i)
   {
+    auto& binding = bindings[i];
+
     // Resize binding array if needed
     if(mImpl->mCurrentTextureBindings.size() <= binding.binding)
     {
@@ -337,16 +563,26 @@ void Context::BindTextures(const std::vector<Graphics::TextureBinding>& bindings
   }
 }
 
-void Context::BindVertexBuffers(const std::vector<GLES::VertexBufferBindingDescriptor>& bindings)
+void Context::BindVertexBuffers(const GLES::VertexBufferBindingDescriptor* bindings, uint32_t count)
 {
-  if(bindings.size() > mImpl->mCurrentVertexBufferBindings.size())
+  if(count > mImpl->mCurrentVertexBufferBindings.size())
   {
-    mImpl->mCurrentVertexBufferBindings.resize(bindings.size());
+    mImpl->mCurrentVertexBufferBindings.resize(count);
   }
   // Copy only set slots
-  std::copy_if(bindings.begin(), bindings.end(), mImpl->mCurrentVertexBufferBindings.begin(), [](auto& item) {
-    return (nullptr != item.buffer);
-  });
+  mImpl->mVertexBuffersChanged = false;
+  auto toIter                  = mImpl->mCurrentVertexBufferBindings.begin();
+  for(auto fromIter = bindings, end = bindings + count; fromIter != end; ++fromIter)
+  {
+    if(fromIter->buffer != nullptr)
+    {
+      if(toIter->buffer != fromIter->buffer || toIter->offset != fromIter->offset)
+      {
+        mImpl->mVertexBuffersChanged = true;
+      }
+      *toIter++ = *fromIter;
+    }
+  }
 }
 
 void Context::BindIndexBuffer(const IndexBufferBindingDescriptor& indexBufferBinding)
@@ -360,26 +596,28 @@ void Context::BindPipeline(const GLES::Pipeline* newPipeline)
   mImpl->mNewPipeline = &newPipeline->GetPipeline();
 }
 
-void Context::BindUniformBuffers(const std::vector<UniformBufferBindingDescriptor>& uboBindings,
-                                 const UniformBufferBindingDescriptor&              standaloneBindings)
+void Context::BindUniformBuffers(const UniformBufferBindingDescriptor* uboBindings,
+                                 uint32_t                              uboCount,
+                                 const UniformBufferBindingDescriptor& standaloneBindings)
 {
   if(standaloneBindings.buffer)
   {
     mImpl->mCurrentStandaloneUBOBinding = standaloneBindings;
   }
 
-  if(uboBindings.size() >= mImpl->mCurrentUBOBindings.size())
+  if(uboCount && uboCount > mImpl->mCurrentUBOBindings.size())
   {
-    mImpl->mCurrentUBOBindings.resize(uboBindings.size() + 1);
+    mImpl->mCurrentUBOBindings.resize(uboCount);
   }
 
-  auto it = uboBindings.begin();
-  for(auto i = 0u; i < uboBindings.size(); ++i)
+  auto it = uboBindings;
+  for(auto i = 0u; i < uboCount; ++i)
   {
     if(it->buffer)
     {
       mImpl->mCurrentUBOBindings[i] = *it;
     }
+    ++it;
   }
 }
 
@@ -394,14 +632,18 @@ void Context::ResolveBlendState()
     return;
   }
 
-  auto& gl = *mImpl->mController.GetGL();
+  auto* gl = mImpl->GetGL();
+  if(!gl) // Early out if no gl
+  {
+    return;
+  }
 
   if(!currentBlendState || currentBlendState->blendEnable != newBlendState->blendEnable)
   {
     if(newBlendState->blendEnable != mImpl->mGlStateCache.mBlendEnabled)
     {
       mImpl->mGlStateCache.mBlendEnabled = newBlendState->blendEnable;
-      newBlendState->blendEnable ? gl.Enable(GL_BLEND) : gl.Disable(GL_BLEND);
+      newBlendState->blendEnable ? gl->Enable(GL_BLEND) : gl->Disable(GL_BLEND);
     }
   }
 
@@ -433,11 +675,11 @@ void Context::ResolveBlendState()
 
       if(newSrcRGB == newSrcAlpha && newDstRGB == newDstAlpha)
       {
-        gl.BlendFunc(GLBlendFunc(newSrcRGB), GLBlendFunc(newDstRGB));
+        gl->BlendFunc(GLBlendFunc(newSrcRGB), GLBlendFunc(newDstRGB));
       }
       else
       {
-        gl.BlendFuncSeparate(GLBlendFunc(newSrcRGB), GLBlendFunc(newDstRGB), GLBlendFunc(newSrcAlpha), GLBlendFunc(newDstAlpha));
+        gl->BlendFuncSeparate(GLBlendFunc(newSrcRGB), GLBlendFunc(newDstRGB), GLBlendFunc(newSrcAlpha), GLBlendFunc(newDstAlpha));
       }
     }
   }
@@ -454,11 +696,15 @@ void Context::ResolveBlendState()
 
       if(newBlendState->colorBlendOp == newBlendState->alphaBlendOp)
       {
-        gl.BlendEquation(GLBlendOp(newBlendState->colorBlendOp));
+        gl->BlendEquation(GLBlendOp(newBlendState->colorBlendOp));
+        if(newBlendState->colorBlendOp >= Graphics::ADVANCED_BLEND_OPTIONS_START)
+        {
+          gl->BlendBarrier();
+        }
       }
       else
       {
-        gl.BlendEquationSeparate(GLBlendOp(newBlendState->colorBlendOp), GLBlendOp(newBlendState->alphaBlendOp));
+        gl->BlendEquationSeparate(GLBlendOp(newBlendState->colorBlendOp), GLBlendOp(newBlendState->alphaBlendOp));
       }
     }
   }
@@ -475,7 +721,11 @@ void Context::ResolveRasterizationState()
     return;
   }
 
-  auto& gl = *mImpl->mController.GetGL();
+  auto* gl = mImpl->GetGL();
+  if(!gl) // Early out if no gl
+  {
+    return;
+  }
 
   if(!currentRasterizationState ||
      currentRasterizationState->cullMode != newRasterizationState->cullMode)
@@ -485,12 +735,12 @@ void Context::ResolveRasterizationState()
       mImpl->mGlStateCache.mCullFaceMode = newRasterizationState->cullMode;
       if(newRasterizationState->cullMode == CullMode::NONE)
       {
-        gl.Disable(GL_CULL_FACE);
+        gl->Disable(GL_CULL_FACE);
       }
       else
       {
-        gl.Enable(GL_CULL_FACE);
-        gl.CullFace(GLCullMode(newRasterizationState->cullMode));
+        gl->Enable(GL_CULL_FACE);
+        gl->CullFace(GLCullMode(newRasterizationState->cullMode));
       }
     }
   }
@@ -505,109 +755,43 @@ void Context::ResolveUniformBuffers()
   {
     ResolveStandaloneUniforms();
   }
+  if(!mImpl->mCurrentUBOBindings.empty())
+  {
+    ResolveGpuUniformBuffers();
+  }
 }
 
-void Context::ResolveStandaloneUniforms()
+void Context::ResolveGpuUniformBuffers()
 {
-  auto& gl = *mImpl->mController.GetGL();
+  if(auto* gl = mImpl->GetGL())
+  {
+    auto i = 0u;
+    for(auto& binding : mImpl->mCurrentUBOBindings)
+    {
+      gl->BindBufferRange(GL_UNIFORM_BUFFER, i++, binding.buffer->GetGLBuffer(), GLintptr(binding.offset), GLintptr(binding.dataSize));
+    }
+  }
+}
 
+void Context::ResolveStandaloneUniforms()
+{
   // Find reflection for program
-  const auto program = static_cast<const GLES::Program*>(mImpl->mNewPipeline->GetCreateInfo().programState->program);
-
-  const auto& reflection = program->GetReflection();
+  const GLES::Program* program{nullptr};
 
-  auto extraInfos = reflection.GetStandaloneUniformExtraInfo();
-
-  const auto ptr = reinterpret_cast<const char*>(mImpl->mCurrentStandaloneUBOBinding.buffer->GetCPUAllocatedAddress()) + mImpl->mCurrentStandaloneUBOBinding.offset;
+  if(mImpl->mNewPipeline)
+  {
+    program = static_cast<const GLES::Program*>(mImpl->mNewPipeline->GetCreateInfo().programState->program);
+  }
+  else if(mImpl->mCurrentPipeline)
+  {
+    program = static_cast<const GLES::Program*>(mImpl->mCurrentPipeline->GetCreateInfo().programState->program);
+  }
 
-  for(const auto& info : extraInfos)
+  if(program)
   {
-    auto type   = GLTypeConversion(info.type).type;
-    auto offset = info.offset;
-    switch(type)
-    {
-      case GLType::FLOAT_VEC2:
-      {
-        gl.Uniform2fv(info.location, info.arraySize, reinterpret_cast<const float*>(&ptr[offset]));
-        break;
-      }
-      case GLType::FLOAT_VEC3:
-      {
-        gl.Uniform3fv(info.location, info.arraySize, reinterpret_cast<const float*>(&ptr[offset]));
-        break;
-      }
-      case GLType::FLOAT_VEC4:
-      {
-        gl.Uniform4fv(info.location, info.arraySize, reinterpret_cast<const float*>(&ptr[offset]));
-        break;
-      }
-      case GLType::INT_VEC2:
-      {
-        gl.Uniform2iv(info.location, info.arraySize, reinterpret_cast<const GLint*>(&ptr[offset]));
-        break;
-      }
-      case GLType::INT_VEC3:
-      {
-        gl.Uniform3iv(info.location, info.arraySize, reinterpret_cast<const GLint*>(&ptr[offset]));
-        break;
-      }
-      case GLType::INT_VEC4:
-      {
-        gl.Uniform4iv(info.location, info.arraySize, reinterpret_cast<const GLint*>(&ptr[offset]));
-        break;
-      }
-      case GLType::BOOL:
-      {
-        // not supported by DALi
-        break;
-      }
-      case GLType::BOOL_VEC2:
-      {
-        // not supported by DALi
-        break;
-      }
-      case GLType::BOOL_VEC3:
-      {
-        // not supported by DALi
-        break;
-      }
-      case GLType::BOOL_VEC4:
-      {
-        // not supported by DALi
-        break;
-      }
-      case GLType::FLOAT:
-      {
-        gl.Uniform1fv(info.location, info.arraySize, reinterpret_cast<const float*>(&ptr[offset]));
-        break;
-      }
-      case GLType::FLOAT_MAT2:
-      {
-        gl.UniformMatrix2fv(info.location, info.arraySize, GL_FALSE, reinterpret_cast<const float*>(&ptr[offset]));
-        break;
-      }
-      case GLType::FLOAT_MAT3:
-      {
-        gl.UniformMatrix3fv(info.location, info.arraySize, GL_FALSE, reinterpret_cast<const float*>(&ptr[offset]));
-        break;
-      }
-      case GLType::FLOAT_MAT4:
-      {
-        gl.UniformMatrix4fv(info.location, info.arraySize, GL_FALSE, reinterpret_cast<const float*>(&ptr[offset]));
-        break;
-      }
-      case GLType::SAMPLER_2D:
-      {
-        break;
-      }
-      case GLType::SAMPLER_CUBE:
-      {
-        break;
-      }
-      default:
-      {
-      }
-    }
+    const auto ptr = reinterpret_cast<const char*>(mImpl->mCurrentStandaloneUBOBinding.buffer->GetCPUAllocatedAddress()) + mImpl->mCurrentStandaloneUBOBinding.offset;
+    // Update program uniforms
+    program->GetImplementation()->UpdateStandaloneUniformBlock(ptr);
   }
 }
 
@@ -618,7 +802,11 @@ void Context::BeginRenderPass(const BeginRenderPassDescriptor& renderPassBegin)
 
   const auto& targetInfo = renderTarget.GetCreateInfo();
 
-  auto& gl = *mImpl->mController.GetGL();
+  auto* gl = mImpl->GetGL();
+  if(!gl) // Early out if no gl
+  {
+    return;
+  }
 
   if(targetInfo.surface)
   {
@@ -628,7 +816,8 @@ void Context::BeginRenderPass(const BeginRenderPassDescriptor& renderPassBegin)
   else if(targetInfo.framebuffer)
   {
     // bind framebuffer and swap.
-    renderTarget.GetFramebuffer()->Bind();
+    auto framebuffer = renderTarget.GetFramebuffer();
+    framebuffer->Bind();
   }
 
   // clear (ideally cache the setup)
@@ -640,6 +829,7 @@ void Context::BeginRenderPass(const BeginRenderPassDescriptor& renderPassBegin)
   const auto& attachments = *renderPass.GetCreateInfo().attachments;
   const auto& color0      = attachments[0];
   GLuint      mask        = 0;
+
   if(color0.loadOp == AttachmentLoadOp::CLEAR)
   {
     mask |= GL_COLOR_BUFFER_BIT;
@@ -648,22 +838,24 @@ void Context::BeginRenderPass(const BeginRenderPassDescriptor& renderPassBegin)
     // Something goes wrong here if Alpha mask is GL_TRUE
     ColorMask(true);
 
-    if(!mImpl->mGlStateCache.mClearColorSet ||
-       mImpl->mGlStateCache.mClearColor.r != renderPassBegin.clearValues[0].color.r ||
-       mImpl->mGlStateCache.mClearColor.g != renderPassBegin.clearValues[0].color.g ||
-       mImpl->mGlStateCache.mClearColor.b != renderPassBegin.clearValues[0].color.b ||
-       mImpl->mGlStateCache.mClearColor.a != renderPassBegin.clearValues[0].color.a)
+    const auto clearValues = renderPassBegin.clearValues.Ptr();
+
+    if(!Dali::Equals(mImpl->mGlStateCache.mClearColor.r, clearValues[0].color.r) ||
+       !Dali::Equals(mImpl->mGlStateCache.mClearColor.g, clearValues[0].color.g) ||
+       !Dali::Equals(mImpl->mGlStateCache.mClearColor.b, clearValues[0].color.b) ||
+       !Dali::Equals(mImpl->mGlStateCache.mClearColor.a, clearValues[0].color.a) ||
+       !mImpl->mGlStateCache.mClearColorSet)
     {
-      gl.ClearColor(renderPassBegin.clearValues[0].color.r,
-                    renderPassBegin.clearValues[0].color.g,
-                    renderPassBegin.clearValues[0].color.b,
-                    renderPassBegin.clearValues[0].color.a);
+      gl->ClearColor(clearValues[0].color.r,
+                     clearValues[0].color.g,
+                     clearValues[0].color.b,
+                     clearValues[0].color.a);
 
       mImpl->mGlStateCache.mClearColorSet = true;
-      mImpl->mGlStateCache.mClearColor    = Vector4(renderPassBegin.clearValues[0].color.r,
-                                                 renderPassBegin.clearValues[0].color.g,
-                                                 renderPassBegin.clearValues[0].color.b,
-                                                 renderPassBegin.clearValues[0].color.a);
+      mImpl->mGlStateCache.mClearColor    = Vector4(clearValues[0].color.r,
+                                                 clearValues[0].color.g,
+                                                 clearValues[0].color.b,
+                                                 clearValues[0].color.a);
     }
   }
 
@@ -676,7 +868,7 @@ void Context::BeginRenderPass(const BeginRenderPassDescriptor& renderPassBegin)
       if(!mImpl->mGlStateCache.mDepthMaskEnabled)
       {
         mImpl->mGlStateCache.mDepthMaskEnabled = true;
-        gl.DepthMask(true);
+        gl->DepthMask(true);
       }
       mask |= GL_DEPTH_BUFFER_BIT;
     }
@@ -685,14 +877,14 @@ void Context::BeginRenderPass(const BeginRenderPassDescriptor& renderPassBegin)
       if(mImpl->mGlStateCache.mStencilMask != 0xFF)
       {
         mImpl->mGlStateCache.mStencilMask = 0xFF;
-        gl.StencilMask(0xFF);
+        gl->StencilMask(0xFF);
       }
       mask |= GL_STENCIL_BUFFER_BIT;
     }
   }
 
   SetScissorTestEnabled(true);
-  gl.Scissor(renderPassBegin.renderArea.x, renderPassBegin.renderArea.y, renderPassBegin.renderArea.width, renderPassBegin.renderArea.height);
+  gl->Scissor(renderPassBegin.renderArea.x, renderPassBegin.renderArea.y, renderPassBegin.renderArea.width, renderPassBegin.renderArea.height);
   ClearBuffer(mask, true);
   SetScissorTestEnabled(false);
 
@@ -700,14 +892,28 @@ void Context::BeginRenderPass(const BeginRenderPassDescriptor& renderPassBegin)
   mImpl->mCurrentRenderTarget = &renderTarget;
 }
 
-void Context::EndRenderPass()
+void Context::EndRenderPass(GLES::TextureDependencyChecker& dependencyChecker)
 {
   if(mImpl->mCurrentRenderTarget)
   {
-    if(mImpl->mCurrentRenderTarget->GetFramebuffer())
+    GLES::Framebuffer* framebuffer = mImpl->mCurrentRenderTarget->GetFramebuffer();
+    auto*              gl          = mImpl->GetGL();
+    if(framebuffer && gl)
     {
-      auto& gl = *mImpl->mController.GetGL();
-      gl.Flush();
+      gl->Flush();
+
+      /* @todo Full dependency checking would need to store textures in Begin, and create
+       * fence objects here; but we're going to draw all fbos on shared context in serial,
+       * so no real need (yet). Might want to consider ensuring order of render passes,
+       * but that needs doing in the controller, and would need doing before ProcessCommandQueues.
+       *
+       * Currently up to the client to create render tasks in the right order.
+       */
+
+      /* Create fence sync objects. Other contexts can then wait on these fences before reading
+       * textures.
+       */
+      dependencyChecker.AddTextures(this, framebuffer);
     }
   }
 }
@@ -715,16 +921,16 @@ void Context::EndRenderPass()
 void Context::ClearState()
 {
   mImpl->mCurrentTextureBindings.clear();
+  mImpl->mCurrentUBOBindings.clear();
 }
 
 void Context::ColorMask(bool enabled)
 {
-  if(enabled != mImpl->mGlStateCache.mColorMask)
+  auto* gl = mImpl->GetGL();
+  if(gl && enabled != mImpl->mGlStateCache.mColorMask)
   {
     mImpl->mGlStateCache.mColorMask = enabled;
-
-    auto& gl = *mImpl->mController.GetGL();
-    gl.ColorMask(enabled, enabled, enabled, enabled);
+    gl->ColorMask(enabled, enabled, enabled, enabled);
   }
 }
 
@@ -740,58 +946,67 @@ void Context::ClearDepthBuffer()
 
 void Context::ClearBuffer(uint32_t mask, bool forceClear)
 {
-  mask = mImpl->mGlStateCache.mFrameBufferStateCache.GetClearMask(mask, forceClear, mImpl->mGlStateCache.mScissorTestEnabled);
-  if(mask > 0)
+  mask     = mImpl->mGlStateCache.mFrameBufferStateCache.GetClearMask(mask, forceClear, mImpl->mGlStateCache.mScissorTestEnabled);
+  auto* gl = mImpl->GetGL();
+  if(mask > 0 && gl)
+  {
+    gl->Clear(mask);
+  }
+}
+
+void Context::InvalidateDepthStencilBuffers()
+{
+  if(auto* gl = mImpl->GetGL())
   {
-    auto& gl = *mImpl->mController.GetGL();
-    gl.Clear(mask);
+    GLenum attachments[] = {GL_DEPTH, GL_STENCIL};
+    gl->InvalidateFramebuffer(GL_FRAMEBUFFER, 2, attachments);
   }
 }
 
 void Context::SetScissorTestEnabled(bool scissorEnabled)
 {
-  if(mImpl->mGlStateCache.mScissorTestEnabled != scissorEnabled)
+  auto* gl = mImpl->GetGL();
+  if(gl && mImpl->mGlStateCache.mScissorTestEnabled != scissorEnabled)
   {
     mImpl->mGlStateCache.mScissorTestEnabled = scissorEnabled;
 
-    auto& gl = *mImpl->mController.GetGL();
     if(scissorEnabled)
     {
-      gl.Enable(GL_SCISSOR_TEST);
+      gl->Enable(GL_SCISSOR_TEST);
     }
     else
     {
-      gl.Disable(GL_SCISSOR_TEST);
+      gl->Disable(GL_SCISSOR_TEST);
     }
   }
 }
 
 void Context::SetStencilTestEnable(bool stencilEnable)
 {
-  if(stencilEnable != mImpl->mGlStateCache.mStencilBufferEnabled)
+  auto* gl = mImpl->GetGL();
+  if(gl && stencilEnable != mImpl->mGlStateCache.mStencilBufferEnabled)
   {
     mImpl->mGlStateCache.mStencilBufferEnabled = stencilEnable;
 
-    auto& gl = *mImpl->mController.GetGL();
     if(stencilEnable)
     {
-      gl.Enable(GL_STENCIL_TEST);
+      gl->Enable(GL_STENCIL_TEST);
     }
     else
     {
-      gl.Disable(GL_STENCIL_TEST);
+      gl->Disable(GL_STENCIL_TEST);
     }
   }
 }
 
 void Context::StencilMask(uint32_t writeMask)
 {
-  if(writeMask != mImpl->mGlStateCache.mStencilMask)
+  auto* gl = mImpl->GetGL();
+  if(gl && writeMask != mImpl->mGlStateCache.mStencilMask)
   {
     mImpl->mGlStateCache.mStencilMask = writeMask;
 
-    auto& gl = *mImpl->mController.GetGL();
-    gl.StencilMask(writeMask);
+    gl->StencilMask(writeMask);
   }
 }
 
@@ -799,16 +1014,17 @@ void Context::StencilFunc(Graphics::CompareOp compareOp,
                           uint32_t            reference,
                           uint32_t            compareMask)
 {
-  if(compareOp != mImpl->mGlStateCache.mStencilFunc ||
-     reference != mImpl->mGlStateCache.mStencilFuncRef ||
-     compareMask != mImpl->mGlStateCache.mStencilFuncMask)
+  auto* gl = mImpl->GetGL();
+  if(gl &&
+     (compareOp != mImpl->mGlStateCache.mStencilFunc ||
+      reference != mImpl->mGlStateCache.mStencilFuncRef ||
+      compareMask != mImpl->mGlStateCache.mStencilFuncMask))
   {
     mImpl->mGlStateCache.mStencilFunc     = compareOp;
     mImpl->mGlStateCache.mStencilFuncRef  = reference;
     mImpl->mGlStateCache.mStencilFuncMask = compareMask;
 
-    auto& gl = *mImpl->mController.GetGL();
-    gl.StencilFunc(GLCompareOp(compareOp).op, reference, compareMask);
+    gl->StencilFunc(GLCompareOp(compareOp).op, reference, compareMask);
   }
 }
 
@@ -816,130 +1032,163 @@ void Context::StencilOp(Graphics::StencilOp failOp,
                         Graphics::StencilOp depthFailOp,
                         Graphics::StencilOp passOp)
 {
-  if(failOp != mImpl->mGlStateCache.mStencilOpFail ||
-     depthFailOp != mImpl->mGlStateCache.mStencilOpDepthFail ||
-     passOp != mImpl->mGlStateCache.mStencilOpDepthPass)
+  auto* gl = mImpl->GetGL();
+  if(gl &&
+     (failOp != mImpl->mGlStateCache.mStencilOpFail ||
+      depthFailOp != mImpl->mGlStateCache.mStencilOpDepthFail ||
+      passOp != mImpl->mGlStateCache.mStencilOpDepthPass))
   {
     mImpl->mGlStateCache.mStencilOpFail      = failOp;
     mImpl->mGlStateCache.mStencilOpDepthFail = depthFailOp;
     mImpl->mGlStateCache.mStencilOpDepthPass = passOp;
 
-    auto& gl = *mImpl->mController.GetGL();
-    gl.StencilOp(GLStencilOp(failOp).op, GLStencilOp(depthFailOp).op, GLStencilOp(passOp).op);
+    gl->StencilOp(GLStencilOp(failOp).op, GLStencilOp(depthFailOp).op, GLStencilOp(passOp).op);
   }
 }
 
 void Context::SetDepthCompareOp(Graphics::CompareOp compareOp)
 {
-  if(compareOp != mImpl->mGlStateCache.mDepthFunction)
+  auto* gl = mImpl->GetGL();
+  if(gl && compareOp != mImpl->mGlStateCache.mDepthFunction)
   {
     mImpl->mGlStateCache.mDepthFunction = compareOp;
-    auto& gl                            = *mImpl->mController.GetGL();
-    gl.DepthFunc(GLCompareOp(compareOp).op);
+
+    gl->DepthFunc(GLCompareOp(compareOp).op);
   }
 }
 
 void Context::SetDepthTestEnable(bool depthTestEnable)
 {
-  if(depthTestEnable != mImpl->mGlStateCache.mDepthBufferEnabled)
+  auto* gl = mImpl->GetGL();
+  if(gl && depthTestEnable != mImpl->mGlStateCache.mDepthBufferEnabled)
   {
     mImpl->mGlStateCache.mDepthBufferEnabled = depthTestEnable;
 
-    auto& gl = *mImpl->mController.GetGL();
     if(depthTestEnable)
     {
-      gl.Enable(GL_DEPTH_TEST);
+      gl->Enable(GL_DEPTH_TEST);
     }
     else
     {
-      gl.Disable(GL_DEPTH_TEST);
+      gl->Disable(GL_DEPTH_TEST);
     }
   }
 }
 
 void Context::SetDepthWriteEnable(bool depthWriteEnable)
 {
-  if(depthWriteEnable != mImpl->mGlStateCache.mDepthMaskEnabled)
+  auto* gl = mImpl->GetGL();
+  if(gl && depthWriteEnable != mImpl->mGlStateCache.mDepthMaskEnabled)
   {
     mImpl->mGlStateCache.mDepthMaskEnabled = depthWriteEnable;
 
-    auto& gl = *mImpl->mController.GetGL();
-    gl.DepthMask(depthWriteEnable);
+    gl->DepthMask(depthWriteEnable);
   }
 }
 
 void Context::ActiveTexture(uint32_t textureBindingIndex)
 {
-  if(mImpl->mGlStateCache.mActiveTextureUnit != textureBindingIndex)
+  auto* gl = mImpl->GetGL();
+  if(gl && mImpl->mGlStateCache.mActiveTextureUnit != textureBindingIndex)
   {
     mImpl->mGlStateCache.mActiveTextureUnit = textureBindingIndex;
 
-    auto& gl = *mImpl->mController.GetGL();
-    gl.ActiveTexture(GL_TEXTURE0 + textureBindingIndex);
+    gl->ActiveTexture(GL_TEXTURE0 + textureBindingIndex);
   }
 }
 
 void Context::BindTexture(GLenum target, BoundTextureType textureTypeId, uint32_t textureId)
 {
   uint32_t typeId = static_cast<uint32_t>(textureTypeId);
-  if(mImpl->mGlStateCache.mBoundTextureId[mImpl->mGlStateCache.mActiveTextureUnit][typeId] != textureId)
+  auto*    gl     = mImpl->GetGL();
+  if(gl && mImpl->mGlStateCache.mBoundTextureId[mImpl->mGlStateCache.mActiveTextureUnit][typeId] != textureId)
   {
     mImpl->mGlStateCache.mBoundTextureId[mImpl->mGlStateCache.mActiveTextureUnit][typeId] = textureId;
 
-    auto& gl = *mImpl->mController.GetGL();
-    gl.BindTexture(target, textureId);
+    gl->BindTexture(target, textureId);
   }
 }
 
 void Context::GenerateMipmap(GLenum target)
 {
-  auto& gl = *mImpl->mController.GetGL();
-  gl.GenerateMipmap(target);
+  if(auto* gl = mImpl->GetGL())
+  {
+    gl->GenerateMipmap(target);
+  }
 }
 
-void Context::BindBuffer(GLenum target, uint32_t bufferId)
+bool Context::BindBuffer(GLenum target, uint32_t bufferId)
 {
-  if(mImpl->mGlStateCache.mBoundArrayBufferId != bufferId)
+  if(auto* gl = mImpl->GetGL())
   {
-    mImpl->mGlStateCache.mBoundArrayBufferId = bufferId;
+    switch(target)
+    {
+      case GL_ARRAY_BUFFER:
+      {
+        if(mImpl->mGlStateCache.mBoundArrayBufferId == bufferId)
+        {
+          return false;
+        }
+        mImpl->mGlStateCache.mBoundArrayBufferId = bufferId;
+        break;
+      }
+      case GL_ELEMENT_ARRAY_BUFFER:
+      {
+        if(mImpl->mGlStateCache.mBoundElementArrayBufferId == bufferId)
+        {
+          return false;
+        }
+        mImpl->mGlStateCache.mBoundElementArrayBufferId = bufferId;
+        break;
+      }
+    }
 
-    auto& gl = *mImpl->mController.GetGL();
-    gl.BindBuffer(target, bufferId);
+    // Cache miss. Bind buffer.
+    gl->BindBuffer(target, bufferId);
+    return true;
   }
+  return false;
 }
 
 void Context::DrawBuffers(uint32_t count, const GLenum* buffers)
 {
-  mImpl->mGlStateCache.mFrameBufferStateCache.DrawOperation(mImpl->mGlStateCache.mColorMask,
-                                                            mImpl->mGlStateCache.DepthBufferWriteEnabled(),
-                                                            mImpl->mGlStateCache.StencilBufferWriteEnabled());
+  if(auto* gl = mImpl->GetGL())
+  {
+    mImpl->mGlStateCache.mFrameBufferStateCache.DrawOperation(mImpl->mGlStateCache.mColorMask,
+                                                              mImpl->mGlStateCache.DepthBufferWriteEnabled(),
+                                                              mImpl->mGlStateCache.StencilBufferWriteEnabled());
 
-  auto& gl = *mImpl->mController.GetGL();
-  gl.DrawBuffers(count, buffers);
+    gl->DrawBuffers(count, buffers);
+  }
 }
 
 void Context::BindFrameBuffer(GLenum target, uint32_t bufferId)
 {
-  mImpl->mGlStateCache.mFrameBufferStateCache.SetCurrentFrameBuffer(bufferId);
+  if(auto* gl = mImpl->GetGL())
+  {
+    mImpl->mGlStateCache.mFrameBufferStateCache.SetCurrentFrameBuffer(bufferId);
 
-  auto& gl = *mImpl->mController.GetGL();
-  gl.BindFramebuffer(target, bufferId);
+    gl->BindFramebuffer(target, bufferId);
+  }
 }
 
 void Context::GenFramebuffers(uint32_t count, uint32_t* framebuffers)
 {
-  auto& gl = *mImpl->mController.GetGL();
-  gl.GenFramebuffers(count, framebuffers);
-
-  mImpl->mGlStateCache.mFrameBufferStateCache.FrameBuffersCreated(count, framebuffers);
+  if(auto* gl = mImpl->GetGL())
+  {
+    gl->GenFramebuffers(count, framebuffers);
+    mImpl->mGlStateCache.mFrameBufferStateCache.FrameBuffersCreated(count, framebuffers);
+  }
 }
 
 void Context::DeleteFramebuffers(uint32_t count, uint32_t* framebuffers)
 {
-  mImpl->mGlStateCache.mFrameBufferStateCache.FrameBuffersDeleted(count, framebuffers);
+  if(auto* gl = mImpl->GetGL())
+  {
+    mImpl->mGlStateCache.mFrameBufferStateCache.FrameBuffersDeleted(count, framebuffers);
 
-  auto& gl = *mImpl->mController.GetGL();
-  gl.DeleteFramebuffers(count, framebuffers);
+    gl->DeleteFramebuffers(count, framebuffers);
+  }
 }
 
 GLStateCache& Context::GetGLStateCache()
@@ -970,6 +1219,103 @@ void Context::InvalidateCachedPipeline(GLES::Pipeline* pipeline)
   {
     mImpl->mCurrentPipeline = nullptr;
   }
+
+  // Remove cached VAO map
+  auto* gl = mImpl->GetGL();
+  if(gl)
+  {
+    const auto* program = pipeline->GetCreateInfo().programState->program;
+    if(program)
+    {
+      const auto* programImpl = static_cast<const GLES::Program*>(program)->GetImplementation();
+      if(programImpl)
+      {
+        auto iter = mImpl->mProgramVAOMap.find(programImpl);
+        if(iter != mImpl->mProgramVAOMap.end())
+        {
+          for(auto& attributeHashPair : iter->second)
+          {
+            auto vao = attributeHashPair.second;
+
+            // Do not delete vao now. (Since Context might not be current.)
+            mImpl->mDiscardedVAOList.emplace_back(vao);
+            if(mImpl->mProgramVAOCurrentState == vao)
+            {
+              mImpl->mProgramVAOCurrentState = 0u;
+            }
+          }
+
+          // Clear cached Vertex buffer.
+          mImpl->mCurrentVertexBufferBindings.clear();
+
+          mImpl->mProgramVAOMap.erase(iter);
+        }
+      }
+    }
+  }
+}
+
+void Context::PrepareForNativeRendering()
+{
+  // this should be pretty much constant
+  auto display     = eglGetCurrentDisplay();
+  auto drawSurface = eglGetCurrentSurface(EGL_DRAW);
+  auto readSurface = eglGetCurrentSurface(EGL_READ);
+  auto context     = eglGetCurrentContext();
+
+  // push the surface and context data to the impl
+  // It's needed to restore context
+  if(!mImpl->mCacheEGLGraphicsContext)
+  {
+    mImpl->mCacheDrawWriteSurface   = drawSurface;
+    mImpl->mCacheDrawReadSurface    = readSurface;
+    mImpl->mCacheEGLGraphicsContext = context;
+  }
+
+  if(!mImpl->mNativeDrawContext)
+  {
+    EGLint configId{0u};
+    eglQueryContext(display, mImpl->mController.GetSharedContext(), EGL_CONFIG_ID, &configId);
+
+    EGLint configAttribs[3];
+    configAttribs[0] = EGL_CONFIG_ID;
+    configAttribs[1] = configId;
+    configAttribs[2] = EGL_NONE;
+
+    EGLConfig config;
+    EGLint    numConfigs;
+    if(eglChooseConfig(display, configAttribs, &config, 1, &numConfigs) != EGL_TRUE)
+    {
+      DALI_LOG_ERROR("eglChooseConfig failed!\n");
+      return;
+    }
+
+    auto version = int(mImpl->mController.GetGLESVersion());
+
+    std::vector<EGLint> attribs;
+    attribs.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
+    attribs.push_back(version / 10);
+    attribs.push_back(EGL_CONTEXT_MINOR_VERSION_KHR);
+    attribs.push_back(version % 10);
+    attribs.push_back(EGL_NONE);
+
+    mImpl->mNativeDrawContext = eglCreateContext(display, config, mImpl->mController.GetSharedContext(), attribs.data());
+    if(mImpl->mNativeDrawContext == EGL_NO_CONTEXT)
+    {
+      DALI_LOG_ERROR("eglCreateContext failed!\n");
+      return;
+    }
+  }
+
+  eglMakeCurrent(display, drawSurface, readSurface, mImpl->mNativeDrawContext);
+}
+
+void Context::RestoreFromNativeRendering()
+{
+  auto display = eglGetCurrentDisplay();
+
+  // bring back original context
+  eglMakeCurrent(display, mImpl->mCacheDrawWriteSurface, mImpl->mCacheDrawReadSurface, mImpl->mCacheEGLGraphicsContext);
 }
 
 } // namespace Dali::Graphics::GLES