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 mRenderHelper.PostRender();
466 // Trigger event thread to request Update/Render thread to sleep if update not required
467 if( ( Integration::KeepUpdating::NOT_REQUESTED == keepUpdatingStatus ) &&
468 ! renderStatus.NeedsUpdate() )
470 mSleepTrigger->Trigger();
471 updateRequired = false;
472 LOG_UPDATE_RENDER( "Sleep Triggered" );
476 updateRequired = true;
479 //////////////////////////////
481 //////////////////////////////
483 extraFramesDropped = 0;
485 if (timeToSleepUntil == 0)
487 // If this is the first frame after the thread is initialized or resumed, we
488 // use the actual time the current frame starts from to calculate the time to
489 // sleep until the next frame.
490 timeToSleepUntil = currentFrameStartTime + mDefaultFrameDurationNanoseconds;
494 // Otherwise, always use the sleep-until time calculated in the last frame to
495 // calculate the time to sleep until the next frame. In this way, if there is
496 // any time gap between the current frame and the next frame, or if update or
497 // rendering in the current frame takes too much time so that the specified
498 // sleep-until time has already passed, it will try to keep the frames syncing
499 // by shortening the duration of the next frame.
500 timeToSleepUntil += mDefaultFrameDurationNanoseconds;
502 // Check the current time at the end of the frame
503 uint64_t currentFrameEndTime = 0;
504 TimeService::GetNanoseconds( currentFrameEndTime );
505 while ( currentFrameEndTime > timeToSleepUntil + mDefaultFrameDurationNanoseconds )
507 // We are more than one frame behind already, so just drop the next frames
508 // until the sleep-until time is later than the current time so that we can
510 timeToSleepUntil += mDefaultFrameDurationNanoseconds;
511 extraFramesDropped++;
515 // Sleep until at least the the default frame duration has elapsed. This will return immediately if the specified end-time has already passed.
516 TimeService::SleepUntil( timeToSleepUntil );
519 // Inform core of context destruction & shutdown EGL
520 mCore.ContextDestroyed();
521 mRenderHelper.ShutdownEgl();
523 LOG_UPDATE_RENDER( "THREAD DESTROYED" );
525 // Uninstall the logging function
526 mEnvironmentOptions.UnInstallLogFunction();
529 bool CombinedUpdateRenderController::UpdateRenderReady( bool& useElapsedTime, bool updateRequired, uint64_t& timeToSleepUntil )
531 useElapsedTime = true;
533 ConditionalWait::ScopedLock updateLock( mUpdateRenderThreadWaitCondition );
534 while( ( ! mUpdateRenderRunCount || // Should try to wait if event-thread has paused the Update/Render thread
535 ( mUpdateRenderThreadCanSleep && ! updateRequired && ! mPendingRequestUpdate ) ) && // Ensure we wait if we're supposed to be sleeping AND do not require another update
536 ! mDestroyUpdateRenderThread && // Ensure we don't wait if the update-render-thread is supposed to be destroyed
537 ! mNewSurface ) // Ensure we don't wait if we need to replace the surface
539 LOG_UPDATE_RENDER( "WAIT: mUpdateRenderRunCount: %d", mUpdateRenderRunCount );
540 LOG_UPDATE_RENDER( " mUpdateRenderThreadCanSleep: %d, updateRequired: %d, mPendingRequestUpdate: %d", mUpdateRenderThreadCanSleep, updateRequired, mPendingRequestUpdate );
541 LOG_UPDATE_RENDER( " mDestroyUpdateRenderThread: %d", mDestroyUpdateRenderThread );
542 LOG_UPDATE_RENDER( " mNewSurface: %d", mNewSurface );
544 // Reset the time when the thread is waiting, so the sleep-until time for
545 // the first frame after resuming should be based on the actual start time
546 // of the first frame.
547 timeToSleepUntil = 0;
549 mUpdateRenderThreadWaitCondition.Wait( updateLock );
551 if( ! mUseElapsedTimeAfterWait )
553 useElapsedTime = false;
557 LOG_COUNTER_UPDATE_RENDER( "mUpdateRenderRunCount: %d", mUpdateRenderRunCount );
558 LOG_COUNTER_UPDATE_RENDER( "mUpdateRenderThreadCanSleep: %d, updateRequired: %d, mPendingRequestUpdate: %d", mUpdateRenderThreadCanSleep, updateRequired, mPendingRequestUpdate );
559 LOG_COUNTER_UPDATE_RENDER( "mDestroyUpdateRenderThread: %d", mDestroyUpdateRenderThread );
560 LOG_COUNTER_UPDATE_RENDER( "mNewSurface: %d", mNewSurface );
562 mUseElapsedTimeAfterWait = FALSE;
563 mUpdateRenderThreadCanSleep = FALSE;
564 mPendingRequestUpdate = FALSE;
566 // If we've been asked to run Update/Render cycles a finite number of times then decrement so we wait after the
567 // requested number of cycles
568 if( mUpdateRenderRunCount > 0 )
570 --mUpdateRenderRunCount;
573 // Keep the update-render thread alive if this thread is NOT to be destroyed
574 return ! mDestroyUpdateRenderThread;
577 RenderSurface* CombinedUpdateRenderController::ShouldSurfaceBeReplaced()
579 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
581 RenderSurface* newSurface = mNewSurface;
587 void CombinedUpdateRenderController::SurfaceReplaced()
589 // Just increment the semaphore
590 sem_post( &mEventThreadSemaphore );
593 ///////////////////////////////////////////////////////////////////////////////////////////////////
595 ///////////////////////////////////////////////////////////////////////////////////////////////////
597 void CombinedUpdateRenderController::NotifyThreadInitialised()
599 // Just increment the semaphore
600 sem_post( &mEventThreadSemaphore );
603 void CombinedUpdateRenderController::AddPerformanceMarker( PerformanceInterface::MarkerType type )
605 if( mPerformanceInterface )
607 mPerformanceInterface->AddMarker( type );
611 /////////////////////////////////////////////////////////////////////////////////////////////////
612 // POST RENDERING: EVENT THREAD
613 /////////////////////////////////////////////////////////////////////////////////////////////////
615 void CombinedUpdateRenderController::PostRenderComplete()
617 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
618 mPostRendering = FALSE;
619 mUpdateRenderThreadWaitCondition.Notify( lock );
622 ///////////////////////////////////////////////////////////////////////////////////////////////////
623 // POST RENDERING: RENDER THREAD
624 ///////////////////////////////////////////////////////////////////////////////////////////////////
626 void CombinedUpdateRenderController::PostRenderStarted()
628 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
629 mPostRendering = TRUE;
632 void CombinedUpdateRenderController::PostRenderWaitForCompletion()
634 ConditionalWait::ScopedLock lock( mUpdateRenderThreadWaitCondition );
635 while( mPostRendering &&
636 ! mNewSurface && // We should NOT wait if we're replacing the surface
637 ! mDestroyUpdateRenderThread )
639 mUpdateRenderThreadWaitCondition.Wait( lock );
643 } // namespace Adaptor
645 } // namespace Internal