mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend, MG_Remote, MG_Test] (Disaggregated): P5f fs - isolate context epochs and server liveness
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
// MobileGL - DirectGLES context-owned static state identities (P5f fs).
|
||||
#pragma once
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <Config.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
extern Uint g_backendContextGeneration;
|
||||
|
||||
struct ContextEpoch {
|
||||
Uint Native = 0;
|
||||
Uint64 Served = 0;
|
||||
Bool Server = false;
|
||||
Bool operator==(const ContextEpoch&) const = default;
|
||||
};
|
||||
|
||||
inline ContextEpoch CurrentContextEpoch() {
|
||||
const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith;
|
||||
return {g_backendContextGeneration, server ? MG_Pipe::MGPipeApplierContextSerial() : 0, server};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -7,6 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "DirectGLES.h"
|
||||
#include "ContextEpoch.h"
|
||||
#include "EGL/egl.h"
|
||||
#include "MG_Util/Types.h"
|
||||
#include "Utils.h"
|
||||
@@ -308,6 +309,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// grep and a comment naming it is a hit) and for an MG_State type inside
|
||||
// MGPipeResourceOps, and this is neither.
|
||||
SamplerImpl::BackendSamplerObject* GetRawDepthFetchSampler() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
static UniquePtr<SamplerImpl::BackendSamplerObject> native;
|
||||
static Uint generation = 0;
|
||||
if (generation != g_backendContextGeneration) {
|
||||
native.reset(); // old generation's destructor never deletes a successor id
|
||||
generation = g_backendContextGeneration;
|
||||
}
|
||||
if (!native) {
|
||||
native = MakeUnique<SamplerImpl::BackendSamplerObject>();
|
||||
const GLuint id = native->GetBackendSamplerId();
|
||||
g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
g_GLESFuncs.glSamplerParameteri(id, GL_TEXTURE_COMPARE_FUNC, GL_ALWAYS);
|
||||
}
|
||||
return native.get();
|
||||
}
|
||||
#endif
|
||||
if (!g_rawDepthFetchSamplerState) {
|
||||
g_rawDepthFetchSamplerState = MakeShared<MG_State::GLState::SamplerObject>(0);
|
||||
g_rawDepthFetchSamplerState->SetMinFilter(SamplerFilterMode::Nearest);
|
||||
@@ -1600,7 +1620,50 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// null instead of reasoning about stability, and CurrentXfb re-resolves lazily.
|
||||
XfbObjectState* g_currentXfbState = nullptr;
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
struct RoleXfbState {
|
||||
Uint NativeGeneration = 0;
|
||||
// Server keys are record-supplied, never-reused XFB lifetime ids.
|
||||
// The monolith role keeps its existing GL-name semantics separately.
|
||||
UnorderedMap<Uint64, XfbObjectState> Objects;
|
||||
Uint64 Current = 0;
|
||||
XfbObjectState* Cached = nullptr;
|
||||
GLuint ScatterBuffer = 0;
|
||||
SizeT ScatterSize = 0;
|
||||
Bool NeedsBind = true;
|
||||
};
|
||||
Array<RoleXfbState, 2> g_roleXfbState;
|
||||
Int g_activeXfbRole = -1;
|
||||
|
||||
RoleXfbState& ActiveXfbState() {
|
||||
const Int role = MG_Config::Transport != MG_Config::TransportMode::Monolith;
|
||||
auto& state = g_roleXfbState[role];
|
||||
if (state.NativeGeneration != g_backendContextGeneration) {
|
||||
state = {};
|
||||
state.NativeGeneration = g_backendContextGeneration;
|
||||
}
|
||||
if (g_activeXfbRole != role) {
|
||||
state.NeedsBind = true;
|
||||
g_activeXfbRole = role;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
#endif
|
||||
|
||||
XfbObjectState& CurrentXfb() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
auto& state = ActiveXfbState();
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
const Uint64 lifetime = MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId;
|
||||
if (state.Current != lifetime || state.Cached == nullptr || state.NeedsBind)
|
||||
BindTransformFeedback(0); // identity is the applier's lifetime, not this argument
|
||||
} else if (state.NeedsBind) {
|
||||
BindTransformFeedback(static_cast<GLuint>(state.Current));
|
||||
}
|
||||
auto& g_currentXfbState = state.Cached;
|
||||
auto& g_xfbObjects = state.Objects;
|
||||
const auto g_currentXfbName = state.Current;
|
||||
#endif
|
||||
if (g_currentXfbState == nullptr) {
|
||||
g_currentXfbState = &g_xfbObjects[g_currentXfbName];
|
||||
}
|
||||
@@ -1734,6 +1797,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// scratch storage cannot be provided, in which case the caller falls back to the
|
||||
// direct binding (which produces a wrong layout, but is what happened before).
|
||||
Bool BindScatterCaptureBuffer(SizeT packedStride, SizeT capacityVertices) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
auto& g_scatterBufferId = ActiveXfbState().ScatterBuffer;
|
||||
auto& g_scatterBufferSize = ActiveXfbState().ScatterSize;
|
||||
#endif
|
||||
if (packedStride == 0 || capacityVertices == 0) return false;
|
||||
if (g_GLESFuncs.glGenBuffers == nullptr || g_GLESFuncs.glBufferData == nullptr) return false;
|
||||
const SizeT required = packedStride * capacityVertices;
|
||||
@@ -1770,6 +1837,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// occupies are written, so the holes gl_SkipComponents asks for keep whatever the
|
||||
// application had put there - which is the whole point of the feature.
|
||||
void ScatterCapturedRecords(XfbObjectState& xfb) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
auto& g_scatterBufferId = ActiveXfbState().ScatterBuffer;
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const XfbProgramSource program{xfb.scatterProgram, xfb.scatterArchive};
|
||||
#else
|
||||
@@ -2126,6 +2196,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
void BindTransformFeedback(GLuint name) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
auto& roleState = ActiveXfbState();
|
||||
auto& g_currentXfbState = roleState.Cached;
|
||||
auto& g_xfbObjects = roleState.Objects;
|
||||
auto& g_currentXfbName = roleState.Current;
|
||||
const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith;
|
||||
const Uint64 key = server ? MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId : name;
|
||||
roleState.NeedsBind = false;
|
||||
#endif
|
||||
g_currentXfbState = nullptr; // name changes; operator[] below may also rehash
|
||||
// The capture buffer bindings are the OBJECT's, not the context's: the bind below
|
||||
// swaps all of them for whatever the target object holds, which the redundant-bind
|
||||
@@ -2134,18 +2213,44 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (!AreTransformFeedbackObjectsSupported()) {
|
||||
// Without driver objects there is only the default span; keep the frontend
|
||||
// name so the bookkeeping below stays consistent.
|
||||
g_currentXfbName = name;
|
||||
g_currentXfbName =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
key;
|
||||
#else
|
||||
name;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
auto& xfb = g_xfbObjects[key];
|
||||
// Even each virtual context's name-0 object gets its own native object.
|
||||
if ((server || name != 0) && xfb.esId == 0) {
|
||||
#else
|
||||
auto& xfb = g_xfbObjects[name];
|
||||
if (name != 0 && xfb.esId == 0) {
|
||||
#endif
|
||||
g_GLESFuncs.glGenTransformFeedbacks(1, &xfb.esId);
|
||||
}
|
||||
g_GLESFuncs.glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb.esId);
|
||||
g_currentXfbName = name;
|
||||
g_currentXfbName =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
key;
|
||||
#else
|
||||
name;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DeleteTransformFeedback(GLuint name) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// No delete_stream_output opcode exists yet; the client refuses this class-C
|
||||
// verb before emission. Never reinterpret its GL name as a lifetime id.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
|
||||
MG_Pipe::MGPipeUnmigratedEmulation("delete-transform-feedback");
|
||||
auto& roleState = ActiveXfbState();
|
||||
auto& g_currentXfbState = roleState.Cached;
|
||||
auto& g_xfbObjects = roleState.Objects;
|
||||
auto& g_currentXfbName = roleState.Current;
|
||||
#endif
|
||||
const auto it = g_xfbObjects.find(name);
|
||||
if (it == g_xfbObjects.end()) return;
|
||||
if (it->second.esId != 0 && g_GLESFuncs.glDeleteTransformFeedbacks != nullptr) {
|
||||
@@ -2162,6 +2267,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// The ES context went away (or is being torn down): the spans, their buffer ids, the
|
||||
// driver objects and the frontend objects they pinned all belonged to it.
|
||||
void OnBackendContextDestroyed() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
for (auto& state : g_roleXfbState) state = {};
|
||||
g_activeXfbRole = -1;
|
||||
#endif
|
||||
g_currentXfbState = nullptr;
|
||||
g_xfbObjects.clear();
|
||||
g_currentXfbName = 0;
|
||||
@@ -4719,6 +4828,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_syncedBackendScissorBox = IntVec4(-1, -1, -1, -1);
|
||||
}
|
||||
void SyncRenderState(Bool forColorClear) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
static ContextEpoch syncedEpoch{};
|
||||
const auto epoch = CurrentContextEpoch();
|
||||
if (syncedEpoch != epoch) {
|
||||
InvalidateSyncedRenderState();
|
||||
syncedEpoch = epoch;
|
||||
}
|
||||
#endif
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
@@ -16041,6 +16158,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
void DestroyEGLContext() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
|
||||
MG_Pipe::MGPipeServerSetContextLive(false);
|
||||
#endif
|
||||
BufferImpl::OnBackendContextDestroyed();
|
||||
XfbImpl::OnBackendContextDestroyed();
|
||||
MultiDrawImpl::OnBackendContextDestroyed();
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
#include "ContextEpoch.h"
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint g_backendContextGeneration = 1;
|
||||
@@ -6279,6 +6280,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
private:
|
||||
static void EnsureShadowSynced() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const auto epoch = CurrentContextEpoch();
|
||||
if (s_epoch != epoch) {
|
||||
s_synced = false;
|
||||
s_epoch = epoch;
|
||||
}
|
||||
#endif
|
||||
if (s_synced) {
|
||||
return;
|
||||
}
|
||||
@@ -6316,6 +6324,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
// Shadow of the backend GL unpack state (GL defaults). See class comment.
|
||||
static inline Bool s_synced = false;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
static inline ContextEpoch s_epoch{};
|
||||
#endif
|
||||
static inline GLint s_alignment = 4;
|
||||
static inline GLint s_rowLength = 0;
|
||||
static inline GLint s_skipRows = 0;
|
||||
@@ -6324,6 +6335,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
static inline GLint s_skipImages = 0;
|
||||
};
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
void ExerciseDefaultUnpackScopeForTesting() {
|
||||
const ScopedDefaultUnpackState scope;
|
||||
}
|
||||
#endif
|
||||
|
||||
// --- Unpack-ring staging (see BufferImpl::UnpackRingAvailable) ---------------
|
||||
// One rectangular (or whole-level) region of a level shadow, repacked TIGHTLY
|
||||
// into the persistently-mapped unpack PBO. The upload that follows passes the
|
||||
|
||||
@@ -1659,6 +1659,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
} // namespace VertexArrayImpl
|
||||
|
||||
namespace TextureImpl {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Drives the actual unpack save/restore helper without needing a texture upload.
|
||||
void ExerciseDefaultUnpackScopeForTesting();
|
||||
#endif
|
||||
inline Bool IsSupportedTextureTarget(TextureTarget target) {
|
||||
// Every desktop-only target is stored on an ES one; see MapToBackendTextureTarget.
|
||||
(void)target;
|
||||
|
||||
@@ -302,6 +302,15 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeStampAccess::SetServerStamped(MGPipeClientInputs(), false);
|
||||
}
|
||||
|
||||
namespace { Bool g_serverContextLive = false; }
|
||||
|
||||
void MGPipeServerSetContextLive(Bool live) {
|
||||
g_serverContextLive = live;
|
||||
MGPipeServerBlockNoteIdentity();
|
||||
}
|
||||
|
||||
Bool MGPipeServerContextIsLive() { return g_serverContextLive; }
|
||||
|
||||
void MGPipeServerBlockNoteIdentity() {
|
||||
if (!MGPipeRoleSplitActive()) return;
|
||||
// The server cannot name the client's GLContext - under a real transport it is in
|
||||
@@ -313,7 +322,7 @@ namespace MobileGL::MG_Pipe {
|
||||
// hands out a null slot - an unnamed crash where the rehearsal exists to produce a
|
||||
// named one.
|
||||
const Uint64 serial = MGPipeApplierContextSerial();
|
||||
MGPipeStampAccess::SetIdentity(gPipeInputs, true,
|
||||
MGPipeStampAccess::SetIdentity(gPipeInputs, g_serverContextLive,
|
||||
reinterpret_cast<const void*>(static_cast<std::uintptr_t>(
|
||||
(serial << 1) | Uint64{1})));
|
||||
}
|
||||
|
||||
@@ -881,6 +881,10 @@ namespace MobileGL::MG_Pipe {
|
||||
// unnamed crash where the rehearsal exists to produce a named one. A no-op with the
|
||||
// rehearsal off, so the client's per-verb SetIdentity keeps owning the shared block there.
|
||||
void MGPipeServerBlockNoteIdentity();
|
||||
// P5f fs: owned by the server's control lifecycle, never inferred from a client
|
||||
// GLContext or resurrected by stamping a verb after teardown.
|
||||
void MGPipeServerSetContextLive(Bool live);
|
||||
Bool MGPipeServerContextIsLive();
|
||||
#else
|
||||
// The push-without-transport build has one role and one block, so the fill side's spelling
|
||||
// folds onto gPipeInputs and PipeFill.cpp reads identically in both build flavours. An
|
||||
|
||||
@@ -2099,7 +2099,13 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
|
||||
// ---- liveness ----
|
||||
Bool PipeInputs::IsLive() const { return LiveContext() != nullptr; }
|
||||
Bool PipeInputs::IsLive() const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
|
||||
return MGPipeServerContextIsLive();
|
||||
#endif
|
||||
return LiveContext() != nullptr;
|
||||
}
|
||||
|
||||
// ---- the seven F-class forwarders ----
|
||||
//
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// P5 package v1: the apply thread, its affinity, its parking, and the EGL ownership move.
|
||||
|
||||
#include "ServerLoop.h"
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
@@ -286,10 +287,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// removes. m_haveCurrentTuple is left intact so a later identical bind is still a
|
||||
// RepeatNoOp (the driver never lost the context).
|
||||
m_clientReleases.fetch_add(1, std::memory_order_acq_rel);
|
||||
MG_Pipe::MGPipeServerSetContextLive(false);
|
||||
outcome.ok = true;
|
||||
outcome.boundNatively = false;
|
||||
return outcome;
|
||||
case EglBindAction::RepeatNoOp:
|
||||
MG_Pipe::MGPipeServerSetContextLive(true);
|
||||
// ID-54's "a no-op apart from the R-12 republish decision": the decision for an
|
||||
// identical repeat is NO republish, because nothing ran that could have moved the
|
||||
// caps - InitCapabilities runs inside the backend's MakeEGLCurrent, which this arm
|
||||
@@ -308,6 +311,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// control counts THAT at the EGL function table. This counter counts forwards.
|
||||
outcome.ok = backend->MakeEGLCurrent(dpy, draw, read, ctx);
|
||||
if (!outcome.ok) return outcome;
|
||||
MG_Pipe::MGPipeServerSetContextLive(true);
|
||||
m_haveCurrentTuple = true;
|
||||
m_curDpy = dpy;
|
||||
m_curDraw = draw;
|
||||
@@ -468,6 +472,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
"which is the context owner");
|
||||
m_backend.reset();
|
||||
}
|
||||
MG_Pipe::MGPipeServerSetContextLive(false);
|
||||
// N-3: the context died with the backend; a tuple that outlives it would make the next
|
||||
// session's first make-current onto the same (recycled) handle values a RepeatNoOp.
|
||||
ForgetCurrentTuple();
|
||||
@@ -753,6 +758,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// thread called Stop. No context was ever made current from another thread in that
|
||||
// case, which is exactly the condition that makes this safe.
|
||||
if (m_backend != nullptr) m_backend.reset();
|
||||
MG_Pipe::MGPipeServerSetContextLive(false);
|
||||
// N-3, same reason as ApplyThreadMain's exit: no thread runs, so the apply-thread-only
|
||||
// rule on the tuple has no other writer to race.
|
||||
ForgetCurrentTuple();
|
||||
@@ -997,6 +1003,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// DestroyEGLContext - eglMakeCurrent(NO_SURFACE), eglDestroyContext, eglTerminate -
|
||||
// on the thread that made the context current.
|
||||
backend->ReleaseEGLResources();
|
||||
MG_Pipe::MGPipeServerSetContextLive(false);
|
||||
// N-3: DestroyEGLContext just ran; the tuple names nothing. Without this a
|
||||
// destroy-recreate with the same handle values (every EGL handle on this host is
|
||||
// 0x1) classified as a RepeatNoOp, bound nothing, and republished no caps. Red once
|
||||
|
||||
@@ -49,6 +49,10 @@ target_link_libraries(
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
add_executable(ContextEpochTest ContextEpochTest.cpp)
|
||||
target_include_directories(ContextEpochTest PRIVATE ${MGL_ROOT}/include ${MGL_ROOT}/MobileGL)
|
||||
target_link_libraries(ContextEpochTest PRIVATE GTest::gtest_main ${LINK_LIBRARIES})
|
||||
gtest_discover_tests(ContextEpochTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
gtest_discover_tests(EsslShaderPassTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
gtest_discover_tests(BaseInstanceInjectionTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
gtest_discover_tests(ViewportIndexRoutingTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// MobileGL - P5f fs production-state epoch regression tests.
|
||||
#include <gtest/gtest.h>
|
||||
#include <Config.h>
|
||||
#include <MG_Backend/DirectGLES/DirectGLES.h>
|
||||
#include <MG_Backend/DirectGLES/Managers.h>
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
SamplerImpl::BackendSamplerObject* GetRawDepthFetchSampler();
|
||||
}
|
||||
#endif
|
||||
|
||||
using namespace MobileGL;
|
||||
using namespace MobileGL::MG_Backend::DirectGLES;
|
||||
|
||||
namespace {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Uint unpackCalls = 0, samplerParameters = 0, nextNativeId = 100, boundXfb = 0;
|
||||
Vector<GLenum> unpackNames;
|
||||
void GL_APIENTRY PixelStore(GLenum name, GLint) { ++unpackCalls; unpackNames.push_back(name); }
|
||||
void GL_APIENTRY GenNames(GLsizei count, GLuint* names) { while (count-- > 0) *names++ = ++nextNativeId; }
|
||||
void GL_APIENTRY DeleteNames(GLsizei, const GLuint*) {}
|
||||
void GL_APIENTRY SamplerParam(GLuint, GLenum, GLint) { ++samplerParameters; }
|
||||
void GL_APIENTRY BindXfb(GLenum, GLuint id) { boundXfb = id; }
|
||||
void GL_APIENTRY NoArgs() {}
|
||||
void GL_APIENTRY BeginXfb(GLenum) {}
|
||||
struct DriverScope {
|
||||
MG_External::GLESFunctionsTable Functions = g_GLESFuncs;
|
||||
MG_Config::TransportMode Transport = MG_Config::Transport;
|
||||
DriverScope() {
|
||||
++g_backendContextGeneration;
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
g_GLESFuncs.glPixelStorei = &PixelStore;
|
||||
g_GLESFuncs.glGenSamplers = &GenNames;
|
||||
g_GLESFuncs.glDeleteSamplers = &DeleteNames;
|
||||
g_GLESFuncs.glSamplerParameteri = &SamplerParam;
|
||||
g_GLESFuncs.glGenTransformFeedbacks = &GenNames;
|
||||
g_GLESFuncs.glDeleteTransformFeedbacks = &DeleteNames;
|
||||
g_GLESFuncs.glBindTransformFeedback = &BindXfb;
|
||||
g_GLESFuncs.glPauseTransformFeedback = &NoArgs;
|
||||
g_GLESFuncs.glResumeTransformFeedback = &NoArgs;
|
||||
g_GLESFuncs.glBeginTransformFeedback = &BeginXfb;
|
||||
g_GLESFuncs.glEndTransformFeedback = &NoArgs;
|
||||
}
|
||||
~DriverScope() {
|
||||
XfbImpl::OnBackendContextDestroyed();
|
||||
++g_backendContextGeneration;
|
||||
g_GLESFuncs = Functions;
|
||||
MG_Config::Transport = Transport;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ContextEpochTest, UnpackDefaultIsRepublishedForANewNativeContextOrRole) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
DriverScope scope;
|
||||
unpackCalls = 0;
|
||||
TextureImpl::ExerciseDefaultUnpackScopeForTesting();
|
||||
EXPECT_EQ(unpackCalls, 8u); // six defaults, tight alignment, restore
|
||||
unpackCalls = 0;
|
||||
TextureImpl::ExerciseDefaultUnpackScopeForTesting();
|
||||
EXPECT_EQ(unpackCalls, 2u);
|
||||
++g_backendContextGeneration;
|
||||
unpackCalls = 0;
|
||||
TextureImpl::ExerciseDefaultUnpackScopeForTesting();
|
||||
EXPECT_EQ(unpackCalls, 8u) << "a previous context's unpack shadow suppressed the new context's default writes";
|
||||
MG_Config::Transport = MG_Config::TransportMode::Monolith;
|
||||
unpackCalls = 0;
|
||||
TextureImpl::ExerciseDefaultUnpackScopeForTesting();
|
||||
EXPECT_EQ(unpackCalls, 8u) << "the other role inherited the server's unpack shadow";
|
||||
#else
|
||||
GTEST_SKIP() << "context-role isolation is a disaggregated-build mechanism";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ContextEpochTest, RawDepthSamplerOwnsANativeIdForEachContextGeneration) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
DriverScope scope;
|
||||
samplerParameters = 0;
|
||||
const Uint first = GetRawDepthFetchSampler()->GetBackendSamplerId();
|
||||
EXPECT_EQ(samplerParameters, 4u);
|
||||
EXPECT_EQ(GetRawDepthFetchSampler()->GetBackendSamplerId(), first);
|
||||
EXPECT_EQ(samplerParameters, 4u);
|
||||
++g_backendContextGeneration;
|
||||
const Uint second = GetRawDepthFetchSampler()->GetBackendSamplerId();
|
||||
EXPECT_NE(first, second) << "a sampler id outlived its native context";
|
||||
EXPECT_EQ(samplerParameters, 8u);
|
||||
#else
|
||||
GTEST_SKIP() << "native server sampler exists only in the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ContextEpochTest, SameXfbNameInTwoServedContextsDoesNotAliasThePausedSpan) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
DriverScope scope;
|
||||
auto& state = MG_Pipe::MGPipeApplier();
|
||||
const auto saved = state.BoundStreamOutputLifetimeId;
|
||||
state.BoundStreamOutputLifetimeId = 101;
|
||||
XfbImpl::BindTransformFeedback(1);
|
||||
const Uint first = boundXfb;
|
||||
XfbImpl::BeginTransformFeedback(GL_POINTS);
|
||||
EXPECT_TRUE(XfbImpl::IsCaptureSpanOpen());
|
||||
XfbImpl::PauseTransformFeedback();
|
||||
EXPECT_FALSE(XfbImpl::IsCaptureSpanOpen());
|
||||
state.BoundStreamOutputLifetimeId = 202;
|
||||
XfbImpl::BindTransformFeedback(1);
|
||||
EXPECT_NE(first, boundXfb) << "the GL name was mistaken for cross-context identity";
|
||||
EXPECT_FALSE(XfbImpl::IsCaptureSpanOpen());
|
||||
XfbImpl::BeginTransformFeedback(GL_POINTS);
|
||||
EXPECT_TRUE(XfbImpl::IsCaptureSpanOpen());
|
||||
state.BoundStreamOutputLifetimeId = 101;
|
||||
EXPECT_FALSE(XfbImpl::IsCaptureSpanOpen()) << "returning to the paused object inherited another context's span";
|
||||
EXPECT_EQ(boundXfb, first);
|
||||
XfbImpl::ResumeTransformFeedback();
|
||||
EXPECT_TRUE(XfbImpl::IsCaptureSpanOpen());
|
||||
++g_backendContextGeneration;
|
||||
EXPECT_FALSE(XfbImpl::IsCaptureSpanOpen()) << "native context replacement retained a dead capture";
|
||||
EXPECT_NE(boundXfb, first);
|
||||
state.BoundStreamOutputLifetimeId = saved;
|
||||
#else
|
||||
GTEST_SKIP() << "server XFB identity exists only in the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ContextEpochTest, ServerLivenessDoesNotFollowTheClientContextOrAVerbStamp) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
DriverScope scope;
|
||||
auto context = Move(MG_State::pGLContext);
|
||||
const Bool previous = MG_Pipe::MGPipeServerContextIsLive();
|
||||
MG_Pipe::MGPipeServerSetContextLive(false);
|
||||
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
|
||||
EXPECT_FALSE(MG_Pipe::gPipeInputs.IsLive());
|
||||
MG_Pipe::MGPipeServerSetContextLive(true);
|
||||
MG_State::pGLContext.reset();
|
||||
EXPECT_TRUE(MG_Pipe::gPipeInputs.IsLive());
|
||||
MG_Pipe::MGPipeServerSetContextLive(false);
|
||||
MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::DrawArrays);
|
||||
EXPECT_FALSE(MG_Pipe::gPipeInputs.IsLive()) << "a stale command resurrected a destroyed server context";
|
||||
MG_Pipe::MGPipeServerClearVerbBoundary();
|
||||
MG_State::pGLContext = Move(context);
|
||||
MG_Pipe::MGPipeServerSetContextLive(previous);
|
||||
#else
|
||||
GTEST_SKIP() << "server liveness exists only in the disaggregated build";
|
||||
#endif
|
||||
}
|
||||
@@ -1694,6 +1694,63 @@ TEST(ServerLoopEglTest, ADestroyedContextForgetsTheTupleSoTheSameHandleValuesBin
|
||||
fixture.TearDown();
|
||||
}
|
||||
|
||||
TEST(ServerLoopEglTest, RenderShadowCannotSkipAnUnchangedVersionAfterContextEpochChanges) {
|
||||
EglServerFixture fixture;
|
||||
MGL_EGL_BRING_UP_OR_BAIL(fixture);
|
||||
ASSERT_TRUE(fixture.MakeCurrent());
|
||||
Bool nativeRestored = false, servedRestored = false;
|
||||
ASSERT_EQ(OnApply([&] {
|
||||
using namespace MG_Backend::DirectGLES;
|
||||
MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::Clear);
|
||||
RenderStateImpl::SyncRenderState(true);
|
||||
// Simulate the successor driver's default/foreign state while retaining exactly
|
||||
// the same parameter bytes and version. Epoch, not a value diff, must force it.
|
||||
g_GLESFuncs.glEnable(GL_BLEND);
|
||||
++g_backendContextGeneration;
|
||||
RenderStateImpl::SyncRenderState(true);
|
||||
nativeRestored = g_GLESFuncs.glIsEnabled(GL_BLEND) == GL_FALSE;
|
||||
g_GLESFuncs.glEnable(GL_BLEND);
|
||||
MG_Pipe::MGPipeApplierReset();
|
||||
MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::Clear);
|
||||
RenderStateImpl::SyncRenderState(true);
|
||||
servedRestored = g_GLESFuncs.glIsEnabled(GL_BLEND) == GL_FALSE;
|
||||
MG_Pipe::MGPipeServerClearVerbBoundary();
|
||||
}), MOBILEGL_OK);
|
||||
EXPECT_TRUE(nativeRestored) << "unchanged render version hid a new native context";
|
||||
EXPECT_TRUE(servedRestored) << "unchanged render version hid a new served context";
|
||||
fixture.TearDown();
|
||||
}
|
||||
|
||||
TEST(ServerLoopEglTest, ServerLivenessFollowsControlFramesAcrossReleaseAndRecreation) {
|
||||
EglServerFixture fixture;
|
||||
MGL_EGL_BRING_UP_OR_BAIL(fixture);
|
||||
const auto live = [] {
|
||||
Bool answer = false;
|
||||
EXPECT_EQ(OnApply([&] { answer = MG_Pipe::gPipeInputs.IsLive(); }), MOBILEGL_OK);
|
||||
return answer;
|
||||
};
|
||||
EXPECT_FALSE(live()) << "surface creation alone is not a served current context";
|
||||
ASSERT_TRUE(fixture.MakeCurrent());
|
||||
EXPECT_TRUE(live());
|
||||
auto client = Move(MG_State::pGLContext);
|
||||
EXPECT_TRUE(live()) << "server liveness must not consult the client GLContext";
|
||||
MG_State::pGLContext = Move(client);
|
||||
ASSERT_TRUE(fixture.ReleaseCurrent());
|
||||
EXPECT_FALSE(live());
|
||||
ASSERT_TRUE(fixture.MakeCurrent()); // identical held native tuple, new logical binding
|
||||
EXPECT_TRUE(live());
|
||||
Server::ServerReleaseEGLResources();
|
||||
EXPECT_FALSE(live());
|
||||
EGLint major = 0, minor = 0;
|
||||
ASSERT_TRUE(Server::ServerInitializeEGLDisplay(EglServerFixture::Dpy(), &major, &minor));
|
||||
ASSERT_TRUE(Server::ServerCreateEGLPbufferSurface(EglServerFixture::Surf(), 64, 64));
|
||||
EXPECT_FALSE(live());
|
||||
ASSERT_TRUE(fixture.MakeCurrent());
|
||||
EXPECT_TRUE(live());
|
||||
fixture.TearDown();
|
||||
EXPECT_FALSE(MG_Pipe::MGPipeServerContextIsLive());
|
||||
}
|
||||
|
||||
// P5f (fc): InitializeEGLDisplay's out-pointers are the frame's reply fields now (the one place
|
||||
// the old mailbox carried pointers INTO the poster's stack). The answer is the driver's real
|
||||
// version through the frame - the BringUp already consumed one init; a second call is an
|
||||
|
||||
Reference in New Issue
Block a user