Reuse latest pipeline if possible 71/289371/9
authorEunki Hong <eunkiki.hong@samsung.com>
Mon, 6 Mar 2023 18:54:48 +0000 (03:54 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Tue, 14 Mar 2023 14:19:07 +0000 (23:19 +0900)
Search pipeline cache is quite heay operation.
So if we can re-use pipeline, It will be helpful
if we use equal shader continously.

It will give power for 3D layer who did depth test, so
rendering order is determined by shader, texture and geometry.

Change-Id: I3bf9abab125b6adebad5ecc6bb04463d7e3db972
Signed-off-by: Eunki Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali-internal/utc-Dali-Internal-PipelineCache.cpp
automated-tests/src/dali/utc-Dali-Renderer.cpp
dali/internal/render/common/render-manager.cpp
dali/internal/render/renderers/pipeline-cache.cpp
dali/internal/render/renderers/pipeline-cache.h
dali/internal/render/renderers/render-renderer.cpp

index 4f3bb13..9783618 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.
 
 using namespace Dali;
 
-
-
 template<class Object, class... Args>
-void InvokeNext(Object *obj, Args... args)
+void InvokeNext(Objectobj, Args... args)
 {
-  auto addr = __builtin_return_address(0);
+  auto    addr = __builtin_return_address(0);
   Dl_info info;
   dladdr(addr, &info);
   auto func = dlsym(RTLD_NEXT, info.dli_sname);
-  typedef void(*FuncPtr)(void*, Args...);
+  typedef void (*FuncPtr)(void*, Args...);
   auto memb = FuncPtr(func);
   memb(obj, args...);
 }
 
 template<class Ret, class Object, class... Args>
-Ret InvokeReturnNext(Object *obj, Args... args)
+Ret InvokeReturnNext(Objectobj, Args... args)
 {
-  auto addr = __builtin_return_address(0);
+  auto    addr = __builtin_return_address(0);
   Dl_info info;
   dladdr(addr, &info);
   auto func = dlsym(RTLD_NEXT, info.dli_sname);
-  typedef Ret(*FuncPtr)(void*, Args...);
+  typedef Ret (*FuncPtr)(void*, Args...);
   auto memb = FuncPtr(func);
   return memb(obj, args...);
 }
@@ -60,18 +58,17 @@ namespace Internal
 {
 namespace Render
 {
-
 // Store internal PipelineCache as singleton
 
 PipelineCache::PipelineCache(Dali::Graphics::Controller& controller)
 {
   gPipelineCache = this;
-  InvokeNext( this, &controller );
+  InvokeNext(this, &controller);
 }
 
-}
-}
-}
+} // namespace Render
+} // namespace Internal
+} // namespace Dali
 
 int UtcDaliCorePipelineCacheTest(void)
 {
@@ -82,10 +79,10 @@ int UtcDaliCorePipelineCacheTest(void)
 
   // PipelineCache* cache = PipelineCache::GetPipelineCacheWithController( &application.GetGraphicsController() );
   // Pipeline cache must be initialized
-  DALI_TEST_EQUALS( gPipelineCache != 0, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(gPipelineCache != 0, true, TEST_LOCATION);
 
   // Test size of level0 nodes (should be 0, nothing added yet)
-  DALI_TEST_EQUALS( (gPipelineCache->level0nodes.size() == 0), true, TEST_LOCATION);
+  DALI_TEST_EQUALS((gPipelineCache->level0nodes.size() == 0), true, TEST_LOCATION);
 
   // Create something to render
   Geometry   geometry   = CreateQuadGeometry();
@@ -107,11 +104,11 @@ int UtcDaliCorePipelineCacheTest(void)
   application.Render();
 
   // 1 pipeline should be added
-  DALI_TEST_EQUALS( (gPipelineCache->level0nodes.size() == 1), true, TEST_LOCATION);
+  DALI_TEST_EQUALS((gPipelineCache->level0nodes.size() == 1), true, TEST_LOCATION);
 
   // Add another actor, new pipeline will be created
-  Shader shader1    = Shader::New("newVertexSrc", "newFragmentSrc");
-  Actor actor1 = Actor::New();
+  Shader   shader1   = Shader::New("newVertexSrc", "newFragmentSrc");
+  Actor    actor1    = Actor::New();
   Renderer renderer1 = Renderer::New(geometry, shader1);
   renderer1.SetProperty(Dali::Renderer::Property::BLEND_MODE, Dali::BlendMode::ON);
   actor1.AddRenderer(renderer1);
@@ -121,11 +118,11 @@ int UtcDaliCorePipelineCacheTest(void)
   application.SendNotification();
   application.Render();
 
-  DALI_TEST_EQUALS( (gPipelineCache->level0nodes.size() == 2), true, TEST_LOCATION);
+  DALI_TEST_EQUALS((gPipelineCache->level0nodes.size() == 2), true, TEST_LOCATION);
 
   // Now add 3rd actor reusing first pipeline
   {
-    Actor actor2 = Actor::New();
+    Actor    actor2    = Actor::New();
     Renderer renderer2 = Renderer::New(geometry, shader);
     renderer2.SetProperty(Dali::Renderer::Property::BLEND_MODE, Dali::BlendMode::ON);
     actor2.AddRenderer(renderer);
@@ -136,11 +133,11 @@ int UtcDaliCorePipelineCacheTest(void)
   application.Render();
 
   // Number of pipelines shouldn't change
-  DALI_TEST_EQUALS( (gPipelineCache->level0nodes.size() == 2), true, TEST_LOCATION);
+  DALI_TEST_EQUALS((gPipelineCache->level0nodes.size() == 2), true, TEST_LOCATION);
 
   // Test final 'noBlend' path on first pipeline
   {
-    Actor actor3 = Actor::New();
+    Actor    actor3    = Actor::New();
     Renderer renderer3 = Renderer::New(geometry, shader);
     renderer3.SetProperty(Dali::Renderer::Property::BLEND_MODE, Dali::BlendMode::OFF);
     actor3.AddRenderer(renderer3);
@@ -151,7 +148,18 @@ int UtcDaliCorePipelineCacheTest(void)
   application.Render();
 
   // Test whether noBlend pipeline is set in cache
-  DALI_TEST_EQUALS( gPipelineCache->level0nodes[0].level1nodes[0].noBlend.pipeline != nullptr, true, TEST_LOCATION);
+  uint32_t noBlendFoundCount = 0u;
+  for(auto& iterLevel0 : gPipelineCache->level0nodes)
+  {
+    for(auto& iterLevel1 : iterLevel0.level1nodes)
+    {
+      if(iterLevel1.noBlend.pipeline != nullptr)
+      {
+        noBlendFoundCount++;
+      }
+    }
+  }
+  DALI_TEST_EQUALS(noBlendFoundCount, 1u, TEST_LOCATION);
 
   END_TEST;
 }
\ No newline at end of file
index 67fa251..763fa67 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 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.
index b72ec9b..4f8e236 100644 (file)
@@ -449,6 +449,9 @@ void RenderManager::PreRender(Integration::RenderStatus& status, bool forceClear
     }
   }
 
+  // Clean latest used pipeline
+  mImpl->pipelineCache->CleanLatestUsedCache();
+
   mImpl->commandBufferSubmitted = false;
 }
 
index aefbe32..3f655f4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 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.
@@ -190,10 +190,10 @@ constexpr Graphics::BlendOp ConvertBlendEquation(DevelBlendEquation::Type blendE
 }
 } // namespace
 
