Files
renderdoc/renderdoc/os/win32/win32_threading.cpp
T
baldurk 78f1f8f3d1 Remove volatile from Atomic parameter declarations
* This was leaky from windows' InterlockedIncrement etc declarations, and is not
  necessary.
2020-09-09 16:40:04 +01:00

359 lines
8.0 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2020 Baldur Karlsson
* Copyright (c) 2014 Crytek
*
* 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.
******************************************************************************/
#include <time.h>
#include "common/common.h"
#include "os/os_specific.h"
double Timing::GetTickFrequency()
{
LARGE_INTEGER li;
QueryPerformanceFrequency(&li);
return double(li.QuadPart) / 1000.0;
}
uint64_t Timing::GetTick()
{
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
return li.QuadPart;
}
uint64_t Timing::GetUnixTimestamp()
{
return (uint64_t)time(NULL);
}
time_t Timing::GetUTCTime()
{
return time(NULL);
}
namespace Atomic
{
int32_t Inc32(int32_t *i)
{
return (int32_t)InterlockedIncrement((volatile LONG *)i);
}
int32_t Dec32(int32_t *i)
{
return (int32_t)InterlockedDecrement((volatile LONG *)i);
}
int64_t Inc64(int64_t *i)
{
return (int64_t)InterlockedIncrement64((volatile LONG64 *)i);
}
int64_t Dec64(int64_t *i)
{
return (int64_t)InterlockedDecrement64((volatile LONG64 *)i);
}
int64_t ExchAdd64(int64_t *i, int64_t a)
{
return (int64_t)InterlockedExchangeAdd64((volatile LONG64 *)i, a);
}
int32_t CmpExch32(int32_t *dest, int32_t oldVal, int32_t newVal)
{
return (int32_t)InterlockedCompareExchange((volatile LONG *)dest, newVal, oldVal);
}
};
namespace Threading
{
CriticalSection::CriticalSectionTemplate()
{
InitializeCriticalSection(&m_Data);
}
CriticalSection::~CriticalSectionTemplate()
{
DeleteCriticalSection(&m_Data);
}
void CriticalSection::Lock()
{
EnterCriticalSection(&m_Data);
}
bool CriticalSection::Trylock()
{
return TryEnterCriticalSection(&m_Data) != FALSE;
}
void CriticalSection::Unlock()
{
LeaveCriticalSection(&m_Data);
}
RWLock::RWLockTemplate()
{
InitializeSRWLock(&m_Data);
}
RWLock::~RWLockTemplate()
{
}
void RWLock::WriteLock()
{
AcquireSRWLockExclusive(&m_Data);
}
bool RWLock::TryWritelock()
{
return TryAcquireSRWLockExclusive(&m_Data) != FALSE;
}
void RWLock::WriteUnlock()
{
ReleaseSRWLockExclusive(&m_Data);
}
void RWLock::ReadLock()
{
AcquireSRWLockShared(&m_Data);
}
bool RWLock::TryReadlock()
{
return TryAcquireSRWLockShared(&m_Data) != FALSE;
}
void RWLock::ReadUnlock()
{
ReleaseSRWLockShared(&m_Data);
}
struct ThreadInitData
{
std::function<void()> entryFunc;
};
static DWORD __stdcall sThreadInit(void *init)
{
ThreadInitData *data = (ThreadInitData *)init;
// delete before going into entry function so lifetime is limited.
ThreadInitData local = *data;
delete data;
local.entryFunc();
return 0;
}
// to not exhaust OS slots, we only allocate one that points
// to our own array
DWORD OSTLSHandle;
int64_t nextTLSSlot = 0;
struct TLSData
{
rdcarray<void *> data;
};
static CriticalSection *m_TLSListLock = NULL;
static rdcarray<TLSData *> *m_TLSList = NULL;
void Init()
{
OSTLSHandle = TlsAlloc();
if(OSTLSHandle == TLS_OUT_OF_INDEXES)
RDCFATAL("Can't allocate OS TLS slot");
m_TLSListLock = new CriticalSection();
m_TLSList = new rdcarray<TLSData *>();
}
void Shutdown()
{
if(m_TLSList)
{
for(size_t i = 0; i < m_TLSList->size(); i++)
delete m_TLSList->at(i);
}
delete m_TLSList;
delete m_TLSListLock;
TlsFree(OSTLSHandle);
}
// allocate a TLS slot in our per-thread vectors with an atomic increment.
// Note this is going to be 1-indexed because Inc64 returns the post-increment
// value
uint64_t AllocateTLSSlot()
{
return Atomic::Inc64(&nextTLSSlot);
}
// look up our per-thread vector.
void *GetTLSValue(uint64_t slot)
{
TLSData *slots = (TLSData *)TlsGetValue(OSTLSHandle);
if(slots == NULL || slot - 1 >= slots->data.size())
return NULL;
return slots->data[(size_t)slot - 1];
}
void SetTLSValue(uint64_t slot, void *value)
{
TLSData *slots = (TLSData *)TlsGetValue(OSTLSHandle);
// resize or allocate slot data if needed.
// We don't need to lock this, as it is by definition thread local so we are
// blocking on the only possible concurrent access.
if(slots == NULL || slot - 1 >= slots->data.size())
{
if(slots == NULL)
{
slots = new TLSData;
TlsSetValue(OSTLSHandle, slots);
// in the case where this thread is entirely new, we globally lock so we can
// store its data for shutdown (as we might not get notified of every thread
// that exits). This only happens once, so we take the hit of the lock.
m_TLSListLock->Lock();
m_TLSList->push_back(slots);
m_TLSListLock->Unlock();
}
if(slot - 1 >= slots->data.size())
slots->data.resize((size_t)slot);
}
slots->data[(size_t)slot - 1] = value;
}
ThreadHandle CreateThread(std::function<void()> entryFunc)
{
ThreadInitData *initData = new ThreadInitData;
initData->entryFunc = entryFunc;
HANDLE h = ::CreateThread(NULL, 0, &sThreadInit, (void *)initData, 0, NULL);
return (ThreadHandle)h;
}
typedef HRESULT(WINAPI *PFN_SetThreadDescription)(HANDLE hThread, PCWSTR lpThreadDescription);
const DWORD MS_VC_EXCEPTION = 0x406D1388;
#pragma pack(push, 8)
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack(pop)
static void SetThreadNameWithException(const char *name)
{
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = name;
info.dwThreadID = GetCurrentThreadId();
info.dwFlags = 0;
__try
{
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR *)(&info));
}
__except(EXCEPTION_CONTINUE_EXECUTION)
{
}
}
void SetCurrentThreadName(const rdcstr &name)
{
// try to use the fancy modern API
static PFN_SetThreadDescription setThreadDesc = (PFN_SetThreadDescription)GetProcAddress(
GetModuleHandleA("kernel32.dll"), "SetThreadDescription");
if(setThreadDesc)
{
setThreadDesc(GetCurrentThread(), StringFormat::UTF82Wide(name).c_str());
}
else
{
// don't throw the exception if there's no debugger present
if(!IsDebuggerPresent())
return;
SetThreadNameWithException(name.c_str());
}
}
uint64_t GetCurrentID()
{
return (uint64_t)::GetCurrentThreadId();
}
void JoinThread(ThreadHandle handle)
{
if(handle == 0)
return;
WaitForSingleObject((HANDLE)handle, INFINITE);
}
void DetachThread(ThreadHandle handle)
{
if(handle == 0)
return;
CloseHandle((HANDLE)handle);
}
void CloseThread(ThreadHandle handle)
{
if(handle == 0)
return;
CloseHandle((HANDLE)handle);
}
static HMODULE ownModuleHandle = 0;
void KeepModuleAlive()
{
// deliberately omitting GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT to bump refcount
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (const char *)&ownModuleHandle,
&ownModuleHandle);
}
void ReleaseModuleExitThread()
{
FreeLibraryAndExitThread(ownModuleHandle, 0);
}
void Sleep(uint32_t milliseconds)
{
::Sleep((DWORD)milliseconds);
}
};