mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-16 06:23:23 +09:00
[Feature] (SharedImage): server-allocated AHB images over a shared_image pipe verb, with dma-buf fds on the aux socket
This commit is contained in:
@@ -675,6 +675,9 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
MobileGL/MG_Remote/Server/PairAcceptor.cpp
|
||||
# P11 B2: T0's Offers on the aux socket, matched to their map_persistent records by seq.
|
||||
MobileGL/MG_Remote/Server/AdoptInbox.cpp
|
||||
# Shared images: server-allocated AHardwareBuffers exported to clients as dma-buf descriptors
|
||||
# (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md).
|
||||
MobileGL/MG_Remote/Server/SharedImageRegistry.cpp
|
||||
MobileGL/MG_Remote/Server/ServerSpawn.cpp
|
||||
# P5f fc: the value frame that replaced the control mailbox's function-pointer payload,
|
||||
# and its codec against protocol.fbs's SurfaceOp/SurfaceReply (the spawn arm's seam).
|
||||
|
||||
@@ -583,10 +583,82 @@ namespace MobileGL {
|
||||
Uint32 Height = 0;
|
||||
};
|
||||
|
||||
// SHARED IMAGES (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md): server-allocated colour
|
||||
// buffers exported to clients as dma-buf descriptors.
|
||||
//
|
||||
// The server-side view of one image, as a backend sees it: `NativeBuffer` is the image's
|
||||
// AHardwareBuffer (null where there is none), `Fourcc` a DRM fourcc - ABGR8888 (R, G, B, A in
|
||||
// memory, GL RGBA8) or XBGR8888 (alpha ignored).
|
||||
struct SharedImageView {
|
||||
Uint64 Id = 0;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
Uint32 Fourcc = 0;
|
||||
void* NativeBuffer = nullptr;
|
||||
};
|
||||
|
||||
// The client-side answer of an allocation: the image's id and its exported descriptor
|
||||
// (`Fd`, owned by the caller), with the plane layout a linux-dmabuf wl_buffer is created with.
|
||||
struct SharedImageExport {
|
||||
Uint64 Id = 0;
|
||||
int Fd = -1;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
Uint32 Fourcc = 0;
|
||||
Uint32 Stride = 0;
|
||||
Uint32 Offset = 0;
|
||||
Uint64 Modifier = 0;
|
||||
};
|
||||
|
||||
class BackendObject {
|
||||
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. Returns once the copy has COMPLETED on the GPU (the client attaches the buffer
|
||||
// to a wl_surface right after, and nothing else orders the compositor's read). The
|
||||
// application's GL state is untouched. False = not supported / failed (logged).
|
||||
virtual Bool BlitDefaultFramebufferToSharedImage(const SharedImageView& image) {
|
||||
(void)image;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- 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;
|
||||
}
|
||||
// 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). Returns when it completed.
|
||||
virtual Bool PresentToSharedImage(Uint64 id) {
|
||||
(void)id;
|
||||
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;
|
||||
}
|
||||
|
||||
virtual void Initialize() = 0;
|
||||
virtual Bool InitCapabilities() = 0;
|
||||
virtual Bool InitWindowSurface() = 0;
|
||||
|
||||
@@ -1978,6 +1978,65 @@ namespace MobileGL::MG_Pipe {
|
||||
};
|
||||
MGP_ASSERT_POD(MGPBindContext, 8);
|
||||
|
||||
// shared_image = opcode 85 (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md). Colour
|
||||
// buffers the SERVER allocates and exports as dma-buf descriptors, so one client's frames
|
||||
// reach another client's textures without leaving GPU memory. One row, five operations, all
|
||||
// answered through the reply slot with an MGPSharedImageReply:
|
||||
//
|
||||
// Allocate a new Width x Height Format image; the reply names it, and its descriptor
|
||||
// reaches the client on the aux socket BEFORE the reply is posted.
|
||||
// Import the descriptor the client queued on the aux socket (sideband: a
|
||||
// MGPSharedImageFdOffer naming this record's seq) is identified as an image this
|
||||
// server exported; the reply names it. Width/Height/Format must match it.
|
||||
// Release the session's reference to ImageId goes.
|
||||
// Present the current context's default framebuffer is copied, top row first, into
|
||||
// ImageId (a window frame becoming a wl_buffer's contents); the reply is posted
|
||||
// when the copy has completed on the GPU.
|
||||
// Attach the texture Texture's storage becomes ImageId (glEGLImageTargetTexture2DOES);
|
||||
// it lasts until the texture's next storage-defining respecify.
|
||||
//
|
||||
// ImageId is a server-minted id, never reused within the server's life.
|
||||
enum MGPSharedImageOpKind : Uint32 {
|
||||
kMGPSharedImageAllocate = 1,
|
||||
kMGPSharedImageImport = 2,
|
||||
kMGPSharedImageRelease = 3,
|
||||
kMGPSharedImagePresent = 4,
|
||||
kMGPSharedImageAttach = 5,
|
||||
};
|
||||
|
||||
struct MGPSharedImageOp {
|
||||
Uint32 Op;
|
||||
Uint32 Format; // DRM fourcc
|
||||
Uint32 Width;
|
||||
Uint32 Height;
|
||||
Uint64 ImageId;
|
||||
MGPipeHandle Texture; // Attach only
|
||||
};
|
||||
MGP_ASSERT_POD(MGPSharedImageOp, 32);
|
||||
|
||||
struct MGPSharedImageReply {
|
||||
Uint64 ImageId;
|
||||
Uint64 Modifier;
|
||||
Uint32 Width;
|
||||
Uint32 Height;
|
||||
Uint32 Format;
|
||||
Uint32 Stride;
|
||||
Uint32 Offset;
|
||||
Uint32 Pad0;
|
||||
};
|
||||
MGP_ASSERT_POD(MGPSharedImageReply, 40);
|
||||
|
||||
// The aux-socket sideband an Import's descriptor travels with (and an Allocate's answer).
|
||||
inline constexpr Uint32 kMGPSharedImageFdMagic = 0x49534D47u; // 'MGSI'
|
||||
struct MGPSharedImageFdOffer {
|
||||
Uint32 Magic;
|
||||
Uint32 Version;
|
||||
Uint64 Seq;
|
||||
Uint64 ImageId;
|
||||
Uint64 Reserved;
|
||||
};
|
||||
MGP_ASSERT_POD(MGPSharedImageFdOffer, 32);
|
||||
|
||||
|
||||
// set_context_values = opcode 79, P5c rv (CONTRACT-P5C.md §5.3): the rv field table as
|
||||
// ONE fixed-width POD. It carries every value-class field that no set_* call supplies -
|
||||
|
||||
@@ -2238,6 +2238,16 @@ namespace MobileGL::MG_Pipe {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool MGPipeApplyAttachSharedImage(MGPipeHandle texture, Uint64 imageId) {
|
||||
MGPipeResourceRecord* record = FindIn(MGPipeApplier().TextureResources, texture);
|
||||
if (record == nullptr) return false;
|
||||
record->SharedImageId = imageId;
|
||||
// The storage moved, so every twin's synced serial is stale, and the sampling answer with it.
|
||||
++record->Serial;
|
||||
MGPipeApplierNoteTextureStateMoved();
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool MGPipeApplyResourceRespecify(const MGPResourceDesc& desc, const void* initialBytes,
|
||||
const MGPRespecifiedLevel* level) {
|
||||
Vector<MGPipeResourceRecord>* table = ResourceTableForTarget(desc.Target);
|
||||
@@ -2291,6 +2301,8 @@ namespace MobileGL::MG_Pipe {
|
||||
// asking a frontend object for them. A metadata update replaces it too - that is how
|
||||
// the mask arrives - and by construction only the non-storage fields differ.
|
||||
record->Desc = desc;
|
||||
// A shared image is the storage this call replaces - unless the call replaces none.
|
||||
if (level != nullptr || !metadataOnly) record->SharedImageId = 0;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (level != nullptr && desc.Target != static_cast<Uint8>(MGPipeResourceTarget::TexBuffer)) {
|
||||
// Scope and BufOffset/BufSize are transient respecify carriers, not resource state.
|
||||
|
||||
@@ -294,6 +294,13 @@ namespace MobileGL::MG_Pipe {
|
||||
// DirectGLESBufferDrawProbe pair now pins from both sides.
|
||||
Bool HasLiveHostWrites = false;
|
||||
|
||||
// SHARED IMAGES (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md). Nonzero: this
|
||||
// texture's level-0 storage IS the server's shared image of this id (an EGLImage target),
|
||||
// set by shared_image's Attach and cleared by the next respecify that redefines storage.
|
||||
// The backend resolves the id at its texture sync (MG_Remote::Server::SharedImages::Find)
|
||||
// and binds that buffer instead of allocating storage; Serial moves with it.
|
||||
Uint64 SharedImageId = 0;
|
||||
|
||||
// ---- P4a. Only a record of kind Texture ever carries these; a buffer's stay at
|
||||
// their defaults, which is what keeps ONE record type for the discriminated
|
||||
// descriptor rather than a second one that would have to be kept in step with it.
|
||||
@@ -1392,6 +1399,10 @@ namespace MobileGL::MG_Pipe {
|
||||
// Returns true when the descriptor was stored - metadata updates included, since the record
|
||||
// did move - and false when the call was refused: a descriptor whose target names no
|
||||
// resource kind, or a handle this applier has no live record for at that generation.
|
||||
// SHARED IMAGES: the texture `texture`'s level-0 storage becomes shared image `imageId`
|
||||
// (MGPipeResourceRecord::SharedImageId). False when the texture has no live record.
|
||||
Bool MGPipeApplyAttachSharedImage(MGPipeHandle texture, Uint64 imageId);
|
||||
|
||||
Bool MGPipeApplyResourceRespecify(const MGPResourceDesc& desc, const void* initialBytes,
|
||||
const MGPRespecifiedLevel* level = nullptr);
|
||||
// resource_subdata, buffer half: the destination range rides in the record's box through
|
||||
|
||||
@@ -72,9 +72,9 @@
|
||||
// that the expansion, the two generated tables and this number agree.
|
||||
//
|
||||
// class entries group (as the plan tabulates it)
|
||||
// kScreen 13 screen: caps 1 + resource 3 + persistent map 2 + fence 4, plus the
|
||||
// kScreen 14 screen: caps 1 + resource 3 + persistent map 2 + fence 4, plus the
|
||||
// appended server-side fence wait 1 and P5c's applier_reset 1 and
|
||||
// P14 S1's bind_context 1
|
||||
// P14 S1's bind_context 1 and the shared-image row 1
|
||||
// kCtxQuery 8 query object namespace 6, plus the appended timestamp pair 2
|
||||
// kCtxCso 13 CSO create/bind/delete
|
||||
// kCtxState 19 16 of the 18 set_* calls + the temporary set_residual_value_state
|
||||
@@ -153,9 +153,11 @@
|
||||
// design's per-record ContextSlot was NOT taken here: the token is announced at the edge
|
||||
// (a context change) instead of stamped on every verb, which costs one record per
|
||||
// make-current and leaves the 71 context-family payloads untouched. 84 unique records.
|
||||
// shared_image (85), the server-allocated colour buffers exported as dma-bufs. 85 unique
|
||||
// records.
|
||||
// ---------------------------------------------------------------------------------------
|
||||
|
||||
#define MGP_CALL_LIST_DOCUMENTED_COUNT 84
|
||||
#define MGP_CALL_LIST_DOCUMENTED_COUNT 85
|
||||
|
||||
// clang-format off
|
||||
#define MGP_CALL_LIST(X) \
|
||||
@@ -374,7 +376,14 @@
|
||||
/* ---- context-family verb, and it has no MGPipeApply* entry point and no monolith producer. ---- */ \
|
||||
/* ---- kWaitNone: the apply reads no client memory and the record's placement in the ring ---- */ \
|
||||
/* ---- is the whole of its ordering. ---- */ \
|
||||
X(BindContext, MGPBindContext, kScreen, kNone, kWaitNone)
|
||||
X(BindContext, MGPBindContext, kScreen, kNone, kWaitNone) \
|
||||
/* ---- APPENDED FOR SHARED IMAGES (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md), ---- */ \
|
||||
/* ---- opcode 85: allocate / import / release / present / attach of a server-allocated ---- */ \
|
||||
/* ---- colour buffer exported as a dma-buf. kScreen: the image table is the server's, and ---- */ \
|
||||
/* ---- present / attach act on the session's current context the way bind_context does. ---- */ \
|
||||
/* ---- kWaitReply: every operation's answer is acted on (an id, a descriptor, a fence). ---- */ \
|
||||
/* ---- No MGPipeApply* entry point and no monolith producer. ---- */ \
|
||||
X(SharedImage, MGPSharedImageOp, kScreen, kReplySlot, kWaitReply)
|
||||
// clang-format on
|
||||
|
||||
// Explicitly NOT migrated (plan 4.4.6 / appendix A "explicit deletions"):
|
||||
|
||||
@@ -301,6 +301,10 @@
|
||||
#define MGP_FIELDS_MGPBindContext(F) \
|
||||
F(ClientContextToken)
|
||||
|
||||
// shared_image's POD.
|
||||
#define MGP_FIELDS_MGPSharedImageOp(F) \
|
||||
F(Op) F(Format) F(Width) F(Height) F(ImageId) F(Texture)
|
||||
|
||||
// P5c rv (CONTRACT-P5C.md §5.3): set_context_values' POD, one row per member. The 15-entry
|
||||
// array is one field, exactly as MGPFramebufferState's Color[8] is one.
|
||||
#define MGP_FIELDS_MGPContextValues(F) \
|
||||
@@ -427,7 +431,7 @@
|
||||
P(MGPSurfaceInfo) \
|
||||
P(MGPImageBind) P(MGPPatchParameter) P(MGPStreamOutputBind) P(MGPStorageBlockBinding) \
|
||||
P(MGPCopyFromFramebuffer) \
|
||||
P(MGPApplierReset) P(MGPBindContext) P(MGPContextValues) \
|
||||
P(MGPApplierReset) P(MGPBindContext) P(MGPSharedImageOp) P(MGPContextValues) \
|
||||
P(MGPProgramBindings) P(MGPProgramSamplerUnit) P(MGPProgramStorageOverride) \
|
||||
P(RenderStateParameters) P(PixelStoreParameters) P(SamplerParameters) P(PerBufferBlendState) \
|
||||
P(StencilFaceState) \
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// Regenerate with `python3 scripts/gen_pipe.py`; CI runs it and diffs the result.
|
||||
// This file is included from MG_Pipe/MGPipe.h inside namespace MobileGL::MG_Pipe.
|
||||
|
||||
// share group: 13 calls. A null entry means the backend does not implement this
|
||||
// share group: 14 calls. A null entry means the backend does not implement this
|
||||
// call and the frontend keeps its own path (plan B section 4.1).
|
||||
struct MGPipeScreen {
|
||||
void (*GetCaps)(const MGPCaps* payload, const void* blobBytes, Uint64 blobByteCount, MGPReplySlot* reply);
|
||||
@@ -28,6 +28,7 @@ struct MGPipeScreen {
|
||||
void (*FenceWaitServer)(const MGPFenceWait* payload);
|
||||
void (*ApplierReset)(const MGPApplierReset* payload);
|
||||
void (*BindContext)(const MGPBindContext* payload);
|
||||
void (*SharedImage)(const MGPSharedImageOp* payload, MGPReplySlot* reply);
|
||||
};
|
||||
|
||||
// context: 71 calls. A null entry means the backend does not implement this
|
||||
@@ -106,9 +107,9 @@ struct MGPipeContext {
|
||||
void (*GetTextureImageToBuffer)(const MGPReadbackToBuffer* payload);
|
||||
};
|
||||
|
||||
inline constexpr SizeT kMGPipeScreenCallCount = 13;
|
||||
inline constexpr SizeT kMGPipeScreenCallCount = 14;
|
||||
inline constexpr SizeT kMGPipeContextCallCount = 71;
|
||||
inline constexpr SizeT kMGPipeCallCount = 84;
|
||||
inline constexpr SizeT kMGPipeCallCount = 85;
|
||||
|
||||
// A table that is not exactly its call count of function pointers has grown a
|
||||
// member that no generator knows about.
|
||||
|
||||
@@ -352,3 +352,7 @@ inline void MGP_GetTextureImageToBuffer(const MGPReadbackToBuffer* payload) {
|
||||
inline void MGP_BindContext(const MGPBindContext* payload) {
|
||||
gMGPipeScreen.BindContext(payload);
|
||||
}
|
||||
|
||||
inline void MGP_SharedImage(const MGPSharedImageOp* payload, MGPReplySlot* reply) {
|
||||
gMGPipeScreen.SharedImage(payload, reply);
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ inline Bool MGPipeVerify(const MGPStorageBlockBinding& a, const MGPStorageBlockB
|
||||
inline Bool MGPipeVerify(const MGPCopyFromFramebuffer& a, const MGPCopyFromFramebuffer& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPApplierReset& a, const MGPApplierReset& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPBindContext& a, const MGPBindContext& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPSharedImageOp& a, const MGPSharedImageOp& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPContextValues& a, const MGPContextValues& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPProgramBindings& a, const MGPProgramBindings& b, const char** outField);
|
||||
inline Bool MGPipeVerify(const MGPProgramSamplerUnit& a, const MGPProgramSamplerUnit& b, const char** outField);
|
||||
@@ -272,6 +273,8 @@ struct MGPipeHasFieldVerifier<MGPApplierReset> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<MGPBindContext> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<MGPSharedImageOp> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<MGPContextValues> : std::true_type {};
|
||||
template <>
|
||||
struct MGPipeHasFieldVerifier<MGPProgramBindings> : std::true_type {};
|
||||
@@ -698,6 +701,11 @@ inline Bool MGPipeVerify(const MGPBindContext& a, const MGPBindContext& b, const
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Bool MGPipeVerify(const MGPSharedImageOp& a, const MGPSharedImageOp& b, const char** outField) {
|
||||
MGP_FIELDS_MGPSharedImageOp(MGP_VERIFY_FIELD)
|
||||
return true;
|
||||
}
|
||||
|
||||
inline Bool MGPipeVerify(const MGPContextValues& a, const MGPContextValues& b, const char** outField) {
|
||||
MGP_FIELDS_MGPContextValues(MGP_VERIFY_FIELD)
|
||||
return true;
|
||||
@@ -765,4 +773,4 @@ inline Bool MGPipeVerify(const MGPVertexBindingPointWire& a, const MGPVertexBind
|
||||
|
||||
#undef MGP_VERIFY_FIELD
|
||||
|
||||
inline constexpr SizeT kMGPipeVerifiedPayloadCount = 84;
|
||||
inline constexpr SizeT kMGPipeVerifiedPayloadCount = 85;
|
||||
|
||||
@@ -154,7 +154,8 @@ enum class MGPWireOp : Uint16 {
|
||||
ReadPixelsToBuffer = 82,
|
||||
GetTextureImageToBuffer = 83,
|
||||
BindContext = 84,
|
||||
kOpCount = 85,
|
||||
SharedImage = 85,
|
||||
kOpCount = 86,
|
||||
};
|
||||
|
||||
// THE FLAGS, EXPORTED ONCE, INDEXED BY OPCODE (P5 R-13.4). MGPWireRecHeader::Flags is
|
||||
@@ -260,6 +261,7 @@ inline constexpr Uint32 kMGPipeCallFlags[static_cast<SizeT>(MGPWireOp::kOpCount)
|
||||
/* 82 ReadPixelsToBuffer */ static_cast<Uint32>(kNone),
|
||||
/* 83 GetTextureImageToBuffer */ static_cast<Uint32>(kNone),
|
||||
/* 84 BindContext */ static_cast<Uint32>(kNone),
|
||||
/* 85 SharedImage */ static_cast<Uint32>(kReplySlot),
|
||||
};
|
||||
static_assert(sizeof(kMGPipeCallFlags) / sizeof(kMGPipeCallFlags[0]) ==
|
||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||
@@ -364,6 +366,7 @@ inline constexpr Uint8 kMGPipeCallClasses[static_cast<SizeT>(MGPWireOp::kOpCount
|
||||
/* 82 ReadPixelsToBuffer */ static_cast<Uint8>(kCtxVerb),
|
||||
/* 83 GetTextureImageToBuffer */ static_cast<Uint8>(kCtxObject),
|
||||
/* 84 BindContext */ static_cast<Uint8>(kScreen),
|
||||
/* 85 SharedImage */ static_cast<Uint8>(kScreen),
|
||||
};
|
||||
static_assert(sizeof(kMGPipeCallClasses) / sizeof(kMGPipeCallClasses[0]) ==
|
||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||
@@ -472,6 +475,7 @@ inline constexpr Uint8 kMGPipeWaitClasses[static_cast<SizeT>(MGPWireOp::kOpCount
|
||||
/* 82 ReadPixelsToBuffer */ static_cast<Uint8>(kWaitNone),
|
||||
/* 83 GetTextureImageToBuffer */ static_cast<Uint8>(kWaitNone),
|
||||
/* 84 BindContext */ static_cast<Uint8>(kWaitNone),
|
||||
/* 85 SharedImage */ static_cast<Uint8>(kWaitReply),
|
||||
};
|
||||
static_assert(sizeof(kMGPipeWaitClasses) / sizeof(kMGPipeWaitClasses[0]) ==
|
||||
static_cast<SizeT>(MGPWireOp::kOpCount),
|
||||
@@ -1210,6 +1214,14 @@ static_assert(sizeof(MGPWireRec_BindContext) ==
|
||||
((sizeof(MGPWireRecHeader) + sizeof(MGPBindContext) + 7u) & ~SizeT(7u)),
|
||||
"MGPWireRec_BindContext gained padding; the wire format moved");
|
||||
|
||||
struct alignas(8) MGPWireRec_SharedImage {
|
||||
MGPWireRecHeader Header;
|
||||
MGPSharedImageOp Payload;
|
||||
};
|
||||
static_assert(sizeof(MGPWireRec_SharedImage) ==
|
||||
((sizeof(MGPWireRecHeader) + sizeof(MGPSharedImageOp) + 7u) & ~SizeT(7u)),
|
||||
"MGPWireRec_SharedImage gained padding; the wire format moved");
|
||||
|
||||
[[noreturn]] inline void MGPipeWireProtocolFatal(const char* call, Uint64 size, Uint64 remaining) {
|
||||
MGLOG_F("MGPipe: protocol corruption applying %s: size=%llu remaining=%llu", call,
|
||||
static_cast<unsigned long long>(size), static_cast<unsigned long long>(remaining));
|
||||
@@ -1511,6 +1523,9 @@ inline Bool MGPipeApplyWireRecord(MGPWireOp op, const void* record, Uint64 size,
|
||||
case MGPWireOp::BindContext:
|
||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_BindContext, "BindContext");
|
||||
break;
|
||||
case MGPWireOp::SharedImage:
|
||||
MGP_WIRE_CHECK_BOUNDS(MGPWireRec_SharedImage, "SharedImage");
|
||||
break;
|
||||
case MGPWireOp::kInvalid:
|
||||
case MGPWireOp::kOpCount:
|
||||
default:
|
||||
|
||||
@@ -433,6 +433,13 @@ inline constexpr WireLayoutMember kMGPipeWireLayoutMembers[] = {
|
||||
{"MGPApplierReset.ContextSerial", offsetof(MGPApplierReset, ContextSerial), sizeof(((MGPApplierReset*)nullptr)->ContextSerial)},
|
||||
{"MGPBindContext", 0, sizeof(MGPBindContext)},
|
||||
{"MGPBindContext.ClientContextToken", offsetof(MGPBindContext, ClientContextToken), sizeof(((MGPBindContext*)nullptr)->ClientContextToken)},
|
||||
{"MGPSharedImageOp", 0, sizeof(MGPSharedImageOp)},
|
||||
{"MGPSharedImageOp.Op", offsetof(MGPSharedImageOp, Op), sizeof(((MGPSharedImageOp*)nullptr)->Op)},
|
||||
{"MGPSharedImageOp.Format", offsetof(MGPSharedImageOp, Format), sizeof(((MGPSharedImageOp*)nullptr)->Format)},
|
||||
{"MGPSharedImageOp.Width", offsetof(MGPSharedImageOp, Width), sizeof(((MGPSharedImageOp*)nullptr)->Width)},
|
||||
{"MGPSharedImageOp.Height", offsetof(MGPSharedImageOp, Height), sizeof(((MGPSharedImageOp*)nullptr)->Height)},
|
||||
{"MGPSharedImageOp.ImageId", offsetof(MGPSharedImageOp, ImageId), sizeof(((MGPSharedImageOp*)nullptr)->ImageId)},
|
||||
{"MGPSharedImageOp.Texture", offsetof(MGPSharedImageOp, Texture), sizeof(((MGPSharedImageOp*)nullptr)->Texture)},
|
||||
{"MGPContextValues", 0, sizeof(MGPContextValues)},
|
||||
{"MGPContextValues.ActiveTextureUnit", offsetof(MGPContextValues, ActiveTextureUnit), sizeof(((MGPContextValues*)nullptr)->ActiveTextureUnit)},
|
||||
{"MGPContextValues.MaxTouchedTextureUnit", offsetof(MGPContextValues, MaxTouchedTextureUnit), sizeof(((MGPContextValues*)nullptr)->MaxTouchedTextureUnit)},
|
||||
@@ -765,4 +772,5 @@ inline constexpr WireCallLayout kMGPipeWireCallLayouts[] = {
|
||||
{"ReadPixelsToBuffer", "MGPReadbackToBuffer", static_cast<Uint64>(MGPWireOp::ReadPixelsToBuffer), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"GetTextureImageToBuffer", "MGPReadbackToBuffer", static_cast<Uint64>(MGPWireOp::GetTextureImageToBuffer), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"BindContext", "MGPBindContext", static_cast<Uint64>(MGPWireOp::BindContext), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SharedImage", "MGPSharedImageOp", static_cast<Uint64>(MGPWireOp::SharedImage), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "../Server/ServerLoop.h"
|
||||
|
||||
#include <MG_Impl/Pipe/SlotAllocator.h>
|
||||
#include <MG_State/EGLState/Core.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
@@ -429,6 +430,78 @@ namespace MobileGL::MG_Remote::Client {
|
||||
MG_Backend::BackendObject::ReleaseEGLResources();
|
||||
}
|
||||
|
||||
// ---- shared images ----------------------------------------------------------------------
|
||||
//
|
||||
// One shared_image record each (ClientSession::EmitSharedImage). Allocate/Import/Release are
|
||||
// table operations and order against nothing; Present and Attach ride the ring behind the GL
|
||||
// calls they follow, which is the order they need.
|
||||
Bool BackendObject_Remote::AllocateSharedImage(Uint32 width, Uint32 height, Uint32 fourcc,
|
||||
MG_Backend::SharedImageExport* out) {
|
||||
ClientSession* session = ClientSession::Active();
|
||||
if (session == nullptr || out == nullptr) return false;
|
||||
MG_Pipe::MGPSharedImageOp op{};
|
||||
op.Op = MG_Pipe::kMGPSharedImageAllocate;
|
||||
op.Format = fourcc;
|
||||
op.Width = width;
|
||||
op.Height = height;
|
||||
MG_Pipe::MGPSharedImageReply reply{};
|
||||
int fd = -1;
|
||||
if (!session->EmitSharedImage(op, -1, &reply, &fd)) return false;
|
||||
out->Id = reply.ImageId;
|
||||
out->Fd = fd;
|
||||
out->Width = reply.Width;
|
||||
out->Height = reply.Height;
|
||||
out->Fourcc = reply.Format;
|
||||
out->Stride = reply.Stride;
|
||||
out->Offset = reply.Offset;
|
||||
out->Modifier = reply.Modifier;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_Remote::ImportSharedImage(int fd, Uint32 width, Uint32 height, Uint32 fourcc, Uint64* outId) {
|
||||
ClientSession* session = ClientSession::Active();
|
||||
if (session == nullptr || fd < 0) return false;
|
||||
MG_Pipe::MGPSharedImageOp op{};
|
||||
op.Op = MG_Pipe::kMGPSharedImageImport;
|
||||
op.Format = fourcc;
|
||||
op.Width = width;
|
||||
op.Height = height;
|
||||
MG_Pipe::MGPSharedImageReply reply{};
|
||||
if (!session->EmitSharedImage(op, fd, &reply, nullptr)) return false;
|
||||
if (outId != nullptr) *outId = reply.ImageId;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_Remote::ReleaseSharedImage(Uint64 id) {
|
||||
ClientSession* session = ClientSession::Active();
|
||||
if (session == nullptr || !session->Started()) return false;
|
||||
MG_Pipe::MGPSharedImageOp op{};
|
||||
op.Op = MG_Pipe::kMGPSharedImageRelease;
|
||||
op.ImageId = id;
|
||||
return session->EmitSharedImage(op, -1, nullptr, nullptr);
|
||||
}
|
||||
|
||||
Bool BackendObject_Remote::PresentToSharedImage(Uint64 id) {
|
||||
ClientSession* session = ClientSession::Active();
|
||||
if (session == nullptr) return false;
|
||||
MG_Pipe::MGPSharedImageOp op{};
|
||||
op.Op = MG_Pipe::kMGPSharedImagePresent;
|
||||
op.ImageId = id;
|
||||
return session->EmitSharedImage(op, -1, nullptr, nullptr);
|
||||
}
|
||||
|
||||
Bool BackendObject_Remote::AttachSharedImageToTexture(Uint64 textureLifetimeId, Uint64 id) {
|
||||
ClientSession* session = ClientSession::Active();
|
||||
if (session == nullptr) return false;
|
||||
MG_Pipe::MGPSharedImageOp op{};
|
||||
op.Op = MG_Pipe::kMGPSharedImageAttach;
|
||||
op.ImageId = id;
|
||||
// The handle the texture's resource records were published under (TextureEmit's own
|
||||
// AcquireTexture is this call on the same lifetime id).
|
||||
op.Texture = MG_Pipe::MGPipeSlots().Acquire(MG_Pipe::MGPipeKind::Texture, textureLifetimeId);
|
||||
return session->EmitSharedImage(op, -1, nullptr, nullptr);
|
||||
}
|
||||
|
||||
// NO strong CreateRemoteBackendObject() lives here, and the reason is a link fact, not an
|
||||
// oversight. v1's Init.cpp calls MG_Remote::Client::CreateRemoteBackendObject() and ships a
|
||||
// __attribute__((weak)) placeholder for it beside ServerLoop that aborts by name; its comment
|
||||
|
||||
@@ -78,6 +78,14 @@ namespace MobileGL::MG_Remote::Client {
|
||||
void ReleaseEGLSurface(EGLSurface surface) override;
|
||||
void ReleaseEGLResources() override;
|
||||
|
||||
// ---- shared images (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md) -------------
|
||||
Bool AllocateSharedImage(Uint32 width, Uint32 height, Uint32 fourcc,
|
||||
MG_Backend::SharedImageExport* out) override;
|
||||
Bool ImportSharedImage(int fd, Uint32 width, Uint32 height, Uint32 fourcc, Uint64* outId) override;
|
||||
Bool ReleaseSharedImage(Uint64 id) override;
|
||||
Bool PresentToSharedImage(Uint64 id) override;
|
||||
Bool AttachSharedImageToTexture(Uint64 textureLifetimeId, Uint64 id) override;
|
||||
|
||||
// Copies the caps mirror's FormatCapabilityCache into the base class's
|
||||
// m_formatCapabilities. Public because CapsMirror's adoption hook is a free function
|
||||
// and this is what it calls; it is the whole of trap 2's answer.
|
||||
|
||||
@@ -2857,6 +2857,59 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Bool ClientSession::EmitSharedImage(const MG_Pipe::MGPSharedImageOp& op, int sendFd,
|
||||
MG_Pipe::MGPSharedImageReply* reply, int* receivedFd) {
|
||||
if (receivedFd != nullptr) *receivedFd = -1;
|
||||
MobileGLResult shared = MOBILEGL_OK;
|
||||
if (sendFd >= 0) {
|
||||
m_prePublish = [&](Uint64 seq) {
|
||||
MG_Pipe::MGPSharedImageFdOffer offer{MG_Pipe::kMGPSharedImageFdMagic, 1, seq, op.ImageId, 0};
|
||||
shared = m_transport != nullptr ? m_transport->ShareFd(sendFd, MobileGLByteSpan{&offer, sizeof(offer)})
|
||||
: MOBILEGL_ERR_NOT_INITIALIZED;
|
||||
};
|
||||
}
|
||||
MG_Pipe::MGPSharedImageReply answer{};
|
||||
Int32 status = Wire::ReplySink::kStatusError;
|
||||
Uint64 returned = 0;
|
||||
const Uint64 seq = EmitAndWait(MG_Pipe::MGPWireOp::SharedImage, &op, sizeof(op), nullptr, 0, &answer,
|
||||
sizeof(answer), &status, &returned);
|
||||
m_prePublish = nullptr;
|
||||
if (shared != MOBILEGL_OK) {
|
||||
MGLOG_E("MG_Remote client: a shared-image descriptor could not be queued on the aux socket (rc=%d)",
|
||||
static_cast<int>(shared));
|
||||
}
|
||||
if (status != Wire::ReplySink::kStatusOk || returned != sizeof(answer)) return false;
|
||||
if (reply != nullptr) *reply = answer;
|
||||
if (receivedFd == nullptr) return true;
|
||||
#if defined(_WIN32)
|
||||
return false;
|
||||
#else
|
||||
if (m_transport == nullptr) return false;
|
||||
for (;;) {
|
||||
int fd = -1;
|
||||
Uint8 sideband[Transport::FdPassing::kMaxSidebandBytes] = {};
|
||||
std::uint64_t sidebandSize = 0;
|
||||
const MobileGLResult received =
|
||||
m_transport->ReceiveFd(&fd, MobileGLMutableByteSpan{sideband, sizeof(sideband)}, &sidebandSize, 2000);
|
||||
if (received != MOBILEGL_OK) {
|
||||
MGLOG_E("MG_Remote client: shared image %llu was answered but its descriptor did not arrive (rc=%d)",
|
||||
static_cast<unsigned long long>(answer.ImageId), static_cast<int>(received));
|
||||
return false;
|
||||
}
|
||||
MG_Pipe::MGPSharedImageFdOffer offer{};
|
||||
if (sidebandSize == sizeof(offer)) std::memcpy(&offer, sideband, sizeof(offer));
|
||||
if (offer.Magic == MG_Pipe::kMGPSharedImageFdMagic && offer.Seq == seq && offer.ImageId == answer.ImageId) {
|
||||
*receivedFd = fd;
|
||||
return true;
|
||||
}
|
||||
// An earlier record's descriptor whose answer was never taken: not this one.
|
||||
MGLOG_W("MG_Remote client: an aux descriptor for record %llu arrived while record %llu's was awaited; closed",
|
||||
static_cast<unsigned long long>(offer.Seq), static_cast<unsigned long long>(seq));
|
||||
::close(fd);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClientSession::ReleaseT0Store(MG_Pipe::MGPipeHandle handle) {
|
||||
auto it = m_t0Stores.find(T0Key(handle));
|
||||
if (it == m_t0Stores.end()) return;
|
||||
|
||||
@@ -242,6 +242,13 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// server's inbox relies on), and wait for the answer. The held pointer on OK - the caller
|
||||
// adopts it as the store's mapping - or null on DECLINED (the store stays T2).
|
||||
void* AdoptPersistentT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 size, const void* seed);
|
||||
// shared_image (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md): emits `op` and waits
|
||||
// for its answer. `sendFd` >= 0 (an Import) is queued on the aux socket for the record's
|
||||
// seq BEFORE the record is published; `receivedFd` non-null (an Allocate) takes the
|
||||
// descriptor the server queued before its answer. False = DECLINED (the server's log says
|
||||
// why) or the descriptor did not travel.
|
||||
Bool EmitSharedImage(const MG_Pipe::MGPSharedImageOp& op, int sendFd, MG_Pipe::MGPSharedImageReply* reply,
|
||||
int* receivedFd);
|
||||
// The client's reference to buffer `handle`'s T0 store ends because the buffer was
|
||||
// respecified or destroyed: unlock and release it. The server's import holds its own
|
||||
// reference until its own fence for the store's last use has completed. No-op for a
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "../Transport/FdPassing.h"
|
||||
|
||||
#include <MG_Pipe/MGPipeTypes.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <chrono>
|
||||
@@ -40,17 +41,31 @@ namespace MobileGL::MG_Remote::Server {
|
||||
|
||||
AdoptInbox::Outcome AdoptInbox::Take(Uint64 seq, Uint32 timeoutMs, int* outFd,
|
||||
Transport::AdoptT0::Offer* outOffer, std::string& why) {
|
||||
return TakeKind(Kind::T0, seq, timeoutMs, outFd, outOffer, why);
|
||||
}
|
||||
|
||||
AdoptInbox::Outcome AdoptInbox::TakeSharedImageFd(Uint64 seq, Uint32 timeoutMs, int* outFd, std::string& why) {
|
||||
Transport::AdoptT0::Offer unused{};
|
||||
return TakeKind(Kind::SharedImage, seq, timeoutMs, outFd, &unused, why);
|
||||
}
|
||||
|
||||
// Both kinds are 32-byte sidebands keyed by the record's seq. A shared-image offer
|
||||
// (MGPSharedImageFdOffer) is carried in the T0 Offer's storage - only its magic and seq are read.
|
||||
AdoptInbox::Outcome AdoptInbox::TakeKind(Kind kind, Uint64 seq, Uint32 timeoutMs, int* outFd,
|
||||
Transport::AdoptT0::Offer* outOffer, std::string& why) {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
static_assert(sizeof(MG_Pipe::MGPSharedImageFdOffer) == sizeof(Transport::AdoptT0::Offer),
|
||||
"the two aux sidebands share one size");
|
||||
*outFd = -1;
|
||||
*outOffer = Transport::AdoptT0::Offer{};
|
||||
if (auto it = m_kept.find(seq); it != m_kept.end()) {
|
||||
if (auto it = m_kept.find(KeyFor(kind, seq)); it != m_kept.end()) {
|
||||
*outFd = it->second.fd;
|
||||
*outOffer = it->second.offer;
|
||||
m_kept.erase(it);
|
||||
return Outcome::Taken;
|
||||
}
|
||||
if (m_transport == nullptr) {
|
||||
why = "the session has no control transport to receive the store on";
|
||||
why = "the session has no control transport to receive the descriptor on";
|
||||
return Outcome::Closed;
|
||||
}
|
||||
const auto deadline = Clock::now() + std::chrono::milliseconds(timeoutMs);
|
||||
@@ -64,57 +79,61 @@ namespace MobileGL::MG_Remote::Server {
|
||||
const MobileGLResult received = m_transport->ReceiveFd(
|
||||
&fd, MobileGLMutableByteSpan{sideband, sizeof(sideband)}, &sidebandSize, left);
|
||||
if (received == MOBILEGL_ERR_TIMEOUT) {
|
||||
why = "no store arrived on the aux socket within " + std::to_string(timeoutMs) +
|
||||
why = "no descriptor arrived on the aux socket within " + std::to_string(timeoutMs) +
|
||||
" ms of its record";
|
||||
return Outcome::TimedOut;
|
||||
}
|
||||
if (received != MOBILEGL_OK) {
|
||||
why = "the aux socket closed (rc=" + std::to_string(static_cast<int>(received)) +
|
||||
") before the store arrived";
|
||||
") before the descriptor arrived";
|
||||
return Outcome::Closed;
|
||||
}
|
||||
Transport::AdoptT0::Offer offer{};
|
||||
if (sidebandSize != sizeof(offer)) {
|
||||
++m_malformed;
|
||||
MGLOG_W("MG_Remote server: T0 - an aux descriptor with a %llu-byte sideband is not a store "
|
||||
"offer; closed",
|
||||
MGLOG_W("MG_Remote server: an aux descriptor with a %llu-byte sideband is neither a T0 store nor a "
|
||||
"shared image; closed",
|
||||
static_cast<unsigned long long>(sidebandSize));
|
||||
CloseFd(fd);
|
||||
continue;
|
||||
}
|
||||
std::memcpy(&offer, sideband, sizeof(offer));
|
||||
if (offer.magic != Transport::AdoptT0::kOfferMagic ||
|
||||
offer.version != Transport::AdoptT0::kOfferVersion) {
|
||||
Kind arrived;
|
||||
if (offer.magic == Transport::AdoptT0::kOfferMagic && offer.version == Transport::AdoptT0::kOfferVersion) {
|
||||
arrived = Kind::T0;
|
||||
} else if (offer.magic == MG_Pipe::kMGPSharedImageFdMagic) {
|
||||
arrived = Kind::SharedImage;
|
||||
} else {
|
||||
++m_malformed;
|
||||
MGLOG_W("MG_Remote server: T0 - an aux descriptor whose sideband is not an Offer "
|
||||
"(magic 0x%08x version %u); closed",
|
||||
MGLOG_W("MG_Remote server: an aux descriptor whose sideband is neither a T0 Offer nor a shared "
|
||||
"image (magic 0x%08x version %u); closed",
|
||||
offer.magic, offer.version);
|
||||
CloseFd(fd);
|
||||
continue;
|
||||
}
|
||||
if (offer.seq == seq) {
|
||||
if (arrived == kind && offer.seq == seq) {
|
||||
*outFd = fd;
|
||||
*outOffer = offer;
|
||||
return Outcome::Taken;
|
||||
}
|
||||
if (offer.seq < seq) {
|
||||
// Its record already timed out and was declined; the client has released the
|
||||
// store on that answer, and this late copy of the handle is only a reference.
|
||||
// Its record already timed out and was declined; this late copy is only a reference.
|
||||
++m_stale;
|
||||
MGLOG_W("MG_Remote server: T0 - the store for record %llu arrived after its record was "
|
||||
MGLOG_W("MG_Remote server: the descriptor for record %llu arrived after its record was "
|
||||
"answered; closed (stale)",
|
||||
static_cast<unsigned long long>(offer.seq));
|
||||
CloseFd(fd);
|
||||
continue;
|
||||
}
|
||||
// For a later record: keep it for that record. Replacing a kept one would leak it.
|
||||
if (auto it = m_kept.find(offer.seq); it != m_kept.end()) {
|
||||
const Uint64 key = KeyFor(arrived, offer.seq);
|
||||
if (auto it = m_kept.find(key); it != m_kept.end()) {
|
||||
++m_malformed;
|
||||
CloseFd(it->second.fd);
|
||||
it->second = KeptOffer{fd, offer};
|
||||
continue;
|
||||
}
|
||||
m_kept.emplace(offer.seq, KeptOffer{fd, offer});
|
||||
m_kept.emplace(key, KeptOffer{fd, offer});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,11 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Outcome Take(Uint64 seq, Uint32 timeoutMs, int* outFd, Transport::AdoptT0::Offer* outOffer,
|
||||
std::string& why);
|
||||
|
||||
// A shared image's descriptor for record `seq` (an Import; sideband MGPSharedImageFdOffer).
|
||||
// Same matching and the same bound as Take; the two kinds share the aux socket and are kept
|
||||
// apart by their sideband's magic.
|
||||
Outcome TakeSharedImageFd(Uint64 seq, Uint32 timeoutMs, int* outFd, std::string& why);
|
||||
|
||||
// Closes every kept descriptor (session teardown).
|
||||
void Clear();
|
||||
|
||||
@@ -59,10 +64,14 @@ namespace MobileGL::MG_Remote::Server {
|
||||
SizeT Kept() const { return m_kept.size(); }
|
||||
|
||||
private:
|
||||
enum class Kind : Uint8 { T0, SharedImage };
|
||||
struct KeptOffer {
|
||||
int fd = -1;
|
||||
Transport::AdoptT0::Offer offer{};
|
||||
};
|
||||
static Uint64 KeyFor(Kind kind, Uint64 seq) { return (seq << 1) | (kind == Kind::SharedImage ? 1u : 0u); }
|
||||
Outcome TakeKind(Kind kind, Uint64 seq, Uint32 timeoutMs, int* outFd, Transport::AdoptT0::Offer* outOffer,
|
||||
std::string& why);
|
||||
Transport::ITransport* m_transport = nullptr;
|
||||
std::map<Uint64, KeptOffer> m_kept;
|
||||
Uint64 m_stale = 0;
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#if !defined(_WIN32)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
@@ -1472,6 +1475,109 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// never created is corruption, not a late record. Token 0 is the release edge and is always
|
||||
// legal (a session whose client released its context has no context, which is exactly the
|
||||
// value a session that never bound one already answers).
|
||||
Bool ServerVerbSink::OnSharedImage(const MG_Pipe::MGPSharedImageOp& op, Uint64 seq,
|
||||
MG_Pipe::MGPSharedImageReply& reply) {
|
||||
namespace SI = SharedImages;
|
||||
ServerSession* session = ServerSession::Active();
|
||||
if (session == nullptr) return false;
|
||||
SI::SessionHolder& holder = session->SharedImageHolder();
|
||||
const auto fill = [&reply](const SI::Image& image) {
|
||||
reply.ImageId = image.Id;
|
||||
reply.Modifier = image.Modifier;
|
||||
reply.Width = image.Width;
|
||||
reply.Height = image.Height;
|
||||
reply.Format = image.Fourcc;
|
||||
reply.Stride = image.Stride;
|
||||
reply.Offset = image.Offset;
|
||||
};
|
||||
switch (op.Op) {
|
||||
case MG_Pipe::kMGPSharedImageAllocate: {
|
||||
std::string why;
|
||||
SI::ImageRef image = SI::Allocate(op.Width, op.Height, op.Format, why);
|
||||
if (image == nullptr) {
|
||||
MGLOG_E("MG_Remote server: shared image %ux%u (fourcc 0x%08x) not allocated: %s", op.Width, op.Height,
|
||||
op.Format, why.c_str());
|
||||
return false;
|
||||
}
|
||||
Transport::ITransport* transport = session->ControlTransport();
|
||||
// The descriptor goes out BEFORE the reply is posted, so the client finds it queued.
|
||||
MG_Pipe::MGPSharedImageFdOffer offer{MG_Pipe::kMGPSharedImageFdMagic, 1, seq, image->Id, 0};
|
||||
const MobileGLResult shared = transport != nullptr
|
||||
? transport->ShareFd(image->Fd, MobileGLByteSpan{&offer, sizeof(offer)})
|
||||
: MOBILEGL_ERR_NOT_INITIALIZED;
|
||||
if (shared != MOBILEGL_OK) {
|
||||
MGLOG_E("MG_Remote server: shared image %llu could not be sent on the aux socket (rc=%d)",
|
||||
static_cast<unsigned long long>(image->Id), static_cast<int>(shared));
|
||||
return false;
|
||||
}
|
||||
holder.Hold(image);
|
||||
fill(*image);
|
||||
MGLOG_D("MG_Remote server: shared image %llu allocated, %ux%u stride %u",
|
||||
static_cast<unsigned long long>(image->Id), image->Width, image->Height, image->Stride);
|
||||
return true;
|
||||
}
|
||||
case MG_Pipe::kMGPSharedImageImport: {
|
||||
int fd = -1;
|
||||
std::string why;
|
||||
if (session->T0Inbox().TakeSharedImageFd(seq, Transport::AdoptT0::kOfferWaitMs, &fd, why) !=
|
||||
AdoptInbox::Outcome::Taken) {
|
||||
MGLOG_E("MG_Remote server: shared-image import (record %llu) has no descriptor: %s",
|
||||
static_cast<unsigned long long>(seq), why.c_str());
|
||||
return false;
|
||||
}
|
||||
SI::ImageRef image = SI::Identify(fd, why);
|
||||
#if !defined(_WIN32)
|
||||
::close(fd);
|
||||
#endif
|
||||
if (image == nullptr) {
|
||||
MGLOG_W_ONCE("MG_Remote server: a dma-buf import was refused: %s", why.c_str());
|
||||
return false;
|
||||
}
|
||||
if (image->Width != op.Width || image->Height != op.Height || !SI::FourccSupported(op.Format)) {
|
||||
MGLOG_W("MG_Remote server: a dma-buf import of image %llu (%ux%u) as %ux%u fourcc 0x%08x was refused",
|
||||
static_cast<unsigned long long>(image->Id), image->Width, image->Height, op.Width, op.Height,
|
||||
op.Format);
|
||||
return false;
|
||||
}
|
||||
holder.Hold(image);
|
||||
fill(*image);
|
||||
return true;
|
||||
}
|
||||
case MG_Pipe::kMGPSharedImageRelease:
|
||||
reply.ImageId = op.ImageId;
|
||||
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::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)) return false;
|
||||
fill(*image);
|
||||
return true;
|
||||
}
|
||||
case MG_Pipe::kMGPSharedImageAttach: {
|
||||
SI::ImageRef image = holder.Get(op.ImageId);
|
||||
if (image == nullptr) return false;
|
||||
if (!MG_Pipe::MGPipeApplyAttachSharedImage(op.Texture, image->Id)) {
|
||||
MGLOG_E("MG_Remote server: shared image %llu could not be attached to texture {slot=%u, gen=%u}: the "
|
||||
"texture has no record",
|
||||
static_cast<unsigned long long>(image->Id), op.Texture.Slot, op.Texture.Gen);
|
||||
return false;
|
||||
}
|
||||
fill(*image);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
MGLOG_E("MG_Remote server: shared_image operation %u is not one this server knows", op.Op);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Bool ServerVerbSink::OnBindContext(const MG_Pipe::MGPBindContext& bind) {
|
||||
// NO SESSION IS A DECLINE, NOT A FAULT, and it is the one case here that a peer's bytes
|
||||
// cannot cause: a bind is answered by the session's own table, so with no accepted
|
||||
|
||||
@@ -254,6 +254,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// rather than a silent bind. Token 0 (the release) is always legal.
|
||||
Bool OnBindContext(const MG_Pipe::MGPBindContext& bind) override;
|
||||
|
||||
// shared_image, opcode 85 (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md): the
|
||||
// session's image table (allocate / import / release) and the two GL-ordered operations
|
||||
// (present the default framebuffer into an image, attach an image to a texture).
|
||||
Bool OnSharedImage(const MG_Pipe::MGPSharedImageOp& op, Uint64 seq,
|
||||
MG_Pipe::MGPSharedImageReply& reply) override;
|
||||
|
||||
// P5c ct's tallies, for the same reason every other row's tally exists (R-16: a probe
|
||||
// may not arm against a stub). ApplierResets counts the records ACCEPTED (serial
|
||||
// checked, reset run); ObjectDeaths counts every record the sink dispatched.
|
||||
|
||||
@@ -728,6 +728,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
m_t0Stores = m_t0Bytes = m_t0Declined = m_t0Refusals = 0;
|
||||
m_adoptInbox.Clear();
|
||||
m_adoptInbox.Attach(&transport);
|
||||
m_sharedImages.Clear();
|
||||
// P14 S1: the context registry is the session's, so it is emptied with the rest of the
|
||||
// per-session state this Accept resets. A session that never crossed a bind_context
|
||||
// leaves the current token at 0, which is the single-context shape's answer.
|
||||
@@ -1110,6 +1111,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
}
|
||||
m_adoptInbox.Clear();
|
||||
m_adoptInbox.Attach(nullptr);
|
||||
m_sharedImages.Clear();
|
||||
m_adoptT0Asked = m_adoptT0Settled = m_adoptT0 = m_adoptT0Quiet = false;
|
||||
m_adoptAsk = 2;
|
||||
m_consumer.Detach();
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include "../Transport/SessionRings.h"
|
||||
#include "../Wire/PipeWireCodec.h"
|
||||
#include "AdoptInbox.h"
|
||||
#include "SharedImageRegistry.h"
|
||||
#include "ApplyThreadPolicy.h"
|
||||
#include "PipeApplier.h"
|
||||
|
||||
@@ -331,6 +332,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool AdoptT0Quiet() const { return m_adoptT0Quiet; }
|
||||
AdoptInbox& T0Inbox() { return m_adoptInbox; }
|
||||
|
||||
// ---- shared images (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md) -------------
|
||||
// The images this session allocated or imported and has not released; emptied when the
|
||||
// session closes. The control transport is where an allocation's descriptor goes out.
|
||||
SharedImages::SessionHolder& SharedImageHolder() { return m_sharedImages; }
|
||||
Transport::ITransport* ControlTransport() const { return m_transport; }
|
||||
|
||||
// ---- P14 S1 (docs/Disaggregated/design/11-state-ownership.md): the session's contexts -
|
||||
//
|
||||
// THE PLACEHOLDER TABLE. `CreateContext`/`DestroyContext` on the control plane build and
|
||||
@@ -416,6 +423,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool m_adoptT0Settled = false;
|
||||
Bool m_adoptT0 = false;
|
||||
AdoptInbox m_adoptInbox;
|
||||
SharedImages::SessionHolder m_sharedImages;
|
||||
Uint64 m_t0Stores = 0;
|
||||
Uint64 m_t0Bytes = 0;
|
||||
Uint64 m_t0Declined = 0;
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
// MobileGL - MobileGL/MG_Remote/Server/SharedImageRegistry.cpp
|
||||
// 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
|
||||
|
||||
#include "SharedImageRegistry.h"
|
||||
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <android/hardware_buffer.h>
|
||||
#endif
|
||||
#if !defined(_WIN32)
|
||||
#include <sys/mman.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Remote::Server::SharedImages {
|
||||
|
||||
namespace {
|
||||
std::string Format(const char* fmt, long long a, long long b = 0, long long c = 0) {
|
||||
char buffer[256];
|
||||
std::snprintf(buffer, sizeof(buffer), fmt, a, b, c);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
struct Registry {
|
||||
std::mutex mutex;
|
||||
std::atomic<Uint64> nextId{1};
|
||||
UnorderedMap<Uint64, std::weak_ptr<const Image>> byId;
|
||||
std::map<std::pair<Uint64, Uint64>, std::weak_ptr<const Image>> byIdentity;
|
||||
// Set once two live images were seen with one identity: the kernel shares an inode
|
||||
// between dma-bufs, so a descriptor can no longer say which image it is.
|
||||
Bool identityAmbiguous = false;
|
||||
};
|
||||
|
||||
Registry& Reg() {
|
||||
static Registry* registry = new Registry(); // outlives every holder at process exit
|
||||
return *registry;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
Bool IdentityOf(int fd, Uint64* dev, Uint64* ino) {
|
||||
struct stat st {};
|
||||
if (::fstat(fd, &st) != 0) return false;
|
||||
*dev = static_cast<Uint64>(st.st_dev);
|
||||
*ino = static_cast<Uint64>(st.st_ino);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
// Every descriptor of the buffer's native handle, through the public NDK only: the
|
||||
// handle is written to a socket pair and read back, and only the SCM_RIGHTS payload is
|
||||
// looked at - never the flattened bytes, whose layout is the platform's.
|
||||
Bool NativeHandleFds(const AHardwareBuffer* ahb, Vector<int>& fds, std::string& why) {
|
||||
int pair[2] = {-1, -1};
|
||||
if (::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, pair) != 0) {
|
||||
why = Format("socketpair errno=%lld", errno);
|
||||
return false;
|
||||
}
|
||||
const int sent = AHardwareBuffer_sendHandleToUnixSocket(ahb, pair[0]);
|
||||
::close(pair[0]);
|
||||
if (sent != 0) {
|
||||
why = Format("AHardwareBuffer_sendHandleToUnixSocket rc=%lld errno=%lld", sent, errno);
|
||||
::close(pair[1]);
|
||||
return false;
|
||||
}
|
||||
for (;;) {
|
||||
Uint8 data[4096];
|
||||
alignas(cmsghdr) Uint8 control[CMSG_SPACE(sizeof(int) * 64)];
|
||||
iovec iov{data, sizeof(data)};
|
||||
msghdr msg{};
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = control;
|
||||
msg.msg_controllen = sizeof(control);
|
||||
const ssize_t got = ::recvmsg(pair[1], &msg, MSG_CMSG_CLOEXEC);
|
||||
if (got < 0 && errno == EINTR) continue;
|
||||
if (got <= 0) break;
|
||||
for (cmsghdr* c = CMSG_FIRSTHDR(&msg); c != nullptr; c = CMSG_NXTHDR(&msg, c)) {
|
||||
if (c->cmsg_level != SOL_SOCKET || c->cmsg_type != SCM_RIGHTS) continue;
|
||||
const SizeT n = (c->cmsg_len - CMSG_LEN(0)) / sizeof(int);
|
||||
const int* in = reinterpret_cast<const int*>(CMSG_DATA(c));
|
||||
for (SizeT i = 0; i < n; ++i) fds.push_back(in[i]);
|
||||
}
|
||||
}
|
||||
::close(pair[1]);
|
||||
if (fds.empty()) {
|
||||
why = "the buffer's native handle carries no descriptor";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool AllocateNative(Image& image, std::string& why) {
|
||||
AHardwareBuffer_Desc desc{};
|
||||
desc.width = image.Width;
|
||||
desc.height = image.Height;
|
||||
desc.layers = 1;
|
||||
desc.format = image.Fourcc == kFourccXbgr8888 ? AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM
|
||||
: AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT;
|
||||
AHardwareBuffer* ahb = nullptr;
|
||||
const int allocated = AHardwareBuffer_allocate(&desc, &ahb);
|
||||
if (allocated != 0 || ahb == nullptr) {
|
||||
why = Format("AHardwareBuffer_allocate(%lldx%lld) rc=%lld", image.Width, image.Height, allocated);
|
||||
return false;
|
||||
}
|
||||
AHardwareBuffer_Desc described{};
|
||||
AHardwareBuffer_describe(ahb, &described);
|
||||
Vector<int> fds;
|
||||
if (!NativeHandleFds(ahb, fds, why)) {
|
||||
AHardwareBuffer_release(ahb);
|
||||
return false;
|
||||
}
|
||||
image.Native = ahb;
|
||||
image.Fd = fds[0];
|
||||
for (SizeT i = 1; i < fds.size(); ++i) ::close(fds[i]);
|
||||
image.Stride = described.stride * 4;
|
||||
image.Offset = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReleaseNative(void* native) {
|
||||
if (native != nullptr) AHardwareBuffer_release(static_cast<AHardwareBuffer*>(native));
|
||||
}
|
||||
#elif !defined(_WIN32)
|
||||
// A host has no AHardwareBuffer. A memfd stands in for the dma-buf so the registry, the
|
||||
// descriptor round trip and identification can be exercised by the unit tests; no backend
|
||||
// binds one.
|
||||
Bool AllocateNative(Image& image, std::string& why) {
|
||||
image.Stride = image.Width * 4;
|
||||
image.Offset = 0;
|
||||
const int fd = ::memfd_create("mobilegl-shared-image", MFD_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
why = Format("memfd_create errno=%lld", errno);
|
||||
return false;
|
||||
}
|
||||
if (::ftruncate(fd, static_cast<off_t>(image.Stride) * image.Height) != 0) {
|
||||
why = Format("ftruncate errno=%lld", errno);
|
||||
::close(fd);
|
||||
return false;
|
||||
}
|
||||
image.Fd = fd;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReleaseNative(void*) {}
|
||||
#else
|
||||
Bool AllocateNative(Image&, std::string& why) {
|
||||
why = "shared images need POSIX descriptors";
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReleaseNative(void*) {}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
Image::~Image() {
|
||||
Registry& reg = Reg();
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(reg.mutex);
|
||||
// Only this image's own entries: the weak pointer is already expired, but a newer
|
||||
// image may have taken the identity over (the kernel recycles inodes).
|
||||
if (auto it = reg.byId.find(Id); it != reg.byId.end() && it->second.expired()) reg.byId.erase(it);
|
||||
const auto key = std::make_pair(IdentityDev, IdentityIno);
|
||||
if (auto it = reg.byIdentity.find(key); it != reg.byIdentity.end() && it->second.expired())
|
||||
reg.byIdentity.erase(it);
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
if (Fd >= 0) ::close(Fd);
|
||||
#endif
|
||||
ReleaseNative(Native);
|
||||
}
|
||||
|
||||
Bool FourccSupported(Uint32 fourcc) { return fourcc == kFourccAbgr8888 || fourcc == kFourccXbgr8888; }
|
||||
|
||||
ImageRef Allocate(Uint32 width, Uint32 height, Uint32 fourcc, std::string& why) {
|
||||
if (width == 0 || height == 0 || width > 16384 || height > 16384) {
|
||||
why = Format("%lldx%lld is not an image size", width, height);
|
||||
return nullptr;
|
||||
}
|
||||
if (!FourccSupported(fourcc)) {
|
||||
why = Format("fourcc 0x%08llx is not a shared-image format", fourcc);
|
||||
return nullptr;
|
||||
}
|
||||
Registry& reg = Reg();
|
||||
auto image = std::make_shared<Image>();
|
||||
image->Id = reg.nextId.fetch_add(1, std::memory_order_relaxed);
|
||||
image->Width = width;
|
||||
image->Height = height;
|
||||
image->Fourcc = fourcc;
|
||||
if (!AllocateNative(*image, why)) return nullptr;
|
||||
#if !defined(_WIN32)
|
||||
if (!IdentityOf(image->Fd, &image->IdentityDev, &image->IdentityIno)) {
|
||||
why = Format("fstat on the exported descriptor errno=%lld", errno);
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
const std::lock_guard<std::mutex> lock(reg.mutex);
|
||||
reg.byId[image->Id] = image;
|
||||
const auto key = std::make_pair(image->IdentityDev, image->IdentityIno);
|
||||
if (auto it = reg.byIdentity.find(key); it != reg.byIdentity.end() && !it->second.expired()) {
|
||||
if (!reg.identityAmbiguous) {
|
||||
MGLOG_E("MG_Remote server: two live shared images have one dma-buf identity (dev %llu ino %llu); "
|
||||
"this kernel does not keep dma-buf inodes unique, so a returned descriptor cannot name its "
|
||||
"image and every shared-image import is refused from here on",
|
||||
static_cast<unsigned long long>(key.first), static_cast<unsigned long long>(key.second));
|
||||
}
|
||||
reg.identityAmbiguous = true;
|
||||
}
|
||||
reg.byIdentity[key] = image;
|
||||
return image;
|
||||
}
|
||||
|
||||
ImageRef Identify(int fd, std::string& why) {
|
||||
#if defined(_WIN32)
|
||||
(void)fd;
|
||||
why = "shared images need POSIX descriptors";
|
||||
return nullptr;
|
||||
#else
|
||||
Uint64 dev = 0, ino = 0;
|
||||
if (!IdentityOf(fd, &dev, &ino)) {
|
||||
why = Format("fstat on the imported descriptor errno=%lld", errno);
|
||||
return nullptr;
|
||||
}
|
||||
Registry& reg = Reg();
|
||||
const std::lock_guard<std::mutex> lock(reg.mutex);
|
||||
if (reg.identityAmbiguous) {
|
||||
why = "dma-buf identities are not unique on this kernel";
|
||||
return nullptr;
|
||||
}
|
||||
const auto it = reg.byIdentity.find(std::make_pair(dev, ino));
|
||||
ImageRef image = it != reg.byIdentity.end() ? it->second.lock() : nullptr;
|
||||
if (image == nullptr) {
|
||||
why = Format("the descriptor (dev %llu ino %llu) was not exported by this server", static_cast<long long>(dev),
|
||||
static_cast<long long>(ino));
|
||||
}
|
||||
return image;
|
||||
#endif
|
||||
}
|
||||
|
||||
ImageRef Find(Uint64 id) {
|
||||
Registry& reg = Reg();
|
||||
const std::lock_guard<std::mutex> lock(reg.mutex);
|
||||
const auto it = reg.byId.find(id);
|
||||
return it != reg.byId.end() ? it->second.lock() : nullptr;
|
||||
}
|
||||
|
||||
SizeT LiveCount() {
|
||||
Registry& reg = Reg();
|
||||
const std::lock_guard<std::mutex> lock(reg.mutex);
|
||||
SizeT live = 0;
|
||||
for (const auto& [id, weak] : reg.byId) live += weak.expired() ? 0 : 1;
|
||||
return live;
|
||||
}
|
||||
|
||||
void SessionHolder::Hold(const ImageRef& image) {
|
||||
if (image == nullptr) return;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_images[image->Id] = image;
|
||||
}
|
||||
|
||||
Bool SessionHolder::Release(Uint64 id) {
|
||||
ImageRef dropped;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const auto it = m_images.find(id);
|
||||
if (it == m_images.end()) return false;
|
||||
dropped = std::move(it->second); // destroyed after the unlock: ~Image takes the registry lock
|
||||
m_images.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
ImageRef SessionHolder::Get(Uint64 id) const {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
const auto it = m_images.find(id);
|
||||
return it != m_images.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void SessionHolder::Clear() {
|
||||
UnorderedMap<Uint64, ImageRef> dropped;
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
dropped.swap(m_images);
|
||||
}
|
||||
}
|
||||
|
||||
SizeT SessionHolder::Count() const {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_images.size();
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Server::SharedImages
|
||||
@@ -0,0 +1,98 @@
|
||||
// MobileGL - MobileGL/MG_Remote/Server/SharedImageRegistry.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
|
||||
|
||||
// SHARED IMAGES: colour buffers the server allocates and hands to clients as dma-buf descriptors,
|
||||
// so a frame one client renders can be sampled by another (a Wayland window by its compositor)
|
||||
// without leaving GPU memory (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md).
|
||||
//
|
||||
// THE SERVER IS THE ALLOCATOR AND THE ONLY IMPORTER. An image is an AHardwareBuffer; what leaves
|
||||
// the process is a duplicate of its first native-handle descriptor, which on every Android kernel
|
||||
// with dma-buf heaps is a dma-buf. A descriptor that comes BACK (a compositor importing a client's
|
||||
// wl_buffer) is never imported as raw memory - its layout is the allocator's business - it is
|
||||
// IDENTIFIED: the kernel gives each dma-buf its own inode, so (st_dev, st_ino) names the image it
|
||||
// was exported from, and the backends bind that AHardwareBuffer through the Android import paths.
|
||||
// Nothing here reads a vendor's handle layout or assumes a memory layout.
|
||||
//
|
||||
// LIFETIME. The registry holds weak references only. Whoever still needs an image holds an
|
||||
// ImageRef: the session that allocated or imported it (until its release or the session's end)
|
||||
// and a backend object built on it (an EGLImage, a VkImage) for as long as that object lives.
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace MobileGL::MG_Remote::Server::SharedImages {
|
||||
|
||||
// DRM fourcc codes (drm_fourcc.h). Little-endian packed: ABGR8888 is R, G, B, A in memory,
|
||||
// which is GL's RGBA8 and AHardwareBuffer's R8G8B8A8.
|
||||
inline constexpr Uint32 FourCC(char a, char b, char c, char d) {
|
||||
return static_cast<Uint32>(static_cast<Uint8>(a)) | (static_cast<Uint32>(static_cast<Uint8>(b)) << 8) |
|
||||
(static_cast<Uint32>(static_cast<Uint8>(c)) << 16) | (static_cast<Uint32>(static_cast<Uint8>(d)) << 24);
|
||||
}
|
||||
inline constexpr Uint32 kFourccAbgr8888 = FourCC('A', 'B', '2', '4');
|
||||
inline constexpr Uint32 kFourccXbgr8888 = FourCC('X', 'B', '2', '4');
|
||||
// "The layout is the allocator's": what an AHardwareBuffer is to anybody but its allocator.
|
||||
inline constexpr Uint64 kModifierInvalid = 0x00ffffffffffffffull;
|
||||
inline constexpr Uint64 kModifierLinear = 0;
|
||||
|
||||
// Whether images of this fourcc can be allocated (and so imported) at all.
|
||||
Bool FourccSupported(Uint32 fourcc);
|
||||
|
||||
struct Image {
|
||||
Uint64 Id = 0;
|
||||
Uint32 Width = 0;
|
||||
Uint32 Height = 0;
|
||||
Uint32 Fourcc = 0;
|
||||
Uint32 Stride = 0; // bytes
|
||||
Uint32 Offset = 0;
|
||||
Uint64 Modifier = kModifierInvalid;
|
||||
// AHardwareBuffer* on Android; null on a host, where the memory is a memfd (unit tests).
|
||||
void* Native = nullptr;
|
||||
// The descriptor clients receive duplicates of. Owned.
|
||||
int Fd = -1;
|
||||
Uint64 IdentityDev = 0;
|
||||
Uint64 IdentityIno = 0;
|
||||
|
||||
Image() = default;
|
||||
Image(const Image&) = delete;
|
||||
Image& operator=(const Image&) = delete;
|
||||
~Image();
|
||||
};
|
||||
using ImageRef = SharedPtr<const Image>;
|
||||
|
||||
// A new image of `width` x `height` `fourcc`, registered. Null with `why` on failure.
|
||||
ImageRef Allocate(Uint32 width, Uint32 height, Uint32 fourcc, std::string& why);
|
||||
|
||||
// The live image `fd` was exported from, or null (`why` says which test failed): a descriptor
|
||||
// this server never exported, or one whose identity the kernel does not keep unique.
|
||||
ImageRef Identify(int fd, std::string& why);
|
||||
|
||||
// The live image with this id, or null.
|
||||
ImageRef Find(Uint64 id);
|
||||
|
||||
// Live images, for tests and diagnostics.
|
||||
SizeT LiveCount();
|
||||
|
||||
// One session's references: what it allocated or imported and has not released yet. Dropping
|
||||
// the holder (the session ending) drops every one of them.
|
||||
class SessionHolder {
|
||||
public:
|
||||
void Hold(const ImageRef& image);
|
||||
Bool Release(Uint64 id);
|
||||
ImageRef Get(Uint64 id) const;
|
||||
void Clear();
|
||||
SizeT Count() const;
|
||||
|
||||
private:
|
||||
mutable std::mutex m_mutex;
|
||||
UnorderedMap<Uint64, ImageRef> m_images;
|
||||
};
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Server::SharedImages
|
||||
@@ -264,7 +264,8 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
X(DeleteStreamOutput, MGPStreamOutputBind) \
|
||||
X(ReadPixelsToBuffer, MGPReadbackToBuffer) \
|
||||
X(GetTextureImageToBuffer, MGPReadbackToBuffer) \
|
||||
X(BindContext, MGPBindContext)
|
||||
X(BindContext, MGPBindContext) \
|
||||
X(SharedImage, MGPSharedImageOp)
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -2565,6 +2566,20 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
return m_verbs != nullptr &&
|
||||
m_verbs->OnBindContext(*static_cast<const MGPBindContext*>(payload));
|
||||
|
||||
// ---- shared images (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md), opcode 85.
|
||||
//
|
||||
// A fixed-size POD; every operation is answered, and a refusal is an answer (DECLINED,
|
||||
// the sink logs why) rather than a fault: an import of a descriptor this server never
|
||||
// exported is something a compositor is allowed to try.
|
||||
case MGPWireOp::SharedImage: {
|
||||
MGPSharedImageReply result{};
|
||||
const Bool ok = m_verbs != nullptr &&
|
||||
m_verbs->OnSharedImage(*static_cast<const MGPSharedImageOp*>(payload), seq, result);
|
||||
PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, ok ? &result : nullptr,
|
||||
ok ? sizeof(result) : 0);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ---- P5c rv (CONTRACT-P5C.md §5.3): the residual-value record, opcode 79 -------------
|
||||
//
|
||||
// An ordinary set_* row, unlike the two control records beside it: a fixed-width POD
|
||||
|
||||
@@ -687,6 +687,17 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
(void)bind;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- shared images, opcode 85 (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md).
|
||||
// `seq` is the record's reply-slot id, which an Import's descriptor names on the aux
|
||||
// socket. True fills `reply`; false is a DECLINED answer.
|
||||
virtual Bool OnSharedImage(const MG_Pipe::MGPSharedImageOp& op, Uint64 seq,
|
||||
MG_Pipe::MGPSharedImageReply& reply) {
|
||||
(void)op;
|
||||
(void)seq;
|
||||
(void)reply;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Not thread safe: one decoder on the apply thread, by construction.
|
||||
|
||||
Reference in New Issue
Block a user