-PipelineCacheL0* PipelineCache::GetPipelineCacheL0(Program* program, Render::Geometry* geometry)
+PipelineCacheL0* PipelineCache::GetPipelineCacheL0(std::size_t hash, Program* program, Render::Geometry* geometry)
 {
-  auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [program, geometry](PipelineCacheL0& item) {
-    return ((item.program == program && item.geometry == geometry));
+  auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [hash, program, geometry](PipelineCacheL0& item) {
+    return ((item.hash == hash && item.program == program && item.geometry == geometry));
   });
 
   // Add new node to cache
@@ -237,6 +237,7 @@ PipelineCacheL0* PipelineCache::GetPipelineCacheL0(Program* program, Render::Geo
       ++bindingIndex;
     }
     PipelineCacheL0 level0;
+    level0.hash       = hash;
     level0.program    = program;
     level0.geometry   = geometry;
     level0.inputState = vertexInputState;
@@ -403,14 +404,59 @@ PipelineCacheL2* PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, Bl
   return retval;
 }
 
+void PipelineCacheQueryInfo::GenerateHash()
+{
+  // 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)
 {
-  auto* level0 = GetPipelineCacheL0(queryInfo.program, queryInfo.geometry);
+  // 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))
+  {
+    return mLatestResult[latestUsedCacheIndex];
+  }
+
+  auto* level0 = GetPipelineCacheL0(queryInfo.hash, queryInfo.program, queryInfo.geometry);
   auto* level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
   auto* level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
 
@@ -440,7 +486,16 @@ PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInf
   result.level1   = level1;
   result.level2   = level2;
 
+  // Copy query and result
+  mLatestQuery[latestUsedCacheIndex]  = queryInfo;
+  mLatestResult[latestUsedCacheIndex] = result;
+
   return result;
 }
 
+bool PipelineCache::ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const
+{
+  return mLatestResult[latestUsedCacheIndex].pipeline != nullptr && PipelineCacheQueryInfo::Equal(queryInfo, mLatestQuery[latestUsedCacheIndex]);
+}
+
 } // namespace Dali::Internal::Render
