Files
tubestation/widget/gtk/WaylandBuffer.cpp
Cristian Tuns 3d07941e8a Backed out 3 changesets (bug 1943736, bug 1942232) for causing Bug 1943732 and Bug 1943736 CLOSED TREE
Backed out changeset 52438b61a819 (bug 1943736)
Backed out changeset cd369e4db2ee (bug 1942232)
Backed out changeset 2606acece21a (bug 1942232)
2025-01-27 17:21:25 -05:00

401 lines
12 KiB
C++

/* -*- 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 <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 "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> WaylandShmPool::Create(nsWaylandDisplay* aWaylandDisplay,
int aSize) {
if (!aWaylandDisplay->GetShm()) {
NS_WARNING("WaylandShmPool: Missing Wayland shm interface!");
return nullptr;
}
RefPtr<WaylandShmPool> shmPool = new WaylandShmPool();
shmPool->mShm = MakeRefPtr<ipc::SharedMemory>();
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<WaylandSurface> 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<WaylandBuffer*>(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<WaylandSurface> 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<WaylandSurface> 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<WaylandBuffer> buffer = static_cast<WaylandBuffer*>(aData);
buffer->BufferDetachedCallbackHandler(aBuffer, /* aWlBufferDeleted */ false);
}
static const struct wl_buffer_listener sBufferDetachListener = {
BufferDetachedCallbackHandler};
/* 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;
}
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<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-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(
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> 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();
}
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