Texture mipmap support
[platform/core/uifw/dali-toolkit.git] / automated-tests / src / dali-toolkit / dali-toolkit-test-utils / test-graphics-controller.cpp
index e7f80cd..e5db6ed 100644 (file)
 
 #include "test-graphics-controller.h"
 
-#include "dali-test-suite-utils.h"
 #include "test-graphics-buffer.h"
 #include "test-graphics-command-buffer.h"
+#include "test-graphics-framebuffer.h"
+#include "test-graphics-reflection.h"
+#include "test-graphics-render-pass.h"
+#include "test-graphics-render-target.h"
 #include "test-graphics-sampler.h"
+#include "test-graphics-shader.h"
 #include "test-graphics-texture.h"
 
 #include <dali/integration-api/gl-defines.h>
+#include <cstdio>
 #include <iostream>
 #include <sstream>
 
@@ -141,6 +146,314 @@ std::ostream& operator<<(std::ostream& o, const Graphics::SamplerCreateInfo& cre
   return o;
 }
 
+std::ostream& operator<<(std::ostream& o, const Graphics::ColorAttachment& colorAttachment)
+{
+  o << "attachmentId:" << colorAttachment.attachmentId
+    << " layerId:" << colorAttachment.layerId
+    << " levelId:" << colorAttachment.levelId
+    << " texture:" << colorAttachment.texture;
+  return o;
+}
+
+std::ostream& operator<<(std::ostream& o, const Graphics::DepthStencilAttachment& depthStencilAttachment)
+{
+  o << "depthTexture:" << depthStencilAttachment.depthTexture
+    << "depthLevel:" << depthStencilAttachment.depthLevel
+    << "stencilTexture:" << depthStencilAttachment.stencilTexture
+    << "stencilLevel:" << depthStencilAttachment.stencilLevel;
+  return o;
+}
+
+std::ostream& operator<<(std::ostream& o, const Graphics::FramebufferCreateInfo& createInfo)
+{
+  o << "colorAttachments:";
+  for(auto i = 0u; i < createInfo.colorAttachments.size(); ++i)
+  {
+    o << "[" << i << "]=" << createInfo.colorAttachments[i] << "  ";
+  }
+  o << "depthStencilAttachment:" << createInfo.depthStencilAttachment;
+  o << "size: " << createInfo.size;
+  return o;
+}
+
+int GetNumComponents(Graphics::VertexInputFormat vertexFormat)
+{
+  switch(vertexFormat)
+  {
+    case Graphics::VertexInputFormat::UNDEFINED:
+    case Graphics::VertexInputFormat::FLOAT:
+    case Graphics::VertexInputFormat::INTEGER:
+      return 1;
+    case Graphics::VertexInputFormat::IVECTOR2:
+    case Graphics::VertexInputFormat::FVECTOR2:
+      return 2;
+    case Graphics::VertexInputFormat::IVECTOR3:
+    case Graphics::VertexInputFormat::FVECTOR3:
+      return 3;
+    case Graphics::VertexInputFormat::FVECTOR4:
+    case Graphics::VertexInputFormat::IVECTOR4:
+      return 4;
+  }
+  return 1;
+}
+
+GLint GetSize(Graphics::VertexInputFormat vertexFormat)
+{
+  switch(vertexFormat)
+  {
+    case Graphics::VertexInputFormat::UNDEFINED:
+      return 1u;
+    case Graphics::VertexInputFormat::INTEGER:
+    case Graphics::VertexInputFormat::IVECTOR2:
+    case Graphics::VertexInputFormat::IVECTOR3:
+    case Graphics::VertexInputFormat::IVECTOR4:
+      return 2u;
+    case Graphics::VertexInputFormat::FLOAT:
+    case Graphics::VertexInputFormat::FVECTOR2:
+    case Graphics::VertexInputFormat::FVECTOR3:
+    case Graphics::VertexInputFormat::FVECTOR4:
+      return 4u;
+  }
+  return 1u;
+}
+
+GLint GetGlType(Graphics::VertexInputFormat vertexFormat)
+{
+  switch(vertexFormat)
+  {
+    case Graphics::VertexInputFormat::UNDEFINED:
+      return GL_BYTE;
+    case Graphics::VertexInputFormat::INTEGER:
+    case Graphics::VertexInputFormat::IVECTOR2:
+    case Graphics::VertexInputFormat::IVECTOR3:
+    case Graphics::VertexInputFormat::IVECTOR4:
+      return GL_SHORT;
+    case Graphics::VertexInputFormat::FLOAT:
+    case Graphics::VertexInputFormat::FVECTOR2:
+    case Graphics::VertexInputFormat::FVECTOR3:
+    case Graphics::VertexInputFormat::FVECTOR4:
+      return GL_FLOAT;
+  }
+  return GL_BYTE;
+}
+
+GLenum GetTopology(Graphics::PrimitiveTopology topology)
+{
+  switch(topology)
+  {
+    case Graphics::PrimitiveTopology::POINT_LIST:
+      return GL_POINTS;
+
+    case Graphics::PrimitiveTopology::LINE_LIST:
+      return GL_LINES;
+
+    case Graphics::PrimitiveTopology::LINE_LOOP:
+      return GL_LINE_LOOP;
+
+    case Graphics::PrimitiveTopology::LINE_STRIP:
+      return GL_LINE_STRIP;
+
+    case Graphics::PrimitiveTopology::TRIANGLE_LIST:
+      return GL_TRIANGLES;
+
+    case Graphics::PrimitiveTopology::TRIANGLE_STRIP:
+      return GL_TRIANGLE_STRIP;
+
+    case Graphics::PrimitiveTopology::TRIANGLE_FAN:
+      return GL_TRIANGLE_FAN;
+  }
+  return GL_TRIANGLES;
+}
+
+GLenum GetCullFace(Graphics::CullMode cullMode)
+{
+  switch(cullMode)
+  {
+    case Graphics::CullMode::NONE:
+      return GL_NONE;
+    case Graphics::CullMode::FRONT:
+      return GL_FRONT;
+    case Graphics::CullMode::BACK:
+      return GL_BACK;
+    case Graphics::CullMode::FRONT_AND_BACK:
+      return GL_FRONT_AND_BACK;
+  }
+  return GL_NONE;
+}
+
+GLenum GetFrontFace(Graphics::FrontFace frontFace)
+{
+  if(frontFace == Graphics::FrontFace::CLOCKWISE)
+  {
+    return GL_CW;
+  }
+  return GL_CCW;
+}
+
+GLenum GetBlendFactor(Graphics::BlendFactor blendFactor)
+{
+  GLenum glFactor = GL_ZERO;
+
+  switch(blendFactor)
+  {
+    case Graphics::BlendFactor::ZERO:
+      glFactor = GL_ZERO;
+      break;
+    case Graphics::BlendFactor::ONE:
+      glFactor = GL_ONE;
+      break;
+    case Graphics::BlendFactor::SRC_COLOR:
+      glFactor = GL_SRC_COLOR;
+      break;
+    case Graphics::BlendFactor::ONE_MINUS_SRC_COLOR:
+      glFactor = GL_ONE_MINUS_SRC_COLOR;
+      break;
+    case Graphics::BlendFactor::DST_COLOR:
+      glFactor = GL_DST_COLOR;
+      break;
+    case Graphics::BlendFactor::ONE_MINUS_DST_COLOR:
+      glFactor = GL_ONE_MINUS_DST_COLOR;
+      break;
+    case Graphics::BlendFactor::SRC_ALPHA:
+      glFactor = GL_SRC_ALPHA;
+      break;
+    case Graphics::BlendFactor::ONE_MINUS_SRC_ALPHA:
+      glFactor = GL_ONE_MINUS_SRC_ALPHA;
+      break;
+    case Graphics::BlendFactor::DST_ALPHA:
+      glFactor = GL_DST_ALPHA;
+      break;
+    case Graphics::BlendFactor::ONE_MINUS_DST_ALPHA:
+      glFactor = GL_ONE_MINUS_DST_ALPHA;
+      break;
+    case Graphics::BlendFactor::CONSTANT_COLOR:
+      glFactor = GL_CONSTANT_COLOR;
+      break;
+    case Graphics::BlendFactor::ONE_MINUS_CONSTANT_COLOR:
+      glFactor = GL_ONE_MINUS_CONSTANT_COLOR;
+      break;
+    case Graphics::BlendFactor::CONSTANT_ALPHA:
+      glFactor = GL_CONSTANT_ALPHA;
+      break;
+    case Graphics::BlendFactor::ONE_MINUS_CONSTANT_ALPHA:
+      glFactor = GL_ONE_MINUS_CONSTANT_ALPHA;
+      break;
+    case Graphics::BlendFactor::SRC_ALPHA_SATURATE:
+      glFactor = GL_SRC_ALPHA_SATURATE;
+      break;
+      // GLES doesn't appear to have dual source blending.
+    case Graphics::BlendFactor::SRC1_COLOR:
+      glFactor = GL_SRC_COLOR;
+      break;
+    case Graphics::BlendFactor::ONE_MINUS_SRC1_COLOR:
+      glFactor = GL_ONE_MINUS_SRC_COLOR;
+      break;
+    case Graphics::BlendFactor::SRC1_ALPHA:
+      glFactor = GL_SRC_ALPHA;
+      break;
+    case Graphics::BlendFactor::ONE_MINUS_SRC1_ALPHA:
+      glFactor = GL_ONE_MINUS_SRC_ALPHA;
+      break;
+  }
+  return glFactor;
+}
+
+GLenum GetBlendOp(Graphics::BlendOp blendOp)
+{
+  GLenum op = GL_FUNC_ADD;
+  switch(blendOp)
+  {
+    case Graphics::BlendOp::ADD:
+      op = GL_FUNC_ADD;
+      break;
+    case Graphics::BlendOp::SUBTRACT:
+      op = GL_FUNC_SUBTRACT;
+      break;
+    case Graphics::BlendOp::REVERSE_SUBTRACT:
+      op = GL_FUNC_REVERSE_SUBTRACT;
+      break;
+    case Graphics::BlendOp::MIN:
+      op = GL_MIN;
+      break;
+    case Graphics::BlendOp::MAX:
+      op = GL_MAX;
+      break;
+
+      // @todo Add advanced blend equations
+  }
+  return op;
+}
+
+struct GLCompareOp
+{
+  constexpr explicit GLCompareOp(Graphics::CompareOp compareOp)
+  {
+    switch(compareOp)
+    {
+      case Graphics::CompareOp::NEVER:
+        op = GL_NEVER;
+        break;
+      case Graphics::CompareOp::LESS:
+        op = GL_LESS;
+        break;
+      case Graphics::CompareOp::EQUAL:
+        op = GL_EQUAL;
+        break;
+      case Graphics::CompareOp::LESS_OR_EQUAL:
+        op = GL_LEQUAL;
+        break;
+      case Graphics::CompareOp::GREATER:
+        op = GL_GREATER;
+        break;
+      case Graphics::CompareOp::NOT_EQUAL:
+        op = GL_NOTEQUAL;
+        break;
+      case Graphics::CompareOp::GREATER_OR_EQUAL:
+        op = GL_GEQUAL;
+        break;
+      case Graphics::CompareOp::ALWAYS:
+        op = GL_ALWAYS;
+        break;
+    }
+  }
+  GLenum op{GL_LESS};
+};
+
+struct GLStencilOp
+{
+  constexpr explicit GLStencilOp(Graphics::StencilOp stencilOp)
+  {
+    switch(stencilOp)
+    {
+      case Graphics::StencilOp::KEEP:
+        op = GL_KEEP;
+        break;
+      case Graphics::StencilOp::ZERO:
+        op = GL_ZERO;
+        break;
+      case Graphics::StencilOp::REPLACE:
+        op = GL_REPLACE;
+        break;
+      case Graphics::StencilOp::INCREMENT_AND_CLAMP:
+        op = GL_INCR;
+        break;
+      case Graphics::StencilOp::DECREMENT_AND_CLAMP:
+        op = GL_DECR;
+        break;
+      case Graphics::StencilOp::INVERT:
+        op = GL_INVERT;
+        break;
+      case Graphics::StencilOp::INCREMENT_AND_WRAP:
+        op = GL_INCR_WRAP;
+        break;
+      case Graphics::StencilOp::DECREMENT_AND_WRAP:
+        op = GL_DECR_WRAP;
+        break;
+    }
+  }
+  GLenum op{GL_KEEP};
+};
+
 class TestGraphicsMemory : public Graphics::Memory
 {
 public:
@@ -148,7 +461,9 @@ public:
   : mCallStack(callStack),
     mBuffer(buffer),
     mMappedOffset(mappedOffset),
-    mMappedSize(mappedSize)
+    mMappedSize(mappedSize),
+    mLockedOffset(0u),
+    mLockedSize(0u)
   {
   }
 
@@ -161,7 +476,7 @@ public:
     if(offset > mMappedOffset + mMappedSize ||
        size + offset > mMappedOffset + mMappedSize)
     {
-      tet_infoline("TestGraphics.Memory::LockRegion() Out of bounds");
+      fprintf(stderr, "TestGraphics.Memory::LockRegion() Out of bounds");
       mBuffer.memory.resize(mMappedOffset + offset + size); // Grow to prevent memcpy from crashing
     }
     mLockedOffset = offset;
@@ -195,61 +510,435 @@ public:
 };
 
 TestGraphicsController::TestGraphicsController()
