Syncing test harness
[platform/core/uifw/dali-toolkit.git] / automated-tests / src / dali-toolkit / dali-toolkit-test-utils / test-graphics-controller.cpp
index 51f0721..c42d372 100644 (file)
 
 #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"
 
 namespace Dali
 {
-template<typename T>
-T* Uncast(const Graphics::CommandBuffer* object)
-{
-  return const_cast<T*>(static_cast<const T*>(object));
-}
-
-template<typename T>
-T* Uncast(const Graphics::Texture* object)
-{
-  return const_cast<T*>(static_cast<const T*>(object));
-}
-
-template<typename T>
-T* Uncast(const Graphics::Sampler* object)
-{
-  return const_cast<T*>(static_cast<const T*>(object));
-}
-
-template<typename T>
-T* Uncast(const Graphics::Buffer* object)
-{
-  return const_cast<T*>(static_cast<const T*>(object));
-}
-
-template<typename T>
-T* Uncast(const Graphics::Shader* object)
-{
-  return const_cast<T*>(static_cast<const T*>(object));
-}
-
 std::ostream& operator<<(std::ostream& o, const Graphics::BufferCreateInfo& bufferCreateInfo)
 {
   return o << "usage:" << std::hex << bufferCreateInfo.usage << ", size:" << std::dec << bufferCreateInfo.size;
@@ -173,72 +146,37 @@ std::ostream& operator<<(std::ostream& o, const Graphics::SamplerCreateInfo& cre
   return o;
 }
 
-class TestGraphicsMemory : public Graphics::Memory
+std::ostream& operator<<(std::ostream& o, const Graphics::ColorAttachment& colorAttachment)
 {
-public:
-  TestGraphicsMemory(TraceCallStack& callStack, TestGraphicsBuffer& buffer, uint32_t mappedOffset, uint32_t mappedSize)
-  : mCallStack(callStack),
-    mBuffer(buffer),
-    mMappedOffset(mappedOffset),
-    mMappedSize(mappedSize),
-    mLockedOffset(0u),
-    mLockedSize(0u)
-  {
-  }
-
-  void* LockRegion(uint32_t offset, uint32_t size) override
-  {
-    std::ostringstream o;
-    o << offset << ", " << size;
-    mCallStack.PushCall("Memory::LockRegion", o.str());
-
-    if(offset > mMappedOffset + mMappedSize ||
-       size + offset > mMappedOffset + mMappedSize)
-    {
-      fprintf(stderr, "TestGraphics.Memory::LockRegion() Out of bounds");
-      mBuffer.memory.resize(mMappedOffset + offset + size); // Grow to prevent memcpy from crashing
-    }
-    mLockedOffset = offset;
-    mLockedSize   = size;
-    return &mBuffer.memory[mMappedOffset + offset];
-  }
+  o << "attachmentId:" << colorAttachment.attachmentId
+    << " layerId:" << colorAttachment.layerId
+    << " levelId:" << colorAttachment.levelId
+    << " texture:" << colorAttachment.texture;
+  return o;
+}
 
-  void Unlock(bool flush) override
-  {
-    mCallStack.PushCall("Memory::Unlock", (flush ? "Flush" : "NoFlush"));
-    if(flush)
-    {
-      Flush();
-    }
-  }
+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;
+}
 
-  void Flush() override
+std::ostream& operator<<(std::ostream& o, const Graphics::FramebufferCreateInfo& createInfo)
+{
+  o << "colorAttachments:";
+  for(auto i = 0u; i < createInfo.colorAttachments.size(); ++i)
   {
-    mCallStack.PushCall("Memory::Flush", "");
-    mBuffer.Bind();
-    mBuffer.Upload(mMappedOffset + mLockedOffset, mLockedSize);
-    mBuffer.Unbind();
+    o << "[" << i << "]=" << createInfo.colorAttachments[i] << "  ";
   }
-
-  TraceCallStack&     mCallStack;
-  TestGraphicsBuffer& mBuffer;
-  uint32_t            mMappedOffset;
-  uint32_t            mMappedSize;
-  uint32_t            mLockedOffset;
-  uint32_t            mLockedSize;
-};
-
-TestGraphicsController::TestGraphicsController()
-: mCallStack(true, "TestGraphicsController."),
-  mCommandBufferCallStack(true, "TestCommandBuffer.")
-{
-  mCallStack.Enable(true);
-  mCommandBufferCallStack.Enable(true);
-  auto& trace = mGl.GetTextureTrace();
-  trace.Enable(true);
-  trace.EnableLogging(true);
+  o << "depthStencilAttachment:" << createInfo.depthStencilAttachment;
+  o << "size: " << createInfo.size;
+  return o;
 }
 
+
 int GetNumComponents(Graphics::VertexInputFormat vertexFormat)
 {
   switch(vertexFormat)
@@ -447,6 +385,75 @@ GLenum GetBlendOp(Graphics::BlendOp blendOp)
   return op;
 }
 
+
+class TestGraphicsMemory : public Graphics::Memory
+{
+public:
+  TestGraphicsMemory(TraceCallStack& callStack, TestGraphicsBuffer& buffer, uint32_t mappedOffset, uint32_t mappedSize)
+  : mCallStack(callStack),
+    mBuffer(buffer),
+    mMappedOffset(mappedOffset),
+    mMappedSize(mappedSize),
+    mLockedOffset(0u),
+    mLockedSize(0u)
+  {
+  }
+
+  void* LockRegion(uint32_t offset, uint32_t size) override
+  {
+    std::ostringstream o;
+    o << offset << ", " << size;
+    mCallStack.PushCall("Memory::LockRegion", o.str());
+
+    if(offset > mMappedOffset + mMappedSize ||
+       size + offset > mMappedOffset + mMappedSize)
+    {
+      fprintf(stderr, "TestGraphics.Memory::LockRegion() Out of bounds");
+      mBuffer.memory.resize(mMappedOffset + offset + size); // Grow to prevent memcpy from crashing
+    }
+    mLockedOffset = offset;
+    mLockedSize   = size;
+    return &mBuffer.memory[mMappedOffset + offset];
+  }
+
+  void Unlock(bool flush) override
+  {
+    mCallStack.PushCall("Memory::Unlock", (flush ? "Flush" : "NoFlush"));
+    if(flush)
+    {
+      Flush();
+    }
+  }
+
+  void Flush() override
+  {
+    mCallStack.PushCall("Memory::Flush", "");
+    mBuffer.Bind();
+    mBuffer.Upload(mMappedOffset + mLockedOffset, mLockedSize);
+    mBuffer.Unbind();
+  }
+
+  TraceCallStack&     mCallStack;
+  TestGraphicsBuffer& mBuffer;
+  uint32_t            mMappedOffset;
+  uint32_t            mMappedSize;
+  uint32_t            mLockedOffset;
+  uint32_t            mLockedSize;
+};
+
+TestGraphicsController::TestGraphicsController()
+: mCallStack(true, "TestGraphicsController."),
+  mCommandBufferCallStack(true, "TestCommandBuffer."),
+  mFrameBufferCallStack(true, "TestFrameBuffer.")
+{
+  mCallStack.Enable(true);
+  mCommandBufferCallStack.Enable(true);
+  auto& trace = mGl.GetTextureTrace();
+  trace.Enable(true);
+  trace.EnableLogging(true);
+}
+
+
 void TestGraphicsController::SubmitCommandBuffers(const Graphics::SubmitInfo& submitInfo)
 {
   TraceCallStack::NamedParams namedParams;
@@ -460,193 +467,308 @@ void TestGraphicsController::SubmitCommandBuffers(const Graphics::SubmitInfo& su
   for(auto& graphicsCommandBuffer : submitInfo.cmdBuffer)
   {
     auto commandBuffer = Uncast<TestGraphicsCommandBuffer>(graphicsCommandBuffer);
+    ProcessCommandBuffer(*commandBuffer);
+  }
+}
+
+void TestGraphicsController::ProcessCommandBuffer(TestGraphicsCommandBuffer& commandBuffer)
+{
+  bool                     scissorEnabled = false;
+  TestGraphicsFramebuffer* currentFramebuffer{nullptr};
+  TestGraphicsPipeline*    currentPipeline{nullptr};
 
-    auto value = commandBuffer->GetCommandsByType(0 | CommandType::BIND_TEXTURES);
-    if(!value.empty())
+  for(auto& cmd : commandBuffer.GetCommands())
+  {
+    // process command
+    switch(cmd.type)
     {
-      // must be fixed
-      for(auto& binding : value[0]->data.bindTextures.textureBindings)
+      case CommandType::FLUSH:
+      {
+        // Nothing to do here
+        break;
+      }
+      case CommandType::BIND_TEXTURES:
       {
-        if(binding.texture)
+        for(auto& binding : cmd.data.bindTextures.textureBindings)
         {
-          auto texture = Uncast<TestGraphicsTexture>(binding.texture);
-
-          texture->Bind(binding.binding);
-
-          if(binding.sampler)
+          if(binding.texture)
           {
-            auto sampler = Uncast<TestGraphicsSampler>(binding.sampler);
-            if(sampler)
+            auto texture = Uncast<TestGraphicsTexture>(binding.texture);
+            texture->Bind(binding.binding);
+
+            if(binding.sampler)
             {
-              sampler->Apply(texture->GetTarget());
+              auto sampler = Uncast<TestGraphicsSampler>(binding.sampler);
+              if(sampler)
+              {
+                sampler->Apply(texture->GetTarget());
+              }
             }
-          }
 
-          texture->Prepare(); // Ensure native texture is ready
+            texture->Prepare(); // Ensure native texture is ready
+          }
         }
+        break;
       }
-    }
-
-    // IndexBuffer binding,
-    auto bindIndexBufferCmds = commandBuffer->GetCommandsByType(0 | CommandType::BIND_INDEX_BUFFER);
-    if(!bindIndexBufferCmds.empty())
-    {
-      auto& indexBufferBinding = bindIndexBufferCmds[0]->data.bindIndexBuffer;
-      if(indexBufferBinding.buffer)
+      case CommandType::BIND_VERTEX_BUFFERS:
       {
-        auto buffer = Uncast<TestGraphicsBuffer>(indexBufferBinding.buffer);
-        buffer->Bind();
+        for(auto& binding : cmd.data.bindVertexBuffers.vertexBufferBindings)
+        {
+          auto graphicsBuffer = binding.buffer;
+          auto vertexBuffer   = Uncast<TestGraphicsBuffer>(graphicsBuffer);
+          vertexBuffer->Bind();
+        }
+        break;
       }
-    }
-
-    // VertexBuffer binding,
-    auto bindVertexBufferCmds = commandBuffer->GetCommandsByType(0 | CommandType::BIND_VERTEX_BUFFERS);
-    if(!bindVertexBufferCmds.empty())
-    {
-      for(auto& binding : bindVertexBufferCmds[0]->data.bindVertexBuffers.vertexBufferBindings)
+      case CommandType::BIND_INDEX_BUFFER:
       {
-        auto graphicsBuffer = binding.buffer;
-        auto vertexBuffer   = Uncast<TestGraphicsBuffer>(graphicsBuffer);
-        vertexBuffer->Bind();
+        auto& indexBufferBinding = cmd.data.bindIndexBuffer;
+        if(indexBufferBinding.buffer)
+        {
+          auto buffer = Uncast<TestGraphicsBuffer>(indexBufferBinding.buffer);
+          buffer->Bind();
+        }
+        break;
       }
-    }
-
-    bool scissorEnabled = false;
-
-    auto scissorTestList = commandBuffer->GetCommandsByType(0 | CommandType::SET_SCISSOR_TEST);
-    if(!scissorTestList.empty())
-    {
-      if(scissorTestList[0]->data.scissorTest.enable)
+      case CommandType::BIND_UNIFORM_BUFFER:
       {
-        mGl.Enable(GL_SCISSOR_TEST);
-        scissorEnabled = true;
+        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;
       }
-      else
+      case CommandType::BIND_SAMPLERS:
       {
-        mGl.Disable(GL_SCISSOR_TEST);
+        break;
       }
-    }
-
-    auto scissorList = commandBuffer->GetCommandsByType(0 | CommandType::SET_SCISSOR);
-    if(!scissorList.empty() && scissorEnabled)
-    {
-      auto& rect = scissorList[0]->data.scissor.region;
-      mGl.Scissor(rect.x, rect.y, rect.width, rect.height);
-    }
-
-    auto viewportList = commandBuffer->GetCommandsByType(0 | CommandType::SET_VIEWPORT);
-    if(!viewportList.empty())
-    {
-      mGl.Viewport(viewportList[0]->data.viewport.region.x, viewportList[0]->data.viewport.region.y, viewportList[0]->data.viewport.region.width, viewportList[0]->data.viewport.region.height);
-    }
-
-    // ignore viewport enable
+      case CommandType::BIND_PIPELINE:
+      {
+        currentPipeline = Uncast<TestGraphicsPipeline>(cmd.data.bindPipeline.pipeline);
 
-    // Pipeline attribute setup
-    auto bindPipelineCmds = commandBuffer->GetCommandsByType(0 | CommandType::BIND_PIPELINE);
-    if(!bindPipelineCmds.empty())
-    {
-      auto  pipeline = bindPipelineCmds[0]->data.bindPipeline.pipeline;
-      auto& vi       = pipeline->vertexInputState;
-      for(auto& attribute : vi.attributes)
+        // Bind framebuffer if different. @todo Move to RenderPass
+        auto framebuffer = currentPipeline->framebufferState.framebuffer;
+        if(framebuffer && framebuffer != currentFramebuffer)
+        {
+          auto graphicsFramebuffer = Uncast<TestGraphicsFramebuffer>(framebuffer);
+          graphicsFramebuffer->Bind();
+        }
+        else
+        {
+          if(currentFramebuffer)
+            currentFramebuffer->Bind();
+          else
+            mGl.BindFramebuffer(GL_FRAMEBUFFER, 0);
+        }
+        BindPipeline(currentPipeline);
+        break;
+      }
+      case CommandType::DRAW:
       {
-        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));
+        mGl.DrawArrays(GetTopology(currentPipeline->inputAssemblyState.topology),
+                       0,
+                       cmd.data.draw.draw.vertexCount);
+        break;
       }
-
-      // Cull face setup
-      auto& rasterizationState = pipeline->rasterizationState;
-      if(rasterizationState.cullMode == Graphics::CullMode::NONE)
+      case CommandType::DRAW_INDEXED:
       {
-        mGl.Disable(GL_CULL_FACE);
+        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;
       }
-      else
+      case CommandType::DRAW_INDEXED_INDIRECT:
       {
-        mGl.Enable(GL_CULL_FACE);
-        mGl.CullFace(GetCullFace(rasterizationState.cullMode));
+        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;
       }
-
-      mGl.FrontFace(GetFrontFace(rasterizationState.frontFace));
-      // We don't modify glPolygonMode in our context/abstraction from GL_FILL (the GL default),
-      // so it isn't present in the API (and won't have any tests!)
-
-      // Blending setup
-      auto& colorBlendState = pipeline->colorBlendState;
-      if(colorBlendState.blendEnable)
+      case CommandType::SET_SCISSOR:
       {
-        mGl.Enable(GL_BLEND);
-
-        mGl.BlendFuncSeparate(GetBlendFactor(colorBlendState.srcColorBlendFactor),
-                              GetBlendFactor(colorBlendState.dstColorBlendFactor),
-                              GetBlendFactor(colorBlendState.srcAlphaBlendFactor),
-                              GetBlendFactor(colorBlendState.dstAlphaBlendFactor));
-        if(colorBlendState.colorBlendOp != colorBlendState.alphaBlendOp)
+        if(scissorEnabled)
         {
-          mGl.BlendEquationSeparate(GetBlendOp(colorBlendState.colorBlendOp), GetBlendOp(colorBlendState.alphaBlendOp));
+          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.BlendEquation(GetBlendOp(colorBlendState.colorBlendOp));
+          mGl.Disable(GL_SCISSOR_TEST);
+          scissorEnabled = false;
         }
-        mGl.BlendColor(colorBlendState.blendConstants[0],
-                       colorBlendState.blendConstants[1],
-                       colorBlendState.blendConstants[2],
-                       colorBlendState.blendConstants[3]);
+        break;
       }
-      else
+      case CommandType::SET_VIEWPORT_TEST:
       {
-        mGl.Disable(GL_BLEND);
+        break;
       }
-
-      // draw call
-      auto topology = pipeline->inputAssemblyState.topology;
-
-      // UniformBuffer binding (once we know pipeline)
-      auto bindUniformBuffersCmds = commandBuffer->GetCommandsByType(0 | CommandType::BIND_UNIFORM_BUFFER);
-      if(!bindUniformBuffersCmds.empty())
+      case CommandType::SET_VIEWPORT: // @todo Consider correcting for orientation here?
       {
-        auto buffer = bindUniformBuffersCmds[0]->data.bindUniformBuffers.standaloneUniformsBufferBinding;
-
-        // based on reflection, issue gl calls
-        buffer.buffer->BindAsUniformBuffer(static_cast<const TestGraphicsProgram*>(pipeline->programState.program));
+        auto& rect = cmd.data.viewport.region;
+        mGl.Viewport(rect.x, rect.y, rect.width, rect.height);
+        break;
       }
-
-      auto drawCmds = commandBuffer->GetCommandsByType(0 |
-                                                       CommandType::DRAW |
-                                                       CommandType::DRAW_INDEXED_INDIRECT |
-                                                       CommandType::DRAW_INDEXED);
-
-      if(!drawCmds.empty())
+      case CommandType::EXECUTE_COMMAND_BUFFERS:
+      {
+        // Process secondary command buffers
+        for(auto& buf : cmd.data.executeCommandBuffers.buffers)
+        {
+          ProcessCommandBuffer(*static_cast<TestGraphicsCommandBuffer*>(buf));
+        }
+        break;
+      }
+      case CommandType::BEGIN_RENDER_PASS:
       {
-        if(drawCmds[0]->data.draw.type == DrawCallDescriptor::Type::DRAW_INDEXED)
+        auto renderTarget = Uncast<TestGraphicsRenderTarget>(cmd.data.beginRenderPass.renderTarget);
+
+        if(renderTarget)
         {
-          mGl.DrawElements(GetTopology(topology),
-                           static_cast<GLsizei>(drawCmds[0]->data.draw.drawIndexed.indexCount),
-                           GL_UNSIGNED_SHORT,
-                           reinterpret_cast<void*>(drawCmds[0]->data.draw.drawIndexed.firstIndex));
+          auto fb = renderTarget->mCreateInfo.framebuffer;
+          if(fb)
+          {
+            if(currentFramebuffer != fb)
+            {
+              currentFramebuffer = Uncast<TestGraphicsFramebuffer>(fb);
+              currentFramebuffer->Bind();
+            }
+          }
+          else
+          {
+            mGl.BindFramebuffer(GL_FRAMEBUFFER, 0);
+          }
         }
         else
         {
-          mGl.DrawArrays(GetTopology(topology), 0, drawCmds[0]->data.draw.draw.vertexCount);
+          mGl.BindFramebuffer(GL_FRAMEBUFFER, 0);
+        }
+
+        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)
+              {
+                mask |= GL_DEPTH_BUFFER_BIT;
+              }
+              if(depthStencil.stencilLoadOp == Graphics::AttachmentLoadOp::CLEAR)
+              {
+                mask |= GL_STENCIL_BUFFER_BIT;
+              }
+            }
+
+            if(mask != 0)
+            {
+              mGl.Clear(mask);
+            }
+          }
+          else
+          {
+            DALI_ASSERT_DEBUG(0 && "BeginRenderPass has no render pass");
+          }
         }
