Current logic try to clean program cache every 10 seconds.
That mean, we need to re-create program.
It might make some unneccessary performance slow-down actions.
To avoid it, let we try to GC only if the number of program cache size
is bigger than threshold, not always.
And also, for every GC time, we delete only 5 programs incrementally per each frame.
So make we less-block the rendering time.
It will be useful for real world app who don't use custom shader.
Change-Id: Ie4e3bfb3486984673caa79750f55d813f256c0bf
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
END_TEST;
}
-int UtcDaliRenderTaskWithWrongShaderData(void)
+int UtcDaliRenderTaskWithWrongShaderData01(void)
{
TestApplication application;
tet_infoline("Testing RenderTask with wrong shader data");
END_TEST;
}
+int UtcDaliRenderTaskWithWrongShaderData02(void)
+{
+ TestApplication application;
+ tet_infoline("Testing RenderTask with wrong shader data. This UTC is for line coverage");
+
+ Stage stage = Stage::GetCurrent();
+ Vector2 stageSize(stage.GetSize());
+
+ Actor blue = Actor::New();
+ blue[Dali::Actor::Property::NAME] = "Blue";
+ blue[Dali::Actor::Property::ANCHOR_POINT] = AnchorPoint::CENTER;
+ blue[Dali::Actor::Property::PARENT_ORIGIN] = ParentOrigin::CENTER;
+ blue[Dali::Actor::Property::SIZE] = Vector2(300, 300);
+ blue[Dali::Actor::Property::POSITION] = Vector2(0, 0);
+
+ Geometry geometry = Geometry::New();
+
+ Shader validShader = Shader::New("vertexSrc", "fragmentSrc");
+ Renderer renderer = Renderer::New(geometry, validShader);
+ blue.AddRenderer(renderer);
+
+ stage.Add(blue);
+
+ auto& gfx = application.GetGraphicsController();
+
+ RenderTaskList renderTaskList = stage.GetRenderTaskList();
+ DALI_TEST_EQUALS(0u, renderTaskList.GetTask(0u).GetRenderPassTag(), TEST_LOCATION);
+ // Render and notify
+ application.SendNotification();
+ application.Render(16);
+ DALI_TEST_CHECK(gfx.mCallStack.FindMethod("CreatePipeline"));
+ gfx.mCallStack.Reset();
+ DALI_TEST_EQUALS(0u, renderTaskList.GetTask(0u).GetRenderPassTag(), TEST_LOCATION);
+
+ // Change the shader as invalid.
+ Shader invalidShader = Shader::New(Property::Value(10.0f));
+ renderer.SetShader(invalidShader);
+
+ // Render and notify
+ application.SendNotification();
+ application.Render(16);
+ DALI_TEST_CHECK(!gfx.mCallStack.FindMethod("CreatePipeline"));
+ gfx.mCallStack.Reset();
+ DALI_TEST_EQUALS(0u, renderTaskList.GetTask(0u).GetRenderPassTag(), TEST_LOCATION);
+
+ END_TEST;
+}
+
int UtcDaliRenderTaskOrderIndex01(void)
{
TestApplication application;
DALI_TEST_CHECK(true);
END_TEST;
-}
\ No newline at end of file
+}
END_TEST;
}
+namespace
+{
+constexpr uint32_t PROGRAM_CACHE_CLEAN_FRAME_COUNT = 300u; // 60fps * 5sec
+
+constexpr uint32_t INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD = 64u; // Let we trigger program cache clean if the number of shader is bigger than this value
+constexpr uint32_t MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD = 1024u;
+
+constexpr uint32_t PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT = PROGRAM_CACHE_CLEAN_FRAME_COUNT + MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD;
+
+static_assert(PROGRAM_CACHE_CLEAN_FRAME_COUNT <= PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT);
+static_assert(MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD <= PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT);
+} // namespace
+
+int UtcDaliShaderStressTest01(void)
+{
+ TestApplication application;
+
+ tet_infoline("Test that use a lots of independence shaders, for line coverage of program cache removal");
+
+ const int testFrameCount = INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD /*Initial threshold*/ + 1u /*..And make over the threshold*/ + PROGRAM_CACHE_CLEAN_FRAME_COUNT /*Program GC frame count*/;
+
+ try
+ {
+ // We should keep some renderer that some Program should be 'Garbage Collected' when they are the latest program for some renderer.
+ const int referenceKeepedRendererCount = INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD;
+
+ std::vector<Renderer> rendererList; ///< To keep reference of Renderer
+ rendererList.reserve(referenceKeepedRendererCount);
+
+ for(int frameCount = 0; frameCount < testFrameCount; ++frameCount)
+ {
+ // Generate new shader code
+ std::ostringstream oss;
+ oss << VertexSource << "\n"
+ << frameCount << "\n";
+
+ Shader shader = Shader::New(oss.str(), FragmentSource);
+ Geometry geometry = CreateQuadGeometry();
+ Renderer renderer = Renderer::New(geometry, shader);
+
+ if(frameCount < referenceKeepedRendererCount)
+ {
+ rendererList.push_back(renderer);
+ }
+
+ Actor actor = Actor::New();
+ actor.AddRenderer(renderer);
+ actor.SetProperty(Actor::Property::SIZE, Vector2(400.0f, 400.0f));
+ application.GetScene().Add(actor);
+
+ tet_printf("start %d ~ ", frameCount);
+ application.SendNotification();
+ application.Render(0);
+ tet_printf(" ~ end %d\n", frameCount);
+
+ actor.Unparent();
+ }
+
+ tet_printf("Generate shader finished. Let's wait incremental GC");
+
+ for(uint32_t frameCount = 0u; frameCount < PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT; ++frameCount)
+ {
+ tet_printf("start %u ~ ", frameCount);
+ application.SendNotification();
+ application.Render(0);
+ tet_printf(" ~ end %u\n", frameCount);
+ }
+ DALI_TEST_CHECK(true);
+ }
+ catch(...)
+ {
+ DALI_TEST_CHECK(false);
+ }
+ END_TEST;
+}
+
+int UtcDaliShaderStressTest02(void)
+{
+ TestApplication application;
+
+ tet_infoline("Test that use a lots of independence shaders, for line coverage of program cache removal 02");
+ tet_infoline("For this UTC, we will test a renderer that has 2 programs A and B. Use A first, and use B, and use A again. But A become GC, B is not GC.");
+
+ const int testFrameCount = INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD /*Initial threshold*/ + 1u /*..And make over the threshold*/ + PROGRAM_CACHE_CLEAN_FRAME_COUNT /*Program GC frame count*/;
+
+ try
+ {
+ // Keep first frame reference for test.
+ Renderer firstFrameRenderer;
+ for(int frameCount = 0; frameCount < testFrameCount; ++frameCount)
+ {
+ // Generate new shader code
+ std::ostringstream oss;
+ oss << VertexSource << "\n"
+ << frameCount << "\n";
+
+ Shader shader = Shader::New(oss.str(), FragmentSource);
+ Geometry geometry = CreateQuadGeometry();
+ Renderer renderer = Renderer::New(geometry, shader);
+
+ if(frameCount == 0u)
+ {
+ firstFrameRenderer = renderer;
+ }
+
+ Actor actor = Actor::New();
+ actor.AddRenderer(renderer);
+ actor.SetProperty(Actor::Property::SIZE, Vector2(400.0f, 400.0f));
+ application.GetScene().Add(actor);
+
+ tet_printf("start %d ~ ", frameCount);
+ application.SendNotification();
+ application.Render(0);
+ tet_printf(" ~ end %d\n", frameCount);
+
+ if(frameCount == 0u)
+ {
+ // Generate new shader for future code
+ std::ostringstream oss2;
+ oss2 << VertexSource << "\n"
+ << (frameCount + INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD + 1) << "\n";
+
+ // Create new Program that will not be GC.
+ Shader futureShader = Shader::New(oss2.str(), FragmentSource);
+ renderer.SetShader(futureShader);
+
+ // And render 1 frame.
+ tet_printf("start %d ~ ", frameCount);
+ application.SendNotification();
+ application.Render(0);
+ tet_printf(" ~ end %d\n", frameCount);
+
+ // Change back the Program that will be GC.
+ renderer.SetShader(shader);
+
+ // And render 1 frame again.
+ tet_printf("start %d ~ ", frameCount);
+ application.SendNotification();
+ application.Render(0);
+ tet_printf(" ~ end %d\n", frameCount);
+ }
+
+ actor.Unparent();
+ }
+
+ tet_printf("Generate shader finished. Let's wait incremental GC");
+
+ for(uint32_t frameCount = 0u; frameCount < PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT; ++frameCount)
+ {
+ tet_printf("start %u ~ ", frameCount);
+ application.SendNotification();
+ application.Render(0);
+ tet_printf(" ~ end %u\n", frameCount);
+ }
+
+ DALI_TEST_CHECK(true);
+ }
+ catch(...)
+ {
+ DALI_TEST_CHECK(false);
+ }
+ END_TEST;
+}
int UtcDaliShaderDestructWorkerThreadN(void)
{
namespace
{
-// TODO : Cache clean logic have some problem now. Just block it until bug resolved
-// constexpr uint32_t CACHE_CLEAN_FRAME_COUNT = 600u; // 60fps * 10sec
+constexpr uint32_t PROGRAM_CACHE_CLEAN_FRAME_COUNT = 300u; // 60fps * 5sec
+
+constexpr uint32_t INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD = 64u; // Let we trigger program cache clean if the number of shader is bigger than this value
+constexpr uint32_t MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD = 1024u;
+
+constexpr uint32_t PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT = PROGRAM_CACHE_CLEAN_FRAME_COUNT + MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD;
+
+static_assert(PROGRAM_CACHE_CLEAN_FRAME_COUNT <= PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT);
+static_assert(MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD <= PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT);
#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
constexpr uint32_t SHRINK_TO_FIT_FRAME_COUNT = (1u << 8); ///< 256 frames. Make this value as power of 2.
~Impl()
{
- rendererContainer.Clear(); // clear now before the pipeline cache is deleted
+ rendererContainer.Clear(); // clear now before the program contoller and the pipeline cache are deleted
+ pipelineCache.reset(); // clear now before the program contoller is deleted
}
void AddRenderTracker(Render::RenderTracker* renderTracker)
}
}
+ /**
+ * @brief Prepare to check used count of shader and program cache.
+ * It will be used when the size of shader and program cache is too big, so we need to collect garbages.
+ * Currently, we collect and remove programs only if the number of program/shader is bigger than threshold.
+ *
+ * @note Should be called at PreRender
+ */
+ void RequestProgramCacheCleanIfNeed()
+ {
+ if(programCacheCleanRequestedFrame == 0u)
+ {
+ if(DALI_UNLIKELY(programController.GetCachedProgramCount() > programCacheCleanRequiredThreshold))
+ {
+ // Mark current frame count
+ programCacheCleanRequestedFrame = frameCount;
+
+ DALI_LOG_RELEASE_INFO("Trigger ProgramCache GC. program : [%u]\n", programController.GetCachedProgramCount());
+
+ // Prepare to collect program used flag.
+ programController.ResetUsedFlag();
+ }
+ }
+ }
+
+ /**
+ * @brief Cleanup unused program and shader cache if need.
+ *
+ * @note Should be called at PostRender
+ */
+ void ClearUnusedProgramCacheIfNeed()
+ {
+ // Remove unused shader and programs during we render PROGRAM_CACHE_CLEAN_FRAME_COUNT frames.
+ if(programCacheCleanRequestedFrame != 0u && programCacheCleanRequestedFrame + PROGRAM_CACHE_CLEAN_FRAME_COUNT - 1u <= frameCount)
+ {
+ // Clean cache incrementally, or force clean if we spend too much frames to collect them.
+ if(!programController.ClearUnusedCacheIncrementally(programCacheCleanRequestedFrame + PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT - 1u <= frameCount))
+ {
+ // Reset current frame count.
+ programCacheCleanRequestedFrame = 0u;
+
+ DALI_LOG_RELEASE_INFO("ProgramCache GC finished. program : [%u]\n", programController.GetCachedProgramCount());
+
+ // Double up threshold
+ programCacheCleanRequiredThreshold <<= 1;
+ programCacheCleanRequiredThreshold = std::max(programCacheCleanRequiredThreshold, programController.GetCachedProgramCount());
+
+ if(programCacheCleanRequiredThreshold > MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD)
+ {
+ programCacheCleanRequiredThreshold = MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD;
+ }
+ }
+ }
+ }
+
// the order is important for destruction,
Graphics::Controller& graphicsController;
RenderQueue renderQueue; ///< A message queue for receiving messages from the update-thread.
uint32_t frameCount{0u}; ///< The current frame count
BufferIndex renderBufferIndex{SceneGraphBuffers::INITIAL_UPDATE_BUFFER_INDEX}; ///< The index of the buffer to read from;
+ uint32_t programCacheCleanRequiredThreshold{INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD}; ///< The threshold to request program cache clean up operation.
+ ///< It will be automatically increased after we request cache clean.
+
+ uint32_t programCacheCleanRequestedFrame{0u}; ///< 0 mean, we didn't request program cache clean. otherwise, we request cache clean.
+ ///< otherwise, we request program cache clean at that frame, and now we are checking reference.
+
bool lastFrameWasRendered{false}; ///< Keeps track of the last frame being rendered due to having render instructions
bool commandBufferSubmitted{false};
};
// Reset pipeline cache before rendering
mImpl->pipelineCache->PreRender();
- // Let we collect reference counts during CACHE_CLEAN_FRAME_COUNT frames.
- // TODO : Cache clean logic have some problem now. Just block it until bug resolved
- /*
- if(mImpl->frameCount % CACHE_CLEAN_FRAME_COUNT == 1)
- {
- mImpl->programController.ResetReferenceCount();
- }
- */
+ // Check we need to clean up program cache
+ mImpl->RequestProgramCacheCleanIfNeed();
mImpl->commandBufferSubmitted = false;
}
if(targetsToPresent.size() > 0u)
{
- DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_RENDER_FINISHED", [&](std::ostringstream& oss)
- { oss << "[" << targetsToPresent.size() << "]"; });
+ DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_RENDER_FINISHED", [&](std::ostringstream& oss) { oss << "[" << targetsToPresent.size() << "]"; });
}
// Flush UBOs
count += scene->GetRenderInstructions().Count(mImpl->renderBufferIndex);
}
- // Remove unused shader and programs during CACHE_CLEAN_FRAME_COUNT frames.
- // TODO : Cache clean logic have some problem now. Just block it until bug resolved
- /*
- if(mImpl->frameCount % CACHE_CLEAN_FRAME_COUNT == 0)
- {
- mImpl->programController.ClearUnusedCache();
- }
- */
+ mImpl->ClearUnusedProgramCacheIfNeed();
#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
// Shrink relevant containers if required.
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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.
level0.geometry = geometry;
level0.inputState = vertexInputState;
+ // Observer program lifecycle
+ program->AddLifecycleObserver(*this);
+
it = level0nodes.insert(level0nodes.end(), std::move(level0));
if(attrNotFound)
CleanLatestUsedCache();
}
+PipelineCache::~PipelineCache()
+{
+ // Stop observer program lifecycle
+ for(auto&& level0node : level0nodes)
+ {
+ level0node.program->RemoveLifecycleObserver(*this);
+ }
+}
+
PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
{
// Seperate branch whether query use blending or not.
if(iter->level1nodes.empty())
{
+ // Stop observer program lifecycle
+ iter->program->RemoveLifecycleObserver(*this);
iter = level0nodes.erase(iter);
}
else
pipelineCache->referenceCount--;
}
+void PipelineCache::ProgramDestroyed(const Program* program)
+{
+ // Remove latest used pipeline cache infomation.
+ CleanLatestUsedCache();
+
+ // Remove cached items what cache hold now.
+ for(auto iter = level0nodes.begin(); iter != level0nodes.end();)
+ {
+ if(iter->program == program)
+ {
+ iter = level0nodes.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
+ }
+}
+
} // namespace Dali::Internal::Render
#define DALI_INTERNAL_RENDER_PIPELINE_CACHE_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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/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>
#include <dali/public-api/common/list-wrapper.h>
+#include <dali/internal/common/blending-options.h>
+#include <dali/internal/render/shaders/program.h> ///< For Program::LifecycleObserver
+
namespace Dali::Internal
{
-class Program;
namespace Render
{
class Renderer;
};
/**
- * Cache Level 0 : Stores hash, geometry, program amd vertex input state
+ * Cache Level 0 : Stores geometry, program amd vertex input state
*/
struct PipelineCacheL0 // L0 cache
{
/**
* Pipeline cache
*/
-class PipelineCache
+class PipelineCache : public Program::LifecycleObserver
{
public:
/**
explicit PipelineCache(Graphics::Controller& controller);
/**
+ * Destructor
+ */
+ ~PipelineCache();
+
+ /**
* Retrieves next cache level
*/
PipelineCacheL0Ptr GetPipelineCacheL0(Program* program, Render::Geometry* geometry);
*/
void ResetPipeline(PipelineCachePtr pipelineCache);
+public: // From Program::LifecycleObserver
+ /**
+ * @copydoc Dali::Internal::Program::LifecycleObserver::ProgramDestroyed()
+ */
+ void ProgramDestroyed(const Program* program);
+
private:
/**
* @brief Clear latest bound result.
mPipelineCache->ResetPipeline(mPipeline);
mPipelineCached = false;
}
+
+ // Stop observing
+ if(mCurrentProgram)
+ {
+ mCurrentProgram->RemoveLifecycleObserver(*this);
+ mCurrentProgram = nullptr;
+ }
}
void Renderer::operator delete(void* ptr)
if(!shaderData)
{
DALI_LOG_ERROR("Failed to get shader data.\n");
+ if(mCurrentProgram)
+ {
+ mCurrentProgram->RemoveLifecycleObserver(*this);
+ }
mCurrentProgram = nullptr;
return nullptr;
}
if(!program)
{
DALI_LOG_ERROR("Failed to create program for shader at address %p.\n", reinterpret_cast<const void*>(&shader));
+ if(mCurrentProgram)
+ {
+ mCurrentProgram->RemoveLifecycleObserver(*this);
+ }
mCurrentProgram = nullptr;
return nullptr;
}
}
// Set prefetched program to be used during rendering
- mCurrentProgram = program;
+ if(mCurrentProgram != program)
+ {
+ if(mCurrentProgram)
+ {
+ mCurrentProgram->RemoveLifecycleObserver(*this);
+ }
+ mCurrentProgram = program;
+ mCurrentProgram->AddLifecycleObserver(*this);
+ }
return mCurrentProgram;
}
continue;
}
- uniform.uniformOffset = uniformInfo.offset;
- uniform.uniformLocation = int16_t(uniformInfo.location);
- uniform.uniformBlockIndex = uniformInfo.bufferIndex;
- uniform.initialized = true;
+ uniform.uniformOffset = uniformInfo.offset;
+ uniform.uniformLocation = int16_t(uniformInfo.location);
+ uniform.uniformBlockIndex = uniformInfo.bufferIndex;
+ uniform.initialized = true;
- auto dst = ubo->GetOffset() + uniformInfo.offset;
- const auto typeSize = iter.propertyValue->GetValueSize();
+ auto dst = ubo->GetOffset() + uniformInfo.offset;
+ const auto typeSize = iter.propertyValue->GetValueSize();
uniform.arrayElementStride = uniformInfo.elementCount > 0 ? (uniformInfo.elementStride ? uniformInfo.elementStride : typeSize) : typeSize;
- const auto dest = dst + uniform.arrayElementStride * arrayIndex;
+ const auto dest = dst + uniform.arrayElementStride * arrayIndex;
ubo->Write(iter.propertyValue->GetValueAddress(updateBufferIndex),
typeSize,
}
}
+void Renderer::ProgramDestroyed(const Program* program)
+{
+ DALI_ASSERT_ALWAYS(mCurrentProgram == program && "Something wrong happend when Render::Renderer observed by program!");
+ mCurrentProgram = nullptr;
+
+ // The cached pipeline might be invalided after program destroyed.
+ // Remove current cached pipeline information.
+ // Note that reset flag is enought since mPipeline pointer will be invalidate now.
+ mPipelineCached = false;
+
+ // Destroy whole mNodeIndexMap and mUniformIndexMaps container.
+ // It will be re-created at next render time.
+ // Note : Destroy the program will be happened at RenderManager::PostRender().
+ // We don't worry about the mNodeIndexMap and mUniformIndexMaps become invalidated after this call.
+ mNodeIndexMap.clear();
+ mUniformIndexMaps.clear();
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+ mNodeIndexMap.shrink_to_fit();
+ mUniformIndexMaps.shrink_to_fit();
+#endif
+}
+
Vector4 Renderer::GetTextureUpdateArea() const noexcept
{
Vector4 result = Vector4::ZERO;
* These objects are used during RenderManager::Render(), so properties modified during
* the Update must either be double-buffered, or set via a message added to the RenderQueue.
*/
-class Renderer
+class Renderer : public Program::LifecycleObserver
{
public:
/**
*/
Vector4 GetTextureUpdateArea() const noexcept;
+public: // From Program::LifecycleObserver
+ /**
+ * @copydoc Dali::Internal::Program::LifecycleObserver::ProgramDestroyed()
+ */
+ void ProgramDestroyed(const Program* program);
+
private:
struct UniformIndexMap;
const PropertyInputImpl* propertyValue{nullptr}; ///< The property value
Hash uniformNameHash{0u};
Hash uniformNameHashNoArray{0u};
- int32_t arrayIndex{-1}; ///< The array index
+ int32_t arrayIndex{-1}; ///< The array index
uint32_t arrayElementStride{0u}; ///< The stride for element of an array (0 - tightly packed)
int16_t uniformLocation{0u};
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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.
{
namespace Internal
{
+namespace
+{
+constexpr uint32_t MAXIMUM_COLLECTING_ITEM_COUNTS_PER_GC_CALL = 5u;
+static_assert(1u <= MAXIMUM_COLLECTING_ITEM_COUNTS_PER_GC_CALL); /// Should delete at least 1 item.
+} // namespace
+
ProgramController::ProgramController(Graphics::Controller& graphicsController)
-: mGraphicsController(graphicsController)
+: mGraphicsController(graphicsController),
+ mProgramCacheAdded(false)
{
mProgramCache.Reserve(32);
+
+ mClearCacheIterator = mProgramCache.Begin();
}
ProgramController::~ProgramController() = default;
-void ProgramController::ResetReferenceCount()
+void ProgramController::ResetUsedFlag()
{
for(auto&& item : mProgramCache)
{
- item->ClearReferenceCount();
+ item->ClearUsedFlag();
}
+ mClearCacheIterator = mProgramCache.Begin();
}
-void ProgramController::ClearUnusedCache()
+bool ProgramController::ClearUnusedCacheIncrementally(bool fullCollect)
{
- for(auto iter = mProgramCache.Begin(); iter != mProgramCache.End();)
+ if(mProgramCacheAdded)
{
- if((*iter)->GetReferenceCount() == 0u)
+ // Clear from begin again if cache container changed.
+ mClearCacheIterator = mProgramCache.Begin();
+
+ // Reset flag
+ mProgramCacheAdded = false;
+ }
+
+ uint32_t checkedCount = 0u;
+ // Check only limited items. (Since this loop give overhead for rendering.)
+ // TODO : Could we check running time here, instead of check counter?
+ for(; mClearCacheIterator != mProgramCache.End() && (fullCollect || ++checkedCount <= MAXIMUM_COLLECTING_ITEM_COUNTS_PER_GC_CALL);)
+ {
+ if(!((*mClearCacheIterator)->IsUsed()))
{
- iter = mProgramCache.Erase(iter);
+ mClearCacheIterator = mProgramCache.Erase(mClearCacheIterator);
}
else
{
- ++iter;
+ ++mClearCacheIterator;
}
}
+
+ return mClearCacheIterator != mProgramCache.End();
}
Program* ProgramController::GetProgram(size_t shaderHash)
size_t hash = (*iter)->GetHash();
if(shaderHash == hash)
{
- (*iter)->Reference();
+ (*iter)->MarkAsUsed();
program = (*iter)->GetProgram();
break;
}
// we expect unique hash values so it is caller's job to guarantee that
// AddProgram is only called after program checks that GetProgram returns NULL
mProgramCache.PushBack(new ProgramPair(program, shaderHash));
+
+ mProgramCacheAdded = true;
}
} // namespace Internal
#define DALI_INTERNAL_PROGRAM_CONTROLLER_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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.
ProgramPair(Program* program, size_t shaderHash)
: mProgram(program),
mShaderHash(shaderHash),
- mRefCount{1u}
+ mUsed{true} ///< Initialize as be used at construct time.
{
}
return mShaderHash;
}
- [[nodiscard]] inline uint16_t GetReferenceCount() const
+ [[nodiscard]] inline bool IsUsed() const
{
- return mRefCount;
+ return mUsed;
}
- void Reference()
+ void MarkAsUsed()
{
- ++mRefCount;
+ mUsed = true;
}
- void ClearReferenceCount()
+ void ClearUsedFlag()
{
- mRefCount = 0u;
+ mUsed = false;
}
ProgramPair(const ProgramPair&) = delete;
private: // Data
Program* mProgram;
size_t mShaderHash;
- uint16_t mRefCount;
+ bool mUsed : 1;
};
/**
public: // API
/**
- * @brief Reset all program reference count as 0.
+ * @brief Reset all program used flags.
+ * @note The used flag of program will be marked when we call GetProgram() or AddProgram().
*/
- void ResetReferenceCount();
+ void ResetUsedFlag();
/**
- * @brief Clear program who the reference count is 0.
+ * @brief Clear program incrementally who are not be used.
+ *
+ * @param[in] fullCollect True if we want to clear whole items.
+ * @return True if we need to iterate more item to check used count. False if we clear cache completely.
*/
- void ClearUnusedCache();
+ bool ClearUnusedCacheIncrementally(bool fullCollect);
+
+ /**
+ * @brief Get the number of cached program
+ *
+ * @return The number of cached program.
+ */
+ uint32_t GetCachedProgramCount() const
+ {
+ return static_cast<uint32_t>(mProgramCache.Count());
+ }
private: // From ProgramCache
/**
using ProgramContainer = OwnerContainer<ProgramPair*>;
using ProgramIterator = ProgramContainer::Iterator;
ProgramContainer mProgramCache;
+
+ ProgramIterator mClearCacheIterator;
+ bool mProgramCacheAdded : 1;
};
} // namespace Internal
Program::Program(ProgramCache& cache, Internal::ShaderDataPtr shaderData, Graphics::Controller& controller)
: mCache(cache),
- mGfxProgram(nullptr),
+ mLifecycleObservers(),
mGfxController(controller),
- mProgramData(std::move(shaderData))
+ mProgramData(std::move(shaderData)),
+ mObserverNotifying(false)
{
}
-Program::~Program() = default;
+Program::~Program()
+{
+ mObserverNotifying = true;
+ for(auto&& iter : mLifecycleObservers)
+ {
+ auto* observer = iter.first;
+ observer->ProgramDestroyed(this);
+ }
+ mLifecycleObservers.clear();
+
+ // Note : We don't need to restore mObserverNotifying to false as we are in delete the object.
+ // If someone call AddObserver / RemoveObserver after this, assert.
+}
void Program::BuildReflection(const Graphics::Reflection& graphicsReflection, Render::UniformBufferManager& uniformBufferManager)
{
#define DALI_INTERNAL_PROGRAM_H
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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.
// EXTERNAL INCLUDES
#include <cstdint> // int32_t, uint32_t
#include <string>
+#include <unordered_map>
// INTERNAL INCLUDES
#include <dali/internal/common/const-string.h>
namespace Render
{
class UniformBufferManager;
-}
+} // namespace Render
/**
* A program contains a vertex & fragment shader.
using Hash = std::size_t;
/**
+ * Observer to determine when the program is no longer present
+ */
+ class LifecycleObserver
+ {
+ public:
+ /**
+ * Called shortly before the program is destroyed.
+ */
+ virtual void ProgramDestroyed(const Program* program) = 0;
+
+ protected:
+ /**
+ * Virtual destructor, no deletion through this interface
+ */
+ virtual ~LifecycleObserver() = default;
+ };
+
+ /**
* Indices of default uniforms
*/
enum class DefaultUniformIndex
*/
[[nodiscard]] const Graphics::UniformInfo* GetDefaultUniform(DefaultUniformIndex defaultUniformIndex) const;
+ /**
+ * Allows Program to track the life-cycle of this object.
+ * Note that we allow to observe lifecycle multiple times.
+ * But ProgramDestroyed callback will be called only one time.
+ * @param[in] observer The observer to add.
+ */
+ void AddLifecycleObserver(LifecycleObserver& observer)
+ {
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Cannot add observer while notifying Program::LifecycleObservers");
+
+ auto iter = mLifecycleObservers.find(&observer);
+ if(iter != mLifecycleObservers.end())
+ {
+ // Increase the number of observer count
+ ++(iter->second);
+ }
+ else
+ {
+ mLifecycleObservers.insert({&observer, 1u});
+ }
+ }
+
+ /**
+ * The Program no longer needs to track the life-cycle of this object.
+ * @param[in] observer The observer that to remove.
+ */
+ void RemoveLifecycleObserver(LifecycleObserver& observer)
+ {
+ DALI_ASSERT_ALWAYS(!mObserverNotifying && "Cannot remove observer while notifying Program::LifecycleObservers");
+
+ auto iter = mLifecycleObservers.find(&observer);
+ DALI_ASSERT_ALWAYS(iter != mLifecycleObservers.end());
+
+ if(--(iter->second) == 0u)
+ {
+ mLifecycleObservers.erase(iter);
+ }
+ }
+
private: // Implementation
/**
* Constructor, private so no direct instantiation
return mUniformBlockMemoryRequirements;
}
-private: // Data
- ProgramCache& mCache; ///< The program cache
+private: // Data
+ ProgramCache& mCache; ///< The program cache
+
+ using LifecycleObserverContainer = std::unordered_map<LifecycleObserver*, uint32_t>; ///< Lifecycle observers container. We allow to add same observer multiple times.
+ ///< Key is a pointer to observer, value is the number of observer added.
+ LifecycleObserverContainer mLifecycleObservers;
+
Graphics::UniquePtr<Graphics::Program> mGfxProgram; ///< Gfx program
- Graphics::Controller& mGfxController; /// < Gfx controller
+ Graphics::Controller& mGfxController; ///< Gfx controller
Internal::ShaderDataPtr mProgramData; ///< Shader program source and binary (when compiled & linked or loaded)
// uniform value caching
UniformReflectionContainer mReflectionDefaultUniforms{}; ///< Contains default uniforms
UniformBlockMemoryRequirements mUniformBlockMemoryRequirements; ///< Memory requirements per each block, block 0 = standalone/emulated
+
+ bool mObserverNotifying : 1; ///< Safety guard flag to ensure that the LifecycleObserver not be added or deleted while observing.
};
} // namespace Internal