// Copyright Epic Games, Inc. All Rights Reserved. #include "AndroidEGL.h" #if USE_ANDROID_OPENGL #include "OpenGLDrvPrivate.h" #include "AndroidEGL.h" #include "Android/AndroidApplication.h" #include "Android/AndroidWindow.h" #include #if USE_ANDROID_JNI #include #endif #include "Misc/ScopeLock.h" #include "UObject/GarbageCollection.h" #include "Android/AndroidPlatformFramePacer.h" #include #include "UnrealEngine.h" AndroidEGL* AndroidEGL::Singleton = NULL; DEFINE_LOG_CATEGORY(LogEGL); #if USE_ANDROID_EGL_NO_ERROR_CONTEXT #ifndef EGL_KHR_create_context_no_error #define EGL_KHR_create_context_no_error 1 #define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31B3 #endif // EGL_KHR_create_context_no_error #endif // USE_ANDROID_EGL_NO_ERROR_CONTEXT typedef int32(*PFN_ANativeWindow_setBuffersTransform)(struct ANativeWindow* window, int32 transform); static PFN_ANativeWindow_setBuffersTransform ANativeWindow_setBuffersTransform_API = nullptr; // Use blit by default as setBuffersTransform is broken on random devices static TAutoConsoleVariable CVarAndroidGLESFlipYMethod( TEXT("r.Android.GLESFlipYMethod"), 2, TEXT(" 0: Flip Y method detected automatically by GPU vendor.\n" " 1: Force flip Y by native window setBuffersTransform.\n" " 2: Force flip Y by BlitFrameBuffer."), ECVF_RenderThreadSafe); const int EGLMinRedBits = 5; const int EGLMinGreenBits = 6; const int EGLMinBlueBits = 5; const int EGLMinAlphaBits = 0; const int EGLMinDepthBits = 16; const int EGLMinStencilBits = 8; // This is required for UMG clipping const int EGLMinSampleBuffers = 0; const int EGLMinSampleSamples = 0; struct EGLConfigParms { /** Whether this is a valid configuration or not */ int validConfig = 0; /** The number of bits requested for the red component */ int redSize = 8; /** The number of bits requested for the green component */ int greenSize = 8; /** The number of bits requested for the blue component */ int blueSize = 8; /** The number of bits requested for the alpha component */ int alphaSize = 0; /** The number of bits requested for the depth component */ int depthSize = 24; /** The number of bits requested for the stencil component */ int stencilSize = 0; /** The number of multisample buffers requested */ int sampleBuffers = 0; /** The number of samples requested */ int sampleSamples = 0; EGLConfigParms() { // If not default, set the preference int DepthBufferPreference = (int)FAndroidWindow::GetDepthBufferPreference(); if (DepthBufferPreference > 0) { depthSize = DepthBufferPreference; } if (FAndroidMisc::GetMobilePropagateAlphaSetting() > 0) { alphaSize = 8; } } }; struct AndroidESPImpl { FPlatformOpenGLContext RenderingContext; EGLDisplay eglDisplay = EGL_NO_DISPLAY; EGLint eglNumConfigs = 0; EGLint eglFormat = -1; EGLConfig eglConfigParam = nullptr; EGLSurface eglSurface = EGL_NO_SURFACE; EGLSurface auxSurface = EGL_NO_SURFACE; EGLint eglWidth = 8; // required for Gear VR apps with internal win surf mgmt EGLint eglHeight = 8; // required for Gear VR apps with internal win surf mgmt EGLint NativeVisualID = 0; EGLConfigParms Parms; float eglRatio = 0; int DepthSize = 0; ANativeWindow* Window = nullptr; GLuint ResolveFrameBuffer = 0; GLuint DummyFrameBuffer = 0; FPlatformRect CachedWindowRect {}; bool Initalized = false; bool bIsDebug = false; bool bIsWndSurface = false; // True when the surface is attached to a HW window. AndroidESPImpl() = default; }; const EGLint Attributes[] = { EGL_RED_SIZE, EGLMinRedBits, EGL_GREEN_SIZE, EGLMinGreenBits, EGL_BLUE_SIZE, EGLMinBlueBits, EGL_ALPHA_SIZE, EGLMinAlphaBits, EGL_DEPTH_SIZE, EGLMinDepthBits, EGL_STENCIL_SIZE, EGLMinStencilBits, EGL_SAMPLE_BUFFERS, EGLMinSampleBuffers, EGL_SAMPLES, EGLMinSampleSamples, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, EGL_CONFIG_CAVEAT, EGL_NONE, EGL_NONE }; AndroidEGL::AndroidEGL() { PImplData = new AndroidESPImpl(); void* const LibNativeWindow = dlopen("libnativewindow.so", RTLD_NOW | RTLD_LOCAL); if (LibNativeWindow != nullptr) { ANativeWindow_setBuffersTransform_API = reinterpret_cast(dlsym(LibNativeWindow, "ANativeWindow_setBuffersTransform")); } FPlatformMisc::LowLevelOutputDebugStringf(TEXT("ANativeWindow_setBuffersTransform is %s on this device"), ANativeWindow_setBuffersTransform_API == nullptr ? TEXT("not supported") : TEXT("supported")); } void AndroidEGL::ResetDisplay() { VERIFY_EGL_SCOPE(); if(PImplData->eglDisplay != EGL_NO_DISPLAY) { FPlatformMisc::LowLevelOutputDebugStringf( TEXT("AndroidEGL::ResetDisplay()" )); eglMakeCurrent(PImplData->eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } } void AndroidEGL::DestroyRenderSurface() { VERIFY_EGL_SCOPE(); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("AndroidEGL::DestroyRenderSurface()" )); if( PImplData->eglSurface != EGL_NO_SURFACE ) { eglDestroySurface(PImplData->eglDisplay, PImplData->eglSurface); PImplData->eglSurface = EGL_NO_SURFACE; } PImplData->RenderingContext.eglSurface = EGL_NO_SURFACE; } void AndroidEGL::TerminateEGL() { VERIFY_EGL_SCOPE(); eglTerminate(PImplData->eglDisplay); PImplData->eglDisplay = EGL_NO_DISPLAY; PImplData->Initalized = false; } /* Can be called from any thread */ EGLBoolean AndroidEGL::SetCurrentContext(EGLContext InContext, EGLSurface InSurface) { VERIFY_EGL_SCOPE(); //context can be null.so can surface from PlatformNULLContextSetup EGLBoolean Result = EGL_FALSE; EGLContext CurrentContext = GetCurrentContext(); // activate the context if (CurrentContext != InContext) { if (CurrentContext !=EGL_NO_CONTEXT) { glFlush(); } if (InContext == EGL_NO_CONTEXT && InSurface == EGL_NO_SURFACE) { ResetDisplay(); } else { //if we have a valid context, and no surface then create a tiny pbuffer and use that temporarily EGLSurface Surface = InSurface; if (!bSupportsKHRSurfacelessContext && InContext != EGL_NO_CONTEXT && InSurface == EGL_NO_SURFACE) { checkf(PImplData->auxSurface == EGL_NO_SURFACE, TEXT("ERROR: PImplData->auxSurface already in use. PBuffer surface leak!")); EGLint PBufferAttribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_TEXTURE_TARGET, EGL_NO_TEXTURE, EGL_TEXTURE_FORMAT, EGL_NO_TEXTURE, EGL_NONE }; PImplData->auxSurface = eglCreatePbufferSurface(PImplData->eglDisplay, PImplData->eglConfigParam, PBufferAttribs); if (PImplData->auxSurface == EGL_NO_SURFACE) { checkf(PImplData->auxSurface != EGL_NO_SURFACE, TEXT("eglCreatePbufferSurface error : 0x%x"), eglGetError()); } Surface = PImplData->auxSurface; } Result = eglMakeCurrent(PImplData->eglDisplay, Surface, Surface, InContext); checkf(Result == EGL_TRUE, TEXT("ERROR: SetCurrentContext eglMakeCurrent failed : 0x%x"), eglGetError()); } } return Result; } void AndroidEGL::ResetInternal() { Terminate(); } void AndroidEGL::CreateEGLRenderSurface(ANativeWindow* InWindow, bool bCreateWndSurface) { VERIFY_EGL_SCOPE(); // due to possible early initialization, don't redo this if (PImplData->eglSurface != EGL_NO_SURFACE) { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::CreateEGLRenderSurface() Already initialized: %p"), PImplData->eglSurface); return; } PImplData->bIsWndSurface = bCreateWndSurface; if (bCreateWndSurface) { check(InWindow); //need ANativeWindow PImplData->eglSurface = eglCreateWindowSurface(PImplData->eglDisplay, PImplData->eglConfigParam,InWindow, NULL); if (FAndroidPlatformRHIFramePacer::CVarAllowFrameTimestamps.GetValueOnAnyThread()) { STANDALONE_DEBUG_LOGf(LogAndroid, TEXT("AndroidEGL::CreateEGLRenderSurface(InWindow = %p) using a.allowFrameTimestamps enable EGL_TIMESTAMPS_ANDROID on %p"), InWindow, PImplData->eglSurface); eglSurfaceAttrib(PImplData->eglDisplay, PImplData->eglSurface, EGL_TIMESTAMPS_ANDROID, EGL_TRUE); } else { // HAD to add the false condition so that android attributes reflect current state of CVar. STANDALONE_DEBUG_LOGf(LogAndroid, TEXT("AndroidEGL::CreateEGLRenderSurface(InWindow = %p) using a.allowFrameTimestamps disable EGL_TIMESTAMPS_ANDROID on %p"), InWindow, PImplData->eglSurface); eglSurfaceAttrib(PImplData->eglDisplay, PImplData->eglSurface, EGL_TIMESTAMPS_ANDROID, EGL_FALSE); } STANDALONE_DEBUG_LOGf(LogAndroid, TEXT("AndroidEGL::CreateEGLRenderSurface() %p" ), PImplData->eglSurface); if (PImplData->eglSurface == EGL_NO_SURFACE) { checkf(PImplData->eglSurface != EGL_NO_SURFACE, TEXT("eglCreateWindowSurface error : 0x%x"), eglGetError()); ResetInternal(); } // On some Android devices, eglChooseConfigs will lie about valid configurations (specifically 32-bit color) /* if (eglGetError() == EGL10.EGL_BAD_MATCH) { Logger.LogOut("eglCreateWindowSurface FAILED, retrying with more restricted context"); // Dump what's already been initialized cleanupEGL(); // Reduce target color down to 565 eglAttemptedParams.redSize = 5; eglAttemptedParams.greenSize = 6; eglAttemptedParams.blueSize = 5; eglAttemptedParams.alphaSize = 0; initEGL(eglAttemptedParams); // try again eglSurface = eglCreateWindowSurface(PImplData->eglDisplay, eglConfig, surface, null); } */ EGLBoolean result = EGL_FALSE; if (!( result = ( eglQuerySurface(PImplData->eglDisplay, PImplData->eglSurface, EGL_WIDTH, &PImplData->eglWidth) && eglQuerySurface(PImplData->eglDisplay, PImplData->eglSurface, EGL_HEIGHT, &PImplData->eglHeight) ) ) ) { ResetInternal(); } checkf(result == EGL_TRUE, TEXT("eglQuerySurface error : 0x%x"), eglGetError()); } else { // create a fake surface instead EGLint pbufferAttribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_TEXTURE_TARGET, EGL_NO_TEXTURE, EGL_TEXTURE_FORMAT, EGL_NO_TEXTURE, EGL_NONE }; checkf(PImplData->eglWidth != 0, TEXT("eglWidth is ZERO; could be a problem!")); checkf(PImplData->eglHeight != 0, TEXT("eglHeight is ZERO; could be a problem!")); pbufferAttribs[1] = PImplData->eglWidth; pbufferAttribs[3] = PImplData->eglHeight; FPlatformMisc::LowLevelOutputDebugStringf( TEXT("AndroidEGL::CreateEGLRenderSurface(%d), eglSurface = eglCreatePbufferSurface(), %dx%d" ), int(bCreateWndSurface), pbufferAttribs[1], pbufferAttribs[3]); PImplData->eglSurface = eglCreatePbufferSurface(PImplData->eglDisplay, PImplData->eglConfigParam, pbufferAttribs); if(PImplData->eglSurface== EGL_NO_SURFACE ) { checkf(PImplData->eglSurface != EGL_NO_SURFACE, TEXT("eglCreatePbufferSurface error : 0x%x"), eglGetError()); ResetInternal(); } } } void AndroidEGL::InitEGL(APIVariant API) { VERIFY_EGL_SCOPE(); // make sure we only do this once (it's optionally done early for cooker communication) // static bool bAlreadyInitialized = false; if (PImplData->Initalized) { return; } // bAlreadyInitialized = true; check(PImplData->eglDisplay == EGL_NO_DISPLAY) PImplData->eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); checkf(PImplData->eglDisplay, TEXT(" eglGetDisplay error : 0x%x "), eglGetError()); EGLBoolean result = eglInitialize(PImplData->eglDisplay, 0 , 0); checkf( result == EGL_TRUE, TEXT("elgInitialize error: 0x%x "), eglGetError()); // Get the EGL Extension list to determine what is supported FString Extensions = ANSI_TO_TCHAR( eglQueryString( PImplData->eglDisplay, EGL_EXTENSIONS)); UE_LOG(LogAndroid, Log, TEXT("EGL Extensions: \n%s"), *Extensions); bSupportsKHRCreateContext = Extensions.Contains(TEXT("EGL_KHR_create_context")); bSupportsKHRSurfacelessContext = Extensions.Contains(TEXT("EGL_KHR_surfaceless_context")); bSupportsKHRNoErrorContext = Extensions.Contains(TEXT("EGL_KHR_create_context_no_error")); bSupportsEXTRobustContext = Extensions.Contains(TEXT("EGL_EXT_create_context_robustness")); if (API == AV_OpenGLES) { result = eglBindAPI(EGL_OPENGL_ES_API); } else if (API == AV_OpenGLCore) { result = eglBindAPI(EGL_OPENGL_API); } else { checkf( 0, TEXT("Attempt to initialize EGL with unexpected API type")); } checkf( result == EGL_TRUE, TEXT("eglBindAPI error: 0x%x "), eglGetError()); #if ENABLE_CONFIG_FILTER EGLConfig* EGLConfigList = NULL; result = eglChooseConfig(PImplData->eglDisplay, Attributes, NULL, 0, &PImplData->eglNumConfigs); if (result) { int NumConfigs = PImplData->eglNumConfigs; EGLConfigList = new EGLConfig[NumConfigs]; result = eglChooseConfig(PImplData->eglDisplay, Attributes, EGLConfigList, NumConfigs, &PImplData->eglNumConfigs); } if (!result) { ResetInternal(); } checkf(result == EGL_TRUE , TEXT(" eglChooseConfig error: 0x%x"), eglGetError()); checkf(PImplData->eglNumConfigs != 0 ,TEXT(" eglChooseConfig num EGLConfigLists is 0 . error: 0x%x"), eglGetError()); int ResultValue = 0 ; bool haveConfig = false; int64 score = LONG_MAX; for (uint32_t i = 0; i < PImplData->eglNumConfigs; i++) { int64 currScore = 0; int r, g, b, a, d, s, sb, sc, nvi; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_RED_SIZE, &ResultValue); r = ResultValue; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_GREEN_SIZE, &ResultValue); g = ResultValue; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_BLUE_SIZE, &ResultValue); b = ResultValue; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_ALPHA_SIZE, &ResultValue); a = ResultValue; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_DEPTH_SIZE, &ResultValue); d = ResultValue; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_STENCIL_SIZE, &ResultValue); s = ResultValue; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_SAMPLE_BUFFERS, &ResultValue); sb = ResultValue; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_SAMPLES, &ResultValue); sc = ResultValue; // Optional, Tegra-specific non-linear depth buffer, which allows for much better // effective depth range in relatively limited bit-depths (e.g. 16-bit) int bNonLinearDepth = 0; if (eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_DEPTH_ENCODING_NV, &ResultValue)) { bNonLinearDepth = (ResultValue == EGL_DEPTH_ENCODING_NONLINEAR_NV) ? 1 : 0; } else { // explicitly consume the egl error if EGL_DEPTH_ENCODING_NV does not exist. GetError(); } // Favor EGLConfigLists by RGB, then Depth, then Non-linear Depth, then Stencil, then Alpha currScore = 0; currScore |= ((int64)FPlatformMath::Min(FPlatformMath::Abs(sb - PImplData->Parms.sampleBuffers), 15)) << 29; currScore |= ((int64)FPlatformMath::Min(FPlatformMath::Abs(sc - PImplData->Parms.sampleSamples), 31)) << 24; currScore |= FPlatformMath::Min( FPlatformMath::Abs(r - PImplData->Parms.redSize) + FPlatformMath::Abs(g - PImplData->Parms.greenSize) + FPlatformMath::Abs(b - PImplData->Parms.blueSize), 127) << 17; currScore |= FPlatformMath::Min(FPlatformMath::Abs(d - PImplData->Parms.depthSize), 63) << 11; currScore |= FPlatformMath::Min(FPlatformMath::Abs(1 - bNonLinearDepth), 1) << 10; currScore |= FPlatformMath::Min(FPlatformMath::Abs(s - PImplData->Parms.stencilSize), 31) << 6; currScore |= FPlatformMath::Min(FPlatformMath::Abs(a - PImplData->Parms.alphaSize), 31) << 0; #if ENABLE_EGL_DEBUG LogConfigInfo(EGLConfigList[i]); #endif if (currScore < score || !haveConfig) { PImplData->eglConfigParam = EGLConfigList[i]; PImplData->DepthSize = d; // store depth/stencil sizes haveConfig = true; score = currScore; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[i], EGL_NATIVE_VISUAL_ID, &ResultValue);PImplData->NativeVisualID = ResultValue; } } check(haveConfig); delete[] EGLConfigList; #else EGLConfig EGLConfigList[1]; if(!( result = eglChooseConfig(PImplData->eglDisplay, Attributes, EGLConfigList, 1, &PImplData->eglNumConfigs))) { ResetInternal(); } checkf(result == EGL_TRUE , TEXT(" eglChooseConfig error: 0x%x"), eglGetError()); checkf(PImplData->eglNumConfigs != 0 ,TEXT(" eglChooseConfig num EGLConfigLists is 0 . error: 0x%x"), eglGetError()); PImplData->eglConfigParam = EGLConfigList[0]; int ResultValue = 0 ; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[0], EGL_DEPTH_SIZE, &ResultValue); PImplData->DepthSize = ResultValue; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigList[0], EGL_NATIVE_VISUAL_ID, &ResultValue);PImplData->NativeVisualID = ResultValue; #endif } // call out to JNI to see if the application was packaged for Oculus Mobile extern bool AndroidThunkCpp_IsOculusMobileApplication(); void AndroidEGL::SetRenderContextWindowSurface(const TOptional& WindowContainer) { VERIFY_GL_SCOPE(); FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::SetRenderContextWindowSurface recreating context! tid: %d"), FPlatformTLS::GetCurrentThreadId()); UnBindRender(); SetCurrentContext(EGL_NO_CONTEXT, EGL_NO_SURFACE); bool bCreateSurface = !AndroidThunkCpp_IsOculusMobileApplication(); if (!FAndroidMisc::UseNewWindowBehavior()) { // SetRenderContextWindowSurface is called only when the window lock is successful. PImplData->Window = (ANativeWindow*)FAndroidWindow::GetHardwareWindow_EventThread(); check(PImplData->Window); } InitRenderSurface(false, bCreateSurface, WindowContainer); SetCurrentRenderingContext(); FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::SetRenderContextWindowSurface DONE! tid: %d"), FPlatformTLS::GetCurrentThreadId()); } void AndroidEGL::ResizeRenderContextSurface(const TOptional& WindowContainer) { VERIFY_GL_SCOPE(); check(!FAndroidMisc::UseNewWindowBehavior() || !WindowContainer.IsSet() || WindowContainer.GetValue().GetANativeWindow() == PImplData->Window); // Resize render originates from the gamethread, we cant use Window_Event here. if (PImplData->Window) { if (PImplData->eglWidth != (PImplData->CachedWindowRect.Right - PImplData->CachedWindowRect.Left) || PImplData->eglHeight != (PImplData->CachedWindowRect.Bottom - PImplData->CachedWindowRect.Top)) { UE_LOG(LogAndroid, Log, TEXT("AndroidEGL::ResizeRenderContextSurface, PImplData->Window=%p, PImplData->eglWidth=%d, PImplData->eglHeight=%d!, CachedWidth=%d, CachedHeight=%d, tid: %d"), PImplData->Window, PImplData->eglWidth, PImplData->eglHeight, (PImplData->CachedWindowRect.Right - PImplData->CachedWindowRect.Left), (PImplData->CachedWindowRect.Bottom - PImplData->CachedWindowRect.Top), FPlatformTLS::GetCurrentThreadId() ); UnBindRender(); SetCurrentContext(EGL_NO_CONTEXT, EGL_NO_SURFACE); { bool bCreateSurface = !AndroidThunkCpp_IsOculusMobileApplication(); InitRenderSurface(false, bCreateSurface, WindowContainer); } SetCurrentRenderingContext(); } } } AndroidEGL* AndroidEGL::GetInstance() { if(!Singleton) { Singleton = new AndroidEGL(); } return Singleton; } void AndroidEGL::DestroyBackBuffer() { if (PImplData->ResolveFrameBuffer) { VERIFY_GL_SCOPE(); glDeleteFramebuffers(1, &PImplData->ResolveFrameBuffer); PImplData->ResolveFrameBuffer = 0 ; } if (PImplData->DummyFrameBuffer) { VERIFY_GL_SCOPE(); glDeleteFramebuffers(1, &PImplData->DummyFrameBuffer); PImplData->DummyFrameBuffer = 0; } } void AndroidEGL::InitBackBuffer() { PImplData->RenderingContext.ViewportFramebuffer = GetResolveFrameBuffer(); } extern void AndroidThunkCpp_SetDesiredViewSize(int32 Width, int32 Height); void AndroidEGL::InitRenderSurface(bool bUseSmallSurface, bool bCreateWndSurface, const TOptional& WindowContainer) { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::InitRenderSurface %d, %d"), int(bUseSmallSurface), int(bCreateWndSurface)); if (FAndroidMisc::UseNewWindowBehavior()) { bUseSmallSurface = true; bCreateWndSurface = false; PImplData->Window = nullptr; if (WindowContainer.IsSet()) { const FAndroidWindow::FNativeAccessor& Accessor = WindowContainer.GetValue(); if(Accessor.GetANativeWindow()) { bUseSmallSurface = false; bCreateWndSurface = true; PImplData->Window = (ANativeWindow*) Accessor.GetANativeWindow(); FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::InitRenderSurface window %p was supplied %d, %d"), PImplData->Window, int(bUseSmallSurface), int(bCreateWndSurface)); } else { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::InitRenderSurface null native window was supplied %d, %d"), int(bUseSmallSurface), int(bCreateWndSurface)); } } else { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::InitRenderSurface No window was supplied %d, %d"), int(bUseSmallSurface), int(bCreateWndSurface)); } } else { check(PImplData->Window); } int32 Width = 8, Height = 8; if (!bUseSmallSurface) { FPlatformRect WindowSize = FAndroidWindow::GetScreenRect(); if (PImplData->CachedWindowRect.Right > 0 && PImplData->CachedWindowRect.Bottom > 0) { // If we resumed from a lost window reuse the window size, the game thread will update the window dimensions. FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::InitRenderSurface, Using CachedWindowRect, left: %d, top: %d, right: %d, bottom: %d "), PImplData->CachedWindowRect.Left, PImplData->CachedWindowRect.Top, PImplData->CachedWindowRect.Right, PImplData->CachedWindowRect.Bottom); WindowSize = PImplData->CachedWindowRect; } #if USE_ANDROID_STANDALONE if (WindowSize.Left != 0 || WindowSize.Top != 0) { STANDALONE_DEBUG_LOGf(LogAndroid, TEXT("AndroidEGL::InitRenderSurface, WARNING!!! WindowSize is offset, left: %d, top: %d, right: %d, bottom: %d "), WindowSize.Left, WindowSize.Top, WindowSize.Right, WindowSize.Bottom); } Width = WindowSize.Right - WindowSize.Left; Height = WindowSize.Bottom - WindowSize.Top; #else Width = WindowSize.Right; Height = WindowSize.Bottom; #endif FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::InitRenderSurface, Using width: %d, height %d "), Width, Height); AndroidThunkCpp_SetDesiredViewSize(Width, Height); } if(PImplData->Window) { FIntVector2 OriginalWindowSize(ANativeWindow_getWidth(PImplData->Window), ANativeWindow_getHeight(PImplData->Window)); FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL::InitRenderSurface, setting wnd: %p, width: %d->%d, height %d->%d "), PImplData->Window, OriginalWindowSize.X, Width, OriginalWindowSize.Y, Height); ANativeWindow_setBuffersGeometry(PImplData->Window, Width, Height, PImplData->NativeVisualID); } CreateEGLRenderSurface(PImplData->Window, bCreateWndSurface); PImplData->RenderingContext.eglSurface = PImplData->eglSurface; } // EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT is enabled if configrules asks for it or the command line specifies it. // If -OpenGLRobustContext=[0/1] is specified on the command line it takes precedence. void AndroidEGL::Init(APIVariant API, uint32 MajorVersion, uint32 MinorVersion) { check(IsInGameThread()); const bool bDebug = IsOGLDebugOutputEnabled(); const FString* ConfigRulesForceRobustGLContext = FAndroidMisc::GetConfigRulesVariable(TEXT("ForceRobustGLContext")); bool bWantsRobustGLContext = ConfigRulesForceRobustGLContext && ConfigRulesForceRobustGLContext->Equals("true", ESearchCase::IgnoreCase); FString RobustArg; if (FParse::Value(FCommandLine::Get(), TEXT("-OpenGLRobustContext="), RobustArg)) { bWantsRobustGLContext = RobustArg.Contains(TEXT("1")); } if (PImplData->Initalized) { ensure(bDebug == PImplData->bIsDebug); // if this fires you would need to tear down the previous context and recreate to honour the debug change. return; } InitEGL(API); PImplData->bIsDebug = bDebug; if (bSupportsKHRCreateContext) { const uint32 MaxElements = 16; uint32 Flags = 0; Flags |= PImplData->bIsDebug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0; ContextAttributes = new int[MaxElements]; uint32 Element = 0; ContextAttributes[Element++] = EGL_CONTEXT_MAJOR_VERSION_KHR; ContextAttributes[Element++] = MajorVersion; ContextAttributes[Element++] = EGL_CONTEXT_MINOR_VERSION_KHR; ContextAttributes[Element++] = MinorVersion; #if USE_ANDROID_EGL_NO_ERROR_CONTEXT if (bSupportsKHRNoErrorContext && AndroidThunkCpp_IsOculusMobileApplication()) { ContextAttributes[Element++] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR; ContextAttributes[Element++] = EGL_TRUE; } #endif // USE_ANDROID_EGL_NO_ERROR_CONTEXT bIsEXTRobustContextActive = bSupportsEXTRobustContext && bWantsRobustGLContext; if (bIsEXTRobustContextActive) { UE_LOG(LogAndroid, Log, TEXT("Enabling: EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT")); ContextAttributes[Element++] = EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT; ContextAttributes[Element++] = EGL_TRUE; } if (API == AV_OpenGLCore) { ContextAttributes[Element++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; ContextAttributes[Element++] = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR; } ContextAttributes[Element++] = EGL_CONTEXT_FLAGS_KHR; ContextAttributes[Element++] = Flags; ContextAttributes[Element++] = EGL_NONE; checkf( Element <= MaxElements, TEXT("Too many elements in config list")); } else { // Fall back to the least common denominator ContextAttributes = new int[3]; ContextAttributes[0] = EGL_CONTEXT_CLIENT_VERSION; ContextAttributes[1] = 2; ContextAttributes[2] = EGL_NONE; } bool bSuccess = InitContexts(); // Try to create the context again for ES3.1 if it is failed to create for ES3.2 if (!bSuccess && ContextAttributes[3] > 1) { ContextAttributes[3] -= 1; bSuccess = InitContexts(); if (!bSuccess) { // Try to create an ES2 context if ES3.1 also failed, which can happen in the Android emulator. // This is enough for FAndroidGPUInfo detection to enable Vulkan. ContextAttributes[0] = EGL_CONTEXT_CLIENT_VERSION; ContextAttributes[1] = 2; ContextAttributes[2] = EGL_NONE; bSuccess = InitContexts(); } } if (!FAndroidMisc::UseNewWindowBehavior()) { // Getting the hardware window is valid during preinit as we have GAndroidWindowLock held. PImplData->Window = (ANativeWindow*)FAndroidWindow::GetHardwareWindow_EventThread(); } PImplData->Initalized = true; } AndroidEGL::~AndroidEGL() { delete PImplData; delete []ContextAttributes; } void AndroidEGL::GetDimensions(uint32& OutWidth, uint32& OutHeight) { OutWidth = PImplData->eglWidth; OutHeight = PImplData->eglHeight; } void AndroidEGL::DestroyContext(EGLContext InContext) { VERIFY_EGL_SCOPE(); if(InContext != EGL_NO_CONTEXT) //soft fail { eglDestroyContext(PImplData->eglDisplay, InContext); } } EGLContext AndroidEGL::CreateContext(EGLContext InParentContext) { VERIFY_EGL_SCOPE(); return eglCreateContext(PImplData->eglDisplay, PImplData->eglConfigParam, InParentContext, ContextAttributes); } int32 AndroidEGL::GetError() { return eglGetError(); } bool AndroidEGL::IsInitialized() { return PImplData->Initalized; } GLuint AndroidEGL::GetResolveFrameBuffer() { return PImplData->ResolveFrameBuffer; } EGLContext AndroidEGL::GetCurrentContext() { VERIFY_EGL_SCOPE(); return eglGetCurrentContext(); } EGLDisplay AndroidEGL::GetDisplay() const { return PImplData->eglDisplay; } EGLSurface AndroidEGL::GetSurface() const { return PImplData->eglSurface; } EGLConfig AndroidEGL::GetConfig() const { return PImplData->eglConfigParam; } bool AndroidEGL::IsUsingWindowedSurface() const { return PImplData->bIsWndSurface; } void AndroidEGL::GetSwapIntervalRange(EGLint& OutMinSwapInterval, EGLint& OutMaxSwapInterval) const { eglGetConfigAttrib(PImplData->eglDisplay, PImplData->eglConfigParam, EGL_MIN_SWAP_INTERVAL, &OutMinSwapInterval); eglGetConfigAttrib(PImplData->eglDisplay, PImplData->eglConfigParam, EGL_MAX_SWAP_INTERVAL, &OutMaxSwapInterval); } ANativeWindow* AndroidEGL::GetNativeWindow() const { return PImplData->Window; } bool AndroidEGL::InitContexts() { PImplData->RenderingContext.eglContext = CreateContext(EGL_NO_CONTEXT); return PImplData->RenderingContext.eglContext != EGL_NO_CONTEXT; } void AndroidEGL::AcquireCurrentRenderingContext() { SetCurrentRenderingContext(); if (!PImplData->DummyFrameBuffer) { // Dummy FBO we bind right after SwapBuffers to tell driver that backbuffer is no longer in use by the App glGenFramebuffers(1, &PImplData->DummyFrameBuffer); PImplData->RenderingContext.DummyFrameBuffer = PImplData->DummyFrameBuffer; } if (IsOfflineSurfaceRequired()) { // Needs to be generated on rendering context if (!PImplData->ResolveFrameBuffer) { glGenFramebuffers(1, &PImplData->ResolveFrameBuffer); } } else { PImplData->ResolveFrameBuffer = 0; } } void AndroidEGL::SetCurrentRenderingContext() { SetCurrentContext(PImplData->RenderingContext.eglContext, PImplData->RenderingContext.eglSurface); } void AndroidEGL::ReleaseContextOwnership() { if (PlatformOpenGLThreadHasRenderingContext()) { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL:: ReleaseContextOwnership, thread releasing rendering context tid: %d"), FPlatformTLS::GetCurrentThreadId()); SetCurrentContext(EGL_NO_CONTEXT, EGL_NO_SURFACE); } else { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL:: ReleaseContextOwnership, rendering context was not current to this thread tid: %d"), FPlatformTLS::GetCurrentThreadId()); } } void AndroidEGL::Terminate() { FPlatformMisc::LowLevelOutputDebugStringf(TEXT("AndroidEGL:: Terminate! tid: %d"), FPlatformTLS::GetCurrentThreadId()); ResetDisplay(); DestroyContext(PImplData->RenderingContext.eglContext); PImplData->RenderingContext.Reset(); DestroyRenderSurface(); TerminateEGL(); } bool AndroidEGL::ThreadHasRenderingContext() { return GetCurrentContext() == PImplData->RenderingContext.eglContext; } FPlatformOpenGLContext* AndroidEGL::GetRenderingContext() { return &PImplData->RenderingContext; } bool AndroidEGL::GetSupportsNoErrorContext() { return bSupportsKHRNoErrorContext; } void AndroidEGL::UnBindRender() { FPlatformMisc::LowLevelOutputDebugString(TEXT("AndroidEGL::UnBindRender()")); ResetDisplay(); DestroyRenderSurface(); } void FAndroidAppEntry::ReInitWindow(const TOptional& WindowContainer) { check(IsInGameThread()); // Window creation is now handled by BlockRendering, when it resumes after a new window is created. FPlatformMisc::LowLevelOutputDebugString(TEXT("AndroidEGL::ReInitWindow()")); GSystemResolution.bForceRefresh = true; // It isn't safe to call ShouldUseVulkan if AndroidEGL is not initialized. // However, since we don't need to ReInit the window in that case anyways we // can return early. if (!AndroidEGL::GetInstance()->IsInitialized()) { return; } // @todo vulkan: Clean this up, and does vulkan need any code here? if (!FAndroidMisc::ShouldUseVulkan()) { // the window size could have been adjusted by the GT by now, if so it must be updated. AndroidEGL::GetInstance()->RefreshWindowSize(WindowContainer); } } void AndroidEGL::RefreshWindowSize(const TOptional& WindowContainer) { check(IsInGameThread()); check(!FAndroidMisc::ShouldUseVulkan()); FPlatformRect WindowRect = FAndroidWindow::GetScreenRect(); UE_LOG(LogAndroid, Log, TEXT("AndroidEGL::RefreshWindowSize updating window size = %d, %d, cached size : %d, %d tid : %d"), WindowRect.Right, WindowRect.Bottom, PImplData->CachedWindowRect.Right, PImplData->CachedWindowRect.Bottom, FPlatformTLS::GetCurrentThreadId()); PImplData->CachedWindowRect = WindowRect; ENQUEUE_RENDER_COMMAND(EGLResizeRenderContextSurface)( [&WindowContainer](FRHICommandListImmediate& RHICmdList) mutable { RHICmdList.EnqueueLambda([&WindowContainer](FRHICommandListImmediate&) mutable { AndroidEGL::GetInstance()->ResizeRenderContextSurface(WindowContainer); }); }); FlushRenderingCommands(); } void FAndroidAppEntry::OnPauseEvent() { const auto& OnPauseCallback = FAndroidMisc::GetOnPauseCallback(); if (OnPauseCallback) { OnPauseCallback(); } } void AndroidEGL::LogConfigInfo(EGLConfig EGLConfigInfo) { VERIFY_EGL_SCOPE(); EGLint ResultValue = 0 ; eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigInfo, EGL_RED_SIZE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo : EGL_RED_SIZE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigInfo, EGL_GREEN_SIZE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_GREEN_SIZE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigInfo, EGL_BLUE_SIZE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_BLUE_SIZE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigInfo, EGL_ALPHA_SIZE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_ALPHA_SIZE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigInfo, EGL_DEPTH_SIZE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_DEPTH_SIZE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigInfo, EGL_STENCIL_SIZE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_STENCIL_SIZE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay, EGLConfigInfo, EGL_SAMPLE_BUFFERS, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_SAMPLE_BUFFERS : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_BIND_TO_TEXTURE_RGB, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_BIND_TO_TEXTURE_RGB : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_SAMPLES, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_SAMPLES : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_COLOR_BUFFER_TYPE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_COLOR_BUFFER_TYPE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_CONFIG_CAVEAT, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_CONFIG_CAVEAT : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_CONFIG_ID, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_CONFIG_ID : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_CONFORMANT, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_CONFORMANT : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_LEVEL, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_LEVEL : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_LUMINANCE_SIZE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_LUMINANCE_SIZE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_MAX_PBUFFER_WIDTH, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_MAX_PBUFFER_WIDTH : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_MAX_PBUFFER_HEIGHT, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_MAX_PBUFFER_HEIGHT : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_MAX_PBUFFER_PIXELS, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_MAX_PBUFFER_PIXELS : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_MAX_SWAP_INTERVAL, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_MAX_SWAP_INTERVAL : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_MIN_SWAP_INTERVAL, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_MIN_SWAP_INTERVAL : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_NATIVE_RENDERABLE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_NATIVE_RENDERABLE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_NATIVE_VISUAL_TYPE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_NATIVE_VISUAL_TYPE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_NATIVE_VISUAL_ID, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_NATIVE_VISUAL_ID : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_RENDERABLE_TYPE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_RENDERABLE_TYPE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_SURFACE_TYPE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_SURFACE_TYPE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_TRANSPARENT_TYPE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_TRANSPARENT_TYPE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_TRANSPARENT_RED_VALUE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_TRANSPARENT_RED_VALUE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_TRANSPARENT_GREEN_VALUE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_TRANSPARENT_GREEN_VALUE : %u" ), ResultValue ); eglGetConfigAttrib(PImplData->eglDisplay,EGLConfigInfo, EGL_TRANSPARENT_BLUE_VALUE, &ResultValue); FPlatformMisc::LowLevelOutputDebugStringf( TEXT("EGLConfigInfo :EGL_TRANSPARENT_BLUE_VALUE : %u" ), ResultValue ); } void AndroidEGL::UpdateBuffersTransform() { if (ANativeWindow_setBuffersTransform_API != nullptr && !IsOfflineSurfaceRequired()) { int32 BufferTransform = ANATIVEWINDOW_TRANSFORM_IDENTITY; EDeviceScreenOrientation ScreenOrientation = FPlatformMisc::GetDeviceOrientation(); // Update the device orientation in case it hasn't been updated yet. if (ScreenOrientation == EDeviceScreenOrientation::Unknown) { FAndroidMisc::UpdateDeviceOrientation(); ScreenOrientation = FPlatformMisc::GetDeviceOrientation(); } switch (ScreenOrientation) { case EDeviceScreenOrientation::Portrait: BufferTransform = ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL; break; case EDeviceScreenOrientation::PortraitUpsideDown: BufferTransform = ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL; break; case EDeviceScreenOrientation::LandscapeLeft: BufferTransform = ANATIVEWINDOW_TRANSFORM_ROTATE_90 | ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL; break; case EDeviceScreenOrientation::LandscapeRight: BufferTransform = ANATIVEWINDOW_TRANSFORM_ROTATE_90 | ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL; break; default: ensureMsgf(false, TEXT("BufferTransform %d should be handled with no exception, otherwise wrong orientation could be displayed on device"), BufferTransform); break; } ANativeWindow_setBuffersTransform_API(GetNativeWindow(), BufferTransform); } } bool AndroidEGL::IsOfflineSurfaceRequired() { return FAndroidMisc::SupportsBackbufferSampling() // force to use BlitFrameBuffer || CVarAndroidGLESFlipYMethod.GetValueOnAnyThread() == 2 // setBuffersTransform doesn't work on android 9 and below devices || !(CVarAndroidGLESFlipYMethod.GetValueOnAnyThread() == 1 || FAndroidMisc::GetAndroidMajorVersion() >= 10) // setBuffersTransform doesn't work on arm and powerVR GPU devices || (CVarAndroidGLESFlipYMethod.GetValueOnAnyThread() == 0 && (GRHIVendorId == 0x13B5 || GRHIVendorId == 0x1010)); } /// extern FCriticalSection GAndroidWindowLock; void BlockOnLostWindowRenderCommand(TSharedPtr RTBlockedTrigger) { QUICK_SCOPE_CYCLE_COUNTER(STAT_BlockOnLostWindowRenderCommand); check(IsInRenderingThread()); // Hold GC scope guard, as GC will timeout if anything waits for RT fences. FGCScopeGuard GCGuard; FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get(); UE_LOG(LogAndroid, Log, TEXT("Blocking renderer")); if (FAndroidMisc::ShouldUseVulkan()) { if (IsRunningRHIInSeparateThread() && !RHICmdList.Bypass()) { UE_LOG(LogAndroid, Log, TEXT("RendererBlock FlushRHIThread")); RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread); UE_LOG(LogAndroid, Log, TEXT("RendererBlock DONE FlushRHIThread")); } const auto& OnReleaseWindowCallback = FAndroidMisc::GetOnReleaseWindowCallback(); if (OnReleaseWindowCallback) { UE_LOG(LogAndroid, Log, TEXT("RendererBlock release window callback")); OnReleaseWindowCallback(); } RTBlockedTrigger->Trigger(); GAndroidWindowLock.Lock(); UE_LOG(LogAndroid, Log, TEXT("RendererBlock acquired window lock")); const auto& OnReinitWindowCallback = FAndroidMisc::GetOnReInitWindowCallback(); if (OnReinitWindowCallback) { OnReinitWindowCallback(FAndroidWindow::GetHardwareWindow_EventThread()); UE_LOG(LogAndroid, Log, TEXT("RendererBlock updating window")); } GAndroidWindowLock.Unlock(); } else { RHICmdList.EnqueueLambda([RTBlockedTrigger](FRHICommandListImmediate&) { RTBlockedTrigger->Trigger(); GAndroidWindowLock.Lock(); UE_LOG(LogAndroid, Log, TEXT("RendererBlock acquired window lock")); AndroidEGL::GetInstance()->SetRenderContextWindowSurface(TOptional()); // FNativeAccessor is ignored with previous window behavior. UE_LOG(LogAndroid, Log, TEXT("RendererBlock updating window")); GAndroidWindowLock.Unlock(); }); RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread); } UE_LOG(LogAndroid, Log, TEXT("RendererBlock released window lock")); } void SetSharedContextGameCommand(TSharedPtr GTBlockedTrigger) { check(IsInGameThread()); AndroidEGL* EGL = AndroidEGL::GetInstance(); EGL->SetCurrentContext(EGL_NO_CONTEXT, EGL_NO_SURFACE); GTBlockedTrigger->Trigger(); } extern bool IsInAndroidEventThread(); void BlockRendering() { check(IsInAndroidEventThread()); check(GIsRHIInitialized); UE_LOG(LogAndroid, Log, TEXT("Blocking renderer on suspended window.")); TSharedPtr BlockedTrigger = MakeShareable(FPlatformProcess::GetSynchEventFromPool(), [](FEvent* EventToDelete) { FPlatformProcess::ReturnSynchEventToPool(EventToDelete); }); #if !USE_ANDROID_ALTERNATIVE_SUSPEND // Flush GT first in case it has any dependency on RT work to complete FGraphEventRef GTBlockTask = FFunctionGraphTask::CreateAndDispatchWhenReady([BlockedTrigger]() { SetSharedContextGameCommand(BlockedTrigger); }, TStatId(), NULL, ENamedThreads::GameThread); UE_LOG(LogAndroid, Log, TEXT("Waiting for game thread to release EGL context/surface.")); BlockedTrigger->Wait(); #endif // Wait for GC to complete and prevent further GCs FGCScopeGuard GCGuard; FGraphEventRef RTBlockTask = FFunctionGraphTask::CreateAndDispatchWhenReady([BlockedTrigger]() { BlockOnLostWindowRenderCommand(BlockedTrigger); }, TStatId(), NULL, ENamedThreads::GetRenderThread()); // wait for the render thread to process. UE_LOG(LogAndroid, Log, TEXT("Waiting for renderer to encounter blocking command.")); BlockedTrigger->Wait(); } #endif