-//
-// Copyright (c) 2014 Samsung Electronics Co., Ltd.
-//
-// Licensed under the Flora License, Version 1.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://floralicense.org/license/
-//
-// 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.
-//
+/*
+ * 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 "render-thread.h"
+// EXTERNAL INCLUDES
+#include <dali/integration-api/debug.h>
// INTERNAL INCLUDES
-#include <dali/integration-api/debug.h>
#include <base/interfaces/adaptor-internal-services.h>
-#include <base/update-render-synchronization.h>
-#include <base/log-options.h>
-
+#include <base/thread-synchronization.h>
+#include <base/environment-options.h>
+#include <base/display-connection.h>
namespace Dali
{
namespace
{
+#if defined(DEBUG_ENABLED)
+Integration::Log::Filter* gRenderLogFilter = Integration::Log::Filter::New(Debug::NoLogging, false, "LOG_RENDER_THREAD");
+#endif
+}
-const unsigned int TIME_PER_FRAME_IN_MICROSECONDS = 16667;
+RenderRequest::RenderRequest(RenderRequest::Request type)
+: mRequestType(type)
+{
+}
-} // unnamed namespace
+RenderRequest::Request RenderRequest::GetType()
+{
+ return mRequestType;
+}
+
+ReplaceSurfaceRequest::ReplaceSurfaceRequest()
+: RenderRequest(RenderRequest::REPLACE_SURFACE),
+ mNewSurface( NULL ),
+ mReplaceCompleted(false)
+{
+}
-RenderThread::RenderThread( UpdateRenderSynchronization& sync,
+void ReplaceSurfaceRequest::SetSurface(RenderSurface* newSurface)
+{
+ mNewSurface = newSurface;
+}
+
+RenderSurface* ReplaceSurfaceRequest::GetSurface()
+{
+ return mNewSurface;
+}
+
+void ReplaceSurfaceRequest::ReplaceCompleted()
+{
+ mReplaceCompleted = true;
+}
+
+bool ReplaceSurfaceRequest::GetReplaceCompleted()
+{
+ return mReplaceCompleted != 0u;
+}
+
+
+RenderThread::RenderThread( ThreadSynchronization& sync,
AdaptorInternalServices& adaptorInterfaces,
- const LogOptions& logOptions )
-: mUpdateRenderSync( sync ),
+ const EnvironmentOptions& environmentOptions )
+: mThreadSynchronization( sync ),
mCore( adaptorInterfaces.GetCore() ),
mGLES( adaptorInterfaces.GetGlesInterface() ),
mEglFactory( &adaptorInterfaces.GetEGLFactoryInterface()),
mEGL( NULL ),
mThread( NULL ),
- mSurfaceReplacing( false ),
- mNewDataAvailable( false ),
- mSurfaceReplaceCompleted( false ),
- mLogOptions( logOptions )
+ mEnvironmentOptions( environmentOptions ),
+ mSurfaceReplaced(false)
{
// set the initial values before render thread starts
- mCurrent.surface = adaptorInterfaces.GetRenderSurfaceInterface();
+ mSurface = adaptorInterfaces.GetRenderSurfaceInterface();
+
+ mDisplayConnection = Dali::DisplayConnection::New();
}
RenderThread::~RenderThread()
{
+ if (mDisplayConnection)
+ {
+ delete mDisplayConnection;
+ mDisplayConnection = NULL;
+ }
+
DALI_ASSERT_ALWAYS( mThread == NULL && "RenderThread is still alive");
mEglFactory->Destroy();
}
void RenderThread::Start()
{
+ DALI_LOG_INFO( gRenderLogFilter, Debug::Verbose, "RenderThread::Start()\n");
+
// initialise GL and kick off render thread
DALI_ASSERT_ALWAYS( !mEGL && "Egl already initialized" );
- // Tell core what the minimum frame interval is
- mCore.SetMinimumFrameTimeInterval( mCurrent.syncMode * TIME_PER_FRAME_IN_MICROSECONDS );
-
// create the render thread, initially we are rendering
- mThread = new boost::thread(boost::bind(&RenderThread::Run, this));
-
- // Inform surface to block waiting for RenderSync
- mCurrent.surface->SetSyncMode( RenderSurface::SYNC_MODE_WAIT );
- }
+ mThread = new pthread_t();
+ int error = pthread_create( mThread, NULL, InternalThreadEntryFunc, this );
+ DALI_ASSERT_ALWAYS( !error && "Return code from pthread_create() in RenderThread" );
-void RenderThread::Stop()
-{
- // shutdown the render thread and destroy the opengl context
- if( mThread )
+ if( mSurface )
{
- // Tell surface we have stopped rendering
- mCurrent.surface->StopRender();
-
- // wait for the thread to finish
- mThread->join();
-
- delete mThread;
- mThread = NULL;
+ mSurface->StartRender();
}
}
-void RenderThread::ReplaceSurface( RenderSurface* surface )
+void RenderThread::Stop()
{
- // Make sure it's a new surface. Note! we are reading the current value of render thread here, but reading is ok
- DALI_ASSERT_ALWAYS( surface != mCurrent.surface && "Trying to replace surface with itself" );
+ DALI_LOG_INFO( gRenderLogFilter, Debug::Verbose, "RenderThread::Stop()\n");
- // lock and set to false
+ if( mSurface )
{
- boost::unique_lock<boost::mutex> lock( mSurfaceChangedMutex );
- mSurfaceReplaceCompleted = false;
- }
+ // Tell surface we have stopped rendering
+ mSurface->StopRender();
- // lock cache and set update flag at the end of function
- {
- SendMessageGuard msg( *this );
- // set new values to cache
- mNewValues.replaceSurface = true;
- mNewValues.surface = surface;
+ // The surface will be destroyed soon; this pointer will become invalid
+ mSurface = NULL;
}
- /*
- * Reset the mPixmapFlushed condition if surface was changed.
- * : in this case, client can not handle the previous damage because surface was changed.
- */
- RenderSync();
-}
-
-void RenderThread::WaitForSurfaceReplaceComplete()
-{
- boost::unique_lock<boost::mutex> lock( mSurfaceChangedMutex );
-
- // if already completed no need to wait
- while( !mSurfaceReplaceCompleted )
+ // shutdown the render thread and destroy the opengl context
+ if( mThread )
{
- mSurfaceChangedNotify.wait( lock ); // Block the main thread here and releases mSurfaceChangedMutex so the render-thread can notify us
- }
-}
-
-void RenderThread::SetVSyncMode( EglInterface::SyncMode syncMode )
-{
- // lock cache and set update flag at the end of function
- SendMessageGuard msg( *this );
- // set new values to cache
- mNewValues.syncMode = syncMode;
-}
+ // wait for the thread to finish
+ pthread_join(*mThread, NULL);
-void RenderThread::RenderSync()
-{
- mCurrent.surface->RenderSync();
+ delete mThread;
+ mThread = NULL;
+ }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool RenderThread::Run()
{
- // install a function for logging
- mLogOptions.InstallLogFunction();
-
- InitializeEgl();
+ DALI_LOG_INFO( gRenderLogFilter, Debug::Verbose, "RenderThread::Run\n");
- bool running( true );
+ // Install a function for logging
+ mEnvironmentOptions.InstallLogFunction();
- // Wait for first update
- mUpdateRenderSync.RenderSyncWithUpdate();
+ InitializeEgl();
Dali::Integration::RenderStatus renderStatus;
+ RenderRequest* request = NULL;
- uint64_t currentTime( 0 );
-
- // render loop, we stay inside here when rendering
- while( running )
+ // Render loop, we stay inside here when rendering
+ while( mThreadSynchronization.RenderReady( request ) )
{
- // Consume any pending events
- ConsumeEvents();
+ DALI_LOG_INFO( gRenderLogFilter, Debug::Verbose, "RenderThread::Run. 1 - RenderReady\n");
- // Check if we've got updates from the main thread
- CheckForUpdates();
+ // Consume any pending events to avoid memory leaks
+ DALI_LOG_INFO( gRenderLogFilter, Debug::Verbose, "RenderThread::Run. 2 - ConsumeEvents\n");
+ mDisplayConnection->ConsumeEvents();
- // perform any pre-render operations
- if(PreRender() == true)
+ // Check if we've got a request from the main thread (e.g. replace surface)
+ if( request )
{
- // Render
- mCore.Render( renderStatus );
-
- // Notify the update-thread that a render has completed
- mUpdateRenderSync.RenderFinished( renderStatus.NeedsUpdate() );
-
- uint64_t newTime( mUpdateRenderSync.GetTimeMicroseconds() );
-
- // perform any post-render operations
- if ( renderStatus.HasRendered() )
- {
- PostRender( static_cast< unsigned int >(newTime - currentTime) );
- }
-
- if(mSurfaceReplacing)
+ // Process the request, we should NOT render when we have a request
+ DALI_LOG_INFO( gRenderLogFilter, Debug::Verbose, "RenderThread::Run. 3 - Process requests\n");
+ ProcessRequest( request );
+ }
+ else
+ {
+ // No request to process so we render
+ if( PreRender() ) // Returns false if no surface onto which to render
{
- // Notify main thread that surface was changed so it can release the old one
- NotifySurfaceChangeCompleted();
- mSurfaceReplacing = false;
+ // Render
+ DALI_LOG_INFO( gRenderLogFilter, Debug::Verbose, "RenderThread::Run. 3 - Core.Render()\n");
+
+ mThreadSynchronization.AddPerformanceMarker( PerformanceInterface::RENDER_START );
+ mCore.Render( renderStatus );
+ mThreadSynchronization.AddPerformanceMarker( PerformanceInterface::RENDER_END );
+
+ // Perform any post-render operations
+ if ( renderStatus.HasRendered() )
+ {
+ DALI_LOG_INFO( gRenderLogFilter, Debug::Verbose, "RenderThread::Run. 4 - PostRender()\n");
+ PostRender();
+ }
}
-
- currentTime = newTime;
}
- // Wait until another frame has been updated
- running = mUpdateRenderSync.RenderSyncWithUpdate();
+ request = NULL; // Clear the request if it was set, no need to release memory
}
- // shut down egl
+ // Shut down EGL
ShutdownEgl();
- // install a function for logging
- mLogOptions.UnInstallLogFunction();
+ // Uninstall the logging function
+ mEnvironmentOptions.UnInstallLogFunction();
return true;
}
{
mEGL = mEglFactory->Create();
- DALI_ASSERT_ALWAYS( mCurrent.surface && "NULL surface" );
+ DALI_ASSERT_ALWAYS( mSurface && "NULL surface" );
// initialize egl & OpenGL
- mCurrent.surface->InitializeEgl( *mEGL );
+ mDisplayConnection->InitializeEgl( *mEGL );
+ mSurface->InitializeEgl( *mEGL );
// create the OpenGL context
mEGL->CreateContext();
// create the OpenGL surface
- mCurrent.surface->CreateEglSurface( *mEGL );
+ mSurface->CreateEglSurface(*mEGL);
// Make it current
mEGL->MakeContextCurrent();
// set the initial sync mode
- mEGL->SetRefreshSync( mCurrent.syncMode );
// tell core it has a context
mCore.ContextCreated();
- DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "*** GL_VENDOR : %s ***\n", mGLES.GetString(GL_VENDOR));
- DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "*** GL_RENDERER : %s ***\n", mGLES.GetString(GL_RENDERER));
- DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "*** GL_VERSION : %s ***\n", mGLES.GetString(GL_VERSION));
- DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "*** GL_SHADING_LANGUAGE_VERSION : %s***\n", mGLES.GetString(GL_SHADING_LANGUAGE_VERSION));
- DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "*** Supported Extensions ***\n%s\n\n", mGLES.GetString(GL_EXTENSIONS));
}
-void RenderThread::ConsumeEvents()
+void RenderThread::ProcessRequest( RenderRequest* request )
{
- // tell surface to consume any events to avoid memory leaks
- mCurrent.surface->ConsumeEvents();
-}
-
-void RenderThread::CheckForUpdates()
-{
- // atomic check to see if we've got updates, resets the flag int
- if( __sync_fetch_and_and( &mNewDataAvailable, 0 ) )
+ if( request != NULL )
{
- // scope for lock
- // NOTE! This block is the only place in render side where mNewValues can be used inside render thread !!!
+ switch(request->GetType())
{
- // need to lock to access new values
- boost::unique_lock< boost::mutex > lock( mThreadDataLock );
-
- // did the sync mode change
- if( mCurrent.syncMode != mNewValues.syncMode )
- {
- mCurrent.syncMode = mNewValues.syncMode;
- mEGL->SetRefreshSync( mCurrent.syncMode );
- }
-
- // check if the surface needs replacing
- if( mNewValues.replaceSurface )
+ case RenderRequest::REPLACE_SURFACE:
{
- mNewValues.replaceSurface = false; // reset the flag
// change the surface
- ChangeSurface( mNewValues.surface );
- mNewValues.surface = NULL;
+ ReplaceSurfaceRequest* replaceSurfaceRequest = static_cast<ReplaceSurfaceRequest*>(request);
+ ReplaceSurface( replaceSurfaceRequest->GetSurface() );
+ replaceSurfaceRequest->ReplaceCompleted();
+ mThreadSynchronization.RenderInformSurfaceReplaced();
+ break;
}
}
}
}
-void RenderThread::ChangeSurface( RenderSurface* newSurface )
+void RenderThread::ReplaceSurface( RenderSurface* newSurface )
{
// This is designed for replacing pixmap surfaces, but should work for window as well
// we need to delete the egl surface and renderable (pixmap / window)
// Then create a new pixmap/window and new egl surface
// If the new surface has a different display connection, then the context will be lost
- DALI_ASSERT_ALWAYS( newSurface && "NULL surface" )
- bool contextLost = newSurface->ReplaceEGLSurface( *mEGL );
+ DALI_ASSERT_ALWAYS(newSurface && "NULL surface");
- if( contextLost )
- {
- DALI_LOG_WARNING("Context lost\n");
- mCore.ContextToBeDestroyed();
- mCore.ContextCreated();
- }
+ mDisplayConnection->InitializeEgl(*mEGL);
- // if both new and old surface are using the same display, and the display
- // connection was created by Dali, then transfer
- // display owner ship to the new surface.
- mCurrent.surface->TransferDisplayOwner( *newSurface );
+ newSurface->ReplaceEGLSurface(*mEGL);
// use the new surface from now on
- mCurrent.surface = newSurface;
-
- // after rendering, NotifySurfaceChangeCompleted will be called
- mSurfaceReplacing = true;
-}
-
-void RenderThread::NotifySurfaceChangeCompleted()
-{
- {
- boost::unique_lock< boost::mutex > lock( mSurfaceChangedMutex );
- mSurfaceReplaceCompleted = true;
- }
- // notify main thread
- mSurfaceChangedNotify.notify_all();
+ mSurface = newSurface;
+ mSurfaceReplaced = true;
}
void RenderThread::ShutdownEgl()
{
- // inform core the context is about to be destroyed,
- mCore.ContextToBeDestroyed();
+ // inform core of context destruction
+ mCore.ContextDestroyed();
- // give a chance to destroy the OpenGL surface that created externally
- mCurrent.surface->DestroyEglSurface( *mEGL );
+ if( mSurface )
+ {
+ // give a chance to destroy the OpenGL surface that created externally
+ mSurface->DestroyEglSurface( *mEGL );
+ }
// delete the GL context / egl surface
mEGL->TerminateGles();
bool RenderThread::PreRender()
{
- return mCurrent.surface->PreRender( *mEGL, mGLES );
+ bool success( false );
+ if( mSurface )
+ {
+ success = mSurface->PreRender( *mEGL, mGLES );
+ }
+
+ if( success )
+ {
+ mGLES.PreRender();
+ }
+ return success;
}
-void RenderThread::PostRender( unsigned int timeDelta )
+void RenderThread::PostRender()
{
- mCurrent.surface->PostRender( *mEGL, mGLES, timeDelta,
- mSurfaceReplacing ? RenderSurface::SYNC_MODE_NONE : RenderSurface::SYNC_MODE_WAIT );
+ // Inform the gl implementation that rendering has finished before informing the surface
+ mGLES.PostRender();
+
+ if( mSurface )
+ {
+ // Inform the surface that rendering this frame has finished.
+ mSurface->PostRender( *mEGL, mGLES, mDisplayConnection, mSurfaceReplaced );
+ }
+ mSurfaceReplaced = false;
}
} // namespace Adaptor