[Refactor] (MG_Backend, MG_Backend/DirectGLES, MG_Backend/DirectVulkan, MG_Impl, MG_Impl/EGLImpl, MG_Impl/GLImpl, MG_Impl/GLXImpl, MG_Pipe, MG_Remote/Client, MG_Remote/Server, MG_Test/Wire): split the server-only and client-only hooks out of BackendObject

- ServerHooks and ClientHooks (BackendRoles.h) hold the virtuals that only the server, or only a client of a remote server, calls, with the defaults they had; BackendObject::AsServer and AsClient return them, or null for a backend without the role
- DirectGLES and DirectVulkan implement ServerHooks, BackendObject_Remote implements ClientHooks, and the test doubles that run as a server's backend take the server role
- every call site keeps its answer: where no backend and a backend without the role answered differently, the backend check stays and the role check gives the old default
- ServerLoop::BackendHooks is the server's accessor, asked at each use
- comments that placed the function table in BackendObject.h name BackendFunctions.h
This commit is contained in:
2026-10-17 00:10:31 -04:00
parent 55fa9cdfdc
commit 1aa5cf63a6
24 changed files with 348 additions and 276 deletions
+13 -172
View File
@@ -10,7 +10,7 @@
#include <Includes.h>
#include <BackendCaps.h>
#include <MG_Backend/BackendFunctions.h>
#include <MG_Backend/SharedImageTypes.h>
#include <MG_Backend/BackendRoles.h>
#include <MG_Util/Damage/Damage.h>
namespace MobileGL {
@@ -36,152 +36,15 @@ namespace MobileGL {
public:
virtual ~BackendObject() = default;
// ---- shared images, SERVER side (a backend implements these) ---------------------
//
// Copies the default framebuffer of the surface current on this (the apply) thread into
// `image`, rows TOP FIRST - GL's top row (y = height-1) lands in the image's first memory
// row, which is what a wl_buffer's first row is - scaled to the image's extent if the two
// differ. The image is ORDERED THROUGH ITS SYNC STATE (SharedImageRegistry.h): the copy
// waits for the image's pending read fences before it writes, and its own completion
// is published with SharedImages::PublishWrite as a sync_file - the call returns once
// the copy is submitted, never waiting for it on the CPU (a backend with no fence export
// waits and publishes -1). A reader binding the image waits for that fence on its GPU
// when the write generation moved, and at its frame boundary publishes its frame's fence
// with PublishRead / ReadTracker. The application's GL state is untouched. False = not
// supported / failed (logged).
//
// `region` (GL window coordinates, origin bottom-left) is the part of the frame to copy;
// Full copies all of it. The rest of the image is left as it is - the client sends a
// region only for an image that already holds the frame outside it - and a backend that
// cannot be sure the image's contents survived (a target it has never written) copies
// all of it regardless.
virtual Bool BlitDefaultFramebufferToSharedImage(const SharedImageView& image,
const MG_Util::Damage::Region& region) {
(void)image, (void)region;
return false;
}
// EGL_BUFFER_AGE_EXT of the draw surface of the context current on this (the apply)
// thread: how many presents ago the buffer the next frame draws into held the frame then
// presented; 0 = unknown (the application repaints everything). Asking says the client
// relies on the answer, so from then on the backend keeps what it presents.
//
// `damageRegionFollows`: the client declares the region it draws (SetCurrentDamageRegion)
// before drawing. A driver whose only buffer age is EGL_KHR_partial_update's keeps a
// buffer's content only outside that region, so without one coming it is not asked.
virtual Int32 QueryCurrentBufferAge(Bool damageRegionFollows) {
(void)damageRegionFollows;
return 0;
}
// eglSetDamageRegionKHR for that draw surface: the frame draws `region` (GL window
// coordinates; Full = all of it) and nothing outside it. A backend without the notion
// ignores it.
virtual Bool SetCurrentDamageRegion(const MG_Util::Damage::Region& region) {
(void)region;
return true;
}
// IMPLICIT SYNC BY FLUSH (docs/archive/notes/anland/plan-x11-gpu.md): the session
// on this (the apply) thread ends a frame at a glFlush. Every shared image it used since
// its last boundary is published as written and read by one fence of everything it
// submitted (SharedImages::ReadTracker::PublishFrameAsWrite), and from now on its first
// use of an image in a frame waits for that image's pending reads (it may be about to
// overwrite what another session still samples). A backend without shared images has
// nothing to publish.
virtual Bool PublishSharedImageAccesses() { return true; }
// EGL_ANDROID_native_fence_sync's fence command, on the server: in `*fence`, a sync_file
// (the caller's to close) that signals once everything the session submitted so far has
// completed - and, for a session that writes images, what PublishSharedImageAccesses
// does. -1 when there is none to give: the backend cannot export one, or the work has
// already completed - in both cases it HAS completed when this returns.
virtual Bool ExportNativeFence(int* fence) {
*fence = -1;
return PublishSharedImageAccesses();
}
// ---- shared images, CLIENT side (BackendObject_Remote implements these) ----------
//
// A new image, allocated by the server; `out->Fd` is the caller's to close.
virtual Bool AllocateSharedImage(Uint32 width, Uint32 height, Uint32 fourcc, SharedImageExport* out) {
(void)width, (void)height, (void)fourcc, (void)out;
return false;
}
// Names the image `fd` was exported from (the caller keeps `fd`). False: not one of this
// server's images, or the size/format disagree.
virtual Bool ImportSharedImage(int fd, Uint32 width, Uint32 height, Uint32 fourcc, Uint64* outId) {
(void)fd, (void)width, (void)height, (void)fourcc, (void)outId;
return false;
}
// The same for a YUV fourcc, whose planes and hints `layout` gives. A YUV buffer the
// server did not export is taken too (its labelled CPU-copy fallback).
virtual Bool ImportSharedImagePlanes(int fd, Uint32 width, Uint32 height, Uint32 fourcc,
const SharedImageImportLayout& layout, Uint64* outId) {
(void)fd, (void)width, (void)height, (void)fourcc, (void)layout, (void)outId;
return false;
}
// Drops this client's reference (an allocation's or an import's).
virtual Bool ReleaseSharedImage(Uint64 id) {
(void)id;
return false;
}
// In the GL stream: the current context's default framebuffer, copied top row first into
// the image (BlitDefaultFramebufferToSharedImage on the server) - `region` of it, Full for
// all. Returns when it completed.
virtual Bool PresentToSharedImage(Uint64 id, const MG_Util::Damage::Region& region) {
(void)id, (void)region;
return false;
}
// In the GL stream: EGL_BUFFER_AGE_EXT of the current draw surface (QueryCurrentBufferAge
// on the server). False: the backend has no answer (the caller reports 0).
virtual Bool QueryBufferAge(Bool damageRegionFollows, EGLint* age) {
(void)damageRegionFollows, (void)age;
return false;
}
// In the GL stream: eglSetDamageRegionKHR for the current draw surface
// (SetCurrentDamageRegion on the server).
virtual Bool SetDamageRegion(const MG_Util::Damage::Region& region) {
(void)region;
return false;
}
// In the GL stream: the texture whose lifetime id is `textureLifetimeId` takes the image as
// its level-0 storage (glEGLImageTargetTexture2DOES), until its next storage definition.
// The caller has already defined level 0 as Width x Height RGBA8 with no data.
virtual Bool AttachSharedImageToTexture(Uint64 textureLifetimeId, Uint64 id) {
(void)textureLifetimeId, (void)id;
return false;
}
// In the GL stream, from glFlush/glFinish: PublishSharedImageAccesses on the server,
// returning once the fence is published. Skipped (true) when nothing reached the stream
// since the last one.
virtual Bool FlushSharedImageAccesses() { return false; }
// In the GL stream, from eglCreateSync(EGL_SYNC_NATIVE_FENCE_ANDROID) with no descriptor:
// ExportNativeFence on the server. `*fence` is the sync_file it handed back (the
// caller's to close), or -1 when it had none - the fenced work has then completed.
virtual Bool CreateNativeFence(int* fence) {
*fence = -1;
return false;
}
// The hooks only a server, or only a client of a remote server, calls (BackendRoles.h);
// null for a backend that plays no such role.
virtual ServerHooks* AsServer() { return nullptr; }
virtual ClientHooks* AsClient() { return nullptr; }
virtual void Initialize() = 0;
virtual Bool InitCapabilities() = 0;
virtual Bool InitWindowSurface() = 0;
// A served session's apply thread - the one thread that runs this backend from here on -
// calls this first, in a process that may serve several sessions at once. A backend whose
// native state is process-global by construction gives this object its own copy and binds
// it to the calling thread. The default has nothing per process to separate.
virtual void BindSessionStateToThisThread() {}
// A served session's apply thread asks this each time it wakes, BEFORE it applies another
// record: true when this session's own GPU work faulted or hung the GPU and the backend has
// latched the session for it (MGPipeSessionLatch, family DeviceLost), so the next frame -
// which would very likely stop the GPU again - is never applied. Cheap by contract: no
// wait, at most a status read. The default knows of no fault.
virtual Bool LatchIfGpuFaulted() { return false; }
// A served session's ring moved to another of its client's contexts (bind_context). A
// backend whose surface follows the native context it binds needs nothing; one that keeps
// a single native surface binding re-binds the surface that context was last made current
// with. `token` is the client's context token (the EGLContext MakeEGLCurrent was given).
virtual void OnClientContextBound(Uint64 token) { (void)token; }
virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor);
virtual Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle);
virtual Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height);
@@ -201,36 +64,6 @@ namespace MobileGL {
virtual void ReleaseEGLSurface(EGLSurface surface);
virtual void ReleaseEGLResources();
// ---- the server's own display window going away and coming back -------------------
//
// `window` (a window surface's native window) is about to be destroyed - the display's
// screen went off. Release ONLY what is bound to it: every window surface on it keeps its
// client handle and draws into a placeholder from here on, and every context, object and
// device stays. Called on the thread that runs this backend; nothing may reference the
// window once it returns true. False: not supported (the caller ends the session instead).
virtual Bool SuspendServerWindow(void* window) {
(void)window;
return false;
}
// The surfaces SuspendServerWindow parked are rebuilt on `window` (a new native window,
// `width`x`height`), and their new extent is published as a creation publishes it. False:
// they could not be (the caller ends the session).
virtual Bool ResumeServerWindow(void* window, Uint32 width, Uint32 height) {
(void)window, (void)width, (void)height;
return false;
}
// `window` changed size to `width`x`height` (the display's report - the one size to publish).
// Its surfaces publish that extent to the client now, without waiting for a present; a
// swapchain that has to follow is rebuilt when no frame is half-recorded, else at the
// present. False: nothing was published (no surface on it, or not supported).
virtual Bool ServerWindowResized(void* window, Uint32 width, Uint32 height) {
(void)window, (void)width, (void)height;
return false;
}
// CLIENT side, from eglQuerySurface(EGL_WIDTH/EGL_HEIGHT): a backend whose surface extents
// change under the application (a remote server's window) applies what it has received.
virtual void RefreshSurfaceExtent(EGLSurface surface) { (void)surface; }
void SetWindowHandle(const WindowHandle& handle);
virtual const RendererInfo& GetRendererInfo() const = 0;
@@ -303,6 +136,14 @@ namespace MobileGL {
void ReleaseEGLCurrentThread(const std::thread::id& threadKey);
};
// A backend's role hooks; null for no backend, or one that plays no such role.
inline ServerHooks* ServerHooksOf(BackendObject* backend) {
return backend != nullptr ? backend->AsServer() : nullptr;
}
inline ClientHooks* ClientHooksOf(BackendObject* backend) {
return backend != nullptr ? backend->AsClient() : nullptr;
}
extern UniquePtr<BackendObject>& pActiveBackendObject;
} // namespace MG_Backend
} // namespace MobileGL
+204
View File
@@ -0,0 +1,204 @@
// MobileGL - MobileGL/MG_Backend/BackendRoles.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#pragma once
#include <Includes.h>
#include <MG_Backend/SharedImageTypes.h>
#include <MG_Util/Damage/Damage.h>
namespace MobileGL::MG_Backend {
// What only the server asks of the backend it applies a session's records with. A backend that
// can serve sessions implements it and returns it from BackendObject::AsServer; each default is
// the answer of a backend with nothing to do for that hook.
class ServerHooks {
public:
// ---- shared images ------------------------------------------------------------------
//
// Copies the default framebuffer of the surface current on this (the apply) thread into
// `image`, rows TOP FIRST - GL's top row (y = height-1) lands in the image's first memory
// row, which is what a wl_buffer's first row is - scaled to the image's extent if the two
// differ. The image is ORDERED THROUGH ITS SYNC STATE (SharedImageRegistry.h): the copy
// waits for the image's pending read fences before it writes, and its own completion
// is published with SharedImages::PublishWrite as a sync_file - the call returns once
// the copy is submitted, never waiting for it on the CPU (a backend with no fence export
// waits and publishes -1). A reader binding the image waits for that fence on its GPU
// when the write generation moved, and at its frame boundary publishes its frame's fence
// with PublishRead / ReadTracker. The application's GL state is untouched. False = not
// supported / failed (logged).
//
// `region` (GL window coordinates, origin bottom-left) is the part of the frame to copy;
// Full copies all of it. The rest of the image is left as it is - the client sends a
// region only for an image that already holds the frame outside it - and a backend that
// cannot be sure the image's contents survived (a target it has never written) copies
// all of it regardless.
virtual Bool BlitDefaultFramebufferToSharedImage(const SharedImageView& image,
const MG_Util::Damage::Region& region) {
(void)image, (void)region;
return false;
}
// EGL_BUFFER_AGE_EXT of the draw surface of the context current on this (the apply)
// thread: how many presents ago the buffer the next frame draws into held the frame then
// presented; 0 = unknown (the application repaints everything). Asking says the client
// relies on the answer, so from then on the backend keeps what it presents.
//
// `damageRegionFollows`: the client declares the region it draws (SetCurrentDamageRegion)
// before drawing. A driver whose only buffer age is EGL_KHR_partial_update's keeps a
// buffer's content only outside that region, so without one coming it is not asked.
virtual Int32 QueryCurrentBufferAge(Bool damageRegionFollows) {
(void)damageRegionFollows;
return 0;
}
// eglSetDamageRegionKHR for that draw surface: the frame draws `region` (GL window
// coordinates; Full = all of it) and nothing outside it. A backend without the notion
// ignores it.
virtual Bool SetCurrentDamageRegion(const MG_Util::Damage::Region& region) {
(void)region;
return true;
}
// IMPLICIT SYNC BY FLUSH (docs/archive/notes/anland/plan-x11-gpu.md): the session
// on this (the apply) thread ends a frame at a glFlush. Every shared image it used since
// its last boundary is published as written and read by one fence of everything it
// submitted (SharedImages::ReadTracker::PublishFrameAsWrite), and from now on its first
// use of an image in a frame waits for that image's pending reads (it may be about to
// overwrite what another session still samples). A backend without shared images has
// nothing to publish.
virtual Bool PublishSharedImageAccesses() { return true; }
// EGL_ANDROID_native_fence_sync's fence command, on the server: in `*fence`, a sync_file
// (the caller's to close) that signals once everything the session submitted so far has
// completed - and, for a session that writes images, what PublishSharedImageAccesses
// does. -1 when there is none to give: the backend cannot export one, or the work has
// already completed - in both cases it HAS completed when this returns.
virtual Bool ExportNativeFence(int* fence) {
*fence = -1;
return PublishSharedImageAccesses();
}
// A served session's apply thread - the one thread that runs this backend from here on -
// calls this first, in a process that may serve several sessions at once. A backend whose
// native state is process-global by construction gives this object its own copy and binds
// it to the calling thread. The default has nothing per process to separate.
virtual void BindSessionStateToThisThread() {}
// A served session's apply thread asks this each time it wakes, BEFORE it applies another
// record: true when this session's own GPU work faulted or hung the GPU and the backend has
// latched the session for it (MGPipeSessionLatch, family DeviceLost), so the next frame -
// which would very likely stop the GPU again - is never applied. Cheap by contract: no
// wait, at most a status read. The default knows of no fault.
virtual Bool LatchIfGpuFaulted() { return false; }
// A served session's ring moved to another of its client's contexts (bind_context). A
// backend whose surface follows the native context it binds needs nothing; one that keeps
// a single native surface binding re-binds the surface that context was last made current
// with. `token` is the client's context token (the EGLContext MakeEGLCurrent was given).
virtual void OnClientContextBound(Uint64 token) { (void)token; }
// ---- the server's own display window going away and coming back -------------------
//
// `window` (a window surface's native window) is about to be destroyed - the display's
// screen went off. Release ONLY what is bound to it: every window surface on it keeps its
// client handle and draws into a placeholder from here on, and every context, object and
// device stays. Called on the thread that runs this backend; nothing may reference the
// window once it returns true. False: not supported (the caller ends the session instead).
virtual Bool SuspendServerWindow(void* window) {
(void)window;
return false;
}
// The surfaces SuspendServerWindow parked are rebuilt on `window` (a new native window,
// `width`x`height`), and their new extent is published as a creation publishes it. False:
// they could not be (the caller ends the session).
virtual Bool ResumeServerWindow(void* window, Uint32 width, Uint32 height) {
(void)window, (void)width, (void)height;
return false;
}
// `window` changed size to `width`x`height` (the display's report - the one size to publish).
// Its surfaces publish that extent to the client now, without waiting for a present; a
// swapchain that has to follow is rebuilt when no frame is half-recorded, else at the
// present. False: nothing was published (no surface on it, or not supported).
virtual Bool ServerWindowResized(void* window, Uint32 width, Uint32 height) {
(void)window, (void)width, (void)height;
return false;
}
protected:
~ServerHooks() = default;
};
// What only a client of a remote server asks of its backend. BackendObject_Remote implements it
// and returns it from BackendObject::AsClient; each default is the answer of a backend with no
// server behind it.
class ClientHooks {
public:
// ---- shared images ------------------------------------------------------------------
//
// A new image, allocated by the server; `out->Fd` is the caller's to close.
virtual Bool AllocateSharedImage(Uint32 width, Uint32 height, Uint32 fourcc, SharedImageExport* out) {
(void)width, (void)height, (void)fourcc, (void)out;
return false;
}
// Names the image `fd` was exported from (the caller keeps `fd`). False: not one of this
// server's images, or the size/format disagree.
virtual Bool ImportSharedImage(int fd, Uint32 width, Uint32 height, Uint32 fourcc, Uint64* outId) {
(void)fd, (void)width, (void)height, (void)fourcc, (void)outId;
return false;
}
// The same for a YUV fourcc, whose planes and hints `layout` gives. A YUV buffer the
// server did not export is taken too (its labelled CPU-copy fallback).
virtual Bool ImportSharedImagePlanes(int fd, Uint32 width, Uint32 height, Uint32 fourcc,
const SharedImageImportLayout& layout, Uint64* outId) {
(void)fd, (void)width, (void)height, (void)fourcc, (void)layout, (void)outId;
return false;
}
// Drops this client's reference (an allocation's or an import's).
virtual Bool ReleaseSharedImage(Uint64 id) {
(void)id;
return false;
}
// In the GL stream: the current context's default framebuffer, copied top row first into
// the image (BlitDefaultFramebufferToSharedImage on the server) - `region` of it, Full for
// all. Returns when it completed.
virtual Bool PresentToSharedImage(Uint64 id, const MG_Util::Damage::Region& region) {
(void)id, (void)region;
return false;
}
// In the GL stream: EGL_BUFFER_AGE_EXT of the current draw surface (QueryCurrentBufferAge
// on the server). False: the backend has no answer (the caller reports 0).
virtual Bool QueryBufferAge(Bool damageRegionFollows, EGLint* age) {
(void)damageRegionFollows, (void)age;
return false;
}
// In the GL stream: eglSetDamageRegionKHR for the current draw surface
// (SetCurrentDamageRegion on the server).
virtual Bool SetDamageRegion(const MG_Util::Damage::Region& region) {
(void)region;
return false;
}
// In the GL stream: the texture whose lifetime id is `textureLifetimeId` takes the image as
// its level-0 storage (glEGLImageTargetTexture2DOES), until its next storage definition.
// The caller has already defined level 0 as Width x Height RGBA8 with no data.
virtual Bool AttachSharedImageToTexture(Uint64 textureLifetimeId, Uint64 id) {
(void)textureLifetimeId, (void)id;
return false;
}
// In the GL stream, from glFlush/glFinish: PublishSharedImageAccesses on the server,
// returning once the fence is published. Skipped (true) when nothing reached the stream
// since the last one.
virtual Bool FlushSharedImageAccesses() { return false; }
// In the GL stream, from eglCreateSync(EGL_SYNC_NATIVE_FENCE_ANDROID) with no descriptor:
// ExportNativeFence on the server. `*fence` is the sync_file it handed back (the
// caller's to close), or -1 when it had none - the fenced work has then completed.
virtual Bool CreateNativeFence(int* fence) {
*fence = -1;
return false;
}
// From eglQuerySurface(EGL_WIDTH/EGL_HEIGHT): a backend whose surface extents
// change under the application (a remote server's window) applies what it has received.
virtual void RefreshSurfaceExtent(EGLSurface surface) { (void)surface; }
protected:
~ClientHooks() = default;
};
} // namespace MobileGL::MG_Backend
@@ -27,10 +27,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
Int ClampSamplesToBackendSupport(SizeT targetIndex, TextureInternalFormat logicalFormat, GLenum imageFormat,
Int samples);
class BackendObject_DirectGLES : public BackendObject {
class BackendObject_DirectGLES : public BackendObject, public ServerHooks {
public:
~BackendObject_DirectGLES() override;
ServerHooks* AsServer() override { return this; }
// The driver's verdict on this session's context (DirectGLES::LatchIfDeviceLost).
Bool LatchIfGpuFaulted() override;
void Initialize() override;
+4 -4
View File
@@ -150,7 +150,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// `debugKnob` false: the knob is not consulted here (the on-screen Present, so an injected loss
// lands on an offscreen client's shared-image present or readback, never on the compositor's).
Bool LatchIfDeviceLost(const char* site, Bool debugKnob = true);
// The apply loop's form of it (BackendObject::LatchIfGpuFaulted), asked each time the apply thread
// The apply loop's form of it (ServerHooks::LatchIfGpuFaulted), asked each time the apply thread
// wakes: the driver's report only, no debug knob, and no EGL ground-truth verification spent.
Bool LatchIfGuiltyBeforeApply();
#endif
@@ -536,7 +536,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image) on what the active unit has bound to
// GL_TEXTURE_2D. False when the driver refused it (its GL error is consumed).
Bool TargetBoundTexture2D(const EglImageRef& image);
// BackendObject::BlitDefaultFramebufferToSharedImage, for the calling thread's context.
// ServerHooks::BlitDefaultFramebufferToSharedImage, for the calling thread's context.
Bool BlitDefaultFramebufferTo(const SharedImageView& image, const MG_Util::Damage::Region& region);
// THE READER'S HALF of the image's sync state (SharedImageRegistry.h). Called on every use
@@ -556,12 +556,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// at Present and wherever the current native context stops being current (its reads can
// only be fenced from it). Nothing noted = nothing done.
void PublishPendingReads();
// IMPLICIT SYNC BY FLUSH (BackendObject::PublishSharedImageAccesses): everything the
// IMPLICIT SYNC BY FLUSH (ServerHooks::PublishSharedImageAccesses): everything the
// calling session used since its last boundary is published as written and read by one
// fence, and the session's first use of an image in each frame from now on also waits for
// the image's pending reads.
Bool PublishPendingAccesses();
// EGL_ANDROID_native_fence_sync's fence command (BackendObject::ExportNativeFence): a
// EGL_ANDROID_native_fence_sync's fence command (ServerHooks::ExportNativeFence): a
// sync_file of everything the current context submitted in `*fence` (-1: none, and the work
// is waited out here), and - for a session that writes images - the boundary
// PublishPendingAccesses makes.
@@ -20,11 +20,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
const MG_External::VulkanCapabilities& capabilities, FormatCapabilityCache& cache);
class BackendObject_DirectVulkan : public BackendObject {
class BackendObject_DirectVulkan : public BackendObject, public ServerHooks {
public:
BackendObject_DirectVulkan();
~BackendObject_DirectVulkan() override;
ServerHooks* AsServer() override { return this; }
void Initialize() override;
void BindSessionStateToThisThread() override;
// The GPU hang watch's verdict on this session's device (VulkanRenderer::LatchIfGpuHung).
@@ -214,7 +214,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void* getAhbProperties = nullptr; // PFN_vkGetAndroidHardwareBufferPropertiesANDROID
};
Bool GetWireAhbImport(WireAhbImport& out) const;
// SHARED IMAGES (BackendObject::BlitDefaultFramebufferToSharedImage): the active surface
// SHARED IMAGES (ServerHooks::BlitDefaultFramebufferToSharedImage): the active surface
// target's default framebuffer, copied into `image` top row first and scaled to its
// extent. Returns once the copy is submitted, with its fence published to the image
// (SharedImages::PublishWrite); waits for it on the CPU only on a device without sync_file
@@ -225,12 +225,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// renderer has written before - the rest of it is left as it is; a first write, a scaled
// or turned copy copies all of it.
Bool BlitDefaultFramebufferToSharedImage(const SharedImageView& image, const MG_Util::Damage::Region& region);
// IMPLICIT SYNC BY FLUSH (BackendObject::PublishSharedImageAccesses): every image acquired
// IMPLICIT SYNC BY FLUSH (ServerHooks::PublishSharedImageAccesses): every image acquired
// since the last boundary goes back to the foreign family, the recording is submitted with
// the exportable semaphore, and its sync_file is published to each as a write and a read.
// From then on an image's first acquire per frame also waits for its pending reads.
Bool PublishSharedImageAccesses();
// EGL_ANDROID_native_fence_sync's fence command (BackendObject::ExportNativeFence): the
// EGL_ANDROID_native_fence_sync's fence command (ServerHooks::ExportNativeFence): the
// submission's sync_file in `*fence` (-1: none, and the work is waited out here), and - for
// a session that writes images - the boundary PublishSharedImageAccesses makes.
Bool ExportNativeFence(int* fence);
+28 -23
View File
@@ -1194,8 +1194,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
}
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
// The display's dma-buf EGLImages went with it; so do their shared-image references.
MG_Backend::ClientHooks* client = backendObject->AsClient();
for (const Uint64 sharedImageId : state->TakeOrphanedSharedImages()) {
(void)backendObject->ReleaseSharedImage(sharedImageId);
if (client != nullptr) (void)client->ReleaseSharedImage(sharedImageId);
}
backendObject->ReleaseEGLResources();
}
@@ -1305,9 +1306,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (StreamGateActive()) StreamBindCallingThreadLocked();
#endif
EGLint age = 0;
auto* backendObject = MG_Backend::pActiveBackendObject.get();
MG_Backend::ClientHooks* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
const Bool regionFollows = DamageRegionSurfaces().count(surface) != 0;
if (backendObject == nullptr || !backendObject->QueryBufferAge(regionFollows, &age)) age = 0;
if (client == nullptr || !client->QueryBufferAge(regionFollows, &age)) age = 0;
*value = age;
return EGL_TRUE;
}
@@ -1315,8 +1316,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
// backend: let it bring the surface's record up to date first, so a caller that polls the size
// while it draws nothing still sees the change.
if (attribute == EGL_WIDTH || attribute == EGL_HEIGHT) {
if (auto* backendObject = MG_Backend::pActiveBackendObject.get())
backendObject->RefreshSurfaceExtent(surface);
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get()))
client->RefreshSurfaceExtent(surface);
}
return state->QuerySurface(display, surface, attribute, value) ? EGL_TRUE : EGL_FALSE;
}
@@ -1358,7 +1359,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (StreamGateActive()) StreamBindCallingThreadLocked();
#endif
DamageRegionSurfaces().insert(surface);
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) (void)backendObject->SetDamageRegion(region);
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get()))
(void)client->SetDamageRegion(region);
return EGL_TRUE;
}
@@ -1618,8 +1620,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
{
const GLStreamScope stream;
MG_Pipe::MGPipeClientDrainTextureUploads();
auto* backendObject = MG_Backend::pActiveBackendObject.get();
if (backendObject == nullptr || !backendObject->CreateNativeFence(&fence)) {
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
if (client == nullptr || !client->CreateNativeFence(&fence)) {
MGLOG_E_ONCE("eglCreateSync: the server did not answer a native fence; none is created");
state->SetError(EGL_BAD_ALLOC);
return EGL_NO_SYNC;
@@ -1690,13 +1692,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (known >= 0) return known != 0;
auto* backendObject = MG_Backend::pActiveBackendObject.get();
if (backendObject == nullptr) return false;
MG_Backend::ClientHooks* client = backendObject->AsClient();
MG_Backend::SharedImageExport probe{};
const Bool allocated =
backendObject->AllocateSharedImage(16, 16, MG_State::EGLState::EGLContext::kDrmFourccNv12, &probe);
const Bool allocated = client != nullptr && client->AllocateSharedImage(
16, 16, MG_State::EGLState::EGLContext::kDrmFourccNv12, &probe);
#if !defined(_WIN32)
if (probe.Fd >= 0) ::close(probe.Fd);
#endif
if (allocated && probe.Id != 0) (void)backendObject->ReleaseSharedImage(probe.Id);
if (allocated && probe.Id != 0) (void)client->ReleaseSharedImage(probe.Id);
answer.store(allocated ? 1 : 0, std::memory_order_release);
MGLOG_I("Shared images: the server %s YUV (NV12) images", allocated ? "holds" : "has no");
return allocated;
@@ -1751,7 +1754,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
// Uploads still waiting for a validate point go first: they are part of what is published.
MG_Pipe::MGPipeClientDrainTextureUploads();
#endif
if (backendObject != nullptr && !backendObject->FlushSharedImageAccesses()) {
MG_Backend::ClientHooks* client = MG_Backend::ClientHooksOf(backendObject);
if (backendObject != nullptr && (client == nullptr || !client->FlushSharedImageAccesses())) {
MGLOG_E_ONCE("glFlush: this session's shared-image accesses could not be published; another session may "
"read them early");
}
@@ -1778,7 +1782,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (!state->PrepareDmaBufImport(dpy, ctx, buffer, attrib_list, &request)) {
return EGL_NO_IMAGE;
}
auto* backendObject = GetBackendObject(state);
// GetBackendObject reports the missing backend; a backend that is no remote server's client
// imports nothing.
MG_Backend::ClientHooks* client = MG_Backend::ClientHooksOf(GetBackendObject(state));
Uint64 sharedImageId = 0;
Bool imported = false;
if (MG_State::EGLState::EGLContext::IsDmaBufFourccYuv(request.Fourcc)) {
@@ -1804,14 +1810,13 @@ namespace MobileGL::MG_Impl::EGLImpl {
layout.SitingY = request.SitingY;
layout.HasModifier = request.HasModifier;
layout.Modifier = request.Modifier;
imported = backendObject != nullptr &&
backendObject->ImportSharedImagePlanes(request.Fd, static_cast<Uint32>(request.Width),
static_cast<Uint32>(request.Height), request.Fourcc,
layout, &sharedImageId);
} else {
imported =
backendObject != nullptr &&
backendObject->ImportSharedImage(request.Fd, static_cast<Uint32>(request.Width),
client != nullptr && client->ImportSharedImagePlanes(request.Fd, static_cast<Uint32>(request.Width),
static_cast<Uint32>(request.Height),
request.Fourcc, layout, &sharedImageId);
} else {
imported = client != nullptr &&
client->ImportSharedImage(request.Fd, static_cast<Uint32>(request.Width),
static_cast<Uint32>(request.Height), request.Fourcc, &sharedImageId);
}
if (!imported || sharedImageId == 0) {
@@ -1831,7 +1836,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
.Height = request.Height,
.Fourcc = request.Fourcc,
.WireEpoch = wireEpoch});
if (image == EGL_NO_IMAGE) (void)backendObject->ReleaseSharedImage(sharedImageId);
if (image == EGL_NO_IMAGE) (void)client->ReleaseSharedImage(sharedImageId);
return image;
}
@@ -1857,8 +1862,8 @@ namespace MobileGL::MG_Impl::EGLImpl {
// A texture the image was bound to keeps its own reference on the server (EGLImage
// siblings outlive the image), so only this handle's goes.
if (sharedImageId != 0) {
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
(void)backendObject->ReleaseSharedImage(sharedImageId);
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get())) {
(void)client->ReleaseSharedImage(sharedImageId);
}
}
return EGL_TRUE;
+1 -1
View File
@@ -64,7 +64,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
EGLBoolean QueryDmaBufFormats(EGLDisplay dpy, EGLint max_formats, EGLint* formats, EGLint* num_formats);
EGLBoolean QueryDmaBufModifiers(EGLDisplay dpy, EGLint format, EGLint max_modifiers, EGLuint64KHR* modifiers,
EGLBoolean* external_only, EGLint* num_modifiers);
// Whether the active backend has shared images (MG_Backend::BackendObject::AllocateSharedImage):
// Whether the active backend has shared images (MG_Backend::ClientHooks::AllocateSharedImage):
// a split client's does, a monolith's does not. Decides the dma-buf EGL extensions, the
// Wayland linux-dmabuf presentation and GL_OES_EGL_image.
Bool SharedImagesAvailable();
+8 -6
View File
@@ -470,8 +470,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
api.marshalFlags(buffer.buffer, kBufferDestroy, nullptr, api.getVersion(buffer.buffer),
kMarshalFlagDestroy);
if (buffer.imageId != 0) {
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) {
(void)backendObject->ReleaseSharedImage(buffer.imageId);
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get())) {
(void)client->ReleaseSharedImage(buffer.imageId);
}
}
buffer = DmabufBuffer{};
@@ -487,15 +487,16 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
Release(buffer);
auto* backendObject = MG_Backend::pActiveBackendObject.get();
if (protocol == nullptr || dmabuf == nullptr || backendObject == nullptr) return false;
MG_Backend::ClientHooks* client = backendObject->AsClient();
MG_Backend::SharedImageExport image;
if (!backendObject->AllocateSharedImage(static_cast<Uint32>(width), static_cast<Uint32>(height), fourcc,
&image)) {
if (client == nullptr ||
!client->AllocateSharedImage(static_cast<Uint32>(width), static_cast<Uint32>(height), fourcc, &image)) {
MGLOG_E("Wayland: the server allocated no %dx%d shared image", width, height);
return false;
}
const auto dropImage = [&] {
if (image.Fd >= 0) close(image.Fd);
(void)backendObject->ReleaseSharedImage(image.Id);
(void)client->ReleaseSharedImage(image.Id);
};
if (image.Fd < 0 || image.Width != static_cast<Uint32>(width) ||
image.Height != static_cast<Uint32>(height)) {
@@ -871,7 +872,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
const SizeT index = static_cast<SizeT>(buffer - impl.dmabufBuffers);
const MG_Util::Damage::Region copy =
impl.dmabufDamage.TakeForWrite(index, damage, impl.width, impl.height);
if (backendObject->PresentToSharedImage(buffer->imageId, copy)) {
MG_Backend::ClientHooks* client = backendObject->AsClient();
if (client != nullptr && client->PresentToSharedImage(buffer->imageId, copy)) {
impl.Commit(buffer->buffer, damage);
buffer->busy = true;
return PresentResult::kPresented;
@@ -6219,9 +6219,8 @@ namespace MobileGL::MG_Impl::GLImpl {
if (unpackBuffer != 0) BindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(unpackBuffer));
}
auto* backendObject = MG_Backend::pActiveBackendObject.get();
if (backendObject == nullptr ||
!backendObject->AttachSharedImageToTexture(textureObject->GetLifetimeId(), sharedImageId)) {
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
if (client == nullptr || !client->AttachSharedImageToTexture(textureObject->GetLifetimeId(), sharedImageId)) {
MGLOG_E_ONCE("glEGLImageTargetTexture2DOES: shared image %llu could not be bound to texture %u",
static_cast<unsigned long long>(sharedImageId), textureObject->GetExternalIndex());
MG_State::pGLContext->RecordError(
+6 -5
View File
@@ -249,9 +249,9 @@ namespace MobileGL::MG_Impl::GLXImpl {
class BackendImageSource final : public X11Present::ImageSource {
public:
Bool Allocate(Uint32 width, Uint32 height, Uint32 fourcc, X11Present::Image* out) override {
auto* backend = MG_Backend::pActiveBackendObject.get();
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
MG_Backend::SharedImageExport image;
if (backend == nullptr || !backend->AllocateSharedImage(width, height, fourcc, &image)) return false;
if (client == nullptr || !client->AllocateSharedImage(width, height, fourcc, &image)) return false;
out->Id = image.Id;
out->Fd = image.Fd;
out->Width = image.Width;
@@ -263,11 +263,12 @@ namespace MobileGL::MG_Impl::GLXImpl {
return true;
}
void Release(Uint64 id) override {
if (auto* backend = MG_Backend::pActiveBackendObject.get()) (void)backend->ReleaseSharedImage(id);
if (auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get()))
(void)client->ReleaseSharedImage(id);
}
Bool CopyFrame(Uint64 id, const MG_Util::Damage::Region& region) override {
auto* backend = MG_Backend::pActiveBackendObject.get();
return backend != nullptr && backend->PresentToSharedImage(id, region);
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
return client != nullptr && client->PresentToSharedImage(id, region);
}
};
+8 -4
View File
@@ -86,8 +86,9 @@ MOBILEGL_EXPORT int mobilegl_shared_image_allocate(uint32_t width, uint32_t heig
const std::lock_guard<std::mutex> lock(g_apiMutex);
BackendObject* backend = ActiveBackend(true);
if (backend == nullptr) return -EIO;
MobileGL::MG_Backend::ClientHooks* client = backend->AsClient();
MobileGL::MG_Backend::SharedImageExport image{};
if (!backend->AllocateSharedImage(width, height, fourcc, &image) || image.Fd < 0) {
if (client == nullptr || !client->AllocateSharedImage(width, height, fourcc, &image) || image.Fd < 0) {
if (image.Fd >= 0) ::close(image.Fd);
return -EIO;
}
@@ -115,8 +116,9 @@ MOBILEGL_EXPORT int mobilegl_shared_image_import(int fd, uint32_t width, uint32_
const std::lock_guard<std::mutex> lock(g_apiMutex);
BackendObject* backend = ActiveBackend(true);
if (backend == nullptr) return -EIO;
MobileGL::MG_Backend::ClientHooks* client = backend->AsClient();
uint64_t id = 0;
if (!backend->ImportSharedImage(fd, width, height, fourcc, &id)) return -EIO;
if (client == nullptr || !client->ImportSharedImage(fd, width, height, fourcc, &id)) return -EIO;
*out_id = id;
return 0;
}
@@ -130,6 +132,7 @@ MOBILEGL_EXPORT int mobilegl_shared_image_import_planes(int fd, uint32_t width,
const std::lock_guard<std::mutex> lock(g_apiMutex);
BackendObject* backend = ActiveBackend(true);
if (backend == nullptr) return -EIO;
MobileGL::MG_Backend::ClientHooks* client = backend->AsClient();
MobileGL::MG_Backend::SharedImageImportLayout layout;
layout.PlaneCount = 2;
for (int i = 0; i < 2; ++i) {
@@ -140,7 +143,7 @@ MOBILEGL_EXPORT int mobilegl_shared_image_import_planes(int fd, uint32_t width,
layout.HasModifier = planes->modifier != kModifierInvalid;
layout.Modifier = planes->modifier;
uint64_t id = 0;
if (!backend->ImportSharedImagePlanes(fd, width, height, fourcc, layout, &id)) return -EIO;
if (client == nullptr || !client->ImportSharedImagePlanes(fd, width, height, fourcc, layout, &id)) return -EIO;
*out_id = id;
return 0;
}
@@ -153,5 +156,6 @@ MOBILEGL_EXPORT int mobilegl_shared_image_release(uint64_t id) {
const std::lock_guard<std::mutex> lock(g_apiMutex);
BackendObject* backend = ActiveBackend(false);
if (backend == nullptr) return -ENODEV;
return backend->ReleaseSharedImage(id) ? 0 : -EIO;
MobileGL::MG_Backend::ClientHooks* client = backend->AsClient();
return client != nullptr && client->ReleaseSharedImage(id) ? 0 : -EIO;
}
+1 -1
View File
@@ -15,7 +15,7 @@
// gen_pipe.py's block regexes end at a blank line: keep the empty line after each macro.
// clang-format off
// X(Verb, Class) - one row per function-pointer member of MG_Backend::GLFunctionsTable (BackendObject.h),
// X(Verb, Class) - one row per function-pointer member of MG_Backend::GLFunctionsTable (BackendFunctions.h),
// in declaration order. gen_pipe.py parses that struct and refuses a row set that is not exactly its member set.
#define MGP_FILL_VERB_LIST(X) \
X(DrawArrays, kDraw) \
+1 -1
View File
@@ -1602,7 +1602,7 @@ namespace MobileGL::MG_Pipe {
MGP_ASSERT_POD(MGPStreamOutputBind, 16);
// set_storage_block_binding = glShaderStorageBlockBinding (P5b i1). The block is named,
// not indexed: BackendObject.h says why - the application's index is the frontend
// not indexed: BackendFunctions.h says why - the application's index is the frontend
// interface-query enumeration's and no backend shares that index space, the NAME is the
// one coordinate all three agree on. The name is bytes of unbounded length, so it rides
// the ONE client -> server byte carrier, SEG_STAGE, as a kHasBlob blob: Name.Size is
+1 -1
View File
@@ -13,7 +13,7 @@
// (DirectGLES.cpp documents that behaviour where it does the same comparison itself).
//
// Hand maintained alongside MGPipeTypes.h, MGPipeValueTypes.h, MGPipeHostSpan.h and
// MG_Backend/BackendObject.h. Adding a member to one of these structs without adding it here
// MG_Backend/BackendFunctions.h. Adding a member to one of these structs without adding it here
// would make the comparator blind to it, so gen_pipe.py asserts - in both modes, hence in
// pipe-gates - that every list below names exactly the direct data members of its struct
// (P1 brief D8; a member named Pad<n> is padding and is not listed).
@@ -26,11 +26,13 @@
namespace MobileGL::MG_Remote::Client {
class BackendObject_Remote final : public MG_Backend::BackendObject {
class BackendObject_Remote final : public MG_Backend::BackendObject, public MG_Backend::ClientHooks {
public:
BackendObject_Remote();
~BackendObject_Remote() override;
MG_Backend::ClientHooks* AsClient() override { return this; }
// ---- the eight pure virtuals ------------------------------------------------------
void Initialize() override;
Bool InitCapabilities() override;
+1 -1
View File
@@ -58,7 +58,7 @@ namespace MobileGL::MG_Remote::Client {
// F1 (P7 wave 2): IT NEVER ANSWERS FROM A PLACEHOLDER ANY MORE. See RequireFirstSnapshot.
Bool ServerConsumes(Uint64 subsystemBit) const;
// GLFunctionsTable::PrefersCpuXfbPrimitiveAccounting (BackendObject.h) does NOT
// GLFunctionsTable::PrefersCpuXfbPrimitiveAccounting (BackendFunctions.h) does NOT
// ride in MGPCaps::Dynamic - it is a member of the function table, which is precisely
// the thing a split client never receives. Its only non-test client reader is
// GL_Query.cpp:221, and under split it must be answered from kCapCpuXfbPrimitiveAccounting.
+18 -18
View File
@@ -509,45 +509,45 @@ namespace MobileGL::MG_Remote::Server {
return holder.Release(op.ImageId);
case MG_Pipe::kMGPSharedImagePresent: {
SI::ImageRef image = holder.Get(op.ImageId);
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
if (image == nullptr || backend == nullptr) return false;
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
if (image == nullptr || server == nullptr) return false;
MG_Backend::SharedImageView view;
view.Id = image->Id;
view.Width = image->Width;
view.Height = image->Height;
view.Fourcc = image->Fourcc;
view.NativeBuffer = image->Native;
if (!backend->BlitDefaultFramebufferToSharedImage(view, DamageOfRecord(op.DamageCount, op.Damage)))
if (!server->BlitDefaultFramebufferToSharedImage(view, DamageOfRecord(op.DamageCount, op.Damage)))
return false;
fill(*image);
return true;
}
case MG_Pipe::kMGPSharedImageQueryBufferAge: {
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
if (backend == nullptr) return false;
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
if (server == nullptr) return false;
reply.BufferAge =
backend->QueryCurrentBufferAge((op.Format & MG_Pipe::kMGPBufferAgeDamageRegionFollows) != 0);
server->QueryCurrentBufferAge((op.Format & MG_Pipe::kMGPBufferAgeDamageRegionFollows) != 0);
return true;
}
case MG_Pipe::kMGPSharedImageSetDamageRegion: {
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
if (backend == nullptr) return false;
return backend->SetCurrentDamageRegion(DamageOfRecord(op.DamageCount, op.Damage));
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
if (server == nullptr) return false;
return server->SetCurrentDamageRegion(DamageOfRecord(op.DamageCount, op.Damage));
}
case MG_Pipe::kMGPSharedImageFlush: {
// The answer is posted after the fence is published: the client commits the buffer to
// its compositor only after this returns, so the compositor's session finds the write.
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
if (backend == nullptr) return false;
return backend->PublishSharedImageAccesses();
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
if (server == nullptr) return false;
return server->PublishSharedImageAccesses();
}
case MG_Pipe::kMGPSharedImageNativeFence: {
// A descriptor of the session's work, and an image writer's flush (MGPipeTypes.h). Like
// an Allocate's, the descriptor goes out BEFORE the reply is posted.
MG_Backend::BackendObject* backend = ServerLoopInstance().Backend();
if (backend == nullptr) return false;
MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks();
if (server == nullptr) return false;
int fence = -1;
if (!backend->ExportNativeFence(&fence)) return false;
if (!server->ExportNativeFence(&fence)) return false;
#if !defined(_WIN32)
if (fence >= 0) {
m_lastNativeFence.Reset(::dup(fence));
@@ -626,8 +626,8 @@ namespace MobileGL::MG_Remote::Server {
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
(void)MG_Backend::DirectGLES::MakeNativeContextCurrentForBoundToken();
// The backend half for one that keeps a single native surface binding (Magma).
if (MG_Backend::BackendObject* backend = ServerLoopInstance().Backend()) {
backend->OnClientContextBound(bind.ClientContextToken);
if (MG_Backend::ServerHooks* server = ServerLoopInstance().BackendHooks()) {
server->OnClientContextBound(bind.ClientContextToken);
}
}
#endif
@@ -767,7 +767,7 @@ namespace MobileGL::MG_Remote::Server {
// Clear -> Clear, DrawVbo -> DrawArrays, ReadPixels -> ReadPixels, Blit -> BlitFramebuffer.
//
// PRESENT IS DELIBERATELY NOT ONE, although contract 7 puts it in class B: Present and
// SetSwapInterval sit outside GLFunctionsTable (BackendObject.h) and read no frontend state,
// SetSwapInterval sit outside GLFunctionsTable (BackendFunctions.h) and read no frontend state,
// so FillPoints.def's MGP_FILL_VERB_LIST has no row for them. There is no MGPipeVerb::Present,
// and stamping there would retire the previous verb's answers with nothing to put in their place.
void PipeApplier::StampVerbBoundary(MG_Pipe::MGPWireOp op) {
+14 -9
View File
@@ -393,6 +393,10 @@ namespace MobileGL::MG_Remote::Server {
return m_backend.get();
}
MG_Backend::ServerHooks* ServerLoop::BackendHooks() {
return MG_Backend::ServerHooksOf(m_backend.get());
}
MobileGLResult ServerLoop::InstallBackendForTesting(UniquePtr<MG_Backend::BackendObject> backend) {
if (m_backend != nullptr || backend == nullptr || m_running.load(std::memory_order_acquire)) {
return MOBILEGL_ERR_INVALID_ARGUMENT;
@@ -644,9 +648,9 @@ namespace MobileGL::MG_Remote::Server {
session.PeerSharedSegments() ? "shared segments" : "stream", policy.rule);
// A served session shares its process with other sessions: its backend's own native state
// is bound to this thread before anything can apply a record (BackendObject.h). The inproc
// is bound to this thread before anything can apply a record (BackendRoles.h). The inproc
// one-process shape has no runtime and keeps the process-wide state.
if (m_runtime != nullptr && m_backend != nullptr) m_backend->BindSessionStateToThisThread();
if (m_runtime != nullptr && BackendHooks() != nullptr) BackendHooks()->BindSessionStateToThisThread();
// The decoder is built HERE, on this thread, because PipeWireDecoder is "not thread
// safe: one decoder on the apply thread, by construction" and its constructor installs
@@ -722,9 +726,9 @@ namespace MobileGL::MG_Remote::Server {
// The head is loaded BEFORE the poll, so every record below it was in the ring when the
// poll ran and the drain need not poll for it again (DrainRing's `polledHead`).
std::uint64_t polledHead = 0;
if (m_backend != nullptr && !SessionLatched()) {
if (BackendHooks() != nullptr && !SessionLatched()) {
polledHead = signals.CmdHead != nullptr ? signals.CmdHead->load(std::memory_order_acquire) : 0;
(void)m_backend->LatchIfGpuFaulted();
(void)BackendHooks()->LatchIfGpuFaulted();
}
if (eventRingHasRoom()) DrainRing(polledHead);
if (stopOrForfeit()) break;
@@ -1141,7 +1145,8 @@ namespace MobileGL::MG_Remote::Server {
record.kind == static_cast<std::uint16_t>(MG_Pipe::MGPWireOp::Present)) {
if (unseen) polledHead = signals.CmdHead->load(std::memory_order_acquire);
recordsSinceFaultCheck = 0;
if (m_backend->LatchIfGpuFaulted()) return false;
MG_Backend::ServerHooks* server = BackendHooks();
if (server != nullptr && server->LatchIfGpuFaulted()) return false;
}
}
if (SessionLatched()) return false;
@@ -2185,7 +2190,7 @@ namespace MobileGL::MG_Remote::Server {
}
void ServerLoop::PublishServerWindowResize() {
if (!m_holdsWindowLease || m_backend == nullptr) return;
if (!m_holdsWindowLease || BackendHooks() == nullptr) return;
// THE DISPLAY'S EXTENT, READ NOW: the latest report wins over any a burst of them left queued.
void* window = nullptr;
Uint32 width = 0;
@@ -2197,7 +2202,7 @@ namespace MobileGL::MG_Remote::Server {
const Uint32 previousWidth = m_publishedWindowWidth;
const Uint32 previousHeight = m_publishedWindowHeight;
NoteServerWindowExtent(window, width, height);
const Bool published = m_backend->ServerWindowResized(window, width, height);
const Bool published = BackendHooks()->ServerWindowResized(window, width, height);
if (published) m_serverWindowResizes.fetch_add(1, std::memory_order_acq_rel);
MGLOG_I("MG_Remote server: server window %p resized %ux%u -> %ux%u; %s", window, previousWidth, previousHeight,
width, height,
@@ -2228,7 +2233,7 @@ namespace MobileGL::MG_Remote::Server {
// window surfaces, Magma the swapchain and VkSurfaceKHR - keeps its contexts, objects and
// device, and the session waits for the display's next window with every record still applied.
void* const window = m_leasedWindow;
if (m_backend != nullptr && m_backend->SuspendServerWindow(window)) {
if (BackendHooks() != nullptr && BackendHooks()->SuspendServerWindow(window)) {
// N-3: the native binding moved off the window's surface.
ForgetCurrentTuple();
m_serverWindowSuspended.store(true, std::memory_order_release);
@@ -2277,7 +2282,7 @@ namespace MobileGL::MG_Remote::Server {
Bool ServerLoop::ResumeServerWindowOnLease(const ServerWindowLease& lease) {
// Before the backend: its republished extent is the server window's (MGPipeServerOwnedWindow).
NoteServerWindowExtent(lease.window, lease.width, lease.height);
if (m_backend == nullptr || !m_backend->ResumeServerWindow(lease.window, lease.width, lease.height)) {
if (BackendHooks() == nullptr || !BackendHooks()->ResumeServerWindow(lease.window, lease.width, lease.height)) {
LatchServerWindowLost("the suspended on-screen session's surfaces could not be rebuilt on the new server "
"window");
return false;
+3
View File
@@ -111,6 +111,9 @@ namespace MobileGL::MG_Remote::Server {
// lookups - take the format cache as a parameter instead. That is six functions across
// two files, and it is why no thread-keyed shim is needed for MOBILEGL_BUILD_DISAGGREGATED_INPROC.
MG_Backend::BackendObject* Backend();
// Its server hooks (BackendRoles.h); null with no backend. Asked at each use, never kept: a
// test may swap the backend under a running apply thread.
MG_Backend::ServerHooks* BackendHooks();
// Runs one blocking control request on the apply thread and waits for it; every Server*
// EGL forwarder funnels through here. Blocking rather than async, because each caller
@@ -314,9 +314,9 @@ namespace {
return Note(r, "the owner's context did not go current");
r.initialized = 1;
auto* backend = MG_Backend::pActiveBackendObject.get();
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
MG_Backend::SharedImageExport image;
if (backend == nullptr || !backend->AllocateSharedImage(kImageWidth, kImageHeight, kXrgb8888, &image) ||
if (client == nullptr || !client->AllocateSharedImage(kImageWidth, kImageHeight, kXrgb8888, &image) ||
image.Fd < 0)
return Note(r, "no shared image was allocated");
r.allocated = 1;
@@ -339,12 +339,12 @@ namespace {
r.pixelAfter = ClearAndRead(1.0f, 0.0f, 0.0f);
Uint64 again = 0;
r.stillIdentified =
backend->ImportSharedImage(image.Fd, image.Width, image.Height, image.Fourcc, &again) && again == image.Id
client->ImportSharedImage(image.Fd, image.Width, image.Height, image.Fourcc, &again) && again == image.Id
? 1
: 0;
if (again != 0) (void)backend->ReleaseSharedImage(again);
if (again != 0) (void)client->ReleaseSharedImage(again);
::close(image.Fd);
(void)backend->ReleaseSharedImage(image.Id);
(void)client->ReleaseSharedImage(image.Id);
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
(void)EGL::DestroyContext(dpy, context);
(void)EGL::DestroySurface(dpy, surface);
+11 -11
View File
@@ -163,9 +163,9 @@ namespace {
// A window pixmap: a shared image (what gbm_bo_create gives glamor), bound through
// EGL_LINUX_DMA_BUF_EXT the way xwl_glamor_gbm_create_pixmap_for_bo does.
auto* backend = MG_Backend::pActiveBackendObject.get();
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
MG_Backend::SharedImageExport image;
if (backend != nullptr && backend->AllocateSharedImage(64, 32, kXrgb8888, &image) && image.Fd >= 0) {
if (client != nullptr && client->AllocateSharedImage(64, 32, kXrgb8888, &image) && image.Fd >= 0) {
const EGLAttrib attribs[] = {EGL_WIDTH,
64,
EGL_HEIGHT,
@@ -183,16 +183,16 @@ namespace {
r.imageCreated = eglImage != EGL_NO_IMAGE ? 1 : 0;
if (eglImage != EGL_NO_IMAGE) (void)EGL::DestroyImage(dpy, eglImage);
::close(image.Fd);
(void)backend->ReleaseSharedImage(image.Id);
(void)client->ReleaseSharedImage(image.Id);
}
// glamor's block handler: glFlush, which in an X server is where its renders become visible
// to the compositor. The verb must be answered (an unknown verb is refused).
using ClearFn = void (*)(unsigned);
if (const auto clear = reinterpret_cast<ClearFn>(EGL::GetProcAddress("glClear"))) clear(0x4000);
r.flushAnswered = backend != nullptr && backend->FlushSharedImageAccesses() ? 1 : 0;
r.flushAnswered = client != nullptr && client->FlushSharedImageAccesses() ? 1 : 0;
// Nothing reached the stream since: no round trip, still a success.
r.flushAgainAnswered = backend != nullptr && backend->FlushSharedImageAccesses() ? 1 : 0;
r.flushAgainAnswered = client != nullptr && client->FlushSharedImageAccesses() ? 1 : 0;
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
(void)EGL::DestroyContext(dpy, ctx);
@@ -272,7 +272,7 @@ namespace {
// A GBM scanout buffer (a shared image), bound as a texture and rendered into through a
// framebuffer - and then no glFlush, as Chrome does when it has native fences.
auto* backend = MG_Backend::pActiveBackendObject.get();
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
MG_Backend::SharedImageExport image;
EGLImage eglImage = EGL_NO_IMAGE;
using GenFn = void (*)(int, unsigned*);
@@ -291,7 +291,7 @@ namespace {
const auto checkStatus = reinterpret_cast<StatusFn>(EGL::GetProcAddress("glCheckFramebufferStatus"));
const auto clearColor = reinterpret_cast<ClearColorFn>(EGL::GetProcAddress("glClearColor"));
const auto clear = reinterpret_cast<ClearFn>(EGL::GetProcAddress("glClear"));
if (backend != nullptr && backend->AllocateSharedImage(64, 32, kArgb8888, &image) && image.Fd >= 0) {
if (client != nullptr && client->AllocateSharedImage(64, 32, kArgb8888, &image) && image.Fd >= 0) {
const EGLAttrib attribs[] = {EGL_WIDTH,
64,
EGL_HEIGHT,
@@ -354,7 +354,7 @@ namespace {
if (eglImage != EGL_NO_IMAGE) (void)EGL::DestroyImage(dpy, eglImage);
if (image.Fd >= 0) ::close(image.Fd);
if (backend != nullptr && image.Id != 0) (void)backend->ReleaseSharedImage(image.Id);
if (client != nullptr && image.Id != 0) (void)client->ReleaseSharedImage(image.Id);
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
(void)EGL::DestroyContext(dpy, ctx);
(void)EGL::Terminate(dpy);
@@ -399,12 +399,12 @@ namespace {
// The GL extension list is where a split client asks the server whether it holds YUV images.
r.externalImage = HasGlExtension("GL_OES_EGL_image_external") ? 1 : 0;
// Asked anyway (a C-ABI or GLX caller does not look at the extensions first): refused.
auto* backend = MG_Backend::pActiveBackendObject.get();
auto* client = MG_Backend::ClientHooksOf(MG_Backend::pActiveBackendObject.get());
MG_Backend::SharedImageExport image;
if (backend != nullptr && backend->AllocateSharedImage(64, 32, kXrgb8888, &image)) {
if (client != nullptr && client->AllocateSharedImage(64, 32, kXrgb8888, &image)) {
r.allocated = 1;
if (image.Fd >= 0) ::close(image.Fd);
(void)backend->ReleaseSharedImage(image.Id);
(void)client->ReleaseSharedImage(image.Id);
}
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
(void)EGL::DestroyContext(dpy, ctx);
@@ -131,8 +131,9 @@ struct ErrorPeer : Codec::ReplySink {
}
};
struct CapsPeer : MG_Backend::BackendObject {
struct CapsPeer : MG_Backend::BackendObject, MG_Backend::ServerHooks {
MG_Backend::DynamicBackendParameters dynamic{};
ServerHooks* AsServer() override { return this; }
MG_Backend::GlobalBackendFunctionsTable table{};
RendererInfo renderer{};
void Initialize() override {}
+5 -4
View File
@@ -3634,7 +3634,7 @@ TEST(ServerLoopLatchTest, ALostServerWindowIsReleasedMidBatchWhileTheClientStrea
}
// =====================================================================================
// A SESSION WHOSE OWN GPU WORK HUNG THE GPU APPLIES NOTHING MORE (BackendObject::LatchIfGpuFaulted)
// A SESSION WHOSE OWN GPU WORK HUNG THE GPU APPLIES NOTHING MORE (ServerHooks::LatchIfGpuFaulted)
// =====================================================================================
namespace {
@@ -3642,9 +3642,10 @@ namespace {
// watch's thread (Magma) - while the session's next frame is already queued. The double counts
// the frames that reach it and, once `faulted` is set, latches its session from the apply thread
// as both real backends do.
class GpuFaultBackendDouble final : public MG_Backend::BackendObject {
class GpuFaultBackendDouble final : public MG_Backend::BackendObject, public MG_Backend::ServerHooks {
public:
GpuFaultBackendDouble() { m_functions.Present = &Present; }
ServerHooks* AsServer() override { return this; }
Bool LatchIfGpuFaulted() override {
asked.fetch_add(1, std::memory_order_acq_rel);
if (!faulted.load(std::memory_order_acquire)) return false;
@@ -3758,10 +3759,10 @@ namespace {
// window - presents from then on reach no window - and by taking the new one. Its Present counts
// the frames and the ones that came while no window was held. Statics, because the loop owns
// (and destroys) the object and each case runs in its own death-test child.
class WindowBackendDouble final : public MG_Backend::BackendObject {
class WindowBackendDouble final : public MG_Backend::BackendObject, public MG_Backend::ServerHooks {
public:
explicit WindowBackendDouble(Bool canSuspend) : m_canSuspend(canSuspend) { m_functions.Present = &Present; }
ServerHooks* AsServer() override { return this; }
void Initialize() override {}
Bool InitCapabilities() override { return true; }
Bool InitWindowSurface() override { return true; }