// // Notice Regarding Standards. AMD does not provide a license or sublicense to // any Intellectual Property Rights relating to any standards, including but not // limited to any audio and/or video codec technologies such as MPEG-2, MPEG-4; // AVC/H.264; HEVC/H.265; AAC decode/FFMPEG; AAC encode/FFMPEG; VC-1; and MP3 // (collectively, the "Media Technologies"). For clarity, you will pay any // royalties due for such third party technologies, which may include the Media // Technologies that are owed as a result of AMD providing the Software to you. // // MIT license // // Copyright (c) 2018 Advanced Micro Devices, Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #ifndef AMF_Thread_h #define AMF_Thread_h #pragma once #include #include #include #include "../include/core/Platform.h" #ifndef _WIN32 #include #endif extern "C" { // threads #define AMF_INFINITE (0xFFFFFFFF) // Infinite ulTimeout // threads: atomic amf_long AMF_CDECL_CALL amf_atomic_inc(amf_long* X); amf_long AMF_CDECL_CALL amf_atomic_dec(amf_long* X); // threads: critical section amf_handle AMF_CDECL_CALL amf_create_critical_section(); bool AMF_CDECL_CALL amf_delete_critical_section(amf_handle cs); bool AMF_CDECL_CALL amf_enter_critical_section(amf_handle cs); bool AMF_CDECL_CALL amf_wait_critical_section(amf_handle cs, amf_ulong ulTimeout); bool AMF_CDECL_CALL amf_leave_critical_section(amf_handle cs); // threads: event amf_handle AMF_CDECL_CALL amf_create_event(bool bInitiallyOwned, bool bManualReset, const wchar_t* pName); bool AMF_CDECL_CALL amf_delete_event(amf_handle hevent); bool AMF_CDECL_CALL amf_set_event(amf_handle hevent); bool AMF_CDECL_CALL amf_reset_event(amf_handle hevent); bool AMF_CDECL_CALL amf_wait_for_event(amf_handle hevent, amf_ulong ulTimeout); bool AMF_CDECL_CALL amf_wait_for_event_timeout(amf_handle hevent, amf_ulong ulTimeout); // threads: mutex amf_handle AMF_CDECL_CALL amf_create_mutex(bool bInitiallyOwned, const wchar_t* pName); #if defined(_WIN32) amf_handle AMF_CDECL_CALL amf_open_mutex(const wchar_t* pName); #endif bool AMF_CDECL_CALL amf_delete_mutex(amf_handle hmutex); bool AMF_CDECL_CALL amf_wait_for_mutex(amf_handle hmutex, amf_ulong ulTimeout); bool AMF_CDECL_CALL amf_release_mutex(amf_handle hmutex); // threads: semaphore amf_handle AMF_CDECL_CALL amf_create_semaphore(amf_long iInitCount, amf_long iMaxCount, const wchar_t* pName); bool AMF_CDECL_CALL amf_delete_semaphore(amf_handle hsemaphore); bool AMF_CDECL_CALL amf_wait_for_semaphore(amf_handle hsemaphore, amf_ulong ulTimeout); bool AMF_CDECL_CALL amf_release_semaphore(amf_handle hsemaphore, amf_long iCount, amf_long* iOldCount); // threads: delay void AMF_CDECL_CALL amf_sleep(amf_ulong delay); amf_pts AMF_CDECL_CALL amf_high_precision_clock(); // in 100 of nanosec void AMF_CDECL_CALL amf_increase_timer_precision(); void AMF_CDECL_CALL amf_restore_timer_precision(); amf_handle AMF_CDECL_CALL amf_load_library(const wchar_t* filename); amf_handle AMF_CDECL_CALL amf_load_library1(const wchar_t* filename, bool bGlobal); void* AMF_CDECL_CALL amf_get_proc_address(amf_handle module, const char* procName); int AMF_CDECL_CALL amf_free_library(amf_handle module); #if defined(__APPLE__) amf_uint64 AMF_STD_CALL get_current_thread_id(); #else amf_uint32 AMF_STD_CALL get_current_thread_id(); #endif #if !defined(METRO_APP) // virtual memory void* AMF_CDECL_CALL amf_virtual_alloc(amf_size size); void AMF_CDECL_CALL amf_virtual_free(void* ptr); #else #define amf_virtual_alloc amf_alloc #define amf_virtual_free amf_free #endif // cpu #if defined(_WIN32) || (__linux__) amf_int32 AMF_STD_CALL amf_get_cpu_cores(); #endif } namespace amf { //---------------------------------------------------------------- class AMF_NO_VTABLE AMFSyncBase { public: virtual bool Lock(amf_ulong ulTimeout = AMF_INFINITE) = 0; virtual bool Unlock() = 0; }; //---------------------------------------------------------------- class AMFEvent : public AMFSyncBase { private: amf_handle m_hSyncObject; AMFEvent(const AMFEvent&); AMFEvent& operator=(const AMFEvent&); public: AMFEvent(bool bInitiallyOwned = false, bool bManualReset = false, const wchar_t* pName = NULL); virtual ~AMFEvent(); virtual bool Lock(amf_ulong ulTimeout = AMF_INFINITE); virtual bool LockTimeout(amf_ulong ulTimeout = AMF_INFINITE); virtual bool Unlock(); bool SetEvent(); bool ResetEvent(); amf_handle GetNative() { return m_hSyncObject; } }; //---------------------------------------------------------------- class AMFMutex : public AMFSyncBase { private: amf_handle m_hSyncObject; AMFMutex(const AMFMutex&); AMFMutex& operator=(const AMFMutex&); public: AMFMutex(bool bInitiallyOwned = false, const wchar_t* pName = NULL #if defined(_WIN32) , bool bOpenExistent = false #endif ); virtual ~AMFMutex(); virtual bool Lock(amf_ulong ulTimeout = AMF_INFINITE); virtual bool Unlock(); bool IsValid(); }; //---------------------------------------------------------------- class AMFCriticalSection : public AMFSyncBase { private: amf_handle m_Sect; AMFCriticalSection(const AMFCriticalSection&); AMFCriticalSection& operator=(const AMFCriticalSection&); public: AMFCriticalSection(); virtual ~AMFCriticalSection(); virtual bool Lock(amf_ulong ulTimeout = AMF_INFINITE); virtual bool Unlock(); }; //---------------------------------------------------------------- class AMFSemaphore : public AMFSyncBase { private: amf_handle m_hSemaphore; AMFSemaphore(const AMFSemaphore&); AMFSemaphore& operator=(const AMFSemaphore&); public: AMFSemaphore(amf_long iInitCount, amf_long iMaxCount, const wchar_t* pName = NULL); virtual ~AMFSemaphore(); virtual bool Create(amf_long iInitCount, amf_long iMaxCount, const wchar_t* pName = NULL); virtual bool Lock(amf_ulong ulTimeout = AMF_INFINITE); virtual bool Unlock(); }; //---------------------------------------------------------------- class AMFLock { private: AMFSyncBase* m_pBase; bool m_bLocked; AMFLock(const AMFLock&); AMFLock& operator=(const AMFLock&); public: AMFLock(AMFSyncBase* pBase, amf_ulong ulTimeout = AMF_INFINITE); ~AMFLock(); bool Lock(amf_ulong ulTimeout = AMF_INFINITE); bool Unlock(); bool IsLocked(); }; //---------------------------------------------------------------- class AMFReadWriteSync { private: struct ReadWriteResources { // max threads reading concurrently const int m_maxReadThreads; AMFSemaphore m_readSemaphore; AMFCriticalSection m_writeCriticalSection; ReadWriteResources() : m_maxReadThreads(10), m_readSemaphore(m_maxReadThreads, m_maxReadThreads), m_writeCriticalSection() { } }; class ReadSync : public AMFSyncBase { private: ReadSync(const ReadSync&); ReadSync& operator=(const ReadSync&); ReadWriteResources& m_resources; public: ReadSync(ReadWriteResources& resources) : m_resources(resources) { } virtual bool Lock(amf_ulong ulTimeout = AMF_INFINITE) { return m_resources.m_readSemaphore.Lock(ulTimeout); } virtual bool Unlock() { return m_resources.m_readSemaphore.Unlock(); } }; class WriteSync : public AMFSyncBase { private: WriteSync(const WriteSync&); WriteSync& operator=(const WriteSync&); ReadWriteResources& m_resources; public: WriteSync(ReadWriteResources& resources) : m_resources(resources) { } /// waits passed timeout for other writers; wait readers for infinite virtual bool Lock(amf_ulong ulTimeout = AMF_INFINITE) { if(!m_resources.m_writeCriticalSection.Lock(ulTimeout)) { return false; } for(int i = 0; i < m_resources.m_maxReadThreads; i++) { m_resources.m_readSemaphore.Lock(); } return true; } virtual bool Unlock() { // there is windows function to release N times by one call - could be optimize later for(int i = 0; i < m_resources.m_maxReadThreads; i++) { m_resources.m_readSemaphore.Unlock(); } return m_resources.m_writeCriticalSection.Unlock(); } }; private: ReadWriteResources m_resources; ReadSync m_readSync; WriteSync m_writeSync; public: AMFReadWriteSync() : m_resources(), m_readSync(m_resources), m_writeSync(m_resources) { } AMFSyncBase* GetReadSync() { return &m_readSync; } AMFSyncBase* GetWriteSync() { return &m_writeSync; } }; //---------------------------------------------------------------- class AMFThreadObj; class AMFThread { friend class AMFThreadObj; public: AMFThread(); virtual ~AMFThread(); virtual bool Start(); virtual bool RequestStop(); virtual bool WaitForStop(); virtual bool StopRequested(); virtual bool IsRunning() const; protected: // this is executed in the thread and overloaded by implementor virtual void Run() = 0; virtual bool Init() { return true; } virtual bool Terminate() { return true; } private: AMFThreadObj* m_thread; AMFThread(const AMFThread&); AMFThread& operator=(const AMFThread&); }; void ExitThread(); //---------------------------------------------------------------- template class AMFQueue { protected: class ItemData { public: T data; amf_ulong ulID; amf_long ulPriority; ItemData() : data(), ulID(), ulPriority(){} }; typedef std::list< ItemData > QueueList; QueueList m_Queue; AMFCriticalSection m_cSect; AMFEvent m_SomethingInQueueEvent; AMFSemaphore m_QueueSizeSem; amf_int32 m_iQueueSize; bool InternalGet(amf_ulong& ulID, T& item) { AMFLock lock(&m_cSect); if(!m_Queue.empty()) // something to get { ItemData& itemdata = m_Queue.front(); ulID = itemdata.ulID; item = itemdata.data; m_Queue.pop_front(); m_QueueSizeSem.Unlock(); if(m_Queue.empty()) { m_SomethingInQueueEvent.ResetEvent(); } return true; } return false; } public: AMFQueue(amf_int32 iQueueSize = 0) : m_Queue(), m_cSect(), m_SomethingInQueueEvent(false, false), m_QueueSizeSem(iQueueSize, iQueueSize > 0 ? iQueueSize + 1 : 0), m_iQueueSize(iQueueSize) {} virtual ~AMFQueue(){} virtual bool SetQueueSize(amf_int32 iQueueSize) { bool success = m_QueueSizeSem.Create(iQueueSize, iQueueSize > 0 ? iQueueSize + 1 : 0); if(success) { m_iQueueSize = iQueueSize; } return success; } virtual amf_int32 GetQueueSize() { return m_iQueueSize; } virtual bool Add(amf_ulong ulID, const T& item, amf_long ulPriority = 0, amf_ulong ulTimeout = AMF_INFINITE) { if(m_QueueSizeSem.Lock(ulTimeout) == false) { return false; } { AMFLock lock(&m_cSect); ItemData itemdata; itemdata.ulID = ulID; itemdata.data = item; itemdata.ulPriority = ulPriority; typename QueueList::iterator iter = m_Queue.end(); for(; iter != m_Queue.begin(); ) { iter--; if(ulPriority <= (iter->ulPriority)) { iter++; break; } } m_Queue.insert(iter, itemdata); m_SomethingInQueueEvent.SetEvent(); // this will set all waiting threads - some of them get data, some of them not } return true; } virtual bool Get(amf_ulong& ulID, T& item, amf_ulong ulTimeout) { if(InternalGet(ulID, item)) // try right away { return true; } // wait for queue if(m_SomethingInQueueEvent.Lock(ulTimeout)) { return InternalGet(ulID, item); } return false; } virtual void Clear() { bool bValue = true; while(bValue) { amf_ulong ulID; T item; bValue = InternalGet(ulID, item); } } virtual amf_size GetSize() { AMFLock lock(&m_cSect); return m_Queue.size(); } }; //---------------------------------------------------------------- template class AMFQueueThread : public AMFThread { private: AMFQueueThread(const AMFQueueThread&); AMFQueueThread& operator=(const AMFQueueThread&); protected: AMFQueue* m_pInQueue; AMFQueue* m_pOutQueue; AMFMutex m_mutexInProcess; ///< This mutex shows other threads that the thread function allocates ///< some objects on stack and it is unsafe state. To manipulate objects owned by descendant classes ///< client must lock this mutex by calling BlockProcessing member function. When client finished its work ///< corresponding UnblockProcessing member function call must be done. bool m_blockProcessingRequested; AMFCriticalSection m_csBlockingRequest; public: AMFQueueThread(AMFQueue* pInQueue, AMFQueue* pOutQueue) : m_pInQueue(pInQueue), m_pOutQueue(pOutQueue), m_mutexInProcess(), m_blockProcessingRequested(false), m_csBlockingRequest() {} virtual bool Process(amf_ulong& ulID, inT& inData, outT& outData) = 0; virtual void BlockProcessing() { AMFLock lock(&m_csBlockingRequest); m_blockProcessingRequested = true; m_mutexInProcess.Lock(); } virtual void UnblockProcessing() { AMFLock lock(&m_csBlockingRequest); m_mutexInProcess.Unlock(); m_blockProcessingRequested = false; } virtual bool IsPaused() { return false; } virtual void OnHaveOutput() {} virtual void OnIdle() {} virtual void Run() { bool bStop = false; while(!bStop) { { AMFLock lock(&m_mutexInProcess); inT inData; amf_ulong ulID = 0; bool callProcess = true; if(m_pInQueue != NULL) { amf_ulong waitTimeout = 5; bool validInput = m_pInQueue->Get(ulID, inData, waitTimeout); // Pulse to check Stop from time to time if(StopRequested()) { bStop = true; } if(!validInput) { callProcess = false; } } if(!bStop && callProcess) { outT outData; bool validOutput = Process(ulID, inData, outData); if(StopRequested()) { bStop = true; } if(!bStop && (m_pOutQueue != NULL) && validOutput) { m_pOutQueue->Add(ulID, outData); OnHaveOutput(); } } else { OnIdle(); } } if(StopRequested()) { bStop = true; } #if defined(__linux) || defined(__APPLE__) ///< HACK ///< This amf_sleep(0) is required to emulate windows mutex behavior. ///< In Windows release mutext causes some other waiting thread is receiving ownership of mutex. ///< In Linux it is not true. ///< Without sleep AMFLock destructor releases mutex but immediately on next cycle AMFLock constructor tries to lock ///< the mutex and now system have two threads waiting for the mutex. ///< Using some random logic system decides who will be unlocked. ///< This thread may win during several seconds it looks like pipeline is hang. ///< amf_sleep call causes waiting thread is becoming unlocked. if(m_blockProcessingRequested) { amf_sleep(0); } #endif } } }; //---------------------------------------------------------------- template class AMFQueueThreadPipeline { private: AMFQueueThreadPipeline(const AMFQueueThreadPipeline&); AMFQueueThreadPipeline& operator=(const AMFQueueThreadPipeline&); public: AMFQueue* m_pInQueue; AMFQueue* m_pOutQueue; std::vector<_Thread*> m_ThreadPool; AMFQueueThreadPipeline(AMFQueue* pInQueue, AMFQueue* pOutQueue) : m_pInQueue(pInQueue), m_pOutQueue(pOutQueue), m_ThreadPool() {} virtual ~AMFQueueThreadPipeline() { Stop(); } void Start(int iNumberOfThreads, ThreadParam param) { if((long)m_ThreadPool.size() >= iNumberOfThreads) { Stop(); //temporary to remove stopped threads. need callback from thread to clean pool //return; } size_t initialSize = m_ThreadPool.size(); for(size_t i = initialSize; i < (size_t)iNumberOfThreads; i++) { _Thread* pThread = new _Thread(m_pInQueue, m_pOutQueue, param); m_ThreadPool.push_back(pThread); pThread->Start(); } } void RequestStop() { long num = (long)m_ThreadPool.size(); for(long i = 0; i < num; i++) { m_ThreadPool[i]->RequestStop(); } } void BlockProcessing() { long num = (long)m_ThreadPool.size(); for(long i = 0; i < num; i++) { m_ThreadPool[i]->BlockProcessing(); } } void UnblockProcessing() { long num = (long)m_ThreadPool.size(); for(long i = 0; i < num; i++) { m_ThreadPool[i]->UnblockProcessing(); } } void WaitForStop() { long num = (long)m_ThreadPool.size(); for(long i = 0; i < num; i++) { _Thread* pThread = m_ThreadPool[i]; pThread->WaitForStop(); delete pThread; } m_ThreadPool.clear(); } void Stop() { RequestStop(); WaitForStop(); } }; //---------------------------------------------------------------- class AMFPreciseWaiter { public: AMFPreciseWaiter() : m_WaitEvent(), m_bCancel(false) {} virtual ~AMFPreciseWaiter() {} amf_pts Wait(amf_pts waittime) { if (waittime < 0) { return 0; } m_bCancel = false; amf_pts start = amf_high_precision_clock(); amf_pts waited = 0; int count = 0; while(!m_bCancel) { count++; if(!m_WaitEvent.LockTimeout(1)) { break; } waited = amf_high_precision_clock() - start; if(waited >= waittime) { break; } } return waited; } amf_pts WaitEx(amf_pts waittime) { m_bCancel = false; amf_pts start = amf_high_precision_clock(); amf_pts waited = 0; int count = 0; while (!m_bCancel && waited < waittime) { if (waittime - waited < 2 * AMF_SECOND / 1000)// last 2 ms burn CPU for precision { for (int i = 0; i < 1000; i++) { count++; #ifdef _WIN32 YieldProcessor(); #endif } } else if (!m_WaitEvent.LockTimeout(1)) { break; } waited = amf_high_precision_clock() - start; } return waited; } void Cancel() { m_bCancel = true; } protected: AMFEvent m_WaitEvent; bool m_bCancel; }; //---------------------------------------------------------------- } // namespace amf #endif // AMF_Thread_h