Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "WaylandBuffer.h"
#include "WaylandSurface.h"
#include "WaylandSurfaceLock.h"
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include "gfx2DGlue.h"
#include "gfxPlatform.h"
#include "mozilla/WidgetUtilsGtk.h"
#include "mozilla/gfx/Tools.h"
#include "mozilla/gfx/Logging.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> WaylandShmPool::Create(nsWaylandDisplay* aWaylandDisplay,
int aSize) {
if (!aWaylandDisplay->GetShm()) {
NS_WARNING("WaylandShmPool: Missing Wayland shm interface!");
return nullptr;
}
RefPtr<WaylandShmPool> 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::IsAttached() const {
for (const auto& transaction : mBufferTransactions) {
if (transaction->IsAttached()) {
return true;
}
}
return false;
}
BufferTransaction* WaylandBuffer::GetTransaction() {
for (const auto& transaction : mBufferTransactions) {
if (transaction->IsDetached()) {
LOGWAYLAND("WaylandBuffer::GetTransaction() [%p] reuse transaction [%d]",
(void*)this, (int)mBufferTransactions.Length());
return transaction;
}
}
wl_buffer* buffer = mExternalWlBuffer;
if (!buffer) {
buffer = CreateWlBuffer();
}
if (!buffer) {
gfxCriticalError()
<< "WaylandBuffer::GetTransaction() failed to create wl_buffer!";
return nullptr;
}
LOGWAYLAND(
"WaylandBuffer::GetTransaction() create new [%p] wl_buffer [%p] "
"transactions [%d] external buffer [%d]",
(void*)this, buffer, (int)mBufferTransactions.Length(),
!!mExternalWlBuffer);
auto* transaction = new BufferTransaction(this, buffer, !!mExternalWlBuffer);
mBufferTransactions.AppendElement(transaction);
return transaction;
}
void WaylandBuffer::RemoveTransaction(RefPtr<BufferTransaction> aTransaction) {
LOGWAYLAND("WaylandBuffer::RemoveTransaction() [%p]", (void*)aTransaction);
[[maybe_unused]] bool removed =
mBufferTransactions.RemoveElement(aTransaction);
MOZ_DIAGNOSTIC_ASSERT(removed);
MOZ_DIAGNOSTIC_ASSERT(!mBufferTransactions.Contains(aTransaction));
}
void WaylandBuffer::SetExternalWLBuffer(wl_buffer* aWLBuffer) {
LOGWAYLAND("WaylandBuffer::SetExternalWLBuffer() [%p] wl_buffer %p",
(void*)this, aWLBuffer);
MOZ_DIAGNOSTIC_ASSERT(!mExternalWlBuffer);
mExternalWlBuffer = aWLBuffer;
}
/* static */
RefPtr<WaylandBufferSHM> WaylandBufferSHM::Create(
const LayoutDeviceIntSize& aSize) {
RefPtr<WaylandBufferSHM> 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;
}
wl_buffer* WaylandBufferSHM::CreateWlBuffer() {
MOZ_DIAGNOSTIC_ASSERT(!mExternalWlBuffer);
auto* buffer = wl_shm_pool_create_buffer(
mShmPool->GetShmPool(), 0, mSize.width, mSize.height,
mSize.width * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888);
LOGWAYLAND("WaylandBufferSHM::CreateWlBuffer() [%p] wl_buffer [%p]",
(void*)this, buffer);
return buffer;
}
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(!IsAttached());
}
already_AddRefed<gfx::DrawTarget> WaylandBufferSHM::Lock() {
LOGWAYLAND("WaylandBufferSHM::lock() [%p]\n", (void*)this);
return gfxPlatform::CreateDrawTargetForData(
static_cast<unsigned char*>(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> WaylandBufferDMABUF::CreateRGBA(
const LayoutDeviceIntSize& aSize, GLContext* aGL,
RefPtr<DRMFormat> aFormat) {
RefPtr<WaylandBufferDMABUF> 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> WaylandBufferDMABUF::CreateExternal(
RefPtr<DMABufSurface> aSurface) {
const auto size =
LayoutDeviceIntSize(aSurface->GetWidth(), aSurface->GetWidth());
RefPtr<WaylandBufferDMABUF> 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();
}
wl_buffer* WaylandBufferDMABUF::CreateWlBuffer() {
MOZ_DIAGNOSTIC_ASSERT(mDMABufSurface);
MOZ_DIAGNOSTIC_ASSERT(!mExternalWlBuffer);
auto* buffer = mDMABufSurface->CreateWlBuffer();
LOGWAYLAND("WaylandBufferDMABUF::CreateWlBuffer() [%p] UID %d wl_buffer [%p]",
(void*)this, mDMABufSurface->GetUID(), buffer);
return buffer;
}
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(!IsAttached());
}
#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();
}
wl_buffer* BufferTransaction::BufferBorrowLocked(
const WaylandSurfaceLock& aSurfaceLock) {
LOGWAYLAND(
"BufferTransaction::BufferBorrow() [%p] widget [%p] WaylandSurface [%p] "
"(old %p) "
"WaylandBuffer [%p]",
this, aSurfaceLock.GetWaylandSurface()->GetLoggingWidget(),
aSurfaceLock.GetWaylandSurface(), mSurface.get(), mBuffer.get());
MOZ_DIAGNOSTIC_ASSERT(mBufferState == BufferState::Detached);
mSurface = aSurfaceLock.GetWaylandSurface();
// We don't take reference to this. Some compositors doesn't send
// buffer release callback and we may leak BufferTransaction then.
// Rather we destroy wl_buffer at end which makes sure no release callback
// comes after BufferTransaction release.
if (wl_proxy_get_listener((wl_proxy*)mWLBuffer)) {
wl_proxy_set_user_data((wl_proxy*)mWLBuffer, this);
} else {
static const struct wl_buffer_listener listener{
[](void* aData, wl_buffer* aBuffer) {
auto transaction = static_cast<BufferTransaction*>(aData);
if (transaction) {
// BufferDetachCallback can delete transaction
RefPtr grip{transaction};
transaction->BufferDetachCallback();
}
}};
if (wl_buffer_add_listener(mWLBuffer, &listener, this) < 0) {
gfxCriticalError() << "wl_buffer_add_listener() failed";
}
}
mBufferState = BufferState::WaitingForDetach;
return mWLBuffer;
}
void BufferTransaction::BufferDetachCallback() {
WaylandSurfaceLock lock(mSurface);
LOGWAYLAND(
"BufferTransaction::BufferDetach() [%p] WaylandBuffer [%p] attached to "
"WaylandSurface %d",
this, (void*)mBuffer, mSurface->IsBufferAttached(mBuffer));
if (mBufferState != BufferState::WaitingForDelete) {
mBufferState = BufferState::Detached;
// Delete this transaction if WaylandSurface uses different WaylandBuffer
// already, we don't need to keep it for recycling.
if (!mSurface->IsBufferAttached(mBuffer)) {
DeleteTransactionLocked(lock);
}
}
}
void BufferTransaction::BufferDeleteCallback() {
LOGWAYLAND("BufferTransaction::DeleteCallback() [%p] WaylandBuffer [%p] ",
this, (void*)mBuffer);
WaylandSurfaceLock lock(mSurface);
mBufferState = BufferState::Deleted;
DeleteLocked(lock);
}
void BufferTransaction::WlBufferDeleteLocked(
const WaylandSurfaceLock& aSurfaceLock) {
LOGWAYLAND(
"BufferTransaction::WlBufferDeleteLocked() [%p] WaylandBuffer [%p] ",
this, (void*)mBuffer);
MOZ_DIAGNOSTIC_ASSERT(mWLBuffer);
if (mIsExternalBuffer) {
wl_proxy_set_user_data((wl_proxy*)mWLBuffer, nullptr);
mWLBuffer = nullptr;
} else {
MozClearPointer(mWLBuffer, wl_buffer_destroy);
}
}
void BufferTransaction::DeleteTransactionLocked(
const WaylandSurfaceLock& aSurfaceLock) {
// It's possible that the transaction is already deleted. It happens
// if one WaylandBuffer is attached to WaylandSurface,
// then detached/deleted from WaylandSurface::UnmapLocked() where all buffers
// are removed and then attached to another WaylandSurface.
if (mBufferState == BufferState::WaitingForDelete ||
mBufferState == BufferState::Deleted) {
return;
}
LOGWAYLAND(
"BufferTransaction::BufferDelete() [%p] WaylandBuffer [%p] wl_buffer "
"[%p] "
"external %d state %d",
this, (void*)mBuffer, mWLBuffer, mIsExternalBuffer, (int)mBufferState);
WlBufferDeleteLocked(aSurfaceLock);
// wl_buffer is detached so we can't get any release event so
// delete the transaction now.
if (mBufferState == BufferState::Detached) {
mBufferState = BufferState::Deleted;
DeleteLocked(aSurfaceLock);
return;
}
mBufferState = BufferState::WaitingForDelete;
// 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.
// We need to wait with WaylandMonitor
AddRef();
static const struct wl_callback_listener listener{
[](void* aData, struct wl_callback* callback, uint32_t time) {
RefPtr t = dont_AddRef(static_cast<BufferTransaction*>(aData));
t->BufferDeleteCallback();
}};
wl_callback_add_listener(wl_display_sync(WaylandDisplayGetWLDisplay()),
&listener, this);
}
void BufferTransaction::DeleteLocked(const WaylandSurfaceLock& aSurfaceLock) {
LOGWAYLAND("BufferTransaction::DeleteLocked() [%p] WaylandBuffer [%p]", this,
(void*)mBuffer);
MOZ_DIAGNOSTIC_ASSERT(mBufferState == BufferState::Deleted);
MOZ_DIAGNOSTIC_ASSERT(mSurface);
// Unlink from Surface
mSurface->RemoveTransactionLocked(aSurfaceLock, this);
mSurface = nullptr;
// This can destroy us
RefPtr grip{this};
mBuffer->RemoveTransaction(this);
mBuffer = nullptr;
}
BufferTransaction::BufferTransaction(WaylandBuffer* aBuffer,
wl_buffer* aWLBuffer,
bool aIsExternalBuffer)
: mBuffer(aBuffer),
mWLBuffer(aWLBuffer),
mIsExternalBuffer(aIsExternalBuffer) {
MOZ_COUNT_CTOR(BufferTransaction);
LOGWAYLAND(
"BufferTransaction::BufferTransaction() [%p] WaylandBuffer [%p] "
"wl_buffer [%p] external [%d]",
this, aBuffer, mWLBuffer, mIsExternalBuffer);
}
BufferTransaction::~BufferTransaction() {
MOZ_COUNT_DTOR(BufferTransaction);
LOGWAYLAND("BufferTransaction::~BufferTransaction() [%p] ", this);
MOZ_DIAGNOSTIC_ASSERT(mBufferState == BufferState::Deleted);
MOZ_DIAGNOSTIC_ASSERT(!mWLBuffer);
}
} // namespace mozilla::widget