Added Combined Update/Render Thread Controller 18/50518/29
authorAdeel Kazmi <adeel.kazmi@samsung.com>
Thu, 29 Oct 2015 10:36:31 +0000 (10:36 +0000)
committerAdeel Kazmi <adeel.kazmi@samsung.com>
Tue, 24 Nov 2015 16:16:36 +0000 (16:16 +0000)
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, then 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 still blocked while the surface is being replaced.

Change-Id: I54a1ce9ae18fbb4927b29ae8f259034baa9d675f

adaptors/base/combined-update-render/combined-update-render-controller-debug.h [new file with mode: 0644]
adaptors/base/combined-update-render/combined-update-render-controller.cpp [new file with mode: 0644]
adaptors/base/combined-update-render/combined-update-render-controller.h [new file with mode: 0644]
adaptors/base/environment-options.cpp
adaptors/base/file.list
adaptors/base/thread-controller.cpp
adaptors/base/thread-controller.h
adaptors/base/time-service.cpp
adaptors/base/time-service.h

diff --git a/adaptors/base/combined-update-render/combined-update-render-controller-debug.h b/adaptors/base/combined-update-render/combined-update-render-controller-debug.h
new file mode 100644 (file)
index 0000000..41b9162
--- /dev/null
@@ -0,0 +1,140 @@
+#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__
diff --git a/adaptors/base/combined-update-render/combined-update-render-controller.cpp b/adaptors/base/combined-update-render/combined-update-render-controller.cpp
new file mode 100644 (file)
index 0000000..c0ff4e3
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+ * 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
diff --git a/adaptors/base/combined-update-render/combined-update-render-controller.h b/adaptors/base/combined-update-render/combined-update-render-controller.h
new file mode 100644 (file)
index 0000000..2b8c274
--- /dev/null
@@ -0,0 +1,309 @@
+#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__
index 87a7406..b5e293c 100644 (file)
@@ -104,7 +104,7 @@ EnvironmentOptions::EnvironmentOptions()
   mGlesCallTime(0),
   mWindowWidth( 0 ),
   mWindowHeight( 0 ),
-  mThreadingMode( ThreadingMode::SEPARATE_UPDATE_RENDER ),
+  mThreadingMode( ThreadingMode::COMBINED_UPDATE_RENDER ),
   mRenderRefreshRate( 1 ),
   mLogFunction( NULL )
 {
index 92e011f..022f01f 100644 (file)
@@ -15,6 +15,7 @@ base_adaptor_src_files = \
   $(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 \
index 3c8aa5f..ed12064 100644 (file)
@@ -21,6 +21,7 @@
 // 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>
 
@@ -39,12 +40,17 @@ ThreadController::ThreadController( AdaptorInternalServices& adaptorInterfaces,
   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 );
index fba36c5..d9b8976 100644 (file)
@@ -51,43 +51,54 @@ public:
   ~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 );
index 61d9f7f..4c6c346 100644 (file)
@@ -38,13 +38,26 @@ namespace
 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
index ed2ca8a..39d0c6d 100644 (file)
@@ -35,11 +35,22 @@ 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