剖析虚幻渲染体系(18)- 操作系统(Android)

18.14 UE Platform

前面章节详细阐述了操作系统相关的概念、原理和运行机制,本章将阐述UE对操作系统的封装。本篇以UE5.0.5的源码进行分析。

18.14.1 UE Platform概述

UE的操作系统相关接口封装在了Core文件夹下,具体见下图:

以上文件中,有部分是通用接口层,如HAL、Memory等。这其中最重要的类非FGenericPlatformMisc莫属,它抽象了大多数平台的通用接口。后面章节会详细阐述之。

18.14.2 FGenericPlatformMisc

FGenericPlatformMisc是UE跨平台的重要类型,抽象了大多数操作系统的操作接口,以便其它模块实现平台无关的操作。当然,FGenericPlatformMisc只是声明了一组接口,具体实现由不同的子类实现。FGenericPlatformMisc的主要接口如下所示:

// GenericPlatformMisc.h

struct CORE_API FGenericPlatformMisc
{
    // 平台生命周期
    static void PlatformPreInit();
    static void PlatformInit();
    static void PlatformTearDown();
    static void RequestExit( bool Force );
    static void RequestExitWithStatus( bool Force, uint8 ReturnCode );
    static bool RestartApplication();
    static bool RestartApplicationWithCmdLine(const char* CmdLine);
    static void TearDown();
    
    // 窗口/UI
    static void PlatformHandleSplashScreen(bool ShowSplashScreen);
    static void HidePlatformStartupScreen();
    static EAppReturnType::Type MessageBoxExt( EAppMsgType::Type MsgType, const TCHAR* Text, const TCHAR* Caption );
    static void ShowConsoleWindow();
    static int GetMobilePropagateAlphaSetting();
    
    // 调试/错误
    static void SetGracefulTerminationHandler();
    static void SetCrashHandler(void (* CrashHandler)(const FGenericCrashContext& Context));
    static bool SupportsFullCrashDumps();
    static uint32 GetLastError();
    static void SetLastError(uint32 ErrorCode);
    static void RaiseException( uint32 ExceptionCode );
    static void LowLevelOutputDebugString(const TCHAR *Message);
    static void VARARGS LowLevelOutputDebugStringf(const TCHAR *Format, ... );
    static void SetUTF8Output();
    static void LocalPrint( const TCHAR* Str );
    static bool IsLocalPrintThreadSafe();
    static bool HasSeparateChannelForDebugOutput();
    static const TCHAR* GetSystemErrorMessage(TCHAR* OutBuffer, int32 BufferCount, int32 Error);
    static void PromptForRemoteDebugging(bool bIsEnsure);

    // 环境变量、路径
    static FString GetEnvironmentVariable(const TCHAR* VariableName);
    static void SetEnvironmentVar(const TCHAR* VariableName, const TCHAR* Value);
    FORCEINLINE static int32 GetMaxPathLength();
    static const TCHAR* GetPathVarDelimiter();
    static bool IsValidAbsolutePathFormat(const FString& Path);
    static void NormalizePath(FString& InPath);
    static void NormalizePath(FStringBuilderBase& InPath);
    static const TCHAR* GetDefaultPathSeparator();
    static const TCHAR* RootDir();
    static TArray<FString> GetAdditionalRootDirectories();
    static void AddAdditionalRootDirectory(const FString& RootDir);
    static const TCHAR* EngineDir();
    static const TCHAR* LaunchDir();
    static void CacheLaunchDir();
    static const TCHAR* ProjectDir();
    static FString CloudDir();
    static bool HasProjectPersistentDownloadDir();
    static bool CheckPersistentDownloadStorageSpaceAvailable( uint64 BytesRequired, bool bAttemptToUseUI );
    static const TCHAR* GamePersistentDownloadDir();
    static const TCHAR* GeneratedConfigDir();
    static void SetOverrideProjectDir(const FString& InOverrideDir);

    // 设备/硬件
    static FString GetDeviceId();
    static FString GetUniqueAdvertisingId();
    static void SubmitErrorReport( const TCHAR* InErrorHist, EErrorReportMode::Type InMode );
    static bool IsRemoteSession();
    FORCEINLINE static bool IsDebuggerPresent();
    static EProcessDiagnosticFlags GetProcessDiagnostics();
    static FString GetCPUVendor();
    static uint32 GetCPUInfo();
    static bool HasNonoptionalCPUFeatures();
    static bool NeedsNonoptionalCPUFeaturesCheck();
    static FString GetCPUBrand();
    static FString GetCPUChipset();
    static FString GetPrimaryGPUBrand();
    static FString GetDeviceMakeAndModel();
    static struct FGPUDriverInfo GetGPUDriverInfo(const FString& DeviceDescription);
    
    static void PrefetchBlock(const void* InPtr, int32 NumBytes = 1);
    static void Prefetch(void const* x, int32 offset = 0);

    static const TCHAR* GetDefaultDeviceProfileName();
    FORCEINLINE static int GetBatteryLevel();
    FORCEINLINE static void SetBrightness(float bBright);
    FORCEINLINE static float GetBrightness();
    FORCEINLINE static bool SupportsBrightness();
    FORCEINLINE static bool IsInLowPowerMode();
    static float GetDeviceTemperatureLevel();
    static inline int32 GetMaxRefreshRate();
    static inline int32 GetMaxSyncInterval();
    static bool IsPGOEnabled();
    
    static TArray<uint8> GetSystemFontBytes();
    static bool HasActiveWiFiConnection();
    static ENetworkConnectionType GetNetworkConnectionType();

    static bool HasVariableHardware();
    static bool HasPlatformFeature(const TCHAR* FeatureName);
    static bool IsRunningOnBattery();
    static EDeviceScreenOrientation GetDeviceOrientation();
    static void SetDeviceOrientation(EDeviceScreenOrientation NewDeviceOrientation);
    static int32 GetDeviceVolume();

    // OS相关
    static void GetOSVersions( FString& out_OSVersionLabel, FString& out_OSSubVersionLabel );
    static FString GetOSVersion();
    static bool GetDiskTotalAndFreeSpace( const FString& InPath, uint64& TotalNumberOfBytes, uint64& NumberOfFreeBytes );
    static bool GetPageFaultStats(FPageFaultStats& OutStats, EPageFaultFlags Flags);
    static bool GetBlockingIOStats(FProcessIOStats& OutStats, EInputOutputFlags Flags=EInputOutputFlags::All);
    static bool GetContextSwitchStats(FContextSwitchStats& OutStats, EContextSwitchFlags Flags=EContextSwitchFlags::All);
    static bool CommandLineCommands();
    static bool Is64bitOperatingSystem();
    static bool OsExecute(const TCHAR* CommandType, const TCHAR* Command, const TCHAR* CommandLine = NULL);
    static bool Exec(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Out);
   
    static bool GetSHA256Signature(const void* Data, uint32 ByteSize, FSHA256Signature& OutSignature);
    static FString GetDefaultLanguage();
    static FString GetDefaultLocale();
    static FString GetTimeZoneId();
    static TArray<FString> GetPreferredLanguages();
    
    static const TCHAR* GetUBTPlatform();
    static const TCHAR* GetUBTTarget();
    static void SetUBTTargetName(const TCHAR* InTargetName);
    static const TCHAR* GetUBTTargetName();
    static const TCHAR* GetNullRHIShaderFormat();
    static IPlatformChunkInstall* GetPlatformChunkInstall();
    static IPlatformCompression* GetPlatformCompression();
    
    static void GetValidTargetPlatforms(TArray<FString>& TargetPlatformNames);
    static FPlatformUserId GetPlatformUserForUserIndex(int32 LocalUserIndex);
    static int32 GetUserIndexForPlatformUser(FPlatformUserId PlatformUser);
   
    // 消息/事件
    static bool SupportsMessaging();
    static bool SupportsLocalCaching();
    static bool AllowLocalCaching();
    static void BeginNamedEvent(const struct FColor& Color, const TCHAR* Text);
    static void BeginNamedEvent(const struct FColor& Color, const ANSICHAR* Text);
    template<typename CharType>
    static void StatNamedEvent(const CharType* Text);
    static void TickStatNamedEvents();
    static void LogNameEventStatsInit();
    static void EndNamedEvent();
    static void CustomNamedStat(const TCHAR* Text, float Value, const TCHAR* Graph, const TCHAR* Unit);
    static void CustomNamedStat(const ANSICHAR* Text, float Value, const ANSICHAR* Graph, const ANSICHAR* Unit);
    static void BeginEnterBackgroundEvent(const TCHAR* Text) ;
    static void EndEnterBackgroundEvent();
    static void BeginNamedEventFrame();
    
    static void RegisterForRemoteNotifications();
    static bool IsRegisteredForRemoteNotifications();
    static void UnregisterForRemoteNotifications();
    
    static void PumpMessagesOutsideMainLoop();
    static void PumpMessagesForSlowTask();
    static void PumpEssentialAppMessages();

    // 内存
    static void MemoryBarrier();
    static void SetMemoryWarningHandler(void (* Handler)(const FGenericMemoryWarningContext& Context));
    static bool HasMemoryWarningHandler();

    // I/O
    static void InitTaggedStorage(uint32 NumTags);
    static void ShutdownTaggedStorage();
    static void TagBuffer(const char* Label, uint32 Category, const void* Buffer, size_t BufferSize);
    static bool SetStoredValues(const FString& InStoreId, const FString& InSectionName, const TMap<FString, FString>& InKeyValues);
    static bool SetStoredValue(const FString& InStoreId, const FString& InSectionName, const FString& InKeyName, const FString& InValue);
    static bool GetStoredValue(const FString& InStoreId, const FString& InSectionName, const FString& InKeyName, FString& OutValue);
    static bool DeleteStoredValue(const FString& InStoreId, const FString& InSectionName, const FString& InKeyName);
    static bool DeleteStoredSection(const FString& InStoreId, const FString& InSectionName);
    
    static TArray<FCustomChunk> GetOnDemandChunksForPakchunkIndices(const TArray<int32>& PakchunkIndices);
    static TArray<FCustomChunk> GetAllOnDemandChunks();
    static TArray<FCustomChunk> GetAllLanguageChunks();
    static TArray<FCustomChunk> GetCustomChunksByType(ECustomChunkType DesiredChunkType);
    static void ParseChunkIdPakchunkIndexMapping(TArray<FString> ChunkIndexRedirects, TMap<int32, int32>& OutMapping);
    static int32 GetChunkIDFromPakchunkIndex(int32 PakchunkIndex);
    static int32 GetPakchunkIndexFromPakFile(const FString& InFilename);
    
    static FText GetFileManagerName();
    static bool IsPackagedForDistribution();
    static FString LoadTextFileFromPlatformPackage(const FString& RelativePath);
    static bool FileExistsInPlatformPackage(const FString& RelativePath);
    
    static bool Expand16BitIndicesTo32BitOnLoad();
    static void GetNetworkFileCustomData(TMap<FString,FString>& OutCustomPlatformData);
    static bool SupportsBackbufferSampling();

    // 线程/作业/异步
    static bool UseRenderThread();
    static bool AllowAudioThread();
    static bool AllowThreadHeartBeat();
    static int32 NumberOfCores();
    static const FProcessorGroupDesc& GetProcessorGroupDesc();
    static int32 NumberOfCoresIncludingHyperthreads();
    static int32 NumberOfWorkerThreadsToSpawn();
    static int32 NumberOfIOWorkerThreadsToSpawn();
    static struct FAsyncIOSystemBase* GetPlatformSpecificAsyncIOSystem();
    static const TCHAR* GetPlatformFeaturesModuleName();
    static bool SupportsMultithreadedFileHandles();
    
    // 交互
    static bool GetUseVirtualJoysticks();
    static bool SupportsTouchInput();
    static bool SupportsForceTouchInput();
    static bool ShouldDisplayTouchInterfaceOnFakingTouchEvents();
    static bool DesktopTouchScreen();
    static bool FullscreenSameAsWindowedFullscreen();
    static bool GetVolumeButtonsHandledBySystem();
    static void SetVolumeButtonsHandledBySystem(bool enabled);
    
    static void PrepareMobileHaptics(EMobileHapticsType Type);
    static void TriggerMobileHaptics();
    static void ReleaseMobileHaptics();

    // 系统信息
    static FString GetLoginId();
    static FString GetEpicAccountId();
    static FString GetOperatingSystemId();
    static EConvertibleLaptopMode GetConvertibleLaptopMode();
    static bool SupportsDeviceCheckToken();
    static bool RequestDeviceCheckToken(...);
    
    // 引擎
    static const TCHAR* GetEngineMode();
    static bool ShouldDisablePluginAtRuntime(const FString& PluginName);
    static bool UseHDRByDefault();
    static void ChooseHDRDeviceAndColorGamut(uint32 DeviceId, uint32 DisplayNitLevel, int32& OutputDevice, int32& ColorGamut);
    
    // 其它
    static void CreateGuid(struct FGuid& Result);
    static void TickHotfixables();
    static FString GetLocalCurrencyCode();
    static FString GetLocalCurrencySymbol();
    static void ShareURL(const FString& URL, const FText& Description, int32 LocationHintX, int32 LocationHintY);

    (...)
};

由上可知,FGenericPlatformMisc不仅包含OS相关的接口,还包含了设备、硬件、应用程序、输入输出、UI、交互、多线程、路径等相关的接口。下图是它的继承体系图:

FGenericPlatformMisc

FWindowsPlatformMisc

FAndroidMisc

FApplePlatformMisc

FMacPlatformMisc

FIOSPlatformMisc

FUnixPlatformMisc

FLinuxPlatformMisc

FHoloLensMisc

下面小节将抽取部分平台的部分接口进行分析。

18.14.2.1 FWindowsPlatformMisc

FWindowsPlatformMisc实现了Windows平台的相关接口,部分接口分析如下:

// WindowsPlatformMisc.cpp

void FWindowsPlatformMisc::PlatformPreInit()
{
    FGenericPlatformMisc::PlatformPreInit();

    (...)
    
    // 使用自己的处理程序来调用纯虚拟对象。
    DefaultPureCallHandler = _set_purecall_handler( PureCallHandler );

    const int32 MinResolution[] = {640, 480};
    if ( ::GetSystemMetrics(SM_CXSCREEN) < MinResolution[0] || ::GetSystemMetrics(SM_CYSCREEN) < MinResolution[1] )
    {
        FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("Launch", "Error_ResolutionTooLow", "The current resolution is too low to run this game.") );
        FPlatformMisc::RequestExit( false );
    }

    // 初始化文件SHA哈希映射
    InitSHAHashes();
}

