mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Feature] (EGL+GL): advertise EGL_EXT_create_context_robustness and GL robustness, with per-context reset notification and a pid-targeted device-loss knob.
This commit is contained in:
@@ -18,9 +18,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
inline constexpr const char* kPlatformExtensionString =
|
||||
"EGL_EXT_client_extensions EGL_EXT_platform_base EGL_KHR_platform_base "
|
||||
"EGL_MESA_platform_surfaceless" MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS;
|
||||
// EGL_EXT_create_context_robustness: a context may ask for EGL_LOSE_CONTEXT_ON_RESET, and a
|
||||
// split client's contexts are lost exactly when their server session is (a device loss on the
|
||||
// server ends that session). An application learns of it through glGetGraphicsResetStatus only
|
||||
// if it could ask for it here: ANGLE's GL-on-EGL backend mirrors this extension onto its own
|
||||
// display, and Chrome checks that display for it before it ever queries the reset status.
|
||||
inline constexpr const char* kDisplayExtensionString =
|
||||
"EGL_KHR_create_context EGL_KHR_surfaceless_context EGL_KHR_no_config_context EGL_EXT_platform_base "
|
||||
"EGL_KHR_platform_base "
|
||||
"EGL_EXT_create_context_robustness EGL_KHR_platform_base "
|
||||
"EGL_MESA_platform_surfaceless" MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS;
|
||||
// The display list again, with the dma-buf import a backend with shared images has (a split
|
||||
// client: the server allocates the images and names the descriptors that come back). A
|
||||
@@ -28,7 +33,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
// same server answers the buffer age of its surfaces and takes swap damage to its presents.
|
||||
inline constexpr const char* kDisplayExtensionStringWithSharedImages =
|
||||
"EGL_KHR_create_context EGL_KHR_surfaceless_context EGL_KHR_no_config_context EGL_EXT_platform_base "
|
||||
"EGL_KHR_platform_base "
|
||||
"EGL_EXT_create_context_robustness EGL_KHR_platform_base "
|
||||
"EGL_MESA_platform_surfaceless" MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS
|
||||
" EGL_KHR_image_base EGL_EXT_image_dma_buf_import EGL_EXT_image_dma_buf_import_modifiers"
|
||||
" EGL_EXT_buffer_age EGL_KHR_partial_update EGL_KHR_swap_buffers_with_damage"
|
||||
|
||||
@@ -435,9 +435,9 @@ DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture, GLenum target, GLenum attachm
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PrimitiveBoundingBox, GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PrimitiveBoundingBox, minX, minY, minZ, minW, maxX, maxY, maxZ, maxW)
|
||||
DECLARE_GL_FUNCTION_HEAD(GLenum, GetGraphicsResetStatus) DECLARE_GL_FUNCTION_END(GLenum, GetGraphicsResetStatus)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, ReadnPixels, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ReadnPixels, x, y, width, height, format, type, bufSize, data)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformfv, GLuint program, GLint location, GLsizei bufSize, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformfv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformiv, GLuint program, GLint location, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformiv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformuiv, GLuint program, GLint location, GLsizei bufSize, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformuiv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetnUniformfv, GLuint program, GLint location, GLsizei bufSize, GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetnUniformfv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetnUniformiv, GLuint program, GLint location, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetnUniformiv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetnUniformuiv, GLuint program, GLint location, GLsizei bufSize, GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetnUniformuiv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, MinSampleShading, GLfloat value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MinSampleShading, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, PatchParameteri, GLenum pname, GLint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PatchParameteri, pname, value)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, TexParameterIiv, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIiv, target, pname, params)
|
||||
@@ -1121,7 +1121,7 @@ DECLARE_GL_FUNCTION_HEAD(void, GetTextureSubImage, GLuint texture, GLint level,
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetCompressedTextureSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetCompressedTextureSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnCompressedTexImage, GLenum target, GLint lod, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnCompressedTexImage, target, lod, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnTexImage, GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnTexImage, target, level, format, type, bufSize, pixels)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformdv, GLuint program, GLint location, GLsizei bufSize, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformdv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_HEAD(void, GetnUniformdv, GLuint program, GLint location, GLsizei bufSize, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetnUniformdv, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnMapdv, GLenum target, GLenum query, GLsizei bufSize, GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnMapdv, target, query, bufSize, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnMapfv, GLenum target, GLenum query, GLsizei bufSize, GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnMapfv, target, query, bufSize, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnMapiv, GLenum target, GLenum query, GLsizei bufSize, GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnMapiv, target, query, bufSize, v)
|
||||
@@ -1292,7 +1292,6 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfARB, GLenum pname, GLfloat pa
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfvARB, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameterfvARB, pname, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnTexImageARB, GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* img) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnTexImageARB, target, level, format, type, bufSize, img)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnCompressedTexImageARB, GLenum target, GLint lod, GLsizei bufSize, void* img) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnCompressedTexImageARB, target, lod, bufSize, img)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformdvARB, GLuint program, GLint location, GLsizei bufSize, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformdvARB, program, location, bufSize, params)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnMapdvARB, GLenum target, GLenum query, GLsizei bufSize, GLdouble* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnMapdvARB, target, query, bufSize, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnMapfvARB, GLenum target, GLenum query, GLsizei bufSize, GLfloat* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnMapfvARB, target, query, bufSize, v)
|
||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnMapivARB, GLenum target, GLenum query, GLsizei bufSize, GLint* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnMapivARB, target, query, bufSize, v)
|
||||
@@ -2993,6 +2992,50 @@ MOBILEGL_GL_API void glGetnUniformuivARB(GLuint program, GLint location, GLsizei
|
||||
glGetnUniformuiv(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API void glGetnUniformdvARB(GLuint program, GLint location, GLsizei bufSize, GLdouble* params) {
|
||||
glGetnUniformdv(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
// KHR_robustness and EXT_robustness name the same entry points with their own suffixes (an ES
|
||||
// application, ANGLE's GL backend among them, looks them up by those names).
|
||||
MOBILEGL_GL_API GLenum glGetGraphicsResetStatusKHR(void) {
|
||||
return glGetGraphicsResetStatus();
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API GLenum glGetGraphicsResetStatusEXT(void) {
|
||||
return glGetGraphicsResetStatus();
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API void glReadnPixelsKHR(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
GLsizei bufSize, void* data) {
|
||||
glReadnPixels(x, y, width, height, format, type, bufSize, data);
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API void glReadnPixelsEXT(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
GLsizei bufSize, void* data) {
|
||||
glReadnPixels(x, y, width, height, format, type, bufSize, data);
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API void glGetnUniformfvKHR(GLuint program, GLint location, GLsizei bufSize, GLfloat* params) {
|
||||
glGetnUniformfv(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API void glGetnUniformfvEXT(GLuint program, GLint location, GLsizei bufSize, GLfloat* params) {
|
||||
glGetnUniformfv(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API void glGetnUniformivKHR(GLuint program, GLint location, GLsizei bufSize, GLint* params) {
|
||||
glGetnUniformiv(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API void glGetnUniformivEXT(GLuint program, GLint location, GLsizei bufSize, GLint* params) {
|
||||
glGetnUniformiv(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API void glGetnUniformuivKHR(GLuint program, GLint location, GLsizei bufSize, GLuint* params) {
|
||||
glGetnUniformuiv(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
MOBILEGL_GL_API void glGetObjectLabelEXT(GLenum identifier, GLuint name, GLsizei bufSize, GLsizei* length,
|
||||
GLchar* label) {
|
||||
glGetObjectLabel(identifier, name, bufSize, length, label);
|
||||
|
||||
@@ -592,12 +592,60 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
|
||||
namespace {
|
||||
// GL_OES_EGL_image binds the EGL side's dma-buf images, which only a backend with shared
|
||||
// images has (EGLImpl::SharedImagesAvailable). The backend's own list - the server's,
|
||||
// under split - cannot know that, so the extension is appended after it.
|
||||
const char* ClientSideExtension() {
|
||||
return MG_Impl::EGLImpl::SharedImagesAvailable() ? "GL_OES_EGL_image" : nullptr;
|
||||
// Extensions this library implements itself, appended after the backend's own list (the
|
||||
// server's, under split), which cannot know about them:
|
||||
// * GL_OES_EGL_image binds the EGL side's dma-buf images, which only a backend with
|
||||
// shared images has (EGLImpl::SharedImagesAvailable);
|
||||
// * the robustness family - reset notification (glGetGraphicsResetStatus, the contexts'
|
||||
// EGL reset strategy, GL_CONTEXT_LOST) and the bounded queries (glReadnPixels,
|
||||
// glGetnUniform*) are this frontend's, whatever the backend. KHR for GL and ES, ARB for
|
||||
// GL applications that ask for that name, EXT for ES applications that ask for that one.
|
||||
// Robust buffer ACCESS is a separate promise (GL_CONTEXT_ROBUST_ACCESS) and is not
|
||||
// implied by any of them.
|
||||
// One list for glGetString and glGetStringi, so the two forms always agree.
|
||||
const Vector<const char*>& ClientSideExtensions() {
|
||||
static const Vector<const char*> withImages = {"GL_OES_EGL_image", "GL_KHR_robustness",
|
||||
"GL_ARB_robustness", "GL_EXT_robustness"};
|
||||
static const Vector<const char*> withoutImages = {"GL_KHR_robustness", "GL_ARB_robustness",
|
||||
"GL_EXT_robustness"};
|
||||
return MG_Impl::EGLImpl::SharedImagesAvailable() ? withImages : withoutImages;
|
||||
}
|
||||
|
||||
// Robust buffer access as GL 4.5 / KHR_robust_buffer_access_behavior define it: the backend
|
||||
// promises it by listing one of the behavior extensions. Neither backend does today, so a
|
||||
// context that asked for robust access gets it reported as absent rather than claimed.
|
||||
Bool BackendProvidesRobustBufferAccess() {
|
||||
const auto& backend = MG_Backend::pActiveBackendObject;
|
||||
if (!backend) return false;
|
||||
const auto& extensions = backend->GetRendererInfo().RendererGLInfo.Extensions;
|
||||
return std::find(extensions.begin(), extensions.end(), E_GL_KHR_robust_buffer_access_behavior) !=
|
||||
extensions.end() ||
|
||||
std::find(extensions.begin(), extensions.end(), E_GL_ARB_robust_buffer_access_behavior) !=
|
||||
extensions.end();
|
||||
}
|
||||
|
||||
Bool CurrentContextHasRobustAccess() {
|
||||
return MG_State::pEGLContext && MG_State::pEGLContext->IsCurrentContextRobustAccessRequested() &&
|
||||
BackendProvidesRobustBufferAccess();
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// KHR_robustness 2.6.1: a context whose reset notification behavior is
|
||||
// NO_RESET_NOTIFICATION is never told of a reset - glGetGraphicsResetStatus answers
|
||||
// GL_NO_ERROR and no command raises GL_CONTEXT_LOST. That is what an explicit core-profile
|
||||
// context gets. Everywhere else (MG_State::IsRelaxedSemanticsActive) the loss is reported
|
||||
// to every context, as before the strategy existed: an application that queries the reset
|
||||
// status without having asked for notification still learns that its context is gone,
|
||||
// which on a split client it is - there is nothing to recover in place. A context created
|
||||
// with EGL_LOSE_CONTEXT_ON_RESET is always told.
|
||||
Bool ResetIsNotified() {
|
||||
if (MG_State::pEGLContext &&
|
||||
MG_State::pEGLContext->GetCurrentContextResetNotificationStrategy() == EGL_LOSE_CONTEXT_ON_RESET) {
|
||||
return true;
|
||||
}
|
||||
return MG_State::IsRelaxedSemanticsActive();
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
||||
@@ -664,7 +712,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
extensionsString += MG_Util::ConvertGLExtToString(ext);
|
||||
}
|
||||
if (const char* extra = ClientSideExtension()) {
|
||||
for (const char* extra : ClientSideExtensions()) {
|
||||
if (!extensionsString.empty()) extensionsString += " ";
|
||||
extensionsString += extra;
|
||||
}
|
||||
@@ -688,8 +736,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
const auto& rendererInfo = activeBackendObject->GetRendererInfo();
|
||||
|
||||
const auto& exts = rendererInfo.RendererGLInfo.Extensions;
|
||||
if (index == exts.size()) {
|
||||
if (const char* extra = ClientSideExtension()) return (const GLubyte*)extra;
|
||||
if (index >= exts.size()) {
|
||||
const auto& extras = ClientSideExtensions();
|
||||
if (index - exts.size() < extras.size()) return (const GLubyte*)extras[index - exts.size()];
|
||||
}
|
||||
if (index >= exts.size()) {
|
||||
return nullptr;
|
||||
@@ -1675,8 +1724,20 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
case GL_CONTEXT_FLAGS: {
|
||||
*params = MG_State::pEGLContext ? MG_State::pEGLContext->GetCurrentContextFlags() : 0;
|
||||
if (CurrentContextHasRobustAccess()) *params |= GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT;
|
||||
return;
|
||||
}
|
||||
case GL_CONTEXT_ROBUST_ACCESS:
|
||||
// GL ES 3.2 / KHR_robustness: true only where it was asked for AND is provided.
|
||||
*params = CurrentContextHasRobustAccess() ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
case GL_RESET_NOTIFICATION_STRATEGY:
|
||||
// The EGL reset notification behavior the context was created with.
|
||||
*params = MG_State::pEGLContext && MG_State::pEGLContext->GetCurrentContextResetNotificationStrategy() ==
|
||||
EGL_LOSE_CONTEXT_ON_RESET
|
||||
? GL_LOSE_CONTEXT_ON_RESET
|
||||
: GL_NO_RESET_NOTIFICATION;
|
||||
return;
|
||||
case GL_CULL_FACE:
|
||||
*params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace) ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
@@ -2812,8 +2873,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*params = rendererInfo.RendererGLInfo.TargetGLVersion.Minor;
|
||||
break;
|
||||
case GL_NUM_EXTENSIONS:
|
||||
*params = static_cast<Int>(rendererInfo.RendererGLInfo.Extensions.size()) +
|
||||
(ClientSideExtension() != nullptr ? 1 : 0);
|
||||
*params = static_cast<Int>(rendererInfo.RendererGLInfo.Extensions.size() + ClientSideExtensions().size());
|
||||
break;
|
||||
case GL_POINT_SIZE_GRANULARITY:
|
||||
*params = static_cast<GLint>(dynamicParameters.PointSizeGranularity);
|
||||
@@ -2883,7 +2943,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// context and ahead of whatever else is queued, so an application that learns of the loss
|
||||
// from glGetError (Chromium's decoder does) learns of it, and one that drains errors in a
|
||||
// loop until GL_NO_ERROR still terminates.
|
||||
if (MG_Remote::Client::ClientSession::DeviceLost() && MG_State::pGLContext &&
|
||||
// Only where the reset is notified at all (ResetIsNotified: not on a NO_RESET_NOTIFICATION
|
||||
// explicit core-profile context).
|
||||
if (MG_Remote::Client::ClientSession::DeviceLost() && MG_State::pGLContext && ResetIsNotified() &&
|
||||
MG_State::pGLContext->TakeContextLostReport()) {
|
||||
return GL_CONTEXT_LOST;
|
||||
}
|
||||
@@ -2907,13 +2969,35 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
//
|
||||
// GL_UNKNOWN_CONTEXT_RESET, not GUILTY or INNOCENT. Those two assign BLAME - guilty
|
||||
// means this context's commands caused the reset, innocent means another context's did -
|
||||
// and neither is knowable here: the server died for a reason that never crossed the
|
||||
// wire, and it could as easily have been a driver fault as this client's draw. UNKNOWN is
|
||||
// the spec's answer for exactly that, and it is the one that does not lie.
|
||||
// and neither is knowable here:
|
||||
// * a session ends for a fault the server names by FAMILY only (BackendDeviceLost, a
|
||||
// protocol fault, ...) or by hanging up; nothing it sends attributes the fault to a
|
||||
// context, and a hangup is a server death, not a GPU reset at all;
|
||||
// * a GPU device loss is not attributable either: Vulkan's device loss carries no
|
||||
// culprit, and the host GL driver's own GUILTY/INNOCENT (which Espryt reads when it
|
||||
// latches) is about whichever backend context the server had current, while the
|
||||
// session ends as a whole - every context of it is lost with that one;
|
||||
// * INNOCENT would mean another context's fault reset this one, but a loss ends exactly
|
||||
// the session that faulted (every other session keeps running), so the contexts that
|
||||
// see it are all in the faulting session.
|
||||
// UNKNOWN is the spec's answer for exactly that, and it is the one that does not lie. The
|
||||
// cause - the latch's words, with the fault family where the server named one - is
|
||||
// logged once per context, the first time its reset is reported.
|
||||
//
|
||||
// The status does not go back to GL_NO_ERROR: the context stays lost (KHR_robustness
|
||||
// allows a reset status to be returned repeatedly while the reset "is in progress", and
|
||||
// this one never completes - the context has to be recreated, which puts it on a fresh
|
||||
// session).
|
||||
//
|
||||
// WHOLLY INSIDE THE GUARD, including the include above. In a pull build this function is
|
||||
// byte-for-byte what it was, which is what G1 measures.
|
||||
if (MG_Remote::Client::ClientSession::DeviceLost()) {
|
||||
if (!ResetIsNotified()) return GL_NO_ERROR;
|
||||
if (MG_State::pGLContext && MG_State::pGLContext->TakeResetStatusReport()) {
|
||||
const String reason = MG_Remote::Client::ClientSession::LastLossReason();
|
||||
MGLOG_W("glGetGraphicsResetStatus: GL_UNKNOWN_CONTEXT_RESET for a context of a lost session (%s)",
|
||||
reason.empty() ? "the session was lost" : reason.c_str());
|
||||
}
|
||||
return GL_UNKNOWN_CONTEXT_RESET;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1380,6 +1380,36 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GetUniformScalar_State(program, location, params);
|
||||
}
|
||||
|
||||
// glGetnUniform* (GL 4.5 core, KHR_robustness): glGetUniform* bounded by bufSize. The query
|
||||
// writes one value per component of the uniform at `location` (an opaque uniform's unit is
|
||||
// one), so the bound is that count times the element size; a destination smaller than that -
|
||||
// a negative bufSize included - is GL_INVALID_OPERATION and nothing is written. Every other
|
||||
// error is the unbounded query's, raised by it.
|
||||
template <typename T>
|
||||
void GetnUniform_State(GLuint program, GLint location, GLsizei bufSize, T* params) {
|
||||
auto& programObject = TryToGetProgramObject(program);
|
||||
if (!programObject) return;
|
||||
if (programObject->GetLinkStatus() && programObject->IsValidUniformLocation(location)) {
|
||||
SizeT components = 1;
|
||||
if (!programObject->IsUniformOpaqueAtLocation(location)) {
|
||||
const auto& type = programObject->GetUniformTypeFacts(location);
|
||||
components = type.isMatrix ? static_cast<SizeT>(type.matrixCols) * static_cast<SizeT>(type.matrixRows)
|
||||
: type.isVector ? static_cast<SizeT>(type.vectorSize)
|
||||
: 1;
|
||||
}
|
||||
if (bufSize < 0 || components * sizeof(T) > static_cast<SizeT>(bufSize)) {
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||
"the uniform's " + std::to_string(components) +
|
||||
" component(s) do not fit in bufSize " + std::to_string(bufSize) +
|
||||
"."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
GetUniformScalar_State(program, location, params);
|
||||
}
|
||||
|
||||
GLboolean IsProgram_State(GLuint program) {
|
||||
// Deletion-flagged names stay valid while the object is still GL-visible (program in
|
||||
// use, shader attached), so name validity is exactly the Is* answer.
|
||||
@@ -2553,6 +2583,22 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GetUniformuiv_State(program, location, params);
|
||||
}
|
||||
|
||||
void GetnUniformfv(GLuint program, GLint location, GLsizei bufSize, GLfloat* params) {
|
||||
GetnUniform_State(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
void GetnUniformiv(GLuint program, GLint location, GLsizei bufSize, GLint* params) {
|
||||
GetnUniform_State(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
void GetnUniformuiv(GLuint program, GLint location, GLsizei bufSize, GLuint* params) {
|
||||
GetnUniform_State(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
void GetnUniformdv(GLuint program, GLint location, GLsizei bufSize, GLdouble* params) {
|
||||
GetnUniform_State(program, location, bufSize, params);
|
||||
}
|
||||
|
||||
GLboolean IsProgram(GLuint program) {
|
||||
return IsProgram_State(program);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void GetUniformfv(GLuint program, GLint location, GLfloat* params);
|
||||
void GetUniformiv(GLuint program, GLint location, GLint* params);
|
||||
void GetUniformuiv(GLuint program, GLint location, GLuint* params);
|
||||
// GL 4.5 / KHR_robustness: the queries above, bounded by bufSize.
|
||||
void GetnUniformfv(GLuint program, GLint location, GLsizei bufSize, GLfloat* params);
|
||||
void GetnUniformiv(GLuint program, GLint location, GLsizei bufSize, GLint* params);
|
||||
void GetnUniformuiv(GLuint program, GLint location, GLsizei bufSize, GLuint* params);
|
||||
void GetnUniformdv(GLuint program, GLint location, GLsizei bufSize, GLdouble* params);
|
||||
GLboolean IsProgram(GLuint program);
|
||||
GLboolean IsShader(GLuint shader);
|
||||
void LinkProgram(GLuint program);
|
||||
|
||||
@@ -29,6 +29,19 @@ extern "C" EGLBoolean eglQueryDmaBufModifiersEXT(EGLDisplay dpy, EGLint format,
|
||||
khronos_uint64_t* modifiers, EGLBoolean* external_only,
|
||||
EGLint* num_modifiers);
|
||||
extern "C" void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image);
|
||||
// KHR_robustness / EXT_robustness suffixed names (Definitions.cpp); the desktop headers declare
|
||||
// only the unsuffixed and ARB ones.
|
||||
extern "C" GLenum glGetGraphicsResetStatusKHR(void);
|
||||
extern "C" GLenum glGetGraphicsResetStatusEXT(void);
|
||||
extern "C" void glReadnPixelsKHR(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
GLsizei bufSize, void* data);
|
||||
extern "C" void glReadnPixelsEXT(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||
GLsizei bufSize, void* data);
|
||||
extern "C" void glGetnUniformfvKHR(GLuint program, GLint location, GLsizei bufSize, GLfloat* params);
|
||||
extern "C" void glGetnUniformfvEXT(GLuint program, GLint location, GLsizei bufSize, GLfloat* params);
|
||||
extern "C" void glGetnUniformivKHR(GLuint program, GLint location, GLsizei bufSize, GLint* params);
|
||||
extern "C" void glGetnUniformivEXT(GLuint program, GLint location, GLsizei bufSize, GLint* params);
|
||||
extern "C" void glGetnUniformuivKHR(GLuint program, GLint location, GLsizei bufSize, GLuint* params);
|
||||
|
||||
#define GETPROC(name, var) \
|
||||
if (strcmp(#name, var) == 0) { \
|
||||
@@ -876,6 +889,15 @@ namespace MobileGL::MG_Impl {
|
||||
GETPROC(glGetnUniformivARB, name);
|
||||
GETPROC(glGetnUniformuivARB, name);
|
||||
GETPROC(glGetnUniformdvARB, name);
|
||||
GETPROC(glGetGraphicsResetStatusKHR, name);
|
||||
GETPROC(glGetGraphicsResetStatusEXT, name);
|
||||
GETPROC(glReadnPixelsKHR, name);
|
||||
GETPROC(glReadnPixelsEXT, name);
|
||||
GETPROC(glGetnUniformfvKHR, name);
|
||||
GETPROC(glGetnUniformfvEXT, name);
|
||||
GETPROC(glGetnUniformivKHR, name);
|
||||
GETPROC(glGetnUniformivEXT, name);
|
||||
GETPROC(glGetnUniformuivKHR, name);
|
||||
GETPROC(glFramebufferSampleLocationsfvARB, name);
|
||||
GETPROC(glNamedFramebufferSampleLocationsfvARB, name);
|
||||
GETPROC(glEvaluateDepthValuesARB, name);
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
@@ -209,6 +210,47 @@ namespace MobileGL::MG_Pipe {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MGPipeDebugSessionLossDue(unsigned clientPid) {
|
||||
if (clientPid == 0) return false;
|
||||
// The first call on a thread only starts the clock, so a session is first asked 100 ms
|
||||
// after it started rendering - never in the middle of its first make-current.
|
||||
thread_local auto lastAsked = std::chrono::steady_clock::time_point{};
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (lastAsked == std::chrono::steady_clock::time_point{}) {
|
||||
lastAsked = now;
|
||||
return false;
|
||||
}
|
||||
if (now - lastAsked < std::chrono::milliseconds(100)) return false;
|
||||
lastAsked = now;
|
||||
long target = 0;
|
||||
if (const char* value = std::getenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_PID")) {
|
||||
target = std::strtol(value, nullptr, 10);
|
||||
}
|
||||
#if defined(__ANDROID__)
|
||||
else {
|
||||
char property[PROP_VALUE_MAX] = {};
|
||||
if (__system_property_get("debug.mobilegl.inject_device_lost_pid", property) > 0) {
|
||||
target = std::strtol(property, nullptr, 10);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Once per value: the session it hit is over, and a later session of the same process (the
|
||||
// recovery's) must not be lost again by the same setting. Another pid - or unset/0 and
|
||||
// then the same one - re-arms it.
|
||||
static std::atomic<long> firedFor{0};
|
||||
if (target <= 0) {
|
||||
if (firedFor.load(std::memory_order_relaxed) != 0) firedFor.store(0, std::memory_order_relaxed);
|
||||
return false;
|
||||
}
|
||||
if (static_cast<unsigned long>(target) != clientPid) return false;
|
||||
long previous = firedFor.load(std::memory_order_acquire);
|
||||
if (previous == target || !firedFor.compare_exchange_strong(previous, target, std::memory_order_acq_rel)) {
|
||||
return false;
|
||||
}
|
||||
MGLOG_E("MGPipe: debug knob inject_device_lost_pid=%ld - this session's device is reported LOST", target);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MGPipeSessionLatch(MGPipeFatalFamily family, const char* fmt, ...) {
|
||||
char line[512];
|
||||
va_list args;
|
||||
|
||||
@@ -124,6 +124,15 @@ namespace MobileGL::MG_Pipe {
|
||||
// several sessions in turn. The backends ask at their frame boundaries and readbacks; unset
|
||||
// (the default) it is one getenv.
|
||||
bool MGPipeDebugDeviceLossDue();
|
||||
// THE SAME KNOB, AIMED AT ONE CLIENT. MOBILEGL_DEBUG_INJECT_DEVICE_LOST_PID=P (Android: the
|
||||
// property debug.mobilegl.inject_device_lost_pid) loses the session whose client named pid P in
|
||||
// its Hello (its own pid, as it sees it - a container's own pid namespace included), once per
|
||||
// value, whatever the backend and whether or not that client ever presents or reads back: the
|
||||
// server's apply thread asks after every drain. It is how one application among several is
|
||||
// made to lose its device - a browser's GPU process, which renders into dma-bufs and never
|
||||
// presents, included. Asked only for a session with a context bound; asks the environment /
|
||||
// property at most every 100 ms per thread.
|
||||
bool MGPipeDebugSessionLossDue(unsigned clientPid);
|
||||
#endif
|
||||
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
|
||||
@@ -103,6 +103,27 @@ namespace MobileGL::MG_Remote::Client {
|
||||
thread_local Uint32 tl_wireMute = 0;
|
||||
thread_local Uint32 tl_currentSessionWork = 0;
|
||||
std::atomic<Uint64> g_remoteSessionsStarted{0};
|
||||
// ClientSession::LastLossReason(). Leaked on purpose: read from GL entry points that may
|
||||
// run during process exit.
|
||||
std::mutex& LossReasonMutex() {
|
||||
static auto* mutex = new std::mutex();
|
||||
return *mutex;
|
||||
}
|
||||
String& LossReason() {
|
||||
static auto* reason = new String();
|
||||
return *reason;
|
||||
}
|
||||
// The latch's words for a session the server ended on a named fault: what happened, and
|
||||
// the fault family the server named (BackendDeviceLost for a GPU device loss).
|
||||
String FaultReason(const ::MobileGL::Wire::CtrlEnvelope* envelope, const char* what) {
|
||||
String reason = what;
|
||||
if (const auto* fatal = envelope->msg_as_Fatal(); fatal != nullptr && fatal->family() != nullptr) {
|
||||
reason += " (Fatal family ";
|
||||
reason += fatal->family()->c_str();
|
||||
reason += ")";
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
// The same bounded handshake deadline the server uses. Bounded, not kWaitForever: a
|
||||
// bring-up that never answers has to be a red lane rather than a wedged CI job.
|
||||
@@ -614,6 +635,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return context != nullptr && context->IsFromEndedWireSession();
|
||||
}
|
||||
|
||||
String ClientSession::LastLossReason() {
|
||||
const std::lock_guard<std::mutex> lock(LossReasonMutex());
|
||||
return LossReason();
|
||||
}
|
||||
|
||||
Bool ClientSession::SessionLatchedLost() {
|
||||
const ClientSession* session = Active();
|
||||
return session != nullptr && session->m_deviceLost.load(std::memory_order_acquire);
|
||||
@@ -633,6 +659,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
if (m_deviceLost.exchange(true, std::memory_order_acq_rel)) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(LossReasonMutex());
|
||||
LossReason() = why == nullptr ? "the peer hung up" : why;
|
||||
}
|
||||
MGLOG_E("MGPipe: DEVICE LOST - %s. This session's server side is gone; every verb from here "
|
||||
"is DECLINED and glGetGraphicsResetStatus reports GL_UNKNOWN_CONTEXT_RESET. This is "
|
||||
"latched from the peer's HANGUP (or its named SessionFault) and not from any "
|
||||
@@ -1645,7 +1675,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
MGLOG_E("MG_Remote client: the server published a SessionFault before dying - "
|
||||
"Fatal family %s: %s", fam, msg);
|
||||
}
|
||||
LatchDeviceLost("the server published a SessionFault and then aborted");
|
||||
LatchDeviceLost(FaultReason(envelope, "the server published a SessionFault and then aborted").c_str());
|
||||
frame.ok = false;
|
||||
return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
}
|
||||
@@ -2742,7 +2772,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
fatal->family() ? fatal->family()->c_str() : "<none>",
|
||||
fatal->message() ? fatal->message()->c_str() : "");
|
||||
}
|
||||
LatchDeviceLost("the server published a SessionFault for this session");
|
||||
LatchDeviceLost(FaultReason(envelope, "the server published a SessionFault for this session").c_str());
|
||||
continue;
|
||||
}
|
||||
if (envelope->msg_type() != ::MobileGL::Wire::CtrlMsg::CapsSnapshot) {
|
||||
|
||||
@@ -100,6 +100,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// Arms the latch and says why, once. Safe to call repeatedly and from any thread; only
|
||||
// the first call logs.
|
||||
void LatchDeviceLost(const char* why);
|
||||
// Why the most recent session of this process was lost, in the latch's words (empty
|
||||
// before any loss). Kept past the recovery, because the contexts of the lost session
|
||||
// outlive it: glGetGraphicsResetStatus names it when it first reports a context's reset.
|
||||
static String LastLossReason();
|
||||
|
||||
// A reply-owning record came back DECLINED where only a latched server declines (a
|
||||
// readback). The server publishes its SessionFault on the control plane BEFORE it declines,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "ApplyThreadPolicy.h"
|
||||
#include "SessionRuntime.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
#include <MG_Pipe/PipeSessionFail.h>
|
||||
#include <MG_Remote/Transport/LinkMetrics.h>
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
|
||||
@@ -633,6 +634,18 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (stopOrForfeit()) break;
|
||||
if (eventRingHasRoom()) DrainRing();
|
||||
if (stopOrForfeit()) break;
|
||||
// The session-targeted device-loss debug knob (PipeSessionFail.h): asked here, after a
|
||||
// drain, so it reaches a client that never presents or reads back, once the session
|
||||
// has a context bound (it renders); latched as the backends latch a real loss. Unset
|
||||
// it is one clock read per iteration.
|
||||
if (!SessionLatched() && session.CurrentContextToken() != 0 &&
|
||||
MG_Pipe::MGPipeDebugSessionLossDue(session.ClientPid())) {
|
||||
(void)MG_Pipe::MGPipeSessionLatch(MG_Pipe::MGPipeFatalFamily::DeviceLost,
|
||||
"MGPipe: Fatal{BackendDeviceLost, \"injected:pid=%u\"} - the debug "
|
||||
"knob lost this session's device; the session ends and its client "
|
||||
"reads a lost context, every other session keeps running",
|
||||
session.ClientPid());
|
||||
}
|
||||
if (eventRingHasRoom() && !SessionLatched()) verbs.ReportSignaledFences(/*flush=*/false);
|
||||
// PH-1 (3): a latched session applies nothing more, so this thread leaves the loop
|
||||
// (and does not spin on a ring whose head is still ahead of a tail DrainRing will no
|
||||
|
||||
@@ -687,6 +687,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
m_peer = hello->dialMode() == ::MobileGL::Wire::DialMode::Connect ? ApplyPeer::Connected
|
||||
: hello->dialMode() == ::MobileGL::Wire::DialMode::Fork ? ApplyPeer::Forked
|
||||
: ApplyPeer::InProcess;
|
||||
m_clientPid = hello->pid();
|
||||
const MobileGLResult compatible = ValidatePeerHandshake(transport, hello->abiMajor(),
|
||||
hello->abiMinor(), hello->wireFingerprint(), theirStamp, hello->dialMode());
|
||||
if (compatible != MOBILEGL_OK) return compatible;
|
||||
|
||||
@@ -370,6 +370,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// 0 = no context is bound. Every record the session has applied so far belongs to this
|
||||
// token; a session that never crossed a bind_context has never bound one.
|
||||
Uint64 CurrentContextToken() const { return m_currentContextToken.load(std::memory_order_acquire); }
|
||||
// The pid the client's Hello names (its own, in its own pid namespace; 0 before Accept).
|
||||
// What the session-targeted device-loss debug knob matches.
|
||||
Uint32 ClientPid() const { return m_clientPid; }
|
||||
Bool HasContext(Uint64 token) const { return m_contexts.find(token) != m_contexts.end(); }
|
||||
Uint64 ContextCount() const { return static_cast<Uint64>(m_contexts.size()); }
|
||||
Uint64 ContextsCreated() const { return m_contextsCreated; }
|
||||
@@ -418,6 +421,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// P11 B2 (T0). Set by Accept from the Hello, by SettleAdoptT0AtBind on the apply thread;
|
||||
// the inbox and the counters are the apply thread's.
|
||||
Bool m_adoptT0Asked = false;
|
||||
Uint32 m_clientPid = 0;
|
||||
Bool m_adoptT0Quiet = false;
|
||||
Uint8 m_adoptAsk = 2;
|
||||
Bool m_adoptT0Settled = false;
|
||||
|
||||
@@ -679,6 +679,31 @@ namespace MobileGL {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// RESET NOTIFICATION (EGL_EXT_create_context_robustness, EGL 1.5): validated before
|
||||
// anything of the context is built, since building its GL state already makes
|
||||
// objects. Both names of the attribute are taken - the extension's (what Chrome,
|
||||
// KWin and Qt pass) and EGL 1.5's (what ANGLE's GL-on-EGL backend passes for its
|
||||
// own native context) - with the two values they share. Contexts that share
|
||||
// objects must agree on it (EGL_BAD_MATCH otherwise).
|
||||
EGLint resetStrategy = EGL_NO_RESET_NOTIFICATION;
|
||||
for (const EGLint name :
|
||||
{EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY}) {
|
||||
if (auto value = ParseAttribValue(attribList, name); value) {
|
||||
if (*value != EGL_NO_RESET_NOTIFICATION && *value != EGL_LOSE_CONTEXT_ON_RESET) {
|
||||
SetError(EGL_BAD_ATTRIBUTE);
|
||||
return nullptr;
|
||||
}
|
||||
resetStrategy = *value;
|
||||
}
|
||||
}
|
||||
if (shareCtx != nullptr) {
|
||||
const auto* shared = TryGetContext(shareCtx);
|
||||
if (shared != nullptr && shared->ResetNotificationStrategy != resetStrategy) {
|
||||
SetError(EGL_BAD_MATCH);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ContextObject contextObject = {
|
||||
.Display = display,
|
||||
.Config = config,
|
||||
@@ -688,6 +713,7 @@ namespace MobileGL {
|
||||
.MajorVersion = 1,
|
||||
.MinorVersion = 0,
|
||||
};
|
||||
contextObject.ResetNotificationStrategy = resetStrategy;
|
||||
|
||||
// P14 S1: the wire identity, minted where the context is born. A context that
|
||||
// shares with an existing one joins ITS share group; EGL_NO_CONTEXT starts a new
|
||||
@@ -735,7 +761,7 @@ namespace MobileGL {
|
||||
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_DEBUG_BIT;
|
||||
}
|
||||
if (*value & EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR) {
|
||||
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT;
|
||||
contextObject.RobustAccessRequested = true;
|
||||
}
|
||||
}
|
||||
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE);
|
||||
@@ -747,10 +773,17 @@ namespace MobileGL {
|
||||
contextObject.EGLContextFlags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
|
||||
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_DEBUG_BIT;
|
||||
}
|
||||
if (auto value = ParseAttribValue(attribList, EGL_CONTEXT_OPENGL_ROBUST_ACCESS);
|
||||
value && *value == EGL_TRUE) {
|
||||
contextObject.EGLContextFlags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;
|
||||
contextObject.OpenGLContextFlags |= GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT;
|
||||
// ROBUST BUFFER ACCESS is recorded as asked for and reported only where it is
|
||||
// provided (GL_CONTEXT_ROBUST_ACCESS, GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT; the GL
|
||||
// layer decides). The request is not refused where it is not: compositors and
|
||||
// browsers ask for robust access and reset notification in the same attempt, and
|
||||
// refusing the first would cost them the second, which is the one that matters to
|
||||
// a split client - its contexts are lost exactly when the server session is.
|
||||
for (const EGLint name : {EGL_CONTEXT_OPENGL_ROBUST_ACCESS, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT}) {
|
||||
if (auto value = ParseAttribValue(attribList, name); value && *value == EGL_TRUE) {
|
||||
contextObject.EGLContextFlags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;
|
||||
contextObject.RobustAccessRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto context = EncodeHandle<EGLContextHandle>(m_nextContextHandle++);
|
||||
@@ -913,6 +946,26 @@ namespace MobileGL {
|
||||
return ctx ? ctx->OpenGLContextFlags : 0;
|
||||
}
|
||||
|
||||
EGLint EGLContext::GetCurrentContextResetNotificationStrategy() const {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
|
||||
if (currentIt == m_threadCurrents.end()) {
|
||||
return EGL_NO_RESET_NOTIFICATION;
|
||||
}
|
||||
const auto* ctx = TryGetContext(currentIt->second.Context);
|
||||
return ctx ? ctx->ResetNotificationStrategy : EGL_NO_RESET_NOTIFICATION;
|
||||
}
|
||||
|
||||
Bool EGLContext::IsCurrentContextRobustAccessRequested() const {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
auto currentIt = m_threadCurrents.find(CurrentThreadKey());
|
||||
if (currentIt == m_threadCurrents.end()) {
|
||||
return false;
|
||||
}
|
||||
const auto* ctx = TryGetContext(currentIt->second.Context);
|
||||
return ctx && ctx->RobustAccessRequested;
|
||||
}
|
||||
|
||||
EGLContext::EGLSurfaceHandle EGLContext::CreateWindowSurface(EGLDisplayHandle display,
|
||||
EGLConfigHandle config,
|
||||
NativeWindowType window,
|
||||
|
||||
@@ -69,6 +69,11 @@ namespace MobileGL {
|
||||
Bool IsCurrentContextOpenGLCoreProfile() const;
|
||||
Bool IsCurrentContextOpenGLCompatibilityProfile() const;
|
||||
EGLint GetCurrentContextFlags() const;
|
||||
// The calling thread's current context's reset notification behavior
|
||||
// (EGL_NO_RESET_NOTIFICATION with no context current) and whether it asked for
|
||||
// robust buffer access.
|
||||
EGLint GetCurrentContextResetNotificationStrategy() const;
|
||||
Bool IsCurrentContextRobustAccessRequested() const;
|
||||
|
||||
// P14 S1. The wire identity of a context, and of the context the CALLING THREAD
|
||||
// has current (0 when it has none - the release edge's value, and the value a
|
||||
@@ -265,7 +270,15 @@ namespace MobileGL {
|
||||
EGLint MinorVersion = 0;
|
||||
EGLint OpenGLProfileMask = 0;
|
||||
EGLint EGLContextFlags = 0;
|
||||
// GL_CONTEXT_FLAGS as requested, without GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT: that
|
||||
// one is reported only where robust buffer access is actually provided, which
|
||||
// the GL layer decides (RobustAccessRequested below is the request).
|
||||
EGLint OpenGLContextFlags = 0;
|
||||
// EGL_EXT_create_context_robustness / EGL 1.5. EGL_NO_RESET_NOTIFICATION or
|
||||
// EGL_LOSE_CONTEXT_ON_RESET; contexts that share must agree on it.
|
||||
EGLint ResetNotificationStrategy = EGL_NO_RESET_NOTIFICATION;
|
||||
// EGL_CONTEXT_OPENGL_ROBUST_ACCESS(_EXT) or the KHR flag bit was given as true.
|
||||
Bool RobustAccessRequested = false;
|
||||
};
|
||||
|
||||
struct SurfaceObject {
|
||||
|
||||
@@ -115,6 +115,13 @@ namespace MobileGL {
|
||||
m_contextLostReported = true;
|
||||
return true;
|
||||
}
|
||||
// True the first time glGetGraphicsResetStatus reports this context's reset (the
|
||||
// one time it names the cause in the log).
|
||||
Bool TakeResetStatusReport() {
|
||||
if (m_resetStatusReported) return false;
|
||||
m_resetStatusReported = true;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Error
|
||||
@@ -743,6 +750,7 @@ namespace MobileGL {
|
||||
// See WireEpoch(); taken when the context is constructed.
|
||||
Uint64 m_wireEpoch = CurrentWireEpoch();
|
||||
Bool m_contextLostReported = false;
|
||||
Bool m_resetStatusReported = false;
|
||||
#endif
|
||||
};
|
||||
} // namespace GLState
|
||||
|
||||
@@ -344,3 +344,92 @@ TEST(EGLStateDmaBufImport, FormatAndModifierQueries) {
|
||||
EXPECT_FALSE(fixture->State.QueryDmaBufFormats(fixture->Display, true, 0, nullptr, &count));
|
||||
EXPECT_EQ(fixture->State.ConsumeError(), EGL_NOT_INITIALIZED);
|
||||
}
|
||||
|
||||
// EGL_EXT_create_context_robustness / EGL 1.5: the reset notification behavior a context is
|
||||
// created with, under both names of the attribute (the extension's, which Chrome, KWin and Qt
|
||||
// pass, and EGL 1.5's, which ANGLE's GL-on-EGL backend passes for its own native context), the
|
||||
// errors the extension defines, and robust buffer access recorded as a request only.
|
||||
TEST(EGLStateRobustness, ResetNotificationStrategyIsRecordedValidatedAndMatchedAcrossShares) {
|
||||
auto fixture = CreateFixture();
|
||||
auto& state = fixture->State;
|
||||
auto strategyOf = [&](StateContext::EGLContextHandle context) {
|
||||
EXPECT_TRUE(state.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, context));
|
||||
const EGLint strategy = state.GetCurrentContextResetNotificationStrategy();
|
||||
EXPECT_TRUE(state.MakeCurrent(fixture->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
|
||||
return strategy;
|
||||
};
|
||||
|
||||
// The default, and no context current at all.
|
||||
EXPECT_EQ(state.GetCurrentContextResetNotificationStrategy(), EGL_NO_RESET_NOTIFICATION);
|
||||
EXPECT_EQ(strategyOf(fixture->Context), EGL_NO_RESET_NOTIFICATION);
|
||||
|
||||
const EGLint loseExt[] = {EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_LOSE_CONTEXT_ON_RESET_EXT,
|
||||
EGL_NONE};
|
||||
const auto robustExt = state.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, loseExt);
|
||||
ASSERT_NE(robustExt, EGL_NO_CONTEXT);
|
||||
EXPECT_EQ(strategyOf(robustExt), EGL_LOSE_CONTEXT_ON_RESET);
|
||||
|
||||
// ANGLE's shape: an ES 3.2 request with the EGL 1.5 name.
|
||||
const EGLint loseCore[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 2,
|
||||
EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY, EGL_LOSE_CONTEXT_ON_RESET, EGL_NONE};
|
||||
const auto robustCore = state.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, loseCore);
|
||||
ASSERT_NE(robustCore, EGL_NO_CONTEXT);
|
||||
EXPECT_EQ(strategyOf(robustCore), EGL_LOSE_CONTEXT_ON_RESET);
|
||||
|
||||
const EGLint noReset[] = {EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_NO_RESET_NOTIFICATION_EXT,
|
||||
EGL_NONE};
|
||||
const auto plain = state.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, noReset);
|
||||
ASSERT_NE(plain, EGL_NO_CONTEXT);
|
||||
EXPECT_EQ(strategyOf(plain), EGL_NO_RESET_NOTIFICATION);
|
||||
|
||||
// A value that is neither: EGL_BAD_ATTRIBUTE, for both names.
|
||||
const EGLint badExt[] = {EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_TRUE, EGL_NONE};
|
||||
EXPECT_EQ(state.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, badExt), EGL_NO_CONTEXT);
|
||||
EXPECT_EQ(state.ConsumeError(), EGL_BAD_ATTRIBUTE);
|
||||
const EGLint badCore[] = {EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY, 0, EGL_NONE};
|
||||
EXPECT_EQ(state.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, badCore), EGL_NO_CONTEXT);
|
||||
EXPECT_EQ(state.ConsumeError(), EGL_BAD_ATTRIBUTE);
|
||||
|
||||
// Sharing: the two must agree (EGL_BAD_MATCH), in both directions; agreeing shares are made.
|
||||
EXPECT_EQ(state.CreateContext(fixture->Display, fixture->Config, robustExt, nullptr), EGL_NO_CONTEXT);
|
||||
EXPECT_EQ(state.ConsumeError(), EGL_BAD_MATCH);
|
||||
EXPECT_EQ(state.CreateContext(fixture->Display, fixture->Config, fixture->Context, loseExt), EGL_NO_CONTEXT);
|
||||
EXPECT_EQ(state.ConsumeError(), EGL_BAD_MATCH);
|
||||
const auto sharedRobust = state.CreateContext(fixture->Display, fixture->Config, robustExt, loseCore);
|
||||
ASSERT_NE(sharedRobust, EGL_NO_CONTEXT);
|
||||
EXPECT_EQ(state.GetContextShareGroupToken(sharedRobust), state.GetContextShareGroupToken(robustExt));
|
||||
EXPECT_EQ(strategyOf(sharedRobust), EGL_LOSE_CONTEXT_ON_RESET);
|
||||
const auto sharedPlain = state.CreateContext(fixture->Display, fixture->Config, fixture->Context, noReset);
|
||||
EXPECT_NE(sharedPlain, EGL_NO_CONTEXT);
|
||||
}
|
||||
|
||||
TEST(EGLStateRobustness, RobustAccessIsARequestAndNeverAGLContextFlagByItself) {
|
||||
auto fixture = CreateFixture();
|
||||
auto& state = fixture->State;
|
||||
auto requestOf = [&](const EGLint* attribs, EGLint* flags) {
|
||||
const auto context = state.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, attribs);
|
||||
EXPECT_NE(context, EGL_NO_CONTEXT);
|
||||
EXPECT_TRUE(state.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, context));
|
||||
const bool requested = state.IsCurrentContextRobustAccessRequested();
|
||||
*flags = state.GetCurrentContextFlags();
|
||||
EXPECT_TRUE(state.MakeCurrent(fixture->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
|
||||
return requested;
|
||||
};
|
||||
EGLint flags = 0;
|
||||
const EGLint none[] = {EGL_NONE};
|
||||
EXPECT_FALSE(requestOf(none, &flags));
|
||||
const EGLint ext[] = {EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_TRUE, EGL_NONE};
|
||||
EXPECT_TRUE(requestOf(ext, &flags));
|
||||
EXPECT_EQ(flags & GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT, 0) << "GL_CONTEXT_FLAGS claims robust access";
|
||||
const EGLint core[] = {EGL_CONTEXT_OPENGL_ROBUST_ACCESS, EGL_TRUE, EGL_NONE};
|
||||
EXPECT_TRUE(requestOf(core, &flags));
|
||||
EXPECT_EQ(flags & GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT, 0);
|
||||
// KWin's desktop candidate: the KHR flag bit, next to the debug bit that IS a GL flag.
|
||||
const EGLint khr[] = {EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR | EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
|
||||
EGL_NONE};
|
||||
EXPECT_TRUE(requestOf(khr, &flags));
|
||||
EXPECT_EQ(flags & GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT, 0);
|
||||
EXPECT_NE(flags & GL_CONTEXT_FLAG_DEBUG_BIT, 0);
|
||||
const EGLint off[] = {EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_FALSE, EGL_NONE};
|
||||
EXPECT_FALSE(requestOf(off, &flags));
|
||||
}
|
||||
|
||||
@@ -661,6 +661,30 @@ TEST_F(ProgramTest, CompileAndLink) {
|
||||
GetUniformiv(program, locInt, &intVal);
|
||||
EXPECT_EQ(intVal, 114514);
|
||||
|
||||
// glGetnUniform* (GL 4.5 / KHR_robustness): the same answers, bounded by bufSize. A
|
||||
// destination too small for the uniform's components is GL_INVALID_OPERATION and untouched.
|
||||
while (GetError() != GL_NO_ERROR) {
|
||||
}
|
||||
float boundedRed[4] = {-1.0f, -1.0f, -1.0f, -1.0f};
|
||||
GetnUniformfv(program, locRed, static_cast<GLsizei>(3 * sizeof(float)), boundedRed);
|
||||
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_NO_ERROR));
|
||||
EXPECT_EQ(boundedRed[0], 1.0f);
|
||||
EXPECT_EQ(boundedRed[2], 5.0f);
|
||||
EXPECT_EQ(boundedRed[3], -1.0f) << "glGetnUniformfv wrote past the uniform";
|
||||
float tooSmall[3] = {-1.0f, -1.0f, -1.0f};
|
||||
GetnUniformfv(program, locRed, static_cast<GLsizei>(2 * sizeof(float)), tooSmall);
|
||||
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_INVALID_OPERATION));
|
||||
EXPECT_EQ(tooSmall[0], -1.0f) << "a refused glGetnUniformfv wrote anyway";
|
||||
int boundedInt = 0;
|
||||
GetnUniformiv(program, locInt, static_cast<GLsizei>(sizeof(int)), &boundedInt);
|
||||
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_NO_ERROR));
|
||||
EXPECT_EQ(boundedInt, 114514);
|
||||
GetnUniformiv(program, locInt, -1, &boundedInt);
|
||||
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_INVALID_OPERATION));
|
||||
// Every other error is the unbounded query's: a location the program does not have.
|
||||
GetnUniformfv(program, 4096, static_cast<GLsizei>(sizeof(boundedRed)), boundedRed);
|
||||
EXPECT_EQ(GetError(), static_cast<GLenum>(GL_INVALID_OPERATION));
|
||||
|
||||
auto programObj = MG_State::pGLContext->GetProgramObject(program);
|
||||
auto& shaderSpirvs = programObj->GetGeneratedSpirv();
|
||||
for (int index = 0; index < shaderSpirvs.size(); ++index) {
|
||||
|
||||
@@ -14,8 +14,11 @@
|
||||
//
|
||||
// What an application that handles context loss (Chrome through ANGLE) needs, each case asserting
|
||||
// it from the application's side:
|
||||
// * the loss is REPORTED: glGetGraphicsResetStatus answers a reset, glGetError GL_CONTEXT_LOST
|
||||
// (once), eglSwapBuffers and eglMakeCurrent on the old context fail with EGL_CONTEXT_LOST;
|
||||
// * the loss is REPORTED to a context that asked to be told (EGL_EXT_create_context_robustness,
|
||||
// EGL_LOSE_CONTEXT_ON_RESET - advertised, and what ANGLE asks for): glGetGraphicsResetStatus
|
||||
// answers a reset and keeps answering it, glGetError GL_CONTEXT_LOST (once), eglSwapBuffers
|
||||
// and eglMakeCurrent on the old context fail with EGL_CONTEXT_LOST; a context that did not ask
|
||||
// is told through GL only where semantics are relaxed, and through EGL everywhere;
|
||||
// * the old context is SAFE TO USE AND TO DESTROY: a draw and a texture upload on it after the
|
||||
// loss do nothing, and destroying it puts nothing on the new session's wire;
|
||||
// * a context created after the loss gets a FRESH session (the server starts session #2, nothing
|
||||
@@ -41,6 +44,11 @@
|
||||
#include <mutex>
|
||||
|
||||
extern "C" {
|
||||
const GLubyte* glGetString(GLenum name);
|
||||
const GLubyte* glGetStringi(GLenum name, GLuint index);
|
||||
void glGetIntegerv(GLenum pname, GLint* data);
|
||||
void glGetnUniformfv(GLuint program, GLint location, GLsizei bufSize, GLfloat* params);
|
||||
void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
|
||||
void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
|
||||
void glClear(GLbitfield mask);
|
||||
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
|
||||
@@ -130,9 +138,25 @@ namespace {
|
||||
Uint32 resetStatusEnd = 0xFFFFFFFFu;
|
||||
Uint32 serverPidBefore = 0;
|
||||
Uint32 serverPidAfter = 0;
|
||||
// Robustness, as the application sees it: the extensions it checks before asking, the
|
||||
// strategy its contexts report, the status again (it stays) and through the KHR name.
|
||||
Int32 eglRobustnessAdvertised = -1;
|
||||
Int32 glRobustnessAdvertised = -1;
|
||||
Int32 glRobustnessIndexed = -1;
|
||||
Int32 strategyBefore = 0;
|
||||
Int32 robustAccess = -1;
|
||||
Uint32 resetStatusRepeat = 0;
|
||||
Uint32 resetStatusKhr = 0;
|
||||
Int32 strategyNew = 0;
|
||||
char note[256] = {};
|
||||
};
|
||||
|
||||
Bool HasToken(const char* list, const char* token) {
|
||||
if (list == nullptr) return false;
|
||||
const std::string all = std::string(" ") + list + " ";
|
||||
return all.find(std::string(" ") + token + " ") != std::string::npos;
|
||||
}
|
||||
|
||||
void Note(RecoveryReport& r, const char* text) {
|
||||
if (r.note[0] == '\0') std::snprintf(r.note, sizeof(r.note), "%s", text);
|
||||
}
|
||||
@@ -233,10 +257,32 @@ namespace {
|
||||
return session != nullptr ? session->LastPublishedSeq() : 0;
|
||||
}
|
||||
|
||||
// The contexts an application that handles loss creates: a 3.3 context that asks to be told of
|
||||
// a reset (EGL_EXT_create_context_robustness - what Chrome, KWin and Qt pass when the display
|
||||
// has the extension). Contexts that share must agree on it.
|
||||
const EGLint kRobustAttribs[] = {EGL_CONTEXT_MAJOR_VERSION,
|
||||
3,
|
||||
EGL_CONTEXT_MINOR_VERSION,
|
||||
3,
|
||||
EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT,
|
||||
EGL_LOSE_CONTEXT_ON_RESET_EXT,
|
||||
EGL_NONE};
|
||||
// ANGLE's GL-on-EGL backend's own native context: the ES API, the highest ES version it tries
|
||||
// first, and EGL 1.5's name of the attribute.
|
||||
const EGLint kAngleNativeAttribs[] = {EGL_CONTEXT_MAJOR_VERSION,
|
||||
3,
|
||||
EGL_CONTEXT_MINOR_VERSION,
|
||||
2,
|
||||
EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY,
|
||||
EGL_LOSE_CONTEXT_ON_RESET,
|
||||
EGL_NONE};
|
||||
|
||||
struct Egl {
|
||||
EGLDisplay dpy = EGL_NO_DISPLAY;
|
||||
EGLConfig config = nullptr;
|
||||
EGLSurface surface = EGL_NO_SURFACE;
|
||||
EGLenum api = EGL_OPENGL_API;
|
||||
const EGLint* contextAttribs = kRobustAttribs;
|
||||
|
||||
Bool Bring(RecoveryReport& r) {
|
||||
dpy = EGL::GetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
@@ -244,7 +290,7 @@ namespace {
|
||||
EGLint minor = 0;
|
||||
if (dpy == EGL_NO_DISPLAY || EGL::Initialize(dpy, &major, &minor) != EGL_TRUE)
|
||||
return Note(r, "eglInitialize failed (no spawn session?)"), false;
|
||||
(void)EGL::BindAPI(EGL_OPENGL_API);
|
||||
(void)EGL::BindAPI(api);
|
||||
const EGLint configAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_NONE};
|
||||
EGLint count = 0;
|
||||
if (EGL::ChooseConfig(dpy, configAttribs, &config, 1, &count) != EGL_TRUE || count < 1)
|
||||
@@ -255,21 +301,42 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
EGLContext Context() const {
|
||||
const EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_NONE};
|
||||
return EGL::CreateContext(dpy, config, EGL_NO_CONTEXT, attribs);
|
||||
}
|
||||
EGLContext Context() const { return EGL::CreateContext(dpy, config, EGL_NO_CONTEXT, contextAttribs); }
|
||||
};
|
||||
|
||||
// The robustness answers of the current context, before the loss.
|
||||
void NoteRobustness(Egl& egl, RecoveryReport& r) {
|
||||
r.eglRobustnessAdvertised =
|
||||
HasToken(EGL::QueryString(egl.dpy, EGL_EXTENSIONS), "EGL_EXT_create_context_robustness") ? 1 : 0;
|
||||
r.glRobustnessAdvertised =
|
||||
HasToken(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS)), "GL_KHR_robustness") ? 1 : 0;
|
||||
GLint count = 0;
|
||||
glGetIntegerv(GL_NUM_EXTENSIONS, &count);
|
||||
r.glRobustnessIndexed = 0;
|
||||
for (GLint i = 0; i < count; ++i) {
|
||||
const auto* name = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(i)));
|
||||
if (name != nullptr && std::string(name) == "GL_KHR_robustness") r.glRobustnessIndexed = 1;
|
||||
}
|
||||
glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY, &r.strategyBefore);
|
||||
GLint robust = -1;
|
||||
glGetIntegerv(GL_CONTEXT_ROBUST_ACCESS, &robust);
|
||||
r.robustAccess = robust;
|
||||
}
|
||||
|
||||
// The client, in a fork: everything an application does around a loss, through the entry points.
|
||||
void RunRecoveringClient(Route route, RecoveryReport& r) {
|
||||
// `api` and `attribs` are the contexts' shape (the first and every replacement).
|
||||
void RunRecoveringClient(Route route, RecoveryReport& r, EGLenum api = EGL_OPENGL_API,
|
||||
const EGLint* attribs = kRobustAttribs) {
|
||||
Egl egl;
|
||||
egl.api = api;
|
||||
egl.contextAttribs = attribs;
|
||||
if (!egl.Bring(r)) return;
|
||||
const EGLContext lost = egl.Context();
|
||||
if (lost == EGL_NO_CONTEXT || EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, lost) != EGL_TRUE)
|
||||
return Note(r, "the first context did not go current");
|
||||
r.initialized = 1;
|
||||
r.serverPidBefore = ServerPid();
|
||||
NoteRobustness(egl, r);
|
||||
|
||||
// The objects of the context that is about to be lost, and device check #1 in the server
|
||||
// process: a textured draw, read back.
|
||||
@@ -285,6 +352,10 @@ namespace {
|
||||
r.resetStatusLost = glGetGraphicsResetStatus();
|
||||
r.errorLost = glGetError();
|
||||
r.errorAfterLost = glGetError();
|
||||
r.resetStatusRepeat = glGetGraphicsResetStatus();
|
||||
using ResetStatusFn = GLenum (*)(void);
|
||||
if (auto khr = reinterpret_cast<ResetStatusFn>(EGL::GetProcAddress("glGetGraphicsResetStatusKHR")))
|
||||
r.resetStatusKhr = khr();
|
||||
// What an application does before it has looked: more work on the lost context - a draw,
|
||||
// and then an upload that no later verb of the lost context will ever carry (it is owed to
|
||||
// a server that is gone). None of it may surface on the fresh session.
|
||||
@@ -318,6 +389,8 @@ namespace {
|
||||
(void)EGL::DestroySurface(egl.dpy, egl.surface);
|
||||
(void)EGL::Terminate(egl.dpy);
|
||||
Egl again;
|
||||
again.api = egl.api;
|
||||
again.contextAttribs = egl.contextAttribs;
|
||||
if (!again.Bring(r)) return;
|
||||
egl = again;
|
||||
NoteTextureBooks(oldHandle, &r.oldTexturePublishedAfter, &r.oldTextureOwedAfter);
|
||||
@@ -331,6 +404,7 @@ namespace {
|
||||
if (!r.newCurrentOk) return Note(r, "the new context did not go current");
|
||||
r.serverPidAfter = ServerPid();
|
||||
r.resetStatusNew = glGetGraphicsResetStatus();
|
||||
glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY, &r.strategyNew);
|
||||
r.oldNameIsTexture = glIsTexture(old.texture) == GL_TRUE ? 1 : 0;
|
||||
|
||||
// THE NEW CONTEXT RENDERS: the same textured draw with a new program and texture, a clear of
|
||||
@@ -384,7 +458,7 @@ namespace {
|
||||
Egl egl;
|
||||
RecoveryReport bring{};
|
||||
if (!egl.Bring(bring)) return Note(r, bring.note);
|
||||
const EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_NONE};
|
||||
const EGLint* attribs = kRobustAttribs;
|
||||
const EGLContext share = egl.Context();
|
||||
const EGLContext working = EGL::CreateContext(egl.dpy, egl.config, share, attribs);
|
||||
if (working == EGL_NO_CONTEXT || EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, working) != EGL_TRUE)
|
||||
@@ -562,6 +636,95 @@ namespace {
|
||||
(void)EGL::Terminate(egl.dpy);
|
||||
}
|
||||
|
||||
// NO RESET NOTIFICATION. Two contexts of one session that did not ask to be told of a reset,
|
||||
// each current on its own thread when the session is lost: an explicit core-profile one, which
|
||||
// gets KHR_robustness' NO_RESET_NOTIFICATION behavior exactly (no reset status, no
|
||||
// GL_CONTEXT_LOST), and a compatibility-profile one, which is told all the same (the relaxed
|
||||
// default). Both still learn it from EGL - a swap and a make-current fail with EGL_CONTEXT_LOST -
|
||||
// and a context created afterwards renders on a fresh session.
|
||||
const EGLint kCoreNoResetAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_NONE};
|
||||
const EGLint kCompatNoResetAttribs[] = {EGL_CONTEXT_MAJOR_VERSION,
|
||||
3,
|
||||
EGL_CONTEXT_MINOR_VERSION,
|
||||
3,
|
||||
EGL_CONTEXT_OPENGL_PROFILE_MASK,
|
||||
EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT,
|
||||
EGL_NONE};
|
||||
|
||||
struct UntoldReport {
|
||||
Int32 initialized = 0;
|
||||
Uint32 firstPixel = 0;
|
||||
Int32 lossSeen = 0;
|
||||
Int32 coreStrategy = 0;
|
||||
Uint32 coreStatus = 0xFFFFFFFFu;
|
||||
Uint32 coreError = 0xFFFFFFFFu;
|
||||
Int32 coreSwap = -1;
|
||||
Int32 coreSwapError = 0;
|
||||
Uint32 compatStatus = 0;
|
||||
Uint32 compatError = 0;
|
||||
Int32 coreAgain = -1;
|
||||
Int32 coreAgainError = 0;
|
||||
Int32 freshCurrent = 0;
|
||||
Uint32 freshStatus = 0xFFFFFFFFu;
|
||||
Uint32 freshPixel = 0;
|
||||
char note[256] = {};
|
||||
};
|
||||
|
||||
void Note(UntoldReport& r, const char* text) {
|
||||
if (r.note[0] == '\0') std::snprintf(r.note, sizeof(r.note), "%s", text);
|
||||
}
|
||||
|
||||
void RunUntoldClient(UntoldReport& r) {
|
||||
Egl egl;
|
||||
egl.contextAttribs = kCoreNoResetAttribs;
|
||||
RecoveryReport bring{};
|
||||
if (!egl.Bring(bring)) return Note(r, bring.note);
|
||||
const EGLint pbufferAttribs[] = {EGL_WIDTH, 8, EGL_HEIGHT, 8, EGL_NONE};
|
||||
const EGLSurface other = EGL::CreatePbufferSurface(egl.dpy, egl.config, pbufferAttribs);
|
||||
const EGLContext core = egl.Context();
|
||||
const EGLContext compat = EGL::CreateContext(egl.dpy, egl.config, EGL_NO_CONTEXT, kCompatNoResetAttribs);
|
||||
if (core == EGL_NO_CONTEXT || compat == EGL_NO_CONTEXT || other == EGL_NO_SURFACE)
|
||||
return Note(r, "the contexts or the second surface were not created");
|
||||
Worker second;
|
||||
Bool compatCurrent = false;
|
||||
second.Run([&] { compatCurrent = EGL::MakeCurrent(egl.dpy, other, other, compat) == EGL_TRUE; });
|
||||
if (!compatCurrent || EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, core) != EGL_TRUE)
|
||||
return Note(r, "the contexts did not go current");
|
||||
r.initialized = 1;
|
||||
glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY, &r.coreStrategy);
|
||||
r.firstPixel = ClearAndRead(0.0f, 1.0f, 0.0f); // device check #1
|
||||
(void)ClearAndRead(0.0f, 0.0f, 1.0f); // device check #2: lost
|
||||
r.lossSeen = WaitFor([] { return Remote::Client::ClientSession::SessionLatchedLost(); }, 5000) ? 1 : 0;
|
||||
|
||||
r.coreStatus = glGetGraphicsResetStatus();
|
||||
r.coreError = glGetError();
|
||||
second.Run([&] {
|
||||
r.compatStatus = glGetGraphicsResetStatus();
|
||||
r.compatError = glGetError();
|
||||
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
});
|
||||
r.coreSwap = EGL::SwapBuffers(egl.dpy, egl.surface);
|
||||
r.coreSwapError = EGL::GetError();
|
||||
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
r.coreAgain = EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, core);
|
||||
r.coreAgainError = EGL::GetError();
|
||||
if (r.coreAgain == EGL_TRUE) (void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
|
||||
const EGLContext fresh = egl.Context();
|
||||
r.freshCurrent = fresh != EGL_NO_CONTEXT && EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, fresh) == EGL_TRUE;
|
||||
if (!r.freshCurrent) return Note(r, "no fresh context went current");
|
||||
r.freshStatus = glGetGraphicsResetStatus();
|
||||
r.freshPixel = ClearAndRead(0.0f, 0.0f, 1.0f);
|
||||
|
||||
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(egl.dpy, fresh);
|
||||
(void)EGL::DestroyContext(egl.dpy, compat);
|
||||
(void)EGL::DestroyContext(egl.dpy, core);
|
||||
(void)EGL::DestroySurface(egl.dpy, other);
|
||||
(void)EGL::DestroySurface(egl.dpy, egl.surface);
|
||||
(void)EGL::Terminate(egl.dpy);
|
||||
}
|
||||
|
||||
// Runs `body` in a forked client (the environment a deployment sets, then the entry points)
|
||||
// and hands back the report it filled.
|
||||
template <class Report, class Body>
|
||||
@@ -607,9 +770,163 @@ namespace {
|
||||
return report;
|
||||
}
|
||||
|
||||
RecoveryReport RunClient(const ServerProcess& server, Route route, const std::string& clientLogBase) {
|
||||
return RunInClient<RecoveryReport>(server, clientLogBase,
|
||||
[route](RecoveryReport& r) { RunRecoveringClient(route, r); });
|
||||
// THE SESSION-TARGETED KNOB (MOBILEGL_DEBUG_INJECT_DEVICE_LOST_PID): one client of several is
|
||||
// lost, whenever its session drains, whether or not it ever presents or reads back. Its pid
|
||||
// has to be known before the server starts (the knob is the server's environment), so it is
|
||||
// forked first and held until the server listens; the endpoint is handed to it then.
|
||||
struct TargetedReport {
|
||||
Int32 initialized = 0;
|
||||
Int32 lossSeen = 0;
|
||||
Uint32 resetStatus = 0;
|
||||
Int32 freshCurrent = 0;
|
||||
Uint32 freshPixel = 0;
|
||||
Int32 freshFrames = 0;
|
||||
Uint32 freshStatusEnd = 0xFFFFFFFFu;
|
||||
char note[256] = {};
|
||||
};
|
||||
|
||||
void Note(TargetedReport& r, const char* text) {
|
||||
if (r.note[0] == '\0') std::snprintf(r.note, sizeof(r.note), "%s", text);
|
||||
}
|
||||
|
||||
// Draws until its context reports the reset, recovers on a new context, then keeps drawing
|
||||
// for half a second - long enough for the knob to be asked again - and must not be lost again.
|
||||
void RunTargetedClient(TargetedReport& r) {
|
||||
Egl egl;
|
||||
RecoveryReport bring{};
|
||||
if (!egl.Bring(bring)) return Note(r, bring.note);
|
||||
const EGLContext lost = egl.Context();
|
||||
if (lost == EGL_NO_CONTEXT || EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, lost) != EGL_TRUE)
|
||||
return Note(r, "the first context did not go current");
|
||||
r.initialized = 1;
|
||||
r.lossSeen = WaitFor(
|
||||
[] {
|
||||
(void)ClearAndRead(0.0f, 1.0f, 0.0f);
|
||||
return glGetGraphicsResetStatus() != GL_NO_ERROR;
|
||||
},
|
||||
10000)
|
||||
? 1
|
||||
: 0;
|
||||
r.resetStatus = glGetGraphicsResetStatus();
|
||||
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
const EGLContext fresh = egl.Context();
|
||||
(void)EGL::DestroyContext(egl.dpy, lost);
|
||||
r.freshCurrent = fresh != EGL_NO_CONTEXT && EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, fresh) == EGL_TRUE;
|
||||
if (!r.freshCurrent) return Note(r, "no fresh context went current");
|
||||
const auto until = std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
|
||||
while (std::chrono::steady_clock::now() < until) {
|
||||
r.freshPixel = ClearAndRead(0.0f, 0.0f, 1.0f);
|
||||
++r.freshFrames;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
r.freshStatusEnd = glGetGraphicsResetStatus();
|
||||
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(egl.dpy, fresh);
|
||||
(void)EGL::DestroySurface(egl.dpy, egl.surface);
|
||||
(void)EGL::Terminate(egl.dpy);
|
||||
}
|
||||
|
||||
// A client of the same server that is not the target: half a second of read-backs, untouched.
|
||||
struct BystanderReport {
|
||||
Int32 initialized = 0;
|
||||
Int32 frames = 0;
|
||||
Uint32 lastPixel = 0;
|
||||
Uint32 resetStatus = 0xFFFFFFFFu;
|
||||
char note[256] = {};
|
||||
};
|
||||
|
||||
void Note(BystanderReport& r, const char* text) {
|
||||
if (r.note[0] == '\0') std::snprintf(r.note, sizeof(r.note), "%s", text);
|
||||
}
|
||||
|
||||
void RunBystanderClient(BystanderReport& r) {
|
||||
Egl egl;
|
||||
RecoveryReport bring{};
|
||||
if (!egl.Bring(bring)) return Note(r, bring.note);
|
||||
const EGLContext context = egl.Context();
|
||||
if (context == EGL_NO_CONTEXT || EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, context) != EGL_TRUE)
|
||||
return Note(r, "the context did not go current");
|
||||
r.initialized = 1;
|
||||
const auto until = std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
|
||||
while (std::chrono::steady_clock::now() < until) {
|
||||
r.lastPixel = ClearAndRead(1.0f, 0.0f, 0.0f);
|
||||
++r.frames;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
r.resetStatus = glGetGraphicsResetStatus();
|
||||
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(egl.dpy, context);
|
||||
(void)EGL::DestroySurface(egl.dpy, egl.surface);
|
||||
(void)EGL::Terminate(egl.dpy);
|
||||
}
|
||||
|
||||
// A forked client that waits for the endpoint before it does anything.
|
||||
template <class Report, class Body>
|
||||
struct HeldClient {
|
||||
pid_t pid = -1;
|
||||
int go = -1;
|
||||
int report = -1;
|
||||
|
||||
Bool Fork(const std::string& clientLogBase, Body body) {
|
||||
int goFds[2] = {-1, -1};
|
||||
int reportFds[2] = {-1, -1};
|
||||
if (::pipe(goFds) != 0 || ::pipe(reportFds) != 0) return false;
|
||||
std::fflush(nullptr);
|
||||
pid = ::fork();
|
||||
if (pid == 0) {
|
||||
::close(goFds[1]);
|
||||
::close(reportFds[0]);
|
||||
char endpoint[512] = {};
|
||||
const ssize_t got = ::read(goFds[0], endpoint, sizeof(endpoint) - 1);
|
||||
if (got <= 0) ::_exit(0);
|
||||
::setenv("MOBILEGL_LOG_FILE_PATH", clientLogBase.c_str(), 1);
|
||||
::setenv("MOBILEGL_TRANSPORT", "spawn", 1);
|
||||
::setenv("MOBILEGL_IPC_CONTROL", endpoint, 1);
|
||||
::unsetenv("MOBILEGL_IPC_SURFACE");
|
||||
::unsetenv("MOBILEGL_BACKEND_TYPE");
|
||||
::unsetenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_AT");
|
||||
::unsetenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_PID");
|
||||
Report r{};
|
||||
body(r);
|
||||
const ssize_t wrote = ::write(reportFds[1], &r, sizeof(r));
|
||||
(void)wrote;
|
||||
std::fflush(nullptr);
|
||||
::_exit(0);
|
||||
}
|
||||
::close(goFds[0]);
|
||||
::close(reportFds[1]);
|
||||
go = goFds[1];
|
||||
report = reportFds[0];
|
||||
return pid > 0;
|
||||
}
|
||||
|
||||
Report Release(const std::string& endpoint) {
|
||||
Report r{};
|
||||
const ssize_t wrote = ::write(go, endpoint.c_str(), endpoint.size());
|
||||
(void)wrote;
|
||||
::close(go);
|
||||
pollfd pfd{report, POLLIN, 0};
|
||||
if (::poll(&pfd, 1, 90000) > 0) {
|
||||
if (::read(report, &r, sizeof(r)) != static_cast<ssize_t>(sizeof(r))) {
|
||||
r = Report{};
|
||||
Note(r, "the client died before reporting");
|
||||
}
|
||||
} else {
|
||||
Note(r, "the client timed out");
|
||||
::kill(pid, SIGKILL);
|
||||
}
|
||||
::close(report);
|
||||
int status = 0;
|
||||
::waitpid(pid, &status, 0);
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
RecoveryReport RunClient(const ServerProcess& server, Route route, const std::string& clientLogBase,
|
||||
EGLenum api = EGL_OPENGL_API, const EGLint* attribs = kRobustAttribs) {
|
||||
return RunInClient<RecoveryReport>(server, clientLogBase, [route, api, attribs](RecoveryReport& r) {
|
||||
RunRecoveringClient(route, r, api, attribs);
|
||||
});
|
||||
}
|
||||
|
||||
struct ClientLog {
|
||||
@@ -646,6 +963,15 @@ namespace {
|
||||
ASSERT_EQ(r.firstPixel, kGreen) << "the first draw (device check #1) did not read back: " << std::hex
|
||||
<< r.firstPixel << "\n" << r.note << "\n" << diagnostics;
|
||||
EXPECT_NE(r.lostPixel, kBlue) << "a read-back on the lost device returned a frame";
|
||||
// What the application checked before it asked to be told (ANGLE mirrors the EGL
|
||||
// extension onto its own display; Chrome checks that and GL_*_robustness), and what
|
||||
// its context then reports. Robust buffer ACCESS was not asked for and is not claimed.
|
||||
EXPECT_EQ(r.eglRobustnessAdvertised, 1) << "EGL_EXT_create_context_robustness is not advertised";
|
||||
EXPECT_EQ(r.glRobustnessAdvertised, 1) << "GL_KHR_robustness is not in glGetString(GL_EXTENSIONS)";
|
||||
EXPECT_EQ(r.glRobustnessIndexed, 1) << "GL_KHR_robustness is not in glGetStringi(GL_EXTENSIONS, i)";
|
||||
EXPECT_EQ(r.strategyBefore, GL_LOSE_CONTEXT_ON_RESET) << "GL_RESET_NOTIFICATION_STRATEGY: 0x" << std::hex
|
||||
<< r.strategyBefore;
|
||||
EXPECT_EQ(r.robustAccess, GL_FALSE);
|
||||
// The loss is reported.
|
||||
EXPECT_TRUE(r.resetStatusLost == GL_UNKNOWN_CONTEXT_RESET || r.resetStatusLost == GL_GUILTY_CONTEXT_RESET)
|
||||
<< "glGetGraphicsResetStatus after the loss: 0x" << std::hex << r.resetStatusLost;
|
||||
@@ -653,6 +979,13 @@ namespace {
|
||||
<< "glGetError after the loss: 0x" << std::hex << r.errorLost;
|
||||
EXPECT_EQ(r.errorAfterLost, static_cast<Uint32>(GL_NO_ERROR))
|
||||
<< "GL_CONTEXT_LOST must be reported once, not forever (a drain loop must end)";
|
||||
// LOSE_CONTEXT_ON_RESET: the context stays lost, so the status does not go back to
|
||||
// GL_NO_ERROR (that would read as a reset that completed in place), and the KHR name
|
||||
// an ES application looks up answers the same.
|
||||
EXPECT_EQ(r.resetStatusRepeat, r.resetStatusLost) << "the reset status of a lost context went back to 0x"
|
||||
<< std::hex << r.resetStatusRepeat;
|
||||
EXPECT_EQ(r.resetStatusKhr, r.resetStatusLost) << "glGetGraphicsResetStatusKHR: 0x" << std::hex
|
||||
<< r.resetStatusKhr;
|
||||
EXPECT_EQ(r.swapLost, EGL_FALSE) << "eglSwapBuffers on the lost context succeeded";
|
||||
EXPECT_EQ(r.swapLostError, EGL_CONTEXT_LOST) << "eglSwapBuffers on the lost context: 0x" << std::hex
|
||||
<< r.swapLostError;
|
||||
@@ -668,6 +1001,7 @@ namespace {
|
||||
<< r.newCurrentError << ")\n" << r.note << "\n" << diagnostics;
|
||||
EXPECT_EQ(r.serverPidAfter, r.serverPidBefore) << "the new session is not on the same server process";
|
||||
EXPECT_EQ(r.resetStatusNew, static_cast<Uint32>(GL_NO_ERROR)) << "the new context reports a reset";
|
||||
EXPECT_EQ(r.strategyNew, GL_LOSE_CONTEXT_ON_RESET) << "the replacement is not told of resets";
|
||||
EXPECT_EQ(r.oldNameIsTexture, 0) << "the old context's texture name resolves in the new context";
|
||||
// The client's books on the lost texture: before the recovery the lost session had its
|
||||
// record (the arm proof that there was something to forget), and the upload made on the
|
||||
@@ -724,6 +1058,51 @@ TEST_F(ClientRecovery, ANewContextAfterADeviceLossRendersOnAFreshSession) {
|
||||
const std::string diagnostics = "server:\n" + server.Log() + "\nclient:\n" + client.Text();
|
||||
ExpectLossReportedAndRecovered(r, diagnostics);
|
||||
EXPECT_EQ(r.recordsByOldDestroy, 0) << "destroying the lost context published records on the new session";
|
||||
// The status is UNKNOWN for a reason, and the client log names it once.
|
||||
EXPECT_EQ(Count(client.Text(), "GL_UNKNOWN_CONTEXT_RESET for a context of a lost session"), 1u) << client.Text();
|
||||
ExpectOneLossAndACleanSecondSession(server);
|
||||
server.Stop();
|
||||
}
|
||||
|
||||
// ANGLE'S OWN NATIVE CONTEXT. ANGLE's GL-on-EGL backend (Chrome's --use-angle=gles) creates one ES
|
||||
// context per display with EGL 1.5's name of the reset attribute, and learns of a reset ONLY through
|
||||
// that context's reset status - which it then hands to Chrome. The same recovery, in its shape.
|
||||
TEST_F(ClientRecovery, AnglesNativeContextShapeIsToldOfTheLossAndRecovers) {
|
||||
ServerProcess server;
|
||||
ASSERT_TRUE(Launch(&server, "angle")) << server.Log();
|
||||
ClientLog client("angle");
|
||||
const RecoveryReport r = RunClient(server, Route::NewContext, client.base, EGL_OPENGL_ES_API, kAngleNativeAttribs);
|
||||
const std::string diagnostics = "server:\n" + server.Log() + "\nclient:\n" + client.Text();
|
||||
ExpectLossReportedAndRecovered(r, diagnostics);
|
||||
ExpectOneLossAndACleanSecondSession(server);
|
||||
server.Stop();
|
||||
}
|
||||
|
||||
// NO RESET NOTIFICATION (RunUntoldClient): the explicit core-profile context is not told through
|
||||
// GL, the compatibility-profile one is; both learn it from EGL, and the application recovers.
|
||||
TEST_F(ClientRecovery, ContextsThatDidNotAskAreToldOnlyWhereRelaxedAndStillRecoverThroughEgl) {
|
||||
ServerProcess server;
|
||||
ASSERT_TRUE(Launch(&server, "untold")) << server.Log();
|
||||
ClientLog client("untold");
|
||||
const UntoldReport r = RunInClient<UntoldReport>(server, client.base, &RunUntoldClient);
|
||||
const std::string diagnostics = "server:\n" + server.Log() + "\nclient:\n" + client.Text();
|
||||
ASSERT_EQ(r.initialized, 1) << r.note << "\n" << diagnostics;
|
||||
ASSERT_EQ(r.firstPixel, kGreen) << std::hex << r.firstPixel << "\n" << diagnostics;
|
||||
ASSERT_EQ(r.lossSeen, 1) << "the injected loss never latched\n" << diagnostics;
|
||||
EXPECT_EQ(r.coreStrategy, GL_NO_RESET_NOTIFICATION);
|
||||
EXPECT_EQ(r.coreStatus, static_cast<Uint32>(GL_NO_ERROR))
|
||||
<< "a NO_RESET_NOTIFICATION core-profile context was told of the reset: 0x" << std::hex << r.coreStatus;
|
||||
EXPECT_NE(r.coreError, static_cast<Uint32>(GL_CONTEXT_LOST));
|
||||
EXPECT_EQ(r.compatStatus, static_cast<Uint32>(GL_UNKNOWN_CONTEXT_RESET))
|
||||
<< "the compatibility-profile context was not told: 0x" << std::hex << r.compatStatus;
|
||||
EXPECT_EQ(r.compatError, static_cast<Uint32>(GL_CONTEXT_LOST));
|
||||
EXPECT_EQ(r.coreSwap, EGL_FALSE);
|
||||
EXPECT_EQ(r.coreSwapError, EGL_CONTEXT_LOST) << std::hex << r.coreSwapError;
|
||||
EXPECT_EQ(r.coreAgain, EGL_FALSE);
|
||||
EXPECT_EQ(r.coreAgainError, EGL_CONTEXT_LOST) << std::hex << r.coreAgainError;
|
||||
ASSERT_EQ(r.freshCurrent, 1) << r.note << "\n" << diagnostics;
|
||||
EXPECT_EQ(r.freshStatus, static_cast<Uint32>(GL_NO_ERROR));
|
||||
EXPECT_EQ(r.freshPixel, kBlue) << std::hex << r.freshPixel << "\n" << diagnostics;
|
||||
ExpectOneLossAndACleanSecondSession(server);
|
||||
server.Stop();
|
||||
}
|
||||
@@ -791,3 +1170,52 @@ TEST_F(ClientRecovery, ContextsNamingALostShareContextShareOneNewGroup) {
|
||||
ExpectOneLossAndACleanSecondSession(server);
|
||||
server.Stop();
|
||||
}
|
||||
|
||||
// THE SESSION-TARGETED KNOB (what the device test sets on a running desktop): a bystander client
|
||||
// of the same server draws on untouched; the target is lost although it only clears and reads
|
||||
// back - no present - recovers on a fresh session, and is not lost again by the same setting.
|
||||
TEST_F(ClientRecovery, ThePidTargetedKnobLosesOnlyThatClientAndOnlyOnce) {
|
||||
auto targetBody = [](TargetedReport& r) { RunTargetedClient(r); };
|
||||
HeldClient<TargetedReport, decltype(targetBody)> target;
|
||||
ClientLog targetLog("pid-target");
|
||||
ASSERT_TRUE(target.Fork(targetLog.base, targetBody));
|
||||
|
||||
ServerProcess server;
|
||||
::setenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_PID", std::to_string(target.pid).c_str(), 1);
|
||||
const bool launched = LaunchUnixInProcessServer(&server, "pid");
|
||||
::unsetenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_PID");
|
||||
if (!launched) {
|
||||
(void)target.Release("");
|
||||
FAIL() << server.Log();
|
||||
}
|
||||
|
||||
ClientLog bystanderLog("pid-bystander");
|
||||
const BystanderReport bystander =
|
||||
RunInClient<BystanderReport>(server, bystanderLog.base, &RunBystanderClient);
|
||||
const TargetedReport r = target.Release(server.endpoint);
|
||||
const std::string diagnostics = "server:\n" + server.Log() + "\ntarget:\n" + targetLog.Text() +
|
||||
"\nbystander:\n" + bystanderLog.Text();
|
||||
|
||||
ASSERT_EQ(bystander.initialized, 1) << bystander.note << "\n" << diagnostics;
|
||||
EXPECT_GT(bystander.frames, 5);
|
||||
EXPECT_EQ(bystander.lastPixel, kRed) << std::hex << bystander.lastPixel;
|
||||
EXPECT_EQ(bystander.resetStatus, static_cast<Uint32>(GL_NO_ERROR)) << "the bystander was lost";
|
||||
|
||||
ASSERT_EQ(r.initialized, 1) << r.note << "\n" << diagnostics;
|
||||
ASSERT_EQ(r.lossSeen, 1) << "the targeted client was never lost\n" << diagnostics;
|
||||
EXPECT_EQ(r.resetStatus, static_cast<Uint32>(GL_UNKNOWN_CONTEXT_RESET)) << std::hex << r.resetStatus;
|
||||
ASSERT_EQ(r.freshCurrent, 1) << r.note << "\n" << diagnostics;
|
||||
EXPECT_GT(r.freshFrames, 5);
|
||||
EXPECT_EQ(r.freshPixel, kBlue) << std::hex << r.freshPixel << "\n" << diagnostics;
|
||||
EXPECT_EQ(r.freshStatusEnd, static_cast<Uint32>(GL_NO_ERROR)) << "the recovered session was lost again";
|
||||
|
||||
// Three sessions: the bystander's and the recovered one end cleanly, the target's latched on
|
||||
// the knob, naming the pid.
|
||||
ASSERT_TRUE(WaitFor([&] { return Count(server.Log(), "reaped exit=") == 3; }, 10000)) << server.Log();
|
||||
const std::string log = server.Log();
|
||||
EXPECT_EQ(Count(log, "SessionLatch{"), 1u) << log;
|
||||
EXPECT_NE(log.find("injected:pid=" + std::to_string(target.pid)), std::string::npos) << log;
|
||||
EXPECT_EQ(Count(log, "reaped exit=75"), 1u) << log;
|
||||
EXPECT_EQ(Count(log, "reaped exit=0"), 2u) << log;
|
||||
server.Stop();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user