From: Eunki Hong Date: Mon, 6 Mar 2023 18:54:48 +0000 (+0900) Subject: Reuse latest pipeline if possible X-Git-Tag: dali_2.2.18~3^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;ds=sidebyside;h=068c5d3906ac2acb845c09e339dfe346bbea89e2;p=platform%2Fcore%2Fuifw%2Fdali-core.git Reuse latest pipeline if possible 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 --- diff --git a/automated-tests/src/dali-internal/utc-Dali-Internal-PipelineCache.cpp b/automated-tests/src/dali-internal/utc-Dali-Internal-PipelineCache.cpp index 4f3bb13..9783618 100644 --- a/automated-tests/src/dali-internal/utc-Dali-Internal-PipelineCache.cpp +++ b/automated-tests/src/dali-internal/utc-Dali-Internal-PipelineCache.cpp @@ -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. @@ -27,28 +27,26 @@ using namespace Dali; - - template -void InvokeNext(Object *obj, Args... args) +void InvokeNext(Object* obj, 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 -Ret InvokeReturnNext(Object *obj, Args... args) +Ret InvokeReturnNext(Object* obj, 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 diff --git a/automated-tests/src/dali/utc-Dali-Renderer.cpp b/automated-tests/src/dali/utc-Dali-Renderer.cpp index 67fa251..763fa67 100644 --- a/automated-tests/src/dali/utc-Dali-Renderer.cpp +++ b/automated-tests/src/dali/utc-Dali-Renderer.cpp @@ -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. diff --git a/dali/internal/render/common/render-manager.cpp b/dali/internal/render/common/render-manager.cpp index b72ec9b..4f8e236 100644 --- a/dali/internal/render/common/render-manager.cpp +++ b/dali/internal/render/common/render-manager.cpp @@ -449,6 +449,9 @@ void RenderManager::PreRender(Integration::RenderStatus& status, bool forceClear } } + // Clean latest used pipeline + mImpl->pipelineCache->CleanLatestUsedCache(); + mImpl->commandBufferSubmitted = false; } diff --git a/dali/internal/render/renderers/pipeline-cache.cpp b/dali/internal/render/renderers/pipeline-cache.cpp index aefbe32..3f655f4 100644 --- a/dali/internal/render/renderers/pipeline-cache.cpp +++ b/dali/internal/render/renderers/pipeline-cache.cpp @@ -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(program) >> Dali::Log::value) ^ + (reinterpret_cast(geometry) >> Dali::Log::value) ^ + ((blendingEnabled ? 1u : 0u) << 0u) ^ + ((alphaPremultiplied ? 1u : 0u) << 1u) ^ + (static_cast(geometry->GetTopology()) << 2u) ^ + (static_cast(renderer->GetFaceCullMode()) << 5u) ^ + ((cameraUsingReflection ? 1u : 0u) << 8u) ^ + (blendingEnabled ? static_cast(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 diff --git a/dali/internal/render/renderers/pipeline-cache.h b/dali/internal/render/renderers/pipeline-cache.h index c9027d3..6bf11d8 100644 --- a/dali/internal/render/renderers/pipeline-cache.h +++ b/dali/internal/render/renderers/pipeline-cache.h @@ -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. @@ -18,10 +18,10 @@ */ // INTERNAL INCLUDES -#include -#include #include #include +#include +#include // EXTERNAL INCLUDES #include @@ -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 level1nodes; @@ -77,17 +77,25 @@ struct PipelineCacheL0 // L0 cache struct PipelineCacheQueryInfo { // Program/Geometry - Renderer *renderer; - Program *program; - Geometry *geometry; + Renderer* renderer; + Program* program; + Geometry* geometry; 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 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 diff --git a/dali/internal/render/renderers/render-renderer.cpp b/dali/internal/render/renderers/render-renderer.cpp index 45aa582..7a19c6b 100644 --- a/dali/internal/render/renderers/render-renderer.cpp +++ b/dali/internal/render/renderers/render-renderer.cpp @@ -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 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?