/* -*- 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 "mozilla/ipc/SharedMemoryHandle.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 WaylandBuffer::mDumpSerial = PR_GetEnv("MOZ_WAYLAND_DUMP_WL_BUFFERS") ? 1 : 0; MOZ_RUNINIT char* WaylandBuffer::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(); auto handle = ipc::shared_memory::Create(aSize); if (!handle) { NS_WARNING("WaylandShmPool: Unable to allocate shared memory!"); return nullptr; } shmPool->mShmHandle = handle.Clone(); shmPool->mShmPool = wl_shm_create_pool(aWaylandDisplay->GetShm(), handle.Clone().TakePlatformHandle().get(), aSize); if (!shmPool->mShmPool) { NS_WARNING("WaylandShmPool: Unable to allocate shared memory pool!"); return nullptr; } return shmPool; } void* WaylandShmPool::GetImageData() { if (!mShm) { mShm = mShmHandle.Map(); if (!mShm) { NS_WARNING("WaylandShmPool: Failed to map Shm!"); return nullptr; } } return mShm.Address(); } WaylandShmPool::~WaylandShmPool() { MozClearPointer(mShmPool, wl_shm_pool_destroy); } WaylandBuffer::WaylandBuffer(const LayoutDeviceIntSize& aSize) : mSize(aSize) {} bool WaylandBuffer::IsAttachedToSurface(WaylandSurface* aWaylandSurface) { return mAttachedToSurface == aWaylandSurface; } wl_buffer* WaylandBuffer::BorrowBuffer(WaylandSurfaceLock& aSurfaceLock) { LOGWAYLAND( "WaylandBuffer::BorrowBuffer() [%p] WaylandSurface [%p] wl_buffer [%p]", (void*)this, mAttachedToSurface ? mAttachedToSurface->GetLoggingWidget() : nullptr, mWLBuffer); MOZ_RELEASE_ASSERT(!mAttachedToSurface && !mIsAttachedToCompositor, "We're already attached!"); MOZ_DIAGNOSTIC_ASSERT(!mBufferDeleteSyncCallback, "We're already deleted!?"); if (!CreateWlBuffer()) { return nullptr; } mAttachedToSurface = aSurfaceLock.GetWaylandSurface(); LOGWAYLAND( "WaylandBuffer::BorrowBuffer() [%p] WaylandSurface [%p] wl_buffer [%p]", (void*)this, mAttachedToSurface ? mAttachedToSurface->GetLoggingWidget() : nullptr, mWLBuffer); return mWLBuffer; } void WaylandBuffer::DeleteWlBuffer() { if (!mWLBuffer) { return; } LOGWAYLAND("WaylandBuffer::DeleteWlBuffer() [%p] wl_buffer [%p] managed %d", (void*)this, mWLBuffer, mManagingWLBuffer); if (mManagingWLBuffer) { MozClearPointer(mWLBuffer, wl_buffer_destroy); } else { // Remove reference to this WaylandBuffer wl_proxy_set_user_data((wl_proxy*)mWLBuffer, nullptr); mWLBuffer = nullptr; } } void WaylandBuffer::ReturnBufferDetached(WaylandSurfaceLock& aSurfaceLock) { LOGWAYLAND("WaylandBuffer::ReturnBufferDetached() [%p] WaylandSurface [%p]", (void*)this, mAttachedToSurface.get()); MOZ_DIAGNOSTIC_ASSERT(aSurfaceLock.GetWaylandSurface() == mAttachedToSurface); DeleteWlBuffer(); mIsAttachedToCompositor = false; mAttachedToSurface = nullptr; } wl_buffer* WaylandBuffer::CreateAndTakeWLBuffer() { LOGWAYLAND("WaylandBuffer::CreateAndTakeWLBuffer() [%p]", (void*)this); MOZ_DIAGNOSTIC_ASSERT(!mAttachedToSurface); if (!CreateWlBuffer()) { return nullptr; } mManagingWLBuffer = false; return mWLBuffer; } void WaylandBuffer::SetExternalWLBuffer(wl_buffer* aWLBuffer) { LOGWAYLAND("WaylandBuffer::SetExternalWLBuffer() [%p] wl_buffer %p", (void*)this, aWLBuffer); MOZ_DIAGNOSTIC_ASSERT(!mAttachedToSurface); MOZ_DIAGNOSTIC_ASSERT(!mWLBuffer); mManagingWLBuffer = false; mWLBuffer = aWLBuffer; mWLBufferID = reinterpret_cast(mWLBuffer); } struct SurfaceAndBuffer { SurfaceAndBuffer(WaylandSurface* aSurface, WaylandBuffer* aBuffer) : mSurface(aSurface), mBuffer(aBuffer) {}; RefPtr mSurface; RefPtr mBuffer; }; static void BufferDeleteSyncFinished(void* aData, struct wl_callback* callback, uint32_t time) { UniquePtr ref(static_cast(aData)); LOGWAYLAND( "BufferDeleteSyncFinished() WaylandSurface [%p] WaylandBuffer [%p]", ref->mSurface.get(), ref->mBuffer.get()); ref->mBuffer->ClearSyncHandler(); ref->mSurface->BufferFreeCallbackHandler(ref->mBuffer->GetWlBufferID(), /* aWlBufferDelete */ true); } static const struct wl_callback_listener sBufferDeleteSyncListener = { .done = BufferDeleteSyncFinished, }; void WaylandBuffer::ClearSyncHandler() { AssertIsOnMainThread(); MOZ_DIAGNOSTIC_ASSERT(!mWLBuffer); mBufferDeleteSyncCallback = nullptr; } void WaylandBuffer::ReturnBufferAttached(WaylandSurfaceLock& aSurfaceLock) { LOGWAYLAND("WaylandBuffer::ReturnBufferAttached() [%p] WaylandSurface [%p]", (void*)this, mAttachedToSurface.get()); MOZ_DIAGNOSTIC_ASSERT(aSurfaceLock.GetWaylandSurface() == mAttachedToSurface); MOZ_DIAGNOSTIC_ASSERT(mIsAttachedToCompositor, "WaylandBuffer is not attached to compostor!"); // It's possible that ReturnBufferAttached() is called twice for the same // WaylandBuffer, may happens if WaylandSurface is // unmapped -> mapped -> unmapped quickly so the mBufferDeleteSyncCallback // from the first unmap is not finished yet. if (mBufferDeleteSyncCallback) { MOZ_DIAGNOSTIC_ASSERT(!mWLBuffer, "We should not have wl_buffer!"); return; } MOZ_DIAGNOSTIC_ASSERT(mWLBuffer, "Missing wl_buffer!"); // Delete wl_buffer now and use wl_display_sync() to make sure // it's really deleted. DeleteWlBuffer(); // 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()); wl_callback_add_listener(mBufferDeleteSyncCallback, &sBufferDeleteSyncListener, new SurfaceAndBuffer(mAttachedToSurface, this)); return; } /* 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; } mWLBuffer = wl_shm_pool_create_buffer(mShmPool->GetShmPool(), 0, mSize.width, mSize.height, mSize.width * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888); mWLBufferID = reinterpret_cast(mWLBuffer); LOGWAYLAND("WaylandBufferSHM::CreateWlBuffer() [%p] wl_buffer [%p]", (void*)this, mWLBuffer); return !!mWLBuffer; } WaylandBufferSHM::WaylandBufferSHM(const LayoutDeviceIntSize& aSize) : WaylandBuffer(aSize) { LOGWAYLAND("WaylandBufferSHM::WaylandBufferSHM() [%p]\n", (void*)this); } WaylandBufferSHM::~WaylandBufferSHM() { LOGWAYLAND("WaylandBufferSHM::~WaylandBufferSHM() [%p]\n", (void*)this); MOZ_RELEASE_ASSERT(!mBufferDeleteSyncCallback); MOZ_RELEASE_ASSERT(!IsAttached()); // We can delete wl_buffer as it not attached. DeleteWlBuffer(); } 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-sw-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( aGL, aSize.width, aSize.height, DMABUF_SCANOUT | DMABUF_USE_MODIFIERS, aFormat); 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 mWLBuffer; } mWLBuffer = mDMABufSurface->CreateWlBuffer(); mWLBufferID = reinterpret_cast(mWLBuffer); LOGWAYLAND("WaylandBufferDMABUF::CreateWlBuffer() [%p] UID %d wl_buffer [%p]", (void*)this, mDMABufSurface->GetUID(), mWLBuffer); return !!mWLBuffer; } 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_RELEASE_ASSERT(!mBufferDeleteSyncCallback); MOZ_RELEASE_ASSERT(!IsAttached()); // We can delete wl_buffer as it not attached. DeleteWlBuffer(); } #ifdef MOZ_LOGGING void WaylandBufferDMABUF::DumpToFile(const char* aHint) { if (!mDumpSerial) { return; } nsCString filename; if (mDumpDir) { filename.Append(mDumpDir); filename.Append('/'); } filename.AppendPrintf("firefox-wl-buffer-dmabuf-%.5d-%s.png", mDumpSerial++, aHint); mDMABufSurface->DumpToFile(filename.get()); LOGWAYLAND("Dumped wl_buffer to %s\n", filename.get()); } #endif WaylandBufferDMABUFHolder::WaylandBufferDMABUFHolder(DMABufSurface* aSurface, wl_buffer* aWLBuffer) : mWLBuffer(aWLBuffer) { mUID = aSurface->GetUID(); mPID = aSurface->GetPID(); LOGWAYLAND( "WaylandBufferDMABUFHolder::WaylandBufferDMABUFHolder wl_buffer [%p] UID " "%d PID %d", mWLBuffer, mUID, mPID); } WaylandBufferDMABUFHolder::~WaylandBufferDMABUFHolder() { LOGWAYLAND( "WaylandBufferDMABUFHolder::~WaylandBufferDMABUFHolder wl_buffer [%p] " "UID %d PID %d", mWLBuffer, mUID, mPID); MozClearPointer(mWLBuffer, wl_buffer_destroy); } bool WaylandBufferDMABUFHolder::Matches(DMABufSurface* aSurface) const { return mUID == aSurface->GetUID() && mPID == aSurface->GetPID(); } } // namespace mozilla::widget