// @lint-ignore-every LICENSELINT // Copyright Epic Games, Inc. All Rights Reserved. #include "OculusXRHMD_StressTester.h" #if OCULUS_STRESS_TESTS_ENABLED #include "OculusXRHMD.h" #include "GlobalShader.h" #include "UniformBuffer.h" #include "RHICommandList.h" #include "ShaderParameterUtils.h" #include "RHIStaticStates.h" #include "PipelineStateCache.h" #include "OculusShaders.h" #include "SceneUtils.h" // for SCOPED_DRAW_EVENT() DECLARE_STATS_GROUP(TEXT("Oculus"), STATGROUP_Oculus, STATCAT_Advanced); DECLARE_CYCLE_STAT(TEXT("GPUStressRendering"), STAT_GPUStressRendering, STATGROUP_Oculus); //------------------------------------------------------------------------------------------------- // Uniform buffers //------------------------------------------------------------------------------------------------- //This buffer should contain variables that never, or rarely change BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FOculusPixelShaderConstantParameters, ) //SHADER_PARAMETER(FVector4, Name) END_GLOBAL_SHADER_PARAMETER_STRUCT() IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FOculusPixelShaderConstantParameters, "PSConstants"); typedef TUniformBufferRef FOculusPixelShaderConstantParametersRef; //This buffer is for variables that change very often (each frame for example) BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FOculusPixelShaderVariableParameters, ) SHADER_PARAMETER(int, IterationsMultiplier) END_GLOBAL_SHADER_PARAMETER_STRUCT() IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FOculusPixelShaderVariableParameters, "PSVariables"); typedef TUniformBufferRef FOculusPixelShaderVariableParametersRef; namespace OculusXRHMD { //------------------------------------------------------------------------------------------------- // FTextureVertexDeclaration //------------------------------------------------------------------------------------------------- struct FTextureVertex { FVector4 Position; FVector2f UV; }; class FTextureVertexDeclaration : public FRenderResource { public: FVertexDeclarationRHIRef VertexDeclarationRHI; #if UE_VERSION_OLDER_THAN(5, 3, 0) virtual void InitRHI() override #else virtual void InitRHI(FRHICommandListBase& RHICmdList) override #endif { FVertexDeclarationElementList Elements; uint32 Stride = sizeof(FTextureVertex); Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, Position), VET_Float4, 0, Stride)); Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, UV), VET_Float2, 1, Stride)); VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(Elements); } virtual void ReleaseRHI() override { VertexDeclarationRHI.SafeRelease(); } }; static TGlobalResource GOculusTextureVertexDeclaration; //------------------------------------------------------------------------------------------------- // FStressTester //------------------------------------------------------------------------------------------------- TSharedPtr FStressTester::SharedInstance; TSharedRef FStressTester::Get() { CheckInGameThread(); if (!SharedInstance.IsValid()) { SharedInstance = TSharedPtr(new FStressTester()); check(SharedInstance.IsValid()); } return SharedInstance.ToSharedRef(); } FStressTester::FStressTester() : Mode(STM_None) , CPUSpinOffInSeconds(0.011 / 3.) // one third of the frame (default value) , PDsTimeLimitInSeconds(10.) // 10 secs , CPUsTimeLimitInSeconds(10.) // 10 secs , GPUsTimeLimitInSeconds(10.) // 10 secs , GPUIterationsMultiplier(0.) , CPUStartTimeInSeconds(0.) , GPUStartTimeInSeconds(0.) , PDStartTimeInSeconds(0.) { } // multiple masks could be set, see EStressTestMode void FStressTester::SetStressMode(uint32 InStressMask) { check((InStressMask & (~STM__All)) == 0); Mode = InStressMask; for (uint32 m = 1; m < STM__All; m <<= 1) { if (InStressMask & m) { switch (m) { case STM_EyeBufferRealloc: UE_LOG(LogHMD, Log, TEXT("PD of EyeBuffer stress test is started")); break; case STM_CPUSpin: UE_LOG(LogHMD, Log, TEXT("CPU stress test is started")); break; case STM_GPU: UE_LOG(LogHMD, Log, TEXT("GPU stress test is started")); break; } } } } void FStressTester::DoTickCPU_GameThread(FOculusXRHMD* pPlugin) { CheckInGameThread(); if (Mode & STM_EyeBufferRealloc) { // Change PixelDensity every frame within MinPixelDensity..MaxPixelDensity range if (PDStartTimeInSeconds == 0.) { PDStartTimeInSeconds = FPlatformTime::Seconds(); } else { const double Now = FPlatformTime::Seconds(); if (Now - PDStartTimeInSeconds >= PDsTimeLimitInSeconds) { PDStartTimeInSeconds = 0.; Mode &= ~STM_EyeBufferRealloc; UE_LOG(LogHMD, Log, TEXT("PD of EyeBuffer stress test is finished")); } } const int divisor = int((MaxPixelDensity - MinPixelDensity) * 10.f); float NewPD = float(uint64(FPlatformTime::Seconds() * 1000) % divisor) / 10.f + MinPixelDensity; pPlugin->SetPixelDensity(NewPD); } if (Mode & STM_CPUSpin) { // Simulate heavy CPU load within specified time limits if (CPUStartTimeInSeconds == 0.) { CPUStartTimeInSeconds = FPlatformTime::Seconds(); } else { const double Now = FPlatformTime::Seconds(); if (Now - CPUStartTimeInSeconds >= CPUsTimeLimitInSeconds) { CPUStartTimeInSeconds = 0.; Mode &= ~STM_CPUSpin; UE_LOG(LogHMD, Log, TEXT("CPU stress test is finished")); } } const double StartSeconds = FPlatformTime::Seconds(); int i, num = 1, primes = 0; bool bFinish = false; while (!bFinish) { i = 2; while (i <= num) { if (num % i == 0) { break; } i++; const double NowSeconds = FPlatformTime::Seconds(); if (NowSeconds - StartSeconds >= CPUSpinOffInSeconds) { bFinish = true; } } if (i == num) { ++primes; } ++num; } } if (Mode & STM_GPU) { // Simulate heavy CPU load within specified time limits if (GPUStartTimeInSeconds == 0.) { GPUStartTimeInSeconds = FPlatformTime::Seconds(); } else { const double Now = FPlatformTime::Seconds(); if (Now - GPUStartTimeInSeconds >= GPUsTimeLimitInSeconds) { GPUStartTimeInSeconds = 0.; Mode &= ~STM_GPU; UE_LOG(LogHMD, Log, TEXT("GPU stress test is finished")); } } } } //------------------------------------------------------------------------------------------------- // Console commands for managing the stress tester: //------------------------------------------------------------------------------------------------- static void StressGPUCmdHandler(const TArray& Args, UWorld* World, FOutputDevice& Ar) { auto StressTester = FStressTester::Get(); StressTester->SetStressMode(FStressTester::STM_GPU | StressTester->GetStressMode()); if (Args.Num() > 0) { const int GpuMult = FCString::Atoi(*Args[0]); StressTester->SetGPULoadMultiplier(GpuMult); } if (Args.Num() > 1) { const float GpuTimeLimit = FCString::Atof(*Args[1]); StressTester->SetGPUsTimeLimitInSeconds(GpuTimeLimit); } } static FAutoConsoleCommand CStressGPUCmd( TEXT("vr.oculus.Stress.GPU"), *NSLOCTEXT("OculusRift", "CCommandText_StressGPU", "Initiates a GPU stress test.\n Usage: vr.oculus.Stress.GPU [LoadMultiplier [TimeLimit]]").ToString(), FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(StressGPUCmdHandler)); static void StressCPUCmdHandler(const TArray& Args, UWorld* World, FOutputDevice& Ar) { auto StressTester = FStressTester::Get(); StressTester->SetStressMode(FStressTester::STM_CPUSpin | StressTester->GetStressMode()); if (Args.Num() > 0) { const float CpuLimit = FCString::Atof(*Args[0]); StressTester->SetCPUSpinOffPerFrameInSeconds(CpuLimit); } if (Args.Num() > 1) { const float CpuTimeLimit = FCString::Atof(*Args[1]); StressTester->SetCPUsTimeLimitInSeconds(CpuTimeLimit); } } static FAutoConsoleCommand CStressCPUCmd( TEXT("vr.oculus.Stress.CPU"), *NSLOCTEXT("OculusRift", "CCommandText_StressCPU", "Initiates a CPU stress test.\n Usage: vr.oculus.Stress.CPU [PerFrameTime [TotalTimeLimit]]").ToString(), FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(StressCPUCmdHandler)); static void StressPDCmdHandler(const TArray& Args, UWorld* World, FOutputDevice& Ar) { auto StressTester = FStressTester::Get(); StressTester->SetStressMode(FStressTester::STM_EyeBufferRealloc | StressTester->GetStressMode()); if (Args.Num() > 0) { const float TimeLimit = FCString::Atof(*Args[0]); StressTester->SetPDsTimeLimitInSeconds(TimeLimit); } } static FAutoConsoleCommand CStressPDCmd( TEXT("vr.oculus.Stress.PD"), *NSLOCTEXT("OculusRift", "CCommandText_StressPD", "Initiates a pixel density stress test wher pixel density is changed every frame for TotalTimeLimit seconds.\n Usage: vr.oculus.Stress.PD [TotalTimeLimit]").ToString(), FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(StressPDCmdHandler)); static void StressResetCmdHandler(const TArray& Args, UWorld* World, FOutputDevice& Ar) { auto StressTester = FStressTester::Get(); StressTester->SetStressMode(0); } static FAutoConsoleCommand CStressResetCmd( TEXT("vr.oculus.Stress.Reset"), *NSLOCTEXT("OculusRift", "CCommandText_StressReset", "Resets the stress tester and stops all currently running stress tests.\n Usage: vr.oculus.Stress.Reset").ToString(), FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(StressResetCmdHandler)); } // namespace OculusXRHMD #endif // #if OCULUS_STRESS_TESTS_ENABLED