+        break;
       }
-      // attribute clear
-      for(auto& attribute : vi.attributes)
+      case CommandType::END_RENDER_PASS:
       {
-        mGl.DisableVertexAttribArray(attribute.location);
+        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);
+  }
+}
+
 /**
  * @brief Presents render target
  * @param renderTarget render target to present
@@ -770,7 +892,7 @@ Graphics::UniquePtr<Graphics::CommandBuffer> TestGraphicsController::CreateComma
 Graphics::UniquePtr<Graphics::RenderPass> TestGraphicsController::CreateRenderPass(const Graphics::RenderPassCreateInfo& renderPassCreateInfo, Graphics::UniquePtr<Graphics::RenderPass>&& oldRenderPass)
 {
   mCallStack.PushCall("CreateRenderPass", "");
-  return nullptr;
+  return Graphics::MakeUnique<TestGraphicsRenderPass>(mGl, renderPassCreateInfo);
 }
 
 Graphics::UniquePtr<Graphics::Texture> TestGraphicsController::CreateTexture(const Graphics::TextureCreateInfo& textureCreateInfo, Graphics::UniquePtr<Graphics::Texture>&& oldTexture)
@@ -782,10 +904,15 @@ Graphics::UniquePtr<Graphics::Texture> TestGraphicsController::CreateTexture(con
   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("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)
@@ -849,7 +976,7 @@ Graphics::UniquePtr<Graphics::Sampler> TestGraphicsController::CreateSampler(con
 Graphics::UniquePtr<Graphics::RenderTarget> TestGraphicsController::CreateRenderTarget(const Graphics::RenderTargetCreateInfo& renderTargetCreateInfo, Graphics::UniquePtr<Graphics::RenderTarget>&& oldRenderTarget)
 {
   mCallStack.PushCall("CreateRenderTarget", "");
-  return nullptr;
+  return Graphics::MakeUnique<TestGraphicsRenderTarget>(mGl, renderTargetCreateInfo);
 }
 
 Graphics::UniquePtr<Graphics::Memory> TestGraphicsController::MapBufferRange(const Graphics::MapBufferInfo& mapInfo)