/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WaylandBuffer.h" #include "WaylandSurface.h" #include "WaylandSurfaceLock.h" #include #include #include #include "gfx2DGlue.h" #include "gfxPlatform.h" #include "mozilla/WidgetUtilsGtk.h" #include "mozilla/gfx/Tools.h" #include "nsGtkUtils.h" #include "nsPrintfCString.h" #include "prenv.h" // For PR_GetEnv #ifdef MOZ_LOGGING # include "mozilla/Logging.h" # include "mozilla/ScopeExit.h" # include "Units.h" extern mozilla::LazyLogModule gWidgetWaylandLog; # define LOGWAYLAND(...) \ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) #else # define LOGWAYLAND(...) #endif /* MOZ_LOGGING */ using namespace mozilla::gl; namespace mozilla::widget { #define BUFFER_BPP 4 #ifdef MOZ_LOGGING MOZ_RUNINIT int WaylandBufferSHM::mDumpSerial = PR_GetEnv("MOZ_WAYLAND_DUMP_WL_BUFFERS") ? 1 : 0; MOZ_RUNINIT char* WaylandBufferSHM::mDumpDir = PR_GetEnv("MOZ_WAYLAND_DUMP_DIR"); #endif /* static */ RefPtr WaylandShmPool::Create(nsWaylandDisplay* aWaylandDisplay, int aSize) { if (!aWaylandDisplay->GetShm()) { NS_WARNING("WaylandShmPool: Missing Wayland shm interface!"); return nullptr; } RefPtr shmPool = new WaylandShmPool(); shmPool->mShm = MakeRefPtr(); if (!shmPool->mShm->Create(aSize)) { NS_WARNING("WaylandShmPool: Unable to allocate shared memory!"); return nullptr; } shmPool->mSize = aSize; shmPool->mShmPool = wl_shm_create_pool( aWaylandDisplay->GetShm(), shmPool->mShm->CloneHandle().get(), aSize); if (!shmPool->mShmPool) { NS_WARNING("WaylandShmPool: Unable to allocate shared memory pool!"); return nullptr; } return shmPool; } void* WaylandShmPool::GetImageData() { if (mImageData) { return mImageData; } if (!mShm->Map(mSize)) { NS_WARNING("WaylandShmPool: Failed to map Shm!"); return nullptr; } mImageData = mShm->Memory(); return mImageData; } WaylandShmPool::~WaylandShmPool() { MozClearPointer(mShmPool, wl_shm_pool_destroy); } WaylandBuffer::WaylandBuffer(const LayoutDeviceIntSize& aSize) : mSize(aSize) {} wl_buffer* WaylandBuffer::BorrowBuffer(RefPtr aWaylandSurface) { MOZ_RELEASE_ASSERT(!mSurface, "We're already attached!"); if (CreateWlBuffer()) { mSurface = std::move(aWaylandSurface); } LOGWAYLAND( "WaylandBuffer::BorrowBuffer() [%p] WaylandSurface [%p] wl_buffer [%p]", (void*)this, mSurface ? mSurface->GetLoggingWidget() : nullptr, mWLBuffer); MOZ_DIAGNOSTIC_ASSERT(!IsWaitingToBufferDelete(), "We're already deleted!"); return mWLBuffer; } void WaylandBuffer::DeleteWlBuffer() { if (!mWLBuffer) { return; } LOGWAYLAND("WaylandBuffer::DeleteWlBuffer() [%p] wl_buffer [%p]\n", (void*)this, mWLBuffer); MozClearPointer(mWLBuffer, wl_buffer_destroy); } static void BufferDeleteSyncFinished(void* aData, struct wl_callback* callback, uint32_t time) { LOGWAYLAND("BufferDeleteSyncFinished() [%p]", aData); RefPtr buffer = already_AddRefed(static_cast(aData)); // wl_buffer should be already deleted on our side. buffer->BufferDetachedCallbackHandler(nullptr, /* aWlBufferDeleted */ true); } static const struct wl_callback_listener sBufferDeleteSyncListener = { .done = BufferDeleteSyncFinished, }; void WaylandBuffer::ReturnBuffer(RefPtr aWaylandSurface) { LOGWAYLAND("WaylandBuffer::ReturnBuffer() [%p] WaylandSurface [%p]", (void*)this, mSurface.get()); MutexAutoLock lock(mBufferReleaseMutex); MOZ_RELEASE_ASSERT(aWaylandSurface == mSurface || !mSurface); if (mBufferDeleteSyncCallback) { MOZ_DIAGNOSTIC_ASSERT(!HasWlBuffer()); return; } DeleteWlBuffer(); // We're already detached from WaylandSurface if (!mSurface) { return; } // There are various Wayland queues processed for every thread. // It's possible that wl_buffer release event is pending in any // queue while we already asked for wl_buffer delete. // We need to finish wl_buffer removal when all events from this // point are processed so we use sync callback. // // When wl_display_sync comes back to us (from main thread) // we know all events are processed and there isn't any // wl_buffer operation pending so we can safely release WaylandSurface // and WaylandBuffer objects. mBufferDeleteSyncCallback = wl_display_sync(WaylandDisplayGetWLDisplay()); // Addref this to keep it live until sync, // we're unref it at sBufferDeleteSyncListener AddRef(); wl_callback_add_listener(mBufferDeleteSyncCallback, &sBufferDeleteSyncListener, this); } void WaylandBuffer::BufferDetachedCallbackHandler(wl_buffer* aBuffer, bool aWlBufferDeleted) { LOGWAYLAND( "WaylandBuffer::BufferDetachedCallbackHandler() [%p] WaylandSurface [%p] " "aBuffer [%p] aWlBufferDeleted %d GetWlBuffer() [%p]", (void*)this, mSurface ? mSurface->GetLoggingWidget() : nullptr, aBuffer, aWlBufferDeleted, GetWlBuffer()); // BufferDetachedCallbackHandler() should be caled by Wayland compostor // on main thread only. AssertIsOnMainThread(); // aWlBufferDeleted means wl_buffer should be nullptr MOZ_DIAGNOSTIC_ASSERT(!aBuffer == aWlBufferDeleted); RefPtr surface; // Don't take mBufferReleaseMutex and mSurface locks together, // may lead to deadlock. { MutexAutoLock lock(mBufferReleaseMutex); // We should release correct buffer. // If GetWlBuffer() is nullptr (deleted) we should have valid delete // callback. MOZ_DIAGNOSTIC_ASSERT(aBuffer == GetWlBuffer() || (!GetWlBuffer() && mBufferDeleteSyncCallback)); if (aWlBufferDeleted) { MOZ_DIAGNOSTIC_ASSERT(mBufferDeleteSyncCallback); mBufferDeleteSyncCallback = nullptr; } // We might be unreffed by previous BufferDetachedCallbackHandler() callback // as it's called for both wl_buffer delete and wl_buffer detach events. if (!mSurface) { return; } // Clear surface reference so WaylandBuffer is marked as not attached now. surface = std::move(mSurface); } // Notify WaylandSurface we're detached by Wayland compositor // so it can clear reference to us. WaylandSurfaceLock surfaceLock(surface); surface->DetachedByWaylandCompositorLocked(surfaceLock, this); } static void BufferDetachedCallbackHandler(void* aData, wl_buffer* aBuffer) { LOGWAYLAND("BufferDetachedCallbackHandler() [%p] received wl_buffer [%p]", aData, aBuffer); RefPtr buffer = static_cast(aData); buffer->BufferDetachedCallbackHandler(aBuffer, /* aWlBufferDeleted */ false); } static const struct wl_buffer_listener sBufferDetachListener = { BufferDetachedCallbackHandler}; /* static */ RefPtr WaylandBufferSHM::Create( const LayoutDeviceIntSize& aSize) { RefPtr buffer = new WaylandBufferSHM(aSize); nsWaylandDisplay* waylandDisplay = WaylandDisplayGet(); LOGWAYLAND("WaylandBufferSHM::Create() [%p] [%d x %d]", (void*)buffer, aSize.width, aSize.height); int size = aSize.width * aSize.height * BUFFER_BPP; buffer->mShmPool = WaylandShmPool::Create(waylandDisplay, size); if (!buffer->mShmPool) { LOGWAYLAND(" failed to create shmPool"); return nullptr; } LOGWAYLAND(" created [%p] WaylandDisplay [%p]\n", buffer.get(), waylandDisplay); return buffer; } bool WaylandBufferSHM::CreateWlBuffer() { if (mWLBuffer) { return true; } LOGWAYLAND("WaylandBufferSHM::CreateWlBuffer() [%p]", (void*)this); mWLBuffer = wl_shm_pool_create_buffer(mShmPool->GetShmPool(), 0, mSize.width, mSize.height, mSize.width * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888); if (!mWLBuffer) { LOGWAYLAND(" failed to create wl_buffer"); return false; } if (wl_buffer_add_listener(mWLBuffer, &sBufferDetachListener, this) < 0) { LOGWAYLAND(" failed to attach listener"); return false; } return true; } WaylandBufferSHM::WaylandBufferSHM(const LayoutDeviceIntSize& aSize) : WaylandBuffer(aSize) { LOGWAYLAND("WaylandBufferSHM::WaylandBufferSHM() [%p]\n", (void*)this); } WaylandBufferSHM::~WaylandBufferSHM() { LOGWAYLAND("WaylandBufferSHM::~WaylandBufferSHM() [%p]\n", (void*)this); MOZ_DIAGNOSTIC_ASSERT(!IsWaitingToBufferDelete()); MOZ_DIAGNOSTIC_ASSERT(!IsAttached()); if (!IsAttached()) { DeleteWlBuffer(); } MOZ_DIAGNOSTIC_ASSERT(!HasWlBuffer()); } already_AddRefed WaylandBufferSHM::Lock() { LOGWAYLAND("WaylandBufferSHM::lock() [%p]\n", (void*)this); return gfxPlatform::CreateDrawTargetForData( static_cast(mShmPool->GetImageData()), mSize.ToUnknownSize(), BUFFER_BPP * mSize.width, GetSurfaceFormat()); } void WaylandBufferSHM::Clear() { LOGWAYLAND("WaylandBufferSHM::Clear() [%p]\n", (void*)this); memset(mShmPool->GetImageData(), 0xff, mSize.height * mSize.width * BUFFER_BPP); } #ifdef MOZ_LOGGING void WaylandBufferSHM::DumpToFile(const char* aHint) { if (!mDumpSerial) { return; } cairo_surface_t* surface = nullptr; auto unmap = MakeScopeExit([&] { if (surface) { cairo_surface_destroy(surface); } }); surface = cairo_image_surface_create_for_data( (unsigned char*)mShmPool->GetImageData(), CAIRO_FORMAT_ARGB32, mSize.width, mSize.height, BUFFER_BPP * mSize.width); if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) { nsCString filename; if (mDumpDir) { filename.Append(mDumpDir); filename.Append('/'); } filename.Append( nsPrintfCString("firefox-wl-buffer-%.5d-%s.png", mDumpSerial++, aHint)); cairo_surface_write_to_png(surface, filename.get()); LOGWAYLAND("Dumped wl_buffer to %s\n", filename.get()); } } #endif /* static */ already_AddRefed WaylandBufferDMABUF::CreateRGBA( const LayoutDeviceIntSize& aSize, GLContext* aGL, RefPtr aFormat) { RefPtr buffer = new WaylandBufferDMABUF(aSize); buffer->mDMABufSurface = DMABufSurfaceRGBA::CreateDMABufSurface( aSize.width, aSize.height, aFormat, DMABUF_SCANOUT | DMABUF_USE_MODIFIERS); if (!buffer->mDMABufSurface || !buffer->mDMABufSurface->CreateTexture(aGL)) { LOGWAYLAND(" failed to create texture"); return nullptr; } LOGWAYLAND("WaylandBufferDMABUF::CreateRGBA() [%p] UID %d [%d x %d]", (void*)buffer, buffer->mDMABufSurface->GetUID(), aSize.width, aSize.height); return buffer.forget(); } /* static */ already_AddRefed WaylandBufferDMABUF::CreateExternal( RefPtr aSurface) { const auto size = LayoutDeviceIntSize(aSurface->GetWidth(), aSurface->GetWidth()); RefPtr buffer = new WaylandBufferDMABUF(size); LOGWAYLAND("WaylandBufferDMABUF::CreateExternal() [%p] UID %d [%d x %d]", (void*)buffer, aSurface->GetUID(), size.width, size.height); buffer->mDMABufSurface = aSurface; return buffer.forget(); } bool WaylandBufferDMABUF::CreateWlBuffer() { MOZ_DIAGNOSTIC_ASSERT(mDMABufSurface); if (mWLBuffer) { return true; } LOGWAYLAND("WaylandBufferDMABUF::CreateWlBuffer() [%p] UID %d", (void*)this, mDMABufSurface->GetUID()); mWLBuffer = mDMABufSurface->CreateWlBuffer(); if (!mWLBuffer) { LOGWAYLAND(" failed to create wl_buffer"); return false; } if (wl_buffer_add_listener(mWLBuffer, &sBufferDetachListener, this) < 0) { LOGWAYLAND(" failed to attach listener!"); return false; } return true; } WaylandBufferDMABUF::WaylandBufferDMABUF(const LayoutDeviceIntSize& aSize) : WaylandBuffer(aSize) { LOGWAYLAND("WaylandBufferDMABUF::WaylandBufferDMABUF [%p]\n", (void*)this); } WaylandBufferDMABUF::~WaylandBufferDMABUF() { LOGWAYLAND("WaylandBufferDMABUF::~WaylandBufferDMABUF [%p] UID %d\n", (void*)this, mDMABufSurface ? mDMABufSurface->GetUID() : -1); MOZ_DIAGNOSTIC_ASSERT(!IsWaitingToBufferDelete()); MOZ_DIAGNOSTIC_ASSERT(!IsAttached()); if (!IsAttached()) { DeleteWlBuffer(); } MOZ_DIAGNOSTIC_ASSERT(!HasWlBuffer()); } } // namespace mozilla::widget