[Fix] (SharedImage): create dma-buf wl_buffers without create_immed, drain the ring before teardown ops, and decline Magma records after the renderer is gone

This commit is contained in:
2026-10-02 09:18:05 -04:00
parent 3d17e8c10b
commit 350fc699a1
5 changed files with 59 additions and 30 deletions
@@ -1760,7 +1760,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void Present() {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::Present called with null VulkanRenderer");
// A served session's renderer is gone once its client released its resources; a present
// that still reaches it has nothing to show and is dropped rather than dereferenced.
if (!pVulkanRenderer) {
MGLOG_E_ONCE("DirectVulkan: a present reached a session whose renderer is already released; dropped");
return;
}
pVulkanRenderer->Present();
// THE frame boundary for the MGPipe counters, at the backend entry point rather
// than inside VulkanRenderer::Present: that function has an early return for the
@@ -181,34 +181,39 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
#if MOBILEGL_BUILD_DISAGGREGATED
VkBufferManager& WireManager() {
if (g_activeBufferManager.Get() == nullptr) {
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"wire-buffer-manager\"}");
std::abort();
// The session's manager, or null once its renderer is gone - a client's records can still
// arrive after its eglTerminate released it (or, surfaceless, before its first surface built
// one). Such a record is declined with one line rather than taking the whole server down.
VkBufferManager* WireManager(const char* op) {
VkBufferManager* manager = g_activeBufferManager.Get();
if (manager == nullptr) {
MGLOG_E_ONCE("Magma: a wire buffer %s reached a session with no renderer; declined", op);
}
return *g_activeBufferManager.Get();
return manager;
}
const MG_Pipe::MGPipeResourceOps g_vulkanWireResourceOps = {
.Create = [](auto res, const auto& desc) { WireManager().CreateWireBuffer(res, desc); },
.Create = [](auto res, const auto& desc) { if (auto* m = WireManager("create")) m->CreateWireBuffer(res, desc); },
.Respecify = [](auto res, const auto& desc, const void* bytes) {
WireManager().RespecifyWireBuffer(res, desc, bytes);
if (auto* m = WireManager("respecify")) m->RespecifyWireBuffer(res, desc, bytes);
},
.SubData = [](auto res, const auto& record, const void* bytes) {
WireManager().WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
if (auto* m = WireManager("write"))
m->WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
},
.SubDataResident = [](auto res, const auto& record, const void* bytes) {
WireManager().WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
if (auto* m = WireManager("write"))
m->WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
},
.FlushRange = [](auto res, const auto& record, const void* bytes) {
WireManager().FlushWireBuffer(res, record.Offset, record.Size, bytes);
if (auto* m = WireManager("flush")) m->FlushWireBuffer(res, record.Offset, record.Size, bytes);
},
.Readback = [](auto res, const auto& record) {
WireManager().ReadbackWireBuffer(res, record.Offset, record.Size);
if (auto* m = WireManager("readback")) m->ReadbackWireBuffer(res, record.Offset, record.Size);
},
.Destroy = [](auto res) { WireManager().DestroyWireBuffer(res); },
.Destroy = [](auto res) { if (auto* m = WireManager("destroy")) m->DestroyWireBuffer(res); },
// Split mapping remains T2: the client owns its map and pushes exact
// modified ranges. A server pointer is never donated across the wire.
.MapPersistent = [](MG_Pipe::MGPipeHandle, Uint64, const void*) -> void* { return nullptr; },
@@ -217,7 +222,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P11 B2 (T0): the one door a server pointer is still never donated through stays shut;
// what T0 adds is the other direction - the CLIENT's pages imported as the store.
.ImportExternal = [](MG_Pipe::MGPipeHandle res, void* ahb, Uint64 size) -> Bool {
return WireManager().ImportWireBuffer(res, ahb, size);
auto* m = WireManager("import");
return m != nullptr && m->ImportWireBuffer(res, ahb, size);
},
.SelfTestExternal = [](char* why, Uint64 whyBytes) -> Bool {
return VkBufferManager::SelfTestWireImport(why, whyBytes);
+20 -14
View File
@@ -129,7 +129,7 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
constexpr uint32_t kDmabufCreateParams = 1;
constexpr uint32_t kParamsDestroy = 0;
constexpr uint32_t kParamsAdd = 1;
constexpr uint32_t kParamsCreateImmed = 3; // since version 2
constexpr uint32_t kParamsCreate = 2;
// The highest linux-dmabuf version spoken here: 4 adds feedback objects this never asks for.
constexpr uint32_t kDmabufMaxVersion = 3;
// DRM fourccs of the server's shared images: R, G, B, A in memory - GL's RGBA8, so the
@@ -268,8 +268,13 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
void (*const kDmabufListener[])(void) = {reinterpret_cast<void (*)(void)>(&OnDmabufFormat),
reinterpret_cast<void (*)(void)>(&OnDmabufModifier)};
void OnParamsCreated(void*, wl_proxy*, wl_proxy*) {}
void OnParamsFailed(void* data, wl_proxy*) { *static_cast<Bool*>(data) = true; }
// The answer to a `create`: the new wl_buffer, or `failed`.
struct ParamsOutcome {
wl_proxy* buffer = nullptr;
Bool failed = false;
};
void OnParamsCreated(void* data, wl_proxy*, wl_proxy* buffer) { static_cast<ParamsOutcome*>(data)->buffer = buffer; }
void OnParamsFailed(void* data, wl_proxy*) { static_cast<ParamsOutcome*>(data)->failed = true; }
void (*const kParamsListener[])(void) = {reinterpret_cast<void (*)(void)>(&OnParamsCreated),
reinterpret_cast<void (*)(void)>(&OnParamsFailed)};
@@ -422,9 +427,10 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
buffer = DmabufBuffer{};
}
// A shared image of the window's size, as a wl_buffer. create_immed plus a round trip
// rather than a bare create_immed: a compositor that refuses the import says so with
// `failed`, and attaching that buffer would be a protocol error, fatal to the connection.
// A shared image of the window's size, as a wl_buffer. `create` and a round trip, never
// create_immed: a compositor that cannot import the buffer answers create_immed with a
// protocol error, fatal to the application's whole connection, where `create` answers
// `failed` and the window simply stays on wl_shm.
Bool Allocate(DmabufBuffer& buffer) {
const Api& api = WaylandApi();
const LinuxDmabufProtocol* protocol = LinuxDmabuf();
@@ -455,19 +461,20 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
dropImage();
return false;
}
Bool failed = false;
api.addListener(params, const_cast<void (**)(void)>(kParamsListener), &failed);
ParamsOutcome outcome;
api.addListener(params, const_cast<void (**)(void)>(kParamsListener), &outcome);
// The descriptor is duplicated into the request, so it is closed below either way.
api.marshalFlags(params, kParamsAdd, nullptr, version, 0, image.Fd, 0u, image.Offset, image.Stride,
static_cast<uint32_t>(image.Modifier >> 32),
static_cast<uint32_t>(image.Modifier & 0xffffffffu));
wl_proxy* wlBuffer = api.marshalFlags(params, kParamsCreateImmed, api.bufferInterface, version, 0, nullptr,
width, height, fourcc, 0u);
api.marshalFlags(params, kParamsCreate, nullptr, version, 0, width, height, fourcc, 0u);
const Bool settled = api.roundtripQueue(display, queue) >= 0;
api.marshalFlags(params, kParamsDestroy, nullptr, version, kMarshalFlagDestroy);
if (!settled || failed || wlBuffer == nullptr) {
wl_proxy* wlBuffer = outcome.buffer;
if (!settled || outcome.failed || wlBuffer == nullptr) {
MGLOG_E("Wayland: the compositor refused a %dx%d linux-dmabuf buffer", width, height);
if (wlBuffer) api.marshalFlags(wlBuffer, kBufferDestroy, nullptr, version, kMarshalFlagDestroy);
if (wlBuffer)
api.marshalFlags(wlBuffer, kBufferDestroy, nullptr, api.getVersion(wlBuffer), kMarshalFlagDestroy);
dropImage();
return false;
}
@@ -536,8 +543,7 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
api.marshalFlags(registry, kRegistryBind, api.shmInterface, 1, 0, name, "wl_shm", 1u, nullptr);
return;
}
// create_immed is version 2's: an older linux-dmabuf is left unbound.
if (impl->dmabufWanted && impl->dmabuf == nullptr && version >= 2 &&
if (impl->dmabufWanted && impl->dmabuf == nullptr &&
std::strcmp(interface, "zwp_linux_dmabuf_v1") == 0) {
const LinuxDmabufProtocol* protocol = LinuxDmabuf();
if (protocol == nullptr) return;
+11
View File
@@ -848,6 +848,17 @@ namespace MobileGL::MG_Remote::Server {
m_controlDone.notify_all();
return true;
}
// A TEARDOWN ORDERS AFTER THE RECORDS ITS CLIENT PUBLISHED BEFORE ASKING FOR IT. The loop
// pumps a posted frame before it drains the ring, so an eglTerminate (or a surface or
// context release) could otherwise run ahead of that client's last swap still queued in
// the ring - and the swap then reaches a backend whose renderer the release destroyed. Not
// on a full SEG_EVENT, for the loop's own reason: the drain would park inside a post.
if ((frame.kind == SurfaceControlOp::ReleaseResources || frame.kind == SurfaceControlOp::ReleaseSurface ||
frame.kind == SurfaceControlOp::DestroyContext) &&
m_session != nullptr && m_session->DataLink() != nullptr &&
m_session->DataLink()->Signals().EventRingFull->load(std::memory_order_acquire) == 0) {
DrainRing();
}
// The dispatch runs OUTSIDE the lock, exactly as the old work(user) did, and fills the
// frame's reply half; the reply is then published back into the slot under the lock so
// the poster's copy-out after m_controlDone sees it.
+1
View File
@@ -0,0 +1 @@
bash: build-android.sh: No such file or directory