diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 70977197..94396d9c 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include 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& pActiveBackendObject; } // namespace MG_Backend } // namespace MobileGL diff --git a/MobileGL/MG_Backend/BackendRoles.h b/MobileGL/MG_Backend/BackendRoles.h new file mode 100644 index 00000000..3941e753 --- /dev/null +++ b/MobileGL/MG_Backend/BackendRoles.h @@ -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 +#include +#include + +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 diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h index 5dfa5251..1590ded0 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h @@ -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; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 19cf563c..74af6ddd 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -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. diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h index 85d1e4f9..f8755bf6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h @@ -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). diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index ee7925cc..d374047f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -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); diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 38af821d..cf45d2c2 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -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(request.Width), - static_cast(request.Height), request.Fourcc, - layout, &sharedImageId); - } else { imported = - backendObject != nullptr && - backendObject->ImportSharedImage(request.Fd, static_cast(request.Width), + client != nullptr && client->ImportSharedImagePlanes(request.Fd, static_cast(request.Width), + static_cast(request.Height), + request.Fourcc, layout, &sharedImageId); + } else { + imported = client != nullptr && + client->ImportSharedImage(request.Fd, static_cast(request.Width), static_cast(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; diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.h b/MobileGL/MG_Impl/EGLImpl/EGLImpl.h index 400cbc7f..cca48d61 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.h +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.h @@ -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(); diff --git a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp index f1efd23c..00471842 100644 --- a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp +++ b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp @@ -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(width), static_cast(height), fourcc, - &image)) { + if (client == nullptr || + !client->AllocateSharedImage(static_cast(width), static_cast(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(width) || image.Height != static_cast(height)) { @@ -871,7 +872,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { const SizeT index = static_cast(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; diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index aa3114f0..c1684086 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -6219,9 +6219,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (unpackBuffer != 0) BindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast(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(sharedImageId), textureObject->GetExternalIndex()); MG_State::pGLContext->RecordError( diff --git a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp index f506c610..0de88cfa 100644 --- a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp +++ b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp @@ -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); } }; diff --git a/MobileGL/MG_Impl/SharedImageApi.cpp b/MobileGL/MG_Impl/SharedImageApi.cpp index dc28f8d7..6375d86e 100644 --- a/MobileGL/MG_Impl/SharedImageApi.cpp +++ b/MobileGL/MG_Impl/SharedImageApi.cpp @@ -86,8 +86,9 @@ MOBILEGL_EXPORT int mobilegl_shared_image_allocate(uint32_t width, uint32_t heig const std::lock_guard 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 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 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 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; } diff --git a/MobileGL/MG_Pipe/FillPoints.def b/MobileGL/MG_Pipe/FillPoints.def index 496228f3..3e90b9fa 100644 --- a/MobileGL/MG_Pipe/FillPoints.def +++ b/MobileGL/MG_Pipe/FillPoints.def @@ -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) \ diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index 4360486e..2192031c 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -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 diff --git a/MobileGL/MG_Pipe/PipeFields.def b/MobileGL/MG_Pipe/PipeFields.def index 053c53d2..0dde83f8 100644 --- a/MobileGL/MG_Pipe/PipeFields.def +++ b/MobileGL/MG_Pipe/PipeFields.def @@ -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 is padding and is not listed). diff --git a/MobileGL/MG_Remote/Client/BackendObject_Remote.h b/MobileGL/MG_Remote/Client/BackendObject_Remote.h index 18d82409..96c4cda5 100644 --- a/MobileGL/MG_Remote/Client/BackendObject_Remote.h +++ b/MobileGL/MG_Remote/Client/BackendObject_Remote.h @@ -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; diff --git a/MobileGL/MG_Remote/Client/CapsMirror.h b/MobileGL/MG_Remote/Client/CapsMirror.h index c026685d..2ebeeb51 100644 --- a/MobileGL/MG_Remote/Client/CapsMirror.h +++ b/MobileGL/MG_Remote/Client/CapsMirror.h @@ -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. diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index 02176cd6..48a7fbbf 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -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) { diff --git a/MobileGL/MG_Remote/Server/ServerLoop.cpp b/MobileGL/MG_Remote/Server/ServerLoop.cpp index 7b4b011e..bef7e72b 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.cpp +++ b/MobileGL/MG_Remote/Server/ServerLoop.cpp @@ -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 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(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; diff --git a/MobileGL/MG_Remote/Server/ServerLoop.h b/MobileGL/MG_Remote/Server/ServerLoop.h index 9a792b3f..ffd777c2 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.h +++ b/MobileGL/MG_Remote/Server/ServerLoop.h @@ -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 diff --git a/MobileGL/MG_Test/Wire/CompositorRecoveryTest.cpp b/MobileGL/MG_Test/Wire/CompositorRecoveryTest.cpp index 2a4f7846..5f64c1e0 100644 --- a/MobileGL/MG_Test/Wire/CompositorRecoveryTest.cpp +++ b/MobileGL/MG_Test/Wire/CompositorRecoveryTest.cpp @@ -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); diff --git a/MobileGL/MG_Test/Wire/GlamorEglSequenceTest.cpp b/MobileGL/MG_Test/Wire/GlamorEglSequenceTest.cpp index f0a4c6a9..d5086086 100644 --- a/MobileGL/MG_Test/Wire/GlamorEglSequenceTest.cpp +++ b/MobileGL/MG_Test/Wire/GlamorEglSequenceTest.cpp @@ -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(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(EGL::GetProcAddress("glCheckFramebufferStatus")); const auto clearColor = reinterpret_cast(EGL::GetProcAddress("glClearColor")); const auto clear = reinterpret_cast(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); diff --git a/MobileGL/MG_Test/Wire/RemoteClientControls.inc b/MobileGL/MG_Test/Wire/RemoteClientControls.inc index c5e37ad3..c6a44e87 100644 --- a/MobileGL/MG_Test/Wire/RemoteClientControls.inc +++ b/MobileGL/MG_Test/Wire/RemoteClientControls.inc @@ -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 {} diff --git a/MobileGL/MG_Test/Wire/ServerLoopTest.cpp b/MobileGL/MG_Test/Wire/ServerLoopTest.cpp index 6f5d971a..504f25d4 100644 --- a/MobileGL/MG_Test/Wire/ServerLoopTest.cpp +++ b/MobileGL/MG_Test/Wire/ServerLoopTest.cpp @@ -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; }