Files
FlaxEngine/Source/Engine/Platform/Win32/Win32Platform.cpp
GoaLitiuM cc60814334 Increase accuracy of Windows Sleep function
Windows 10 version 1803 (build 17134) and later versions support
high-resolution waitable timer, which offers much better accuracy over
Sleep function without the need of increasing the global timer
resolution. For older versions of Windows, we reduce the timer
resolution to 1ms and use normal waitable timer for sleeping.
2021-05-30 01:48:15 +03:00

580 lines
16 KiB
C++

// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#if PLATFORM_WIN32
#include "Engine/Platform/Platform.h"
#include "Engine/Platform/MemoryStats.h"
#include "Engine/Platform/CPUInfo.h"
#include "Engine/Core/Types/Guid.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Collections/HashFunctions.h"
#include "Engine/Core/Log.h"
#include "Engine/Engine/CommandLine.h"
#include "IncludeWindowsHeaders.h"
#include <Psapi.h>
#include <WinSock2.h>
#include <IPHlpApi.h>
#include <oleauto.h>
#include <WinBase.h>
#include <xmmintrin.h>
#include <intrin.h>
#include <cstdio>
#pragma comment(lib, "Iphlpapi.lib")
namespace
{
Guid DeviceId;
CPUInfo CpuInfo;
uint64 ClockFrequency;
double CyclesToSeconds;
WSAData WsaData;
}
// Helper function to count set bits in the processor mask
DWORD CountSetBits(ULONG_PTR bitMask)
{
DWORD LSHIFT = sizeof(ULONG_PTR) * 8 - 1;
DWORD bitSetCount = 0;
ULONG_PTR bitTest = static_cast<ULONG_PTR>(1) << LSHIFT;
DWORD i;
for (i = 0; i <= LSHIFT; ++i)
{
bitSetCount += ((bitMask & bitTest) ? 1 : 0);
bitTest /= 2;
}
return bitSetCount;
}
static String GetLastErrorMessage()
{
wchar_t* s = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, WSAGetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&s), 0, nullptr);
String str(s);
LocalFree(s);
return str;
}
bool Win32Platform::Init()
{
if (PlatformBase::Init())
return true;
// Init console output (engine is linked with /SUBSYSTEM:WINDOWS so it lacks of proper console output on Windows)
if (CommandLine::Options.Std)
{
// Attaches output of application to parent console, returns true if running in console-mode
// [Reference: https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application]
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
const HANDLE consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (consoleHandleOut != INVALID_HANDLE_VALUE)
{
freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0);
}
const HANDLE consoleHandleError = GetStdHandle(STD_ERROR_HANDLE);
if (consoleHandleError != INVALID_HANDLE_VALUE)
{
freopen("CONOUT$", "w", stderr);
setvbuf(stderr, NULL, _IONBF, 0);
}
}
}
// Init timing
LARGE_INTEGER frequency;
const auto freqResult = QueryPerformanceFrequency(&frequency);
ASSERT(freqResult && frequency.QuadPart > 0);
ClockFrequency = frequency.QuadPart;
CyclesToSeconds = 1.0 / static_cast<double>(frequency.QuadPart);
// Count CPUs
BOOL done = FALSE;
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = nullptr;
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr;
DWORD returnLength = 0;
DWORD logicalProcessorCount = 0;
DWORD processorCoreCount = 0;
DWORD processorL1CacheSize = 0;
DWORD processorL2CacheSize = 0;
DWORD processorL3CacheSize = 0;
DWORD processorPackageCount = 0;
DWORD byteOffset = 0;
PCACHE_DESCRIPTOR cache;
while (!done)
{
DWORD rc = GetLogicalProcessorInformation(buffer, &returnLength);
if (rc == FALSE)
{
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
if (buffer)
{
free(buffer);
}
buffer = static_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION>(malloc(returnLength));
if (buffer == nullptr)
{
return true;
}
}
else
{
return true;
}
}
else
{
done = TRUE;
}
}
ptr = buffer;
while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength)
{
switch (ptr->Relationship)
{
case RelationProcessorCore:
processorCoreCount++;
logicalProcessorCount += CountSetBits(ptr->ProcessorMask);
break;
case RelationCache:
cache = &ptr->Cache;
if (cache->Level == 1)
{
processorL1CacheSize += cache->Size;
}
else if (cache->Level == 2)
{
processorL2CacheSize += cache->Size;
}
else if (cache->Level == 3)
{
processorL3CacheSize += cache->Size;
}
break;
case RelationProcessorPackage:
processorPackageCount++;
break;
}
byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
ptr++;
}
free(buffer);
// Set info about the CPU
CpuInfo.ProcessorPackageCount = processorPackageCount;
CpuInfo.ProcessorCoreCount = processorCoreCount;
CpuInfo.LogicalProcessorCount = logicalProcessorCount;
CpuInfo.L1CacheSize = processorL1CacheSize;
CpuInfo.L2CacheSize = processorL2CacheSize;
CpuInfo.L3CacheSize = processorL3CacheSize;
// Get page size
SYSTEM_INFO siSysInfo;
GetSystemInfo(&siSysInfo);
CpuInfo.PageSize = siSysInfo.dwPageSize;
// Get clock speed
CpuInfo.ClockSpeed = GetClockFrequency();
// Get cache line size
{
int args[4];
__cpuid(args, 0x80000006);
CpuInfo.CacheLineSize = args[2] & 0xFF;
ASSERT(CpuInfo.CacheLineSize && Math::IsPowerOfTwo(CpuInfo.CacheLineSize));
}
// Setup unique device ID
{
DeviceId = Guid::Empty;
// A - Computer Name and User Name
uint32 hash = GetHash(Platform::GetComputerName());
CombineHash(hash, GetHash(Platform::GetUserName()));
DeviceId.A = hash;
// B - MAC address
DeviceId.B = 0;
IP_ADAPTER_ADDRESSES pAddresses[16];
DWORD dwBufLen = sizeof(pAddresses);
#if PLATFORM_UWP
DWORD dwStatus = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL, pAddresses, &dwBufLen);
PIP_ADAPTER_ADDRESSES pAdapterInfo = dwStatus == ERROR_SUCCESS ? pAddresses : nullptr;
while (pAdapterInfo)
{
hash = pAdapterInfo->PhysicalAddress[0];
for (UINT i = 1; i < pAdapterInfo->PhysicalAddressLength; i++)
CombineHash(hash, pAdapterInfo->PhysicalAddress[i]);
DeviceId.B = hash;
pAdapterInfo = pAdapterInfo->Next;
}
#else
const DWORD dwStatus = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL, pAddresses, &dwBufLen);
PIP_ADAPTER_ADDRESSES pAdapterInfo = dwStatus == ERROR_SUCCESS ? pAddresses : nullptr;
while (pAdapterInfo)
{
hash = pAdapterInfo->PhysicalAddress[0];
for (UINT i = 1; i < pAdapterInfo->PhysicalAddressLength; i++)
CombineHash(hash, pAdapterInfo->PhysicalAddress[i]);
DeviceId.B = hash;
pAdapterInfo = pAdapterInfo->Next;
}
#endif
// C - memory
DeviceId.C = (uint32)Platform::GetMemoryStats().TotalPhysicalMemory;
// D - cpuid
auto cpuInfo = Platform::GetCPUInfo();
DeviceId.D = (uint32)cpuInfo.ClockSpeed * cpuInfo.LogicalProcessorCount * cpuInfo.ProcessorCoreCount * cpuInfo.CacheLineSize;
}
// Init networking
if (WSAStartup(MAKEWORD(2, 0), &WsaData) != 0)
LOG(Error, "Unable to initializes native network! Error : {0}", GetLastErrorMessage());
return false;
}
void Win32Platform::Exit()
{
WSACleanup();
}
void Win32Platform::MemoryBarrier()
{
_ReadWriteBarrier();
#if PLATFORM_64BITS
#ifdef _AMD64_
__faststorefence();
#elif defined(_IA64_)
__mf();
#else
#error "Invalid platform."
#endif
#else
LONG barrier;
__asm {
xchg barrier, eax
}
#endif
}
int64 Win32Platform::InterlockedExchange(int64 volatile* dst, int64 exchange)
{
return InterlockedExchange64(dst, exchange);
}
int32 Win32Platform::InterlockedCompareExchange(int32 volatile* dst, int32 exchange, int32 comperand)
{
static_assert(sizeof(int32) == sizeof(LONG), "Invalid LONG size.");
return _InterlockedCompareExchange((LONG volatile*)dst, exchange, comperand);
}
int64 Win32Platform::InterlockedCompareExchange(int64 volatile* dst, int64 exchange, int64 comperand)
{
return InterlockedCompareExchange64(dst, exchange, comperand);
}
int64 Win32Platform::InterlockedIncrement(int64 volatile* dst)
{
return InterlockedIncrement64(dst);
}
int64 Win32Platform::InterlockedDecrement(int64 volatile* dst)
{
return InterlockedDecrement64(dst);
}
int64 Win32Platform::InterlockedAdd(int64 volatile* dst, int64 value)
{
return InterlockedExchangeAdd64(dst, value);
}
int32 Win32Platform::AtomicRead(int32 volatile* dst)
{
static_assert(sizeof(int32) == sizeof(LONG), "Invalid LONG size.");
return _InterlockedCompareExchange((LONG volatile*)dst, 0, 0);
}
int64 Win32Platform::AtomicRead(int64 volatile* dst)
{
return InterlockedCompareExchange64(dst, 0, 0);
}
void Win32Platform::AtomicStore(int32 volatile* dst, int32 value)
{
static_assert(sizeof(int32) == sizeof(LONG), "Invalid LONG size.");
_InterlockedExchange((LONG volatile*)dst, value);
}
void Win32Platform::AtomicStore(int64 volatile* dst, int64 value)
{
InterlockedExchange64(dst, value);
}
void Win32Platform::Prefetch(void const* ptr)
{
_mm_prefetch((char const*)ptr, _MM_HINT_T0);
}
void* Win32Platform::Allocate(uint64 size, uint64 alignment)
{
void* ptr = _aligned_malloc((size_t)size, (size_t)alignment);
#if COMPILE_WITH_PROFILER
OnMemoryAlloc(ptr, size);
#endif
return ptr;
}
void Win32Platform::Free(void* ptr)
{
#if COMPILE_WITH_PROFILER
OnMemoryFree(ptr);
#endif
_aligned_free(ptr);
}
void* Win32Platform::AllocatePages(uint64 numPages, uint64 pageSize)
{
const uint64 numBytes = numPages * pageSize;
#if PLATFORM_UWP
return VirtualAllocFromApp(nullptr, (SIZE_T)numBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
return VirtualAlloc(nullptr, (SIZE_T)numBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#endif
}
void Win32Platform::FreePages(void* ptr)
{
VirtualFree(ptr, 0, MEM_RELEASE);
}
bool Win32Platform::Is64BitPlatform()
{
#ifdef PLATFORM_64BITS
return true;
#else
BOOL result;
IsWow64Process(GetCurrentProcess(), &result);
return result == TRUE;
#endif
}
CPUInfo Win32Platform::GetCPUInfo()
{
return CpuInfo;
}
int32 Win32Platform::GetCacheLineSize()
{
return CpuInfo.CacheLineSize;
}
MemoryStats Win32Platform::GetMemoryStats()
{
// Get memory stats
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
GlobalMemoryStatusEx(&statex);
// Fill result data
MemoryStats result;
result.TotalPhysicalMemory = statex.ullTotalPhys;
result.UsedPhysicalMemory = statex.ullTotalPhys - statex.ullAvailPhys;
result.TotalVirtualMemory = statex.ullTotalVirtual;
result.UsedVirtualMemory = statex.ullTotalVirtual - statex.ullAvailVirtual;
return result;
}
ProcessMemoryStats Win32Platform::GetProcessMemoryStats()
{
// Get memory stats
PROCESS_MEMORY_COUNTERS_EX countersEx;
countersEx.cb = sizeof(countersEx);
GetProcessMemoryInfo(GetCurrentProcess(), (PPROCESS_MEMORY_COUNTERS)&countersEx, sizeof(countersEx));
// Fill result data
ProcessMemoryStats result;
result.UsedPhysicalMemory = countersEx.WorkingSetSize;
result.UsedVirtualMemory = countersEx.PrivateUsage;
return result;
}
uint64 Win32Platform::GetCurrentProcessId()
{
return ::GetCurrentProcessId();
}
uint64 Win32Platform::GetCurrentThreadID()
{
return ::GetCurrentThreadId();
}
void Win32Platform::SetThreadPriority(ThreadPriority priority)
{
int32 winPriority;
switch (priority)
{
case ThreadPriority::Lowest:
winPriority = THREAD_PRIORITY_LOWEST;
break;
case ThreadPriority::BelowNormal:
winPriority = THREAD_PRIORITY_BELOW_NORMAL;
break;
case ThreadPriority::Normal:
winPriority = THREAD_PRIORITY_NORMAL;
break;
case ThreadPriority::AboveNormal:
winPriority = THREAD_PRIORITY_ABOVE_NORMAL;
break;
case ThreadPriority::Highest:
winPriority = THREAD_PRIORITY_HIGHEST;
break;
default:
winPriority = THREAD_PRIORITY_NORMAL;
break;
}
::SetThreadPriority(::GetCurrentThread(), winPriority);
}
void Win32Platform::SetThreadAffinityMask(uint64 affinityMask)
{
::SetThreadAffinityMask(::GetCurrentThread(), (DWORD_PTR)affinityMask);
}
void Win32Platform::Sleep(int32 milliseconds)
{
static thread_local HANDLE timer = NULL;
if (timer == NULL)
{
// Attempt to create high-resolution timer for each thread (Windows 10 build 17134 or later)
timer = CreateWaitableTimerEx(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
if (timer == NULL) // fallback for older versions of Windows
timer = CreateWaitableTimer(NULL, TRUE, NULL);
}
// Negative value is relative to current time, minimum waitable time is 10 microseconds
LARGE_INTEGER dueTime;
dueTime.QuadPart = -int64_t(milliseconds) * 10000;
SetWaitableTimerEx(timer, &dueTime, 0, NULL, NULL, NULL, 0);
WaitForSingleObject(timer, INFINITE);
}
double Win32Platform::GetTimeSeconds()
{
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return double(counter.QuadPart) * CyclesToSeconds;
}
uint64 Win32Platform::GetTimeCycles()
{
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return counter.QuadPart;
}
uint64 Win32Platform::GetClockFrequency()
{
return ClockFrequency;
}
void Win32Platform::GetSystemTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond)
{
// Get current local time
SYSTEMTIME st;
::GetLocalTime(&st);
// Extract time
year = st.wYear;
month = st.wMonth;
dayOfWeek = st.wDayOfWeek;
day = st.wDay;
hour = st.wHour;
minute = st.wMinute;
second = st.wSecond;
millisecond = st.wMilliseconds;
}
void Win32Platform::GetUTCTime(int32& year, int32& month, int32& dayOfWeek, int32& day, int32& hour, int32& minute, int32& second, int32& millisecond)
{
// Get current system time
SYSTEMTIME st;
::GetSystemTime(&st);
// Extract time
year = st.wYear;
month = st.wMonth;
dayOfWeek = st.wDayOfWeek;
day = st.wDay;
hour = st.wHour;
minute = st.wMinute;
second = st.wSecond;
millisecond = st.wMilliseconds;
}
void Win32Platform::CreateGuid(Guid& result)
{
CoCreateGuid(reinterpret_cast<GUID*>(&result));
}
String Win32Platform::GetMainDirectory()
{
Char buffer[MAX_PATH];
GetModuleFileNameW(nullptr, buffer, MAX_PATH);
const String str(buffer);
int32 pos = str.FindLast(TEXT('\\'));
if (pos != -1 && ++pos < str.Length())
return str.Left(pos);
return str;
}
String Win32Platform::GetExecutableFilePath()
{
Char buffer[MAX_PATH];
GetModuleFileNameW(nullptr, buffer, MAX_PATH);
return String(buffer);
}
Guid Win32Platform::GetUniqueDeviceId()
{
return DeviceId;
}
String Win32Platform::GetWorkingDirectory()
{
Char buffer[MAX_PATH];
GetCurrentDirectoryW(MAX_PATH, buffer);
return String(buffer);
}
bool Win32Platform::SetWorkingDirectory(const String& path)
{
return !SetCurrentDirectoryW(*path);
}
void Win32Platform::FreeLibrary(void* handle)
{
::FreeLibrary((HMODULE)handle);
}
void* Win32Platform::GetProcAddress(void* handle, const char* symbol)
{
return (void*)::GetProcAddress((HMODULE)handle, symbol);
}
#endif