void FWindowsPlatformMisc::PlatformInit()
{
    FGenericPlatformMisc::LogNameEventStatsInit();

    (...)

    // 将睡眠粒度等设置为1毫秒。
    timeBeginPeriod( 1 );

    (...)

    // 获取cpu信息.
    const FPlatformMemoryConstants& MemoryConstants = FPlatformMemory::GetConstants();
    UE_LOG(LogInit, Log, TEXT("CPU Page size=%i, Cores=%i"), MemoryConstants.PageSize, FPlatformMisc::NumberOfCores() );
    UE_LOG(LogInit, Log, TEXT("High frequency timer resolution =%f MHz"), 0.000001 / FPlatformTime::GetSecondsPerCycle() );

    // 在游戏线程上注册。
    FWindowsPlatformStackWalk::RegisterOnModulesChanged();
}

// 获取OS版本.
void FWindowsPlatformMisc::GetOSVersions( FString& OutOSVersionLabel, FString& OutOSSubVersionLabel )
{
    // OS初始化器.
    static struct FOSVersionsInitializer
    {
        FOSVersionsInitializer()
        {
            OSVersionLabel[0] = 0;
            OSSubVersionLabel[0] = 0;
            GetOSVersionsHelper( OSVersionLabel, UE_ARRAY_COUNT(OSVersionLabel), OSSubVersionLabel, UE_ARRAY_COUNT(OSSubVersionLabel) );
        }

        TCHAR OSVersionLabel[128];
        TCHAR OSSubVersionLabel[128];
    } OSVersionsInitializer;

    OutOSVersionLabel = OSVersionsInitializer.OSVersionLabel;
    OutOSSubVersionLabel = OSVersionsInitializer.OSSubVersionLabel;
}

// 获取磁盘的总量和空闲空间.
bool FWindowsPlatformMisc::GetDiskTotalAndFreeSpace( const FString& InPath, uint64& TotalNumberOfBytes, uint64& NumberOfFreeBytes )
{
    const FString ValidatedPath = FPaths::ConvertRelativePathToFull(InPath).Replace(TEXT("/"), TEXT("\\"));
    bool bSuccess = !!::GetDiskFreeSpaceEx( *ValidatedPath, nullptr, reinterpret_cast<ULARGE_INTEGER*>(&TotalNumberOfBytes), reinterpret_cast<ULARGE_INTEGER*>(&NumberOfFreeBytes));
    
    return bSuccess;
}

// 获取页面错误信息.
bool FWindowsPlatformMisc::GetPageFaultStats(FPageFaultStats& OutStats, EPageFaultFlags Flags/*=EPageFaultFlags::All*/)
{
    bool bSuccess = false;

    if (EnumHasAnyFlags(Flags, EPageFaultFlags::TotalPageFaults))
    {
        PROCESS_MEMORY_COUNTERS ProcessMemoryCounters;

        FPlatformMemory::Memzero(&ProcessMemoryCounters, sizeof(ProcessMemoryCounters));
        ::GetProcessMemoryInfo(::GetCurrentProcess(), &ProcessMemoryCounters, sizeof(ProcessMemoryCounters));

        OutStats.TotalPageFaults = ProcessMemoryCounters.PageFaultCount;

        bSuccess = true;
    }

    return bSuccess;
}

// 获取IO阻塞状态.
bool FWindowsPlatformMisc::GetBlockingIOStats(FProcessIOStats& OutStats, EInputOutputFlags Flags/*=EInputOutputFlags::All*/)
{
    bool bSuccess = false;
    IO_COUNTERS Counters;

    FPlatformMemory::Memzero(&Counters, sizeof(Counters));

    // Ignore flags as all values are grabbed at once
    if (::GetProcessIoCounters(::GetCurrentProcess(), &Counters) != 0)
    {
        OutStats.BlockingInput = Counters.ReadOperationCount;
        OutStats.BlockingOutput = Counters.WriteOperationCount;
        OutStats.BlockingOther = Counters.OtherOperationCount;
        OutStats.InputBytes = Counters.ReadTransferCount;
        OutStats.OutputBytes = Counters.WriteTransferCount;
        OutStats.OtherBytes = Counters.OtherTransferCount;

        bSuccess = true;
    }

    return bSuccess;
}

FString FWindowsPlatformMisc::GetOperatingSystemId()
{
    FString Result;
    QueryRegKey(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Cryptography"), TEXT("MachineGuid"), Result);
    return Result;
}

void FWindowsPlatformMisc::PumpMessagesOutsideMainLoop()
{
    TGuardValue<bool> PumpMessageGuard(GPumpingMessagesOutsideOfMainLoop, true);
    // 处理挂起的窗口消息,在某些情况下,D3D将窗口消息(来自IDXGISwapChain::Present)发送到主线程拥有的视口窗口,是渲染线程所必需的。
    MSG Msg;
    PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE | PM_QS_SENDMESSAGE);
    return;
}

18.14.2.2 FAndroidMisc

FAndroidMisc实现了Android平台的相关接口,部分接口分析如下:

// AndroidPlatformMisc.cpp

void FAndroidMisc::RequestExit( bool Force )
{

#if PLATFORM_COMPILER_OPTIMIZATION_PG_PROFILING
    // 在完全关闭时写入PGO配置文件。
    extern void PGO_WriteFile();
    if (!GIsCriticalError)
    {
        PGO_WriteFile();
        // 立即退出,以避免在AndroidMain退出时可能发生的第二次PGO写入。
        Force = true;
    }
#endif

    UE_LOG(LogAndroid, Log, TEXT("FAndroidMisc::RequestExit(%i)"), Force);
    if(GLog)
    {
        GLog->FlushThreadedLogs();
        GLog->Flush();
    }

    // 强制退出.
    if (Force) 
    {
#if USE_ANDROID_JNI
        AndroidThunkCpp_ForceQuit();
#else
        exit(1);
#endif
    }
    else
    {
        RequestEngineExit(TEXT("Android RequestExit"));
    }
}

// 初始化
void FAndroidMisc::PlatformInit()
{
    extern void AndroidSetupDefaultThreadAffinity();
    AndroidSetupDefaultThreadAffinity();

    (...)

    // 初始化JNI环境.
#if USE_ANDROID_JNI
    InitializeJavaEventReceivers();
    AndroidOnBackgroundBinding = FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddStatic(EnableJavaEventReceivers, false);
    AndroidOnForegroundBinding = FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddStatic(EnableJavaEventReceivers, true);
#endif

    // 初始化cpu温度传感器.
    InitCpuThermalSensor();

    (...)
}

// 销毁
void FAndroidMisc::PlatformTearDown()
{
    auto RemoveBinding = [](FCoreDelegates::FApplicationLifetimeDelegate& ApplicationLifetimeDelegate, FDelegateHandle& DelegateBinding)
    {
        if (DelegateBinding.IsValid())
        {
            ApplicationLifetimeDelegate.Remove(DelegateBinding);
            DelegateBinding.Reset();
        }
    };

    RemoveBinding(FCoreDelegates::ApplicationWillEnterBackgroundDelegate, AndroidOnBackgroundBinding);
    RemoveBinding(FCoreDelegates::ApplicationHasEnteredForegroundDelegate, AndroidOnForegroundBinding);
}

// 是否使用渲染线程
bool FAndroidMisc::UseRenderThread()
{
    // 如果由于命令行等原因,我们通常不想使用渲染线程.
    if (!FGenericPlatformMisc::UseRenderThread())
    {
        return false;
    }

    // 检查DeviceProfiles配置中的DisableThreadedRendering CVar,未来任何需要禁用线程渲染的设备都应该得到一个设备配置文件并使用此CVar.
    const IConsoleVariable *const CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.AndroidDisableThreadedRendering"));
    if (CVar && CVar->GetInt() != 0)
    {
        return false;
    }

    // 英伟达tegra双核处理器,即optimus 2x和xoom在运行多线程时发生崩溃。使用lg optimus 2x和motorola xoom测试的opengl(错误)无法处理多线程。
    if (FAndroidMisc::GetGPUFamily() == FString(TEXT("NVIDIA Tegra")) && FPlatformMisc::NumberOfCores() <= 2 && FAndroidMisc::GetGLVersion().StartsWith(TEXT("OpenGL ES 2.")))
    {
        return false;
    }

    // 带有2.x驱动程序的Vivante GC1000存在渲染线程问题
    if (FAndroidMisc::GetGPUFamily().StartsWith(TEXT("Vivante GC1000")) && FAndroidMisc::GetGLVersion().StartsWith(TEXT("OpenGL ES 2.")))
    {
        return false;
    }

    // 使用opengl在kindlefire(第1代)上使用多线程呈现缓冲区存在问题.
    if (FAndroidMisc::GetDeviceModel() == FString(TEXT("Kindle Fire")))
    {
        return false;
    }

    // 在使用opengl的多线程的三星s3 mini上,启动时swapbuffer排序存在问题.
    if (FAndroidMisc::GetDeviceModel() == FString(TEXT("GT-I8190L")))
    {
        return false;
    }

    return true;
}

// 触发奔溃处理
void FAndroidMisc::TriggerCrashHandler(ECrashContextType InType, const TCHAR* InErrorMessage, const TCHAR* OverrideCallstack)
{
    if (InType != ECrashContextType::Crash)
    {
        // 不会在致命信号期间刷新日志,malloccrash会导致死锁。
        if (GLog)
        {
            GLog->PanicFlushThreadedLogs();
            GLog->Flush();
        }
        if (GWarn)
        {
            GWarn->Flush();
        }
        if (GError)
        {
            GError->Flush();
        }
    }

    FAndroidCrashContext CrashContext(InType, InErrorMessage);

    if (OverrideCallstack)
    {
        CrashContext.SetOverrideCallstack(OverrideCallstack);
    }
    else
    {
        CrashContext.CaptureCrashInfo();
    }

    if (GCrashHandlerPointer)
    {
        GCrashHandlerPointer(CrashContext);
    }
    else
    {
        // 默认处理器.
        DefaultCrashHandler(CrashContext);
    }
}

// 设置崩溃处理器.
void FAndroidMisc::SetCrashHandler(void(*CrashHandler)(const FGenericCrashContext& Context))
{
#if ANDROID_HAS_RTSIGNALS
    GCrashHandlerPointer = CrashHandler;

    FFatalSignalHandler::Release();
    FThreadCallstackSignalHandler::Release();
    // 通过-1将使这些恢复,并且不会困住它们.
    if ((PTRINT)CrashHandler == -1)
    {
        return;
    }

    FFatalSignalHandler::Init();
    FThreadCallstackSignalHandler::Init();
#endif
}