index c9027d3..6bf11d8 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_RENDER_PIPELINE_CACHE_H
 
 /*
- * 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.
  */
 
 // INTERNAL INCLUDES
-#include <dali/internal/common/blending-options.h>
-#include <dali/graphics-api/graphics-types.h>
 #include <dali/graphics-api/graphics-controller.h>
 #include <dali/graphics-api/graphics-pipeline.h>
+#include <dali/graphics-api/graphics-types.h>
+#include <dali/internal/common/blending-options.h>
 
 // EXTERNAL INCLUDES
 #include <vector>
@@ -49,8 +49,7 @@ struct PipelineCacheL2
  */
 struct PipelineCacheL1
 {
-
-  PipelineCacheL2 *GetPipelineCacheL2(bool blend, bool premul, BlendingOptions &blendingOptions);
+  PipelineCacheL2* GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions);
 
   uint32_t                     hashCode{}; // 1byte cull, 1byte poly, 1byte frontface
   Graphics::RasterizationState rs{};
@@ -61,14 +60,15 @@ struct PipelineCacheL1
 };
 
 /**
- * Cache Level 0 : Stores geometry, program amd vertex input state
+ * Cache Level 0 : Stores hash, geometry, program amd vertex input state
  */
 struct PipelineCacheL0 // L0 cache
 {
-  PipelineCacheL1 *GetPipelineCacheL1(Render::Renderer *renderer, bool usingReflection);
+  PipelineCacheL1* GetPipelineCacheL1(Render::Renderer* renderer, bool usingReflection);
 
-  Geometry                   *geometry{};
-  Program                    *program{};
+  std::size_t                hash{};
+  Geometry*                  geometry{};
+  Program*                   program{};
   Graphics::VertexInputState inputState;
 
   std::vector<PipelineCacheL1> level1nodes;
@@ -77,17 +77,25 @@ struct PipelineCacheL0 // L0 cache
 struct PipelineCacheQueryInfo
 {
   // Program/Geometry
-  Renderer *renderer;
-  Program  *program;
-  Geometry *geometry;
+  Rendererrenderer;
+  Program*  program;
+  Geometrygeometry;
 
   bool cameraUsingReflection;
 
   // Blending
-  bool blendingEnabled;
-  bool alphaPremultiplied;
-  BlendingOptions *blendingOptions;
+  bool             blendingEnabled;
+  bool             alphaPremultiplied;
+  BlendingOptions* blendingOptions;
+
+  // Lightweight hash value before compare each query.
+  std::size_t hash{0u};
 
+  // Generate hash value for this query.
+  void GenerateHash();
+
+  // Value comparision between two query info.
+  static bool Equal(const PipelineCacheQueryInfo& lhs, const PipelineCacheQueryInfo& rhs) noexcept;
 };
 
 /**
@@ -117,22 +125,47 @@ public:
   /**
    * Retrieves next cache level
    */
-  PipelineCacheL0* GetPipelineCacheL0( Program *program, Render::Geometry *geometry);
+  PipelineCacheL0* GetPipelineCacheL0(std::size_t hash, Program* program, Render::Geometry* geometry);
 
   /**
    * Retrieves pipeline matching queryInfo struct
    *
    * May retrieve existing pipeline or create one or return nullptr.
    */
-  PipelineResult GetPipeline( const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound );
+  PipelineResult GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound);
 
-private:
+  /**
+   * @brief Check whether we can reuse latest found PipelineResult.
+   * We can reuse latest pipeline only if query info is equal with latest query
+   * and we don't call CleanLatestUsedCache() before.
+   *
+   * @param[in] latestUsedCacheIndex Index of cache we want to compare.
+   * @param[in] queryInfo Query for current pipeline.
+   * @return True if we can reuse latest pipeline result. False otherwise
+   */
+  bool ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const;
+
+  /**
+   * @brief Clear latest bound result.
+   */
+  void CleanLatestUsedCache()
+  {
+    // Set pipeline as nullptr is enough.
+    mLatestResult[0].pipeline = nullptr;
+    mLatestResult[1].pipeline = nullptr;
+  }
 
