1147 lines
43 KiB
C++
1147 lines
43 KiB
C++
// 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 <android/native_window.h>
|
|
#if USE_ANDROID_JNI
|
|
#include <android/native_window_jni.h>
|
|
#endif
|
|
#include "Misc/ScopeLock.h"
|
|
#include "UObject/GarbageCollection.h"
|
|
#include "Android/AndroidPlatformFramePacer.h"
|
|
#include <dlfcn.h>
|
|
#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<int32> 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<PFN_ANativeWindow_setBuffersTransform>(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<FAndroidWindow::FNativeAccessor>& 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<FAndroidWindow::FNativeAccessor>& 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<FAndroidWindow::FNativeAccessor>& 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<FAndroidWindow::FNativeAccessor>& 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<FAndroidWindow::FNativeAccessor>& 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<FEvent, ESPMode::ThreadSafe> 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<FAndroidWindow::FNativeAccessor>()); // 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<FEvent, ESPMode::ThreadSafe> 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<FEvent, ESPMode::ThreadSafe> 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
|