+: mCallStack(true, "TestGraphicsController."),
+  mCommandBufferCallStack(true, "TestCommandBuffer."),
+  mFrameBufferCallStack(true, "TestFrameBuffer.")
 {
   mCallStack.Enable(true);
-  mCallStack.EnableLogging(true);
   mCommandBufferCallStack.Enable(true);
-  mCommandBufferCallStack.EnableLogging(true);
-  auto& trace = mGlAbstraction.GetTextureTrace();
+  auto& trace = mGl.GetTextureTrace();
   trace.Enable(true);
   trace.EnableLogging(true);
 }
 
 void TestGraphicsController::SubmitCommandBuffers(const Graphics::SubmitInfo& submitInfo)
 {
-  std::ostringstream          out;
   TraceCallStack::NamedParams namedParams;
-  out << "cmdBuffer[" << submitInfo.cmdBuffer.size() << "], flags:" << std::hex << submitInfo.flags;
-  namedParams["submitInfo"] = out.str();
+  namedParams["submitInfo"] << "cmdBuffer[" << submitInfo.cmdBuffer.size()
+                            << "], flags:" << std::hex << submitInfo.flags;
+
+  mCallStack.PushCall("SubmitCommandBuffers", "", namedParams);
 
-  mCallStack.PushCall("Controller::SubmitCommandBuffers", "", namedParams);
+  mSubmitStack.emplace_back(submitInfo);
 
-  for(auto& commandBuffer : submitInfo.cmdBuffer)
+  for(auto& graphicsCommandBuffer : submitInfo.cmdBuffer)
   {
-    for(auto& binding : (static_cast<TestGraphicsCommandBuffer*>(commandBuffer))->mTextureBindings)
+    auto commandBuffer = Uncast<TestGraphicsCommandBuffer>(graphicsCommandBuffer);
+    ProcessCommandBuffer(*commandBuffer);
+  }
+}
+
+void TestGraphicsController::ProcessCommandBuffer(TestGraphicsCommandBuffer& commandBuffer)
+{
+  bool                     scissorEnabled = false;
+  TestGraphicsFramebuffer* currentFramebuffer{nullptr};
+  TestGraphicsPipeline*    currentPipeline{nullptr};
+
+  for(auto& cmd : commandBuffer.GetCommands())
+  {
+    // process command
+    switch(cmd.type)
     {
-      if(binding.texture)
+      case CommandType::FLUSH:
+      {
+        // Nothing to do here
+        break;
+      }
+      case CommandType::BIND_TEXTURES:
       {
-        auto texture = const_cast<TestGraphicsTexture*>(static_cast<const TestGraphicsTexture*>(binding.texture));
+        for(auto& binding : cmd.data.bindTextures.textureBindings)
+        {
+          if(binding.texture)
+          {
+            auto texture = Uncast<TestGraphicsTexture>(binding.texture);
+            texture->Bind(binding.binding);
 
-        texture->Bind(binding.binding);
+            if(binding.sampler)
+            {
+              auto sampler = Uncast<TestGraphicsSampler>(binding.sampler);
+              if(sampler)
+              {
+                sampler->Apply(texture->GetTarget());
+              }
+            }
 
-        if(binding.sampler)
+            texture->Prepare(); // Ensure native texture is ready
+          }
+        }
+        break;
+      }
+      case CommandType::BIND_VERTEX_BUFFERS:
+      {
+        for(auto& binding : cmd.data.bindVertexBuffers.vertexBufferBindings)
         {
-          auto sampler = const_cast<TestGraphicsSampler*>(static_cast<const TestGraphicsSampler*>(binding.sampler));
-          if(sampler)
+          auto graphicsBuffer = binding.buffer;
+          auto vertexBuffer   = Uncast<TestGraphicsBuffer>(graphicsBuffer);
+          vertexBuffer->Bind();
+        }
+        break;
+      }
+      case CommandType::BIND_INDEX_BUFFER:
+      {
+        auto& indexBufferBinding = cmd.data.bindIndexBuffer;
+        if(indexBufferBinding.buffer)
+        {
+          auto buffer = Uncast<TestGraphicsBuffer>(indexBufferBinding.buffer);
+          buffer->Bind();
+        }
+        break;
+      }
+      case CommandType::BIND_UNIFORM_BUFFER:
+      {
+        auto& bindings = cmd.data.bindUniformBuffers;
+        auto  buffer   = bindings.standaloneUniformsBufferBinding;
+
+        // based on reflection, issue gl calls
+        buffer.buffer->BindAsUniformBuffer(static_cast<const TestGraphicsProgram*>(currentPipeline->programState.program));
+        break;
+      }
+      case CommandType::BIND_SAMPLERS:
+      {
+        break;
+      }
+      case CommandType::BIND_PIPELINE:
+      {
+        currentPipeline = Uncast<TestGraphicsPipeline>(cmd.data.bindPipeline.pipeline);
+        BindPipeline(currentPipeline);
+        break;
+      }
+      case CommandType::DRAW:
+      {
+        mGl.DrawArrays(GetTopology(currentPipeline->inputAssemblyState.topology),
+                       0,
+                       cmd.data.draw.draw.vertexCount);
+        break;
+      }
+      case CommandType::DRAW_INDEXED:
+      {
+        mGl.DrawElements(GetTopology(currentPipeline->inputAssemblyState.topology),
+                         static_cast<GLsizei>(cmd.data.draw.drawIndexed.indexCount),
+                         GL_UNSIGNED_SHORT,
+                         reinterpret_cast<void*>(cmd.data.draw.drawIndexed.firstIndex));
+        break;
+      }
+      case CommandType::DRAW_INDEXED_INDIRECT:
+      {
+        mGl.DrawElements(GetTopology(currentPipeline->inputAssemblyState.topology),
+                         static_cast<GLsizei>(cmd.data.draw.drawIndexed.indexCount),
+                         GL_UNSIGNED_SHORT,
+                         reinterpret_cast<void*>(cmd.data.draw.drawIndexed.firstIndex));
+        break;
+      }
+      case CommandType::SET_SCISSOR:
+      {
+        if(scissorEnabled)
+        {
+          auto& rect = cmd.data.scissor.region;
+          mGl.Scissor(rect.x, rect.y, rect.width, rect.height);
+        }
+        break;
+      }
+      case CommandType::SET_SCISSOR_TEST:
+      {
+        if(cmd.data.scissorTest.enable)
+        {
+          mGl.Enable(GL_SCISSOR_TEST);
+          scissorEnabled = true;
+        }
+        else
+        {
+          mGl.Disable(GL_SCISSOR_TEST);
+          scissorEnabled = false;
+        }
+        break;
+      }
+      case CommandType::SET_VIEWPORT_TEST:
+      {
+        break;
+      }
+      case CommandType::SET_VIEWPORT: // @todo Consider correcting for orientation here?
+      {
+        auto& rect = cmd.data.viewport.region;
+        mGl.Viewport(rect.x, rect.y, rect.width, rect.height);
+        break;
+      }
+
+      case CommandType::SET_COLOR_MASK:
+      {
+        // Set all channels to the same mask
+        const bool mask = cmd.data.colorMask.enabled;
+        mGl.ColorMask(mask, mask, mask, mask);
+        break;
+      }
+      case CommandType::CLEAR_STENCIL_BUFFER:
+      {
+        mGl.Clear(GL_STENCIL_BUFFER_BIT);
+        break;
+      }
+      case CommandType::CLEAR_DEPTH_BUFFER:
+      {
+        mGl.Clear(GL_DEPTH_BUFFER_BIT);
+        break;
+      }
+
+      case CommandType::SET_STENCIL_TEST_ENABLE:
+      {
+        if(cmd.data.stencilTest.enabled)
+        {
+          mGl.Enable(GL_STENCIL_TEST);
+        }
+        else
+        {
+          mGl.Disable(GL_STENCIL_TEST);
+        }
+        break;
+      }
+
+      case CommandType::SET_STENCIL_FUNC:
+      {
+        mGl.StencilFunc(GLCompareOp(cmd.data.stencilFunc.compareOp).op,
+                        cmd.data.stencilFunc.reference,
+                        cmd.data.stencilFunc.compareMask);
+        break;
+      }
+
+      case CommandType::SET_STENCIL_WRITE_MASK:
+      {
+        mGl.StencilMask(cmd.data.stencilWriteMask.mask);
+        break;
+      }
+      case CommandType::SET_STENCIL_OP:
+      {
+        mGl.StencilOp(GLStencilOp(cmd.data.stencilOp.failOp).op,
+                      GLStencilOp(cmd.data.stencilOp.depthFailOp).op,
+                      GLStencilOp(cmd.data.stencilOp.passOp).op);
+        break;
+      }
+
+      case CommandType::SET_DEPTH_COMPARE_OP:
+      {
+        mGl.DepthFunc(GLCompareOp(cmd.data.depth.compareOp).op);
+        break;
+      }
+      case CommandType::SET_DEPTH_TEST_ENABLE:
+      {
+        if(cmd.data.depth.testEnabled)
+        {
+          mGl.Enable(GL_DEPTH_TEST);
+        }
+        else
+        {
+          mGl.Disable(GL_DEPTH_TEST);
+        }
+        break;
+      }
+      case CommandType::SET_DEPTH_WRITE_ENABLE:
+      {
+        mGl.DepthMask(cmd.data.depth.writeEnabled);
+        break;
+      }
+
+      case CommandType::EXECUTE_COMMAND_BUFFERS:
+      {
+        // Process secondary command buffers
+        for(auto& buf : cmd.data.executeCommandBuffers.buffers)
+        {
+          ProcessCommandBuffer(*Uncast<TestGraphicsCommandBuffer>(buf));
+        }
+        break;
+      }
+      case CommandType::BEGIN_RENDER_PASS:
+      {
+        auto renderTarget = Uncast<TestGraphicsRenderTarget>(cmd.data.beginRenderPass.renderTarget);
+
+        if(renderTarget)
+        {
+          auto fb = renderTarget->mCreateInfo.framebuffer;
+          if(fb)
           {
-            sampler->Apply(texture->GetTarget());
+            if(currentFramebuffer != fb)
+            {
+              currentFramebuffer = Uncast<TestGraphicsFramebuffer>(fb);
+              currentFramebuffer->Bind();
+            }
           }
+          else
+          {
+            mGl.BindFramebuffer(GL_FRAMEBUFFER, 0);
+          }
+        }
+        else
+        {
+          mGl.BindFramebuffer(GL_FRAMEBUFFER, 0);
         }
 
-        texture->Prepare(); // Ensure native texture is ready
+        auto& clearValues = cmd.data.beginRenderPass.clearValues;
+        if(clearValues.size() > 0)
+        {
+          const auto renderPass = static_cast<TestGraphicsRenderPass*>(cmd.data.beginRenderPass.renderPass);
+          if(renderPass)
+          {
+            const auto& color0 = renderPass->attachments[0];
+            GLuint      mask   = 0;
+            if(color0.loadOp == Graphics::AttachmentLoadOp::CLEAR)
+            {
+              mask |= GL_COLOR_BUFFER_BIT;
+
+              // Set clear color (todo: cache it!)
+              // Something goes wrong here if Alpha mask is GL_TRUE
+              mGl.ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
+              mGl.ClearColor(clearValues[0].color.r,
+                             clearValues[0].color.g,
+                             clearValues[0].color.b,
+                             clearValues[0].color.a);
+            }
+
+            // check for depth stencil
+            if(renderPass->attachments.size() > 1)
+            {
+              const auto& depthStencil = renderPass->attachments.back();
+              if(depthStencil.loadOp == Graphics::AttachmentLoadOp::CLEAR)
+              {
+                mGl.DepthMask(true);
+                uint32_t depthClearColor = 0u;
+                if(clearValues.size() == renderPass->attachments.size())
+                {
+                  depthClearColor = clearValues.back().depthStencil.depth;
+                }
+                mGl.ClearDepthf(depthClearColor);
+                mask |= GL_DEPTH_BUFFER_BIT;
+              }
+              if(depthStencil.stencilLoadOp == Graphics::AttachmentLoadOp::CLEAR)
+              {
+                uint32_t stencilClearColor = 0u;
+                if(clearValues.size() == renderPass->attachments.size())
+                {
+                  stencilClearColor = clearValues.back().depthStencil.stencil;
+                }
+                mGl.ClearStencil(stencilClearColor);
+                mGl.StencilMask(0xFF); // Clear all the bitplanes (assume 8)
+                mask |= GL_STENCIL_BUFFER_BIT;
+              }
+            }
+
+            if(mask != 0)
+            {
+              // Test scissor area and RT size
+              const auto& area = cmd.data.beginRenderPass.renderArea;
+              if(area.x == 0 &&
+                 area.y == 0 &&
+                 area.width == renderTarget->mCreateInfo.extent.width &&
+                 area.height == renderTarget->mCreateInfo.extent.height)
+              {
+                mGl.Disable(GL_SCISSOR_TEST);
+                mGl.Clear(mask);
+              }
+              else
+              {
+                mGl.Enable(GL_SCISSOR_TEST);
+                mGl.Scissor(cmd.data.beginRenderPass.renderArea.x, cmd.data.beginRenderPass.renderArea.y, cmd.data.beginRenderPass.renderArea.width, cmd.data.beginRenderPass.renderArea.height);
+                mGl.Clear(mask);
+                mGl.Disable(GL_SCISSOR_TEST);
+              }
+            }
+          }
+          else
+          {
+            DALI_ASSERT_DEBUG(0 && "BeginRenderPass has no render pass");
+          }
+        }
+        break;
+      }
+      case CommandType::END_RENDER_PASS:
+      {
+        break;
       }
     }
   }
 }
 
