Fix pipeline hash
[platform/core/uifw/dali-core.git] / dali / internal / render / renderers / pipeline-cache.cpp
index 4d771b9..c2545c0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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 <dali/internal/render/renderers/pipeline-cache.h>
 
 // INTERNAL INCLUDES
-#include <dali/internal/render/renderers/render-renderer.h>
 #include <dali/graphics-api/graphics-types.h>
 #include <dali/integration-api/debug.h>
 #include <dali/internal/render/common/render-instruction.h>
+#include <dali/internal/render/renderers/render-renderer.h>
 #include <dali/internal/render/renderers/render-vertex-buffer.h>
 #include <dali/internal/render/shaders/program.h>
 
@@ -29,12 +29,14 @@ namespace Dali::Internal::Render
 {
 namespace
 {
+constexpr uint32_t CACHE_CLEAN_FRAME_COUNT = 600; // 60fps * 10sec
+
 // Helper to get the vertex input format
 Dali::Graphics::VertexInputFormat GetPropertyVertexFormat(Property::Type propertyType)
 {
   Dali::Graphics::VertexInputFormat type{};
 
-  switch (propertyType)
+  switch(propertyType)
   {
     case Property::BOOLEAN:
     {
@@ -77,7 +79,7 @@ Dali::Graphics::VertexInputFormat GetPropertyVertexFormat(Property::Type propert
 
 constexpr Graphics::CullMode ConvertCullFace(Dali::FaceCullingMode::Type mode)
 {
-  switch (mode)
+  switch(mode)
   {
     case Dali::FaceCullingMode::NONE:
     {
@@ -104,7 +106,7 @@ constexpr Graphics::CullMode ConvertCullFace(Dali::FaceCullingMode::Type mode)
 
 constexpr Graphics::BlendFactor ConvertBlendFactor(BlendFactor::Type blendFactor)
 {
-  switch (blendFactor)
+  switch(blendFactor)
   {
     case BlendFactor::ZERO:
       return Graphics::BlendFactor::ZERO;
@@ -143,7 +145,7 @@ constexpr Graphics::BlendFactor ConvertBlendFactor(BlendFactor::Type blendFactor
 
 constexpr Graphics::BlendOp ConvertBlendEquation(DevelBlendEquation::Type blendEquation)
 {
-  switch (blendEquation)
+  switch(blendEquation)
   {
     case DevelBlendEquation::ADD:
       return Graphics::BlendOp::ADD;
@@ -152,88 +154,108 @@ constexpr Graphics::BlendOp ConvertBlendEquation(DevelBlendEquation::Type blendE
     case DevelBlendEquation::REVERSE_SUBTRACT:
       return Graphics::BlendOp::REVERSE_SUBTRACT;
     case DevelBlendEquation::COLOR:
+      return Graphics::BlendOp::COLOR;
     case DevelBlendEquation::COLOR_BURN:
+      return Graphics::BlendOp::COLOR_BURN;
     case DevelBlendEquation::COLOR_DODGE:
+      return Graphics::BlendOp::COLOR_DODGE;
     case DevelBlendEquation::DARKEN:
+      return Graphics::BlendOp::DARKEN;
     case DevelBlendEquation::DIFFERENCE:
+      return Graphics::BlendOp::DIFFERENCE;
     case DevelBlendEquation::EXCLUSION:
+      return Graphics::BlendOp::EXCLUSION;
     case DevelBlendEquation::HARD_LIGHT:
+      return Graphics::BlendOp::HARD_LIGHT;
     case DevelBlendEquation::HUE:
+      return Graphics::BlendOp::HUE;
     case DevelBlendEquation::LIGHTEN:
+      return Graphics::BlendOp::LIGHTEN;
     case DevelBlendEquation::LUMINOSITY:
+      return Graphics::BlendOp::LUMINOSITY;
     case DevelBlendEquation::MAX:
+      return Graphics::BlendOp::MAX;
     case DevelBlendEquation::MIN:
+      return Graphics::BlendOp::MIN;
     case DevelBlendEquation::MULTIPLY:
+      return Graphics::BlendOp::MULTIPLY;
     case DevelBlendEquation::OVERLAY:
+      return Graphics::BlendOp::OVERLAY;
     case DevelBlendEquation::SATURATION:
+      return Graphics::BlendOp::SATURATION;
     case DevelBlendEquation::SCREEN:
+      return Graphics::BlendOp::SCREEN;
     case DevelBlendEquation::SOFT_LIGHT:
-      return Graphics::BlendOp{};
+      return Graphics::BlendOp::SOFT_LIGHT;
   }
   return Graphics::BlendOp{};
 }
-}
-
+} // namespace
 
-PipelineCacheL0 *PipelineCache::GetPipelineCacheL0(Program *program, Render::Geometry *geometry)
+PipelineCacheL0Ptr PipelineCache::GetPipelineCacheL0(Program* program, Render::Geometry* geometry)
 {
-  auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [program, geometry](
-    PipelineCacheL0 &item)
-  {
+  auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [program, geometry](PipelineCacheL0& item) {
     return ((item.program == program && item.geometry == geometry));
   });
 
-  std::vector<int32_t> attributeLocations;
-
   // Add new node to cache
-  if (it == level0nodes.end())
+  if(it == level0nodes.end())
   {
     uint32_t bindingIndex{0u};
-    auto     &reflection            = graphicsController->GetProgramReflection(program->GetGraphicsProgram());
+    auto&    reflection = graphicsController->GetProgramReflection(program->GetGraphicsProgram());
 
     Graphics::VertexInputState vertexInputState{};
     uint32_t                   base = 0;
 
-    for (auto &&vertexBuffer : geometry->GetVertexBuffers())
+    for(auto&& vertexBuffer : geometry->GetVertexBuffers())
     {
-      const VertexBuffer::Format &vertexFormat = *vertexBuffer->GetFormat();
+      const VertexBuffer::Format& vertexFormat = *vertexBuffer->GetFormat();
+
+      uint32_t                  divisor         = vertexBuffer->GetDivisor();
+      Graphics::VertexInputRate vertexInputRate = (divisor == 0
+                                                     ? Graphics::VertexInputRate::PER_VERTEX
+                                                     : Graphics::VertexInputRate::PER_INSTANCE);
 
       vertexInputState.bufferBindings.emplace_back(vertexFormat.size, // stride
-                                                   Graphics::VertexInputRate::PER_VERTEX);
+                                                   vertexInputRate);
+      //@todo Add the actual rate to the graphics struct
 
-      const uint32_t attributeCount = vertexBuffer->GetAttributeCount();
-      for (uint32_t  i              = 0; i < attributeCount; ++i)
+      const uint32_t attributeCount          = vertexBuffer->GetAttributeCount();
+      uint32_t       lastBoundAttributeIndex = 0;
+      for(uint32_t i = 0; i < attributeCount; ++i)
       {
         auto    attributeName = vertexBuffer->GetAttributeName(i);
         int32_t pLocation     = reflection.GetVertexAttributeLocation(std::string(attributeName.GetStringView()));
-        if (-1 == pLocation)
+        if(-1 != pLocation)
+        {
+          auto location = static_cast<uint32_t>(pLocation);
+          vertexInputState.attributes.emplace_back(location,
+                                                   bindingIndex,
+                                                   vertexFormat.components[i].offset,
+                                                   GetPropertyVertexFormat(vertexFormat.components[i].type));
+          ++lastBoundAttributeIndex;
+        }
+        else
         {
           DALI_LOG_WARNING("Attribute not found in the shader: %s\n", attributeName.GetCString());
+          // Don't bind unused attributes.
         }
-        attributeLocations.emplace_back(pLocation);
-
-        auto location = static_cast<uint32_t>(attributeLocations[base + i]);
-
-        vertexInputState.attributes.emplace_back(location,
-                                                 bindingIndex,
-                                                 vertexFormat.components[i].offset,
-                                                 GetPropertyVertexFormat(vertexFormat.components[i].type));
       }
-      base += attributeCount;
+      base += lastBoundAttributeIndex;
       ++bindingIndex;
     }
     PipelineCacheL0 level0;
     level0.program    = program;
     level0.geometry   = geometry;
     level0.inputState = vertexInputState;
-    level0nodes.emplace_back(std::move(level0));
-    it = level0nodes.end() - 1;
+
+    it = level0nodes.insert(level0nodes.end(), std::move(level0));
   }
 
-  return &*it;
+  return it;
 }
 
-PipelineCacheL1 *PipelineCacheL0::GetPipelineCacheL1(Render::Renderer *renderer, bool usingReflection)
+PipelineCacheL1Ptr PipelineCacheL0::GetPipelineCacheL1(Render::Renderer* renderer, bool usingReflection)
 {
   // hash must be collision free
   uint32_t hash = 0;
@@ -247,47 +269,40 @@ PipelineCacheL1 *PipelineCacheL0::GetPipelineCacheL1(Render::Renderer *renderer,
     Graphics::PolygonMode::LINE,
     Graphics::PolygonMode::FILL,
     Graphics::PolygonMode::FILL,
-    Graphics::PolygonMode::FILL
-  };
+    Graphics::PolygonMode::FILL};
 
   auto poly = polyTable[topo];
 
   static const FaceCullingMode::Type adjFaceCullingMode[4] =
-                                       {
-                                         FaceCullingMode::NONE,
-                                         FaceCullingMode::BACK,
-                                         FaceCullingMode::FRONT,
-                                         FaceCullingMode::FRONT_AND_BACK,
-                                       };
+    {
+      FaceCullingMode::NONE,
+      FaceCullingMode::BACK,
+      FaceCullingMode::FRONT,
+      FaceCullingMode::FRONT_AND_BACK,
+    };
 
   static const FaceCullingMode::Type normalFaceCullingMode[4] =
-                                       {
-                                         FaceCullingMode::NONE,
-                                         FaceCullingMode::FRONT,
-                                         FaceCullingMode::BACK,
-                                         FaceCullingMode::FRONT_AND_BACK,
-                                       };
-
-  static const FaceCullingMode::Type *cullModeTable[2] = {
+    {
+      FaceCullingMode::NONE,
+      FaceCullingMode::FRONT,
+      FaceCullingMode::BACK,
+      FaceCullingMode::FRONT_AND_BACK,
+    };
+
+  static const FaceCullingMode::TypecullModeTable[2] = {
     normalFaceCullingMode,
-    adjFaceCullingMode
-  };
+    adjFaceCullingMode};
 
   // Retrieve cull mode
   auto cullModeTableIndex = uint32_t(usingReflection) & 1u;
-  cull = cullModeTable[cullModeTableIndex][renderer->GetFaceCullMode()];
+  cull                    = cullModeTable[cullModeTableIndex][renderer->GetFaceCullMode()];
 
   hash = (topo & 0xffu) | ((cull << 8u) & 0xff00u) | ((uint32_t(poly) << 16u) & 0xff0000u);
 
   // If L1 not found by hash, create rasterization state describing pipeline and store it
-  auto it = std::find_if(level1nodes.begin(), level1nodes.end(),
-                         [hash](PipelineCacheL1 &item)
-                         {
-                           return item.hashCode == hash;
-                         });
-
-  PipelineCacheL1 *retval = nullptr;
-  if (it == level1nodes.end())
+  auto it = std::find_if(level1nodes.begin(), level1nodes.end(), [hash](PipelineCacheL1& item) { return item.hashCode == hash; });
+
+  if(it == level1nodes.end())
   {
     PipelineCacheL1 item;
     item.hashCode       = hash;
@@ -295,70 +310,86 @@ PipelineCacheL1 *PipelineCacheL0::GetPipelineCacheL1(Render::Renderer *renderer,
     item.rs.frontFace   = Graphics::FrontFace::COUNTER_CLOCKWISE;
     item.rs.polygonMode = poly; // not in use
     item.ia.topology    = geometry->GetTopology();
-    level1nodes.emplace_back(std::move(item));
-    retval = &level1nodes.back();
+
+    it = level1nodes.insert(level1nodes.end(), std::move(item));
   }
-  else
+
+  return it;
+}
+
+void PipelineCacheL0::ClearUnusedCache()
+{
+  for(auto iter = level1nodes.begin(); iter != level1nodes.end();)
   {
-    retval = &*it;
+    if(iter->ClearUnusedCache())
+    {
+      iter = level1nodes.erase(iter);
+    }
+    else
+    {
+      iter++;
+    }
   }
-  return retval;
 }
 
-PipelineCacheL2 *PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions &blendingOptions)
+PipelineCacheL2Ptr PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions)
 {
   // early out
-  if (!blend)
+  if(!blend)
   {
-    if (noBlend.pipeline == nullptr)
+    if(DALI_UNLIKELY(noBlends.empty()))
+    {
+      noBlends.emplace_back(PipelineCacheL2{});
+    }
+
+    auto& noBlend = *noBlends.begin();
+
+    if(noBlend.pipeline == nullptr)
     {
       // reset all before returning if pipeline has never been created for that case
       noBlend.hash = 0;
       memset(&noBlend.colorBlendState, 0, sizeof(Graphics::ColorBlendState));
     }
-    return &noBlend;
+    return noBlends.begin();
   }
 
   auto bitmask = uint32_t(blendingOptions.GetBitmask());
 
   // Find by bitmask (L2 entries must be sorted by bitmask)
-  auto it = std::find_if(level2nodes.begin(), level2nodes.end(), [bitmask](PipelineCacheL2 &item)
-  {
-    return item.hash == bitmask;
-  });
+  auto it = std::find_if(level2nodes.begin(), level2nodes.end(), [bitmask](PipelineCacheL2& item) { return item.hash == bitmask; });
 
   // TODO: find better way of blend constants lookup
-  PipelineCacheL2 *retval = nullptr;
-  if (it != level2nodes.end())
+  PipelineCacheL2Ptr retval = level2nodes.end();
+  if(it != level2nodes.end())
   {
     bool hasBlendColor = blendingOptions.GetBlendColor();
     while(hasBlendColor && it != level2nodes.end() && (*it).hash == bitmask)
     {
-      Vector4 v( it->colorBlendState.blendConstants );
-      if( v == *blendingOptions.GetBlendColor() )
+      Vector4 v(it->colorBlendState.blendConstants);
+      if(v == *blendingOptions.GetBlendColor())
       {
-        retval = &*it;
+        retval = it;
       }
       ++it;
     }
     if(!hasBlendColor)
     {
-      retval = &*it;
+      retval = it;
     }
   }
 
-  if (!retval)
+  if(retval == level2nodes.end())
   {
     // create new entry and return it with null pipeline
-    PipelineCacheL2 l2;
-    l2.pipeline = nullptr;
-    auto &colorBlendState = l2.colorBlendState;
+    PipelineCacheL2 l2{};
+    l2.pipeline           = nullptr;
+    autocolorBlendState = l2.colorBlendState;
     colorBlendState.SetBlendEnable(true);
     Graphics::BlendOp rgbOp   = ConvertBlendEquation(blendingOptions.GetBlendEquationRgb());
-    Graphics::BlendOp alphaOp = ConvertBlendEquation(blendingOptions.GetBlendEquationRgb());
-    if (blendingOptions.IsAdvancedBlendEquationApplied() && premul)
+    Graphics::BlendOp alphaOp = ConvertBlendEquation(blendingOptions.GetBlendEquationAlpha());
+    if(blendingOptions.IsAdvancedBlendEquationApplied() && premul)
     {
-      if (rgbOp != alphaOp)
+      if(rgbOp != alphaOp)
       {
         DALI_LOG_ERROR("Advanced Blend Equation MUST be applied by using BlendEquation.\n");
         alphaOp = rgbOp;
@@ -374,19 +405,17 @@ PipelineCacheL2 *PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, Bl
       .SetAlphaBlendOp(alphaOp);
 
     // Blend color is optional and rarely used
-    auto *blendColor = const_cast<Vector4 *>(blendingOptions.GetBlendColor());
-    if (blendColor)
+    auto* blendColor = const_cast<Vector4*>(blendingOptions.GetBlendColor());
+    if(blendColor)
     {
       colorBlendState.SetBlendConstants(blendColor->AsFloat());
     }
 
     l2.hash = blendingOptions.GetBitmask();
-    level2nodes.emplace_back(std::move(l2));
 
-    std::sort(level2nodes.begin(), level2nodes.end(), [](PipelineCacheL2 &lhs, PipelineCacheL2 &rhs)
-    {
-      return lhs.hash < rhs.hash;
-    });
+    auto upperBound = std::upper_bound(level2nodes.begin(), level2nodes.end(), l2, [](const PipelineCacheL2& lhs, const PipelineCacheL2& rhs) { return lhs.hash < rhs.hash; });
+
+    level2nodes.insert(upperBound, std::move(l2));
 
     // run same function to retrieve retval
     retval = GetPipelineCacheL2(blend, premul, blendingOptions);
@@ -395,19 +424,88 @@ PipelineCacheL2 *PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, Bl
   return retval;
 }
 
-PipelineCache::PipelineCache(Graphics::Controller& controller) :
-graphicsController(&controller)
+bool PipelineCacheL1::ClearUnusedCache()
 {
+  for(auto iter = level2nodes.begin(); iter != level2nodes.end();)
+  {
+    if(iter->referenceCount == 0)
+    {
+      iter = level2nodes.erase(iter);
+    }
+    else
+    {
+      iter++;
+    }
+  }
+
+  if(!noBlends.empty() && noBlends.begin()->referenceCount > 0)
+  {
+    return false;
+  }
+
+  return level2nodes.empty();
 }
 
-PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo &queryInfo, bool createNewIfNotFound)
+void PipelineCacheQueryInfo::GenerateHash()
 {
-  auto *level0 = GetPipelineCacheL0(queryInfo.program, queryInfo.geometry);
-  auto *level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
-  auto *level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
+  // Lightweight hash value generation.
+  hash = (reinterpret_cast<std::size_t>(program) >> Dali::Log<sizeof(decltype(*program))>::value) ^
+         (reinterpret_cast<std::size_t>(geometry) >> Dali::Log<sizeof(decltype(*geometry))>::value) ^
+         ((blendingEnabled ? 1u : 0u) << 0u) ^
+         ((alphaPremultiplied ? 1u : 0u) << 1u) ^
+         (static_cast<std::size_t>(geometry->GetTopology()) << 2u) ^
+         (static_cast<std::size_t>(renderer->GetFaceCullMode()) << 5u) ^
+         ((cameraUsingReflection ? 1u : 0u) << 8u) ^
+         (blendingEnabled ? static_cast<std::size_t>(blendingOptions->GetBitmask()) : 0xDA11u);
+}
+
+bool PipelineCacheQueryInfo::Equal(const PipelineCacheQueryInfo& lhs, const PipelineCacheQueryInfo& rhs) noexcept
+{
+  // Naive equal check.
+  const bool ret = (lhs.hash == rhs.hash) && // Check hash value first
+                   (lhs.program == rhs.program) &&
+                   (lhs.geometry == rhs.geometry) &&
+                   (lhs.blendingEnabled == rhs.blendingEnabled) &&
+                   (lhs.alphaPremultiplied == rhs.alphaPremultiplied) &&
+                   (lhs.geometry->GetTopology() == rhs.geometry->GetTopology()) &&
+                   (lhs.renderer->GetFaceCullMode() == rhs.renderer->GetFaceCullMode()) &&
+                   (lhs.cameraUsingReflection == rhs.cameraUsingReflection) &&
+                   (!lhs.blendingEnabled ||
+                    (lhs.blendingOptions->GetBitmask() == rhs.blendingOptions->GetBitmask() &&
+                     ((lhs.blendingOptions->GetBlendColor() == nullptr && rhs.blendingOptions->GetBlendColor() == nullptr) ||
+                      (lhs.blendingOptions->GetBlendColor() &&
+                       rhs.blendingOptions->GetBlendColor() &&
+                       (*lhs.blendingOptions->GetBlendColor() == *rhs.blendingOptions->GetBlendColor())))));
+
+  return ret;
+}
+
+PipelineCache::PipelineCache(Graphics::Controller& controller)
+: graphicsController(&controller)
+{
+  // Clean up cache first
+  CleanLatestUsedCache();
+}
+
+PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
+{
+  // Seperate branch whether query use blending or not.
+  const int latestUsedCacheIndex = queryInfo.blendingEnabled ? 0 : 1;
+
+  // If we can reuse latest bound pipeline, Fast return.
+  if(ReuseLatestBoundPipeline(latestUsedCacheIndex, queryInfo))
+  {
+    mLatestResult[latestUsedCacheIndex].level2->referenceCount++;
+    return mLatestResult[latestUsedCacheIndex];
+  }
+
+  auto level0 = GetPipelineCacheL0(queryInfo.program, queryInfo.geometry);
+  auto level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
+
+  PipelineCachePtr level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
 
   // Create new pipeline at level2 if requested
-  if (level2->pipeline == nullptr && createNewIfNotFound)
+  if(level2->pipeline == nullptr && createNewIfNotFound)
   {
     Graphics::ProgramState programState{};
     programState.program = &queryInfo.program->GetGraphicsProgram();
@@ -422,20 +520,61 @@ PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo &queryInf
 
     // Store a pipeline per renderer per render (renderer can be owned by multiple nodes,
     // and re-drawn in multiple instructions).
-    // @todo This is only needed because ColorBlend state can change. Fixme!
-    // This is ameliorated by the fact that implementation caches pipelines, and we're only storing
-    // handles.
     level2->pipeline = graphicsController->CreatePipeline(createInfo, nullptr);
   }
 
   PipelineResult result{};
 
   result.pipeline = level2->pipeline.get();
-  result.level0   = level0;
-  result.level1   = level1;
   result.level2   = level2;
 
+  level2->referenceCount++;
+
+  // Copy query and result
+  mLatestQuery[latestUsedCacheIndex]  = queryInfo;
+  mLatestResult[latestUsedCacheIndex] = result;
+
   return result;
 }
 
-}
\ No newline at end of file
+bool PipelineCache::ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const
+{
+  return mLatestResult[latestUsedCacheIndex].pipeline != nullptr && PipelineCacheQueryInfo::Equal(queryInfo, mLatestQuery[latestUsedCacheIndex]);
+}
+
+void PipelineCache::PreRender()
+{
+  CleanLatestUsedCache();
+
+  // We don't need to check this every frame
+  if(++mFrameCount >= CACHE_CLEAN_FRAME_COUNT)
+  {
+    mFrameCount = 0u;
+    ClearUnusedCache();
+  }
+}
+
+void PipelineCache::ClearUnusedCache()
+{
+  for(auto iter = level0nodes.begin(); iter != level0nodes.end();)
+  {
+    iter->ClearUnusedCache();
+
+    if(iter->level1nodes.empty())
+    {
+      iter = level0nodes.erase(iter);
+    }
+    else
+    {
+      iter++;
+    }
+  }
+}
+
+void PipelineCache::ResetPipeline(PipelineCachePtr pipelineCache)
+{
+  // TODO : Can we always assume that pipelineCache input is valid iterator?
+  pipelineCache->referenceCount--;
+}
+
+} // namespace Dali::Internal::Render