// Copyright Epic Games, Inc. All Rights Reserved. #include "AndroidOpenGL.h" #if USE_ANDROID_OPENGL #include "OpenGLDrvPrivate.h" #include "OpenGLES.h" #include "Android/AndroidWindow.h" #include "AndroidOpenGLPrivate.h" #include "Android/AndroidPlatformMisc.h" #include "Android/AndroidPlatformFramePacer.h" #include "Android/AndroidJNI.h" #include "GenericPlatform/GenericPlatformCrashContext.h" #include "Misc/ConfigCacheIni.h" #include "String/Find.h" #include "String/LexFromString.h" #include "Android/AndroidDynamicRHI.h" #include "PSOMetrics.h" int32 FAndroidOpenGL::GLMajorVerion = 0; int32 FAndroidOpenGL::GLMinorVersion = 0; bool FAndroidOpenGL::bSupportsImageExternal = false; bool FAndroidOpenGL::bRequiresAdrenoTilingHint = false; static TAutoConsoleVariable CVarEnableAdrenoTilingHint( TEXT("r.Android.EnableAdrenoTilingHint"), 1, TEXT("Whether Adreno-based Android devices should hint to the driver to use tiling mode for the mobile base pass.\n") TEXT(" 0 = hinting disabled\n") TEXT(" 1 = hinting enabled for Adreno devices running Andorid 8 or earlier [default]\n") TEXT(" 2 = hinting always enabled for Adreno devices\n")); static TAutoConsoleVariable CVarDisableEarlyFragmentTests( TEXT("r.Android.DisableEarlyFragmentTests"), 0, TEXT("Whether to disable early_fragment_tests if any \n"), ECVF_ReadOnly); static TAutoConsoleVariable CVarDisableFBFNonCoherent( TEXT("r.Android.DisableFBFNonCoherent"), 0, TEXT("Whether to disable usage of QCOM_shader_framebuffer_fetch_noncoherent extension\n"), ECVF_ReadOnly); struct FPlatformOpenGLDevice { bool TargetDirty; void SetCurrentRenderingContext(); FPlatformOpenGLDevice(); ~FPlatformOpenGLDevice(); void Init(); void LoadEXT(); }; FPlatformOpenGLDevice::~FPlatformOpenGLDevice() { FPlatformRHIFramePacer::Destroy(); FAndroidAppEntry::ReleaseEGL(); } FPlatformOpenGLDevice::FPlatformOpenGLDevice() { } // call out to JNI to see if the application was packaged for Oculus Mobile extern bool AndroidThunkCpp_IsOculusMobileApplication(); // RenderDoc #define GL_DEBUG_TOOL_EXT 0x6789 static bool bRunningUnderRenderDoc = false; extern bool IsInAndroidEventThread(); #if UE_BUILD_SHIPPING #define CHECK_JNI_EXCEPTIONS(env) env->ExceptionClear(); #else #define CHECK_JNI_EXCEPTIONS(env) if (env->ExceptionCheck()) {env->ExceptionDescribe();env->ExceptionClear();} #endif struct FOpenGLRemoteGLProgramCompileJNI { jclass OGLServiceAccessor = 0; jmethodID DispatchProgramLink = 0; jmethodID StartRemoteProgramLink = 0; jmethodID StopRemoteProgramLink = 0; jmethodID AreProgramServicesReady = 0; jmethodID HaveServicesFailed = 0; jclass ProgramResponseClass = 0; jfieldID ProgramResponse_SuccessField = 0; jfieldID ProgramResponse_ErrorField = 0; jfieldID ProgramResponse_SHMOutputHandleField = 0; jfieldID ProgramResponse_CompiledBinaryField = 0; jfieldID ProgramResponse_CompilationDurationField = 0; bool bAllFound = false; void Init(JNIEnv* Env) { // class JNIProgramLinkResponse // { // boolean bCompileSuccess; // String ErrorMessage; // byte[] CompiledProgram; // }; // JNIProgramLinkResponse AndroidThunkJava_OGLRemoteProgramLink(...): check(OGLServiceAccessor == 0); OGLServiceAccessor = AndroidJavaEnv::FindJavaClassGlobalRef("com/epicgames/unreal/psoservices/PSOProgramServiceAccessor"); CHECK_JNI_EXCEPTIONS(Env); if(OGLServiceAccessor) { DispatchProgramLink = FJavaWrapper::FindStaticMethod(Env, OGLServiceAccessor, "AndroidThunkJava_OGLRemoteProgramLink", "([BJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Lcom/epicgames/unreal/psoservices/PSOProgramServiceAccessor$JNIProgramLinkResponse;", false); CHECK_JNI_EXCEPTIONS(Env); StartRemoteProgramLink = FJavaWrapper::FindStaticMethod(Env, OGLServiceAccessor, "AndroidThunkJava_StartRemoteProgramLink", "(IZZ)Z", false); CHECK_JNI_EXCEPTIONS(Env); StopRemoteProgramLink = FJavaWrapper::FindStaticMethod(Env, OGLServiceAccessor, "AndroidThunkJava_StopRemoteProgramLink", "()V", false); CHECK_JNI_EXCEPTIONS(Env); AreProgramServicesReady = FJavaWrapper::FindStaticMethod(Env, OGLServiceAccessor, "AndroidThunkJava_AreProgramServicesReady", "()Z", false); CHECK_JNI_EXCEPTIONS(Env); HaveServicesFailed = FJavaWrapper::FindStaticMethod(Env, OGLServiceAccessor, "AndroidThunkJava_HaveServicesFailed", "()Z", false); CHECK_JNI_EXCEPTIONS(Env); ProgramResponseClass = AndroidJavaEnv::FindJavaClassGlobalRef("com/epicgames/unreal/psoservices/PSOProgramServiceAccessor$JNIProgramLinkResponse"); CHECK_JNI_EXCEPTIONS(Env); ProgramResponse_SuccessField = FJavaWrapper::FindField(Env, ProgramResponseClass, "bCompileSuccess", "Z", true); CHECK_JNI_EXCEPTIONS(Env); ProgramResponse_CompiledBinaryField = FJavaWrapper::FindField(Env, ProgramResponseClass, "CompiledProgram", "[B", true); CHECK_JNI_EXCEPTIONS(Env); ProgramResponse_ErrorField = FJavaWrapper::FindField(Env, ProgramResponseClass, "ErrorMessage", "Ljava/lang/String;", true); CHECK_JNI_EXCEPTIONS(Env); ProgramResponse_SHMOutputHandleField = FJavaWrapper::FindField(Env, ProgramResponseClass, "SHMOutputHandle", "I", true); CHECK_JNI_EXCEPTIONS(Env); ProgramResponse_CompilationDurationField = FJavaWrapper::FindField(Env, ProgramResponseClass, "CompilationDuration", "F", true); CHECK_JNI_EXCEPTIONS(Env); } bAllFound = OGLServiceAccessor && DispatchProgramLink && StartRemoteProgramLink && StopRemoteProgramLink && AreProgramServicesReady && HaveServicesFailed && ProgramResponseClass && ProgramResponse_SuccessField && ProgramResponse_CompiledBinaryField && ProgramResponse_ErrorField && ProgramResponse_SHMOutputHandleField && ProgramResponse_CompilationDurationField; UE_CLOG(!bAllFound, LogRHI, Fatal, TEXT("Failed to find JNI GL remote program compiler.")); } }OpenGLRemoteGLProgramCompileJNI; static bool AreAndroidOpenGLRemoteCompileServicesAvailable() { static int RemoteCompileService = -1; if (RemoteCompileService == -1) { const FString* ConfigRulesDisableProgramCompileServices = FAndroidMisc::GetConfigRulesVariable(TEXT("DisableProgramCompileServices")); bool bConfigRulesDisableProgramCompileServices = ConfigRulesDisableProgramCompileServices && ConfigRulesDisableProgramCompileServices->Equals("true", ESearchCase::IgnoreCase); static const auto CVarBinaryProgramCache = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ProgramBinaryCache.Enable")); static const auto CVarNumRemoteProgramCompileServices = IConsoleManager::Get().FindConsoleVariable(TEXT("Android.OpenGL.NumRemoteProgramCompileServices")); RemoteCompileService = !bConfigRulesDisableProgramCompileServices && OpenGLRemoteGLProgramCompileJNI.bAllFound && (CVarBinaryProgramCache->GetInt() != 0) && (CVarNumRemoteProgramCompileServices->GetInt() > 0); FGenericCrashContext::SetEngineData(TEXT("Android.PSOService"), RemoteCompileService == 0? TEXT("disabled") : TEXT("enabled")); UE_CLOG(!RemoteCompileService, LogRHI, Log, TEXT("Remote PSO services disabled: (%d, %d, %d, %d)"), bConfigRulesDisableProgramCompileServices, OpenGLRemoteGLProgramCompileJNI.bAllFound, CVarBinaryProgramCache->GetInt(), CVarNumRemoteProgramCompileServices->GetInt()); } return RemoteCompileService; } void FPlatformOpenGLDevice::Init() { // Initialize frame pacer FPlatformRHIFramePacer::Init(new FAndroidOpenGLFramePacer()); extern void InitDebugContext(); bRunningUnderRenderDoc = glIsEnabled(GL_DEBUG_TOOL_EXT) != GL_FALSE; FPlatformMisc::LowLevelOutputDebugString(TEXT("FPlatformOpenGLDevice:Init")); bool bCreateSurface = !AndroidThunkCpp_IsOculusMobileApplication(); // With the new window system we may not have a window yet, the surface will be initially off screen. AndroidEGL::GetInstance()->InitRenderSurface(false, bCreateSurface, TOptional()); LoadEXT(); PlatformRenderingContextSetup(this); InitDebugContext(); { VERIFY_GL_SCOPE(); GLuint* DefaultVao = &AndroidEGL::GetInstance()->GetRenderingContext()->DefaultVertexArrayObject; if (*DefaultVao == 0) { glGenVertexArrays(1, DefaultVao); glBindVertexArray(*DefaultVao); } } InitDefaultGLContextState(); AndroidEGL::GetInstance()->InitBackBuffer(); //can be done only after context is made current. OpenGLRemoteGLProgramCompileJNI.Init(FAndroidApplication::GetJavaEnv()); // AsyncPipelinePrecompile can be enabled on android GL, precompiles are compiled via separate processes and the result is stored in GL's LRU cache as an evicted binary. // The lru cache is a requirement as the precompile produces binary program data only. GRHISupportsAsyncPipelinePrecompile = AreAndroidOpenGLRemoteCompileServicesAvailable(); #if USE_ANDROID_OPENGL_SWAPPY bool bIsSwappyEnabled = FAndroidPlatformRHIFramePacer::CVarUseSwappyForFramePacing.GetValueOnAnyThread() == 1; // don't even initialize this if swappy is not enabled if (bIsSwappyEnabled) { IConsoleVariable* CVarAndroidSupportsTimestampQueries = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Android.SupportsTimestampQueries")); IConsoleVariable* CVarAndroidSupportsDynamicResolution = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Android.SupportsDynamicResolution")); bool bSupportsTimestampQueries = CVarAndroidSupportsTimestampQueries != nullptr && CVarAndroidSupportsTimestampQueries->GetBool(); bool bSupportsDynamicResolution = CVarAndroidSupportsDynamicResolution != nullptr && CVarAndroidSupportsDynamicResolution->GetBool(); GRHISupportsDynamicResolution = bSupportsDynamicResolution; GSupportsTimestampRenderQueries = bSupportsTimestampQueries; GRHISupportsGPUTimestampBubblesRemoval = true; } #endif // register the new window behavior life cycle callbacks. if (FPlatformMisc::UseNewWindowBehavior()) { UE::FAndroidPlatformDynamicRHI::SetRHIOnReleaseWindowCallback( [](const TOptional& WindowContainer) { check(IsInAndroidEventThread()); UE_LOG(LogRHI, Log, TEXT("OnReleaseWindowCallback event thread")); FGraphEventRef OnComplete = FGraphEvent::CreateGraphEvent(); ENQUEUE_RENDER_COMMAND(OnAndroidLostWindow)([&WindowContainer, OnComplete](FRHICommandListImmediate& RHICmdList) mutable { UE_LOG(LogRHI, Log, TEXT("OnReleaseWindowCallback: RT")); RHICmdList.EnqueueLambda([&WindowContainer, OnComplete](FRHICommandListImmediate& ExecutingCmdList) mutable { UE_LOG(LogRHI, Log, TEXT("GLES OnReleaseWindowCallback: RHI, set egl surface to offscreen (window %p)"), WindowContainer->GetANativeWindow()); AndroidEGL::GetInstance()->UnBindRender(); AndroidEGL::GetInstance()->InitRenderSurface(true, false, WindowContainer); AndroidEGL::GetInstance()->SetCurrentRenderingContext(); UE_LOG(LogRHI, Log, TEXT("GLES OnReleaseWindowCallback: RHI, swap surface done.")); }); RHICmdList.RHIThreadFence(true); RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread); OnComplete->DispatchSubsequents(); UE_LOG(LogRHI, Log, TEXT("OnReleaseWindowCallback: RT done")); }); UE_LOG(LogRHI, Log, TEXT("OnLostWindow: waiting for RT")); OnComplete->Wait(); UE_LOG(LogRHI, Log, TEXT("OnLostWindow: done")); }); UE::FAndroidPlatformDynamicRHI::SetRHIOnReInitWindowCallback( [](const TOptional& WindowContainer) { UE_LOG(LogRHI, Log, TEXT("OnReInitWindowCallback event thread")); FGraphEventRef OnComplete = FGraphEvent::CreateGraphEvent(); ENQUEUE_RENDER_COMMAND(OnAndroidFoundWindow)([&WindowContainer, OnComplete](FRHICommandListImmediate& RHICmdList) mutable { UE_LOG(LogRHI, Log, TEXT("OnReInitWindowCallback: RT")); RHICmdList.EnqueueLambda([&WindowContainer](FRHICommandListImmediate& ExecutingCmdList) mutable { // TODO: we should be searching for the affected viewport here. UE_LOG(LogRHI, Log, TEXT("GLES OnReInitWindowCallback: RHI, set egl surface to %p"), WindowContainer->GetANativeWindow()); AndroidEGL::GetInstance()->SetRenderContextWindowSurface(WindowContainer); // gles can swap out the display surface immediately. UE_LOG(LogRHI, Log, TEXT("SWP: GLES OnReInitWindowCallback: RHI, done")); }); RHICmdList.RHIThreadFence(true); RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread); OnComplete->DispatchSubsequents(); UE_LOG(LogRHI, Log, TEXT("OnReInitWindowCallback: RT done")); }); UE_LOG(LogRHI, Log, TEXT("OnReInitWindowCallback: waiting for RT")); OnComplete->Wait(); UE_LOG(LogRHI, Log, TEXT("OnReInitWindowCallback: done")); }); } } FPlatformOpenGLDevice* PlatformCreateOpenGLDevice() { FPlatformOpenGLDevice* Device = new FPlatformOpenGLDevice(); Device->Init(); return Device; } bool PlatformCanEnableGPUCapture() { return bRunningUnderRenderDoc; } void PlatformReleaseOpenGLContext(FPlatformOpenGLDevice* Device, FPlatformOpenGLContext* Context) { } void* PlatformGetWindow(FPlatformOpenGLContext* Context, void** AddParam) { check(Context); return (void*)&Context->eglContext; } bool PlatformBlitToViewport(IRHICommandContext& RHICmdContext, FPlatformOpenGLDevice* Device, const FOpenGLViewport& Viewport, uint32 BackbufferSizeX, uint32 BackbufferSizeY, bool bPresent,bool bLockToVsync ) { SCOPED_NAMED_EVENT(STAT_PlatformBlitToViewportTime, FColor::Red) FPlatformOpenGLContext* const Context = Viewport.GetGLContext(); if (bPresent && AndroidEGL::GetInstance()->IsOfflineSurfaceRequired()) { if (Device->TargetDirty) { VERIFY_GL_SCOPE(); glBindFramebuffer(GL_FRAMEBUFFER, Context->ViewportFramebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, Context->BackBufferTarget, Context->BackBufferResource, 0); Device->TargetDirty = false; } { VERIFY_GL_SCOPE(); glDisable(GL_FRAMEBUFFER_SRGB); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); FOpenGL::DrawBuffer(GL_BACK); glBindFramebuffer(GL_READ_FRAMEBUFFER, Context->ViewportFramebuffer); FOpenGL::ReadBuffer(GL_COLOR_ATTACHMENT0); FOpenGL::BlitFramebuffer( 0, 0, BackbufferSizeX, BackbufferSizeY, 0, BackbufferSizeY, BackbufferSizeX, 0, GL_COLOR_BUFFER_BIT, GL_NEAREST ); glEnable(GL_FRAMEBUFFER_SRGB); // Bind viewport FBO so driver knows we don't need backbuffer image anymore glBindFramebuffer(GL_FRAMEBUFFER, Context->ViewportFramebuffer); } } if (bPresent && Viewport.GetCustomPresent()) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FAndroidOpenGL_PlatformBlitToViewport_CustomPresent); int32 SyncInterval = FAndroidPlatformRHIFramePacer::GetLegacySyncInterval(); bPresent = Viewport.GetCustomPresent()->Present(RHICmdContext, SyncInterval); } if (bPresent) { AndroidEGL::GetInstance()->UpdateBuffersTransform(); FAndroidPlatformRHIFramePacer::SwapBuffers(bLockToVsync); } static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("a.UseFrameTimeStampsForPacing")); const bool bForceGPUFence = CVar ? CVar->GetInt() != 0 : false; return bPresent && ShouldUseGPUFencesToLimitLatency(); } void PlatformRenderingContextSetup(FPlatformOpenGLDevice* Device) { Device->SetCurrentRenderingContext(); } void PlatformFlushIfNeeded() { } void PlatformNULLContextSetup() { AndroidEGL::GetInstance()->ReleaseContextOwnership(); } bool PlatformOpenGLThreadHasRenderingContext() { return AndroidEGL::GetInstance()->ThreadHasRenderingContext(); } void PlatformRestoreDesktopDisplayMode() { } bool PlatformInitOpenGL() { check(!FAndroidMisc::ShouldUseVulkan()); { // determine ES version. PlatformInitOpenGL happens before ProcessExtensions and therefore FAndroidOpenGL::bES31Support. FString FullVersionString, VersionString, SubVersionString; FAndroidGPUInfo::Get().GLVersion.Split(TEXT("OpenGL ES "), nullptr, &FullVersionString); FullVersionString.Split(TEXT(" "), &FullVersionString, nullptr); FullVersionString.Split(TEXT("."), &VersionString, &SubVersionString); FAndroidOpenGL::GLMajorVerion = FCString::Atoi(*VersionString); FAndroidOpenGL::GLMinorVersion = FCString::Atoi(*SubVersionString); bool bES32Supported = FAndroidOpenGL::GLMajorVerion == 3 && FAndroidOpenGL::GLMinorVersion >= 2; static const auto CVarDisableES31 = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Android.DisableOpenGLES31Support")); bool bBuildForES31 = false; GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bBuildForES31"), bBuildForES31, GEngineIni); const bool bSupportsFloatingPointRTs = FAndroidMisc::SupportsFloatingPointRenderTargets(); if (bBuildForES31 && bES32Supported) { FOpenGLES::CurrentFeatureLevelSupport = FAndroidOpenGL::GLMinorVersion >= 2 ? FOpenGLES::EFeatureLevelSupport::ES32 : FOpenGLES::EFeatureLevelSupport::ES31; UE_LOG(LogRHI, Log, TEXT("App is packaged for OpenGL ES 3.1 and an ES %d.%d-capable device was detected."), FAndroidOpenGL::GLMajorVerion, FAndroidOpenGL::GLMinorVersion); } else { FString Message = TEXT(""); if (bES32Supported) { Message.Append(TEXT("This device does not support Vulkan but the app was not packaged with ES 3.1 support.")); if (FAndroidMisc::GetAndroidBuildVersion() < 26) { Message.Append(TEXT(" Updating to a newer Android version may resolve this issue.")); } FPlatformMisc::LowLevelOutputDebugString(*Message); FAndroidMisc::MessageBoxExt(EAppMsgType::Ok, *Message, TEXT("Unable to run on this device!")); } else { Message.Append(TEXT("This device only supports OpenGL ES 2/3/3.1 which is not supported, only supports ES 3.2+ ")); FPlatformMisc::LowLevelOutputDebugString(*Message); FAndroidMisc::MessageBoxExt(EAppMsgType::Ok, *Message, TEXT("Unable to run on this device!")); } } // Needs to initialize GPU vendor id before AndroidEGL::AcquireCurrentRenderingContext FString& VendorName = FAndroidGPUInfo::Get().VendorName; if (VendorName.Contains(TEXT("ImgTec")) || VendorName.Contains(TEXT("Imagination"))) { GRHIVendorId = 0x1010; } else if (VendorName.Contains(TEXT("ARM"))) { GRHIVendorId = 0x13B5; } else if (VendorName.Contains(TEXT("Qualcomm"))) { GRHIVendorId = 0x5143; } } return true; } // ============================================================= void FPlatformOpenGLDevice::LoadEXT() { eglGetSystemTimeNV_p = (PFNEGLGETSYSTEMTIMENVPROC)((void*)eglGetProcAddress("eglGetSystemTimeNV")); eglCreateSyncKHR_p = (PFNEGLCREATESYNCKHRPROC)((void*)eglGetProcAddress("eglCreateSyncKHR")); eglDestroySyncKHR_p = (PFNEGLDESTROYSYNCKHRPROC)((void*)eglGetProcAddress("eglDestroySyncKHR")); eglClientWaitSyncKHR_p = (PFNEGLCLIENTWAITSYNCKHRPROC)((void*)eglGetProcAddress("eglClientWaitSyncKHR")); eglGetSyncAttribKHR_p = (PFNEGLGETSYNCATTRIBKHRPROC)((void*)eglGetProcAddress("eglGetSyncAttribKHR")); eglPresentationTimeANDROID_p = (PFNeglPresentationTimeANDROID)((void*)eglGetProcAddress("eglPresentationTimeANDROID")); eglGetNextFrameIdANDROID_p = (PFNeglGetNextFrameIdANDROID)((void*)eglGetProcAddress("eglGetNextFrameIdANDROID")); eglGetCompositorTimingANDROID_p = (PFNeglGetCompositorTimingANDROID)((void*)eglGetProcAddress("eglGetCompositorTimingANDROID")); eglGetFrameTimestampsANDROID_p = (PFNeglGetFrameTimestampsANDROID)((void*)eglGetProcAddress("eglGetFrameTimestampsANDROID")); eglQueryTimestampSupportedANDROID_p = (PFNeglQueryTimestampSupportedANDROID)((void*)eglGetProcAddress("eglQueryTimestampSupportedANDROID")); eglGetCompositorTimingSupportedANDROID_p = (PFNeglQueryTimestampSupportedANDROID)((void*)eglGetProcAddress("eglGetCompositorTimingSupportedANDROID")); eglGetFrameTimestampsSupportedANDROID_p = (PFNeglQueryTimestampSupportedANDROID)((void*)eglGetProcAddress("eglGetFrameTimestampsSupportedANDROID")); eglGetNativeClientBufferANDROID_p = (PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC)((void*)eglGetProcAddress("eglGetNativeClientBufferANDROID")); eglCreateImageKHR_p = (PFNEGLCREATEIMAGEKHRPROC)((void*)eglGetProcAddress("eglCreateImageKHR")); eglDestroyImageKHR_p = (PFNEGLDESTROYIMAGEKHRPROC)((void*)eglGetProcAddress("eglDestroyImageKHR")); glEGLImageTargetTexture2DOES_p = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)((void*)eglGetProcAddress("glEGLImageTargetTexture2DOES")); const TCHAR* NotAvailable = TEXT("NOT Available"); const TCHAR* Present = TEXT("Present"); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglPresentationTimeANDROID"), eglPresentationTimeANDROID_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglGetNextFrameIdANDROID"), eglGetNextFrameIdANDROID_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglGetCompositorTimingANDROID"), eglGetCompositorTimingANDROID_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglGetFrameTimestampsANDROID"), eglGetFrameTimestampsANDROID_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglQueryTimestampSupportedANDROID"), eglQueryTimestampSupportedANDROID_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglGetCompositorTimingSupportedANDROID"), eglGetCompositorTimingSupportedANDROID_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglGetFrameTimestampsSupportedANDROID"), eglGetFrameTimestampsSupportedANDROID_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglGetNativeClientBufferANDROID"), eglGetNativeClientBufferANDROID_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglCreateImageKHR"), eglCreateImageKHR_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("eglCreateImageKHR"), eglCreateImageKHR_p ? Present : NotAvailable); UE_LOG(LogRHI, Log, TEXT("Extension %s %s"), TEXT("glEGLImageTargetTexture2DOES"), glEGLImageTargetTexture2DOES_p ? Present : NotAvailable); glDebugMessageControlKHR = (PFNGLDEBUGMESSAGECONTROLKHRPROC)((void*)eglGetProcAddress("glDebugMessageControlKHR")); // Some PowerVR drivers (Rogue Han and Intel-based devices) are crashing using glDebugMessageControlKHR (causes signal 11 crash) if (glDebugMessageControlKHR != NULL && FAndroidMisc::GetGPUFamily().Contains(TEXT("PowerVR"))) { glDebugMessageControlKHR = NULL; } glDebugMessageInsertKHR = (PFNGLDEBUGMESSAGEINSERTKHRPROC)((void*)eglGetProcAddress("glDebugMessageInsertKHR")); glDebugMessageCallbackKHR = (PFNGLDEBUGMESSAGECALLBACKKHRPROC)((void*)eglGetProcAddress("glDebugMessageCallbackKHR")); glDebugMessageLogKHR = (PFNGLGETDEBUGMESSAGELOGKHRPROC)((void*)eglGetProcAddress("glDebugMessageLogKHR")); glGetPointervKHR = (PFNGLGETPOINTERVKHRPROC)((void*)eglGetProcAddress("glGetPointervKHR")); glPushDebugGroupKHR = (PFNGLPUSHDEBUGGROUPKHRPROC)((void*)eglGetProcAddress("glPushDebugGroupKHR")); glPopDebugGroupKHR = (PFNGLPOPDEBUGGROUPKHRPROC)((void*)eglGetProcAddress("glPopDebugGroupKHR")); glObjectLabelKHR = (PFNGLOBJECTLABELKHRPROC)((void*)eglGetProcAddress("glObjectLabelKHR")); glGetObjectLabelKHR = (PFNGLGETOBJECTLABELKHRPROC)((void*)eglGetProcAddress("glGetObjectLabelKHR")); glObjectPtrLabelKHR = (PFNGLOBJECTPTRLABELKHRPROC)((void*)eglGetProcAddress("glObjectPtrLabelKHR")); glGetObjectPtrLabelKHR = (PFNGLGETOBJECTPTRLABELKHRPROC)((void*)eglGetProcAddress("glGetObjectPtrLabelKHR")); } FPlatformOpenGLContext* PlatformCreateOpenGLContext(FPlatformOpenGLDevice* Device, void* InWindowHandle) { AndroidEGL* AnEGL = AndroidEGL::GetInstance(); if (FAndroidMisc::UseNewWindowBehavior()) { check(InWindowHandle); FAndroidWindow* AndroidWindow = (FAndroidWindow *)InWindowHandle; FAndroidWindow::FNativeAccessor Accessor = AndroidWindow->GetANativeAccessor(false); AnEGL->InitRenderSurface(false, true, MoveTemp(Accessor)); } // else - with the original window method we block on startup until the window + device are initialized and context fully created. return AnEGL->GetRenderingContext(); } void PlatformDestroyOpenGLContext(FPlatformOpenGLDevice* Device, FPlatformOpenGLContext* Context) { } FOpenGLTexture* PlatformCreateBuiltinBackBuffer(FOpenGLDynamicRHI* OpenGLRHI, uint32 SizeX, uint32 SizeY) { check(IsInRenderingThread()); // Create the built-in back buffer if we disable backbuffer sampling. // Otherwise return null and we will create an off-screen surface afterward. if (!AndroidEGL::GetInstance()->IsOfflineSurfaceRequired()) { const FOpenGLTextureCreateDesc CreateDesc = FRHITextureCreateDesc::Create2D(TEXT("PlatformCreateBuiltinBackBuffer"), SizeX, SizeY, PF_B8G8R8A8) .SetFlags(ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::Presentable | ETextureCreateFlags::ResolveTargetable) .DetermineInititialState(); FOpenGLTexture* Texture = new FOpenGLTexture(CreateDesc); Texture->Initialize(FRHICommandListImmediate::Get()); return Texture; } else { return nullptr; } } void PlatformResizeGLContext( FPlatformOpenGLDevice* Device, FPlatformOpenGLContext* Context, uint32 SizeX, uint32 SizeY, bool bFullscreen, bool bWasFullscreen, GLenum BackBufferTarget, GLuint BackBufferResource) { check(Context); VERIFY_GL_SCOPE(); Context->BackBufferResource = BackBufferResource; Context->BackBufferTarget = BackBufferTarget; if (AndroidEGL::GetInstance()->IsOfflineSurfaceRequired()) { Device->TargetDirty = true; glBindFramebuffer(GL_FRAMEBUFFER, Context->ViewportFramebuffer); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, BackBufferTarget, BackBufferResource, 0); } glViewport(0, 0, SizeX, SizeY); VERIFY_GL(glViewport); } void PlatformGetSupportedResolution(uint32 &Width, uint32 &Height) { } bool PlatformGetAvailableResolutions(FScreenResolutionArray& Resolutions, bool bIgnoreRefreshRate) { return true; } int32 PlatformGlGetError() { return glGetError(); } // ============================================================= void PlatformDestroyOpenGLDevice(FPlatformOpenGLDevice* Device) { delete Device; } void FPlatformOpenGLDevice::SetCurrentRenderingContext() { AndroidEGL::GetInstance()->AcquireCurrentRenderingContext(); } //-------------------------------- bool FAndroidOpenGL::SupportsFramebufferSRGBEnable() { static auto* MobileUseHWsRGBEncodingCVAR = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.UseHWsRGBEncoding")); const bool bMobileUseHWsRGBEncoding = (MobileUseHWsRGBEncodingCVAR && MobileUseHWsRGBEncodingCVAR->GetValueOnAnyThread() == 1); return bMobileUseHWsRGBEncoding; } FAndroidOpenGL::EImageExternalType FAndroidOpenGL::ImageExternalType = FAndroidOpenGL::EImageExternalType::None; extern bool AndroidThunkCpp_GetMetaDataBoolean(const FString& Key); extern FString AndroidThunkCpp_GetMetaDataString(const FString& Key); void FAndroidOpenGL::SetupDefaultGLContextState(const FString& ExtensionsString) { // Enable QCOM non-coherent framebuffer fetch if supported if (CVarDisableFBFNonCoherent.GetValueOnAnyThread() == 0 && ExtensionsString.Contains(TEXT("GL_QCOM_shader_framebuffer_fetch_noncoherent")) && ExtensionsString.Contains(TEXT("GL_EXT_shader_framebuffer_fetch"))) { bDefaultStateNonCoherentFramebufferFetchEnabled = true; glEnable(GL_FRAMEBUFFER_FETCH_NONCOHERENT_QCOM); } } bool FAndroidOpenGL::RequiresAdrenoTilingModeHint() { return bRequiresAdrenoTilingHint; } void FAndroidOpenGL::EnableAdrenoTilingModeHint(bool bEnable) { if(bEnable && CVarEnableAdrenoTilingHint.GetValueOnAnyThread() != 0) { glEnable(GL_BINNING_CONTROL_HINT_QCOM); glHint(GL_BINNING_CONTROL_HINT_QCOM, GL_GPU_OPTIMIZED_QCOM); } else { glDisable(GL_BINNING_CONTROL_HINT_QCOM); } } bool FAndroidOpenGL::ResetNonCoherentFramebufferFetch() { if (bDefaultStateNonCoherentFramebufferFetchEnabled) { glEnable(GL_FRAMEBUFFER_FETCH_NONCOHERENT_QCOM); return true; } return false; } void FAndroidOpenGL::DisableNonCoherentFramebufferFetch() { if (bDefaultStateNonCoherentFramebufferFetchEnabled) { glDisable(GL_FRAMEBUFFER_FETCH_NONCOHERENT_QCOM); } } bool FAndroidOpenGL::bDefaultStateNonCoherentFramebufferFetchEnabled = false; void FAndroidOpenGL::ProcessExtensions(const FString& ExtensionsString) { FString VersionString = FString(ANSI_TO_TCHAR((const ANSICHAR*)glGetString(GL_VERSION))); FString SubVersionString; FOpenGLES::ProcessExtensions(ExtensionsString); FString RendererString = FString(ANSI_TO_TCHAR((const ANSICHAR*)glGetString(GL_RENDERER))); // Common GPU types const bool bIsNvidiaBased = RendererString.Contains(TEXT("NVIDIA")); const bool bIsPoverVRBased = RendererString.Contains(TEXT("PowerVR")); const bool bIsAdrenoBased = RendererString.Contains(TEXT("Adreno")); const bool bIsMaliBased = RendererString.Contains(TEXT("Mali")); const bool bIsSamsungBased = RendererString.Contains(TEXT("Xclipse")); if (bIsPoverVRBased) { bHasHardwareHiddenSurfaceRemoval = true; UE_LOG(LogRHI, Log, TEXT("Enabling support for Hidden Surface Removal on PowerVR")); } if (bIsAdrenoBased) { uint32 AdrenoDriverMajorVersion = 0; FStringView VersionStringView = MakeStringView(VersionString); FStringView AdrenoDriverVersionPrefix = TEXT("V@"); int32 VersionStart = UE::String::FindFirst(VersionStringView, AdrenoDriverVersionPrefix, ESearchCase::CaseSensitive); if (VersionStart != INDEX_NONE) { VersionStart += AdrenoDriverVersionPrefix.Len(); const int32 VersionCharCount = UE::String::FindFirst(VersionStringView.Mid(VersionStart), TEXT("."), ESearchCase::CaseSensitive); if (VersionCharCount != INDEX_NONE) { LexFromString(AdrenoDriverMajorVersion, VersionStringView.SubStr(VersionStart, VersionCharCount)); } } GRHIMaximumInFlightQueries = 510; // This is to avoid a bug in Adreno drivers that define GL_ARM_shader_framebuffer_fetch_depth_stencil even when device does not support this extension // OpenGL ES 3.1 V@127.0 (GIT@I1af360237c) bRequiresARMShaderFramebufferFetchDepthStencilUndef = !bSupportsShaderDepthStencilFetch; if (AdrenoDriverMajorVersion > 0 && AdrenoDriverMajorVersion < 331) { // Shader compiler causes a freeze on older drivers // version 331 is known to work, 313 known not to work bSupportsShaderFramebufferFetchProgrammableBlending = false; } // FORT-221329's broken adreno driver not common on Android 9 and above. TODO: check adreno driver version instead. bRequiresAdrenoTilingHint = FAndroidMisc::GetAndroidBuildVersion() < 28 || CVarEnableAdrenoTilingHint.GetValueOnAnyThread() == 2; UE_CLOG(bRequiresAdrenoTilingHint, LogRHI, Log, TEXT("Enabling Adreno tiling hint.")); } if (bIsMaliBased) { //TODO restrict this to problematic drivers only bRequiresReadOnlyBuffersWorkaround = true; } if (bIsSamsungBased) { FString AndroidVersion = FAndroidMisc::GetAndroidVersion(); if (AndroidVersion.Contains(TEXT("14"))) { bRequiresPreciseQualifierWorkaround = true; UE_LOG(LogRHI, Log, TEXT("Disable \'precise\' qualifier for [Android: %s, GPU: %s]"), *AndroidVersion, *RendererString); } } // Disable ASTC if requested by device profile static const auto CVarDisableASTC = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Android.DisableASTCSupport")); if (bSupportsASTC && CVarDisableASTC->GetValueOnAnyThread()) { bSupportsASTC = false; bSupportsASTCHDR = false; FAndroidGPUInfo::Get().RemoveTargetPlatform(TEXT("Android_ASTC")); UE_LOG(LogRHI, Log, TEXT("ASTC was disabled via r.OpenGL.DisableASTCSupport")); } // Check for external image support for different ES versions ImageExternalType = EImageExternalType::None; static const auto CVarOverrideExternalTextureSupport = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Android.OverrideExternalTextureSupport")); const int32 OverrideExternalTextureSupport = CVarOverrideExternalTextureSupport->GetValueOnAnyThread(); switch (OverrideExternalTextureSupport) { case 1: ImageExternalType = EImageExternalType::None; break; case 2: ImageExternalType = EImageExternalType::ImageExternal100; break; case 3: ImageExternalType = EImageExternalType::ImageExternal300; break; case 4: ImageExternalType = EImageExternalType::ImageExternalESSL300; break; case 0: default: // auto-detect by extensions (default) bool bHasImageExternal = ExtensionsString.Contains(TEXT("GL_OES_EGL_image_external ")) || ExtensionsString.EndsWith(TEXT("GL_OES_EGL_image_external")); bool bHasImageExternalESSL3 = ExtensionsString.Contains(TEXT("OES_EGL_image_external_essl3")); if (bHasImageExternal || bHasImageExternalESSL3) { ImageExternalType = EImageExternalType::ImageExternal100; if (bHasImageExternalESSL3) { ImageExternalType = EImageExternalType::ImageExternalESSL300; } else { // Adreno 5xx can do essl3 even without extension in list if (bIsAdrenoBased && RendererString.Contains(TEXT("(TM) 5"))) { ImageExternalType = EImageExternalType::ImageExternalESSL300; } } if (bIsNvidiaBased) { // Nvidia needs version 100 even though it supports ES3 ImageExternalType = EImageExternalType::ImageExternal100; } } break; } switch (ImageExternalType) { case EImageExternalType::None: UE_LOG(LogRHI, Log, TEXT("Image external disabled")); break; case EImageExternalType::ImageExternal100: UE_LOG(LogRHI, Log, TEXT("Image external enabled: ImageExternal100")); break; case EImageExternalType::ImageExternal300: UE_LOG(LogRHI, Log, TEXT("Image external enabled: ImageExternal300")); break; case EImageExternalType::ImageExternalESSL300: UE_LOG(LogRHI, Log, TEXT("Image external enabled: ImageExternalESSL300")); break; default: ImageExternalType = EImageExternalType::None; UE_LOG(LogRHI, Log, TEXT("Image external disabled; unknown type")); } bSupportsImageExternal = ImageExternalType != EImageExternalType::None; // check for supported texture formats if enabled bool bCookOnTheFly = false; #if !UE_BUILD_SHIPPING FString FileHostIP; bCookOnTheFly = FParse::Value(FCommandLine::Get(), TEXT("filehostip"), FileHostIP); #endif if (!bCookOnTheFly && AndroidThunkCpp_GetMetaDataBoolean(TEXT("com.epicgames.unreal.GameActivity.bValidateTextureFormats"))) { FString CookedFlavorsString = AndroidThunkCpp_GetMetaDataString(TEXT("com.epicgames.unreal.GameActivity.CookedFlavors")); if (!CookedFlavorsString.IsEmpty()) { TArray CookedFlavors; CookedFlavorsString.ParseIntoArray(CookedFlavors, TEXT(","), true); // check each cooked flavor for support (only need one to be supported) bool bFoundSupported = false; for (FString Flavor : CookedFlavors) { if (Flavor.Equals(TEXT("ETC2"))) { if (FOpenGL::SupportsETC2()) { bFoundSupported = true; break; } } if (Flavor.Equals(TEXT("DXT"))) { if (FOpenGL::SupportsDXT()) { bFoundSupported = true; break; } } if (Flavor.Equals(TEXT("ASTC"))) { if (FOpenGL::SupportsASTC()) { bFoundSupported = true; break; } } } if (!bFoundSupported) { FString Message = TEXT("Cooked Flavors: ") + CookedFlavorsString + TEXT("\n\nSupported: ETC2") + (FOpenGL::SupportsDXT() ? TEXT(",DXT") : TEXT("")) + (FOpenGL::SupportsASTC() ? TEXT(",ASTC") : TEXT("")); FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Error: Unsupported Texture Format\n%s"), *Message); FAndroidMisc::MessageBoxExt(EAppMsgType::Ok, *Message, TEXT("Unsupported Texture Format")); } } } // Qualcomm non-coherent framebuffer_fetch if (CVarDisableFBFNonCoherent.GetValueOnAnyThread() == 0 && ExtensionsString.Contains(TEXT("GL_QCOM_shader_framebuffer_fetch_noncoherent")) && ExtensionsString.Contains(TEXT("GL_EXT_shader_framebuffer_fetch"))) { glFramebufferFetchBarrierQCOM = (PFNGLFRAMEBUFFERFETCHBARRIERQCOMPROC)((void*)eglGetProcAddress("glFramebufferFetchBarrierQCOM")); if (glFramebufferFetchBarrierQCOM != nullptr) { UE_LOG(LogRHI, Log, TEXT("Using QCOM_shader_framebuffer_fetch_noncoherent")); } } if (CVarDisableEarlyFragmentTests.GetValueOnAnyThread() != 0) { bRequiresDisabledEarlyFragmentTests = true; UE_LOG(LogRHI, Log, TEXT("Disabling early_fragment_tests")); } } namespace AndroidOGLService { std::atomic GRemoteCompileServicesStarted = false; std::atomic GRemoteCompileServicesActive = false; std::atomic bOneTimeErrorEncountered = false; std::atomic TotalErrors = 0; } extern bool AreAndroidOpenGLRemoteCompileServicesAvailable(); bool FAndroidOpenGL::AreRemoteCompileServicesActive() { // The services could be stopped at any point elsewhere, the return value is not guaranteed to be correct. // it does not need to be exact as the PSO service will reject any new requests after service stop has been encountered. // any existing PSOservice jobs will complete as normal. if (AndroidOGLService::GRemoteCompileServicesStarted && AreAndroidOpenGLRemoteCompileServicesAvailable()) { if (!AndroidOGLService::GRemoteCompileServicesActive) { JNIEnv* Env = FAndroidApplication::GetJavaEnv(); AndroidOGLService::GRemoteCompileServicesActive = (bool)Env->CallStaticBooleanMethod(OpenGLRemoteGLProgramCompileJNI.OGLServiceAccessor, OpenGLRemoteGLProgramCompileJNI.AreProgramServicesReady); if (!AndroidOGLService::GRemoteCompileServicesActive) { if ((bool)Env->CallStaticBooleanMethod(OpenGLRemoteGLProgramCompileJNI.OGLServiceAccessor, OpenGLRemoteGLProgramCompileJNI.HaveServicesFailed)) { UE_LOG(LogRHI, Error, TEXT("Remote compile services failed to start.")); StopRemoteCompileServices(); } } else { UE_LOG(LogRHI, Log, TEXT("Remote compile services are active.")); } } return AndroidOGLService::GRemoteCompileServicesActive; } return false; } bool FAndroidOpenGL::StartRemoteCompileServices(int NumServices) { JNIEnv* Env = FAndroidApplication::GetJavaEnv(); if (Env && AreAndroidOpenGLRemoteCompileServicesAvailable() && !AndroidOGLService::GRemoteCompileServicesStarted) { bool bUseRobustContexts = AndroidEGL::GetInstance()->IsUsingRobustContext(); AndroidOGLService::GRemoteCompileServicesStarted = (bool)Env->CallStaticBooleanMethod(OpenGLRemoteGLProgramCompileJNI.OGLServiceAccessor, OpenGLRemoteGLProgramCompileJNI.StartRemoteProgramLink, (jint)NumServices, (jboolean)bUseRobustContexts, /*bUseVulkan*/(jboolean)false); } return AndroidOGLService::GRemoteCompileServicesStarted; } void FAndroidOpenGL::StopRemoteCompileServices() { bool bExpected = true; if (AndroidOGLService::GRemoteCompileServicesStarted.compare_exchange_strong(bExpected, false)) { UE_LOG(LogRHI, Log, TEXT("Stopping Remote Compile Services")); AndroidOGLService::GRemoteCompileServicesActive = false; JNIEnv* Env = FAndroidApplication::GetJavaEnv(); if (Env && ensure(AreAndroidOpenGLRemoteCompileServicesAvailable())) { Env->CallStaticVoidMethod(OpenGLRemoteGLProgramCompileJNI.OGLServiceAccessor, OpenGLRemoteGLProgramCompileJNI.StopRemoteProgramLink); } } } TArray FAndroidOpenGL::DispatchAndWaitForRemoteGLProgramCompile(FGraphicsPipelineStateInitializer::EPSOPrecacheCompileType PSOCompileType, const TArrayView ContextData, const TArray& VertexGlslCode, const TArray& PixelGlslCode, const TArray& ComputeGlslCode, FString& FailureMessageOUT) { bool bResult = false; JNIEnv* Env = FAndroidApplication::GetJavaEnv(); TArray CompiledProgramBinary; FString ErrorMessage; if (Env && ensure(AndroidOGLService::GRemoteCompileServicesActive) && ensure(AreAndroidOpenGLRemoteCompileServicesAvailable())) { // todo: double conversion :( auto jVS = NewScopedJavaObject(Env, Env->NewStringUTF(TCHAR_TO_UTF8(ANSI_TO_TCHAR(VertexGlslCode.IsEmpty() ? "" : VertexGlslCode.GetData())))); auto jPS = NewScopedJavaObject(Env, Env->NewStringUTF(TCHAR_TO_UTF8(ANSI_TO_TCHAR(PixelGlslCode.IsEmpty() ? "" : PixelGlslCode.GetData())))); auto jCS = NewScopedJavaObject(Env, Env->NewStringUTF(TCHAR_TO_UTF8(ANSI_TO_TCHAR(ComputeGlslCode.IsEmpty() ? "" : ComputeGlslCode.GetData())))); auto ProgramKeyBuffer = NewScopedJavaObject(Env, Env->NewByteArray(ContextData.Num())); Env->SetByteArrayRegion(*ProgramKeyBuffer, 0, ContextData.Num(), reinterpret_cast(ContextData.GetData())); // dont time out if the debugger is attached. bool bEnableTimeOuts = !FPlatformMisc::IsDebuggerPresent(); FPlatformDynamicRHI::FPSOServicePriInfo PriorityInfo(PSOCompileType); auto ProgramResponseObj = NewScopedJavaObject(Env, Env->CallStaticObjectMethod(OpenGLRemoteGLProgramCompileJNI.OGLServiceAccessor, OpenGLRemoteGLProgramCompileJNI.DispatchProgramLink, *ProgramKeyBuffer, PriorityInfo.GetPriorityInfo(), *jVS, *jPS, *jCS, bEnableTimeOuts)); CHECK_JNI_EXCEPTIONS(Env); if(*ProgramResponseObj) { bool bSucceeded = (bool)Env->GetBooleanField(*ProgramResponseObj, OpenGLRemoteGLProgramCompileJNI.ProgramResponse_SuccessField); if (bSucceeded) { auto ProgramResult = NewScopedJavaObject(Env, (jbyteArray)Env->GetObjectField(*ProgramResponseObj, OpenGLRemoteGLProgramCompileJNI.ProgramResponse_CompiledBinaryField)); int len = Env->GetArrayLength(*ProgramResult); CompiledProgramBinary.SetNumUninitialized(len); Env->GetByteArrayRegion(*ProgramResult, 0, len, reinterpret_cast(CompiledProgramBinary.GetData())); float CompilationDuration = (float)Env->GetFloatField(*ProgramResponseObj, OpenGLRemoteGLProgramCompileJNI.ProgramResponse_CompilationDurationField); AccumulatePSOMetrics(CompilationDuration); } else { if (AndroidOGLService::bOneTimeErrorEncountered.exchange(true) == false) { FGenericCrashContext::SetEngineData(TEXT("Android.PSOService"), TEXT("ec")); } FailureMessageOUT = FJavaHelper::FStringFromLocalRef(Env, (jstring)Env->GetObjectField(*ProgramResponseObj, OpenGLRemoteGLProgramCompileJNI.ProgramResponse_ErrorField)); check(!FailureMessageOUT.IsEmpty()); } } else { if (AndroidOGLService::bOneTimeErrorEncountered.exchange(true) == false) { FGenericCrashContext::SetEngineData(TEXT("Android.PSOService"), TEXT("es")); } FailureMessageOUT = TEXT("Remote compiler failed."); } if(CompiledProgramBinary.IsEmpty()) { check(!FailureMessageOUT.IsEmpty()); if ((AndroidOGLService::TotalErrors++) == FPlatformDynamicRHI::GetPSOServiceFailureThreshold()) { StopRemoteCompileServices(); FailureMessageOUT = TEXT("Remote compiler passed failure threshold, disabling further remote compiles."); } } } return CompiledProgramBinary; } #endif