Files
UnrealEngine/Engine/Source/Runtime/OpenGLDrv/Private/Android/AndroidOpenGLFramePacer.cpp
2025-05-18 13:04:45 +08:00

635 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AndroidOpenGLFramePacer.h"
#if USE_ANDROID_OPENGL
/*******************************************************************
* FAndroidOpenGLFramePacer implementation
*******************************************************************/
#include "OpenGLDrvPrivate.h"
#include "Math/UnrealMathUtility.h"
#if USE_ANDROID_OPENGL_SWAPPY
#include "Android/AndroidJNI.h"
#include "Android/AndroidApplication.h"
#include "swappy/swappyGL.h"
#include "swappy/swappyGL_extra.h"
#include "swappy/swappy_common.h"
#include "HAL/Thread.h"
#include "Misc/ScopeRWLock.h"
#include "EngineGlobals.h"
struct FSwappyThreadManager : public SwappyThreadFunctions
{
static FRWLock SwappyThreadManagerMutex;
static TMap<uint64, TUniquePtr<FThread>> Threads;
FSwappyThreadManager()
{
start = [](SwappyThreadId* thread_id, void* (*thread_func)(void*),
void* user_data)
{
FRWScopeLock Lock(SwappyThreadManagerMutex, SLT_Write);
static int ThreadCount = 0;
TUniquePtr<FThread> NewThread = MakeUnique<FThread>(*FString::Printf(TEXT("SwappyThread%d"), ThreadCount++), [thread_func, user_data]() {thread_func(user_data); });
if (NewThread->GetThreadId())
{
*thread_id = NewThread->GetThreadId();
Threads.Add(*thread_id, MoveTemp(NewThread));
return 0;
}
return -1;
};
join = [](SwappyThreadId thread_id)
{
FRWScopeLock Lock(SwappyThreadManagerMutex, SLT_Write);
TUniquePtr<FThread> ThreadPtr;
if(ensure(Threads.RemoveAndCopyValue(thread_id, ThreadPtr)))
{
ThreadPtr->Join();
}
};
joinable = [](SwappyThreadId thread_id)
{
FRWScopeLock Lock(SwappyThreadManagerMutex, SLT_ReadOnly);
return Threads[thread_id]->IsJoinable();
};
}
}SwappyThreads;
TMap<uint64, TUniquePtr<FThread>> FSwappyThreadManager::Threads;
FRWLock FSwappyThreadManager::SwappyThreadManagerMutex;
#endif
#include "AndroidEGL.h"
#include <EGL/egl.h>
void FAndroidOpenGLFramePacer::Init()
{
bSwappyInit = false;
#if USE_ANDROID_OPENGL_SWAPPY
if (FAndroidPlatformRHIFramePacer::CVarUseSwappyForFramePacing.GetValueOnAnyThread() == 1)
{
// initialize now if set on startup
InitSwappy();
}
else
{
// initialize later if set by console
FAndroidPlatformRHIFramePacer::CVarUseSwappyForFramePacing.AsVariable()->SetOnChangedCallback(FConsoleVariableDelegate::CreateLambda([this](IConsoleVariable* Variable)
{
if (Variable->GetInt() == 1)
{
InitSwappy();
}
}));
}
#endif
}
#if USE_ANDROID_OPENGL_SWAPPY
namespace AndroidGL
{
void SwappyPostWaitCallback(void*, int64_t cpu_time_ns, int64_t gpu_time_ns)
{
const double GPUTimeInSeconds = (double)gpu_time_ns / 1000000000.0;
uint64 Cycles = GPUTimeInSeconds / FPlatformTime::GetSecondsPerCycle64();
GetDynamicRHI<FOpenGLDynamicRHI>()->RHISetExternalGPUTime(Cycles);
}
void SetSwappyPostWaitCallback()
{
SwappyTracer Tracer = { 0 };
Tracer.postWait = AndroidGL::SwappyPostWaitCallback;
SwappyGL_injectTracer(&Tracer);
int32 FrameTimeFenceInMillis = FAndroidPlatformRHIFramePacer::CVarSwappyGPUFrameTimeFence.GetValueOnAnyThread();
SwappyGL_setFenceTimeoutNS(FrameTimeFenceInMillis * 1000000); // millis to ns (ms * 1000000)
}
};
void FAndroidOpenGLFramePacer::InitSwappy()
{
if (!bSwappyInit)
{
extern void LoadSwappy();
LoadSwappy();
if( !FParse::Param(FCommandLine::Get(), TEXT("UseSwappyThreads")) )
{
Swappy_setThreadFunctions(&SwappyThreads);
}
// initialize Swappy
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
if (ensure(Env))
{
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Init Swappy: version %d"), Swappy_version());
SwappyGL_init(Env, FJavaWrapper::GameActivityThis);
AndroidGL::SetSwappyPostWaitCallback();
}
bSwappyInit = true;
}
}
#endif
FAndroidOpenGLFramePacer::~FAndroidOpenGLFramePacer()
{
#if USE_ANDROID_OPENGL_SWAPPY
FAndroidPlatformRHIFramePacer::CVarUseSwappyForFramePacing.AsVariable()->SetOnChangedCallback(FConsoleVariableDelegate());
if (bSwappyInit)
{
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Shutdown Swappy"));
SwappyGL_destroy();
bSwappyInit = false;
}
#endif
}
static bool GGetTimeStampsSucceededThisFrame = true;
static uint32 GGetTimeStampsRetryCount = 0;
static bool CanUseGetFrameTimestamps()
{
return FAndroidPlatformRHIFramePacer::CVarUseGetFrameTimestamps.GetValueOnAnyThread()
&& eglGetFrameTimestampsANDROID_p
&& eglGetNextFrameIdANDROID_p
&& eglPresentationTimeANDROID_p
&& (GGetTimeStampsRetryCount < FAndroidPlatformRHIFramePacer::CVarTimeStampErrorRetryCount.GetValueOnAnyThread());
}
static bool CanUseGetFrameTimestampsForThisFrame()
{
return CanUseGetFrameTimestamps() && GGetTimeStampsSucceededThisFrame;
}
bool ShouldUseGPUFencesToLimitLatency()
{
if (CanUseGetFrameTimestampsForThisFrame())
{
return true; // this method requires a GPU fence to give steady results
}
return FAndroidPlatformRHIFramePacer::CVarDisableOpenGLGPUSync.GetValueOnAnyThread() == 0; // otherwise just based on the FAndroidPlatformRHIFramePacer::CVar; thought to be bad to use GPU fences on PowerVR
}
static uint32 NextFrameIDSlot = 1;
#define NUM_FRAMES_TO_MONITOR (4)
static EGLuint64KHR FrameIDs[NUM_FRAMES_TO_MONITOR] = { 0 };
static int32 RecordedFrameInterval[100];
static int32 NumRecordedFrameInterval = 0;
extern float AndroidThunkCpp_GetMetaDataFloat(const FString& Key);
bool FAndroidOpenGLFramePacer::SupportsFramePaceInternal(int32 QueryFramePace, int32& OutRefreshRate, int32& OutSyncInterval)
{
#if USE_ANDROID_OPENGL_SWAPPY
if (FAndroidPlatformRHIFramePacer::CVarUseSwappyForFramePacing.GetValueOnAnyThread() == 1)
{
TArray<int32> RefreshRates = FAndroidMisc::GetSupportedNativeDisplayRefreshRates();
RefreshRates.Sort();
FString RefreshRatesString;
for (int32 Rate : RefreshRates)
{
RefreshRatesString += FString::Printf(TEXT(" %d"), Rate);
}
UE_LOG(LogRHI, Log, TEXT("FAndroidOpenGLFramePacer -> Supported Refresh Rates:%s"), *RefreshRatesString);
for (int32 Rate : RefreshRates)
{
if ((Rate % QueryFramePace) == 0)
{
UE_LOG(LogRHI, Log, TEXT("Supports %d using refresh rate %d and sync interval %d"), QueryFramePace, Rate, Rate / QueryFramePace);
OutRefreshRate = Rate;
OutSyncInterval = Rate / QueryFramePace;
return true;
}
}
// check if we want to use naive frame pacing at less than a multiple of supported refresh rate
if (FAndroidPlatformRHIFramePacer::CVarSupportNonVSyncMultipleFrameRates.GetValueOnAnyThread() == 1)
{
for (int32 Rate : RefreshRates)
{
if (Rate > QueryFramePace)
{
UE_LOG(LogRHI, Log, TEXT("Supports %d using refresh rate %d with naive frame pacing"), QueryFramePace, Rate);
OutRefreshRate = Rate;
OutSyncInterval = 0;
return true;
}
}
}
}
#endif
OutRefreshRate = QueryFramePace;
OutSyncInterval = 0;
return FGenericPlatformRHIFramePacer::SupportsFramePace(QueryFramePace);
}
bool FAndroidOpenGLFramePacer::SupportsFramePace(int32 QueryFramePace)
{
int32 TempRefreshRate, TempSyncInterval;
return SupportsFramePaceInternal(QueryFramePace, TempRefreshRate, TempSyncInterval);
}
extern bool AndroidThunkCpp_IsOculusMobileApplication();
bool FAndroidOpenGLFramePacer::SwapBuffers(bool bLockToVsync)
{
SCOPED_NAMED_EVENT(STAT_OpenGLSwapBuffersTime, FColor::Red)
#if !UE_BUILD_SHIPPING
if (FAndroidPlatformRHIFramePacer::CVarStallSwap.GetValueOnAnyThread() > 0.0f)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_Swap_Intentional_Stall);
FPlatformProcess::Sleep(FAndroidPlatformRHIFramePacer::CVarStallSwap.GetValueOnRenderThread() / 1000.0f);
}
#endif
VERIFY_EGL_SCOPE();
AndroidEGL* AnEGL = AndroidEGL::GetInstance();
EGLDisplay eglDisplay = AnEGL->GetDisplay();
EGLSurface eglSurface = AnEGL->GetSurface();
int32 SyncInterval = FAndroidPlatformRHIFramePacer::GetLegacySyncInterval();
if (AnEGL->IsUsingWindowedSurface() == false && !AndroidThunkCpp_IsOculusMobileApplication())
{
// offscreen surface, no swap to perform, limit update rate.
FPlatformProcess::Sleep(0.1);
return true;
}
bool bPrintMethod = false;
#if USE_ANDROID_OPENGL_SWAPPY
int32 CurrentFramePace = 0;
if (FAndroidPlatformRHIFramePacer::CVarUseSwappyForFramePacing.GetValueOnRenderThread() != 0 && (CurrentFramePace = FAndroidPlatformRHIFramePacer::GetFramePace()) != 0 && ensure(bSwappyInit))
{
ANativeWindow* CurrentNativeWindow = AnEGL->GetNativeWindow();
if (CurrentNativeWindow != CachedNativeWindow)
{
CachedNativeWindow = CurrentNativeWindow;
UE_LOG(LogRHI, Verbose, TEXT("Swappy - setting native window %p"), CachedNativeWindow);
SwappyGL_setWindow(CurrentNativeWindow);
}
// cache refresh rate and sync interval
if (CurrentFramePace != CachedFramePace)
{
CachedFramePace = CurrentFramePace;
SupportsFramePaceInternal(CurrentFramePace, CachedRefreshRate, CachedSyncInterval);
SwappyGL_resetFramePacing();
}
SwappyGL_setAutoSwapInterval(false);
if (CachedSyncInterval != 0)
{
// Multiple of sync interval, use swappy directly
UE_LOG(LogRHI, Verbose, TEXT("Setting swappy to interval of %" PRId64 " (%d fps)"), (int64)(1000000000L) / (int64)CurrentFramePace, CurrentFramePace);
SwappyGL_setSwapIntervalNS((1000000000L) / (int64)CurrentFramePace);
}
else
{
// Unsupported frame rate. Set to higher refresh rate
SwappyGL_setSwapIntervalNS((1000000000L) / (int64)CachedRefreshRate);
// use naive frame pacing to limit the frame rate
float MinTimeBetweenFrames = (1.f / CurrentFramePace);
float ThisTime = FPlatformTime::Seconds() - LastTimeEmulatedSync;
if (ThisTime > 0 && ThisTime < MinTimeBetweenFrames)
{
FPlatformProcess::Sleep(MinTimeBetweenFrames - ThisTime);
}
}
SwappyGL_swap(eglDisplay, eglSurface);
LastTimeEmulatedSync = FPlatformTime::Seconds();
}
else
#endif
{
if (DesiredSyncIntervalRelativeTo60Hz != SyncInterval)
{
GGetTimeStampsRetryCount = 0;
bPrintMethod = true;
DesiredSyncIntervalRelativeTo60Hz = SyncInterval;
DriverRefreshRate = 60.0f;
DriverRefreshNanos = 16666666;
EGLnsecsANDROID EGL_COMPOSITE_DEADLINE_ANDROID_Value = -1;
EGLnsecsANDROID EGL_COMPOSITE_INTERVAL_ANDROID_Value = -1;
EGLnsecsANDROID EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID_Value = -1;
if (eglGetCompositorTimingANDROID_p)
{
{
EGLint Item = EGL_COMPOSITE_DEADLINE_ANDROID;
if (!eglGetCompositorTimingANDROID_p(eglDisplay, eglSurface, 1, &Item, &EGL_COMPOSITE_DEADLINE_ANDROID_Value))
{
EGL_COMPOSITE_DEADLINE_ANDROID_Value = -1;
}
}
{
EGLint Item = EGL_COMPOSITE_INTERVAL_ANDROID;
if (!eglGetCompositorTimingANDROID_p(eglDisplay, eglSurface, 1, &Item, &EGL_COMPOSITE_INTERVAL_ANDROID_Value))
{
EGL_COMPOSITE_INTERVAL_ANDROID_Value = -1;
}
}
{
EGLint Item = EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID;
if (!eglGetCompositorTimingANDROID_p(eglDisplay, eglSurface, 1, &Item, &EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID_Value))
{
EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID_Value = -1;
}
}
UE_LOG(LogRHI, Log, TEXT("AndroidEGL:SwapBuffers eglGetCompositorTimingANDROID EGL_COMPOSITE_DEADLINE_ANDROID=%lld, EGL_COMPOSITE_INTERVAL_ANDROID=%lld, EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID=%lld"),
EGL_COMPOSITE_DEADLINE_ANDROID_Value,
EGL_COMPOSITE_INTERVAL_ANDROID_Value,
EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID_Value
);
}
float RefreshRate = AndroidThunkCpp_GetMetaDataFloat(TEXT("unreal.display.getRefreshRate"));
UE_LOG(LogRHI, Log, TEXT("JNI Display getRefreshRate=%f"),
RefreshRate
);
if (EGL_COMPOSITE_INTERVAL_ANDROID_Value >= 4000000 && EGL_COMPOSITE_INTERVAL_ANDROID_Value <= 41666666)
{
DriverRefreshRate = float(1000000000.0 / double(EGL_COMPOSITE_INTERVAL_ANDROID_Value));
DriverRefreshNanos = EGL_COMPOSITE_INTERVAL_ANDROID_Value;
}
else if (RefreshRate >= 24.0f && RefreshRate <= 250.0f)
{
DriverRefreshRate = RefreshRate;
DriverRefreshNanos = int64(0.5 + 1000000000.0 / double(RefreshRate));
}
UE_LOG(LogRHI, Log, TEXT("Final display timing metrics: DriverRefreshRate=%7.4f DriverRefreshNanos=%lld"),
DriverRefreshRate,
DriverRefreshNanos
);
// make sure requested interval is in supported range
EGLint MinSwapInterval, MaxSwapInterval;
AnEGL->GetSwapIntervalRange(MinSwapInterval, MaxSwapInterval);
int64 SyncIntervalNanos = (30 + 1000000000l * int64(SyncInterval)) / 60;
int32 UnderDriverInterval = int32(SyncIntervalNanos / DriverRefreshNanos);
int32 OverDriverInterval = UnderDriverInterval + 1;
int64 UnderNanos = int64(UnderDriverInterval) * DriverRefreshNanos;
int64 OverNanos = int64(OverDriverInterval) * DriverRefreshNanos;
DesiredSyncIntervalRelativeToDevice = (FMath::Abs(SyncIntervalNanos - UnderNanos) < FMath::Abs(SyncIntervalNanos - OverNanos)) ?
UnderDriverInterval : OverDriverInterval;
int32 DesiredDriverSyncInterval = FMath::Clamp<int32>(DesiredSyncIntervalRelativeToDevice, MinSwapInterval, MaxSwapInterval);
UE_LOG(LogRHI, Log, TEXT("AndroidEGL:SwapBuffers Min=%d, Max=%d, Request=%d, ClosestDriver=%d, SetDriver=%d"), MinSwapInterval, MaxSwapInterval, DesiredSyncIntervalRelativeTo60Hz, DesiredSyncIntervalRelativeToDevice, DesiredDriverSyncInterval);
if (DesiredDriverSyncInterval != DriverSyncIntervalRelativeToDevice)
{
DriverSyncIntervalRelativeToDevice = DesiredDriverSyncInterval;
UE_LOG(LogRHI, Log, TEXT("Called eglSwapInterval %d"), DesiredDriverSyncInterval);
eglSwapInterval(eglDisplay, DriverSyncIntervalRelativeToDevice);
}
}
if (DesiredSyncIntervalRelativeToDevice > DriverSyncIntervalRelativeToDevice)
{
{
UE_CLOG(bPrintMethod, LogRHI, Display, TEXT("Using niave method for frame pacing (possible with timestamps method)"));
if (LastTimeEmulatedSync > 0.0)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_StallForEmulatedSyncInterval);
float MinTimeBetweenFrames = (float(DesiredSyncIntervalRelativeToDevice) / DriverRefreshRate);
for (;;)
{
float ThisTime = FPlatformTime::Seconds() - LastTimeEmulatedSync;
// sleep only when there is substantial time left to a next sync interval
// for a small duration rely on eglSwapBuffers
if (ThisTime > 0.001f && ThisTime < MinTimeBetweenFrames)
{
// do not sleep for too long, poll occlussion queries from time to time as RT might be waiting for them
float SleepDuration = FMath::Min(MinTimeBetweenFrames - ThisTime, 0.003f);
FPlatformProcess::Sleep(SleepDuration);
}
else
{
break;
}
}
}
}
}
if (CanUseGetFrameTimestamps())
{
UE_CLOG(bPrintMethod, LogRHI, Display, TEXT("Using eglGetFrameTimestampsANDROID method for frame pacing"));
//static bool bPrintOnce = true;
if (FrameIDs[(int32(NextFrameIDSlot) - 1) % NUM_FRAMES_TO_MONITOR])
// not supported && eglGetFrameTimestampsSupportedANDROID_p && eglGetFrameTimestampsSupportedANDROID_p(eglDisplay, eglSurface, EGL_FIRST_COMPOSITION_START_TIME_ANDROID))
{
//UE_CLOG(bPrintOnce, LogRHI, Log, TEXT("eglGetFrameTimestampsSupportedANDROID retured true for EGL_FIRST_COMPOSITION_START_TIME_ANDROID"));
EGLint TimestampList = EGL_FIRST_COMPOSITION_START_TIME_ANDROID;
//EGLint TimestampList = EGL_COMPOSITION_LATCH_TIME_ANDROID;
//EGLint TimestampList = EGL_LAST_COMPOSITION_START_TIME_ANDROID;
//EGLint TimestampList = EGL_DISPLAY_PRESENT_TIME_ANDROID;
EGLnsecsANDROID Result = 0;
int32 DeltaFrameIndex = 1;
for (int32 Index = int32(NextFrameIDSlot) - 1; Index >= int32(NextFrameIDSlot) - NUM_FRAMES_TO_MONITOR && Index >= 0; Index--)
{
Result = 0;
if (FrameIDs[Index % NUM_FRAMES_TO_MONITOR])
{
eglGetFrameTimestampsANDROID_p(eglDisplay, eglSurface, FrameIDs[Index % NUM_FRAMES_TO_MONITOR], 1, &TimestampList, &Result);
}
if (Result > 0)
{
break;
}
DeltaFrameIndex++;
}
GGetTimeStampsSucceededThisFrame = Result > 0;
if (GGetTimeStampsSucceededThisFrame)
{
EGLnsecsANDROID FudgeFactor = 0; // 8333 * 1000;
EGLnsecsANDROID DeltaNanos = EGLnsecsANDROID(DesiredSyncIntervalRelativeToDevice) * EGLnsecsANDROID(DeltaFrameIndex) * DriverRefreshNanos;
EGLnsecsANDROID PresentationTime = Result + DeltaNanos + FudgeFactor;
eglPresentationTimeANDROID_p(eglDisplay, eglSurface, PresentationTime);
GGetTimeStampsRetryCount = 0;
}
else
{
GGetTimeStampsRetryCount++;
if (GGetTimeStampsRetryCount == FAndroidPlatformRHIFramePacer::CVarTimeStampErrorRetryCount.GetValueOnAnyThread())
{
UE_LOG(LogRHI, Log, TEXT("eglGetFrameTimestampsANDROID_p failed for %d consecutive frames, reverting to naive frame pacer."), GGetTimeStampsRetryCount);
}
}
}
else
{
//UE_CLOG(bPrintOnce, LogRHI, Log, TEXT("eglGetFrameTimestampsSupportedANDROID doesn't exist or retured false for EGL_FIRST_COMPOSITION_START_TIME_ANDROID, discarding eglGetNextFrameIdANDROID_p and eglGetFrameTimestampsANDROID_p"));
}
//bPrintOnce = false;
}
LastTimeEmulatedSync = FPlatformTime::Seconds();
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_eglSwapBuffers);
FrameIDs[(NextFrameIDSlot) % NUM_FRAMES_TO_MONITOR] = 0;
if (eglGetNextFrameIdANDROID_p && (CanUseGetFrameTimestamps() || FAndroidPlatformRHIFramePacer::CVarSpewGetFrameTimestamps.GetValueOnAnyThread()))
{
eglGetNextFrameIdANDROID_p(eglDisplay, eglSurface, &FrameIDs[(NextFrameIDSlot) % NUM_FRAMES_TO_MONITOR]);
}
NextFrameIDSlot++;
if (eglSurface == NULL || !eglSwapBuffers(eglDisplay, eglSurface))
{
// shutdown if swapbuffering goes down
if (SwapBufferFailureCount > 10)
{
//Process.killProcess(Process.myPid()); //@todo android
}
SwapBufferFailureCount++;
// basic reporting
if (eglSurface == NULL)
{
return false;
}
else
{
if (eglGetError() == EGL_CONTEXT_LOST)
{
//Logger.LogOut("swapBuffers: EGL11.EGL_CONTEXT_LOST err: " + eglGetError());
//Process.killProcess(Process.myPid()); //@todo android
}
}
return false;
}
}
if (DesiredSyncIntervalRelativeToDevice > 0 && eglGetFrameTimestampsANDROID_p && FAndroidPlatformRHIFramePacer::CVarSpewGetFrameTimestamps.GetValueOnAnyThread())
{
static EGLint TimestampList[9] =
{
EGL_REQUESTED_PRESENT_TIME_ANDROID,
EGL_RENDERING_COMPLETE_TIME_ANDROID,
EGL_COMPOSITION_LATCH_TIME_ANDROID,
EGL_FIRST_COMPOSITION_START_TIME_ANDROID,
EGL_LAST_COMPOSITION_START_TIME_ANDROID,
EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID,
EGL_DISPLAY_PRESENT_TIME_ANDROID,
EGL_DEQUEUE_READY_TIME_ANDROID,
EGL_READS_DONE_TIME_ANDROID
};
static const TCHAR* TimestampStrings[9] =
{
TEXT("EGL_REQUESTED_PRESENT_TIME_ANDROID"),
TEXT("EGL_RENDERING_COMPLETE_TIME_ANDROID"),
TEXT("EGL_COMPOSITION_LATCH_TIME_ANDROID"),
TEXT("EGL_FIRST_COMPOSITION_START_TIME_ANDROID"),
TEXT("EGL_LAST_COMPOSITION_START_TIME_ANDROID"),
TEXT("EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID"),
TEXT("EGL_DISPLAY_PRESENT_TIME_ANDROID"),
TEXT("EGL_DEQUEUE_READY_TIME_ANDROID"),
TEXT("EGL_READS_DONE_TIME_ANDROID")
};
EGLnsecsANDROID Results[NUM_FRAMES_TO_MONITOR][9] = { {0} };
EGLnsecsANDROID FirstRealValue = 0;
for (int32 Index = int32(NextFrameIDSlot) - NUM_FRAMES_TO_MONITOR; Index < int32(NextFrameIDSlot); Index++)
{
eglGetFrameTimestampsANDROID_p(eglDisplay, eglSurface, FrameIDs[Index % NUM_FRAMES_TO_MONITOR], 9, TimestampList, Results[Index % NUM_FRAMES_TO_MONITOR]);
for (int32 IndexInner = 0; IndexInner < 9; IndexInner++)
{
if (!FirstRealValue || (Results[Index % NUM_FRAMES_TO_MONITOR][IndexInner] > 1 && Results[Index % NUM_FRAMES_TO_MONITOR][IndexInner] < FirstRealValue))
{
FirstRealValue = Results[Index % NUM_FRAMES_TO_MONITOR][IndexInner];
}
}
}
UE_CLOG(FAndroidPlatformRHIFramePacer::CVarSpewGetFrameTimestamps.GetValueOnAnyThread() > 1, LogRHI, Log, TEXT("************************************ frame %d base time is %lld"), NextFrameIDSlot - 1, FirstRealValue);
for (int32 Index = int32(NextFrameIDSlot) - NUM_FRAMES_TO_MONITOR; Index < int32(NextFrameIDSlot); Index++)
{
UE_CLOG(FAndroidPlatformRHIFramePacer::CVarSpewGetFrameTimestamps.GetValueOnAnyThread() > 1, LogRHI, Log, TEXT("eglGetFrameTimestampsANDROID_p frame %d"), Index);
for (int32 IndexInner = 0; IndexInner < 9; IndexInner++)
{
int32 MsVal = (Results[Index % NUM_FRAMES_TO_MONITOR][IndexInner] > 1) ? int32((Results[Index % NUM_FRAMES_TO_MONITOR][IndexInner] - FirstRealValue) / 1000000) : int32(Results[Index % NUM_FRAMES_TO_MONITOR][IndexInner]);
UE_CLOG(FAndroidPlatformRHIFramePacer::CVarSpewGetFrameTimestamps.GetValueOnAnyThread() > 1, LogRHI, Log, TEXT(" %8d %s"), MsVal, TimestampStrings[IndexInner]);
}
}
int32 IndexLast = int32(NextFrameIDSlot) - NUM_FRAMES_TO_MONITOR;
int32 IndexLastNext = IndexLast + 1;
if (Results[IndexLast % NUM_FRAMES_TO_MONITOR][3] > 1 && Results[IndexLastNext % NUM_FRAMES_TO_MONITOR][3] > 1)
{
int32 MsVal = int32((Results[IndexLastNext % NUM_FRAMES_TO_MONITOR][3] - Results[IndexLast % NUM_FRAMES_TO_MONITOR][3]) / 1000000);
RecordedFrameInterval[NumRecordedFrameInterval++] = MsVal;
if (NumRecordedFrameInterval == 100)
{
FString All;
int32 NumOnTarget = 0;
int32 NumBelowTarget = 0;
int32 NumAboveTarget = 0;
for (int32 Index = 0; Index < 100; Index++)
{
if (Index)
{
All += TCHAR(' ');
}
All += FString::Printf(TEXT("%d"), RecordedFrameInterval[Index]);
if (RecordedFrameInterval[Index] > DesiredSyncIntervalRelativeTo60Hz * 16 - 8 && RecordedFrameInterval[Index] < DesiredSyncIntervalRelativeTo60Hz * 16 + 8)
{
NumOnTarget++;
}
else if (RecordedFrameInterval[Index] < DesiredSyncIntervalRelativeTo60Hz * 16)
{
NumBelowTarget++;
}
else
{
NumAboveTarget++;
}
}
UE_LOG(LogRHI, Log, TEXT("%3d fast %3d ok %3d slow %s"), NumBelowTarget, NumOnTarget, NumAboveTarget, *All);
NumRecordedFrameInterval = 0;
}
}
}
}
return true;
}
#endif