// 是否有Vulkan驱动支持.
bool FAndroidMisc::HasVulkanDriverSupport()
{
#if !USE_ANDROID_JNI
    VulkanSupport = EDeviceVulkanSupportStatus::NotSupported;
    VulkanVersionString = TEXT("0.0.0");
#else
    // 此版本不检查VulkanRHI或被cvars禁用!
    if (VulkanSupport == EDeviceVulkanSupportStatus::Uninitialized)
    {
        // 假设没有
        VulkanSupport = EDeviceVulkanSupportStatus::NotSupported;
        VulkanVersionString = TEXT("0.0.0");

        // 检查libvulkan.so
        void* VulkanLib = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
        if (VulkanLib != nullptr)
        {
            // 如果是Nougat,我们可以检查Vulkan版本
            if (FAndroidMisc::GetAndroidBuildVersion() >= 24)
            {
                extern int32 AndroidThunkCpp_GetMetaDataInt(const FString& Key);
                int32 VulkanVersion = AndroidThunkCpp_GetMetaDataInt(TEXT("android.hardware.vulkan.version"));
                if (VulkanVersion >= UE_VK_API_VERSION)
                {
                    // 最后检查,尝试初始化实例
                    VulkanSupport = AttemptVulkanInit(VulkanLib);
                }
            }
            else
            {
                // 否则,我们需要尝试初始化实例
                VulkanSupport = AttemptVulkanInit(VulkanLib);
            }

            dlclose(VulkanLib);

            if (VulkanSupport == EDeviceVulkanSupportStatus::Supported)
            {
                UE_LOG(LogAndroid, Log, TEXT("VulkanRHI is available, Vulkan capable device detected."));
                return true;
            }
            (...)
#endif
    return VulkanSupport == EDeviceVulkanSupportStatus::Supported;
}

// Vulkan是否可用
bool FAndroidMisc::IsVulkanAvailable()
{
    (...)

    // 不存在VulkanRHI模块.
    if (!FModuleManager::Get().ModuleExists(TEXT("VulkanRHI")))
    {
        UE_LOG(LogAndroid, Log, TEXT("Vulkan not available as VulkanRHI not present."));
    }
    // 没有bSupportsVulkan或bSupportsVulkanSM5,Vulka不能作为打包的项目提供。
    else if (!(bSupportsVulkan || bSupportsVulkanSM5))
    {
        UE_LOG(LogAndroid, Log, TEXT("Vulkan not available as project packaged without bSupportsVulkan or bSupportsVulkanSM5."));
    }
    // Vulkan API检测由命令行选项禁用。
    else if (bVulkanDisabledCmdLine)
    {
        UE_LOG(LogAndroid, Log, TEXT("Vulkan API detection is disabled by a command line option."));
    }
    // Vulkan可用,但在AndroidRuntimeSettings中bDetectVulkanByDefault=False禁用了检测。使用-detectvulkan覆盖。
    else if (!bDetectVulkanByDefault && !bDetectVulkanCmdLine)
    {
        UE_LOG(LogAndroid, Log, TEXT("Vulkan available but detection disabled by bDetectVulkanByDefault=False in AndroidRuntimeSettings. Use -detectvulkan to override."));
    }
    else
    {
        CachedVulkanAvailable = 1;
    }

    return CachedVulkanAvailable == 1;
}

// 检测是否该使用Vulkan
bool FAndroidMisc::ShouldUseVulkan()
{
    static int CachedShouldUseVulkan = -1;

    if (CachedShouldUseVulkan == -1)
    {
        (...)
        // 如果Vulkan可用且控制台变量没有禁用Vulkan, 则可用.
        if (bVulkanAvailable && !bVulkanDisabledCVar)
        {
            CachedShouldUseVulkan = 1;
            UE_LOG(LogAndroid, Log, TEXT("VulkanRHI will be used!"));
        }
        (...)
    }

    return CachedShouldUseVulkan == 1;
}

// 是否该使用桌面Vulkan.
bool FAndroidMisc::ShouldUseDesktopVulkan()
{
    (...)

    // 如果VulkanSM5开启且VulkanSM5没有被禁用, 则可以.
    if (bVulkanSM5Enabled && !bVulkanSM5Disabled)
    {
        CachedShouldUseDesktopVulkan = 1;
        UE_LOG(LogAndroid, Log, TEXT("Vulkan SM5 RHI will be used!"));
    }
    
    (...)
}

// 获取Vulkan版本号.
FString FAndroidMisc::GetVulkanVersion()
{
    check(VulkanSupport != EDeviceVulkanSupportStatus::Uninitialized);
    return VulkanVersionString;
}

void FAndroidMisc::GetOSVersions(FString& out_OSVersionLabel, FString& out_OSSubVersionLabel)
{
    out_OSVersionLabel = TEXT("Android");
    out_OSSubVersionLabel = AndroidVersion;
}

FString FAndroidMisc::GetOSVersion()
{
    return AndroidVersion;
}

// 获取磁盘信息.
bool FAndroidMisc::GetDiskTotalAndFreeSpace(const FString& InPath, uint64& TotalNumberOfBytes, uint64& NumberOfFreeBytes)
{
    extern FString GExternalFilePath;
    struct statfs FSStat = { 0 };
    FTCHARToUTF8 Converter(*GExternalFilePath);
    int Err = statfs((ANSICHAR*)Converter.Get(), &FSStat);

    if (Err == 0)
    {
        TotalNumberOfBytes = FSStat.f_blocks * FSStat.f_bsize;
        NumberOfFreeBytes = FSStat.f_bavail * FSStat.f_bsize;
    }
    (...)

    return (Err == 0);
}

18.14.2.3 FIOSPlatformMisc

FIOSPlatformMisc实现了iOS平台的相关接口,部分接口分析如下:

// IOSPlatformMisc.cpp

// 预初始化.
void FIOSPlatformMisc::PlatformPreInit()
{
    FGenericPlatformMisc::PlatformPreInit();
    
    GIOSAppInfo.Init();
    
    // 关闭SIGPIPE崩溃
    signal(SIGPIPE, SIG_IGN);
}

// 初始化.
void FIOSPlatformMisc::PlatformInit()
{
    // 启动创建帧缓冲区的UI线程,要求“r.MobileContentScaleFactor”在创建之前可用,因此需要立即缓存该值。
    [[IOSAppDelegate GetDelegate] LoadMobileContentScaleFactor];
        
    FAppEntry::PlatformInit();

    // 增加同时打开的文件的最大数量.
    struct rlimit Limit;
    Limit.rlim_cur = OPEN_MAX;
    Limit.rlim_max = RLIM_INFINITY;
    int32 Result = setrlimit(RLIMIT_NOFILE, &Limit);
    check(Result == 0);

    (...)
    
    // 内存
    const FPlatformMemoryConstants& MemoryConstants = FPlatformMemory::GetConstants();
    GStartupFreeMemoryMB = GetFreeMemoryMB();

    // 创建Documents/<GameName>/Content目录,以便我们可以将其从iCloud备份中排除
    FString ResultStr = FPaths::ProjectContentDir();
    ResultStr.ReplaceInline(TEXT("../"), TEXT(""));
    (...)
    NSURL* URL = [NSURL fileURLWithPath : ResultStr.GetNSString()];
    if (![[NSFileManager defaultManager] fileExistsAtPath:[URL path]])
    {
        [[NSFileManager defaultManager] createDirectoryAtURL:URL withIntermediateDirectories : YES attributes : nil error : nil];
    }

    // 标记为不上传.
    NSError *error = nil;
    BOOL success = [URL setResourceValue : [NSNumber numberWithBool : YES] forKey : NSURLIsExcludedFromBackupKey error : &error];
    if (!success)
    {
        NSLog(@"Error excluding %@ from backup %@",[URL lastPathComponent], error);
    }

    (...)
}

// 退出.
void FIOSPlatformMisc::RequestExit(bool Force)
{
    if (Force)
    {
        FApplePlatformMisc::RequestExit(Force);
    }
    else
    {
        [[IOSAppDelegate GetDelegate] ForceExit];
    }
}

void FIOSPlatformMisc::RequestExitWithStatus(bool Force, uint8 ReturnCode)
{
    if (Force)
    {
        FApplePlatformMisc::RequestExit(Force);
    }
    else
    {
        (...)
        [[IOSAppDelegate GetDelegate] ForceExit];
    }
}

// 获取平台特性.
bool FIOSPlatformMisc::HasPlatformFeature(const TCHAR* FeatureName)
{
    if (FCString::Stricmp(FeatureName, TEXT("Metal")) == 0)
    {
        return [IOSAppDelegate GetDelegate].IOSView->bIsUsingMetal;
    }

    return FGenericPlatformMisc::HasPlatformFeature(FeatureName);
}

// 获取设备配置名.
const TCHAR* FIOSPlatformMisc::GetDefaultDeviceProfileName()
{
    static FString IOSDeviceProfileName;
    if (IOSDeviceProfileName.Len() == 0)
    {
        IOSDeviceProfileName = TEXT("IOS");
        FString DeviceIDString = GetIOSDeviceIDString();

        TArray<FString> Mappings;
        if (ensure(GConfig->GetSection(TEXT("IOSDeviceMappings"), Mappings, GDeviceProfilesIni)))
        {
            for (const FString& MappingString : Mappings)
            {
                FString MappingRegex, ProfileName;
                if (MappingString.Split(TEXT("="), &MappingRegex, &ProfileName))
                {
                    const FRegexPattern RegexPattern(MappingRegex);
                    FRegexMatcher RegexMatcher(RegexPattern, *DeviceIDString);
                    if (RegexMatcher.FindNext())
                    {
                        IOSDeviceProfileName = ProfileName;
                        break;
                    }
                }
                
                (...)
            }
        }
    }

    return *IOSDeviceProfileName;
}

// 获取默认的栈大小.
int FIOSPlatformMisc::GetDefaultStackSize()
{
    return 512 * 1024;
}

// 系统版本.
void FIOSPlatformMisc::GetOSVersions(FString& out_OSVersionLabel, FString& out_OSSubVersionLabel)
{
#if PLATFORM_TVOS
    out_OSVersionLabel = TEXT("TVOS");
#else
    out_OSVersionLabel = TEXT("IOS");
#endif
    NSOperatingSystemVersion IOSVersion;
    IOSVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
    out_OSSubVersionLabel = FString::Printf(TEXT("%ld.%ld.%ld"), IOSVersion.majorVersion, IOSVersion.minorVersion, IOSVersion.patchVersion);
}

// 磁盘信息.
bool FIOSPlatformMisc::GetDiskTotalAndFreeSpace(const FString& InPath, uint64& TotalNumberOfBytes, uint64& NumberOfFreeBytes)
{
    bool GetValueSuccess = false;
    
    NSNumber *FreeBytes = nil;
    NSURL *URL = [NSURL fileURLWithPath : NSHomeDirectory()];
    GetValueSuccess = [URL getResourceValue : &FreeBytes forKey : NSURLVolumeAvailableCapacityForImportantUsageKey error : nil];
    if (FreeBytes)
    {
        NumberOfFreeBytes = [FreeBytes longLongValue];
    }
    
    NSNumber *TotalBytes = nil;
    GetValueSuccess = GetValueSuccess &&[URL getResourceValue : &TotalBytes forKey : NSURLVolumeTotalCapacityKey error : nil];
    if (TotalBytes)
    {
        TotalNumberOfBytes = [TotalBytes longLongValue];
    }
    
    if (GetValueSuccess && (NumberOfFreeBytes > 0) && (TotalNumberOfBytes > 0))
    {
        return true;
    }
    
    (...)
}

// 工程版本.
FString FIOSPlatformMisc::GetProjectVersion()
{
    NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
    FString localVersionString = FString(infoDictionary[@"CFBundleShortVersionString"]);
    return localVersionString;
}

// 构建数字.
FString FIOSPlatformMisc::GetBuildNumber()
{
    NSDictionary* infoDictionary = [[NSBundle mainBundle]infoDictionary];
    FString BuildString = FString(infoDictionary[@"CFBundleVersion"]);
    return BuildString;
}

// 设置存储值.
bool FIOSPlatformMisc::SetStoredValue(const FString& InStoreId, const FString& InSectionName, const FString& InKeyName, const FString& InValue)
{
    NSUserDefaults* UserSettings = [NSUserDefaults standardUserDefaults];
    NSString* StoredValue = [NSString stringWithFString:InValue];
    [UserSettings setObject:StoredValue forKey:MakeStoredValueKeyName(InSectionName, InKeyName)];

    return true;
}

// 获取存储值.
bool FIOSPlatformMisc::GetStoredValue(const FString& InStoreId, const FString& InSectionName, const FString& InKeyName, FString& OutValue)
{
    NSUserDefaults* UserSettings = [NSUserDefaults standardUserDefaults];
    NSString* StoredValue = [UserSettings objectForKey:MakeStoredValueKeyName(InSectionName, InKeyName)];
    if (StoredValue != nil)
    {
        OutValue = StoredValue;
        return true;
    }
    return false;
}

// 设置崩溃处理器.
void FIOSPlatformMisc::SetCrashHandler(void (* CrashHandler)(const FGenericCrashContext& Context))
{
    SCOPED_AUTORELEASE_POOL;
    
    GCrashHandlerPointer = CrashHandler;
    
    if (!FIOSApplicationInfo::CrashReporter && !FIOSApplicationInfo::CrashMalloc)
    {
        // 配置崩溃处理程序malloc区域,为其自身保留少量内存.
        FIOSApplicationInfo::CrashMalloc = new FIOSMallocCrashHandler(4*1024*1024);
        
        PLCrashReporterConfig* Config = [[[PLCrashReporterConfig alloc] initWithSignalHandlerType: PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy: PLCrashReporterSymbolicationStrategyNone crashReportFolder: FIOSApplicationInfo::TemporaryCrashReportFolder().GetNSString() crashReportName: FIOSApplicationInfo::TemporaryCrashReportName().GetNSString()] autorelease];
        FIOSApplicationInfo::CrashReporter = [[PLCrashReporter alloc] initWithConfiguration: Config];
        
        PLCrashReporterCallbacks CrashReportCallback = {
            .version = 0,
            .context = nullptr,
            .handleSignal = PLCrashReporterHandler
        };
        
        [FIOSApplicationInfo::CrashReporter setCrashCallbacks: &CrashReportCallback];
        
        NSError* Error = nil;
        if ([FIOSApplicationInfo::CrashReporter enableCrashReporterAndReturnError: &Error])
        {
           // 无操作.
        }
        else
        {
            // 崩溃处理器.
            struct sigaction Action;
            FMemory::Memzero(&Action, sizeof(struct sigaction));
            // 保存崩溃处理器.
            Action.sa_sigaction = PlatformCrashHandler;
            sigemptyset(&Action.sa_mask);
            Action.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
            
            sigaction(SIGQUIT, &Action, NULL);
            sigaction(SIGILL, &Action, NULL);
            sigaction(SIGEMT, &Action, NULL);
            sigaction(SIGFPE, &Action, NULL);
            sigaction(SIGBUS, &Action, NULL);
            sigaction(SIGSEGV, &Action, NULL);
            sigaction(SIGSYS, &Action, NULL);
            sigaction(SIGABRT, &Action, NULL);
        }
    }
}

需要注意的是,苹果的操作系统(Mac、iOS)混合使用了C++和Object C,所以上面的有些语句跟C++差异比较明显,不要对此感到奇怪,也不要觉得是语法错误。

18.14.2.4 FUnixPlatformMisc

FUnixPlatformMisc实现了Unix系统的接口,部分代码分析如下:

// UnixPlatformMisc.cpp

// 预初始化
void FUnixPlatformMisc::PlatformPreInit()
{
    FGenericPlatformMisc::PlatformPreInit();

    UnixCrashReporterTracker::PreInit();
}

void FUnixPlatformMisc::PlatformInit()
{
    // 安装特定于平台的信号处理程序.
    InstallChildExitedSignalHanlder();

    // IsFirstInstance()不仅仅用于日志记录,实际上是第一个.
    bool bFirstInstance = FPlatformProcess::IsFirstInstance();
    bool bIsNullRHI = !FApp::CanEverRender();

    bool bPreloadedModuleSymbolFile = FParse::Param(FCommandLine::Get(), TEXT("preloadmodulesymbols"));

    UnixPlatForm_CheckIfKSMUsable();

    FString GPUInfo = GetGPUInfo();

    (...)

    FPlatformTime::PrintCalibrationLog();

    (...)

    if (bPreloadedModuleSymbolFile)
    {
        UnixPlatformStackWalk_PreloadModuleSymbolFile();
    }

    if (FPlatformMisc::HasBeenStartedRemotely() || FPlatformMisc::IsDebuggerPresent())
    {
        // 立即打印输出
        setvbuf(stdout, NULL, _IONBF, 0);
    }

    if (FParse::Param(FCommandLine::Get(), TEXT("norandomguids")))
    {
        SysGetRandomSupported = 0;
    }

    // 此符号用于调试,但在启用LTO的情况下,会被剥离,因为没有任何东西在使用它让我们在这里使用它来记录它在VeryVerbose设置下是否有效.
    extern uint8** GNameBlocksDebug;
    if (GNameBlocksDebug)
    {
        UE_LOG(LogInit, VeryVerbose, TEXT("GNameBlocksDebug Valid - %i"), !!GNameBlocksDebug);
    }
}

// 销毁.
void FUnixPlatformMisc::PlatformTearDown()
{
    // 我们请求关闭信号,因此无法打印。
    if (GDeferedExitLogging)
    {
        uint8 OverriddenErrorLevel = 0;
        if (FPlatformMisc::HasOverriddenReturnCode(&OverriddenErrorLevel))
        {
            UE_LOG(LogCore, Log, TEXT("FUnixPlatformMisc::RequestExit(bForce=false, ReturnCode=%d)"), OverriddenErrorLevel);
        }
        else
        {
            UE_LOG(LogCore, Log, TEXT("FUnixPlatformMisc::RequestExit(false)"));
        }
    }

    UnixPlatformStackWalk_UnloadPreloadedModuleSymbol();
    FPlatformProcess::CeaseBeingFirstInstance();
}

// 低级别输出调试信息.
void FUnixPlatformMisc::LowLevelOutputDebugString(const TCHAR *Message)
{
    static_assert(PLATFORM_USE_LS_SPEC_FOR_WIDECHAR, "Check printf format");
    fprintf(stderr, "%s", TCHAR_TO_UTF8(Message));    // there's no good way to implement that really
}

// OS版本信息.
void FUnixPlatformMisc::GetOSVersions(FString& out_OSVersionLabel, FString& out_OSSubVersionLabel)
{
    out_OSVersionLabel = FString(TEXT("GenericLinuxVersion"));
    out_OSSubVersionLabel = GetKernelVersion();
    TMap<FString, FString> OsInfo = ReadConfigurationFile(TEXT("/etc/os-release"));
    if (OsInfo.Num() > 0)
    {
        FString* VersionAddress = OsInfo.Find(TEXT("PRETTY_NAME"));
        if (VersionAddress)
        {
            FString* VersionNameAddress = nullptr;
            if (VersionAddress->Equals(TEXT("Linux")))
            {
                VersionNameAddress = OsInfo.Find(TEXT("NAME"));
                if (VersionNameAddress != nullptr)
                {
                    VersionAddress = VersionNameAddress;
                }
            }

            out_OSVersionLabel = FString(*VersionAddress);
        }
    }
    (...)
}

// OS标识符.
FString FUnixPlatformMisc::GetOperatingSystemId()
{
    (...)
    
    int OsGuidFile = open("/etc/machine-id", O_RDONLY);
    if (OsGuidFile != -1)
    {
        char Buffer[PlatformMiscLimits::MaxOsGuidLength + 1] = {0};
        ssize_t ReadBytes = read(OsGuidFile, Buffer, sizeof(Buffer) - 1);

        if (ReadBytes > 0)
        {
            CachedResult = UTF8_TO_TCHAR(Buffer);
        }

        close(OsGuidFile);
    }

    (...)
}

// 获取磁盘信息.
bool FUnixPlatformMisc::GetDiskTotalAndFreeSpace(const FString& InPath, uint64& TotalNumberOfBytes, uint64& NumberOfFreeBytes)
{
    struct statfs FSStat = { 0 };
    FTCHARToUTF8 Converter(*InPath);
    int Err = statfs((ANSICHAR*)Converter.Get(), &FSStat);
    if (Err == 0)
    {
        TotalNumberOfBytes = FSStat.f_blocks * FSStat.f_bsize;
        NumberOfFreeBytes = FSStat.f_bavail * FSStat.f_bsize;
    }
    (...)
    return (Err == 0);
}

// 设置存储值.
bool FUnixPlatformMisc::SetStoredValues(const FString& InStoreId, const FString& InSectionName, const TMap<FString, FString>& InKeyValues)
{
    const FString ConfigPath = FString(FPlatformProcess::ApplicationSettingsDir()) / InStoreId / FString(TEXT("KeyValueStore.ini"));

    FConfigFile ConfigFile;
    ConfigFile.Read(ConfigPath);

    for (auto const& InKeyValue : InKeyValues)
    {
        FConfigSection& Section = ConfigFile.FindOrAdd(InSectionName);

        FConfigValue& KeyValue = Section.FindOrAdd(*InKeyValue.Key);
        KeyValue = FConfigValue(InKeyValue.Value);
    }

    ConfigFile.Dirty = true;
    return ConfigFile.Write(ConfigPath);
}

值得一提的是,Linux平台的实现和Unix完全一样,未作任何的额外修改。

18.14.2.5 FPlatformMisc

有着UE开发经验或者细心的同学肯定发现了,我们在使用平台相关的接口时,使用的是FPlatformMisc而不是FGenericPlatformMisc,那么它们的关系是怎样的呢?为了解开谜底,还需要从以下代码中获取答案:

// PreprocessorHelpers.h
#define COMPILED_PLATFORM_HEADER(Suffix) PREPROCESSOR_TO_STRING(PREPROCESSOR_JOIN(PLATFORM_HEADER_NAME/PLATFORM_HEADER_NAME, Suffix))

// PlatformMisc.h
#include "GenericPlatform/GenericPlatformMisc.h"
#include COMPILED_PLATFORM_HEADER(PlatformMisc.h)

从上面的代码片段可以看出,UE使用了COMPILED_PLATFORM_HEADER(PlatformMisc.h)的宏生成了当前系统对应的文件路径,例如:

Windows: "Windows/WindowsPlatformMisc.h"
Android: "Android/AndroidPlatformMisc.h"
IOS    : "IOS/IOSPlatformMisc.h"
Unix   : "Unix/UnixPlatformMisc.h"
Mac    : "Mac/MacPlatformMisc.h"

然后每个平台的XXXPlatformMisc.h中都有一句typedef FXXXPlatformMisc FPlatformMisc,例如:

// WindowsPlatformMisc.h
typedef FWindowsPlatformMisc FPlatformMisc;

// AndroidPlatformMisc.h
typedef FAndroidMisc FPlatformMisc;

// IOSPlatformMisc.h
typedef FIOSPlatformMisc FPlatformMisc;

// LinuxPlatformMisc.h
typedef FLinuxPlatformMisc FPlatformMisc;

有了以上类型重定义,从而实现了FGenericPlatformMisc的不同子类使用统一的FPlatformMisc类型,其它模块就可以使用统一的类型FPlatformMisc访问OS相关的接口,实现跨平台的目的。

顺带提一下,COMPILED_PLATFORM_HEADER()还用于跨平台的其它文件或模块中:

#include COMPILED_PLATFORM_HEADER(PlatformAGXConfig.h)
#include COMPILED_PLATFORM_HEADER(PlatformApplicationMisc.h)
#include COMPILED_PLATFORM_HEADER(PlatformSplash.h)
#include COMPILED_PLATFORM_HEADER(PlatformSurvey.h)
#include COMPILED_PLATFORM_HEADER(PlatformModuleDiagnostics.h)
#include COMPILED_PLATFORM_HEADER(CriticalSection.h)
#include COMPILED_PLATFORM_HEADER(PlatformCompilerPreSetup.h)
#include COMPILED_PLATFORM_HEADER(PlatformCompilerSetup.h)
#include COMPILED_PLATFORM_HEADER(Platform.h)
#include COMPILED_PLATFORM_HEADER(PlatformAffinity.h)
#include COMPILED_PLATFORM_HEADER(PlatformAtomics.h)
#include COMPILED_PLATFORM_HEADER(PlatformCrashContext.h)
#include COMPILED_PLATFORM_HEADER(PlatformFile.h)
#include COMPILED_PLATFORM_HEADER(PlatformMath.h)
#include COMPILED_PLATFORM_HEADER(PlatformMemory.h)
#include COMPILED_PLATFORM_HEADER(PlatformOutputDevices.h)
#include COMPILED_PLATFORM_HEADER(PlatformProcess.h)
#include COMPILED_PLATFORM_HEADER(PlatformProperties.h)
#include COMPILED_PLATFORM_HEADER(PlatformStackWalk.h)
#include COMPILED_PLATFORM_HEADER(PlatformString.h)
#include COMPILED_PLATFORM_HEADER(PlatformTime.h)
#include COMPILED_PLATFORM_HEADER(PlatformTLS.h)
#include COMPILED_PLATFORM_HEADER(PlatformHttp.h)
#include COMPILED_PLATFORM_HEADER(PlatformBackgroundHttp.h)
#include COMPILED_PLATFORM_HEADER(OpenGLDrvPrivate.h)
#include COMPILED_PLATFORM_HEADER_WITH_PREFIX(Apple/Platform, PlatformDynamicRHI.h)
#include COMPILED_PLATFORM_HEADER(StaticShaderPlatform.inl)
#include COMPILED_PLATFORM_HEADER(StaticFeatureLevel.inl)
#include COMPILED_PLATFORM_HEADER(DataDrivenShaderPlatformInfo.inl)
#include COMPILED_PLATFORM_HEADER_WITH_PREFIX(Framework/Text, PlatformTextField.h)

涉及了进程、原子操作、临界区、堆栈遍历、TLS、内存、文件、崩溃上下文、亲缘性、编译器、应用程序、数学、HTTP、Shader、RHI等等模块。后续小节会对部分重要模块进行分析。

18.14.3 FGenericPlatformApplicationMisc

FGenericPlatformApplicationMisc的跨平台和实现机制和FGenericPlatformMisc类似,下面看看它的声明:

// GenericPlatformApplicationMisc.h

struct APPLICATIONCORE_API FGenericPlatformApplicationMisc
{
    // App声明周期.
    static class GenericApplication* CreateApplication();
    static void PreInit();
    static void Init();
    static void PostInit();
    static void TearDown();

    // 模块/上下文/设备
    static void LoadPreInitModules();
    static void LoadStartupModules();
    static FOutputDeviceConsole* CreateConsoleOutputDevice();
    static FOutputDeviceError* GetErrorOutputDevice();
    static FFeedbackContext* GetFeedbackContext();
    static bool IsThisApplicationForeground();    
    static void RequestMinimize();
    static bool RequiresVirtualKeyboard();
    static void PumpMessages(bool bFromMainLoop);

    // 屏幕/窗口
    static void PreventScreenSaver();
    static bool IsScreensaverEnabled();
    static bool ControlScreensaver(EScreenSaverAction Action);
    static struct FLinearColor GetScreenPixelColor(const FVector2D& InScreenPos, float InGamma);
    static bool GetWindowTitleMatchingText(const TCHAR* TitleStartsWith, FString& OutTitle);
    static void SetHighDPIMode();
    static float GetDPIScaleFactorAtPoint(float X, float Y);
    static bool IsHighDPIAwarenessEnabled();
    static bool AnchorWindowWindowPositionTopLeft();
    static EScreenPhysicalAccuracy GetPhysicalScreenDensity(int32& OutScreenDensity);
    static EScreenPhysicalAccuracy ComputePhysicalScreenDensity(int32& OutScreenDensity);
    static EScreenPhysicalAccuracy ConvertInchesToPixels(T Inches, T2& OutPixels);
    static EScreenPhysicalAccuracy ConvertPixelsToInches(T Pixels, T2& OutInches);

    // 控制器
    static void SetGamepadsAllowed(bool bAllowed);
    static void SetGamepadsBlockDeviceFeedback(bool bAllowed);
    static void ResetGamepadAssignments();
    static void ResetGamepadAssignmentToController(int32 ControllerId);
    static bool IsControllerAssignedToGamepad(int32 ControllerId);
    static FString GetGamepadControllerName(int32 ControllerId);
    static class UTexture2D* GetGamepadButtonGlyph(...);
    static void EnableMotionData(bool bEnable);
    static bool IsMotionDataEnabled();
    
    // 其它操作
    static void ClipboardCopy(const TCHAR* Str);
    static void ClipboardPaste(class FString& Dest);
    
    (...)
};

由此可见,FGenericPlatformApplicationMisc主要是对应用程序的声明周期、窗口、屏幕、设备、控制器等提供统一的接口,而具体的实现由不同的平台子类实现。下面小节分析部分平台的部分接口。

18.14.3.1 FWindowsPlatformApplicationMisc

FWindowsPlatformApplicationMisc实现Windows平台应用程序的接口:

// WindowsPlatformApplicationMisc.cpp

// 创建应用程序
GenericApplication* FWindowsPlatformApplicationMisc::CreateApplication()
{
    HICON AppIconHandle = LoadIcon( hInstance, MAKEINTRESOURCE( GetAppIcon() ) );
    if( AppIconHandle == NULL )
    {
        AppIconHandle = LoadIcon( (HINSTANCE)NULL, IDI_APPLICATION ); 
    }

    // 创建窗口应用程序.
    return FWindowsApplication::CreateWindowsApplication( hInstance, AppIconHandle );
}

void FWindowsPlatformApplicationMisc::PreInit()
{
    FApp::SetHasFocusFunction(&FWindowsPlatformApplicationMisc::IsThisApplicationForeground);
}

void FWindowsPlatformApplicationMisc::LoadStartupModules()
{
    FModuleManager::Get().LoadModule(TEXT("HeadMountedDisplay"));
    (...)
}

class FFeedbackContext* FWindowsPlatformApplicationMisc::GetFeedbackContext()
{
    (...)
    return FPlatformOutputDevices::GetFeedbackContext();
}

// 注入消息.
void FWindowsPlatformApplicationMisc::PumpMessages(bool bFromMainLoop)
{
    const bool bSetPumpingMessages = !GPumpingMessages;
    if (bSetPumpingMessages)
    {
        GPumpingMessages = true;
    }

    ON_SCOPE_EXIT
    {
        if (bSetPumpingMessages)
        {
            GPumpingMessages = false;
        }
    };

    if (!bFromMainLoop)
    {
        FPlatformMisc::PumpMessagesOutsideMainLoop();
        return;
    }

    GPumpingMessagesOutsideOfMainLoop = false;
    WinPumpMessages();

    // 确定应用程序是否具有焦点.
    bool bHasFocus = FApp::HasFocus();
    static bool bHadFocus = false;

    (...)

#if !UE_SERVER
    // 对于非编辑器客户端,记录活动窗口是否处于焦点.
    if( bHadFocus != bHasFocus )
    {
        FGenericCrashContext::SetEngineData(TEXT("Platform.AppHasFocus"), bHasFocus ? TEXT("true") : TEXT("false"));
    }
#endif

    bHadFocus = bHasFocus;

    // 如果是我们的窗口,允许声音,否则应用乘数.
    FApp::SetVolumeMultiplier( bHasFocus ? 1.0f : FApp::GetUnfocusedVolumeMultiplier() );
}

// 设置高DPI模式.
void FWindowsPlatformApplicationMisc::SetHighDPIMode()
{
    if (IsHighDPIAwarenessEnabled())
    {
        if (void* ShCoreDll = FPlatformProcess::GetDllHandle(TEXT("shcore.dll")))
        {
            typedef enum _PROCESS_DPI_AWARENESS {
                PROCESS_DPI_UNAWARE = 0,
                PROCESS_SYSTEM_DPI_AWARE = 1,
                PROCESS_PER_MONITOR_DPI_AWARE = 2
            } PROCESS_DPI_AWARENESS;

            // 从shcore.dll获取SetProcessDpiAwarenessProc接口地址.
            typedef HRESULT(STDAPICALLTYPE *SetProcessDpiAwarenessProc)(PROCESS_DPI_AWARENESS Value);
            SetProcessDpiAwarenessProc SetProcessDpiAwareness = (SetProcessDpiAwarenessProc)FPlatformProcess::GetDllExport(ShCoreDll, TEXT("SetProcessDpiAwareness"));
            GetDpiForMonitor = (GetDpiForMonitorProc)FPlatformProcess::GetDllExport(ShCoreDll, TEXT("GetDpiForMonitor"));

            typedef HRESULT(STDAPICALLTYPE *GetProcessDpiAwarenessProc)(HANDLE hProcess, PROCESS_DPI_AWARENESS* Value);
            GetProcessDpiAwarenessProc GetProcessDpiAwareness = (GetProcessDpiAwarenessProc)FPlatformProcess::GetDllExport(ShCoreDll, TEXT("GetProcessDpiAwareness"));

            if (SetProcessDpiAwareness && GetProcessDpiAwareness && !IsRunningCommandlet() && !FApp::IsUnattended())
            {
                PROCESS_DPI_AWARENESS CurrentAwareness = PROCESS_DPI_UNAWARE;

                GetProcessDpiAwareness(nullptr, &CurrentAwareness);

                if (CurrentAwareness != PROCESS_PER_MONITOR_DPI_AWARE)
                {
                    UE_LOG(LogInit, Log, TEXT("Setting process to per monitor DPI aware"));
                    HRESULT Hr = SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); // 如果我们处于任何无头显模式,则不在乎警告 
                    if (Hr != S_OK)
                    {
                        UE_LOG(LogInit, Warning, TEXT("SetProcessDpiAwareness failed.  Error code %x"), Hr);
                    }
                }
            }

            FPlatformProcess::FreeDllHandle(ShCoreDll);
        }
        else if (void* User32Dll = FPlatformProcess::GetDllHandle(TEXT("user32.dll")))
        {
            // user32.dll获取SetProcessDpiAware接口地址.
            typedef BOOL(WINAPI *SetProcessDpiAwareProc)(void);
            SetProcessDpiAwareProc SetProcessDpiAware = (SetProcessDpiAwareProc)FPlatformProcess::GetDllExport(User32Dll, TEXT("SetProcessDPIAware"));

            if (SetProcessDpiAware && !IsRunningCommandlet() && !FApp::IsUnattended())
            {
                UE_LOG(LogInit, Log, TEXT("Setting process to DPI aware"));

                BOOL Result = SetProcessDpiAware();
                if (Result == 0)
                {
                    UE_LOG(LogInit, Warning, TEXT("SetProcessDpiAware failed"));
                }
            }

            FPlatformProcess::FreeDllHandle(User32Dll);
        }
    }
}

// 获取显示器DPI.
int32 FWindowsPlatformApplicationMisc::GetMonitorDPI(const FMonitorInfo& MonitorInfo)
{
    int32 DisplayDPI = 96;

    if (IsHighDPIAwarenessEnabled())
    {
        if (GetDpiForMonitor)
        {
            RECT MonitorDim;
            MonitorDim.left = MonitorInfo.DisplayRect.Left;
            MonitorDim.top = MonitorInfo.DisplayRect.Top;
            MonitorDim.right = MonitorInfo.DisplayRect.Right;
            MonitorDim.bottom = MonitorInfo.DisplayRect.Bottom;

            HMONITOR Monitor = MonitorFromRect(&MonitorDim, MONITOR_DEFAULTTONEAREST);
            if (Monitor)
            {
                uint32 DPIX = 0;
                uint32 DPIY = 0;
                // 获取显示器DPI.
                if (SUCCEEDED(GetDpiForMonitor(Monitor, 0, &DPIX, &DPIY)))
                {
                    DisplayDPI = DPIX;
                }
            }
        }
        else
        {
            HDC Context = GetDC(nullptr);
            DisplayDPI = GetDeviceCaps(Context, LOGPIXELSX);
            ReleaseDC(nullptr, Context);
        }
    }

    return DisplayDPI;
}

// 获取点的DPI比例因子
float FWindowsPlatformApplicationMisc::GetDPIScaleFactorAtPoint(float X, float Y)
{
    float Scale = 1.0f;

    if (IsHighDPIAwarenessEnabled())
    {
        if (GetDpiForMonitor)
        {
            POINT Position = { static_cast<LONG>(X), static_cast<LONG>(Y) };
            HMONITOR Monitor = MonitorFromPoint(Position, MONITOR_DEFAULTTONEAREST);
            if (Monitor)
            {
                uint32 DPIX = 0;
                uint32 DPIY = 0;
                if (SUCCEEDED(GetDpiForMonitor(Monitor, 0, &DPIX, &DPIY)))
                {
                    Scale = (float)DPIX / 96.0f;
                }
            }
        }
        else
        {
            HDC Context = GetDC(nullptr);
            int32 DPI = GetDeviceCaps(Context, LOGPIXELSX);
            Scale = (float)DPI / 96.0f;
            ReleaseDC(nullptr, Context);
        }
    }

    return Scale;
}

18.14.3.2 FAndroidApplicationMisc

FAndroidApplicationMisc实现Android平台应用程序的接口:

// AndroidPlatformApplicationMisc.cpp

void FAndroidApplicationMisc::LoadPreInitModules()
{
    FModuleManager::Get().LoadModule(TEXT("OpenGLDrv"));
#if USE_ANDROID_AUDIO
    FModuleManager::Get().LoadModule(TEXT("AndroidAudio"));
    FModuleManager::Get().LoadModule(TEXT("AudioMixerAndroid"));
#endif
}

class FFeedbackContext* FAndroidApplicationMisc::GetFeedbackContext()
{
    static FAndroidFeedbackContext Singleton;
    return &Singleton;
}

class FOutputDeviceError* FAndroidApplicationMisc::GetErrorOutputDevice()
{
    static FAndroidErrorOutputDevice Singleton;
    return &Singleton;
}

GenericApplication* FAndroidApplicationMisc::CreateApplication()
{
    return FAndroidApplication::CreateAndroidApplication();
}

void FAndroidApplicationMisc::SetGamepadsAllowed(bool bAllowed)
{
    if (FAndroidInputInterface* InputInterface = (FAndroidInputInterface*)FAndroidApplication::Get()->GetInputInterface())
    {
        InputInterface->SetGamepadsAllowed(bAllowed);
    }
}

// 计算物理屏幕的密度.
ScreenPhysicalAccuracy FAndroidApplicationMisc::ComputePhysicalScreenDensity(int32& OutScreenDensity)
{
    FString MyDeviceModel = FPlatformMisc::GetDeviceModel();

    TArray<FString> DeviceStrings;
    GConfig->GetArray(TEXT("DeviceScreenDensity"), TEXT("Devices"), DeviceStrings, GEngineIni);

    TArray<FScreenDensity> Devices;
    for ( const FString& DeviceString : DeviceStrings )
    {
        FScreenDensity DensityEntry;
        if ( DensityEntry.InitFromString(DeviceString) )
        {
            Devices.Add(DensityEntry);
        }
    }

    for ( const FScreenDensity& Device : Devices )
    {
        if ( Device.IsMatch(MyDeviceModel) )
        {
            OutScreenDensity = Device.Density * GetWindowUpscaleFactor();
            return EScreenPhysicalAccuracy::Truth;
        }
    }

    // JNI模式
#if USE_ANDROID_JNI
    extern FString AndroidThunkCpp_GetMetaDataString(const FString& Key);
    FString DPIStrings = AndroidThunkCpp_GetMetaDataString(TEXT("unreal.displaymetrics.dpi"));
    TArray<FString> DPIValues;
    DPIStrings.ParseIntoArray(DPIValues, TEXT(","));

    float xdpi, ydpi;
    LexFromString(xdpi, *DPIValues[0]);
    LexFromString(ydpi, *DPIValues[1]);

    OutScreenDensity = ( xdpi + ydpi ) / 2.0f;

    if ( OutScreenDensity <= 0 || OutScreenDensity > 2000 )
    {
        return EScreenPhysicalAccuracy::Unknown;
    }

    OutScreenDensity *= GetWindowUpscaleFactor();
    return EScreenPhysicalAccuracy::Approximation;
#else
    return EScreenPhysicalAccuracy::Unknown;
#endif
}

18.14.3.3 FIOSPlatformApplicationMisc

FIOSPlatformApplicationMisc实现IOS平台应用程序的接口:

// IOSPlatformApplicationMisc.cpp

void FIOSPlatformApplicationMisc::LoadPreInitModules()
{
    FModuleManager::Get().LoadModule(TEXT("IOSAudio"));
    FModuleManager::Get().LoadModule(TEXT("AudioMixerAudioUnit"));
}

class FFeedbackContext* FIOSPlatformApplicationMisc::GetFeedbackContext()
{
    static FIOSFeedbackContext Singleton;
    return &Singleton;
}

class FOutputDeviceError* FIOSPlatformApplicationMisc::GetErrorOutputDevice()
{
    static FIOSErrorOutputDevice Singleton;
    return &Singleton;
}

// 创建应用程序.
GenericApplication* FIOSPlatformApplicationMisc::CreateApplication()
{
    CachedApplication = FIOSApplication::CreateIOSApplication();
    return CachedApplication;
}

void FIOSPlatformApplicationMisc::EnableMotionData(bool bEnable)
{
    FIOSInputInterface* InputInterface = (FIOSInputInterface*)CachedApplication->GetInputInterface();
    return InputInterface->EnableMotionData(bEnable);
}

bool FIOSPlatformApplicationMisc::IsMotionDataEnabled()
{
    const FIOSInputInterface* InputInterface = (const FIOSInputInterface*)CachedApplication->GetInputInterface();
    return InputInterface->IsMotionDataEnabled();
}

// 剪切板
void FIOSPlatformApplicationMisc::ClipboardCopy(const TCHAR* Str)
{
#if !PLATFORM_TVOS
    CFStringRef CocoaString = FPlatformString::TCHARToCFString(Str);
    UIPasteboard* Pasteboard = [UIPasteboard generalPasteboard];
    [Pasteboard setString:(NSString*)CocoaString];
#endif
}

void FIOSPlatformApplicationMisc::ClipboardPaste(class FString& Result)
{
#if !PLATFORM_TVOS
    UIPasteboard* Pasteboard = [UIPasteboard generalPasteboard];
    NSString* CocoaString = [Pasteboard string];
    if(CocoaString)
    {
        TArray<TCHAR> Ch;
        Ch.AddUninitialized([CocoaString length] + 1);
        FPlatformString::CFStringToTCHAR((CFStringRef)CocoaString, Ch.GetData());
        Result = Ch.GetData();
    }
    else
    {
        Result = TEXT("");
    }
#endif
}

18.14.3.4 FLinuxPlatformApplicationMisc

FLinuxPlatformApplicationMisc实现Linux平台应用程序的接口:

// LinuxPlatformApplicationMisc.cpp

GenericApplication* FLinuxPlatformApplicationMisc::CreateApplication()
{
    return FLinuxApplication::CreateLinuxApplication();
}

void FLinuxPlatformApplicationMisc::PreInit()
{
    MessageBoxExtCallback = MessageBoxExtImpl;
    FApp::SetHasFocusFunction(&FLinuxPlatformApplicationMisc::IsThisApplicationForeground);
}

void FLinuxPlatformApplicationMisc::Init()
{
    // skip for servers and programs, unless they request later
    bool bIsNullRHI = !FApp::CanEverRender();
    if (!IS_PROGRAM && !bIsNullRHI)
    {
        InitSDL();
    }

    FGenericPlatformApplicationMisc::Init();

    UngrabAllInputCallback = UngrabAllInputImpl;
}

void FLinuxPlatformApplicationMisc::LoadPreInitModules()
{
#if WITH_EDITOR
    FModuleManager::Get().LoadModule(TEXT("OpenGLDrv"));
#endif // WITH_EDITOR
}

void FLinuxPlatformApplicationMisc::LoadStartupModules()
{
#if !IS_PROGRAM && !UE_SERVER
    FModuleManager::Get().LoadModule(TEXT("AudioMixerSDL"));    // added in Launch.Build.cs for non-server targets
    FModuleManager::Get().LoadModule(TEXT("HeadMountedDisplay"));
#endif // !IS_PROGRAM && !UE_SERVER

#if defined(WITH_STEAMCONTROLLER) && WITH_STEAMCONTROLLER
    FModuleManager::Get().LoadModule(TEXT("SteamController"));
#endif // WITH_STEAMCONTROLLER

#if WITH_EDITOR
    FModuleManager::Get().LoadModule(TEXT("SourceCodeAccess"));
#endif    //WITH_EDITOR
}

void FLinuxPlatformApplicationMisc::TearDown()
{
    FGenericPlatformApplicationMisc::TearDown();

    if (GInitializedSDL)
    {
        UE_LOG(LogInit, Log, TEXT("Tearing down SDL."));
        SDL_Quit();
        GInitializedSDL = false;

        MessageBoxExtCallback = nullptr;
        UngrabAllInputCallback = nullptr;
    }
}

// 注入消息.
void FLinuxPlatformApplicationMisc::PumpMessages( bool bFromMainLoop )
{
    if (GInitializedSDL && bFromMainLoop)
    {
        if( LinuxApplication )
        {
            LinuxApplication->SaveWindowPropertiesForEventLoop();

            SDL_Event event;

            while (SDL_PollEvent(&event))
            {
                LinuxApplication->AddPendingEvent( event );
            }

            LinuxApplication->CheckIfApplicatioNeedsDeactivation();
            LinuxApplication->ClearWindowPropertiesAfterEventLoop();
        }
        else
        {
            // 没有要向其发送事件的应用程序, 只需清除队列。
            SDL_Event event;
            while (SDL_PollEvent(&event))
            {
                // noop
            }
        }

        bool bHasFocus = FApp::HasFocus();

        // 如果是我们的窗口,允许声音,否则应用乘数.
        FApp::SetVolumeMultiplier( bHasFocus ? 1.0f : FApp::GetUnfocusedVolumeMultiplier() );
    }
}

18.14.3.5 FPlatformApplicationMisc

FPlatformApplicationMiscFGenericPlatformApplicationMisc之间的关系、实现和用法于FPlatformMiscFGenericPlatformMisc类似,不再累述。

18.14.4 进程、线程和同步

18.14.4.1 FRunnableThread

FRunnableThread是UE封装了各个操作系统下的线程基类,它的定义如下:

// RunnableThread.h

class CORE_API FRunnableThread
{
public:
    // 创建
    static FRunnableThread* Create(FRunnable* InRunnable, const TCHAR* ThreadName, uint32 InStackSize = 0, ...);

    // 线程状态转换.
    virtual void Suspend( bool bShouldPause = true ) = 0;
    virtual bool Kill( bool bShouldWait = true ) = 0;
    virtual void WaitForCompletion() = 0;

    // 线程属性
    
    // 线程类型.
    enum class ThreadType
    {
        Real,
        Fake,
        Forkable,
    };
    virtual FRunnableThread::ThreadType GetThreadType() const;
    static uint32 GetTlsSlot();
    virtual void SetThreadPriority( EThreadPriority NewPriority ) = 0;
    virtual bool SetThreadAffinity( const FThreadAffinity& Affinity );
    const uint32 GetThreadID() const;
    const FString& GetThreadName() const;
    EThreadPriority GetThreadPriority() const;

protected:
    void SetTls();
    void FreeTls();
    static FRunnableThread* GetRunnableThread();

private:
    static uint32 RunnableTlsSlot; // FRunnableThread指针的TLS插槽索引.
    
    static void SetupCreatedThread(...);
    virtual void Tick();
    virtual void OnPostFork();
    void PostCreate(EThreadPriority ThreadPriority);
    
    (...)
};

由此可知,UE抽象了线程的若干接口和数据,包含创建、销毁、转换状态及设置堆栈大小、优先级、亲缘性、TLS等接口。继承自它的子类是实现各个平台的类,继承树如下:

FRunnableThread

FRunnableThreadWin

FRunnableThreadPThread

FRunnableThreadAndroid

FRunnableThreadApple

FRunnableThreadUnix

FRunnableThreadHoloLens

FFakeThread

上图显示了Windows直接继承自FRunnableThread,而Android、Apple、Unix等系统源自Unix系统的POSIX thread(PThread)机制。

POSIX线程库是用于C/C++的基于标准的线程API,允许生成一个新的并发进程流,在多处理器或多核系统上最有效,在这些系统中,进程可以被安排在另一个处理器上运行,从而通过并行或分布式处理提高速度。线程比“分叉”或生成新进程需要更少的开销,因为系统不会为进程初始化新的系统虚拟内存空间和环境。

虽然在多处理器系统上最有效,但在利用I/O延迟和其他可能停止进程执行的系统功能的单处理器系统上也可以获得收益。(一个线程可能在另一个线程等待I/O或其他系统延迟时执行。)并行编程技术(如MPI和PVM)用于分布式计算环境,而线程仅限于单个计算机系统。进程中的所有线程共享相同的地址空间,通过定义一个函数及其将在线程中处理的参数来生成线程。在软件中使用POSIX线程库的目的是更快地执行软件。

本质上,PThread是进程,但和普通的进程更轻量,也常被称为轻量化进程,适合用来模拟线程。它是Unix系的系统才有的概念,Windows不存在。

在UE体系中,FRunnableThread只提供了基础的跨平台线程功能,在实际应用中,需要结合ThreadManager、Runnable、Task、Queue、TaskGraph等类型进行交互,从而形成完整的并发体系。更多技术细节可参阅2.4 UE的多线程机制,本篇不再累述。

18.14.4.2 FPlatformProcess

FPlatformProcess是对FGenericPlatformProcess的类型重定义,机制和FGenericPlatformMisc类似,封装和代表了各个操作系统的进程。下面是FGenericPlatformProcess的定义:

// GenericPlatformProcess.h

struct CORE_API FGenericPlatformProcess
{
    // 信号量
    struct FSemaphore
    {
        const TCHAR* GetName() const;
        virtual void Lock() = 0;
        virtual bool TryLock(uint64 NanosecondsToWait) = 0;
        virtual void Unlock() = 0;


    protected:
        enum Limits
        {
            MaxSemaphoreName = 128
        };
        TCHAR Name[MaxSemaphoreName];
    };

    // dll/模块
    static void* GetDllHandle( const TCHAR* Filename );
    static void FreeDllHandle( void* DllHandle );
    static void* GetDllExport( void* DllHandle, const TCHAR* ProcName );
    static void AddDllDirectory(const TCHAR* Directory);
    static void PushDllDirectory(const TCHAR* Directory);
    static void PopDllDirectory(const TCHAR* Directory);
    static void GetDllDirectories(TArray<FString>& OutDllDirectories);
    static const TCHAR* GetModulePrefix();
    static const TCHAR* GetModuleExtension();
    
    // 进程/应用程序
    static FProcHandle CreateProc( const TCHAR* URL, const TCHAR* Parms, ...);
    static FProcHandle OpenProcess(uint32 ProcessID);
    static bool IsProcRunning( FProcHandle & ProcessHandle );
    static void WaitForProc( FProcHandle & ProcessHandle );
    static void CloseProc( FProcHandle & ProcessHandle );
    static void TerminateProc( FProcHandle & ProcessHandle, bool KillTree = false );
    static EWaitAndForkResult WaitAndFork();
    static bool GetProcReturnCode( FProcHandle & ProcHandle, int32* ReturnCode );
    static bool IsApplicationRunning( uint32 ProcessId );
    static bool IsApplicationRunning( const TCHAR* ProcName );
    static FString GetApplicationName( uint32 ProcessId );
    static bool GetApplicationMemoryUsage(uint32 ProcessId, SIZE_T* OutMemoryUsage);
    static bool ExecProcess(const TCHAR* URL, const TCHAR* Params,...);
    static bool ExecElevatedProcess(const TCHAR* URL, ...);
    static void LaunchURL( const TCHAR* URL, const TCHAR* Parms, FString* Error );
    static bool CanLaunchURL(const TCHAR* URL);
    static bool LaunchFileInDefaultExternalApplication( const TCHAR* FileName, ...);
    
    static void Sleep( float Seconds );
    static void SleepNoStats( float Seconds );
    static void SleepInfinite();
    static void YieldThread();
    static void Yield();
    static void YieldCycles(uint64 Cycles);
    static ENamedThreads::Type GetDesiredThreadForUObjectReferenceCollector();
    static void ModifyThreadAssignmentForUObjectReferenceCollector( int32& NumThreads, i... );
    static void ConditionalSleep(TFunctionRef<bool()> Condition, float SleepTime = 0.0f);
    
    static void SetRealTimeMode();
    static bool Daemonize();
    static bool IsFirstInstance();
    static void TearDown();
    static bool SkipWaitForStats();

    // 进程属性
    static uint32 GetCurrentProcessId();
    static uint32 GetCurrentCoreNumber();
    static const TCHAR* ComputerName();
    static const TCHAR* UserName(bool bOnlyAlphaNumeric = true);
    static bool SetProcessLimits(EProcessResource::Type Resource, uint64 Limit);
    static const TCHAR* ExecutableName(bool bRemoveExtension = true);
    
    // 线程/池/同步
    static void SetThreadAffinityMask( uint64 AffinityMask );
    static void SetThreadPriority( EThreadPriority NewPriority );
    static void SetThreadName( const TCHAR* ThreadName );
    static uint32 GetStackSize();
    static void DumpThreadInfo( const TCHAR* MarkerName );

    static void SetupGameThread();
    static void SetupRenderThread();
    static void SetupRHIThread();
    static void SetupAudioThread();
    static void TeardownAudioThread();
    
    static class FEvent* GetSynchEventFromPool(bool bIsManualReset = false);
    static void FlushPoolSyncEvents();
    static void ReturnSynchEventToPool(FEvent* Event);
    static class FRunnableThread* CreateRunnableThread();

    // 管道
    static void ClosePipe( void* ReadPipe, void* WritePipe );
    static bool CreatePipe(void*& ReadPipe, void*& WritePipe, bool bWritePipeLocal = false);
    static FString ReadPipe( void* ReadPipe );
    static bool ReadPipeToArray(void* ReadPipe, TArray<uint8> & Output);
    static bool WritePipe(void* WritePipe, const FString& Message, FString* OutWritten = nullptr);
    static bool SupportsMultithreading();
    
    // 进程间通信(IPC)
    static FSemaphore* NewInterprocessSynchObject(const FString& Name, bool bCreate, uint32 MaxLocks = 1);
    static FSemaphore* NewInterprocessSynchObject(const TCHAR* Name, bool bCreate, uint32 MaxLocks = 1);
    static bool DeleteInterprocessSynchObject(FSemaphore * Object);
    
    // 目录/路径
    static bool ShouldSaveToUserDir();
    static const TCHAR* BaseDir();
    static const TCHAR* UserDir();
    static const TCHAR *UserSettingsDir();
    static const TCHAR *UserTempDir();
    static const TCHAR *UserHomeDir();
    static const TCHAR* ApplicationSettingsDir();
    static const TCHAR* ShaderDir();
    static void SetShaderDir(const TCHAR*Where);
    static void SetCurrentWorkingDirectoryToBaseDir();
    static FString GetCurrentWorkingDirectory();
    static const FString ShaderWorkingDir();
    static void CleanShaderWorkingDir();
    static const TCHAR* ExecutablePath();
    static FString GenerateApplicationPath( const FString& AppName, EBuildConfiguration BuildConfiguration);
    static const TCHAR* GetBinariesSubdirectory();
    static const FString GetModulesDirectory();
    
    // 其它
    static FString GetGameBundleId();
    static void ExploreFolder( const TCHAR* FilePath );
    
    (...)
};

以上可得知,UE的进程基础封装了很多接口,包含进程生命周期、应用程序操作、线程管理、线程同步、进程间通信和同步、目录和路径、DLL模块等。

FGenericPlatformProcess的继承体系和实现与FGenericPlatformMisc类似,本文不再累述,有兴趣的童鞋自行阅读UE源码。

18.14.4.3 同步对象

UE为了满足各种各样的跨线程、跨进程之间的通信和同步,封装了很多同步对象。下面抽取部分重要的类型进行简要分析。

  • FCriticalSection和FSystemWideCriticalSection

FCriticalSection是使用操作系统的临界区机制,是用户空间的概念和机制,而FSystemWideCriticalSection是使用了操作系统的内核对象Mutex(互斥体)。它们的常见接口如下(以Windows为例):

// WindowsCriticalSection.h

class FWindowsCriticalSection
{
public:
    void Lock();
    bool TryLock();
    void Unlock();
    
    (...)
};

class FWindowsSystemWideCriticalSection
{
public:
    bool IsValid() const;
    void Release();

private:
    // 使用Windows的Mutex内核对象实现.
    Windows::HANDLE Mutex;
    
    (...)
};

FCriticalSection不涉及用户和内核态的转换,效率更高,但只能用于线程间的同步,而不能用于进程间同步。

FSystemWideCriticalSection涉及到了用户和内核态的转换,效率比FCriticalSection低很多,但可以用作进程间同步。

  • FRWLock

FRWLock提供非递归读/写(或共享独占)访问,常用于多线程访问同一个数据块。它的特殊之处在于,如果只是读,则允许多个线程同时读,但如果有一个线程是写,则该线程必须独占数据块,带写入完毕,才允许其它线程读或写,以保证安全。其定义如下(以Windows为例):

// WindowsCriticalSection.h

class FWindowsRWLock
{
public:
    FWindowsRWLock(uint32 Level = 0);
    ~FWindowsRWLock();
    
    void ReadLock();
    void WriteLock();
    void ReadUnlock();
    void WriteUnlock();
    
private:
    Windows::SRWLOCK Mutex;
};
  • FPlatformAtomics

FPlatformAtomics封装了各个操作系统的原子操作,接口如下(以Windows为例):

// GenericPlatformAtomics.h

struct CORE_API FWindowsPlatformAtomics : public FGenericPlatformAtomics
{
    // Increment
    static int8 InterlockedIncrement( volatile int8* Value );
    static int16 InterlockedIncrement( volatile int16* Value );
    static int32 InterlockedIncrement( volatile int32* Value );
    static int64 InterlockedIncrement( volatile int64* Value );
    
    // Decrement
    static int8 InterlockedDecrement( volatile int8* Value );
    static int16 InterlockedDecrement( volatile int16* Value );
    static int32 InterlockedDecrement( volatile int32* Value );
    static int64 InterlockedDecrement( volatile int64* Value );
    
    // Add
    static int8 InterlockedAdd( volatile int8* Value, int8 Amount );
    static int16 InterlockedAdd( volatile int16* Value, int16 Amount );
    static int32 InterlockedAdd( volatile int32* Value, int32 Amount );
    static int64 InterlockedAdd( volatile int64* Value, int64 Amount );
    
    // Exchange
    static int8 InterlockedExchange( volatile int8* Value, int8 Exchange );
    static int16 InterlockedExchange( volatile int16* Value, int16 Exchange );
    static int32 InterlockedExchange( volatile int32* Value, int32 Exchange );
    static int64 InterlockedExchange( volatile int64* Value, int64 Exchange );
    static void* InterlockedExchangePtr( void*volatile* Dest, void* Exchange );
    static int8 InterlockedCompareExchange( volatile int8* Dest, int8 Exchange, int8 Comparand );
    static int16 InterlockedCompareExchange( volatile int16* Dest, int16 Exchange, int16 Comparand );
    static int32 InterlockedCompareExchange( volatile int32* Dest, int32 Exchange, int32 Comparand );
    static int64 InterlockedCompareExchange( volatile int64* Dest, int64 Exchange, int64 Comparand );
    
    // And
    static int8 InterlockedAnd(volatile int8* Value, const int8 AndValue);
    static int16 InterlockedAnd(volatile int16* Value, const int16 AndValue);
    static int32 InterlockedAnd(volatile int32* Value, const int32 AndValue);
    static int64 InterlockedAnd(volatile int64* Value, const int64 AndValue);
    
    // Or / Xor
    static int8 InterlockedOr(volatile int8* Value, const int8 OrValue);
    static int16 InterlockedOr(volatile int16* Value, const int16 OrValue);
    static int32 InterlockedOr(volatile int32* Value, const int32 OrValue);
    static int64 InterlockedOr(volatile int64* Value, const int64 OrValue);
    static int8 InterlockedXor(volatile int8* Value, const int8 XorValue);
    static int16 InterlockedXor(volatile int16* Value, const int16 XorValue);
    static int32 InterlockedXor(volatile int32* Value, const int32 XorValue);
    static int64 InterlockedXor(volatile int64* Value, const int64 XorValue);
    
    // Read
    static int8 AtomicRead(volatile const int8* Src);
    static int16 AtomicRead(volatile const int16* Src);
    static int32 AtomicRead(volatile const int32* Src);
    static int64 AtomicRead(volatile const int64* Src);
    
    // Read Relaxed
    static int8 AtomicRead_Relaxed(volatile const int8* Src);
    static int16 AtomicRead_Relaxed(volatile const int16* Src);
    static int32 AtomicRead_Relaxed(volatile const int32* Src);
    static int64 AtomicRead_Relaxed(volatile const int64* Src);
    
    // Store
    static void AtomicStore(volatile int8* Src, int8 Val);
    static void AtomicStore(volatile int16* Src, int16 Val);
    static void AtomicStore(volatile int32* Src, int32 Val);
    static void AtomicStore(volatile int64* Src, int64 Val);
    
    // Store Relaxed
    static void AtomicStore_Relaxed(volatile int8* Src, int8 Val);
    static void AtomicStore_Relaxed(volatile int16* Src, int16 Val);
    static void AtomicStore_Relaxed(volatile int32* Src, int32 Val);
    static void AtomicStore_Relaxed(volatile int64* Src, int64 Val);

#if    PLATFORM_HAS_128BIT_ATOMICS
    static bool InterlockedCompareExchange128( volatile FInt128* Dest, const FInt128& Exchange, FInt128* Comparand );
    static void AtomicRead128(const volatile FInt128* Src, FInt128* OutResult);
#endif
    static void* InterlockedCompareExchangePointer( void*volatile* Dest, void* Exchange, void* Comparand );
    static bool CanUseCompareExchange128();

protected:
    static void HandleAtomicsFailure( const TCHAR* InFormat, ... );
};
  • FPlatformTLS

TLS全称是Thread Local Storage,意为线程局部存储,顾名思义,就是可以给每个线程存储独有的数据,从而避免多线程之间的竞争,提升性能。

UE提供了FPlatformTLS,以为上层模块提供统一而简洁的TLS操作接口。其定义如下(以Windows为例):

// WindowsPlatformTLS.h

struct FWindowsPlatformTLS : public FGenericPlatformTLS
{
    // TLS对应的线程id。
    static uint32 GetCurrentThreadId(void);
    
    // TLS对应的插槽。
    static uint32 AllocTlsSlot(void);
    static void FreeTlsSlot(uint32 SlotIndex);
    
    // TLS值操作。
    static void SetTlsValue(uint32 SlotIndex,void* Value);
    static void* GetTlsValue(uint32 SlotIndex);
};

使用示例之一是LockFreeList实现:

// LockFreeList.cpp

class LockFreeLinkAllocator_TLSCache : public FNoncopyable
{
public:
    LockFreeLinkAllocator_TLSCache()
    {
        check(IsInGameThread());
        TlsSlot = FPlatformTLS::AllocTlsSlot();
        check(FPlatformTLS::IsValidTlsSlot(TlsSlot));
    }
    
    ~LockFreeLinkAllocator_TLSCache()
    {
        FPlatformTLS::FreeTlsSlot(TlsSlot);
        TlsSlot = 0;
    }

private:
    FThreadLocalCache& GetTLS()
    {
        checkSlow(FPlatformTLS::IsValidTlsSlot(TlsSlot));
        FThreadLocalCache* TLS = (FThreadLocalCache*)FPlatformTLS::GetTlsValue(TlsSlot);
        if (!TLS)
        {
            TLS = new FThreadLocalCache();
            FPlatformTLS::SetTlsValue(TlsSlot, TLS);
        }
        return *TLS;
    }
    
    uint32 TlsSlot;
    
    (...)
};
  • FPlatformNamedPipe

FPlatformNamedPipe是对操作系统的命名管道通信的封装,提供了以下接口:

// GenericPlatformNamedPipe.h

class FGenericPlatformNamedPipe
{
public:
    virtual bool Create(const FString& PipeName, bool bServer, bool bAsync);
    virtual bool Destroy();
    
    virtual bool OpenConnection();
    virtual bool BlockForAsyncIO();
    virtual bool UpdateAsyncStatus();
    
    virtual bool IsCreated() const;
    virtual bool HasFailed() const;
    virtual bool IsReadyForRW() const;
    
    virtual bool WriteBytes(int32 NumBytes, const void* Data);
    inline bool WriteInt32(int32 In);
    virtual bool ReadBytes(int32 NumBytes, void* OutData);
    inline bool ReadInt32(int32& Out);
    
    virtual const FString& GetName() const;

protected:
    FString* NamePtr;
};

UE用到FPlatformNamedPipe的是着色器编译模块:

// XGEControlWorker.cpp

class FXGEControlWorker
{
    const FString PipeName;
    FProcHandle XGConsoleProcHandle;

    // 输入、输出命名管道。
    FPlatformNamedPipe InputNamedPipe;
    FPlatformNamedPipe OutputNamedPipe;

    (...)
};
  • FPlatformStackWalk

FPlatformStackWalk是大多数平台下对堆栈遍历的通用实现,定义如下:

// GenericPlatformStackWalk.h

struct FGenericPlatformStackWalk
{
    // 初始化
    static void Init();
    static bool InitStackWalking();
    static bool InitStackWalkingForProcess(const FProcHandle& Process);
    
    // 程序计数器
    static bool ProgramCounterToHumanReadableString( int32 CurrentCallDepth, ... );
    static void ProgramCounterToSymbolInfo( uint64 ProgramCounter, ...);
    static void ProgramCounterToSymbolInfoEx( uint64 ProgramCounter, ...);
    
    // 符号信息
    static bool SymbolInfoToHumanReadableString( const FProgramCounterSymbolInfo& SymbolInfo, ... );
    static bool SymbolInfoToHumanReadableStringEx( const FProgramCounterSymbolInfoEx& SymbolInfo, ... );
    static TArray<FProgramCounterSymbolInfo> GetStack(int32 IgnoreCount, ...);
    
    // 捕获堆栈
    static uint32 CaptureStackBackTrace( uint64* BackTrace, uint32 MaxDepth, ... );
    static uint32 CaptureThreadStackBackTrace(uint64 ThreadId, ...);
    
    // 遍历
    static void StackWalkAndDump( ANSICHAR* HumanReadableString, ... );
    static void ThreadStackWalkAndDump(ANSICHAR* HumanReadableString, ...);
    static void StackWalkAndDumpEx( ANSICHAR* HumanReadableString, ... );
    
    // 获取接口.
    static int32 GetProcessModuleCount();
    static int32 GetProcessModuleSignatures(FStackWalkModuleInfo *ModuleSignatures, ...);
    static TMap<FName, FString> GetSymbolMetaData();

    (...)
};

FPlatformStackWalk的应用之一是程序崩溃时的调用堆栈打印和分析:

// CrashReportClientMainWindows.cpp

void SaveCrcCrashException(EXCEPTION_POINTERS* ExceptionInfo)
{
    // 如果会话已创建,请尝试在适当的字段中写入异常代码。递增计数器的第一个崩溃线程赢得了竞争,并可以编写其异常代码。
    static volatile int32 CrashCount = 0;
    if (FPlatformAtomics::InterlockedIncrement(&CrashCount) == 1)
    {
        FCrashReportAnalyticsSessionSummary::Get().OnCrcCrashing(ExceptionInfo->ExceptionRecord->ExceptionCode);

        if (ExceptionInfo->ExceptionRecord->ExceptionCode != STATUS_HEAP_CORRUPTION)
        {
            // 尝试让异常调用堆栈记录,以找出CRC崩溃的原因,但是不可靠,因为它在崩溃的进程中运行,并分配内存/使用调用堆栈,但我们仍然可以获得一些有用的数据。
            if (FPlatformStackWalk::InitStackWalkingForProcess(FProcHandle()))
            {
                FPlatformStackWalk::StackWalkAndDump(CrashStackTrace, UE_ARRAY_COUNT(CrashStackTrace), 0);
                if (CrashStackTrace[0] != 0)
                {
                    FCrashReportAnalyticsSessionSummary::Get().LogEvent(ANSI_TO_TCHAR(CrashStackTrace));
                }
            }
        }
    }
}

18.14.5 其它Platform模块

18.14.5.1 FPlatformMemory

FPlatformMemory封装抽象了各个操作系统下对内存的统一操作接口:

// GenericPlatformMemory.h

struct FGenericPlatformMemory
{
    static bool bIsOOM; // 是否内存不足
    static uint64 OOMAllocationSize; // 设置为触发内存不足的分配大小,否则为零.
    static uint32 OOMAllocationAlignment; // 设置为触发内存不足的分配对齐,否则为零。
    static void* BackupOOMMemoryPool; // 内存不足时要删除的预分配缓冲区。用于OOM处理和崩溃报告。
    static uint32 BackupOOMMemoryPoolSize; // BackupOOMMemoryPool的大小(字节)。

    // 可用于内存统计的各种内存区域。枚举的确切含义相对依赖于平台,尽管一般的(物理、GPU)很简单。一个平台可以添加更多的内存,并且不会影响其他平台,除了StatManager跟踪每个区域的最大可用内存(使用数组FPlatformMemory::MCR_max big)所需的少量内存之外.
    enum EMemoryCounterRegion
    {
        MCR_Invalid, // not memory
        MCR_Physical, // main system memory
        MCR_GPU, // memory directly a GPU (graphics card, etc)
        MCR_GPUSystem, // system memory directly accessible by a GPU
        MCR_TexturePool, // presized texture pools
        MCR_StreamingPool, // amount of texture pool available for streaming.
        MCR_UsedStreamingPool, // amount of texture pool used for streaming.
        MCR_GPUDefragPool, // presized pool of memory that can be defragmented.
        MCR_PhysicalLLM, // total physical memory including CPU and GPU
        MCR_MAX
    };

    // 使用的分配器.
    enum EMemoryAllocatorToUse
    {
        Ansi, // Default C allocator
        Stomp, // Allocator to check for memory stomping
        TBB, // Thread Building Blocks malloc
        Jemalloc, // Linux/FreeBSD malloc
        Binned, // Older binned malloc
        Binned2, // Newer binned malloc
        Binned3, // Newer VM-based binned malloc, 64 bit only
        Platform, // Custom platform specific allocator
        Mimalloc, // mimalloc
    };
    static EMemoryAllocatorToUse AllocatorToUse;

    enum ESharedMemoryAccess
    {
        Read    =        (1 << 1),
        Write    =        (1 << 2)
    };

    // 共享内存区域的通用表示
    struct FSharedMemoryRegion
    {
        TCHAR    Name[MaxSharedMemoryName];
        uint32   AccessMode;
        void *   Address;
        SIZE_T   Size;
    };

    // 内存操作.
    static void Init();
    static void OnOutOfMemory(uint64 Size, uint32 Alignment);
    static void SetupMemoryPools();
    static uint32 GetBackMemoryPoolSize()
    static FMalloc* BaseAllocator();
    static FPlatformMemoryStats GetStats();
    static uint64 GetMemoryUsedFast();
    static void GetStatsForMallocProfiler( FGenericMemoryStats& out_Stats );
    static const FPlatformMemoryConstants& GetConstants();
    static uint32 GetPhysicalGBRam();

    static bool PageProtect(void* const Ptr, const SIZE_T Size, const bool bCanRead, const bool bCanWrite);
    
    // 分配.
    static void* BinnedAllocFromOS( SIZE_T Size );
    static void BinnedFreeToOS( void* Ptr, SIZE_T Size );
    static void NanoMallocInit();
    static bool PtrIsOSMalloc( void* Ptr);
    static bool IsNanoMallocAvailable();
    static bool PtrIsFromNanoMalloc( void* Ptr);

    // 虚拟内存块及操作.
    class FBasicVirtualMemoryBlock
    {
    protected:
        void *Ptr;
        uint32 VMSizeDivVirtualSizeAlignment;

    public:
        FBasicVirtualMemoryBlock(const FBasicVirtualMemoryBlock& Other) = default;
        FBasicVirtualMemoryBlock& operator=(const FBasicVirtualMemoryBlock& Other) = default;
        FORCEINLINE uint32 GetActualSizeInPages() const;
        FORCEINLINE void* GetVirtualPointer() const;

        void Commit(size_t InOffset, size_t InSize);
        void Decommit(size_t InOffset, size_t InSize);
        void FreeVirtual();

        void CommitByPtr(void *InPtr, size_t InSize);
        void DecommitByPtr(void *InPtr, size_t InSize);
        void Commit();
        void Decommit();
        size_t GetActualSize() const;

        static FPlatformVirtualMemoryBlock AllocateVirtual(size_t Size, ...);
        
        static size_t GetCommitAlignment();
        static size_t GetVirtualSizeAlignment();

    };

    // 数据和调试
    static bool BinnedPlatformHasMemoryPoolForThisSize(SIZE_T Size);
    static void DumpStats( FOutputDevice& Ar );
    static void DumpPlatformAndAllocatorStats( FOutputDevice& Ar );

    static EPlatformMemorySizeBucket GetMemorySizeBucket();

    // 内存数据操作.
    static void* Memmove( void* Dest, const void* Src, SIZE_T Count );
    static int32 Memcmp( const void* Buf1, const void* Buf2, SIZE_T Count );
    static void* Memset(void* Dest, uint8 Char, SIZE_T Count);
    static void* Memzero(void* Dest, SIZE_T Count);
    static void* Memcpy(void* Dest, const void* Src, SIZE_T Count);
    static void* BigBlockMemcpy(void* Dest, const void* Src, SIZE_T Count);
    static void* StreamingMemcpy(void* Dest, const void* Src, SIZE_T Count);
    static void* ParallelMemcpy(void* Dest, const void* Src, SIZE_T Count, EMemcpyCachePolicy Policy = EMemcpyCachePolicy::StoreCached);

    (...)
};

更多详情可参阅1.4.3 内存分配

18.14.5.2 FPlatformMath

FPlatformMath封装了一组依赖于操作系统的高效数学运算,定义如下:

// GenericPlatformMath.h

struct FGenericPlatformMath
{
    static float LoadHalf(const uint16* Ptr);
    static void StoreHalf(uint16* Ptr, float Value);
    static void VectorLoadHalf(float* RESTRICT Dst, const uint16* RESTRICT Src);
    static void VectorStoreHalf(uint16* RESTRICT Dst, const float* RESTRICT Src);
    static void WideVectorLoadHalf(float* RESTRICT Dst, const uint16* RESTRICT Src);
    static void WideVectorStoreHalf(uint16* RESTRICT Dst, const float* RESTRICT Src);
    
    static inline uint32 AsUInt(float F);
    static inline uint64 AsUInt(double F);
        
    static int32 TruncToInt(float F);
    static int32 TruncToInt(double F);
    static float TruncToFloat(float F);

    static int32 FloorToInt(float F);
    static int32 FloorToInt(double F);
    static float FloorToFloat(float F);
    
    static int32 RoundToInt(float F);
    static int32 RoundToInt(double F);
    static float RoundToFloat(float F);
    
    static int32 CeilToInt(float F);
    static int32 CeilToInt(double F);
    static float CeilToFloat(float F);
        
    static float Fractional(float Value);
    static float Frac(float Value);
    static float Modf(const float InValue, float* OutIntPart);

    static float Pow( float A, float B );
    static float Exp( float Value );
    static float Loge( float Value );
    static float LogX( float Base, float Value );
    static uint32 FloorLog2(uint32 Value);
    
    static float Sqrt( float Value );
    static float InvSqrt( float F );
    static float InvSqrtEst( float F );
    
    static bool IsNaN( float A );
    static bool IsNaN(double A);
    static bool IsFinite( float A );
    static bool IsFinite(double A);
    static bool IsNegative(float A);
    
    static float Fmod(float X, float Y);
    static float Sin( float Value );
    static float Asin( float Value );
    static float Sinh(float Value);
    static float Cos( float Value );
    static float Acos( float Value );
    static float Tan( float Value );
    static float Atan( float Value );
    
    static int32 Rand();
    static void RandInit(int32 Seed);
    static float FRand();
    static void SRandInit( int32 Seed );
    static int32 GetRandSeed();
    static float SRand();
    
    (...)
};

18.14.5.3 IPlatformFile

IFileHandle是UE对各个系统下的单个文件的封装,IPlatformFile是UE对各个系统下的文件的封装,而FPlatformFileManager是对IPlatformFile链的管理。

先看看IFileHandle的核心继承图:

IFileHandle

FFileHandleAndroid

FFileHandleWindows

FFileHandleApple

FIOSFileHandle

FFileHandleHoloLens

FCachedFileHandle

除了上图显示的文件类型,还有FAsyncBufferedFileReaderWindows、FLoggedFileHandle、FManagedStorageFileWriteHandle、FRegisteredFileHandle、FNetworkFileHandle、FStreamingNetworkFileHandle、FPakFileHandle、FStorageServerFileHandle等文件类型。

再看看IPlatformFile的核心UML图:

IPlatformFile

IPhysicalPlatformFile

FHoloLensPlatformFile

FWindowsPlatformFile

IAndroidPlatformFile

FAndroidPlatformFile

FApplePlatformFile

FIOSPlatformFile

FUnixPlatformFile

当然,还有很多非常规的文件类型继承自IPlatformFile:

  • FCachedReadPlatformFile:缓存文件。
  • FLoggedPlatformFile:日志文件。
  • FPlatformFileOpenLog:打开日志文件。
  • FNetworkPlatformFile:网络文件。
  • FPakPlatformFile:Pak包体内文件。
  • FSandboxPlatformFile:沙盒文件。
  • FStorageServerPlatformFile:存储服务器文件。
  • FConcertSandboxPlatformFile:音乐会沙盒文件。

下面看看IFileHandle和IPlatformFile的定义:

// GenericPlatformFile.h

class IFileHandle
{
public:
    virtual int64 Tell() = 0;
    virtual bool  Seek(int64 NewPosition) = 0;
    virtual bool  SeekFromEnd(int64 NewPositionRelativeToEnd = 0) = 0;
    virtual bool  Read(uint8* Destination, int64 BytesToRead) = 0;
    virtual bool  Write(const uint8* Source, int64 BytesToWrite) = 0;
    virtual bool  Flush(const bool bFullFlush = false) = 0;
    virtual bool  Truncate(int64 NewSize) = 0;
    virtual void  ShrinkBuffers()
    virtual int64 Size();
};

class IPlatformFile
{
public:
    static IPlatformFile& GetPlatformPhysical();
    static const TCHAR* GetPhysicalTypeName();

    virtual void SetSandboxEnabled(bool bInEnabled)
    virtual bool IsSandboxEnabled() const

    virtual bool ShouldBeUsed(IPlatformFile* Inner, const TCHAR* CmdLine) const
    virtual bool Initialize(IPlatformFile* Inner, const TCHAR* CmdLine) = 0;
    virtual void InitializeAfterSetActive()
    virtual void InitializeAfterProjectFilePath()
    virtual void MakeUniquePakFilesForTheseFiles(const TArray<TArray<FString>>& InFiles)
    virtual void InitializeNewAsyncIO();
    virtual void AddLocalDirectories(TArray<FString> &LocalDirectories)
    virtual void BypassSecurity(bool bInBypass)
        
    virtual void Tick();
    
    virtual IPlatformFile* GetLowerLevel() = 0;
    virtual void           SetLowerLevel(IPlatformFile* NewLowerLevel) = 0;
    virtual const TCHAR*   GetName() const = 0;
    
    virtual bool        FileExists(const TCHAR* Filename) = 0;
    virtual int64        FileSize(const TCHAR* Filename) = 0;
    virtual bool        DeleteFile(const TCHAR* Filename) = 0;
    virtual bool        IsReadOnly(const TCHAR* Filename) = 0;
    virtual bool        MoveFile(const TCHAR* To, const TCHAR* From) = 0;
    virtual bool        SetReadOnly(const TCHAR* Filename, bool bNewReadOnlyValue) = 0;
    virtual FDateTime    GetTimeStamp(const TCHAR* Filename) = 0;
    virtual void        SetTimeStamp(const TCHAR* Filename, FDateTime DateTime) = 0;
    virtual FDateTime    GetAccessTimeStamp(const TCHAR* Filename) = 0;
    virtual FString     GetFilenameOnDisk(const TCHAR* Filename) = 0;

    virtual IFileHandle* OpenRead(const TCHAR* Filename, bool bAllowWrite = false) = 0;
    virtual IFileHandle* OpenReadNoBuffering(const TCHAR* Filename, bool bAllowWrite = false)
    virtual IFileHandle* OpenWrite(const TCHAR* Filename, bool bAppend = false, bool bAllowRead = false) = 0;

    virtual bool        DirectoryExists(const TCHAR* Directory) = 0;
    virtual bool        CreateDirectory(const TCHAR* Directory) = 0;
    virtual bool        DeleteDirectory(const TCHAR* Directory) = 0;

    virtual FFileStatData GetStatData(const TCHAR* FilenameOrDirectory) = 0;

    // 仅使用名称的文件和目录访问者的基类。
    class FDirectoryVisitor
    {
    public:
        virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) = 0;
        FORCEINLINE bool IsThreadSafe() const
        EDirectoryVisitorFlags DirectoryVisitorFlags;
    };

    typedef TFunctionRef<bool(const TCHAR*, bool)> FDirectoryVisitorFunc;

    // 获取所有统计数据的文件和目录访问者的基类.
    class FDirectoryStatVisitor
    {
    public:
        virtual bool Visit(const TCHAR* FilenameOrDirectory, const FFileStatData& StatData) = 0;
    };

    typedef TFunctionRef<bool(const TCHAR*, const FFileStatData&)> FDirectoryStatVisitorFunc;
    virtual bool        IterateDirectory(const TCHAR* Directory, FDirectoryVisitor& Visitor) = 0;
    virtual bool        IterateDirectoryStat(const TCHAR* Directory, FDirectoryStatVisitor& Visitor) = 0;

    virtual IAsyncReadFileHandle* OpenAsyncRead(const TCHAR* Filename);
    virtual void SetAsyncMinimumPriority(EAsyncIOPriorityAndFlags MinPriority)

    virtual IMappedFileHandle* OpenMapped(const TCHAR* Filename)

    virtual void GetTimeStampPair(const TCHAR* PathA, const TCHAR* PathB, FDateTime& OutTimeStampA, FDateTime& OutTimeStampB);
    virtual FDateTime    GetTimeStampLocal(const TCHAR* Filename);

    virtual bool IterateDirectory(const TCHAR* Directory, FDirectoryVisitorFunc Visitor);
    virtual bool IterateDirectoryStat(const TCHAR* Directory, FDirectoryStatVisitorFunc Visitor);
    virtual bool IterateDirectoryRecursively(const TCHAR* Directory, FDirectoryVisitor& Visitor);
    virtual bool IterateDirectoryStatRecursively(const TCHAR* Directory, FDirectoryStatVisitor& Visitor);
    virtual bool IterateDirectoryRecursively(const TCHAR* Directory, FDirectoryVisitorFunc Visitor);
    virtual bool IterateDirectoryStatRecursively(const TCHAR* Directory, FDirectoryStatVisitorFunc Visitor);
    virtual void FindFiles(TArray<FString>& FoundFiles, const TCHAR* Directory, const TCHAR* FileExtension);
    virtual void FindFilesRecursively(TArray<FString>& FoundFiles, const TCHAR* Directory, const TCHAR* FileExtension);
    virtual bool DeleteDirectoryRecursively(const TCHAR* Directory);
    virtual bool CreateDirectoryTree(const TCHAR* Directory);
    virtual bool CopyFile(const TCHAR* To, const TCHAR* From, EPlatformFileRead ReadFlags = EPlatformFileRead::None, EPlatformFileWrite WriteFlags = EPlatformFileWrite::None);
    virtual bool CopyDirectoryTree(const TCHAR* DestinationDirectory, const TCHAR* Source, bool bOverwriteAllExisting);
    virtual FString ConvertToAbsolutePathForExternalAppForRead( const TCHAR* Filename );
    virtual FString ConvertToAbsolutePathForExternalAppForWrite( const TCHAR* Filename );

    // 用于向文件服务器函数发送/接收数据的帮助程序类.
    class IFileServerMessageHandler
    {
    public:
        virtual ~IFileServerMessageHandler() { }
        virtual void FillPayload(FArchive& Payload) = 0;
        virtual void ProcessResponse(FArchive& Response) = 0;
    };

    virtual bool SendMessageToServer(const TCHAR* Message, IFileServerMessageHandler* Handler)
    virtual bool DoesCreatePublicFiles()
    virtual void SetCreatePublicFiles(bool bCreatePublicFiles)
};

