2 * Copyright (c) 2017 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include "combined-update-render-controller.h"
23 #include <dali/integration-api/platform-abstraction.h>
26 #include <trigger-event-factory.h>
27 #include <base/combined-update-render/combined-update-render-controller-debug.h>
28 #include <base/environment-options.h>
29 #include <base/time-service.h>
30 #include <base/interfaces/adaptor-internal-services.h>
43 const unsigned int CREATED_THREAD_COUNT = 1u;
45 const int CONTINUOUS = -1;
48 const unsigned int TRUE = 1u;
49 const unsigned int FALSE = 0u;
51 const unsigned int MILLISECONDS_PER_SECOND( 1e+3 );
52 const float NANOSECONDS_TO_SECOND( 1e-9f );
53 const unsigned int NANOSECONDS_PER_SECOND( 1e+9 );
54 const unsigned int NANOSECONDS_PER_MILLISECOND( 1e+6 );
56 // The following values will get calculated at compile time
57 const float DEFAULT_FRAME_DURATION_IN_SECONDS( 1.0f / 60.0f );
58 const uint64_t DEFAULT_FRAME_DURATION_IN_MILLISECONDS( DEFAULT_FRAME_DURATION_IN_SECONDS * MILLISECONDS_PER_SECOND );
59 const uint64_t DEFAULT_FRAME_DURATION_IN_NANOSECONDS( DEFAULT_FRAME_DURATION_IN_SECONDS * NANOSECONDS_PER_SECOND );
62 * 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
63 * there is a danger that, on the event-thread we could have:
64 * 1) An update-request where we do nothing as Update/Render thread still running.
65 * 2) Quickly followed by a sleep-request being handled where we pause the Update/Render Thread (even though we have an update to process).
67 * 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:
68 * 1) MAIN THREAD: Update Request: COUNTER = 1
69 * 2) UPDATE/RENDER THREAD: Do Update/Render, then no Updates required -> Sleep Trigger
70 * 3) MAIN THREAD: Update Request: COUNTER = 2
71 * 4) MAIN THREAD: Sleep Request: COUNTER = 1 -> We do not sleep just yet
73 * Also ensures we preserve battery life by only doing ONE update when the above use case is not triggered.
74 * 1) MAIN THREAD: Update Request: COUNTER = 1
75 * 2) UPDATE/RENDER THREAD: Do Update/Render, then no Updates required -> Sleep Trigger
76 * 3) MAIN THREAD: Sleep Request: COUNTER = 0 -> Go to sleep
78 const unsigned int MAXIMUM_UPDATE_REQUESTS = 2;
79 } // unnamed namespace
81 ///////////////////////////////////////////////////////////////////////////////////////////////////
83 ///////////////////////////////////////////////////////////////////////////////////////////////////
85 CombinedUpdateRenderController::CombinedUpdateRenderController( AdaptorInternalServices& adaptorInterfaces, const EnvironmentOptions& environmentOptions )
86 : mFpsTracker( environmentOptions ),
87 mUpdateStatusLogger( environmentOptions ),
88 mRenderHelper( adaptorInterfaces ),
89 mEventThreadSemaphore(),
90 mUpdateRenderThreadWaitCondition(),
91 mAdaptorInterfaces( adaptorInterfaces ),
92 mPerformanceInterface( adaptorInterfaces.GetPerformanceInterface() ),
93 mCore( adaptorInterfaces.GetCore() ),
94 mEnvironmentOptions( environmentOptions ),
95 mNotificationTrigger( adaptorInterfaces.GetProcessCoreEventsTrigger() ),
96 mSleepTrigger( NULL ),
97 mUpdateRenderThread( NULL ),
98 mDefaultFrameDelta( 0.0f ),
99 mDefaultFrameDurationMilliseconds( 0u ),
100 mDefaultFrameDurationNanoseconds( 0u ),
101 mDefaultHalfFrameNanoseconds( 0u ),
102 mUpdateRequestCount( 0u ),
104 mUpdateRenderRunCount( 0 ),
105 mDestroyUpdateRenderThread( FALSE ),
106 mUpdateRenderThreadCanSleep( FALSE ),
107 mPendingRequestUpdate( FALSE ),
108 mUseElapsedTimeAfterWait( FALSE ),
110 mPostRendering( FALSE )
114 // Initialise frame delta/duration variables first
115 SetRenderRefreshRate( environmentOptions.GetRenderRefreshRate() );
117 // Set the thread-synchronization interface on the render-surface
118 RenderSurface* currentSurface = mAdaptorInterfaces.GetRenderSurfaceInterface();
121 currentSurface->SetThreadSynchronization( *this );
124 TriggerEventFactoryInterface& triggerFactory = mAdaptorInterfaces.GetTriggerEventFactoryInterface();
125 mSleepTrigger = triggerFactory.CreateTriggerEvent( MakeCallback( this, &CombinedUpdateRenderController::ProcessSleepRequest ), TriggerEventInterface::KEEP_ALIVE_AFTER_TRIGGER );
127 sem_init( &mEventThreadSemaphore, 0, 0 ); // Initialize to 0 so that it just waits if sem_post has not been called
130 CombinedUpdateRenderController::~CombinedUpdateRenderController()
136 delete mSleepTrigger;
139 void CombinedUpdateRenderController::Initialize()
143 // Ensure Update/Render Thread not already created
144 DALI_ASSERT_ALWAYS( ! mUpdateRenderThread );
146 // Create Update/Render Thread
147 mUpdateRenderThread = new pthread_t();
148 int error = pthread_create( mUpdateRenderThread, NULL, InternalUpdateRenderThreadEntryFunc, this );
149 DALI_ASSERT_ALWAYS( !error && "Return code from pthread_create() when creating UpdateRenderThread" );
151 // The Update/Render thread will now run and initialise EGL etc. and will then wait for Start to be called
152 // When this function returns, the application initialisation on the event thread should occur
155 void CombinedUpdateRenderController::Start()
159 DALI_ASSERT_ALWAYS( !mRunning && mUpdateRenderThread );
161 // Wait until all threads created in Initialise are up and running
162 for( unsigned int i = 0; i < CREATED_THREAD_COUNT; ++i )
164 sem_wait( &mEventThreadSemaphore );
167 mRenderHelper.Start();
171 LOG_EVENT( "Startup Complete, starting Update/Render Thread" );
173 RunUpdateRenderThread( CONTINUOUS, false /* No animation progression */ );
176 void CombinedUpdateRenderController::Pause()
182 PauseUpdateRenderThread();
184 AddPerformanceMarker( PerformanceInterface::PAUSED );
187 void CombinedUpdateRenderController::Resume()
191 if( !mRunning && IsUpdateRenderThreadPaused() )
193 LOG_EVENT( "Resuming" );
195 RunUpdateRenderThread( CONTINUOUS, true /* Animation progression required while we were paused */ );
197 AddPerformanceMarker( PerformanceInterface::RESUME );
203 void CombinedUpdateRenderController::Stop()
207 // Stop Rendering and the Update/Render Thread
208 mRenderHelper.Stop();
210 StopUpdateRenderThread();
212 if( mUpdateRenderThread )
214 LOG_EVENT( "Destroying UpdateRenderThread" );
216 // wait for the thread to finish
217 pthread_join( *mUpdateRenderThread, NULL );
219 delete mUpdateRenderThread;
220 mUpdateRenderThread = NULL;
226 void CombinedUpdateRenderController::RequestUpdate()
230 // Increment the update-request count to the maximum
231 if( mUpdateRequestCount < MAXIMUM_UPDATE_REQUESTS )
233 ++mUpdateRequestCount;
236 if( mRunning && IsUpdateRenderThreadPaused() )
238 LOG_EVENT( "Processing" );
240 RunUpdateRenderThread( CONTINUOUS, false /* No animation progression */ );
243 ConditionalWait::ScopedLock updateLock( mUpdateRenderThreadWaitCondition );
244 mPendingRequestUpdate = TRUE;
247 void CombinedUpdateRenderController::RequestUpdateOnce()
249 // Increment the update-request count to the maximum
250 if( mUpdateRequestCount < MAXIMUM_UPDATE_REQUESTS )
252 ++mUpdateRequestCount;
255 if( IsUpdateRenderThreadPaused() )
259 // Run Update/Render once
260 RunUpdateRenderThread( ONCE, false /* No animation progression */ );
264 void CombinedUpdateRenderController::ReplaceSurface( RenderSurface* newSurface )
268 // Set the ThreadSyncronizationInterface on the new surface
269 newSurface->SetThreadSynchronization( *this );
271 LOG_EVENT( "Starting to replace the surface, event-thread blocked" );
273 // Start replacing the surface.
275 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
276 mPostRendering = FALSE; // Clear the post-rendering flag as Update/Render thread will replace the surface now
277 mNewSurface = newSurface;
278 mUpdateRenderThreadWaitCondition.Notify( lock );
281 // Wait until the surface has been replaced
282 sem_wait( &mEventThreadSemaphore );
284 LOG_EVENT( "Surface replaced, event-thread continuing" );
287 void CombinedUpdateRenderController::SetRenderRefreshRate( unsigned int numberOfFramesPerRender )
289 // Not protected by lock, but written to rarely so not worth adding a lock when reading
290 mDefaultFrameDelta = numberOfFramesPerRender * DEFAULT_FRAME_DURATION_IN_SECONDS;
291 mDefaultFrameDurationMilliseconds = uint64_t( numberOfFramesPerRender ) * DEFAULT_FRAME_DURATION_IN_MILLISECONDS;
292 mDefaultFrameDurationNanoseconds = uint64_t( numberOfFramesPerRender ) * DEFAULT_FRAME_DURATION_IN_NANOSECONDS;
293 mDefaultHalfFrameNanoseconds = mDefaultFrameDurationNanoseconds / 2u;
295 LOG_EVENT( "mDefaultFrameDelta(%.6f), mDefaultFrameDurationMilliseconds(%lld), mDefaultFrameDurationNanoseconds(%lld)", mDefaultFrameDelta, mDefaultFrameDurationMilliseconds, mDefaultFrameDurationNanoseconds );
298 ///////////////////////////////////////////////////////////////////////////////////////////////////
300 ///////////////////////////////////////////////////////////////////////////////////////////////////
302 void CombinedUpdateRenderController::RunUpdateRenderThread( int numberOfCycles, bool useElapsedTime )
304 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
305 mUpdateRenderRunCount = numberOfCycles;
306 mUpdateRenderThreadCanSleep = FALSE;
307 mUseElapsedTimeAfterWait = useElapsedTime;
308 LOG_COUNTER_EVENT( "mUpdateRenderRunCount: %d, mUseElapsedTimeAfterWait: %d", mUpdateRenderRunCount, mUseElapsedTimeAfterWait );
309 mUpdateRenderThreadWaitCondition.Notify( lock );
312 void CombinedUpdateRenderController::PauseUpdateRenderThread()
314 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
315 mUpdateRenderRunCount = 0;
318 void CombinedUpdateRenderController::StopUpdateRenderThread()
320 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
321 mDestroyUpdateRenderThread = TRUE;
322 mUpdateRenderThreadWaitCondition.Notify( lock );
325 bool CombinedUpdateRenderController::IsUpdateRenderThreadPaused()
327 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
328 return ( mUpdateRenderRunCount != CONTINUOUS ) || // Report paused if NOT continuously running
329 mUpdateRenderThreadCanSleep; // Report paused if sleeping
332 void CombinedUpdateRenderController::ProcessSleepRequest()
336 // Decrement Update request count
337 if( mUpdateRequestCount > 0 )
339 --mUpdateRequestCount;
342 // Can sleep if our update-request count is 0
343 // Update/Render thread can choose to carry on updating if it determines more update/renders are required
344 if( mUpdateRequestCount == 0 )
346 LOG_EVENT( "Going to sleep" );
348 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
349 mUpdateRenderThreadCanSleep = TRUE;
353 ///////////////////////////////////////////////////////////////////////////////////////////////////
354 // UPDATE/RENDER THREAD
355 ///////////////////////////////////////////////////////////////////////////////////////////////////
357 void CombinedUpdateRenderController::UpdateRenderThread()
359 // Install a function for logging
360 mEnvironmentOptions.InstallLogFunction();
362 LOG_UPDATE_RENDER( "THREAD CREATED" );
364 mRenderHelper.InitializeEgl();
366 // tell core it has a context
367 mCore.ContextCreated();
369 NotifyThreadInitialised();
372 uint64_t lastFrameTime;
373 TimeService::GetNanoseconds( lastFrameTime );
375 LOG_UPDATE_RENDER( "THREAD INITIALISED" );
377 bool useElapsedTime = true;
378 bool updateRequired = true;
379 uint64_t timeToSleepUntil = 0;
380 int extraFramesDropped = 0;
382 while( UpdateRenderReady( useElapsedTime, updateRequired, timeToSleepUntil ) )
384 LOG_UPDATE_RENDER_TRACE;
386 // Performance statistics are logged upon a VSYNC tick so use this point for a VSync marker
387 AddPerformanceMarker( PerformanceInterface::VSYNC );
389 uint64_t currentFrameStartTime = 0;
390 TimeService::GetNanoseconds( currentFrameStartTime );
392 const uint64_t timeSinceLastFrame = currentFrameStartTime - lastFrameTime;
394 // Optional FPS Tracking when continuously rendering
395 if( useElapsedTime && mFpsTracker.Enabled() )
397 float absoluteTimeSinceLastRender = timeSinceLastFrame * NANOSECONDS_TO_SECOND;
398 mFpsTracker.Track( absoluteTimeSinceLastRender );
401 lastFrameTime = currentFrameStartTime; // Store frame start time
403 //////////////////////////////
405 //////////////////////////////
407 RenderSurface* newSurface = ShouldSurfaceBeReplaced();
408 if( DALI_UNLIKELY( newSurface ) )
410 LOG_UPDATE_RENDER_TRACE_FMT( "Replacing Surface" );
411 mRenderHelper.ReplaceSurface( newSurface );
415 //////////////////////////////
417 //////////////////////////////
419 const unsigned int currentTime = currentFrameStartTime / NANOSECONDS_PER_MILLISECOND;
420 const unsigned int nextFrameTime = currentTime + mDefaultFrameDurationMilliseconds;
422 uint64_t noOfFramesSinceLastUpdate = 1;
423 float frameDelta = 0.0f;
426 // If using the elapsed time, then calculate frameDelta as a multiple of mDefaultFrameDelta
427 noOfFramesSinceLastUpdate += extraFramesDropped;
429 frameDelta = mDefaultFrameDelta * noOfFramesSinceLastUpdate;
431 LOG_UPDATE_RENDER( "timeSinceLastFrame(%llu) noOfFramesSinceLastUpdate(%u) frameDelta(%.6f)", timeSinceLastFrame, noOfFramesSinceLastUpdate, frameDelta );
433 Integration::UpdateStatus updateStatus;
435 AddPerformanceMarker( PerformanceInterface::UPDATE_START );
436 mCore.Update( frameDelta, currentTime, nextFrameTime, updateStatus );
437 AddPerformanceMarker( PerformanceInterface::UPDATE_END );
439 unsigned int keepUpdatingStatus = updateStatus.KeepUpdating();
441 // Tell the event-thread to wake up (if asleep) and send a notification event to Core if required
442 if( updateStatus.NeedsNotification() )
444 mNotificationTrigger.Trigger();
445 LOG_UPDATE_RENDER( "Notification Triggered" );
448 // Optional logging of update/render status
449 mUpdateStatusLogger.Log( keepUpdatingStatus );
451 //////////////////////////////
453 //////////////////////////////
455 mRenderHelper.ConsumeEvents();
456 mRenderHelper.PreRender();
458 Integration::RenderStatus renderStatus;
460 AddPerformanceMarker( PerformanceInterface::RENDER_START );
461 mCore.Render( renderStatus );
462 AddPerformanceMarker( PerformanceInterface::RENDER_END );
464 if( renderStatus.NeedsPostRender() )
466 mRenderHelper.PostRender();
469 // Trigger event thread to request Update/Render thread to sleep if update not required
470 if( ( Integration::KeepUpdating::NOT_REQUESTED == keepUpdatingStatus ) &&
471 ! renderStatus.NeedsUpdate() )
473 mSleepTrigger->Trigger();
474 updateRequired = false;
475 LOG_UPDATE_RENDER( "Sleep Triggered" );
479 updateRequired = true;
482 //////////////////////////////
484 //////////////////////////////
486 extraFramesDropped = 0;
488 if (timeToSleepUntil == 0)
490 // If this is the first frame after the thread is initialized or resumed, we
491 // use the actual time the current frame starts from to calculate the time to
492 // sleep until the next frame.
493 timeToSleepUntil = currentFrameStartTime + mDefaultFrameDurationNanoseconds;
497 // Otherwise, always use the sleep-until time calculated in the last frame to
498 // calculate the time to sleep until the next frame. In this way, if there is
499 // any time gap between the current frame and the next frame, or if update or
500 // rendering in the current frame takes too much time so that the specified
501 // sleep-until time has already passed, it will try to keep the frames syncing
502 // by shortening the duration of the next frame.
503 timeToSleepUntil += mDefaultFrameDurationNanoseconds;
505 // Check the current time at the end of the frame
506 uint64_t currentFrameEndTime = 0;
507 TimeService::GetNanoseconds( currentFrameEndTime );
508 while ( currentFrameEndTime > timeToSleepUntil + mDefaultFrameDurationNanoseconds )
510 // We are more than one frame behind already, so just drop the next frames
511 // until the sleep-until time is later than the current time so that we can
513 timeToSleepUntil += mDefaultFrameDurationNanoseconds;
514 extraFramesDropped++;
518 // Sleep until at least the the default frame duration has elapsed. This will return immediately if the specified end-time has already passed.
519 TimeService::SleepUntil( timeToSleepUntil );
522 // Inform core of context destruction & shutdown EGL
523 mCore.ContextDestroyed();
524 mRenderHelper.ShutdownEgl();
526 LOG_UPDATE_RENDER( "THREAD DESTROYED" );
528 // Uninstall the logging function
529 mEnvironmentOptions.UnInstallLogFunction();
532 bool CombinedUpdateRenderController::UpdateRenderReady( bool& useElapsedTime, bool updateRequired, uint64_t& timeToSleepUntil )
534 useElapsedTime = true;
536 ConditionalWait::ScopedLock updateLock( mUpdateRenderThreadWaitCondition );
537 while( ( ! mUpdateRenderRunCount || // Should try to wait if event-thread has paused the Update/Render thread
538 ( mUpdateRenderThreadCanSleep && ! updateRequired && ! mPendingRequestUpdate ) ) && // Ensure we wait if we're supposed to be sleeping AND do not require another update
539 ! mDestroyUpdateRenderThread && // Ensure we don't wait if the update-render-thread is supposed to be destroyed
540 ! mNewSurface ) // Ensure we don't wait if we need to replace the surface
542 LOG_UPDATE_RENDER( "WAIT: mUpdateRenderRunCount: %d", mUpdateRenderRunCount );
543 LOG_UPDATE_RENDER( " mUpdateRenderThreadCanSleep: %d, updateRequired: %d, mPendingRequestUpdate: %d", mUpdateRenderThreadCanSleep, updateRequired, mPendingRequestUpdate );
544 LOG_UPDATE_RENDER( " mDestroyUpdateRenderThread: %d", mDestroyUpdateRenderThread );
545 LOG_UPDATE_RENDER( " mNewSurface: %d", mNewSurface );
547 // Reset the time when the thread is waiting, so the sleep-until time for
548 // the first frame after resuming should be based on the actual start time
549 // of the first frame.
550 timeToSleepUntil = 0;
552 mUpdateRenderThreadWaitCondition.Wait( updateLock );
554 if( ! mUseElapsedTimeAfterWait )
556 useElapsedTime = false;
560 LOG_COUNTER_UPDATE_RENDER( "mUpdateRenderRunCount: %d", mUpdateRenderRunCount );
561 LOG_COUNTER_UPDATE_RENDER( "mUpdateRenderThreadCanSleep: %d, updateRequired: %d, mPendingRequestUpdate: %d", mUpdateRenderThreadCanSleep, updateRequired, mPendingRequestUpdate );
562 LOG_COUNTER_UPDATE_RENDER( "mDestroyUpdateRenderThread: %d", mDestroyUpdateRenderThread );
563 LOG_COUNTER_UPDATE_RENDER( "mNewSurface: %d", mNewSurface );
565 mUseElapsedTimeAfterWait = FALSE;
566 mUpdateRenderThreadCanSleep = FALSE;
567 mPendingRequestUpdate = FALSE;
569 // If we've been asked to run Update/Render cycles a finite number of times then decrement so we wait after the
570 // requested number of cycles
571 if( mUpdateRenderRunCount > 0 )
573 --mUpdateRenderRunCount;
576 // Keep the update-render thread alive if this thread is NOT to be destroyed
577 return ! mDestroyUpdateRenderThread;
580 RenderSurface* CombinedUpdateRenderController::ShouldSurfaceBeReplaced()
582 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
584 RenderSurface* newSurface = mNewSurface;
590 void CombinedUpdateRenderController::SurfaceReplaced()
592 // Just increment the semaphore
593 sem_post( &mEventThreadSemaphore );
596 ///////////////////////////////////////////////////////////////////////////////////////////////////
598 ///////////////////////////////////////////////////////////////////////////////////////////////////
600 void CombinedUpdateRenderController::NotifyThreadInitialised()
602 // Just increment the semaphore
603 sem_post( &mEventThreadSemaphore );
606 void CombinedUpdateRenderController::AddPerformanceMarker( PerformanceInterface::MarkerType type )
608 if( mPerformanceInterface )
610 mPerformanceInterface->AddMarker( type );
614 /////////////////////////////////////////////////////////////////////////////////////////////////
615 // POST RENDERING: EVENT THREAD
616 /////////////////////////////////////////////////////////////////////////////////////////////////
618 void CombinedUpdateRenderController::PostRenderComplete()
620 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
621 mPostRendering = FALSE;
622 mUpdateRenderThreadWaitCondition.Notify( lock );
625 ///////////////////////////////////////////////////////////////////////////////////////////////////
626 // POST RENDERING: RENDER THREAD
627 ///////////////////////////////////////////////////////////////////////////////////////////////////
629 void CombinedUpdateRenderController::PostRenderStarted()
631 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
632 mPostRendering = TRUE;
635 void CombinedUpdateRenderController::PostRenderWaitForCompletion()
637 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
638 while( mPostRendering &&
639 ! mNewSurface && // We should NOT wait if we're replacing the surface
640 ! mDestroyUpdateRenderThread )
642 mUpdateRenderThreadWaitCondition.Wait( lock );
646 } // namespace Adaptor
648 } // namespace Internal