+void TestGraphicsController::BindPipeline(TestGraphicsPipeline* pipeline)
+{
+  auto& vi = pipeline->vertexInputState;
+  for(auto& attribute : vi.attributes)
+  {
+    mGl.EnableVertexAttribArray(attribute.location);
+    uint32_t attributeOffset = attribute.offset;
+    GLsizei  stride          = vi.bufferBindings[attribute.binding].stride;
+
+    mGl.VertexAttribPointer(attribute.location,
+                            GetNumComponents(attribute.format),
+                            GetGlType(attribute.format),
+                            GL_FALSE, // Not normalized
+                            stride,
+                            reinterpret_cast<void*>(attributeOffset));
+  }
+
+  // Cull face setup
+  auto& rasterizationState = pipeline->rasterizationState;
+  if(rasterizationState.cullMode == Graphics::CullMode::NONE)
+  {
+    mGl.Disable(GL_CULL_FACE);
+  }
+  else
+  {
+    mGl.Enable(GL_CULL_FACE);
+    mGl.CullFace(GetCullFace(rasterizationState.cullMode));
+  }
+
+  mGl.FrontFace(GetFrontFace(rasterizationState.frontFace));
+
+  // Blending setup
+  auto& colorBlendState = pipeline->colorBlendState;
+  if(colorBlendState.blendEnable)
+  {
+    mGl.Enable(GL_BLEND);
+
+    mGl.BlendFuncSeparate(GetBlendFactor(colorBlendState.srcColorBlendFactor),
+                          GetBlendFactor(colorBlendState.dstColorBlendFactor),
+                          GetBlendFactor(colorBlendState.srcAlphaBlendFactor),
+                          GetBlendFactor(colorBlendState.dstAlphaBlendFactor));
+    if(colorBlendState.colorBlendOp != colorBlendState.alphaBlendOp)
+    {
+      mGl.BlendEquationSeparate(GetBlendOp(colorBlendState.colorBlendOp), GetBlendOp(colorBlendState.alphaBlendOp));
+    }
+    else
+    {
+      mGl.BlendEquation(GetBlendOp(colorBlendState.colorBlendOp));
+    }
+    mGl.BlendColor(colorBlendState.blendConstants[0],
+                   colorBlendState.blendConstants[1],
+                   colorBlendState.blendConstants[2],
+                   colorBlendState.blendConstants[3]);
+  }
+  else
+  {
+    mGl.Disable(GL_BLEND);
+  }
+
+  auto* program = static_cast<const TestGraphicsProgram*>(pipeline->programState.program);
+  mGl.UseProgram(program->mImpl->mId);
+}
+
 /**
  * @brief Presents render target
  * @param renderTarget render target to present
  */
 void TestGraphicsController::PresentRenderTarget(Graphics::RenderTarget* renderTarget)
 {
-  std::ostringstream          out;
   TraceCallStack::NamedParams namedParams;
-  out << std::hex << renderTarget;
-  namedParams["renderTarget"] = out.str();
-  mCallStack.PushCall("Controller::PresentRenderTarget", "", namedParams);
+  namedParams["renderTarget"] << std::hex << renderTarget;
+  mCallStack.PushCall("PresentRenderTarget", "", namedParams);
 }
 
 /**
@@ -257,7 +946,7 @@ void TestGraphicsController::PresentRenderTarget(Graphics::RenderTarget* renderT
  */
 void TestGraphicsController::WaitIdle()
 {
-  mCallStack.PushCall("Controller::WaitIdle", "");
+  mCallStack.PushCall("WaitIdle", "");
 }
 
 /**
@@ -265,7 +954,7 @@ void TestGraphicsController::WaitIdle()
  */
 void TestGraphicsController::Pause()
 {
-  mCallStack.PushCall("Controller::Pause", "");
+  mCallStack.PushCall("Pause", "");
 }
 
 /**
@@ -273,21 +962,27 @@ void TestGraphicsController::Pause()
  */
 void TestGraphicsController::Resume()
 {
-  mCallStack.PushCall("Controller::Resume", "");
+  mCallStack.PushCall("Resume", "");
+}
+
+void TestGraphicsController::Shutdown()
+{
+  mCallStack.PushCall("Shutdown", "");
+}
+
+void TestGraphicsController::Destroy()
+{
+  mCallStack.PushCall("Destroy", "");
 }
 
 void TestGraphicsController::UpdateTextures(const std::vector<Graphics::TextureUpdateInfo>&       updateInfoList,
                                             const std::vector<Graphics::TextureUpdateSourceInfo>& sourceList)
 {
-  std::ostringstream          out;
   TraceCallStack::NamedParams namedParams;
-  out << "[" << updateInfoList.size() << "]:";
-  namedParams["updateInfoList"] = out.str();
-  out.str("");
-  out << "[" << sourceList.size() << "]:";
-  namedParams["sourceList"] = out.str();
+  namedParams["updateInfoList"] << "[" << updateInfoList.size() << "]:";
+  namedParams["sourceList"] << "[" << sourceList.size() << "]:";
 
-  mCallStack.PushCall("Controller::UpdateTextures", "", namedParams);
+  mCallStack.PushCall("UpdateTextures", "", namedParams);
 
   // Call either TexImage2D or TexSubImage2D
   for(unsigned int i = 0; i < updateInfoList.size(); ++i)
@@ -301,32 +996,39 @@ void TestGraphicsController::UpdateTextures(const std::vector<Graphics::TextureU
   }
 }
 
