mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (EGL): contexts recreated naming a lost share context share one new group instead of none.
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -424,6 +425,36 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return true;
|
||||
}
|
||||
|
||||
// THE SHARE GROUPS THAT REPLACE LOST ONES, by the lost group's token. An application that
|
||||
// keeps one long-lived context to share with (Qt's global share context) recreates its
|
||||
// other contexts after a loss still naming that context, which is lost and stays so. Those
|
||||
// contexts are put in ONE new share group instead of each in a group of its own: the first
|
||||
// starts it, the later ones share with a live member of it. Without this, every window of
|
||||
// such an application lands in a separate share group after the loss, a shape the
|
||||
// application never asked for. Guarded by EGLOperationMutex.
|
||||
std::unordered_map<Uint64, Vector<EGLContext>>& ReplacementShareContexts() {
|
||||
static auto* replacements = new std::unordered_map<Uint64, Vector<EGLContext>>();
|
||||
return *replacements;
|
||||
}
|
||||
|
||||
// The context a new context should share with in place of the lost `shareCtx`: a live
|
||||
// member of the group that replaces the lost one, or EGL_NO_CONTEXT to start that group.
|
||||
EGLContext ReplacementShareContext(EGLStateContext* state, EGLDisplay dpy, Uint64 lostGroup) {
|
||||
Vector<EGLContext>& members = ReplacementShareContexts()[lostGroup];
|
||||
members.erase(std::remove_if(members.begin(), members.end(),
|
||||
[&](EGLContext member) {
|
||||
return !state->ValidateContextOnDisplay(dpy, member) ||
|
||||
ContextIsLost(state, member);
|
||||
}),
|
||||
members.end());
|
||||
return members.empty() ? EGL_NO_CONTEXT : members.front();
|
||||
}
|
||||
|
||||
void ForgetReplacementShareContext(EGLContext context) {
|
||||
for (auto& [group, members] : ReplacementShareContexts())
|
||||
members.erase(std::remove(members.begin(), members.end(), context), members.end());
|
||||
}
|
||||
|
||||
// The surface entry points' guard: a surface created after a loss is created on a fresh
|
||||
// session. EGL_BAD_ALLOC when no session could be brought up.
|
||||
Bool RecoverBeforeCreatingSurface(EGLStateContext* state) {
|
||||
@@ -646,17 +677,26 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
// building it already emits the creates of its default objects. Sharing with a lost
|
||||
// context would share objects no reachable server holds.
|
||||
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||
// The lost share group this context replaces (ReplacementShareContexts), 0 for none.
|
||||
Uint64 replacedGroup = 0;
|
||||
if (StreamGateActive()) {
|
||||
if (!RecoverLostSession()) {
|
||||
state->SetError(EGL_BAD_ALLOC);
|
||||
return EGL_NO_CONTEXT;
|
||||
}
|
||||
if (shareCtx != EGL_NO_CONTEXT && ContextIsLost(state, shareCtx)) {
|
||||
MGLOG_I_ONCE("eglCreateContext: refused (EGL_BAD_CONTEXT) - the share context %p was lost with its "
|
||||
"session (device loss), and its objects are on no server",
|
||||
shareCtx);
|
||||
state->SetError(EGL_BAD_CONTEXT);
|
||||
return EGL_NO_CONTEXT;
|
||||
// Its objects are on no server, so the context does not share them: it joins the
|
||||
// group that replaces the lost one (the other contexts recreated in its place).
|
||||
if (!state->ValidateContextOnDisplay(dpy, shareCtx)) {
|
||||
state->SetError(EGL_BAD_CONTEXT);
|
||||
return EGL_NO_CONTEXT;
|
||||
}
|
||||
replacedGroup = state->GetContextShareGroupToken(shareCtx);
|
||||
const EGLContext lostShare = shareCtx;
|
||||
shareCtx = ReplacementShareContext(state, dpy, replacedGroup);
|
||||
MGLOG_I_ONCE("eglCreateContext: the share context %p was lost with its session (device loss); "
|
||||
"contexts that name it share with each other in a new share group instead",
|
||||
lostShare);
|
||||
}
|
||||
}
|
||||
// The calling thread may still have a lost context current: what is built here is the
|
||||
@@ -667,6 +707,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
if (context == EGL_NO_CONTEXT) {
|
||||
return EGL_NO_CONTEXT;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (replacedGroup != 0) ReplacementShareContexts()[replacedGroup].push_back(context);
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P14 S1 (docs/Disaggregated/design/11-state-ownership.md). THE TOKEN CROSSES BEFORE
|
||||
// eglCreateContext RETURNS. EGLState minted it, together with the share group it was
|
||||
@@ -694,11 +737,19 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
state->DestroyContext(dpy, context);
|
||||
}
|
||||
context = EGL_NO_CONTEXT;
|
||||
if (RecoverLostSession()) context = state->CreateContext(dpy, config, shareCtx, attrib_list);
|
||||
if (RecoverLostSession()) {
|
||||
// The context it was to share with went with the session, too.
|
||||
if (shareCtx != EGL_NO_CONTEXT && ContextIsLost(state, shareCtx)) {
|
||||
replacedGroup = state->GetContextShareGroupToken(shareCtx);
|
||||
shareCtx = ReplacementShareContext(state, dpy, replacedGroup);
|
||||
}
|
||||
context = state->CreateContext(dpy, config, shareCtx, attrib_list);
|
||||
}
|
||||
if (context == EGL_NO_CONTEXT) {
|
||||
state->SetError(EGL_BAD_ALLOC);
|
||||
return EGL_NO_CONTEXT;
|
||||
}
|
||||
if (replacedGroup != 0) ReplacementShareContexts()[replacedGroup].push_back(context);
|
||||
emitted = MG_Remote::Client::SendCreateContextFrame(state->GetContextClientToken(context),
|
||||
state->GetContextShareGroupToken(context), 0);
|
||||
}
|
||||
@@ -884,6 +935,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return EGL_FALSE;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
ForgetReplacementShareContext(ctx);
|
||||
if (contextToken != 0 && !lost && MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
!MG_Remote::Client::RunsAsTheServerRole()) {
|
||||
const std::lock_guard<std::recursive_mutex> streamLock(EGLOperationMutex());
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
// (the server's per-session refusal line stays silent), and the old texture's name is gone;
|
||||
// * the surface the application kept works with the new context (EGL keeps surfaces across a
|
||||
// power-management loss; only their contents go);
|
||||
// * the other route back - eglTerminate, then eglInitialize - brings a fresh session up too.
|
||||
// * the other route back - eglTerminate, then eglInitialize - brings a fresh session up too;
|
||||
// * contexts recreated still naming a lost share context (Qt's global share context) share
|
||||
// with EACH OTHER in one new group, not each in a group of its own.
|
||||
|
||||
#include "P12ServerRig.h"
|
||||
|
||||
@@ -359,6 +361,80 @@ namespace {
|
||||
(void)EGL::Terminate(egl.dpy);
|
||||
}
|
||||
|
||||
// QT'S ROUTE. A long-lived share context the application never recreates, and its working
|
||||
// contexts created sharing with it; after the loss the working contexts are recreated, still
|
||||
// naming the share context, which is lost.
|
||||
struct ShareReport {
|
||||
Int32 initialized = 0;
|
||||
Int32 firstOk = 0;
|
||||
Int32 secondOk = 0;
|
||||
Int32 firstError = 0;
|
||||
Int32 sameGroup = -1;
|
||||
Int32 lostGroupReused = -1;
|
||||
Int32 textureSeenAcross = -1;
|
||||
Uint32 drawnAcross = 0;
|
||||
char note[256] = {};
|
||||
};
|
||||
|
||||
void Note(ShareReport& r, const char* text) {
|
||||
if (r.note[0] == '\0') std::snprintf(r.note, sizeof(r.note), "%s", text);
|
||||
}
|
||||
|
||||
void RunShareReplacementClient(ShareReport& r) {
|
||||
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 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)
|
||||
return Note(r, "the working context did not go current");
|
||||
r.initialized = 1;
|
||||
TexturedDraw old;
|
||||
RecoveryReport built{};
|
||||
if (!old.Build(built, kGreen)) return Note(r, built.note);
|
||||
old.Draw();
|
||||
(void)ReadPixel(); // device check #1
|
||||
(void)ClearAndRead(0.0f, 0.0f, 1.0f); // device check #2: lost
|
||||
(void)WaitFor([] { return glGetGraphicsResetStatus() != GL_NO_ERROR; }, 5000);
|
||||
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(egl.dpy, working);
|
||||
|
||||
// Two windows' contexts, recreated naming the lost share context.
|
||||
const EGLContext first = EGL::CreateContext(egl.dpy, egl.config, share, attribs);
|
||||
r.firstError = EGL::GetError();
|
||||
const EGLContext second = EGL::CreateContext(egl.dpy, egl.config, share, attribs);
|
||||
r.firstOk = first != EGL_NO_CONTEXT;
|
||||
r.secondOk = second != EGL_NO_CONTEXT;
|
||||
if (!r.firstOk || !r.secondOk) return Note(r, "a context naming the lost share context was refused");
|
||||
const Uint64 lostGroup = MG_State::pEGLContext->GetContextShareGroupToken(share);
|
||||
const Uint64 firstGroup = MG_State::pEGLContext->GetContextShareGroupToken(first);
|
||||
r.sameGroup = firstGroup == MG_State::pEGLContext->GetContextShareGroupToken(second) ? 1 : 0;
|
||||
r.lostGroupReused = firstGroup == lostGroup ? 1 : 0;
|
||||
|
||||
// An object of one is the other's: a texture filled on the first is drawn on the second.
|
||||
if (EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, first) != EGL_TRUE)
|
||||
return Note(r, "the first replacement did not go current");
|
||||
TexturedDraw shared;
|
||||
if (!shared.Build(built, kRed)) return Note(r, built.note);
|
||||
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
if (EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, second) != EGL_TRUE)
|
||||
return Note(r, "the second replacement did not go current");
|
||||
r.textureSeenAcross = glIsTexture(shared.texture) == GL_TRUE ? 1 : 0;
|
||||
GLuint vertexArray = 0; // a vertex array is per context
|
||||
glGenVertexArrays(1, &vertexArray);
|
||||
shared.vertexArray = vertexArray;
|
||||
shared.Draw();
|
||||
r.drawnAcross = ReadPixel();
|
||||
|
||||
(void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(egl.dpy, second);
|
||||
(void)EGL::DestroyContext(egl.dpy, first);
|
||||
(void)EGL::DestroyContext(egl.dpy, share);
|
||||
(void)EGL::DestroySurface(egl.dpy, egl.surface);
|
||||
(void)EGL::Terminate(egl.dpy);
|
||||
}
|
||||
|
||||
// A thread of the client that runs what it is handed, one job at a time, and keeps whatever
|
||||
// EGL context it made current between jobs - an application's other GL thread.
|
||||
class Worker {
|
||||
@@ -692,3 +768,26 @@ TEST_F(ClientRecovery, AThreadStillOnTheLostContextReachesNothingOfTheFreshSessi
|
||||
ExpectOneLossAndACleanSecondSession(server);
|
||||
server.Stop();
|
||||
}
|
||||
|
||||
// QT'S ROUTE: the windows' contexts are recreated still naming the long-lived share context, which
|
||||
// is lost. They are created, in ONE new share group (the lost group's objects are on no server):
|
||||
// a texture made on one is drawn on the other. Red with the share refused (EGL_BAD_CONTEXT): Qt
|
||||
// then falls back to an unshared context per window.
|
||||
TEST_F(ClientRecovery, ContextsNamingALostShareContextShareOneNewGroup) {
|
||||
ServerProcess server;
|
||||
ASSERT_TRUE(Launch(&server, "share")) << server.Log();
|
||||
ClientLog client("share");
|
||||
const ShareReport r = RunInClient<ShareReport>(server, client.base, &RunShareReplacementClient);
|
||||
const std::string diagnostics = "server:\n" + server.Log() + "\nclient:\n" + client.Text();
|
||||
ASSERT_EQ(r.initialized, 1) << r.note << "\n" << diagnostics;
|
||||
ASSERT_EQ(r.firstOk, 1) << "eglCreateContext naming the lost share context failed (0x" << std::hex << r.firstError
|
||||
<< ")\n" << r.note;
|
||||
ASSERT_EQ(r.secondOk, 1) << r.note;
|
||||
EXPECT_EQ(r.sameGroup, 1) << "the replacements landed in separate share groups";
|
||||
EXPECT_EQ(r.lostGroupReused, 0) << "a replacement joined the lost share group";
|
||||
EXPECT_EQ(r.textureSeenAcross, 1) << "a texture of one replacement is not the other's";
|
||||
EXPECT_EQ(r.drawnAcross, kRed) << "the shared texture did not draw on the other replacement: " << std::hex
|
||||
<< r.drawnAcross << "\n" << r.note << "\n" << diagnostics;
|
||||
ExpectOneLossAndACleanSecondSession(server);
|
||||
server.Stop();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user