--- /dev/null
+#ifndef __DALI_INTERNAL_COMBINED_UPDATE_RENDER_CONTROLLER_DEBUG_H__
+#define __DALI_INTERNAL_COMBINED_UPDATE_RENDER_CONTROLLER_DEBUG_H__
+
+/*
+ * Copyright (c) 2015 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.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/integration-api/debug.h>
+
+namespace Dali
+{
+
+namespace Internal
+{
+
+namespace Adaptor
+{
+
+namespace
+{
+// Uncomment next line for FULL logging of the ThreadSynchronization class in release mode
+//#define RELEASE_BUILD_LOGGING
+
+#ifdef DEBUG_ENABLED
+
+#define ENABLE_LOG_IN_COLOR
+#define ENABLE_COUNTER_LOGGING
+#define ENABLE_UPDATE_RENDER_THREAD_LOGGING
+#define ENABLE_EVENT_LOGGING
+
+#define DEBUG_LEVEL_COUNTER Debug::Verbose
+#define DEBUG_LEVEL_UPDATE_RENDER Debug::General
+#define DEBUG_LEVEL_EVENT Debug::Concise
+
+Debug::Filter* gLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_THREAD_SYNC" );
+
+#define LOG_THREAD_SYNC(level, color, format, args...) \
+ DALI_LOG_INFO( gLogFilter, level, "%s" format "%s\n", color, ## args, COLOR_CLEAR )
+
+#define LOG_THREAD_SYNC_TRACE(color) \
+ Dali::Integration::Log::TraceObj debugTraceObj( gLogFilter, "%s%s%s", color, __FUNCTION__, COLOR_CLEAR ); \
+ if( ! gLogFilter->IsTraceEnabled() ) { LOG_THREAD_SYNC( Debug::Concise, color, "%s", __FUNCTION__ ); }
+
+#define LOG_THREAD_SYNC_TRACE_FMT(color, format, args...) \
+ Dali::Integration::Log::TraceObj debugTraceObj( gLogFilter, "%s%s: " format "%s", color, __FUNCTION__, ## args, COLOR_CLEAR ); \
+ if( ! gLogFilter->IsTraceEnabled() ) { LOG_THREAD_SYNC( Debug::Concise, color, "%s: " format, __FUNCTION__, ## args ); }
+
+#elif defined( RELEASE_BUILD_LOGGING )
+
+#define ENABLE_LOG_IN_COLOR
+#define ENABLE_COUNTER_LOGGING
+#define ENABLE_UPDATE_RENDER_THREAD_LOGGING
+#define ENABLE_EVENT_LOGGING
+
+#define DEBUG_LEVEL_COUNTER 0
+#define DEBUG_LEVEL_UPDATE_RENDER 0
+#define DEBUG_LEVEL_EVENT 0
+
+#define LOG_THREAD_SYNC(level, color, format, args...) \
+ Dali::Integration::Log::LogMessage( Dali::Integration::Log::DebugInfo, "%s" format "%s\n", color, ## args, COLOR_CLEAR )
+
+#define LOG_THREAD_SYNC_TRACE(color) \
+ Dali::Integration::Log::LogMessage( Dali::Integration::Log::DebugInfo, "%s%s%s\n", color, __FUNCTION__, COLOR_CLEAR )
+
+#define LOG_THREAD_SYNC_TRACE_FMT(color, format, args...) \
+ Dali::Integration::Log::LogMessage( Dali::Integration::Log::DebugInfo, "%s%s: " format "%s\n", color, __FUNCTION__, ## args, COLOR_CLEAR )
+
+#else
+
+#define LOG_THREAD_SYNC(level, color, format, args...)
+#define LOG_THREAD_SYNC_TRACE(color)
+#define LOG_THREAD_SYNC_TRACE_FMT(color, format, args...)
+
+#endif // DEBUG_ENABLED
+
+#ifdef ENABLE_LOG_IN_COLOR
+#define COLOR_YELLOW "\033[33m"
+#define COLOR_LIGHT_RED "\033[91m"
+#define COLOR_LIGHT_YELLOW "\033[93m"
+#define COLOR_WHITE "\033[97m"
+#define COLOR_CLEAR "\033[0m"
+#else
+#define COLOR_YELLOW
+#define COLOR_LIGHT_RED
+#define COLOR_LIGHT_YELLOW
+#define COLOR_WHITE
+#define COLOR_CLEAR
+#endif
+
+#ifdef ENABLE_COUNTER_LOGGING
+#define LOG_COUNTER_EVENT(format, args...) LOG_THREAD_SYNC(DEBUG_LEVEL_COUNTER, COLOR_LIGHT_RED, "%s: " format, __FUNCTION__, ## args)
+#define LOG_COUNTER_UPDATE_RENDER(format, args...) LOG_THREAD_SYNC(DEBUG_LEVEL_COUNTER, COLOR_LIGHT_YELLOW, "%s: " format, __FUNCTION__, ## args)
+#else
+#define LOG_COUNTER_EVENT(format, args...)
+#define LOG_COUNTER_UPDATE_RENDER(format, args...)
+#endif
+
+#ifdef ENABLE_UPDATE_RENDER_THREAD_LOGGING
+#define LOG_UPDATE_RENDER(format, args...) LOG_THREAD_SYNC(DEBUG_LEVEL_UPDATE_RENDER, COLOR_YELLOW, "%s: " format, __FUNCTION__, ## args)
+#define LOG_UPDATE_RENDER_TRACE LOG_THREAD_SYNC_TRACE(COLOR_YELLOW)
+#define LOG_UPDATE_RENDER_TRACE_FMT(format, args...) LOG_THREAD_SYNC_TRACE_FMT(COLOR_YELLOW, format, ## args)
+#else
+#define LOG_UPDATE_RENDER(format, args...)
+#define LOG_UPDATE_RENDER_TRACE
+#define LOG_UPDATE_RENDER_TRACE_FMT(format, args...)
+#endif
+
+#ifdef ENABLE_EVENT_LOGGING
+#define LOG_EVENT(format, args...) LOG_THREAD_SYNC(DEBUG_LEVEL_EVENT, COLOR_WHITE, "%s: " format, __FUNCTION__, ## args)
+#define LOG_EVENT_TRACE LOG_THREAD_SYNC_TRACE(COLOR_WHITE)
+#define LOG_EVENT_TRACE_FMT(format, args...) LOG_THREAD_SYNC_TRACE_FMT(COLOR_WHITE, format, ## args)
+#else
+#define LOG_EVENT(format, args...)
+#define LOG_EVENT_TRACE
+#define LOG_EVENT_TRACE_FMT(format, args...)
+#endif
+
+} // unnamed namespace
+
+} // namespace Adaptor
+
+} // namespace Internal
+
+} // namespace Dali
+
+#endif // __DALI_INTERNAL_COMBINED_UPDATE_RENDER_CONTROLLER_DEBUG_H__
--- /dev/null
+/*
+ * Copyright (c) 2015 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.
+ *
+ */
+
+// CLASS HEADER
+#include "combined-update-render-controller.h"
+
+// EXTERNAL INCLUDES
+#include <errno.h>
+#include <dali/integration-api/platform-abstraction.h>
+
+// INTERNAL INCLUDES
+#include <trigger-event-factory.h>
+#include <base/combined-update-render/combined-update-render-controller-debug.h>
+#include <base/environment-options.h>
+#include <base/time-service.h>
+#include <base/interfaces/adaptor-internal-services.h>
+
+namespace Dali
+{
+
+namespace Internal
+{
+
+namespace Adaptor
+{
+
+namespace
+{
+const unsigned int CREATED_THREAD_COUNT = 1u;
+
+const int CONTINUOUS = -1;
+const int ONCE = 1;
+
+const unsigned int TRUE = 1u;
+const unsigned int FALSE = 0u;
+
+const unsigned int MILLISECONDS_PER_SECOND( 1e+3 );
+const float NANOSECONDS_TO_SECOND( 1e-9f );
+const unsigned int NANOSECONDS_PER_SECOND( 1e+9 );
+const unsigned int NANOSECONDS_PER_MILLISECOND( 1e+6 );
+
+// The following values will get calculated at compile time
+const float DEFAULT_FRAME_DURATION_IN_SECONDS( 1.0f / 60.0f );
+const unsigned int DEFAULT_FRAME_DURATION_IN_MILLISECONDS( DEFAULT_FRAME_DURATION_IN_SECONDS * MILLISECONDS_PER_SECOND );
+const unsigned int DEFAULT_FRAME_DURATION_IN_NANOSECONDS( DEFAULT_FRAME_DURATION_IN_SECONDS * NANOSECONDS_PER_SECOND );
+
+/**
+ * Handles the use case when an update-request is received JUST before we process a sleep-request. If we did not have an update-request count then
+ * there is a danger that, on the event-thread we could have:
+ * 1) An update-request where we do nothing as Update/Render thread still running.
+ * 2) Quickly followed by a sleep-request being handled where we pause the Update/Render Thread (even though we have an update to process).
+ *
+ * Using a counter means we increment the counter on an update-request, and decrement it on a sleep-request. This handles the above scenario because:
+ * 1) MAIN THREAD: Update Request: COUNTER = 1
+ * 2) UPDATE/RENDER THREAD: Do Update/Render, then no Updates required -> Sleep Trigger
+ * 3) MAIN THREAD: Update Request: COUNTER = 2
+ * 4) MAIN THREAD: Sleep Request: COUNTER = 1 -> We do not sleep just yet
+ *
+ * Also ensures we preserve battery life by only doing ONE update when the above use case is not triggered.
+ * 1) MAIN THREAD: Update Request: COUNTER = 1
+ * 2) UPDATE/RENDER THREAD: Do Update/Render, then no Updates required -> Sleep Trigger
+ * 3) MAIN THREAD: Sleep Request: COUNTER = 0 -> Go to sleep
+ */
+const unsigned int MAXIMUM_UPDATE_REQUESTS = 2;
+} // unnamed namespace
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// EVENT THREAD
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+CombinedUpdateRenderController::CombinedUpdateRenderController( AdaptorInternalServices& adaptorInterfaces, const EnvironmentOptions& environmentOptions )
+: mFpsTracker( environmentOptions ),
+ mUpdateStatusLogger( environmentOptions ),
+ mRenderHelper( adaptorInterfaces ),
+ mEventThreadSemaphore(),
+ mUpdateRenderThreadWaitCondition(),
+ mAdaptorInterfaces( adaptorInterfaces ),
+ mPerformanceInterface( adaptorInterfaces.GetPerformanceInterface() ),
+ mCore( adaptorInterfaces.GetCore() ),
+ mEnvironmentOptions( environmentOptions ),
+ mNotificationTrigger( adaptorInterfaces.GetProcessCoreEventsTrigger() ),
+ mSleepTrigger( NULL ),
+ mUpdateRenderThread( NULL ),
+ mDefaultFrameDelta( 0.0f ),
+ mDefaultFrameDurationMilliseconds( 0u ),
+ mDefaultFrameDurationNanoseconds( 0u ),
+ mUpdateRequestCount( 0u ),
+ mRunning( FALSE ),
+ mUpdateRenderRunCount( 0 ),
+ mDestroyUpdateRenderThread( FALSE ),
+ mNewSurface( NULL ),
+ mPostRendering( FALSE )
+{
+ LOG_EVENT_TRACE;
+
+ // Initialise frame delta/duration variables first
+ SetRenderRefreshRate( environmentOptions.GetRenderRefreshRate() );
+
+ // Set the thread-synchronization interface on the render-surface
+ RenderSurface* currentSurface = mAdaptorInterfaces.GetRenderSurfaceInterface();
+ if( currentSurface )
+ {
+ currentSurface->SetThreadSynchronization( *this );
+ }
+
+ TriggerEventFactoryInterface& triggerFactory = mAdaptorInterfaces.GetTriggerEventFactoryInterface();
+ mSleepTrigger = triggerFactory.CreateTriggerEvent( MakeCallback( this, &CombinedUpdateRenderController::ProcessSleepRequest ), TriggerEventInterface::KEEP_ALIVE_AFTER_TRIGGER );
+
+ sem_init( &mEventThreadSemaphore, 0, 0 ); // Initialize to 0 so that it just waits if sem_post has not been called
+}
+
+CombinedUpdateRenderController::~CombinedUpdateRenderController()
+{
+ LOG_EVENT_TRACE;
+
+ Stop();
+
+ delete mSleepTrigger;
+}
+
+void CombinedUpdateRenderController::Initialize()
+{
+ LOG_EVENT_TRACE;
+
+ // Ensure Update/Render Thread not already created
+ DALI_ASSERT_ALWAYS( ! mUpdateRenderThread );
+
+ // Create Update/Render Thread
+ mUpdateRenderThread = new pthread_t();
+ int error = pthread_create( mUpdateRenderThread, NULL, InternalUpdateRenderThreadEntryFunc, this );
+ DALI_ASSERT_ALWAYS( !error && "Return code from pthread_create() when creating UpdateRenderThread" );
+
+ // The Update/Render thread will now run and initialise EGL etc. and will then wait for Start to be called
+ // When this function returns, the application initialisation on the event thread should occur
+}
+
+void CombinedUpdateRenderController::Start()
+{
+ LOG_EVENT_TRACE;
+
+ DALI_ASSERT_ALWAYS( !mRunning && mUpdateRenderThread );
+
+ // Wait until all threads created in Initialise are up and running
+ for( unsigned int i = 0; i < CREATED_THREAD_COUNT; ++i )
+ {
+ sem_wait( &mEventThreadSemaphore );
+ }
+
+ mRenderHelper.Start();
+
+ mRunning = TRUE;
+
+ LOG_EVENT( "Startup Complete, starting Update/Render Thread" );
+
+ RunUpdateRenderThread( CONTINUOUS );
+}
+
+void CombinedUpdateRenderController::Pause()
+{
+ LOG_EVENT_TRACE;
+
+ mRunning = FALSE;
+
+ PauseUpdateRenderThread();
+
+ AddPerformanceMarker( PerformanceInterface::PAUSED );
+}
+
+void CombinedUpdateRenderController::Resume()
+{
+ LOG_EVENT_TRACE;
+
+ if( !mRunning && IsUpdateRenderThreadPaused() )
+ {
+ LOG_EVENT( "Resuming" );
+
+ RunUpdateRenderThread( CONTINUOUS );
+
+ AddPerformanceMarker( PerformanceInterface::RESUME );
+
+ mRunning = TRUE;
+ }
+}
+
+void CombinedUpdateRenderController::Stop()
+{
+ LOG_EVENT_TRACE;
+
+ // Stop Rendering and the Update/Render Thread
+ mRenderHelper.Stop();
+
+ StopUpdateRenderThread();
+
+ if( mUpdateRenderThread )
+ {
+ LOG_EVENT( "Destroying UpdateRenderThread" );
+
+ // wait for the thread to finish
+ pthread_join( *mUpdateRenderThread, NULL );
+
+ delete mUpdateRenderThread;
+ mUpdateRenderThread = NULL;
+ }
+
+ mRunning = FALSE;
+}
+
+void CombinedUpdateRenderController::RequestUpdate()
+{
+ LOG_EVENT_TRACE;
+
+ // Increment the update-request count to the maximum
+ if( mUpdateRequestCount < MAXIMUM_UPDATE_REQUESTS )
+ {
+ ++mUpdateRequestCount;
+ }
+
+ if( mRunning && IsUpdateRenderThreadPaused() )
+ {
+ LOG_EVENT( "Processing" );
+
+ RunUpdateRenderThread( CONTINUOUS );
+ }
+}
+
+void CombinedUpdateRenderController::RequestUpdateOnce()
+{
+ if( IsUpdateRenderThreadPaused() )
+ {
+ LOG_EVENT_TRACE;
+
+ // Run Update/Render once
+ RunUpdateRenderThread( ONCE );
+ }
+}
+
+void CombinedUpdateRenderController::ReplaceSurface( RenderSurface* newSurface )
+{
+ LOG_EVENT_TRACE;
+
+ // Set the ThreadSyncronizationInterface on the new surface
+ newSurface->SetThreadSynchronization( *this );
+
+ LOG_EVENT( "Starting to replace the surface, event-thread blocked" );
+
+ // Start replacing the surface.
+ {
+ ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
+ mPostRendering = FALSE; // Clear the post-rendering flag as Update/Render thread will replace the surface now
+ mNewSurface = newSurface;
+ mUpdateRenderThreadWaitCondition.Notify( lock );
+ }
+
+ // Wait until the surface has been replaced
+ sem_wait( &mEventThreadSemaphore );
+
+ LOG_EVENT( "Surface replaced, event-thread continuing" );
+}
+
+void CombinedUpdateRenderController::SetRenderRefreshRate( unsigned int numberOfFramesPerRender )
+{
+ // Not protected by lock, but written to rarely so not worth adding a lock when reading
+ mDefaultFrameDelta = numberOfFramesPerRender * DEFAULT_FRAME_DURATION_IN_SECONDS;
+ mDefaultFrameDurationMilliseconds = numberOfFramesPerRender * DEFAULT_FRAME_DURATION_IN_MILLISECONDS;
+ mDefaultFrameDurationNanoseconds = numberOfFramesPerRender * DEFAULT_FRAME_DURATION_IN_NANOSECONDS;
+
+ LOG_EVENT( "mDefaultFrameDelta(%.6f), mDefaultFrameDurationMilliseconds(%lld), mDefaultFrameDurationNanoseconds(%lld)", mDefaultFrameDelta, mDefaultFrameDurationMilliseconds, mDefaultFrameDurationNanoseconds );
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// EVENT THREAD
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+void CombinedUpdateRenderController::RunUpdateRenderThread( int numberOfCycles )
+{
+ ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
+ mUpdateRenderRunCount = numberOfCycles;
+ LOG_COUNTER_EVENT( "mUpdateRenderRunCount: %d", mUpdateRenderRunCount );
+ mUpdateRenderThreadWaitCondition.Notify( lock );
+}
+
+void CombinedUpdateRenderController::PauseUpdateRenderThread()
+{
+ ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
+ mUpdateRenderRunCount = 0;
+}
+
+void CombinedUpdateRenderController::StopUpdateRenderThread()
+{
+ ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
+ mDestroyUpdateRenderThread = TRUE;
+ mUpdateRenderThreadWaitCondition.Notify( lock );
+}
+
+bool CombinedUpdateRenderController::IsUpdateRenderThreadPaused()
+{
+ ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
+ return mUpdateRenderRunCount != CONTINUOUS; // Report paused if NOT continuously running
+}
+
+void CombinedUpdateRenderController::ProcessSleepRequest()
+{
+ LOG_EVENT_TRACE;
+
+ // Decrement Update request count
+ if( mUpdateRequestCount > 0 )
+ {
+ --mUpdateRequestCount;
+ }
+
+ // Only sleep if our update-request count is 0
+ if( mUpdateRequestCount == 0 )
+ {
+ LOG_EVENT( "Going to sleep" );
+
+ PauseUpdateRenderThread();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// UPDATE/RENDER THREAD
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+void CombinedUpdateRenderController::UpdateRenderThread()
+{
+ // Install a function for logging
+ mEnvironmentOptions.InstallLogFunction();
+
+ LOG_UPDATE_RENDER( "THREAD CREATED" );
+
+ mRenderHelper.InitializeEgl();
+
+ // tell core it has a context
+ mCore.ContextCreated();
+
+ NotifyThreadInitialised();
+
+ // Update time
+ uint64_t lastFrameTime;
+ TimeService::GetNanoseconds( lastFrameTime );
+
+ LOG_UPDATE_RENDER( "THREAD INITIALISED" );
+
+ while( UpdateRenderReady() )
+ {
+ LOG_UPDATE_RENDER_TRACE;
+
+ uint64_t currentFrameStartTime = 0;
+ TimeService::GetNanoseconds( currentFrameStartTime );
+
+ // Optional FPS Tracking
+ if( mFpsTracker.Enabled() )
+ {
+ uint64_t timeSinceLastFrame = currentFrameStartTime - lastFrameTime;
+ float absoluteTimeSinceLastRender = timeSinceLastFrame * NANOSECONDS_TO_SECOND;
+ mFpsTracker.Track( absoluteTimeSinceLastRender );
+ lastFrameTime = currentFrameStartTime; // Store frame start time
+ }
+
+ //////////////////////////////
+ // REPLACE SURFACE
+ //////////////////////////////
+
+ RenderSurface* newSurface = ShouldSurfaceBeReplaced();
+ if( newSurface )
+ {
+ LOG_UPDATE_RENDER_TRACE_FMT( "Replacing Surface" );
+ mRenderHelper.ReplaceSurface( newSurface );
+ SurfaceReplaced();
+ }
+
+ //////////////////////////////
+ // UPDATE
+ //////////////////////////////
+
+ unsigned int currentTime = currentFrameStartTime / NANOSECONDS_PER_MILLISECOND;
+ unsigned int nextFrameTime = currentTime + mDefaultFrameDurationMilliseconds;
+
+ Integration::UpdateStatus updateStatus;
+
+ AddPerformanceMarker( PerformanceInterface::UPDATE_START );
+ mCore.Update( mDefaultFrameDelta, currentTime, nextFrameTime, updateStatus );
+ AddPerformanceMarker( PerformanceInterface::UPDATE_END );
+
+ unsigned int keepUpdatingStatus = updateStatus.KeepUpdating();
+
+ // Tell the event-thread to wake up (if asleep) and send a notification event to Core if required
+ if( updateStatus.NeedsNotification() )
+ {
+ mNotificationTrigger.Trigger();
+ }
+
+ // Optional logging of update/render status
+ mUpdateStatusLogger.Log( keepUpdatingStatus );
+
+ //////////////////////////////
+ // RENDER
+ //////////////////////////////
+
+ mRenderHelper.ConsumeEvents();
+ mRenderHelper.PreRender();
+
+ Integration::RenderStatus renderStatus;
+
+ AddPerformanceMarker( PerformanceInterface::RENDER_START );
+ mCore.Render( renderStatus );
+ AddPerformanceMarker( PerformanceInterface::RENDER_END );
+
+ if( renderStatus.HasRendered() )
+ {
+ mRenderHelper.PostRender();
+ }
+
+ // Trigger event thread to request Update/Render thread to sleep if update not required
+ if( ( Integration::KeepUpdating::NOT_REQUESTED == keepUpdatingStatus ) &&
+ ! renderStatus.NeedsUpdate() )
+ {
+ mSleepTrigger->Trigger();
+ }
+
+ //////////////////////////////
+ // FRAME TIME
+ //////////////////////////////
+
+ // Sleep until at least the the default frame duration has elapsed. This will return immediately if the specified end-time has already passed.
+ TimeService::SleepUntil( currentFrameStartTime + mDefaultFrameDurationNanoseconds );
+ }
+
+ // Inform core of context destruction & shutdown EGL
+ mCore.ContextDestroyed();
+ mRenderHelper.ShutdownEgl();
+
+ LOG_UPDATE_RENDER( "THREAD DESTROYED" );
+
+ // Uninstall the logging function
+ mEnvironmentOptions.UnInstallLogFunction();
+}
+
+bool CombinedUpdateRenderController::UpdateRenderReady()
+{
+ ConditionalWait::ScopedLock updateLock( mUpdateRenderThreadWaitCondition );
+ while( ! mUpdateRenderRunCount && // Should try to wait if event-thread has paused the Update/Render thread
+ ! mDestroyUpdateRenderThread && // Ensure we don't wait if the update-render-thread is supposed to be destroyed
+ ! mNewSurface ) // Ensure we don't wait if we need to replace the surface
+ {
+ mUpdateRenderThreadWaitCondition.Wait( updateLock );
+ }
+
+ LOG_COUNTER_UPDATE_RENDER( "mUpdateRenderRunCount: %d", mUpdateRenderRunCount );
+
+ // If we've been asked to run Update/Render cycles a finite number of times then decrement so we wait after the
+ // requested number of cycles
+ if( mUpdateRenderRunCount > 0 )
+ {
+ --mUpdateRenderRunCount;
+ }
+
+ // Keep the update-render thread alive if this thread is NOT to be destroyed
+ return ! mDestroyUpdateRenderThread;
+}
+
+RenderSurface* CombinedUpdateRenderController::ShouldSurfaceBeReplaced()
+{
+ ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
+
+ RenderSurface* newSurface = mNewSurface;
+ mNewSurface = NULL;
+
+ return newSurface;
+}
+
+void CombinedUpdateRenderController::SurfaceReplaced()
+{
+ // Just increment the semaphore
+ sem_post( &mEventThreadSemaphore );
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// ALL THREADS
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+void CombinedUpdateRenderController::NotifyThreadInitialised()
+{
+ // Just increment the semaphore
+ sem_post( &mEventThreadSemaphore );
+}
+
+void CombinedUpdateRenderController::AddPerformanceMarker( PerformanceInterface::MarkerType type )
+{
+ if( mPerformanceInterface )
+ {
+ mPerformanceInterface->AddMarker( type );
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+// POST RENDERING: EVENT THREAD
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+void CombinedUpdateRenderController::PostRenderComplete()
+{
+ ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
+ mPostRendering = FALSE;
+ mUpdateRenderThreadWaitCondition.Notify( lock );
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// POST RENDERING: RENDER THREAD
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+void CombinedUpdateRenderController::PostRenderStarted()
+{
+ ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
+ mPostRendering = TRUE;
+}
+
+void CombinedUpdateRenderController::PostRenderWaitForCompletion()
+{
+ ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
+ while( mPostRendering &&
+ ! mNewSurface ) // We should NOT wait if we're replacing the surface
+ {
+ mUpdateRenderThreadWaitCondition.Wait( lock );
+ }
+}
+
+} // namespace Adaptor
+
+} // namespace Internal
+
+} // namespace Dali
--- /dev/null
+#ifndef __DALI_INTERNAL_COMBINED_UPDATE_RENDER_CONTROLLER_H__
+#define __DALI_INTERNAL_COMBINED_UPDATE_RENDER_CONTROLLER_H__
+
+/*
+ * Copyright (c) 2015 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.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <semaphore.h>
+#include <stdint.h>
+#include <dali/integration-api/core.h>
+#include <dali/devel-api/threading/conditional-wait.h>
+
+// INTERNAL INCLUDES
+#include <integration-api/thread-synchronization-interface.h>
+#include <base/interfaces/performance-interface.h>
+#include <base/fps-tracker.h>
+#include <base/render-helper.h>
+#include <base/thread-controller-interface.h>
+#include <base/update-status-logger.h>
+
+namespace Dali
+{
+
+class RenderSurface;
+class TriggerEventInterface;
+
+namespace Internal
+{
+
+namespace Adaptor
+{
+
+class AdaptorInternalServices;
+class EnvironmentOptions;
+
+/**
+ * @brief Two threads where events/application interaction is handled on the main/event thread and the Update & Render
+ * happen on the other thread.
+ *
+ * Key Points:
+ * 1. Two Threads:
+ * a. Main/Event Thread.
+ * b. Update/Render Thread.
+ * 2. There is NO VSync thread:
+ * a. We retrieve the time before Update.
+ * b. Then retrieve the time after Render.
+ * c. We calculate the difference between these two times and if:
+ * i. The difference is less than the default frame time, we sleep.
+ * ii. If it’s more or the same, we continue.
+ * 3. On the update/render thread, if we discover that we do not need to do any more updates, we use a trigger-event
+ * to inform the main/event thread. This is then processed as soon as the event thread is able to do so where it
+ * is easier to make a decision about whether we should stop the update/render thread or not (depending on any
+ * update requests etc.).
+ * 4. The main thread is blocked while the surface is being replaced.
+ */
+class CombinedUpdateRenderController : public ThreadControllerInterface,
+ public ThreadSynchronizationInterface
+{
+public:
+
+ /**
+ * Constructor
+ */
+ CombinedUpdateRenderController( AdaptorInternalServices& adaptorInterfaces, const EnvironmentOptions& environmentOptions );
+
+ /**
+ * Non virtual destructor. Not intended as base class.
+ */
+ ~CombinedUpdateRenderController();
+
+ /**
+ * @copydoc ThreadControllerInterface::Initialize()
+ */
+ virtual void Initialize();
+
+ /**
+ * @copydoc ThreadControllerInterface::Start()
+ */
+ virtual void Start();
+
+ /**
+ * @copydoc ThreadControllerInterface::Pause()
+ */
+ virtual void Pause();
+
+ /**
+ * @copydoc ThreadControllerInterface::Resume()
+ */
+ virtual void Resume();
+
+ /**
+ * @copydoc ThreadControllerInterface::Stop()
+ */
+ virtual void Stop();
+
+ /**
+ * @copydoc ThreadControllerInterface::RequestUpdate()
+ */
+ virtual void RequestUpdate();
+
+ /**
+ * @copydoc ThreadControllerInterface::RequestUpdateOnce()
+ */
+ virtual void RequestUpdateOnce();
+
+ /**
+ * @copydoc ThreadControllerInterface::ReplaceSurface()
+ */
+ virtual void ReplaceSurface( RenderSurface* surface );
+
+ /**
+ * @copydoc ThreadControllerInterface::SetRenderRefreshRate()
+ */
+ virtual void SetRenderRefreshRate( unsigned int numberOfFramesPerRender );
+
+private:
+
+ // Undefined copy constructor.
+ CombinedUpdateRenderController( const CombinedUpdateRenderController& );
+
+ // Undefined assignment operator.
+ CombinedUpdateRenderController& operator=( const CombinedUpdateRenderController& );
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // EventThread
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Runs the Update/Render Thread.
+ * This will lock the mutex in mUpdateRenderThreadWaitCondition.
+ *
+ * @param[in] numberOfCycles The number of times the update/render cycle should run. If -1, then it will run continuously.
+ */
+ inline void RunUpdateRenderThread( int numberOfCycles );
+
+ /**
+ * Pauses the Update/Render Thread.
+ * This will lock the mutex in mUpdateRenderThreadWaitCondition.
+ */
+ inline void PauseUpdateRenderThread();
+
+ /**
+ * Stops the Update/Render Thread.
+ * This will lock the mutex in mUpdateRenderThreadWaitCondition.
+ *
+ * @note Should only be called in Stop as calling this will kill the update-thread.
+ */
+ inline void StopUpdateRenderThread();
+
+ /**
+ * Checks if the the Update/Render Thread is paused.
+ * This will lock the mutex in mUpdateRenderThreadWaitCondition.
+ *
+ * @return true if paused, false otherwise
+ */
+ inline bool IsUpdateRenderThreadPaused();
+
+ /**
+ * Used as the callback for the sleep-trigger.
+ *
+ * Will sleep when enough requests are made without any requests.
+ */
+ void ProcessSleepRequest();
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // UpdateRenderThread
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * The Update/Render thread loop. This thread will be destroyed on exit from this function.
+ */
+ void UpdateRenderThread();
+
+ /**
+ * Called by the Update/Render Thread which ensures a wait if required.
+ *
+ * @return false, if the thread should stop.
+ */
+ bool UpdateRenderReady();
+
+ /**
+ * Checks to see if the surface needs to be replaced.
+ * This will lock the mutex in mUpdateRenderThreadWaitCondition.
+ *
+ * @return Pointer to the new surface, NULL otherwise
+ */
+ RenderSurface* ShouldSurfaceBeReplaced();
+
+ /**
+ * Called by the Update/Render thread after a surface has been replaced.
+ *
+ * This will lock the mutex in mEventThreadWaitCondition
+ */
+ void SurfaceReplaced();
+
+ /**
+ * Helper for the thread calling the entry function
+ * @param[in] This A pointer to the current object
+ */
+ static void* InternalUpdateRenderThreadEntryFunc( void* This )
+ {
+ ( static_cast<CombinedUpdateRenderController*>( This ) )->UpdateRenderThread();
+ return NULL;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // ALL Threads
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Called by the update-render & v-sync threads when they up and running.
+ *
+ * This will lock the mutex in mEventThreadWaitCondition.
+ */
+ void NotifyThreadInitialised();
+
+ /**
+ * Helper to add a performance marker to the performance server (if it's active)
+ * @param[in] type performance marker type
+ */
+ void AddPerformanceMarker( PerformanceInterface::MarkerType type );
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ // POST RENDERING - ThreadSynchronizationInterface overrides
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ //// Called by the Event Thread if post-rendering is required
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @copydoc ThreadSynchronizationInterface::PostRenderComplete()
+ */
+ virtual void PostRenderComplete();
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+ //// Called by the Render Thread if post-rendering is required
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * @copydoc ThreadSynchronizationInterface::PostRenderStarted()
+ */
+ virtual void PostRenderStarted();
+
+ /**
+ * @copydoc ThreadSynchronizationInterface::PostRenderStarted()
+ */
+ virtual void PostRenderWaitForCompletion();
+
+private:
+
+ FpsTracker mFpsTracker; ///< Object that tracks the FPS
+ UpdateStatusLogger mUpdateStatusLogger; ///< Object that logs the update-status as required.
+
+ RenderHelper mRenderHelper; ///< Helper class for EGL, pre & post rendering
+
+ sem_t mEventThreadSemaphore; ///< Used by the event thread to ensure all threads have been initialised, and when replacing the surface.
+
+ ConditionalWait mUpdateRenderThreadWaitCondition; ///< The wait condition for the update-render-thread.
+
+ AdaptorInternalServices& mAdaptorInterfaces; ///< The adaptor internal interface
+ PerformanceInterface* mPerformanceInterface; ///< The performance logging interface
+ Integration::Core& mCore; ///< Dali core reference
+ const EnvironmentOptions& mEnvironmentOptions; ///< Environment options
+ TriggerEventInterface& mNotificationTrigger; ///< Reference to notification event trigger
+ TriggerEventInterface* mSleepTrigger; ///< Used by the update-render thread to trigger the event thread when it no longer needs to do any updates
+
+ pthread_t* mUpdateRenderThread; ///< The Update/Render thread.
+
+ float mDefaultFrameDelta; ///< Default time delta between each frame (used for animations). Not protected by lock, but written to rarely so not worth adding a lock when reading.
+ uint64_t mDefaultFrameDurationMilliseconds; ///< Default duration of a frame (used for predicting the time of the next frame). Not protected by lock, but written to rarely so not worth adding a lock when reading.
+ uint64_t mDefaultFrameDurationNanoseconds; ///< Default duration of a frame (used for sleeping if not enough time elapsed). Not protected by lock, but written to rarely so not worth adding a lock when reading.
+
+ unsigned int mUpdateRequestCount; ///< Count of update-requests we have received to ensure we do not go to sleep too early.
+ unsigned int mRunning; ///< Read and set on the event-thread only to state whether we are running.
+
+ //
+ // NOTE: cannot use booleans as these are used from multiple threads, must use variable with machine word size for atomic read/write
+ //
+
+ volatile int mUpdateRenderRunCount; ///< The number of times Update/Render cycle should run. If -1, then will run continuously (set by the event-thread, read by v-sync-thread).
+ volatile unsigned int mDestroyUpdateRenderThread; ///< Whether the Update/Render thread be destroyed (set by the event-thread, read by the update-render-thread).
+
+ RenderSurface* volatile mNewSurface; ///< Will be set to the new-surface if requested (set by the event-thread, read & cleared by the update-render thread).
+
+ volatile unsigned int mPostRendering; ///< Whether post-rendering is taking place (set by the event & render threads, read by the render-thread).
+};
+
+} // namespace Adaptor
+
+} // namespace Internal
+
+} // namespace Dali
+
+#endif // __DALI_INTERNAL_COMBINED_UPDATE_RENDER_CONTROLLER_H__
mGlesCallTime(0),
mWindowWidth( 0 ),
mWindowHeight( 0 ),
- mThreadingMode( ThreadingMode::SEPARATE_UPDATE_RENDER ),
+ mThreadingMode( ThreadingMode::COMBINED_UPDATE_RENDER ),
mRenderRefreshRate( 1 ),
mLogFunction( NULL )
{
$(base_adaptor_src_dir)/performance-logging/performance-interface-factory.cpp \
$(base_adaptor_src_dir)/performance-logging/statistics/stat-context.cpp \
$(base_adaptor_src_dir)/performance-logging/statistics/stat-context-manager.cpp \
+ $(base_adaptor_src_dir)/combined-update-render/combined-update-render-controller.cpp \
$(base_adaptor_src_dir)/separate-update-render/frame-time.cpp \
$(base_adaptor_src_dir)/separate-update-render/separate-update-render-controller.cpp \
$(base_adaptor_src_dir)/separate-update-render/render-request.cpp \
// INTERNAL INCLUDES
#include <base/environment-options.h>
#include <base/thread-controller-interface.h>
+#include <base/combined-update-render/combined-update-render-controller.h>
#include <base/separate-update-render/separate-update-render-controller.h>
#include <base/single-threaded/single-thread-controller.h>
switch( environmentOptions.GetThreadingMode() )
{
case ThreadingMode::SEPARATE_UPDATE_RENDER:
- case ThreadingMode::COMBINED_UPDATE_RENDER:
{
mThreadControllerInterface = new SeparateUpdateRenderController( adaptorInterfaces, environmentOptions );
break;
}
+ case ThreadingMode::COMBINED_UPDATE_RENDER:
+ {
+ mThreadControllerInterface = new CombinedUpdateRenderController( adaptorInterfaces, environmentOptions );
+ break;
+ }
+
case ThreadingMode::SINGLE_THREADED:
{
mThreadControllerInterface = new SingleThreadController( adaptorInterfaces, environmentOptions );
~ThreadController();
/**
- * Initializes the thread controller
+ * @brief Initializes the thread controller
+ *
+ * Will do any required initialiszation, e.g. initialize EGL, create threads (if required), etc.
+ *
+ * @note When this function returns, the application Init signal should be emitted
*/
void Initialize();
/**
- * @copydoc Dali::Adaptor::Start()
+ * @brief Called AFTER the Init signal has been emitted.
+ *
+ * In other words, should be called AFTER the Init signal has been emitted and all messages for the first scene
+ * have been queued for update to process.
*/
void Start();
/**
- * @copydoc Dali::Adaptor::Pause()
+ * @brief When called, update and rendering is paused.
*/
void Pause();
/**
- * @copydoc Dali::Adaptor::Resume()
+ * @brief Resumes update/rendering after a previous pause.
*/
void Resume();
/**
- * @copydoc Dali::Adaptor::Stop()
+ * @brief Stops update/rendering altogether.
+ *
+ * Will shutdown EGL, destroy threads (if required) etc.
*/
void Stop();
/**
- * Called by the adaptor when core requires another update
+ * @brief Called by the adaptor when core requires another update
*/
void RequestUpdate();
/**
- * Called by the adaptor when core requires one update
- * If Adaptor is paused, we do one update and return to pause
+ * @brief Called by the adaptor when core requires one update
+ *
+ * @note If Adaptor is paused, we do one update/render only
*/
void RequestUpdateOnce();
/**
- * Replaces the surface.
+ * @brief Replaces the surface.
+ *
* @param surface new surface
*/
void ReplaceSurface( RenderSurface* surface );
const uint64_t NANOSECONDS_PER_SECOND = 1e+9;
}
-void GetNanoseconds( uint64_t& time )
+void GetNanoseconds( uint64_t& timeInNanoseconds )
{
timespec timeSpec;
clock_gettime( CLOCK_MONOTONIC, &timeSpec );
// Convert all values to uint64_t to match our return type
- time = ( static_cast< uint64_t >( timeSpec.tv_sec ) * NANOSECONDS_PER_SECOND ) + static_cast< uint64_t >( timeSpec.tv_nsec );
+ timeInNanoseconds = ( static_cast< uint64_t >( timeSpec.tv_sec ) * NANOSECONDS_PER_SECOND ) + static_cast< uint64_t >( timeSpec.tv_nsec );
+}
+
+void SleepUntil( uint64_t timeInNanoseconds )
+{
+ timespec timeSpec;
+ timeSpec.tv_sec = timeInNanoseconds / NANOSECONDS_PER_SECOND;
+ timeSpec.tv_nsec = timeInNanoseconds % NANOSECONDS_PER_SECOND;
+
+ // clock_nanosleep returns 0 if it sleeps for the period specified, otherwise it returns an error value
+ // If an error value is returned, just sleep again till the absolute time specified
+ while( clock_nanosleep( CLOCK_MONOTONIC, TIMER_ABSTIME, &timeSpec, NULL ) )
+ {
+ }
}
} // namespace TimeService
/**
* @brief Get the monotonic time since some unspecified starting point (usually the boot time).
*
- * @param[out] nanoseconds The time in nanoseconds since the reference point.
+ * @param[out] timeInNanoseconds The time in nanoseconds since the reference point.
*
- * @note The maximum value this can hold is 0xFFFFFFFFFFFFFFFF which is 1.844674407e+19. Therefore, this can overflow after approximately 584 years.
+ * @note The maximum value timeInNanoseconds can hold is 0xFFFFFFFFFFFFFFFF which is 1.844674407e+19. Therefore, this can overflow after approximately 584 years.
*/
-void GetNanoseconds( uint64_t& time );
+void GetNanoseconds( uint64_t& timeInNanoseconds );
+
+/**
+ * @brief Sleeps until the monotonic time specified since some unspecified starting point (usually the boot time).
+ *
+ * If the time specified has already passed, then it returns immediately.
+ *
+ * @param[in] timeInNanoseconds The time to sleep until
+ *
+ * @note The maximum value timeInNanoseconds can hold is 0xFFFFFFFFFFFFFFFF which is 1.844674407e+19. Therefore, this can overflow after approximately 584 years.
+ */
+void SleepUntil( uint64_t timeInNanoseconds );
} // namespace TimeService