+void TestGraphicsController::GenerateTextureMipmaps(const Graphics::Texture& texture)
+{
+  mCallStack.PushCall("GenerateTextureMipmaps", "");
+
+  auto gfxTexture = Uncast<TestGraphicsTexture>(&texture);
+  mGl.BindTexture(gfxTexture->GetTarget(), 0);
+  mGl.GenerateMipmap(gfxTexture->GetTarget());
+}
+
 bool TestGraphicsController::EnableDepthStencilBuffer(bool enableDepth, bool enableStencil)
 {
   TraceCallStack::NamedParams namedParams;
-  namedParams["enableDepth"]   = enableDepth ? "T" : "F";
-  namedParams["enableStencil"] = enableStencil ? "T" : "F";
-  mCallStack.PushCall("Controller::EnableDepthStencilBuffer", "", namedParams);
+  namedParams["enableDepth"] << (enableDepth ? "T" : "F");
+  namedParams["enableStencil"] << (enableStencil ? "T" : "F");
+  mCallStack.PushCall("EnableDepthStencilBuffer", "", namedParams);
   return false;
 }
 
 void TestGraphicsController::RunGarbageCollector(size_t numberOfDiscardedRenderers)
 {
-  std::ostringstream out;
-  out << numberOfDiscardedRenderers;
   TraceCallStack::NamedParams namedParams;
-  namedParams["numberOfDiscardedrenderers"] = out.str();
-  mCallStack.PushCall("Controller::RunGarbageCollector", "", namedParams);
+  namedParams["numberOfDiscardedRenderers"] << numberOfDiscardedRenderers;
+  mCallStack.PushCall("RunGarbageCollector", "", namedParams);
 }
 
 void TestGraphicsController::DiscardUnusedResources()
 {
-  mCallStack.PushCall("Controller::DiscardUnusedResources", "");
+  mCallStack.PushCall("DiscardUnusedResources", "");
 }
 
 bool TestGraphicsController::IsDiscardQueueEmpty()
 {
-  mCallStack.PushCall("Controller::IsDiscardQueueEmpty", "");
+  mCallStack.PushCall("IsDiscardQueueEmpty", "");
   return isDiscardQueueEmptyResult;
 }
 
