From: Eunki, Hong Date: Wed, 10 Jul 2024 08:12:26 +0000 (+0900) Subject: Cache string based shader if required X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a020872e086c9e656aa1646d759efe1d82141e26;p=platform%2Fcore%2Fuifw%2Fdali-core.git Cache string based shader if required Let we cache string based shader's ShaderData. It will be useful to avoid use multiple ShaderData creation even if we use same source code. Change-Id: Ifeaa9c2fa47b402690dab9b650b01ce874002069 Signed-off-by: Eunki, Hong --- diff --git a/automated-tests/src/dali-internal/utc-Dali-Internal-Shader.cpp b/automated-tests/src/dali-internal/utc-Dali-Internal-Shader.cpp index e89c289..64fcd73 100644 --- a/automated-tests/src/dali-internal/utc-Dali-Internal-Shader.cpp +++ b/automated-tests/src/dali-internal/utc-Dali-Internal-Shader.cpp @@ -97,7 +97,7 @@ int UtcDaliShaderWithPrefixTestVersion(void) END_TEST; } -int UtcDaliInternalShaderSaveAndLoad(void) +int UtcDaliInternalShaderSaveAndLoad01(void) { TestApplication application; @@ -202,3 +202,137 @@ int UtcDaliInternalShaderSaveAndLoad(void) END_TEST; } + +int UtcDaliInternalShaderSaveAndLoad02(void) +{ + TestApplication application; + + std::string vertexShader1 = "some vertex code\n"; + std::string fragmentShader1 = "some fragment code\n"; + + std::string vertexShader2 = "some another vertex code\n"; + std::string fragmentShader2 = "some another fragment code\n"; + + Dali::Vector dummyBuffer(5); + for(size_t i = 0; i < 5; i++) + { + dummyBuffer[i] = static_cast(i + 21); + } + + // Make save and load failed successful + auto& platformAbstraction = application.GetPlatform(); + platformAbstraction.SetLoadFileResult(false, dummyBuffer); + + // Test version number in the shader data + Dali::Internal::ThreadLocalStorage& tls = Dali::Internal::ThreadLocalStorage::Get(); + Dali::Internal::ShaderFactory& shaderFactory = tls.GetShaderFactory(); + + tet_printf("Load shader1. It should be cached at string container\n"); + size_t shaderHash1 = 0u; + Internal::ShaderDataPtr shaderData1 = shaderFactory.Load(vertexShader1, fragmentShader1, Shader::Hint::NONE, 0u, "", shaderHash1); + DALI_TEST_CHECK(shaderHash1 != 0u); + + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION); + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), true, TEST_LOCATION); + + // Reset trace callstack + platformAbstraction.GetTrace().Reset(); + + tet_printf("Load shader2. It also should be cached at string container\n"); + size_t shaderHash2 = 0u; + Internal::ShaderDataPtr shaderData2 = shaderFactory.Load(vertexShader2, fragmentShader2, Shader::Hint::NONE, 0u, "", shaderHash2); + DALI_TEST_CHECK(shaderHash2 != 0u); + + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION); + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), true, TEST_LOCATION); + + // Reset trace callstack + platformAbstraction.GetTrace().Reset(); + + tet_printf("Both shader 1 and 2 don't have binary now.\n"); + DALI_TEST_EQUALS(shaderData1.Get()->HasBinary(), false, TEST_LOCATION); + DALI_TEST_EQUALS(shaderData2.Get()->HasBinary(), false, TEST_LOCATION); + + // Copy the memory of dummy + shaderData1.Get()->AllocateBuffer(5); + for(size_t i = 0; i < 5; i++) + { + shaderData1.Get()->GetBuffer()[i] = static_cast(i + 1); + } + + DALI_TEST_EQUALS(shaderData1.Get()->HasBinary(), true, TEST_LOCATION); + + tet_printf("Save shaderData1 as binary, but failed.\n"); + shaderFactory.SaveBinary(shaderData1); + + tet_printf("Check shader saved\n"); + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), true, TEST_LOCATION); + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), false, TEST_LOCATION); + + // Reset trace callstack + platformAbstraction.GetTrace().Reset(); + + tet_printf("Save shaderData1 as binary, and success now.\n"); + platformAbstraction.SetSaveFileResult(true); + shaderFactory.SaveBinary(shaderData1); + + tet_printf("Check shader saved\n"); + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), true, TEST_LOCATION); + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), false, TEST_LOCATION); + + // Reset trace callstack + platformAbstraction.GetTrace().Reset(); + + tet_printf("Load shader with same code as shaderData1\n"); + size_t shaderHash3 = 0u; + Internal::ShaderDataPtr shaderData3 = shaderFactory.Load(vertexShader1, fragmentShader1, Shader::Hint::NONE, 0u, "", shaderHash3); + + tet_printf("Check shaderData1 cached\n"); + DALI_TEST_EQUALS(shaderHash3, shaderHash1, TEST_LOCATION); + DALI_TEST_EQUALS(shaderData3.Get()->HasBinary(), true, TEST_LOCATION); + DALI_TEST_EQUALS(shaderData3.Get(), shaderData1.Get(), TEST_LOCATION); + + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION); + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), false, TEST_LOCATION); + + // Reset trace callstack + platformAbstraction.GetTrace().Reset(); + + tet_printf("Load shader with same code as shaderData2\n"); + size_t shaderHash4 = 0u; + Internal::ShaderDataPtr shaderData4 = shaderFactory.Load(vertexShader2, fragmentShader2, Shader::Hint::NONE, 0u, "", shaderHash4); + + tet_printf("Check shaderData2 cached\n"); + DALI_TEST_EQUALS(shaderHash4, shaderHash2, TEST_LOCATION); + DALI_TEST_EQUALS(shaderData4.Get(), shaderData2.Get(), TEST_LOCATION); + DALI_TEST_EQUALS(shaderData4.Get()->HasBinary(), false, TEST_LOCATION); + + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION); + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), false, TEST_LOCATION); + + // Reset trace callstack + platformAbstraction.GetTrace().Reset(); + + tet_printf("Allow to load shader binary\n"); + platformAbstraction.SetLoadFileResult(true, dummyBuffer); + + tet_printf("Load shader same as shaderData1, but difference render pass tag\n"); + size_t shaderHash5 = 0u; + Internal::ShaderDataPtr shaderData5 = shaderFactory.Load(vertexShader1, fragmentShader1, Shader::Hint::NONE, 1u, "", shaderHash5); + + tet_printf("Check shaderData1 and shaderData5 have same hash, but deferent buffer\n"); + DALI_TEST_EQUALS(shaderHash5, shaderHash1, TEST_LOCATION); + DALI_TEST_CHECK(shaderData5.Get() != shaderData1.Get()); + DALI_TEST_EQUALS(shaderData5.Get()->HasBinary(), true, TEST_LOCATION); + + tet_printf("Check shaderData5 binary same as dummy buffer\n"); + DALI_TEST_EQUALS(shaderData5.Get()->GetBufferSize(), dummyBuffer.Count(), TEST_LOCATION); + for(size_t i = 0; i < 5; ++i) + { + DALI_TEST_EQUALS(shaderData5.Get()->GetBuffer()[i], dummyBuffer[i], TEST_LOCATION); + } + + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION); + DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), true, TEST_LOCATION); + END_TEST; +} diff --git a/automated-tests/src/dali/utc-Dali-Shader.cpp b/automated-tests/src/dali/utc-Dali-Shader.cpp index f419d74..310f233 100644 --- a/automated-tests/src/dali/utc-Dali-Shader.cpp +++ b/automated-tests/src/dali/utc-Dali-Shader.cpp @@ -23,6 +23,8 @@ #include +#include + using namespace Dali; void utc_dali_shader_startup(void) diff --git a/dali/internal/event/effects/shader-factory.cpp b/dali/internal/event/effects/shader-factory.cpp index 8f2ad5e..678ae49 100644 --- a/dali/internal/event/effects/shader-factory.cpp +++ b/dali/internal/event/effects/shader-factory.cpp @@ -62,11 +62,18 @@ ShaderFactory::ShaderFactory() = default; ShaderFactory::~ShaderFactory() { // Let all the cached objects destroy themselves: - for(std::size_t i = 0, cacheSize = mShaderBinaryCache.Size(); i < cacheSize; ++i) + for(auto&& listPair : mShaderBinaryCache) { - if(mShaderBinaryCache[i]) + for(auto&& shaderData : listPair.second) { - mShaderBinaryCache[i]->Unreference(); + shaderData->Unreference(); + } + } + for(auto&& listPair : mShaderStringCache) + { + for(auto&& shaderData : listPair.second) + { + shaderData->Unreference(); } } } @@ -75,26 +82,57 @@ ShaderDataPtr ShaderFactory::Load(std::string_view vertexSource, std::string_vie { // Work out the filename for the binary that the glsl source will be compiled and linked to: shaderHash = CalculateHash(vertexSource, fragmentSource); - std::string binaryShaderFilename; - shaderBinaryFilename(shaderHash, binaryShaderFilename); ShaderDataPtr shaderData; /// Check a cache of previously loaded shaders: - for(std::size_t i = 0, cacheSize = mShaderBinaryCache.Size(); i < cacheSize; ++i) + if(mShaderBinaryCache.count(shaderHash) > 0u) { - if(mShaderBinaryCache[i]->GetHashValue() == shaderHash) + const auto& cacheList = mShaderBinaryCache[shaderHash]; + for(std::size_t i = 0, cacheSize = cacheList.Count(); i < cacheSize; ++i) { - shaderData = mShaderBinaryCache[i]; + if(cacheList[i]->GetHints() == hints && + cacheList[i]->GetRenderPassTag() == renderPassTag) + { + shaderData = cacheList[i]; + +#ifdef DEBUG_ENABLED + if(Debug::Filter::gShader->IsEnabledFor(Debug::General)) + { + std::string binaryShaderFilename; + shaderBinaryFilename(shaderHash, binaryShaderFilename); + + DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "Mem cache hit on path: \"%s\", Hint : %d, Tag : %u\n", binaryShaderFilename.c_str(), static_cast(hints), renderPassTag); + } +#endif + break; + } + } + } - DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "Mem cache hit on path: \"%s\"\n", binaryShaderFilename.c_str()); - break; + /// Check a cache of previously loaded shaders as normal string: + if(shaderData.Get() == nullptr && mShaderStringCache.count(shaderHash) > 0u) + { + const auto& cacheList = mShaderStringCache[shaderHash]; + for(std::size_t i = 0, cacheSize = cacheList.Size(); i < cacheSize; ++i) + { + if(cacheList[i]->GetHints() == hints && + cacheList[i]->GetRenderPassTag() == renderPassTag) + { + shaderData = cacheList[i]; + + DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "Mem cache hit on string shader. Hash : \"%zu\", Hint : %d, Tag : %u\n", shaderHash, static_cast(hints), renderPassTag); + break; + } } } // If memory cache failed check the file system for a binary or return a source-only ShaderData: if(shaderData.Get() == nullptr) { + std::string binaryShaderFilename; + shaderBinaryFilename(shaderHash, binaryShaderFilename); + // Allocate the structure that returns the loaded shader: shaderData = new ShaderData(vertexSource, fragmentSource, hints, renderPassTag, name); shaderData->SetHashValue(shaderHash); @@ -105,10 +143,7 @@ ShaderDataPtr ShaderFactory::Load(std::string_view vertexSource, std::string_vie Integration::PlatformAbstraction& platformAbstraction = tls.GetPlatformAbstraction(); const bool loaded = platformAbstraction.LoadShaderBinaryFile(binaryShaderFilename, shaderData->GetBuffer()); - if(loaded) - { - MemoryCacheInsert(*shaderData); - } + MemoryCacheInsert(*shaderData, loaded); DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, loaded ? "loaded on path: \"%s\"\n" : "failed to load on path: \"%s\"\n", binaryShaderFilename.c_str()); } @@ -127,25 +162,73 @@ void ShaderFactory::SaveBinary(Internal::ShaderDataPtr shaderData) const bool saved = platformAbstraction.SaveShaderBinaryFile(binaryShaderFilename, &shaderData->GetBuffer()[0], static_cast(shaderData->GetBufferSize())); // don't expect buffer larger than unsigned int // Save the binary into to memory cache: - MemoryCacheInsert(*shaderData); + MemoryCacheInsert(*shaderData, saved); DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, saved ? "Saved to file: %s\n" : "Save to file failed: %s\n", binaryShaderFilename.c_str()); - if(saved) - { - } // Avoid unused variable warning in release builds } -void ShaderFactory::MemoryCacheInsert(ShaderData& shaderData) +void ShaderFactory::MemoryCacheInsert(ShaderData& shaderData, const bool isBinaryCached) { - DALI_ASSERT_DEBUG(shaderData.GetBufferSize() > 0); + const size_t shaderHash = shaderData.GetHashValue(); // Save the binary into to memory cache: - if(shaderData.GetBufferSize() > 0) + if(isBinaryCached) + { + DALI_ASSERT_DEBUG(shaderData.GetBufferSize() > 0); + + // Remove shaderdata from string cache if it exists: + RemoveStringShaderData(shaderData); + + auto& cacheList = mShaderBinaryCache[shaderHash]; ///< Get or create a new cache list. + + cacheList.Reserve(cacheList.Size() + 1); // Make sure the push won't throw after we inc the ref count. + shaderData.Reference(); + cacheList.PushBack(&shaderData); + DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "CACHED BINARY FOR HASH: %u, HINT: %d, TAG: %u\n", shaderHash, static_cast(shaderData.GetHints()), shaderData.GetRenderPassTag()); + } + else { - mShaderBinaryCache.Reserve(mShaderBinaryCache.Size() + 1); // Make sure the push won't throw after we inc the ref count. + auto& cacheList = mShaderStringCache[shaderHash]; ///< Get or create a new cache list. + + // Ignore shaderdata with string if it already exists: + for(auto iter = cacheList.Begin(); iter != cacheList.End(); ++iter) + { + if((*iter)->GetHints() == shaderData.GetHints() && + (*iter)->GetRenderPassTag() == shaderData.GetRenderPassTag()) + { + DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "ALREADY CACHED NON-BINARY CACHE FOR HASH: %u, HINT: %d, TAG: %u\n", shaderHash, static_cast(shaderData.GetHints()), shaderData.GetRenderPassTag()); + return; + } + } shaderData.Reference(); - mShaderBinaryCache.PushBack(&shaderData); - DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "CACHED BINARY FOR HASH: %u\n", shaderData.GetHashValue()); + cacheList.PushBack(&shaderData); + DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "CACHED NON-BINARY SHADER FOR HASH: %u, HINT: %d, TAG: %u\n", shaderHash, static_cast(shaderData.GetHints()), shaderData.GetRenderPassTag()); + } +} + +void ShaderFactory::RemoveStringShaderData(ShaderData& shaderData) +{ + const size_t shaderHash = shaderData.GetHashValue(); + + if(mShaderStringCache.count(shaderHash) > 0u) + { + auto& cacheList = mShaderStringCache[shaderHash]; + for(auto iter = cacheList.Begin(); iter != cacheList.End(); ++iter) + { + if((*iter)->GetHints() == shaderData.GetHints() && + (*iter)->GetRenderPassTag() == shaderData.GetRenderPassTag()) + { + DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "REMOVE NON-BINARY CACHE FOR HASH: %u, HINT: %d, TAG: %u\n", shaderHash, static_cast(shaderData.GetHints()), shaderData.GetRenderPassTag()); + // Reduce reference before erase + (*iter)->Unreference(); + cacheList.Erase(iter); + break; + } + } + if(cacheList.Count() == 0) + { + mShaderStringCache.erase(shaderHash); + } } } diff --git a/dali/internal/event/effects/shader-factory.h b/dali/internal/event/effects/shader-factory.h index 2171466..0662c31 100644 --- a/dali/internal/event/effects/shader-factory.h +++ b/dali/internal/event/effects/shader-factory.h @@ -18,6 +18,9 @@ * */ +// EXTERNAL INCLUDES +#include + // INTERNAL INCLUDES #include #include @@ -79,16 +82,25 @@ public: void SaveBinary(Internal::ShaderDataPtr shader) override; private: - void MemoryCacheInsert(Internal::ShaderData& shaderData); + void MemoryCacheInsert(Internal::ShaderData& shaderData, const bool isBinaryCached); + + /** + * @brief Removes the string shader data from the cache. + * @param[in] shaderData The shader data to remove. + */ + void RemoveStringShaderData(Internal::ShaderData& shaderData); // Undefined - ShaderFactory(const ShaderFactory&); + ShaderFactory(const ShaderFactory&) = delete; // Undefined - ShaderFactory& operator=(const ShaderFactory& rhs); + ShaderFactory& operator=(const ShaderFactory& rhs) = delete; private: - Dali::Vector mShaderBinaryCache; ///< Cache of pre-compiled shaders. + using ShaderCacheContainer = std::map>; ///< Container for the shader cache. Key is hash of shader code. + + ShaderCacheContainer mShaderBinaryCache; ///< Cache of pre-compiled shaders. + ShaderCacheContainer mShaderStringCache; ///< Cache of non-pre-compiled shaders. (TODO : Could we clean up this cache by user management?) }; // class ShaderFactory