IFileHandle、IPlatformFile和FPlatformFileManager的UML关系如下(忽略它们各自的子类):

IPlatformFile

IFileHandle

FPlatformFileManager

FPlatformFileManager对外提供了平台无关的文件操作接口,定义如下:

// PlatformFileManager.h

class FPlatformFileManager
{
public:
    static FPlatformFileManager& Get( );
    
    IPlatformFile& GetPlatformFile( );
    IPlatformFile* GetPlatformFile( const TCHAR* Name );
    IPlatformFile* FindPlatformFile( const TCHAR* Name );
    void SetPlatformFile( IPlatformFile& NewTopmostPlatformFile );
    
    void TickActivePlatformFile();
    void InitializeNewAsyncIO();
    void RemovePlatformFile(IPlatformFile* PlatformFileToRemove);
    
private:
    IPlatformFile* TopmostPlatformFile;
};

使用案例如下:

// CrashReportClientApp.cpp

static void HandleAbnormalShutdown(FSharedCrashContext& CrashContext, uint64 ProcessID, ...)
{
    (...)

    // 获取平台文件对象。
    IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

    // 创建临时崩溃目录
    const FString TempCrashDirectory = FPlatformProcess::UserTempDir() / FString::Printf(TEXT("UECrashContext-%d"), ProcessID);
    FCString::Strcpy(CrashContext.CrashFilesDirectory, *TempCrashDirectory);

    if (PlatformFile.CreateDirectory(CrashContext.CrashFilesDirectory))
    {
        // 将日志文件复制到临时目录
        const FString LogDestination = TempCrashDirectory / FPaths::GetCleanFilename(CrashContext.UserSettings.LogFilePath);
        PlatformFile.CopyFile(*LogDestination, CrashContext.UserSettings.LogFilePath);

        // 此崩溃不是真正的崩溃,而是在异常终止时捕获编辑器日志的崩溃。
        FCrashReportAnalyticsSessionSummary::Get().LogEvent(TEXT("SyntheticCrash"));

        (...)

        // 删除临时崩溃目录。
        PlatformFile.DeleteDirectoryRecursively(*TempCrashDirectory);

        (...)
    }
}