@@ -337,7 +1039,7 @@ bool TestGraphicsController::IsDiscardQueueEmpty()
  */
 bool TestGraphicsController::IsDrawOnResumeRequired()
 {
-  mCallStack.PushCall("Controller::IsDrawOnResumeRequired", "");
+  mCallStack.PushCall("IsDrawOnResumeRequired", "");
   return isDrawOnResumeRequiredResult;
 }
 
@@ -345,75 +1047,111 @@ Graphics::UniquePtr<Graphics::Buffer> TestGraphicsController::CreateBuffer(const
 {
   std::ostringstream oss;
   oss << "bufferCreateInfo:" << createInfo;
-  mCallStack.PushCall("Controller::CreateBuffer", oss.str());
-  return Graphics::MakeUnique<TestGraphicsBuffer>(mCallStack, mGlAbstraction, createInfo.size, createInfo.usage);
+  mCallStack.PushCall("CreateBuffer", oss.str());
+  return Graphics::MakeUnique<TestGraphicsBuffer>(mCallStack, mGl, createInfo.size, createInfo.usage);
 }
 
 Graphics::UniquePtr<Graphics::CommandBuffer> TestGraphicsController::CreateCommandBuffer(const Graphics::CommandBufferCreateInfo& commandBufferCreateInfo, Graphics::UniquePtr<Graphics::CommandBuffer>&& oldCommandBuffer)
 {
   std::ostringstream oss;
   oss << "commandBufferCreateInfo:" << commandBufferCreateInfo;
-  mCallStack.PushCall("Controller::CreateCommandBuffer", oss.str());
-  return Graphics::MakeUnique<TestGraphicsCommandBuffer>(mCommandBufferCallStack, mGlAbstraction);
+  mCallStack.PushCall("CreateCommandBuffer", oss.str());
+  return Graphics::MakeUnique<TestGraphicsCommandBuffer>(mCommandBufferCallStack, mGl);
 }
 
 Graphics::UniquePtr<Graphics::RenderPass> TestGraphicsController::CreateRenderPass(const Graphics::RenderPassCreateInfo& renderPassCreateInfo, Graphics::UniquePtr<Graphics::RenderPass>&& oldRenderPass)
 {
-  mCallStack.PushCall("Controller::CreateRenderPass", "");
-  return nullptr;
+  mCallStack.PushCall("CreateRenderPass", "");
+  return Graphics::MakeUnique<TestGraphicsRenderPass>(mGl, renderPassCreateInfo);
 }
 
 Graphics::UniquePtr<Graphics::Texture> TestGraphicsController::CreateTexture(const Graphics::TextureCreateInfo& textureCreateInfo, Graphics::UniquePtr<Graphics::Texture>&& oldTexture)
 {
-  std::ostringstream params, oss;
-  params << "textureCreateInfo:" << textureCreateInfo;
   TraceCallStack::NamedParams namedParams;
-  oss << textureCreateInfo;
-  namedParams["textureCreateInfo"] = oss.str();
-  mCallStack.PushCall("Controller::CreateTexture", params.str(), namedParams);
+  namedParams["textureCreateInfo"] << textureCreateInfo;
+  mCallStack.PushCall("CreateTexture", namedParams.str(), namedParams);
 
-  return Graphics::MakeUnique<TestGraphicsTexture>(mGlAbstraction, textureCreateInfo);
+  return Graphics::MakeUnique<TestGraphicsTexture>(mGl, textureCreateInfo);
 }
 
-Graphics::UniquePtr<Graphics::Framebuffer> TestGraphicsController::CreateFramebuffer(const Graphics::FramebufferCreateInfo& framebufferCreateInfo, Graphics::UniquePtr<Graphics::Framebuffer>&& oldFramebuffer)
+Graphics::UniquePtr<Graphics::Framebuffer> TestGraphicsController::CreateFramebuffer(
+  const Graphics::FramebufferCreateInfo&       createInfo,
+  Graphics::UniquePtr<Graphics::Framebuffer>&& oldFramebuffer)
 {
-  mCallStack.PushCall("Controller::CreateFramebuffer", "");
-  return nullptr;
+  TraceCallStack::NamedParams namedParams;
+  namedParams["framebufferCreateInfo"] << createInfo;
+  mCallStack.PushCall("Controller::CreateFramebuffer", namedParams.str(), namedParams);
+
+  return Graphics::MakeUnique<TestGraphicsFramebuffer>(mFrameBufferCallStack, mGl, createInfo);
 }
 
 Graphics::UniquePtr<Graphics::Pipeline> TestGraphicsController::CreatePipeline(const Graphics::PipelineCreateInfo& pipelineCreateInfo, Graphics::UniquePtr<Graphics::Pipeline>&& oldPipeline)
 {
-  mCallStack.PushCall("Controller::CreatePipeline", "");
-  return nullptr;
+  mCallStack.PushCall("CreatePipeline", "");
+  return std::make_unique<TestGraphicsPipeline>(mGl, pipelineCreateInfo);
+}
+
+Graphics::UniquePtr<Graphics::Program> TestGraphicsController::CreateProgram(const Graphics::ProgramCreateInfo& programCreateInfo, Graphics::UniquePtr<Graphics::Program>&& oldProgram)
+{
+  mCallStack.PushCall("CreateProgram", "");
+
+  for(auto cacheEntry : mProgramCache)
+  {
+    bool found = true;
+    for(auto& shader : *(programCreateInfo.shaderState))
+    {
+      auto                 graphicsShader = Uncast<TestGraphicsShader>(shader.shader);
+      std::vector<uint8_t> source;
+      source.resize(graphicsShader->mCreateInfo.sourceSize);
+      memcpy(&source[0], graphicsShader->mCreateInfo.sourceData, graphicsShader->mCreateInfo.sourceSize);
+
+      if(!std::equal(source.begin(), source.end(), cacheEntry.shaders[shader.pipelineStage].begin()))
+      {
+        found = false;
+        break;
+      }
+    }
+    if(found)
+    {
+      return Graphics::MakeUnique<TestGraphicsProgram>(cacheEntry.programImpl);
+    }
+  }
+
+  mProgramCache.emplace_back();
+  mProgramCache.back().programImpl = new TestGraphicsProgramImpl(mGl, programCreateInfo, mVertexFormats, mCustomUniforms);
+  for(auto& shader : *(programCreateInfo.shaderState))
+  {
+    auto graphicsShader = Uncast<TestGraphicsShader>(shader.shader);
+    mProgramCache.back().shaders[shader.pipelineStage].resize(graphicsShader->mCreateInfo.sourceSize);
+    memcpy(&mProgramCache.back().shaders[shader.pipelineStage][0], graphicsShader->mCreateInfo.sourceData, graphicsShader->mCreateInfo.sourceSize);
+  }
+  return Graphics::MakeUnique<TestGraphicsProgram>(mProgramCache.back().programImpl);
 }
 
 Graphics::UniquePtr<Graphics::Shader> TestGraphicsController::CreateShader(const Graphics::ShaderCreateInfo& shaderCreateInfo, Graphics::UniquePtr<Graphics::Shader>&& oldShader)
 {
-  mCallStack.PushCall("Controller::CreateShader", "");
-  return nullptr;
+  mCallStack.PushCall("CreateShader", "");
+  return Graphics::MakeUnique<TestGraphicsShader>(mGl, shaderCreateInfo);
 }
 
 Graphics::UniquePtr<Graphics::Sampler> TestGraphicsController::CreateSampler(const Graphics::SamplerCreateInfo& samplerCreateInfo, Graphics::UniquePtr<Graphics::Sampler>&& oldSampler)
 {
-  std::ostringstream params, oss;
-  params << "samplerCreateInfo:" << samplerCreateInfo;
   TraceCallStack::NamedParams namedParams;
-  oss << samplerCreateInfo;
-  namedParams["samplerCreateInfo"] = oss.str();
-  mCallStack.PushCall("Controller::CreateSampler", params.str(), namedParams);
+  namedParams["samplerCreateInfo"] << samplerCreateInfo;
+  mCallStack.PushCall("CreateSampler", namedParams.str(), namedParams);
 
-  return Graphics::MakeUnique<TestGraphicsSampler>(mGlAbstraction, samplerCreateInfo);
+  return Graphics::MakeUnique<TestGraphicsSampler>(mGl, samplerCreateInfo);
 }
 
 Graphics::UniquePtr<Graphics::RenderTarget> TestGraphicsController::CreateRenderTarget(const Graphics::RenderTargetCreateInfo& renderTargetCreateInfo, Graphics::UniquePtr<Graphics::RenderTarget>&& oldRenderTarget)
 {
-  mCallStack.PushCall("Controller::CreateRenderTarget", "");
-  return nullptr;
+  mCallStack.PushCall("CreateRenderTarget", "");
+  return Graphics::MakeUnique<TestGraphicsRenderTarget>(mGl, renderTargetCreateInfo);
 }
 
 Graphics::UniquePtr<Graphics::Memory> TestGraphicsController::MapBufferRange(const Graphics::MapBufferInfo& mapInfo)
 {
-  mCallStack.PushCall("Controller::MapBufferRange", "");
+  mCallStack.PushCall("MapBufferRange", "");
 
   auto buffer = static_cast<TestGraphicsBuffer*>(mapInfo.buffer);
   buffer->memory.resize(mapInfo.offset + mapInfo.size); // For initial testing, allow writes past capacity
@@ -423,39 +1161,53 @@ Graphics::UniquePtr<Graphics::Memory> TestGraphicsController::MapBufferRange(con
 
 Graphics::UniquePtr<Graphics::Memory> TestGraphicsController::MapTextureRange(const Graphics::MapTextureInfo& mapInfo)
 {
-  mCallStack.PushCall("Controller::MapTextureRange", "");
+  mCallStack.PushCall("MapTextureRange", "");
   return nullptr;
 }
 
 void TestGraphicsController::UnmapMemory(Graphics::UniquePtr<Graphics::Memory> memory)
 {
-  mCallStack.PushCall("Controller::UnmapMemory", "");
+  mCallStack.PushCall("UnmapMemory", "");
 }
 
 Graphics::MemoryRequirements TestGraphicsController::GetTextureMemoryRequirements(Graphics::Texture& texture) const
 {
-  mCallStack.PushCall("Controller::GetTextureMemoryRequirements", "");
+  mCallStack.PushCall("GetTextureMemoryRequirements", "");
   return Graphics::MemoryRequirements{};
 }
 
 Graphics::MemoryRequirements TestGraphicsController::GetBufferMemoryRequirements(Graphics::Buffer& buffer) const
 {
-  mCallStack.PushCall("Controller::GetBufferMemoryRequirements", "");
+  mCallStack.PushCall("GetBufferMemoryRequirements", "");
   return Graphics::MemoryRequirements{};
 }
 
 const Graphics::TextureProperties& TestGraphicsController::GetTextureProperties(const Graphics::Texture& texture)
 {
   static Graphics::TextureProperties textureProperties{};
-  mCallStack.PushCall("Controller::GetTextureProperties", "");
+  mCallStack.PushCall("GetTextureProperties", "");
 
   return textureProperties;
 }
 
+const Graphics::Reflection& TestGraphicsController::GetProgramReflection(const Graphics::Program& program)
+{
+  mCallStack.PushCall("GetProgramReflection", "");
+
+  return static_cast<const TestGraphicsProgram*>(&program)->GetReflection();
+}
+
 bool TestGraphicsController::PipelineEquals(const Graphics::Pipeline& pipeline0, const Graphics::Pipeline& pipeline1) const
 {
-  mCallStack.PushCall("Controller::PipelineEquals", "");
+  mCallStack.PushCall("PipelineEquals", "");
   return false;
 }
 
+bool TestGraphicsController::GetProgramParameter(Graphics::Program& program, uint32_t parameterId, void* outData)
+{
+  mCallStack.PushCall("GetProgramParameter", "");
+  auto graphicsProgram = Uncast<TestGraphicsProgram>(&program);
+  return graphicsProgram->GetParameter(parameterId, outData);
+}
+
 } // namespace Dali