From: David Steele Date: Thu, 7 Jul 2022 10:14:51 +0000 (+0000) Subject: Merge "Fix npatch visual auxiliary bug" into devel/master X-Git-Tag: dali_2.1.30~1 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=a79b9acaf94aa703c82b6b39d2672d7cfd2999d9;hp=b861f4c549049d55142a0f913bd1fab701511544 Merge "Fix npatch visual auxiliary bug" into devel/master --- diff --git a/automated-tests/resources/dali-logo.webp b/automated-tests/resources/dali-logo.webp new file mode 100644 index 0000000..2f0eb2b Binary files /dev/null and b/automated-tests/resources/dali-logo.webp differ diff --git a/automated-tests/src/dali-toolkit/CMakeLists.txt b/automated-tests/src/dali-toolkit/CMakeLists.txt index 1dbbf90..7076e8a 100755 --- a/automated-tests/src/dali-toolkit/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit/CMakeLists.txt @@ -7,6 +7,7 @@ SET(CAPI_LIB "dali-toolkit") # List of test case sources (Only these get parsed for test cases) SET(TC_SOURCES + toolkit-direct-rendering-egl.cpp utc-Dali-Alignment.cpp utc-Dali-AnimatedImageVisual.cpp utc-Dali-AnimatedVectorImageVisual.cpp diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-graphics-controller.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-graphics-controller.cpp index 6a8eab4..fc176e5 100644 --- a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-graphics-controller.cpp +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/test-graphics-controller.cpp @@ -32,6 +32,8 @@ #include #include +#include + namespace Dali { std::ostream& operator<<(std::ostream& o, const Graphics::BufferCreateInfo& bufferCreateInfo) @@ -696,6 +698,13 @@ void TestGraphicsController::ProcessCommandBuffer(TestGraphicsCommandBuffer& com case CommandType::DRAW_NATIVE: { auto info = &cmd.data.draw.drawNative.drawNativeInfo; + + if(info->glesNativeInfo.eglSharedContextStoragePointer) + { + auto* anyContext = reinterpret_cast(info->glesNativeInfo.eglSharedContextStoragePointer); + *anyContext = reinterpret_cast(0x12345678u); + } + CallbackBase::ExecuteReturn(*info->callback, info->userData); break; } diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-vector-image-renderer.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-vector-image-renderer.cpp index 3093fcb..78252b8 100644 --- a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-vector-image-renderer.cpp +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-vector-image-renderer.cpp @@ -34,7 +34,38 @@ namespace Adaptor namespace { Dali::Internal::Adaptor::VectorImageRenderer* gVectorImageRenderer = nullptr; + +/** + * @brief Check whether the data contain keyword as substring or not. + * It can be used as Naive Check-up to determine vector image loaded successfully or not. + * + * @param[in] data Raw data + * @param[in] keyword Keyword to check data holded or not. + * @return True if data contain keyword. + */ +bool CheckKeywordExist(const Vector& data, std::string keyword) +{ + std::string trimedString; + + // Remove useless character in data. + for(const uint8_t& it : data) + { + if(keyword.find(it) != std::string::npos) + { + trimedString.push_back(it); + } + } + + if(trimedString.length() >= keyword.length()) + { + if(trimedString.find(keyword) != std::string::npos) + { + return true; + } + } + return false; } +} // namespace class VectorImageRenderer : public Dali::BaseObject { @@ -58,6 +89,11 @@ public: { mRasterizeSuccess = false; } + // Naive check-up whether data is valid format or not. Currently we only check svg and tvg file format. + else if(!CheckKeywordExist(data, "") && !CheckKeywordExist(data, "ThorVG")) + { + return false; + } mLoadSuccess = true; return true; } diff --git a/automated-tests/src/dali-toolkit/toolkit-direct-rendering-egl.cpp b/automated-tests/src/dali-toolkit/toolkit-direct-rendering-egl.cpp new file mode 100644 index 0000000..35ee43e --- /dev/null +++ b/automated-tests/src/dali-toolkit/toolkit-direct-rendering-egl.cpp @@ -0,0 +1,140 @@ +/* +* Copyright (c) 2022 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. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ + +#include +#include +extern "C" +{ + +// Flag to be set when we want shader compilation fail +bool gDirectRenderingFailCreateShader = false; + +// Flag to be set when we want program linking fail +bool gDirectRenderingFailCreateProgram = false; + +/** + * To test the multithreaded variant we need override EGL api + * + * The Direct Rendering uses GL directly and it's needed to override certain funtions in order + * to force code execution. + */ +EGLContext eglCreateContext (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list) +{ + return EGLContext(0x12345678); +} + +EGLBoolean eglGetConfigs (EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config) +{ + static EGLConfig config; + if(num_config) + { + *num_config = 1; + } + if(configs) + { + configs[0] = config; + } + + return EGL_TRUE; +} + +GLuint glCreateProgram (void) +{ + static uint32_t programId = 1; + return programId++; +} + +GLuint glCreateShader(GLenum type) +{ + static uint32_t shaderId = 1; + return shaderId++; +} + +void glCompileShader(GLuint shader) +{ +} + +void glLinkProgram (GLuint program) +{ +} + +void glGenTextures(GLsizei n, GLuint *textures) +{ + static GLuint texId = 1u; + for(auto i = 0; i < n; ++i) + { + textures[i] = texId++; + } +} + +void glGetShaderiv(GLuint shader, GLenum pname, GLint *params) +{ + if(pname == GL_COMPILE_STATUS) + { + params[0] = gDirectRenderingFailCreateShader ? GL_FALSE : GL_TRUE; + } + else if(pname == GL_INFO_LOG_LENGTH) + { + params[0] = 4; + } +} + +void glGetProgramiv(GLuint shader, GLenum pname, GLint *params) +{ + if(pname == GL_LINK_STATUS) + { + params[0] = gDirectRenderingFailCreateProgram ? GL_FALSE : GL_TRUE; + } + else if(pname == GL_INFO_LOG_LENGTH) + { + params[0] = 4; + } +} + +void glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) +{ + infoLog[0] = '0'; + infoLog[1] = '\n'; +} + +void glGetProgramInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) +{ + infoLog[0] = '0'; + infoLog[1] = '\n'; +} + +void glDeleteSync (GLsync sync) +{ +} + +GLenum glClientWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout) +{ + return GL_CONDITION_SATISFIED; +} + +GLsync glFenceSync (GLenum condition, GLbitfield flags) +{ + static uint32_t syncId = 0; + return reinterpret_cast(++syncId); +} + +GLenum glCheckFramebufferStatus (GLenum target) +{ + return GL_FRAMEBUFFER_COMPLETE; +} + +} \ No newline at end of file diff --git a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp index 6e8c9d7..9870308 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-AnimatedImageVisual.cpp @@ -49,6 +49,7 @@ namespace const char* TEST_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/application-icon-%02d.png"; const char* TEST_GIF_FILE_NAME = TEST_RESOURCE_DIR "/anim.gif"; const char* TEST_MASK_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/mask.png"; +const char* TEST_WEBP_FILE_NAME = TEST_RESOURCE_DIR "/dali-logo.webp"; } // namespace void CopyUrlsIntoArray(Property::Array& urls, int startIndex = 0) @@ -1779,3 +1780,71 @@ int UtcDaliAnimatedImageVisualPlayback(void) END_TEST; } + +int UtcDaliAnimatedImageVisualWrapMode(void) +{ + ToolkitTestApplication application; + tet_infoline("UtcDaliAnimatedImageVisualWrapMode"); + + VisualFactory factory = VisualFactory::Get(); + DALI_TEST_CHECK(factory); + + // Test wrap mode in animated image visual. + const int width = 950; + const int height = 1080; + const Vector4 pixelArea(0.0f, 0.0f, 950/ 40, 1.0f); + + Property::Map propertyMap; + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::IMAGE); + propertyMap.Insert(ImageVisual::Property::URL, TEST_WEBP_FILE_NAME); + propertyMap.Insert(ImageVisual::Property::PIXEL_AREA, pixelArea); + propertyMap.Insert(ImageVisual::Property::WRAP_MODE_U, WrapMode::REPEAT); + + Visual::Base visual = factory.CreateVisual(propertyMap); + DALI_TEST_CHECK(visual); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + textureTrace.EnableLogging(true); + TraceCallStack& texParameterTrace = gl.GetTexParameterTrace(); + texParameterTrace.Enable(true); + texParameterTrace.EnableLogging(true); + + DummyControl actor = DummyControl::New(); + DummyControlImpl& dummyImpl = static_cast(actor.GetImplementation()); + dummyImpl.RegisterVisual(Control::CONTROL_PROPERTY_END_INDEX + 1, visual); + actor.SetProperty(Actor::Property::SIZE, Vector2(width, height)); + actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER); + + DALI_TEST_EQUALS(actor.GetRendererCount(), 0u, TEST_LOCATION); + application.GetScene().Add(actor); + application.SendNotification(); + application.Render(); + + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + DALI_TEST_CHECK(actor.GetRendererCount() == 1u); + + DALI_TEST_EQUALS(textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION); + + // WITHOUT atlasing, the wrapping is handled by setting gl texture parameters + std::stringstream out; + out << std::hex << GL_TEXTURE_2D << ", " << GL_TEXTURE_WRAP_S << ", " << GL_REPEAT; + DALI_TEST_CHECK(texParameterTrace.FindMethodAndParams("TexParameteri", out.str())); + + // test the uniforms which used to handle the wrap mode + Renderer renderer = actor.GetRendererAt(0u); + DALI_TEST_CHECK(renderer); + + Property::Value pixelAreaValue = renderer.GetProperty(renderer.GetPropertyIndex("pixelArea")); + DALI_TEST_EQUALS(pixelAreaValue.Get(), pixelArea, TEST_LOCATION); + + actor.Unparent(); + DALI_TEST_CHECK(actor.GetRendererCount() == 0u); + + END_TEST; +} diff --git a/automated-tests/src/dali-toolkit/utc-Dali-GlViewDirectRendering.cpp b/automated-tests/src/dali-toolkit/utc-Dali-GlViewDirectRendering.cpp index 82e3be9..75eb570 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-GlViewDirectRendering.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-GlViewDirectRendering.cpp @@ -23,10 +23,13 @@ #include #include #include +#include + using namespace Dali; using namespace Dali::Toolkit; + // Positive test case for a method int UtcDaliGlViewDirectRenderingNew(void) { @@ -202,6 +205,24 @@ void glTerminate(void) { } + +// Internal callback function +void glInitMT(Dali::RenderCallbackInput& input) +{ +} + +int gDRFramesRendered = 0; + +int glRenderFrameMT(Dali::RenderCallbackInput& input) +{ + gDRFramesRendered++; + return 1; +} + +void glTerminateMT(Dali::RenderCallbackInput& input) +{ +} + void resizeCB(Vector2 size) { } @@ -385,4 +406,110 @@ int UtcDaliGlViewDirectRenderingTerminateCallback(void) DALI_TEST_CHECK(true); END_TEST; -} \ No newline at end of file +} + +// Positive test case for a method +int UtcDaliGlViewDirectRenderingThreadedNew(void) +{ + ToolkitTestApplication application; + tet_infoline(" UtcDaliGlViewDirectRenderingThreadedNew"); + GlView view = GlView::New(GlView::BackendMode::DIRECT_RENDERING_THREADED, GlView::ColorFormat::RGBA8888); + DALI_TEST_CHECK(view); + + auto mode1 = view.GetBackendMode(); + + DALI_TEST_EQUALS(mode1, GlView::BackendMode::DIRECT_RENDERING_THREADED, TEST_LOCATION); + + END_TEST; +} + +int UtcDaliGlViewDirectRenderingThreadedOnScene(void) +{ + ToolkitTestApplication application; + + GlView view = Toolkit::GlView::New(GlView::BackendMode::DIRECT_RENDERING_THREADED, GlView::ColorFormat::RGB888); + + //Onscene + application.GetScene().Add(view); + view.SetRenderingMode(GlView::RenderingMode::CONTINUOUS); + view.SetGraphicsConfig(true, true, 0, GlView::GraphicsApiVersion::GLES_VERSION_3_0); + view.RegisterGlCallbacks(Dali::MakeCallback(DirectRenderingCode::glInitMT), Dali::MakeCallback(DirectRenderingCode::glRenderFrameMT), Dali::MakeCallback(DirectRenderingCode::glTerminateMT)); + view.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + view.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT); + + // Set size on the actor (half the window size to show that glClear() and scissor test work together) + view.SetProperty(Actor::Property::SIZE, Size(100, 100)); + view.SetProperty(Actor::Property::POSITION, Vector2(0,0)); + + while( DirectRenderingCode::gDRFramesRendered < 1 ) + { + application.SendNotification(); + application.Render(); + } + DALI_TEST_CHECK(true); + END_TEST; +} + +extern "C" bool gDirectRenderingFailCreateShader; +extern "C" bool gDirectRenderingFailCreateProgram; + + +int UtcDaliGlViewDirectRenderingThreadedOnScene1(void) +{ + ToolkitTestApplication application; + + GlView view = Toolkit::GlView::New(GlView::BackendMode::DIRECT_RENDERING_THREADED, GlView::ColorFormat::RGB888); + + // This test will fail instantiating shaders + gDirectRenderingFailCreateShader = true; + + //Onscene + application.GetScene().Add(view); + view.SetRenderingMode(GlView::RenderingMode::CONTINUOUS); + view.SetGraphicsConfig(true, true, 0, GlView::GraphicsApiVersion::GLES_VERSION_3_0); + view.RegisterGlCallbacks(Dali::MakeCallback(DirectRenderingCode::glInitMT), Dali::MakeCallback(DirectRenderingCode::glRenderFrameMT), Dali::MakeCallback(DirectRenderingCode::glTerminateMT)); + view.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + view.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT); + + // Set size on the actor (half the window size to show that glClear() and scissor test work together) + view.SetProperty(Actor::Property::SIZE, Size(100, 100)); + view.SetProperty(Actor::Property::POSITION, Vector2(0,0)); + + while( DirectRenderingCode::gDRFramesRendered < 1 ) + { + application.SendNotification(); + application.Render(); + } + DALI_TEST_CHECK(true); + END_TEST; +} + +int UtcDaliGlViewDirectRenderingThreadedOnScene2(void) +{ + ToolkitTestApplication application; + + GlView view = Toolkit::GlView::New(GlView::BackendMode::DIRECT_RENDERING_THREADED, GlView::ColorFormat::RGB888); + + // This test will fail instantiating shaders + gDirectRenderingFailCreateProgram = true; + + //Onscene + application.GetScene().Add(view); + view.SetRenderingMode(GlView::RenderingMode::CONTINUOUS); + view.SetGraphicsConfig(true, true, 0, GlView::GraphicsApiVersion::GLES_VERSION_3_0); + view.RegisterGlCallbacks(Dali::MakeCallback(DirectRenderingCode::glInitMT), Dali::MakeCallback(DirectRenderingCode::glRenderFrameMT), Dali::MakeCallback(DirectRenderingCode::glTerminateMT)); + view.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); + view.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT); + + // Set size on the actor (half the window size to show that glClear() and scissor test work together) + view.SetProperty(Actor::Property::SIZE, Size(100, 100)); + view.SetProperty(Actor::Property::POSITION, Vector2(0,0)); + + while( DirectRenderingCode::gDRFramesRendered < 1 ) + { + application.SendNotification(); + application.Render(); + } + DALI_TEST_CHECK(true); + END_TEST; +} diff --git a/build/tizen/CMakeLists.txt b/build/tizen/CMakeLists.txt index 2258fab..31cfe9f 100644 --- a/build/tizen/CMakeLists.txt +++ b/build/tizen/CMakeLists.txt @@ -376,7 +376,7 @@ TARGET_LINK_LIBRARIES( ${name} ) IF( ANDROID ) - TARGET_LINK_LIBRARIES( ${name} log ) + TARGET_LINK_LIBRARIES( ${name} log GLESv3 EGL) ENDIF() SET_TARGET_PROPERTIES( ${name} diff --git a/dali-scene-loader/public-api/facial-animation-loader.cpp b/dali-scene-loader/public-api/facial-animation-loader.cpp index 3209467..6c92d6a 100644 --- a/dali-scene-loader/public-api/facial-animation-loader.cpp +++ b/dali-scene-loader/public-api/facial-animation-loader.cpp @@ -18,6 +18,9 @@ // FILE HEADER #include +// EXTERNAL INCLUDES +#include + // INTERNAL INCLUDES #include #include @@ -135,9 +138,7 @@ AnimationDefinition LoadFacialAnimation(const std::string& url) } // Set the property names - char weightNameBuffer[32]; - char* const pWeightName = weightNameBuffer + sprintf(weightNameBuffer, "%s", BlendShapes::WEIGHTS_UNIFORM.c_str()); - uint32_t targets = 0u; + uint32_t targets = 0u; for(const auto& blendShape : facialAnimation.mBlendShapes) { for(uint32_t morphTargetIndex = 0u; morphTargetIndex < blendShape.mNumberOfMorphTarget; ++morphTargetIndex) @@ -147,8 +148,9 @@ AnimationDefinition LoadFacialAnimation(const std::string& url) animatedProperty.mNodeName = blendShape.mNodeName; - snprintf(pWeightName, sizeof(weightNameBuffer) - (pWeightName - weightNameBuffer), "[%d]", morphTargetIndex); - animatedProperty.mPropertyName = weightNameBuffer; + std::stringstream weightPropertyStream; + weightPropertyStream << BlendShapes::WEIGHTS_UNIFORM << "[" << morphTargetIndex << "]"; + animatedProperty.mPropertyName = weightPropertyStream.str(); } targets += blendShape.mNumberOfMorphTarget; } diff --git a/dali-toolkit/internal/controls/gl-view/drawable-view-impl.cpp b/dali-toolkit/internal/controls/gl-view/drawable-view-impl.cpp index 9b94546..4cbda06 100644 --- a/dali-toolkit/internal/controls/gl-view/drawable-view-impl.cpp +++ b/dali-toolkit/internal/controls/gl-view/drawable-view-impl.cpp @@ -24,34 +24,40 @@ #include #include #include +#include namespace Dali::Toolkit::Internal { -Dali::Toolkit::GlView DrawableView::New() +Dali::Toolkit::GlView DrawableView::New(GlView::BackendMode backendMode) { - auto* impl = new DrawableView(); + auto* impl = new DrawableView(backendMode); Dali::Toolkit::GlView handle = Dali::Toolkit::GlView(*impl); impl->Initialize(); return handle; } -DrawableView::DrawableView() -: Dali::Toolkit::Internal::GlViewImpl( GlView::BackendMode::DIRECT_RENDERING ), +DrawableView::DrawableView(GlView::BackendMode backendMode) +: Dali::Toolkit::Internal::GlViewImpl( backendMode), mRenderingMode(Toolkit::GlView::RenderingMode::CONTINUOUS), mDepth(false), mStencil(false), mMSAA(0) { mRenderCallback = RenderCallback::New( this, &DrawableView::OnRenderCallback); + + // Create NativeRenderer + Dali::Internal::NativeRendererCreateInfo createInfo; + createInfo.maxOffscreenBuffers = 2u; + createInfo.threadEnabled = (backendMode == GlView::BackendMode::DIRECT_RENDERING_THREADED); + createInfo.presentationMode = Dali::Internal::NativeRendererCreateInfo::PresentationMode::FIFO; + mNativeRenderer = std::make_unique(createInfo); } DrawableView::~DrawableView() = default; void DrawableView::RegisterGlCallbacks(CallbackBase* initCallback, CallbackBase* renderFrameCallback, CallbackBase* terminateCallback) { - mOnInitCallback.reset( initCallback ); - mOnRenderCallback.reset(renderFrameCallback ); - mOnTerminateCallback. reset( terminateCallback ); + mNativeRenderer->RegisterGlCallbacks( initCallback, renderFrameCallback, terminateCallback ); } void DrawableView::SetResizeCallback(CallbackBase* resizeCallback) @@ -61,8 +67,8 @@ void DrawableView::SetResizeCallback(CallbackBase* resizeCallback) bool DrawableView::SetGraphicsConfig(bool depth, bool stencil, int msaa, Dali::Toolkit::GlView::GraphicsApiVersion version) { - DALI_LOG_ERROR( "DrawableView::SetGraphicsConfig() is currently not implemented"); - + // Currently, the settings are not relevant for the DirectRendering feature as all the + // setup is inherited from DALi graphics backend. return true; } @@ -110,8 +116,9 @@ void DrawableView::OnSizeSet(const Vector3& targetSize) mSurfaceSize = targetSize; // If the callbacks are set then schedule execution of resize callback - if(mRenderCallback && mOnResizeCallback) + if(mRenderCallback && mNativeRenderer) { + mNativeRenderer->Resize( uint32_t(targetSize.width), uint32_t(targetSize.height)); mSurfaceResized = true; } } @@ -144,6 +151,8 @@ void DrawableView::OnSceneConnection(int depth) void DrawableView::OnSceneDisconnection() { Control::OnSceneDisconnection(); + + mNativeRenderer->Terminate(); } void DrawableView::AddRenderer() @@ -155,17 +164,24 @@ void DrawableView::AddRenderer() bool DrawableView::OnRenderCallback( const RenderCallbackInput& renderCallbackInput ) { + if(mNativeRenderer) + { + mNativeRenderer->PushRenderCallbackInputData( renderCallbackInput ); + } + // Init state if( mCurrentViewState == ViewState::INIT ) { - if(mOnInitCallback) - { - CallbackBase::Execute(*mOnInitCallback); - } + mNativeRenderer->InvokeGlInitCallback(renderCallbackInput); mCurrentViewState = ViewState::RENDER; } - int renderFrameResult = 0; + if(mSurfaceResized) + { + mNativeRenderer->Resize( uint32_t(mSurfaceSize.width), uint32_t(mSurfaceSize.height) ); + mSurfaceResized = false; + } + if( mCurrentViewState == ViewState::RENDER ) { // The mSurfaceResized is set by another thread so atomic check must be provided @@ -178,20 +194,13 @@ bool DrawableView::OnRenderCallback( const RenderCallbackInput& renderCallbackIn CallbackBase::Execute(*mOnResizeCallback, static_cast(mSurfaceSize.x), static_cast(mSurfaceSize.y)); } - if(mOnRenderCallback) - { - renderFrameResult = CallbackBase::ExecuteReturn(*mOnRenderCallback); - if(renderFrameResult) - { - // TODO: may be utilized for RenderOnce feature - } - } + mNativeRenderer->InvokeGlRenderCallback(renderCallbackInput); } // The terminate callback isn't easy to implement for DR. The NativeImage backend // calls it when the GlView is being destroyed. For DrawableView it means that // the RenderCallback won't be executed (as it is a part of graphics pipeline). - // We don't have currenty no way to know whether the View will be destroyed and + // We don't have currently have any way to know whether the View will be destroyed and // to execute last native draw command in the pipeline. // // else if( mCurrentViewState == ViewState::TERMINATE ) diff --git a/dali-toolkit/internal/controls/gl-view/drawable-view-impl.h b/dali-toolkit/internal/controls/gl-view/drawable-view-impl.h index c61832a..55f1db3 100644 --- a/dali-toolkit/internal/controls/gl-view/drawable-view-impl.h +++ b/dali-toolkit/internal/controls/gl-view/drawable-view-impl.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_INTERNAL_DRAWABLE_VIEW_H /* - * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * Copyright (c) 2022 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. @@ -29,6 +29,7 @@ // INTERNAL INCLUDES #include +#include #include #include @@ -44,15 +45,23 @@ protected: virtual ~DrawableView(); public: + /** - * @copydoc Dali::Toolkit::GlView::New() + * @brief Creates GlView interface object using DrawableView implementation + * + * @param[in] backendMode Backend mode to be used. Only DIRECT_RENDERING and DIRECT_RENDERING_THREADED + * are accepted. + * @return Valid GlView object or nullptr on error */ - static Dali::Toolkit::GlView New(); + static Dali::Toolkit::GlView New(GlView::BackendMode backendMode); /** - * Construct a new GlView. + * @brief Constructor creates GlView interface object using DrawableView implementation + * + * @param[in] backendMode Backend mode to be used. Only DIRECT_RENDERING and DIRECT_RENDERING_THREADED + * are accepted. */ - DrawableView(); + explicit DrawableView(GlView::BackendMode backendMode); /** * @copydoc Dali::Toolkit::GlView::RegisterGlCallbacks() @@ -150,15 +159,13 @@ private: ViewState mCurrentViewState{ViewState::INIT}; ///< state within RenderCallback - // These callbacks are stored for GLView API compatibility - std::unique_ptr mOnInitCallback; - std::unique_ptr mOnRenderCallback; - std::unique_ptr mOnTerminateCallback; - std::unique_ptr mOnResizeCallback; + std::unique_ptr mOnResizeCallback; ///< Resize callback called when surface size changes std::atomic_bool mSurfaceResized{false}; ///< Flag to invoke surface resize callback Size mSurfaceSize{}; ///< Surface size + + std::unique_ptr mNativeRenderer; ///< Pointer to the native renderer }; } // namespace Internal diff --git a/dali-toolkit/internal/controls/gl-view/drawable-view-native-renderer.cpp b/dali-toolkit/internal/controls/gl-view/drawable-view-native-renderer.cpp new file mode 100644 index 0000000..78f45a7 --- /dev/null +++ b/dali-toolkit/internal/controls/gl-view/drawable-view-native-renderer.cpp @@ -0,0 +1,823 @@ +/* + * Copyright (c) 2021 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "drawable-view-native-renderer.h" +#include +#include +#include +#include +#include +#include + +// GLES3+ is required for this to work! +#include +#include +#include + +#include +#include + +#define GL(x) \ + { \ + glGetError(); \ + { \ + x; \ + }; \ + auto err = glGetError(); \ + if(err) \ + { \ + printf("%p:%d: ERROR: 0x%X\n", this, __LINE__, int(err)); \ + } \ + } + +namespace +{ +/** + * Vertices of quad to display when using offscreen rendering + */ +constexpr float QUAD_VERTS[] = { + // positions // colors // texture coords + 1.0f, 1.0f, + 1.0f, -1.0f, + -1.0f, -1.0f, + -1.0f, 1.0f, +}; + +/** + * Indices of quad for offscreen rendering + */ +constexpr unsigned short QUAD_INDICES[] = { + 0, 1, 2, + 3, 0, 2 +}; + +/** + * UV coords of quad for offscreen rendering + */ +constexpr float QUAD_UV[] = { + // positions // colors // texture coords + 1.0f, 1.0f, // top right + 1.0f, 0.0f, // bottom right + 0.0f, 0.0f, // bottom left + 0.0f, 1.0f // top left +}; +} + +namespace Dali::Internal +{ +struct DrawableViewNativeRenderer::Impl +{ + /** + * This structure associates framebuffer with texture and fence object + */ + struct FrameBufferTexture + { + uint32_t textureId{0u}; + uint32_t framebufferId{0u}; + GLsync fence{nullptr}; + }; + + // Queues management + bool DequeueTextureDrawBuffer( uint32_t& outIndex ) + { + std::scoped_lock lock(mTextureQueueMutex); + if(mTextureDrawQueue.empty()) + { + // TODO: probably add textures if necessary + return false; + } + + auto retval = mTextureDrawQueue.front(); + mTextureDrawQueue.pop_front(); + outIndex = retval; + return true; + } + + /** + * Enqueues framebuffer for the Read queue to be used by + * the CONSUMER. + */ + void EnqueueTextureReadBuffer(uint32_t fbId) + { + // push ready texture to front of 'read' queue + std::scoped_lock lock(mTextureQueueMutex); + + auto& fb = mFramebufferTexture[fbId]; + + // Check state of fence whether the texture can be passed to the CONSUMER + if(fb.fence) + { + auto checkFenceState = glClientWaitSync(fb.fence, GL_SYNC_FLUSH_COMMANDS_BIT, 0); + if(checkFenceState == GL_ALREADY_SIGNALED || checkFenceState == GL_CONDITION_SATISFIED) + { + // Ready so push directly to Read queue + mTextureReadQueue.push_back(fbId); + } + else + { + // Still busy so push to Stage queue + mTextureStageQueue.push_back(fbId); + } + } + } + + void EnqueueTextureDrawBuffer(uint32_t fbId) + { + // push ready texture to front of 'read' queue + std::scoped_lock lock(mTextureQueueMutex); + mTextureDrawQueue.push_back(fbId); + } + + int32_t DequeueTextureReadBuffer(FrameBufferTexture& framebufferTexture) + { + // executed by DALi RenderThread! + std::deque backTextures; + std::scoped_lock lock(mTextureQueueMutex); + + if(mTextureReadQueue.empty()) + { + EnqueueStagedTexture(); + } + else + { + while(!mTextureStageQueue.empty()) + { + // we have something to render, so discard + auto stagedId = mTextureStageQueue.back(); + EnqueueTextureDrawBuffer(stagedId); + mTextureStageQueue.pop_back(); + } + } + + if(mTextureReadQueue.empty()) + { + return -1; + } + + auto retval = mTextureReadQueue.back(); + mTextureReadQueue.pop_back(); + + // drain all back images and return them to the 'draw' queue + // and remove old images + while(!mTextureReadQueue.empty()) + { + auto texId = mTextureReadQueue.back(); + if(framebufferTexture.fence) + { + glDeleteSync(framebufferTexture.fence); + framebufferTexture.fence = nullptr; + } + mTextureDrawQueue.push_front(texId); + mTextureReadQueue.pop_back(); + } + + return int32_t(retval); + } + + /** + * Enqueues previously staged texture + */ + uint32_t EnqueueStagedTexture() + { + // test stage queue + std::deque stagedQueue; + bool found = false; + uint32_t retval = 0; + while(!mTextureStageQueue.empty()) + { + auto stagedId = mTextureStageQueue.front(); + auto& fb = mFramebufferTexture[stagedId]; + if(!found) + { + auto syncResult = glClientWaitSync(fb.fence, GL_SYNC_FLUSH_COMMANDS_BIT, 0); + if(syncResult == GL_CONDITION_SATISFIED || syncResult == GL_ALREADY_SIGNALED) + { + // push texture into the queue + mTextureReadQueue.push_back(stagedId); + retval = stagedId; + found = true; + } + else + { + stagedQueue.push_back(stagedId); + } + } + else + { + stagedQueue.push_back(stagedId); + } + mTextureStageQueue.pop_front(); + } + mTextureStageQueue = std::move(stagedQueue); + return retval; + } + + uint32_t CreateFramebuffer(uint32_t index, uint32_t width, uint32_t height) + { + auto& fb = mFramebufferTexture[index]; + if(!fb.framebufferId) + { + GLuint offscreenFramebuffer, renderBuffer; + GL(glGenFramebuffers(1, &offscreenFramebuffer)) + GL(glBindFramebuffer(GL_FRAMEBUFFER, offscreenFramebuffer)); + GL(glGenRenderbuffers(1, &renderBuffer)); + GL(glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer)); + GL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.textureId, 0)); + GL(glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height)) + GL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderBuffer)); + fb.framebufferId = offscreenFramebuffer; + + [[maybe_unused]] auto result = glCheckFramebufferStatus(GL_FRAMEBUFFER); + DALI_ASSERT_ALWAYS( result == GL_FRAMEBUFFER_COMPLETE && "Framebuffer incomplete!"); + return offscreenFramebuffer; + } + return 0u; + } + + + /** + * Constructor + */ + explicit Impl(const NativeRendererCreateInfo& renderCreateInfo) + : mCreateInfo(renderCreateInfo) + { + } + + /** + * Destructor + */ + ~Impl() + { + Terminate(); + if(mThread) + { + mThread->join(); + } + } + + /** + * Initializes renderer thread for offscreen rendering + */ + void InitializeThread() + { + mThread = std::make_unique(&Impl::StartThread, this); + } + + void PushRenderCallbackInputData( const Dali::RenderCallbackInput& renderCallbackInput ) + { + + std::scoped_lock lock(mRenderCallbackInputDataMutex); + mRenderCallbackInputData = renderCallbackInput; + } + + void PopRenderCallbackInputData( Dali::RenderCallbackInput& renderCallbackInput ) + { + std::scoped_lock lock(mRenderCallbackInputDataMutex); + renderCallbackInput = mRenderCallbackInputData; + } + + void Terminate() + { + mRunning = false; + } + + /** + * Function initializes thread for parallel rendering. + * + * The internal loop runs until the private EGL context has been + * initialized. + */ + void StartThread() + { + mRunning = true; + + // We need to acquire shared context, while this is not done + // it's necessary to wait for context to be bound. + while(mRunning && !mEglContextBound) + { + // Wait for context to be given + if(!mEglContext) + { + continue; + } + if(!eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglContext)) + { + [[maybe_unused]] auto err = eglGetError(); + printf("%d\n", int(err)); + } + mEglContextBound = true; + } + + InitializeOffscreenFramebuffers(); + + PrepareBlitShader(); + + ThreadRunRender(); + } + + /** + * Reinitializes offscreen framebuffers and textures in case + * the resize has been requested. + */ + void ReinitializeFramebuffers() + { + int index = 0; + for(auto& fb : mFramebufferTexture) + { + if(fb.fence) + { + GL(glDeleteSync(fb.fence)); + } + if(fb.framebufferId) + { + GL(glDeleteFramebuffers(1, &fb.framebufferId)) + fb.framebufferId = 0u; + } + if(fb.textureId) + { + GL(glDeleteTextures(1, &fb.textureId)) + fb.textureId = 0u; + } + fb.textureId = CreateOffscreenTexture( mWidth, mHeight ); + fb.framebufferId = CreateFramebuffer( index, mWidth, mHeight ); + index++; + } + } + + void ThreadRunRender() + { + while(mRunning) + { + // If there is a resize request waiting, then recreate all framebuffers + if(mResizeRequest) + { + ReinitializeFramebuffers(); + mResizeRequest = false; + } + + Dali::RenderCallbackInput input; + + PopRenderCallbackInputData( input ); + + uint32_t index{0u}; + auto result = DequeueTextureDrawBuffer(index); + if(!result) + { + continue; + } + + auto& fb = mFramebufferTexture[index]; + GL(glBindFramebuffer(GL_FRAMEBUFFER, fb.framebufferId)) + GL(glClear(0)); + + + // Invoke callback + if(mOnRenderCallback) + { + CallbackBase::ExecuteReturn(*mOnRenderCallback); + } + + // If the framebuffer is guarded with fence object then + // delete it as at this point it is no longer valid. + if(fb.fence) + { + // Make sure GPU finished + glDeleteSync(fb.fence); + fb.fence = nullptr; + } + + // Inject sync object into the GL commands stream + fb.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + EnqueueTextureReadBuffer(index); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + } + + void Resize(uint32_t width, uint32_t height ) + { + mWidth = width; + mHeight = height; + mResizeRequest = true; + } + + uint32_t mWidth {0u}; + uint32_t mHeight {0u}; + std::atomic_bool mResizeRequest { false }; + + /** + * Clones current EGL context, this function must be called from the render callback + * and be executed on the DALi RenderThread + */ + void CloneEglContext() + { + // extract shared context (void*) + auto context = eglGetCurrentContext(); + + // Obtain configs + EGLint configId{0u}; + EGLint size{0u}; + eglGetConfigs(mEglDisplay, nullptr, 0, &size); + std::vector configs; + configs.resize(size); + eglGetConfigs(mEglDisplay, configs.data(), EGLint(configs.size()), &size); + + // Find out which config is used by current context + eglQueryContext(mEglDisplay, context, EGL_CONFIG_ID, &configId); + + // Setup EGL version + auto version = int(30); // TODO: get context version and select the same one + std::vector attribs; + attribs.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR); + attribs.push_back(version / 10); + attribs.push_back(EGL_CONTEXT_MINOR_VERSION_KHR); + attribs.push_back(version % 10); + attribs.push_back(EGL_NONE); + + // Create cloned context with shared context + mEglContext = eglCreateContext(mEglDisplay, configs[configId], mEglSharedContext, attribs.data()); + } + + // Pre-, Post- functions are being called from the callbacks + void GlViewPreInit( const Dali::RenderCallbackInput& input ) + { + // This runs on DALi RenderThread!!! + + // Bind the shared context in case of threaded rendering + if(mThread && !mEglContextBound) + { + // Store the shared context just once + if(!mEglSharedContext ) + { + // Store the shared context returned by the drawable callback + mEglSharedContext = std::any_cast(input.eglContext); + } + // Setup the EGL context + mEglDisplay = eglGetCurrentDisplay(); + + // switch to shared context in order to create shared GL resources + auto currentContext = eglGetCurrentContext(); + + // Retrieve current surfaces (read and draw) + mDrawSurface = eglGetCurrentSurface(EGL_DRAW); + mReadSurface = eglGetCurrentSurface(EGL_READ); + + eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglSharedContext); + + [[maybe_unused]] auto eglError = eglGetError(); + + // Now clone it to create compatible context for our threaded rendering + CloneEglContext(); + + // Bring back current context + eglMakeCurrent(mEglDisplay, mDrawSurface, mReadSurface, currentContext); + } + } + + GLuint CreateProgram(const char* vertexSource, const char* fragmentSource) + { + GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vertexSource); + if(!vertexShader) + { + return 0; + } + GLuint fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fragmentSource); + if(!fragmentShader) + { + return 0; + } + GLuint program = glCreateProgram(); + if(program) + { + GL(glAttachShader(program, vertexShader)); + GL(glAttachShader(program, fragmentShader)); + GL(glLinkProgram(program)); + GLint linkStatus = GL_FALSE; + GL(glGetProgramiv(program, GL_LINK_STATUS, &linkStatus)); + if(linkStatus != GL_TRUE) + { + GLint bufLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); + if(bufLength) + { + char* buf = (char*)malloc(bufLength); + if(buf) + { + glGetProgramInfoLog(program, bufLength, NULL, buf); + free(buf); + } + } + glDeleteProgram(program); + program = 0; + } + } + return program; + } + + GLuint LoadShader(GLenum shaderType, const char* shaderSource) + { + GLuint shader = glCreateShader(shaderType); + if(shader != 0) + { + GL(glShaderSource(shader, 1, &shaderSource, NULL)); + GL(glCompileShader(shader)); + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if(compiled != GL_TRUE) + { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + + if(infoLen > 0) + { + char* logBuffer = (char*)malloc(infoLen); + + if(logBuffer != NULL) + { + glGetShaderInfoLog(shader, infoLen, NULL, logBuffer); + + DALI_ASSERT_ALWAYS( true && logBuffer); + + free(logBuffer); + logBuffer = NULL; + } + + glDeleteShader(shader); + shader = 0; + } + } + } + return shader; + } + + void PrepareBlitShader() + { + static const char glVertexShader[] = + "attribute vec2 vertexPosition;\n" + "attribute vec2 texCoords;\n" + "varying vec2 vTexCoords ;\n" + "void main()\n" + "{\n" + " gl_Position = vec4(vertexPosition, 0.0, 1.0);\n" + " vTexCoords = texCoords;\n" + "}\n"; + + static const char glFragmentShader[] = + "precision mediump float;\n" + "varying vec2 vTexCoords;\n" + "uniform sampler2D tex;\n" + "void main()\n" + "{\n" + " gl_FragColor = texture2D(tex, vTexCoords);\n" + "}\n"; + + mBlitProgram = CreateProgram(glVertexShader, glFragmentShader); + mBlitVertexLocation = glGetAttribLocation(mBlitProgram, "vertexPosition"); + mBlitTexCoord = glGetAttribLocation(mBlitProgram, "texCoords"); + } + + GLuint mBlitProgram{0u}; + GLuint mBlitVertexLocation{0u}; + GLuint mBlitTexCoord{0u}; + + /** + * Initializes FBO textures + */ + void InitializeOffscreenFramebuffers() + { + for(auto i = 0u; i < mCreateInfo.maxOffscreenBuffers; ++i) + { + mFramebufferTexture.emplace_back(); + mFramebufferTexture.back().textureId = CreateOffscreenTexture(mWidth, mHeight); + + // Populate Draw queue entries + mTextureDrawQueue.push_back(i); + + // Create framebuffers + CreateFramebuffer( i, mWidth, mHeight); + } + } + + /** + * Creates an offscreen texture for threaded renderer + */ + uint32_t CreateOffscreenTexture(uint32_t width, uint32_t height) + { + GLuint offscreenTexture{0u}; + GL(glGenTextures(1, &offscreenTexture)); + GL(glBindTexture(GL_TEXTURE_2D, offscreenTexture)); + GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); + GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + return offscreenTexture; + } + + void GlViewInit(const Dali::RenderCallbackInput& input) + { + if(mOnInitCallback) + { + GlViewPreInit(input); + CallbackBase::Execute(*mOnInitCallback, input); + } + } + + int GlViewRender(const Dali::RenderCallbackInput& input) + { + // Non-threaded solution invokes callback directly + int retval = 0; + if(!mCreateInfo.threadEnabled) + { + if(mOnRenderCallback) + { + retval = CallbackBase::ExecuteReturn(*mOnRenderCallback, input); + } + } + else + { + BlitTexture(); + } + return retval; + } + + void GlViewTerminate(const Dali::RenderCallbackInput& input) + { + // Non-threaded solution invokes callback directly + if(!mCreateInfo.threadEnabled) + { + if(mOnTerminateCallback) + { + CallbackBase::Execute(*mOnTerminateCallback, input); + } + } + } + + void BlitTexture() + { + // If no threaded mode, return + if(!mCreateInfo.threadEnabled) + { + return; + } + + // Read input + auto x = 0; + auto y = 0; + auto w = mWidth; + auto h = mHeight; + + // Deqeueue texture, there should be always something waiting to be drawn, if not, ignore + FrameBufferTexture fb; + auto textureBufferIndex = DequeueTextureReadBuffer(fb); + + // Do nothing if frame not ready + if(textureBufferIndex < 0) + { + if(mLastTextureBufferIndex >= 0) + { + textureBufferIndex = mLastTextureBufferIndex; + } + else + { + return; + } + } + else + { + // return last texture to the pull + if(mLastTextureBufferIndex >= 0) + { + // return it to the queue + EnqueueTextureDrawBuffer( mLastTextureBufferIndex ); + } + } + + GL(glViewport(x, y, w, h)); + if(!mBlitStateDone) + { + mBlitStateDone = true; + GL(glUseProgram(mBlitProgram)); + GL(glVertexAttribPointer(mBlitVertexLocation, 2, GL_FLOAT, GL_FALSE, 0, QUAD_VERTS)); + GL(glEnableVertexAttribArray(mBlitVertexLocation)); + GL(glVertexAttribPointer(mBlitTexCoord, 2, GL_FLOAT, GL_FALSE, 0, QUAD_UV)); + GL(glEnableVertexAttribArray(mBlitTexCoord)); + GL(glActiveTexture(GL_TEXTURE0)); + } + GL(glBindTexture(GL_TEXTURE_2D, mFramebufferTexture[textureBufferIndex].textureId)); + + GL(glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, QUAD_INDICES)) + + mLastTextureBufferIndex = textureBufferIndex; + } + + // List of offscreen framebuffers + std::vector mFramebufferTexture{}; + + /** + * Rendering pipeline uses 3 queues: + * Draw - the producer queue (NativeRenderer thread writes to it) + * Read - the consumer queue (RenderThread reads from it) + * Stage - Already written but not ready to be read (not signaled) + * + * // Rendering offscreen + * 1. PRODUCER pops framebuffer from Draw queue + * 2. PRODUCER renders into the framebuffer + * 3. PRODUCER pushes framebuffer into Stage queue + * + * // Rendering onscreen + * 1. CONSUMER checks Stage queue for signaled (ready) framebuffers + * 2. If framebuffers are ready, pushes them into the Read queue + * 3. If MAILBOX mode, CONSUMER discards all 'outdated' framebuffers and displays the most recent + * 4. If FIFO mode, CONSUMER displays all the images in order of being produced. + */ + std::deque mTextureDrawQueue; + std::deque mTextureStageQueue; + std::deque mTextureReadQueue; + + // Mutex guarding the queues reads/writes + std::recursive_mutex mTextureQueueMutex; + std::unique_ptr mThread; ///< Thread for parallel mode + bool mRunning{false}; ///< Thread running flag + EGLContext mEglContext{nullptr}; ///< EGL context associated with the thread + EGLContext mEglSharedContext{nullptr}; + + EGLDisplay mEglDisplay{nullptr}; ///< Current EGL display + std::atomic_bool mEglContextBound{false}; ///< Flag indicating whether EGL context is bound + EGLSurface mDrawSurface{EGL_NO_SURFACE}; ///< Current EGL draw surface + EGLSurface mReadSurface{EGL_NO_SURFACE}; ///< Current EGL read surface + + // Callbacks associated with GlView interface + std::unique_ptr mOnInitCallback{nullptr}; + std::unique_ptr mOnRenderCallback{nullptr}; + std::unique_ptr mOnTerminateCallback{nullptr}; + + int32_t mLastTextureBufferIndex{-1}; + bool mBlitStateDone{false}; + + std::mutex mRenderCallbackInputDataMutex{}; + Dali::RenderCallbackInput mRenderCallbackInputData{}; + + NativeRendererCreateInfo mCreateInfo{}; +}; + +DrawableViewNativeRenderer::DrawableViewNativeRenderer(const NativeRendererCreateInfo& createInfo) +: mImpl(new Impl(createInfo)) +{ + if(createInfo.threadEnabled) + { + mImpl->InitializeThread(); + } +} + +DrawableViewNativeRenderer::~DrawableViewNativeRenderer() = default; + +void DrawableViewNativeRenderer::RegisterGlCallbacks(Dali::CallbackBase* onInitCallback, Dali::CallbackBase* onRenderCallback, Dali::CallbackBase* onTerminateCallback) +{ + mImpl->mOnInitCallback.reset(onInitCallback); + mImpl->mOnRenderCallback.reset(onRenderCallback); + mImpl->mOnTerminateCallback.reset(onTerminateCallback); +} + +void DrawableViewNativeRenderer::InvokeGlInitCallback(const RenderCallbackInput& renderCallbackInput) +{ + mImpl->GlViewInit(renderCallbackInput); +} + +void DrawableViewNativeRenderer::InvokeGlRenderCallback(const RenderCallbackInput& renderCallbackInput) +{ + mImpl->GlViewRender(renderCallbackInput); +} + +void DrawableViewNativeRenderer::InvokeGlTerminateCallback(const RenderCallbackInput& renderCallbackInput) +{ + mImpl->GlViewTerminate(renderCallbackInput); +} + +void DrawableViewNativeRenderer::Resize(uint32_t width, uint32_t height) +{ + mImpl->Resize(width, height); +} + +void DrawableViewNativeRenderer::PushRenderCallbackInputData( const Dali::RenderCallbackInput& renderCallbackInput ) +{ + mImpl->PushRenderCallbackInputData(renderCallbackInput); +} + +void DrawableViewNativeRenderer::Terminate() +{ + mImpl->Terminate(); +} + +} // namespace Dali::Internal \ No newline at end of file diff --git a/dali-toolkit/internal/controls/gl-view/drawable-view-native-renderer.h b/dali-toolkit/internal/controls/gl-view/drawable-view-native-renderer.h new file mode 100644 index 0000000..005b9c1 --- /dev/null +++ b/dali-toolkit/internal/controls/gl-view/drawable-view-native-renderer.h @@ -0,0 +1,114 @@ +#ifndef DALI_PROJECT_DRAWABLE_VIEW_NATIVE_RENDERER_H +#define DALI_PROJECT_DRAWABLE_VIEW_NATIVE_RENDERER_H + +/* +* Copyright (c) 2021 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. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* + */ + +#include +#include +#include + + +namespace Dali::Internal +{ +/** + * The structure containing the initialization data for the + * DrawableViewNativeRenderer instance. + */ +struct NativeRendererCreateInfo +{ + /** + * Presentation mode is used only for threaded renderer + */ + enum class PresentationMode + { + FIFO, // First-in first-out + MAILBOX // Only most recent out + }; + + bool threadEnabled { false }; + uint32_t maxOffscreenBuffers { 3u }; + PresentationMode presentationMode {PresentationMode::FIFO}; +}; + +/** + * The DrawableViewNativeRenderer is responsible for delegating rendering + * either to the own thread (in parallel mode) or calling the GlView render callbacks + * directly. + * + * The parallel mode creates the thread that invokes GLView callbacks directly. + * The actual render callback will only pass the input data and blit the result. + * + * Parallel mode renders always to the offscreen framebuffer. + */ +class DrawableViewNativeRenderer +{ +public: + explicit DrawableViewNativeRenderer( const NativeRendererCreateInfo& createInfo ); + ~DrawableViewNativeRenderer(); + + /** + * Registers GlView callbacks + */ + void RegisterGlCallbacks( Dali::CallbackBase* onInitCallback,Dali::CallbackBase* onRenderCallback, Dali::CallbackBase* onTerminateCallback ); + + /** + * Dispatches the GlView init callback + * @param renderCallbackInput + */ + void InvokeGlInitCallback( const RenderCallbackInput& renderCallbackInput ); + + /** + * Dispatches the GlView render callback + * @param renderCallbackInput + */ + void InvokeGlRenderCallback( const RenderCallbackInput& renderCallbackInput ); + + /** + * Dispatches the GlView terminate callback + * @param[in] renderCallbackInput + */ + void InvokeGlTerminateCallback( const RenderCallbackInput& renderCallbackInput ); + + /** + * @brief Resizes the render surface + * + * @param[in] width Width of surface + * @param[in] height Height of surface + */ + void Resize( uint32_t width, uint32_t height ); + + /** + * @brief Pushes render callback input data into the native renderer thread + * + * @param[in] renderCallbackInput Valid RenderCallbackInput object + */ + void PushRenderCallbackInputData( const Dali::RenderCallbackInput& renderCallbackInput ); + + /** + * @brief Terminates thread in parallel mode + */ + void Terminate(); + +private: + + struct Impl; + std::unique_ptr mImpl; +}; +} + +#endif // DALI_PROJECT_DRAWABLE_VIEW_NATIVE_RENDERER_H diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 41d8c0d..d80bffe 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -119,6 +119,7 @@ SET( toolkit_src_files ${toolkit_src_dir}/controls/web-view/web-view-impl.cpp ${toolkit_src_dir}/controls/camera-view/camera-view-impl.cpp ${toolkit_src_dir}/controls/gl-view/drawable-view-impl.cpp + ${toolkit_src_dir}/controls/gl-view/drawable-view-native-renderer.cpp ${toolkit_src_dir}/controls/gl-view/gl-view-impl.cpp ${toolkit_src_dir}/controls/gl-view/gl-view-render-thread.cpp ${toolkit_src_dir}/accessibility-manager/accessibility-manager-impl.cpp diff --git a/dali-toolkit/internal/graphics/shaders/color-visual-shader.frag b/dali-toolkit/internal/graphics/shaders/color-visual-shader.frag index 7603982..36a4104 100644 --- a/dali-toolkit/internal/graphics/shaders/color-visual-shader.frag +++ b/dali-toolkit/internal/graphics/shaders/color-visual-shader.frag @@ -130,7 +130,7 @@ lowp vec4 convertBorderlineColor(lowp vec4 textureColor) // But if borderlineOpacity > 0.0 and borderlineColor.a == 0.0, we need to apply tCornerRadius. if(borderlineOpacity > 0.0 && borderlineColor.a * borderlineOpacity < 1.0) { - mediump float tCornerRadius = -gCenterPosition; + mediump float tCornerRadius = -gCenterPosition + gPotentialRange; mediump float MaxTexturelinePotential = tCornerRadius + gPotentialRange; mediump float MinTexturelinePotential = tCornerRadius - gPotentialRange; if(potential > MaxTexturelinePotential) diff --git a/dali-toolkit/internal/graphics/shaders/gradient-visual-shader.frag b/dali-toolkit/internal/graphics/shaders/gradient-visual-shader.frag index febc776..1812cd3 100644 --- a/dali-toolkit/internal/graphics/shaders/gradient-visual-shader.frag +++ b/dali-toolkit/internal/graphics/shaders/gradient-visual-shader.frag @@ -126,7 +126,7 @@ lowp vec4 convertBorderlineColor(lowp vec4 textureColor) // But if borderlineOpacity > 0.0 and borderlineColor.a == 0.0, we need to apply tCornerRadius. if(borderlineOpacity > 0.0 && borderlineColor.a * borderlineOpacity < 1.0) { - mediump float tCornerRadius = -gCenterPosition; + mediump float tCornerRadius = -gCenterPosition + gPotentialRange; mediump float MaxTexturelinePotential = tCornerRadius + gPotentialRange; mediump float MinTexturelinePotential = tCornerRadius - gPotentialRange; if(potential > MaxTexturelinePotential) diff --git a/dali-toolkit/internal/graphics/shaders/image-visual-shader.frag b/dali-toolkit/internal/graphics/shaders/image-visual-shader.frag index c292b0e..a0f39d9 100644 --- a/dali-toolkit/internal/graphics/shaders/image-visual-shader.frag +++ b/dali-toolkit/internal/graphics/shaders/image-visual-shader.frag @@ -154,7 +154,7 @@ lowp vec4 convertBorderlineColor(lowp vec4 textureColor) // But if borderlineOpacity > 0.0 and borderlineColor.a == 0.0, we need to apply tCornerRadius. if(borderlineOpacity > 0.0 && borderlineColor.a * borderlineOpacity < 1.0) { - mediump float tCornerRadius = -gCenterPosition; + mediump float tCornerRadius = -gCenterPosition + gPotentialRange; mediump float MaxTexturelinePotential = tCornerRadius + gPotentialRange; mediump float MinTexturelinePotential = tCornerRadius - gPotentialRange; if(potential > MaxTexturelinePotential) diff --git a/dali-toolkit/internal/helpers/round-robin-container-view.h b/dali-toolkit/internal/helpers/round-robin-container-view.h index b5f97df..5a5f07c 100644 --- a/dali-toolkit/internal/helpers/round-robin-container-view.h +++ b/dali-toolkit/internal/helpers/round-robin-container-view.h @@ -3,7 +3,7 @@ #define DALI_TOOLKIT_INTERNAL_ROUND_ROBIN_CONTAINER_VIEW_H /* - * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * Copyright (c) 2022 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. @@ -56,6 +56,14 @@ public: } /** + * @brief Clear all elements. + */ + void Clear() + { + mElements.clear(); + } + + /** * @brief Reset the position of the iterator returned by GetNext() to the first element. */ void Reset() @@ -86,6 +94,15 @@ public: return mElements.cend(); } + /** + * @brief Returns the element count. + * @return The element count + */ + size_t GetElementCount() const + { + return mElements.size(); + } + // default members ~RoundRobinContainerView() = default; diff --git a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp index 382d52b..db00ff5 100644 --- a/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp +++ b/dali-toolkit/internal/visuals/animated-image/animated-image-visual.cpp @@ -178,7 +178,7 @@ void AnimatedImageVisual::CreateImageCache() if(mAnimatedImageLoading) { - mImageCache = new RollingAnimatedImageCache(textureManager, mAnimatedImageLoading, mMaskingData, *this, mCacheSize, mBatchSize, IsSynchronousLoadingRequired(), mFactoryCache.GetPreMultiplyOnLoad()); + mImageCache = new RollingAnimatedImageCache(textureManager, mAnimatedImageLoading, mMaskingData, *this, mCacheSize, mBatchSize, mWrapModeU, mWrapModeV, IsSynchronousLoadingRequired(), mFactoryCache.GetPreMultiplyOnLoad()); } else if(mImageUrls) { diff --git a/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.cpp b/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.cpp index 74ec088..b8a6961 100644 --- a/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.cpp +++ b/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.cpp @@ -66,6 +66,8 @@ RollingAnimatedImageCache::RollingAnimatedImageCache(TextureManager& ImageCache::FrameReadyObserver& observer, uint16_t cacheSize, uint16_t batchSize, + const Dali::WrapMode::Type& wrapModeU, + const Dali::WrapMode::Type& wrapModeV, bool isSynchronousLoading, bool preMultiplyOnLoad) : ImageCache(textureManager, maskingData, observer, batchSize, 0u), @@ -75,6 +77,8 @@ RollingAnimatedImageCache::RollingAnimatedImageCache(TextureManager& mFrameIndex(FIRST_FRAME_INDEX), mCacheSize(cacheSize), mQueue(cacheSize), + mWrapModeU(wrapModeU), + mWrapModeV(wrapModeV), mIsSynchronousLoading(isSynchronousLoading), mPreMultiplyOnLoad(preMultiplyOnLoad) { @@ -204,8 +208,8 @@ TextureSet RollingAnimatedImageCache::RequestFrameLoading(uint32_t frameIndex, b loadTextureId, mMaskingData, SamplingMode::BOX_THEN_LINEAR, - Dali::WrapMode::Type::DEFAULT, - Dali::WrapMode::Type::DEFAULT, + mWrapModeU, + mWrapModeV, synchronousLoading, this, preMultiplyOnLoading); @@ -328,7 +332,15 @@ void RollingAnimatedImageCache::LoadComplete(bool loadSuccess, TextureInformatio DALI_LOG_INFO(gAnimImgLogFilter, Debug::Concise, "AnimatedImageVisual::LoadComplete(textureId:%d) start\n", textureInformation.textureId); LOG_CACHE; - MakeFrameReady(loadSuccess, mTextureManager.GetTextureSet(textureInformation.textureId), textureInformation.interval); + TextureSet textureSet = mTextureManager.GetTextureSet(textureInformation.textureId); + if(textureSet) + { + Sampler sampler = Sampler::New(); + sampler.SetWrapMode(mWrapModeU, mWrapModeV); + textureSet.SetSampler(0u, sampler); + } + + MakeFrameReady(loadSuccess, textureSet, textureInformation.interval); if(loadSuccess) { diff --git a/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.h b/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.h index 59729de..8fed2d7 100644 --- a/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.h +++ b/dali-toolkit/internal/visuals/animated-image/rolling-animated-image-cache.h @@ -47,7 +47,10 @@ public: * @param[in] observer FrameReady observer * @param[in] cacheSize The size of the cache * @param[in] batchSize The size of a batch to load + * @param[in] wrapModeU Horizontal Wrap mode + * @param[in] wrapModeV Vertical Wrap mode * @param[in] isSynchronousLoading The flag to define whether to load first frame synchronously + * @param[in] preMultiplyOnLoad The flag if image's color should be multiplied by it's alpha * * This will start loading textures immediately, according to the * batch and cache sizes. @@ -58,6 +61,8 @@ public: ImageCache::FrameReadyObserver& observer, uint16_t cacheSize, uint16_t batchSize, + const Dali::WrapMode::Type& wrapModeU, + const Dali::WrapMode::Type& wrapModeV, bool isSynchronousLoading, bool preMultiplyOnLoad); @@ -181,6 +186,8 @@ private: std::vector mIntervals; std::vector mLoadWaitingQueue; CircularQueue mQueue; + Dali::WrapMode::Type mWrapModeU : 3; + Dali::WrapMode::Type mWrapModeV : 3; bool mIsSynchronousLoading; bool mPreMultiplyOnLoad; }; diff --git a/dali-toolkit/internal/visuals/svg/svg-rasterize-thread.cpp b/dali-toolkit/internal/visuals/svg/svg-rasterize-thread.cpp index 768f743..c1ebd67 100644 --- a/dali-toolkit/internal/visuals/svg/svg-rasterize-thread.cpp +++ b/dali-toolkit/internal/visuals/svg/svg-rasterize-thread.cpp @@ -19,6 +19,7 @@ #include "svg-rasterize-thread.h" // EXTERNAL INCLUDES +#include #include #include #include @@ -33,6 +34,26 @@ namespace Toolkit { namespace Internal { +namespace +{ +constexpr auto DEFAULT_NUMBER_OF_SVG_RASTERIZE_THREADS = size_t{4u}; +constexpr auto NUMBER_OF_SVG_RASTERIZE_THREADS_ENV = "DALI_SVG_RASTERIZE_THREADS"; + +size_t GetNumberOfThreads(const char* environmentVariable, size_t defaultValue) +{ + auto numberString = EnvironmentVariable::GetEnvironmentVariable(environmentVariable); + auto numberOfThreads = numberString ? std::strtoul(numberString, nullptr, 10) : 0; + constexpr auto MAX_NUMBER_OF_THREADS = 10u; + DALI_ASSERT_DEBUG(numberOfThreads < MAX_NUMBER_OF_THREADS); + return (numberOfThreads > 0 && numberOfThreads < MAX_NUMBER_OF_THREADS) ? numberOfThreads : defaultValue; +} + +#if defined(DEBUG_ENABLED) +Debug::Filter* gVectorImageLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_VECTOR_IMAGE"); +#endif + +} // unnamed namespace + SvgTask::SvgTask(SvgVisual* svgVisual, VectorImageRenderer vectorRenderer) : mSvgVisual(svgVisual), mVectorRenderer(vectorRenderer), @@ -124,6 +145,8 @@ void SvgRasterizingTask::Process() return; } + DALI_LOG_INFO(gVectorImageLogFilter, Debug::Verbose, "Rasterize: (%d x %d) [%p]\n", mWidth, mHeight, this); + Devel::PixelBuffer pixelBuffer = mVectorRenderer.Rasterize(mWidth, mHeight); if(!pixelBuffer) { @@ -135,50 +158,111 @@ void SvgRasterizingTask::Process() mHasSucceeded = true; } +bool SvgRasterizingTask::IsReady() +{ + return mVectorRenderer.IsLoaded(); +} + PixelData SvgRasterizingTask::GetPixelData() const { return mPixelData; } -SvgRasterizeThread::SvgRasterizeThread() -: mTrigger(new EventThreadCallback(MakeCallback(this, &SvgRasterizeThread::ApplyRasterizedSVGToSampler))), +SvgRasterizeThread::SvgRasterizeThread(SvgRasterizeManager& svgRasterizeManager) +: mConditionalWait(), mLogFactory(Dali::Adaptor::Get().GetLogFactory()), - mIsThreadWaiting(false), - mProcessorRegistered(false) + mSvgRasterizeManager(svgRasterizeManager), + mDestroyThread(false), + mIsThreadStarted(false), + mIsThreadIdle(true) { } SvgRasterizeThread::~SvgRasterizeThread() { - if(mProcessorRegistered) + // Stop the thread { - Adaptor::Get().UnregisterProcessor(*this); + ConditionalWait::ScopedLock lock(mConditionalWait); + mDestroyThread = true; + mConditionalWait.Notify(lock); } + + Join(); } -void SvgRasterizeThread::TerminateThread(SvgRasterizeThread*& thread) +bool SvgRasterizeThread::RequestRasterize() { - if(thread) + if(!mIsThreadStarted) { - // add an empty task would stop the thread from conditional wait. - thread->AddTask(SvgTaskPtr()); - // stop the thread - thread->Join(); - // delete the thread - delete thread; - thread = NULL; + Start(); + mIsThreadStarted = true; } + + { + // Lock while adding task to the queue + ConditionalWait::ScopedLock lock(mConditionalWait); + + if(mIsThreadIdle) + { + mIsThreadIdle = false; + + // wake up the thread + mConditionalWait.Notify(lock); + return true; + } + } + + return false; } -void SvgRasterizeThread::AddTask(SvgTaskPtr task) +void SvgRasterizeThread::Run() { - bool wasEmpty = false; + SetThreadName("SvgRasterizeThread"); + mLogFactory.InstallLogFunction(); + while(!mDestroyThread) + { + SvgTaskPtr task = mSvgRasterizeManager.NextTaskToProcess(); + if(!task) + { + ConditionalWait::ScopedLock lock(mConditionalWait); + mIsThreadIdle = true; + mConditionalWait.Wait(lock); + } + else + { + task->Process(); + + mSvgRasterizeManager.AddCompletedTask(task); + } + } +} + +SvgRasterizeManager::SvgRasterizeManager() +: mRasterizers(GetNumberOfThreads(NUMBER_OF_SVG_RASTERIZE_THREADS_ENV, DEFAULT_NUMBER_OF_SVG_RASTERIZE_THREADS), [&]() { return RasterizeHelper(*this); }), + mTrigger(new EventThreadCallback(MakeCallback(this, &SvgRasterizeManager::ApplyRasterizedSVGToSampler))), + mProcessorRegistered(false) +{ +} + +SvgRasterizeManager::~SvgRasterizeManager() +{ + if(mProcessorRegistered) + { + Adaptor::Get().UnregisterProcessor(*this); + } + + mRasterizers.Clear(); +} + +void SvgRasterizeManager::AddTask(SvgTaskPtr task) +{ { // Lock while adding task to the queue - ConditionalWait::ScopedLock lock(mConditionalWait); - wasEmpty = mRasterizeTasks.empty(); - if(!wasEmpty && task) + Mutex::ScopedLock lock(mMutex); + + // There are other tasks waiting for the rasterization + if(!mRasterizeTasks.empty()) { // Remove the tasks with the same renderer. // Older task which waiting to rasterize and apply the svg to the same renderer is expired. @@ -197,23 +281,34 @@ void SvgRasterizeThread::AddTask(SvgTaskPtr task) } } } + mRasterizeTasks.push_back(task); + } + + size_t count = mRasterizers.GetElementCount(); + size_t index = 0; + while(index++ < count) + { + auto rasterizerHelperIt = mRasterizers.GetNext(); + DALI_ASSERT_ALWAYS(rasterizerHelperIt != mRasterizers.End()); - if(!mProcessorRegistered) + if(rasterizerHelperIt->RequestRasterize()) { - Adaptor::Get().RegisterProcessor(*this); - mProcessorRegistered = true; + break; } + // If all threads are busy, then it's ok just to push the task because they will try to get the next job. } - if(wasEmpty) + if(!mProcessorRegistered) { - // wake up the image loading thread - mConditionalWait.Notify(); + Adaptor::Get().RegisterProcessor(*this); + mProcessorRegistered = true; } + + return; } -SvgTaskPtr SvgRasterizeThread::NextCompletedTask() +SvgTaskPtr SvgRasterizeManager::NextCompletedTask() { // Lock while popping task out from the queue Mutex::ScopedLock lock(mMutex); @@ -230,21 +325,23 @@ SvgTaskPtr SvgRasterizeThread::NextCompletedTask() return nextTask; } -void SvgRasterizeThread::RemoveTask(SvgVisual* visual) +void SvgRasterizeManager::RemoveTask(SvgVisual* visual) { - // Lock while remove task from the queue - ConditionalWait::ScopedLock lock(mConditionalWait); - if(!mRasterizeTasks.empty()) { - for(std::vector::iterator it = mRasterizeTasks.begin(); it != mRasterizeTasks.end();) + // Lock while remove task from the queue + Mutex::ScopedLock lock(mMutex); + if(!mRasterizeTasks.empty()) { - if((*it) && (*it)->GetSvgVisual() == visual) - { - it = mRasterizeTasks.erase(it); - } - else + for(std::vector::iterator it = mRasterizeTasks.begin(); it != mRasterizeTasks.end();) { - it++; + if((*it) && (*it)->GetSvgVisual() == visual) + { + it = mRasterizeTasks.erase(it); + } + else + { + it++; + } } } } @@ -252,28 +349,28 @@ void SvgRasterizeThread::RemoveTask(SvgVisual* visual) UnregisterProcessor(); } -SvgTaskPtr SvgRasterizeThread::NextTaskToProcess() +SvgTaskPtr SvgRasterizeManager::NextTaskToProcess() { // Lock while popping task out from the queue - ConditionalWait::ScopedLock lock(mConditionalWait); + Mutex::ScopedLock lock(mMutex); - // conditional wait - while(mRasterizeTasks.empty()) + // pop out the next task from the queue + SvgTaskPtr nextTask = nullptr; + + for(auto iter = mRasterizeTasks.begin(), endIter = mRasterizeTasks.end(); iter != endIter; ++iter) { - mIsThreadWaiting = true; - mConditionalWait.Wait(lock); + if((*iter)->IsReady()) + { + nextTask = *iter; + mRasterizeTasks.erase(iter); + break; + } } - mIsThreadWaiting = false; - - // pop out the next task from the queue - std::vector::iterator next = mRasterizeTasks.begin(); - SvgTaskPtr nextTask = *next; - mRasterizeTasks.erase(next); return nextTask; } -void SvgRasterizeThread::AddCompletedTask(SvgTaskPtr task) +void SvgRasterizeManager::AddCompletedTask(SvgTaskPtr task) { // Lock while adding task to the queue Mutex::ScopedLock lock(mMutex); @@ -283,35 +380,27 @@ void SvgRasterizeThread::AddCompletedTask(SvgTaskPtr task) mTrigger->Trigger(); } -void SvgRasterizeThread::Run() -{ - SetThreadName("SVGThread"); - mLogFactory.InstallLogFunction(); - - while(SvgTaskPtr task = NextTaskToProcess()) - { - task->Process(); - AddCompletedTask(task); - } -} - -void SvgRasterizeThread::ApplyRasterizedSVGToSampler() +void SvgRasterizeManager::ApplyRasterizedSVGToSampler() { while(SvgTaskPtr task = NextCompletedTask()) { + DALI_LOG_INFO(gVectorImageLogFilter, Debug::Verbose, "task = %p\n", task.Get()); + task->GetSvgVisual()->ApplyRasterizedImage(task->GetPixelData(), task->HasSucceeded()); } UnregisterProcessor(); } -void SvgRasterizeThread::Process(bool postProcessor) +void SvgRasterizeManager::Process(bool postProcessor) { ApplyRasterizedSVGToSampler(); } -void SvgRasterizeThread::UnregisterProcessor() +void SvgRasterizeManager::UnregisterProcessor() { + Mutex::ScopedLock lock(mMutex); + if(mProcessorRegistered) { if(mRasterizeTasks.empty() && mCompletedTasks.empty()) @@ -322,6 +411,26 @@ void SvgRasterizeThread::UnregisterProcessor() } } +SvgRasterizeManager::RasterizeHelper::RasterizeHelper(SvgRasterizeManager& svgRasterizeManager) +: RasterizeHelper(std::unique_ptr(new SvgRasterizeThread(svgRasterizeManager)), svgRasterizeManager) +{ +} + +SvgRasterizeManager::RasterizeHelper::RasterizeHelper(RasterizeHelper&& rhs) +: RasterizeHelper(std::move(rhs.mRasterizer), rhs.mSvgRasterizeManager) +{ +} + +SvgRasterizeManager::RasterizeHelper::RasterizeHelper(std::unique_ptr rasterizer, SvgRasterizeManager& svgRasterizeManager) +: mRasterizer(std::move(rasterizer)), + mSvgRasterizeManager(svgRasterizeManager) +{ +} + +bool SvgRasterizeManager::RasterizeHelper::RequestRasterize() +{ + return mRasterizer->RequestRasterize(); +} } // namespace Internal } // namespace Toolkit diff --git a/dali-toolkit/internal/visuals/svg/svg-rasterize-thread.h b/dali-toolkit/internal/visuals/svg/svg-rasterize-thread.h index 13d284b..c9c305b 100644 --- a/dali-toolkit/internal/visuals/svg/svg-rasterize-thread.h +++ b/dali-toolkit/internal/visuals/svg/svg-rasterize-thread.h @@ -32,6 +32,7 @@ #include // INTERNAL INCLUDES +#include #include namespace Dali @@ -44,6 +45,7 @@ class SvgVisual; typedef IntrusivePtr SvgVisualPtr; class SvgTask; typedef IntrusivePtr SvgTaskPtr; +class SvgRasterizeManager; /** * The svg rasterizing tasks to be processed in the worker thread. @@ -75,6 +77,15 @@ public: virtual void Process() = 0; /** + * Whether the task is ready to process. + * @return True if the task is ready to process. + */ + virtual bool IsReady() + { + return true; + } + + /** * Whether the task has succeeded. * @return True if the task has succeeded. */ @@ -161,6 +172,12 @@ public: void Process() override; /** + * Whether the task is ready to process. + * @return True if the task is ready to process. + */ + bool IsReady() override; + + /** * Get the rasterization result. * @return The pixel data with the rasterized pixels. */ @@ -182,7 +199,52 @@ private: /** * The worker thread for SVG rasterization. */ -class SvgRasterizeThread : public Thread, Integration::Processor +class SvgRasterizeThread : public Thread +{ +public: + /** + * Constructor. + */ + SvgRasterizeThread(SvgRasterizeManager& svgRasterizeManager); + + /** + * Destructor. + */ + ~SvgRasterizeThread() override; + + /** + * @brief Request the thread to rasterizes the task. + * @return True if the request succeeds, otherwise false. + */ + bool RequestRasterize(); + +protected: + /** + * The entry function of the worker thread. + * It rasterizes the image. + */ + void Run() override; + +private: + // Undefined + SvgRasterizeThread(const SvgRasterizeThread& thread) = delete; + + // Undefined + SvgRasterizeThread& operator=(const SvgRasterizeThread& thread) = delete; + +private: + ConditionalWait mConditionalWait; + const Dali::LogFactoryInterface& mLogFactory; + SvgRasterizeManager& mSvgRasterizeManager; + bool mDestroyThread; + bool mIsThreadStarted; + bool mIsThreadIdle; +}; + +/** + * The manager for SVG rasterization. + */ +class SvgRasterizeManager : Integration::Processor { public: /** @@ -190,12 +252,12 @@ public: * * @param[in] trigger The trigger to wake up the main thread. */ - SvgRasterizeThread(); + SvgRasterizeManager(); /** - * Terminate the svg rasterize thread, join and delete. + * Destructor. */ - static void TerminateThread(SvgRasterizeThread*& thread); + ~SvgRasterizeManager() override; /** * Add a rasterization task into the waiting queue, called by main thread. @@ -225,7 +287,6 @@ public: */ void Process(bool postProcessor) override; -private: /** * Pop the next task out from the queue. * @@ -240,6 +301,7 @@ private: */ void AddCompletedTask(SvgTaskPtr task); +private: /** * Applies the rasterized image to material */ @@ -251,34 +313,59 @@ private: */ void UnregisterProcessor(); -protected: - /** - * Destructor. - */ - ~SvgRasterizeThread() override; - +private: /** - * The entry function of the worker thread. - * It fetches task from the Queue, rasterizes the image and apply to the renderer. + * @brief Helper class to keep the relation between SvgRasterizeThread and corresponding container */ - void Run() override; + class RasterizeHelper + { + public: + /** + * @brief Create an RasterizeHelper. + * + * @param[in] svgRasterizeManager Reference to the SvgRasterizeManager + */ + RasterizeHelper(SvgRasterizeManager& svgRasterizeManager); + + /** + * @brief Request the thread to rasterizes the task. + * @return True if the request succeeds, otherwise false. + */ + bool RequestRasterize(); + + public: + RasterizeHelper(const RasterizeHelper&) = delete; + RasterizeHelper& operator=(const RasterizeHelper&) = delete; + + RasterizeHelper(RasterizeHelper&& rhs); + RasterizeHelper& operator=(RasterizeHelper&& rhs) = delete; + + private: + /** + * @brief Main constructor that used by all other constructors + */ + RasterizeHelper(std::unique_ptr rasterizer, SvgRasterizeManager& svgRasterizeManager); + + private: + std::unique_ptr mRasterizer; + SvgRasterizeManager& mSvgRasterizeManager; + }; private: // Undefined - SvgRasterizeThread(const SvgRasterizeThread& thread); + SvgRasterizeManager(const SvgRasterizeManager& thread); // Undefined - SvgRasterizeThread& operator=(const SvgRasterizeThread& thread); + SvgRasterizeManager& operator=(const SvgRasterizeManager& thread); private: std::vector mRasterizeTasks; //The queue of the tasks waiting to rasterize the SVG image std::vector mCompletedTasks; //The queue of the tasks with the SVG rasterization completed - ConditionalWait mConditionalWait; + RoundRobinContainerView mRasterizers; + Dali::Mutex mMutex; std::unique_ptr mTrigger; - const Dali::LogFactoryInterface& mLogFactory; - bool mIsThreadWaiting; bool mProcessorRegistered; }; diff --git a/dali-toolkit/internal/visuals/svg/svg-visual.cpp b/dali-toolkit/internal/visuals/svg/svg-visual.cpp index 9d0a2cc..a0ced87 100644 --- a/dali-toolkit/internal/visuals/svg/svg-visual.cpp +++ b/dali-toolkit/internal/visuals/svg/svg-visual.cpp @@ -100,7 +100,7 @@ void SvgVisual::OnInitialize() } else { - mFactoryCache.GetSVGRasterizationThread()->AddTask(newTask); + mFactoryCache.GetSVGRasterizationManager()->AddTask(newTask); } } @@ -191,7 +191,7 @@ void SvgVisual::DoSetOnScene(Actor& actor) void SvgVisual::DoSetOffScene(Actor& actor) { - mFactoryCache.GetSVGRasterizationThread()->RemoveTask(this); + mFactoryCache.GetSVGRasterizationManager()->RemoveTask(this); actor.RemoveRenderer(mImpl->mRenderer); mPlacementActor.Reset(); @@ -266,7 +266,7 @@ void SvgVisual::AddRasterizationTask(const Vector2& size) } else { - mFactoryCache.GetSVGRasterizationThread()->AddTask(newTask); + mFactoryCache.GetSVGRasterizationManager()->AddTask(newTask); } } } diff --git a/dali-toolkit/internal/visuals/visual-factory-cache.cpp b/dali-toolkit/internal/visuals/visual-factory-cache.cpp index 2f5d123..6b4bf10 100644 --- a/dali-toolkit/internal/visuals/visual-factory-cache.cpp +++ b/dali-toolkit/internal/visuals/visual-factory-cache.cpp @@ -43,8 +43,8 @@ const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); } VisualFactoryCache::VisualFactoryCache(bool preMultiplyOnLoad) -: mSvgRasterizeThread(NULL), - mVectorAnimationManager(), +: mSvgRasterizeManager(nullptr), + mVectorAnimationManager(nullptr), mPreMultiplyOnLoad(preMultiplyOnLoad), mBrokenImageInfoContainer(), mDefaultBrokenImageUrl(""), @@ -54,7 +54,6 @@ VisualFactoryCache::VisualFactoryCache(bool preMultiplyOnLoad) VisualFactoryCache::~VisualFactoryCache() { - SvgRasterizeThread::TerminateThread(mSvgRasterizeThread); } Geometry VisualFactoryCache::GetGeometry(GeometryType type) @@ -131,14 +130,13 @@ NPatchLoader& VisualFactoryCache::GetNPatchLoader() return mNPatchLoader; } -SvgRasterizeThread* VisualFactoryCache::GetSVGRasterizationThread() +SvgRasterizeManager* VisualFactoryCache::GetSVGRasterizationManager() { - if(!mSvgRasterizeThread) + if(!mSvgRasterizeManager) { - mSvgRasterizeThread = new SvgRasterizeThread(); - mSvgRasterizeThread->Start(); + mSvgRasterizeManager = std::unique_ptr(new SvgRasterizeManager()); } - return mSvgRasterizeThread; + return mSvgRasterizeManager.get(); } VectorAnimationManager& VisualFactoryCache::GetVectorAnimationManager() diff --git a/dali-toolkit/internal/visuals/visual-factory-cache.h b/dali-toolkit/internal/visuals/visual-factory-cache.h index 52dbf61..6d9f28c 100644 --- a/dali-toolkit/internal/visuals/visual-factory-cache.h +++ b/dali-toolkit/internal/visuals/visual-factory-cache.h @@ -239,10 +239,10 @@ public: NPatchLoader& GetNPatchLoader(); /** - * Get the SVG rasterization thread. - * @return A raw pointer pointing to the SVG rasterization thread. + * Get the SVG rasterization manager. + * @return A raw pointer pointing to the SVG rasterization manager. */ - SvgRasterizeThread* GetSVGRasterizationThread(); + SvgRasterizeManager* GetSVGRasterizationManager(); /** * Get the vector animation manager. @@ -342,7 +342,7 @@ private: TextureManager mTextureManager; NPatchLoader mNPatchLoader; - SvgRasterizeThread* mSvgRasterizeThread; + std::unique_ptr mSvgRasterizeManager; std::unique_ptr mVectorAnimationManager; bool mPreMultiplyOnLoad; std::vector mBrokenImageInfoContainer; diff --git a/dali-toolkit/public-api/controls/gl-view/gl-view.cpp b/dali-toolkit/public-api/controls/gl-view/gl-view.cpp index 91939c0..77e75e8 100644 --- a/dali-toolkit/public-api/controls/gl-view/gl-view.cpp +++ b/dali-toolkit/public-api/controls/gl-view/gl-view.cpp @@ -49,8 +49,9 @@ GlView GlView::New(BackendMode backendMode, ColorFormat colorFormat) switch(backendMode) { case BackendMode::DIRECT_RENDERING: + case BackendMode::DIRECT_RENDERING_THREADED: { - return Internal::DrawableView::New(); + return Internal::DrawableView::New(backendMode); } case BackendMode::EGL_IMAGE_OFFSCREEN_RENDERING: { diff --git a/dali-toolkit/public-api/controls/gl-view/gl-view.h b/dali-toolkit/public-api/controls/gl-view/gl-view.h index 099b00f..a3b18c6 100644 --- a/dali-toolkit/public-api/controls/gl-view/gl-view.h +++ b/dali-toolkit/public-api/controls/gl-view/gl-view.h @@ -54,20 +54,35 @@ public: * pipeline. When Renderer is about to be drawn, the callback * will be executed and the custom code "injected" into the pipeline. * This allows rendering directly to the surface rather than offscreen. + * + * * @SINCE_2_1.18 */ DIRECT_RENDERING = 0, /** + * DIRECT_RENDERING_THREADED mode executes GL code on separate thread + * and then blits the result within DALi graphics commands stream. + * The mode is logically compatible with the EGL_IMAGE_OFFSCREEN_RENDERING. + * + * @SINCE_2_1.30 + */ + DIRECT_RENDERING_THREADED, + + /** * EGL_IMAGE_OFFSCREEN_RENDERING mode executes GL code in own thread * and renders to the offscreen NativeImage (EGL) buffer. This backend * will render in parallel but has higher memory footprint and may suffer * performance issues due to using EGL image. + * + * @SINCE_2_1.18 */ EGL_IMAGE_OFFSCREEN_RENDERING, /** * The default mode is set to EGL_IMAGE_OFFSCREEN_RENDERING for backwards * compatibility. + * + * @SINCE_2_1.18 */ DEFAULT = EGL_IMAGE_OFFSCREEN_RENDERING }; diff --git a/packaging/dali-toolkit.spec b/packaging/dali-toolkit.spec index 1818b43..e9b88b9 100644 --- a/packaging/dali-toolkit.spec +++ b/packaging/dali-toolkit.spec @@ -14,6 +14,10 @@ BuildRequires: pkgconfig BuildRequires: pkgconfig(dlog) BuildRequires: pkgconfig(dali2-core) BuildRequires: pkgconfig(dali2-adaptor) +BuildRequires: pkgconfig(gles20) +BuildRequires: pkgconfig(glesv2) +BuildRequires: pkgconfig(egl) + BuildRequires: gettext BuildRequires: pkgconfig(libtzplatform-config)