18.15 本篇总结

本篇主要阐述了操作系统的相关知识,包含线程、进程、同步、通信、内存、磁盘等等,以及UE对OS相关模块的封装和实现,使得读者对操作系统模块有着大致的理解,至于更多技术细节和原理,需要读者自己去研读UE源码发掘。


 

特别说明

参考文献

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实时RTAI-Linux操作系统是一个基于Linux的开源实时操作系统。它的设计目标是提供可预测性和可靠性,使用户能够实现对实时任务的精确控制和调度。 对于实时系统的分析与研究,我们可以从以下几个方面进行探讨: 首先,我们可以研究RTAI-Linux操作系统的核心原理和架构。实时操作系统的关键特性是任务调度和中断处理的实时性。我们可以深入了解RTAI-Linux的调度算法和机制,例如优先级调度和周期调度。此外,我们还可以研究RTAI-Linux的中断处理机制,包括中断响应时间和中断处理程序的实时性保证。 其次,我们可以研究RTAI-Linux操作系统的性能分析和优化。实时系统在性能方面有较高的要求,包括延迟时间、响应时间和任务吞吐量等。我们可以使用性能分析工具来测量和评估RTAI-Linux操作系统的性能,并根据分析结果进行优化。例如,通过调整任务的优先级或修改调度算法来改善实时性能。 此外,我们还可以研究RTAI-Linux操作系统在特定应用领域的应用案例和实际使用情况。实时系统广泛应用于工业自动化、航空航天、机器人技术等领域。研究实际应用案例可以帮助我们了解实时系统在实际环境中的应用和挑战,并为系统设计和开发提供实践经验和指导。 总之,实时RTAI-Linux操作系统的分析与研究是一个综合性的课题,涉及到操作系统原理、性能分析和优化、实际应用等多个方面。通过深入研究和探讨,我们可以更好地理解实时系统的特性和性能,并为实时系统的设计和开发提供有效的支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值