-  Graphics::Controller* graphicsController{nullptr};
+private:
+  Graphics::Controller*        graphicsController{nullptr};
   std::vector<PipelineCacheL0> level0nodes;
+
+  // Cache latest queries whether blend enabled or not.
+  // (Since most UI case (like Text and Image) enable blend, and most 3D case disable blend.)
+  PipelineCacheQueryInfo mLatestQuery[2];  ///< Latest requested query info. It will be invalidate after query's renderer / geometry / blendingOptions value changed.
+  PipelineResult         mLatestResult[2]; ///< Latest used result. It will be invalidate when we call CleanLatestUsedCache() or some cache changed.
 };
 
-}
-}
+} // namespace Render
+} // namespace Dali::Internal
 
 #endif // DALI_INTERNAL_RENDER_PIPELINE_CACHE_H
index 45aa582..7a19c6b 100644 (file)
@@ -147,19 +147,19 @@ inline uint32_t GetUniformBufferDataAlignment(uint32_t dataSize)
 }
 
 /**
- * @brief Store latest binded RenderGeometry, and help that we can skip duplicated vertex attributes bind.
+ * @brief Store latest bound RenderGeometry, and help that we can skip duplicated vertex attributes bind.
  *
  * @param[in] geometry Current geometry to be used, or nullptr if render finished
- * @return True if we can reuse latest binded vertex attributes. False otherwise.
+ * @return True if we can reuse latest bound vertex attributes. False otherwise.
  */
-inline bool ReuseLatestBindedVertexAttributes(const Render::Geometry* geometry)
+inline bool ReuseLatestBoundVertexAttributes(const Render::Geometry* geometry)
 {
-  static const Render::Geometry* gLatestVertexBindedGeometry = nullptr;
-  if(gLatestVertexBindedGeometry == geometry)
+  static const Render::Geometry* gLatestVertexBoundGeometry = nullptr;
+  if(gLatestVertexBoundGeometry == geometry)
   {
     return true;
   }
-  gLatestVertexBindedGeometry = geometry;
+  gLatestVertexBoundGeometry = geometry;
   return false;
 }
 
@@ -175,7 +175,7 @@ MemoryPoolObjectAllocator<Renderer> gRenderRendererMemoryPool;
 void Renderer::PrepareCommandBuffer()
 {
   // Reset latest geometry informations, So we can bind the first of geometry.
-  ReuseLatestBindedVertexAttributes(nullptr);
+  ReuseLatestBoundVertexAttributes(nullptr);
 
   // todo : Fill here as many caches as we can store for reduce the number of command buffers
 }
@@ -596,8 +596,8 @@ bool Renderer::Render(Graphics::CommandBuffer&                             comma
   bool drawn = false; // Draw can fail if there are no vertex buffers or they haven't been uploaded yet
                       // @todo We should detect this case much earlier to prevent unnecessary work
 
-  // Reuse latest binded vertex attributes location, or Bind buffers to attribute locations.
-  if(ReuseLatestBindedVertexAttributes(mGeometry) || mGeometry->BindVertexAttributes(commandBuffer))
+  // Reuse latest bound vertex attributes location, or Bind buffers to attribute locations.
+  if(ReuseLatestBoundVertexAttributes(mGeometry) || mGeometry->BindVertexAttributes(commandBuffer))
   {
     if(mDrawCommands.empty())
     {
@@ -614,7 +614,7 @@ bool Renderer::Render(Graphics::CommandBuffer&                             comma
   else
   {
     // BindVertexAttributes failed. Reset cached geometry.
-    ReuseLatestBindedVertexAttributes(nullptr);
+    ReuseLatestBoundVertexAttributes(nullptr);
   }
 
   return drawn;
@@ -961,6 +961,9 @@ Graphics::Pipeline& Renderer::PrepareGraphicsPipeline(
   queryInfo.alphaPremultiplied    = mPremultipliedAlphaEnabled;
   queryInfo.cameraUsingReflection = instruction.GetCamera()->GetReflectionUsed();
 
+  queryInfo.GenerateHash();
+
+  // Find or generate new pipeline.
   auto pipelineResult = mPipelineCache->GetPipeline(queryInfo, true);
 
   // should be never null?