fune/widget/gtk/DMABufSurface.cpp
stransky 186789dac5 Bug 1809026 [Linux] Reference DMABufSurface when it's created/imported r=emilio
Recently we add DMABufSurface reference when DMABufSurface is exported over IPC bridge by Serialize().
That no longer works as Serialize() may be called more times due to WebGL integration.

In this patch remove ref add in Serialize() (GlobalRefCountExport) and add global ref when it's
created/imported by IPC bridge.

That better matches DMABufSurface cycle in compositor as DMABufSurface is referenced on create and undef when it's deleted on compositor side.

Also add logging to ref/unref and print warning if surface ref count is already zero (we're trying to unref already 'unused' surface).

Differential Revision: https://phabricator.services.mozilla.com/D170624
2023-02-27 11:20:33 +00:00

1611 lines
50 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "DMABufSurface.h"
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <dlfcn.h>
#include <sys/mman.h>
#include <sys/eventfd.h>
#include <poll.h>
#include <sys/ioctl.h>
#include "mozilla/widget/gbm.h"
#include "mozilla/widget/va_drmcommon.h"
#include "YCbCrUtils.h"
#include "mozilla/gfx/2D.h"
#include "GLContextTypes.h" // for GLContext, etc
#include "GLContextEGL.h"
#include "GLContextProvider.h"
#include "ScopedGLHelpers.h"
#include "GLBlitHelper.h"
#include "GLReadTexImageHelper.h"
#include "nsGtkUtils.h"
#include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/ScopeExit.h"
/*
TODO:
DRM device selection:
https://lists.freedesktop.org/archives/wayland-devel/2018-November/039660.html
*/
/* C++ / C typecast macros for special EGL handle values */
#if defined(__cplusplus)
# define EGL_CAST(type, value) (static_cast<type>(value))
#else
# define EGL_CAST(type, value) ((type)(value))
#endif
using namespace mozilla;
using namespace mozilla::widget;
using namespace mozilla::gl;
using namespace mozilla::layers;
using namespace mozilla::gfx;
#define BUFFER_FLAGS 0
static RefPtr<GLContext> sSnapshotContext;
static StaticMutex sSnapshotContextMutex MOZ_UNANNOTATED;
static Atomic<int> gNewSurfaceUID(1);
RefPtr<GLContext> ClaimSnapshotGLContext() {
if (!sSnapshotContext) {
nsCString discardFailureId;
sSnapshotContext = GLContextProvider::CreateHeadless({}, &discardFailureId);
if (!sSnapshotContext) {
LOGDMABUF(
("ClaimSnapshotGLContext: Failed to create snapshot GLContext."));
return nullptr;
}
sSnapshotContext->mOwningThreadId = Nothing(); // No singular owner.
}
if (!sSnapshotContext->MakeCurrent()) {
LOGDMABUF(("ClaimSnapshotGLContext: Failed to make GLContext current."));
return nullptr;
}
return sSnapshotContext;
}
void ReturnSnapshotGLContext(RefPtr<GLContext> aGLContext) {
// direct eglMakeCurrent() call breaks current context caching so make sure
// it's not used.
MOZ_ASSERT(!aGLContext->mUseTLSIsCurrent);
if (!aGLContext->IsCurrent()) {
LOGDMABUF(("ReturnSnapshotGLContext() failed, is not current!"));
return;
}
const auto& gle = gl::GLContextEGL::Cast(aGLContext);
const auto& egl = gle->mEgl;
egl->fMakeCurrent(EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
bool DMABufSurface::IsGlobalRefSet() const {
if (!mGlobalRefCountFd) {
return false;
}
struct pollfd pfd;
pfd.fd = mGlobalRefCountFd;
pfd.events = POLLIN;
return poll(&pfd, 1, 0) == 1;
}
void DMABufSurface::GlobalRefRelease() {
LOGDMABUF(("DMABufSurface::GlobalRefRelease UID %d", mUID));
uint64_t counter;
MOZ_ASSERT(mGlobalRefCountFd);
if (read(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
if (errno == EAGAIN) {
LOGDMABUF(
(" GlobalRefRelease failed: already zero reference! UID %d", mUID));
}
// EAGAIN means the refcount is already zero. It happens when we release
// last reference to the surface.
if (errno != EAGAIN) {
NS_WARNING(nsPrintfCString("Failed to unref dmabuf global ref count: %s",
strerror(errno))
.get());
}
}
}
void DMABufSurface::GlobalRefAdd() {
LOGDMABUF(("DMABufSurface::GlobalRefAdd UID %d", mUID));
MOZ_ASSERT(mGlobalRefCountFd);
uint64_t counter = 1;
if (write(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
NS_WARNING(nsPrintfCString("Failed to ref dmabuf global ref count: %s",
strerror(errno))
.get());
}
}
void DMABufSurface::GlobalRefCountCreate() {
MOZ_ASSERT(!mGlobalRefCountFd);
// Create global ref count initialized to 0,
// i.e. is not referenced after create.
mGlobalRefCountFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE);
if (mGlobalRefCountFd < 0) {
NS_WARNING(nsPrintfCString("Failed to create dmabuf global ref count: %s",
strerror(errno))
.get());
mGlobalRefCountFd = 0;
return;
}
}
void DMABufSurface::GlobalRefCountImport(int aFd) {
MOZ_ASSERT(!mGlobalRefCountFd);
mGlobalRefCountFd = aFd;
if (mGlobalRefCountFd) {
GlobalRefAdd();
}
}
int DMABufSurface::GlobalRefCountExport() { return mGlobalRefCountFd; }
void DMABufSurface::GlobalRefCountDelete() {
if (mGlobalRefCountFd) {
GlobalRefRelease();
close(mGlobalRefCountFd);
mGlobalRefCountFd = 0;
}
}
void DMABufSurface::ReleaseDMABuf() {
LOGDMABUF(("DMABufSurface::ReleaseDMABuf() UID %d", mUID));
for (int i = 0; i < mBufferPlaneCount; i++) {
Unmap(i);
}
MutexAutoLock lockFD(mSurfaceLock);
CloseFileDescriptors(lockFD, /* aForceClose */ true);
for (int i = 0; i < mBufferPlaneCount; i++) {
if (mGbmBufferObject[i]) {
nsGbmLib::Destroy(mGbmBufferObject[i]);
mGbmBufferObject[i] = nullptr;
}
}
mBufferPlaneCount = 0;
}
DMABufSurface::DMABufSurface(SurfaceType aSurfaceType)
: mSurfaceType(aSurfaceType),
mBufferPlaneCount(0),
mDrmFormats(),
mStrides(),
mOffsets(),
mGbmBufferObject(),
mMappedRegion(),
mMappedRegionStride(),
mSyncFd(-1),
mSync(0),
mGlobalRefCountFd(0),
mUID(gNewSurfaceUID++),
mSurfaceLock("DMABufSurface") {
for (auto& slot : mDmabufFds) {
slot = -1;
}
for (auto& modifier : mBufferModifiers) {
modifier = DRM_FORMAT_MOD_INVALID;
}
}
DMABufSurface::~DMABufSurface() {
FenceDelete();
GlobalRefCountDelete();
}
already_AddRefed<DMABufSurface> DMABufSurface::CreateDMABufSurface(
const mozilla::layers::SurfaceDescriptor& aDesc) {
const SurfaceDescriptorDMABuf& desc = aDesc.get_SurfaceDescriptorDMABuf();
RefPtr<DMABufSurface> surf;
switch (desc.bufferType()) {
case SURFACE_RGBA:
surf = new DMABufSurfaceRGBA();
break;
case SURFACE_NV12:
case SURFACE_YUV420:
surf = new DMABufSurfaceYUV();
break;
default:
return nullptr;
}
if (!surf->Create(desc)) {
return nullptr;
}
return surf.forget();
}
void DMABufSurface::FenceDelete() {
if (mSyncFd > 0) {
close(mSyncFd);
mSyncFd = -1;
}
if (!mGL) {
return;
}
const auto& gle = gl::GLContextEGL::Cast(mGL);
const auto& egl = gle->mEgl;
if (mSync) {
egl->fDestroySync(mSync);
mSync = nullptr;
}
}
void DMABufSurface::FenceSet() {
if (!mGL || !mGL->MakeCurrent()) {
MOZ_DIAGNOSTIC_ASSERT(mGL,
"DMABufSurface::FenceSet(): missing GL context!");
return;
}
const auto& gle = gl::GLContextEGL::Cast(mGL);
const auto& egl = gle->mEgl;
if (egl->IsExtensionSupported(EGLExtension::KHR_fence_sync) &&
egl->IsExtensionSupported(EGLExtension::ANDROID_native_fence_sync)) {
FenceDelete();
mSync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
if (mSync) {
mSyncFd = egl->fDupNativeFenceFDANDROID(mSync);
mGL->fFlush();
return;
}
}
// ANDROID_native_fence_sync may not be supported so call glFinish()
// as a slow path.
mGL->fFinish();
}
void DMABufSurface::FenceWait() {
if (!mGL || mSyncFd < 0) {
MOZ_DIAGNOSTIC_ASSERT(mGL,
"DMABufSurface::FenceWait() missing GL context!");
return;
}
const auto& gle = gl::GLContextEGL::Cast(mGL);
const auto& egl = gle->mEgl;
const EGLint attribs[] = {LOCAL_EGL_SYNC_NATIVE_FENCE_FD_ANDROID, mSyncFd,
LOCAL_EGL_NONE};
EGLSync sync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
if (!sync) {
MOZ_ASSERT(false, "DMABufSurface::FenceWait(): Failed to create GLFence!");
// We failed to create GLFence so clear mSyncFd to avoid another try.
close(mSyncFd);
mSyncFd = -1;
return;
}
// mSyncFd is owned by GLFence so clear local reference to avoid double close
// at DMABufSurface::FenceDelete().
mSyncFd = -1;
egl->fClientWaitSync(sync, 0, LOCAL_EGL_FOREVER);
egl->fDestroySync(sync);
}
bool DMABufSurface::OpenFileDescriptors(const MutexAutoLock& aProofOfLock) {
for (int i = 0; i < mBufferPlaneCount; i++) {
if (!OpenFileDescriptorForPlane(aProofOfLock, i)) {
return false;
}
}
return true;
}
// We can safely close DMABuf file descriptors only when we have a valid
// GbmBufferObject. When we don't have a valid GbmBufferObject and a DMABuf
// file descriptor is closed, whole surface is released.
void DMABufSurface::CloseFileDescriptors(const MutexAutoLock& aProofOfLock,
bool aForceClose) {
for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
CloseFileDescriptorForPlane(aProofOfLock, i, aForceClose);
}
}
DMABufSurfaceRGBA::DMABufSurfaceRGBA()
: DMABufSurface(SURFACE_RGBA),
mSurfaceFlags(0),
mWidth(0),
mHeight(0),
mGmbFormat(nullptr),
mEGLImage(LOCAL_EGL_NO_IMAGE),
mTexture(0),
mGbmBufferFlags(0),
mWlBuffer(nullptr) {}
DMABufSurfaceRGBA::~DMABufSurfaceRGBA() {
ReleaseWlBuffer();
ReleaseSurface();
}
bool DMABufSurfaceRGBA::OpenFileDescriptorForPlane(
const MutexAutoLock& aProofOfLock, int aPlane) {
if (mDmabufFds[aPlane] >= 0) {
return true;
}
gbm_bo* bo = mGbmBufferObject[0];
if (NS_WARN_IF(!bo)) {
LOGDMABUF(
("DMABufSurfaceRGBA::OpenFileDescriptorForPlane: Missing "
"mGbmBufferObject object!"));
return false;
}
if (mBufferPlaneCount == 1) {
MOZ_ASSERT(aPlane == 0, "DMABuf: wrong surface plane!");
mDmabufFds[0] = nsGbmLib::GetFd(bo);
} else {
uint32_t handle = nsGbmLib::GetHandleForPlane(bo, aPlane).u32;
int ret = nsGbmLib::DrmPrimeHandleToFD(GetDMABufDevice()->GetDRMFd(),
handle, 0, &mDmabufFds[aPlane]);
if (ret < 0) {
mDmabufFds[aPlane] = -1;
}
}
if (mDmabufFds[aPlane] < 0) {
CloseFileDescriptors(aProofOfLock);
return false;
}
return true;
}
void DMABufSurfaceRGBA::CloseFileDescriptorForPlane(
const MutexAutoLock& aProofOfLock, int aPlane, bool aForceClose = false) {
if ((aForceClose || mGbmBufferObject[0]) && mDmabufFds[aPlane] >= 0) {
close(mDmabufFds[aPlane]);
mDmabufFds[aPlane] = -1;
}
}
bool DMABufSurfaceRGBA::Create(int aWidth, int aHeight,
int aDMABufSurfaceFlags) {
MOZ_ASSERT(mGbmBufferObject[0] == nullptr, "Already created?");
mSurfaceFlags = aDMABufSurfaceFlags;
mWidth = aWidth;
mHeight = aHeight;
LOGDMABUF(("DMABufSurfaceRGBA::Create() UID %d size %d x %d\n", mUID, mWidth,
mHeight));
if (!GetDMABufDevice()->GetGbmDevice()) {
LOGDMABUF((" Missing GbmDevice!"));
return false;
}
mGmbFormat = GetDMABufDevice()->GetGbmFormat(mSurfaceFlags & DMABUF_ALPHA);
if (!mGmbFormat) {
// Requested DRM format is not supported.
return false;
}
mDrmFormats[0] = mGmbFormat->mFormat;
bool useModifiers = (aDMABufSurfaceFlags & DMABUF_USE_MODIFIERS) &&
mGmbFormat->mModifiersCount > 0;
if (useModifiers) {
LOGDMABUF((" Creating with modifiers\n"));
mGbmBufferObject[0] = nsGbmLib::CreateWithModifiers(
GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight, mDrmFormats[0],
mGmbFormat->mModifiers, mGmbFormat->mModifiersCount);
if (mGbmBufferObject[0]) {
mBufferModifiers[0] = nsGbmLib::GetModifier(mGbmBufferObject[0]);
}
}
if (!mGbmBufferObject[0]) {
LOGDMABUF((" Creating without modifiers\n"));
mGbmBufferFlags = GBM_BO_USE_LINEAR;
mGbmBufferObject[0] =
nsGbmLib::Create(GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight,
mDrmFormats[0], mGbmBufferFlags);
mBufferModifiers[0] = DRM_FORMAT_MOD_INVALID;
}
if (!mGbmBufferObject[0]) {
LOGDMABUF((" Failed to create GbmBufferObject\n"));
return false;
}
if (mBufferModifiers[0] != DRM_FORMAT_MOD_INVALID) {
mBufferPlaneCount = nsGbmLib::GetPlaneCount(mGbmBufferObject[0]);
if (mBufferPlaneCount > DMABUF_BUFFER_PLANES) {
LOGDMABUF((" There's too many dmabuf planes!"));
ReleaseSurface();
return false;
}
for (int i = 0; i < mBufferPlaneCount; i++) {
mStrides[i] = nsGbmLib::GetStrideForPlane(mGbmBufferObject[0], i);
mOffsets[i] = nsGbmLib::GetOffset(mGbmBufferObject[0], i);
}
} else {
mBufferPlaneCount = 1;
mStrides[0] = nsGbmLib::GetStride(mGbmBufferObject[0]);
}
LOGDMABUF((" Success\n"));
return true;
}
bool DMABufSurfaceRGBA::Create(mozilla::gl::GLContext* aGLContext,
const EGLImageKHR aEGLImage, int aWidth,
int aHeight) {
LOGDMABUF(("DMABufSurfaceRGBA::Create() from EGLImage UID = %d\n", mUID));
if (!aGLContext) {
return false;
}
const auto& gle = gl::GLContextEGL::Cast(aGLContext);
const auto& egl = gle->mEgl;
mGL = aGLContext;
mWidth = aWidth;
mHeight = aHeight;
mEGLImage = aEGLImage;
if (!egl->fExportDMABUFImageQuery(mEGLImage, mDrmFormats, &mBufferPlaneCount,
mBufferModifiers)) {
LOGDMABUF((" ExportDMABUFImageQueryMESA failed, quit\n"));
return false;
}
if (mBufferPlaneCount > DMABUF_BUFFER_PLANES) {
LOGDMABUF((" wrong plane count %d, quit\n", mBufferPlaneCount));
return false;
}
if (!egl->fExportDMABUFImage(mEGLImage, mDmabufFds, mStrides, mOffsets)) {
LOGDMABUF((" ExportDMABUFImageMESA failed, quit\n"));
return false;
}
// A broken driver can return dmabuf without valid file descriptors
// which leads to fails later so quit now.
for (int i = 0; i < mBufferPlaneCount; i++) {
if (mDmabufFds[i] < 0) {
LOGDMABUF(
(" ExportDMABUFImageMESA failed, mDmabufFds[%d] is invalid, quit",
i));
return false;
}
}
LOGDMABUF((" imported size %d x %d format %x planes %d modifiers %" PRIx64,
mWidth, mHeight, mDrmFormats[0], mBufferPlaneCount,
mBufferModifiers[0]));
return true;
}
bool DMABufSurfaceRGBA::ImportSurfaceDescriptor(
const SurfaceDescriptor& aDesc) {
const SurfaceDescriptorDMABuf& desc = aDesc.get_SurfaceDescriptorDMABuf();
mWidth = desc.width()[0];
mHeight = desc.height()[0];
mBufferModifiers[0] = desc.modifier()[0];
mDrmFormats[0] = desc.format()[0];
mBufferPlaneCount = desc.fds().Length();
mGbmBufferFlags = desc.flags();
MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES);
mUID = desc.uid();
LOGDMABUF(
("DMABufSurfaceRGBA::ImportSurfaceDescriptor() UID %d size %d x %d\n",
mUID, mWidth, mHeight));
for (int i = 0; i < mBufferPlaneCount; i++) {
mDmabufFds[i] = desc.fds()[i].ClonePlatformHandle().release();
if (mDmabufFds[i] < 0) {
LOGDMABUF(
(" failed to get DMABuf file descriptor: %s", strerror(errno)));
return false;
}
mStrides[i] = desc.strides()[i];
mOffsets[i] = desc.offsets()[i];
}
if (desc.fence().Length() > 0) {
mSyncFd = desc.fence()[0].ClonePlatformHandle().release();
if (mSyncFd < 0) {
LOGDMABUF(
(" failed to get GL fence file descriptor: %s", strerror(errno)));
return false;
}
}
if (desc.refCount().Length() > 0) {
GlobalRefCountImport(desc.refCount()[0].ClonePlatformHandle().release());
}
LOGDMABUF((" imported size %d x %d format %x planes %d", mWidth, mHeight,
mDrmFormats[0], mBufferPlaneCount));
return true;
}
bool DMABufSurfaceRGBA::Create(const SurfaceDescriptor& aDesc) {
return ImportSurfaceDescriptor(aDesc);
}
bool DMABufSurfaceRGBA::Serialize(
mozilla::layers::SurfaceDescriptor& aOutDescriptor) {
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> width;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> height;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> format;
AutoTArray<ipc::FileDescriptor, DMABUF_BUFFER_PLANES> fds;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> strides;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> offsets;
AutoTArray<uintptr_t, DMABUF_BUFFER_PLANES> images;
AutoTArray<uint64_t, DMABUF_BUFFER_PLANES> modifiers;
AutoTArray<ipc::FileDescriptor, 1> fenceFDs;
AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
LOGDMABUF(("DMABufSurfaceRGBA::Serialize() UID %d\n", mUID));
MutexAutoLock lockFD(mSurfaceLock);
if (!OpenFileDescriptors(lockFD)) {
return false;
}
width.AppendElement(mWidth);
height.AppendElement(mHeight);
format.AppendElement(mDrmFormats[0]);
modifiers.AppendElement(mBufferModifiers[0]);
for (int i = 0; i < mBufferPlaneCount; i++) {
fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i]));
strides.AppendElement(mStrides[i]);
offsets.AppendElement(mOffsets[i]);
}
CloseFileDescriptors(lockFD);
if (mSync) {
fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd));
}
if (mGlobalRefCountFd) {
refCountFDs.AppendElement(ipc::FileDescriptor(GlobalRefCountExport()));
}
aOutDescriptor = SurfaceDescriptorDMABuf(
mSurfaceType, modifiers, mGbmBufferFlags, fds, width, height, width,
height, format, strides, offsets, GetYUVColorSpace(), mColorRange,
fenceFDs, mUID, refCountFDs);
return true;
}
bool DMABufSurfaceRGBA::CreateTexture(GLContext* aGLContext, int aPlane) {
LOGDMABUF(("DMABufSurfaceRGBA::CreateTexture() UID %d\n", mUID));
MOZ_ASSERT(!mEGLImage && !mTexture, "EGLImage is already created!");
nsTArray<EGLint> attribs;
attribs.AppendElement(LOCAL_EGL_WIDTH);
attribs.AppendElement(mWidth);
attribs.AppendElement(LOCAL_EGL_HEIGHT);
attribs.AppendElement(mHeight);
attribs.AppendElement(LOCAL_EGL_LINUX_DRM_FOURCC_EXT);
attribs.AppendElement(mDrmFormats[0]);
#define ADD_PLANE_ATTRIBS(plane_idx) \
{ \
attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_FD_EXT); \
attribs.AppendElement(mDmabufFds[plane_idx]); \
attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_OFFSET_EXT); \
attribs.AppendElement((int)mOffsets[plane_idx]); \
attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_PITCH_EXT); \
attribs.AppendElement((int)mStrides[plane_idx]); \
if (mBufferModifiers[0] != DRM_FORMAT_MOD_INVALID) { \
attribs.AppendElement( \
LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_LO_EXT); \
attribs.AppendElement(mBufferModifiers[0] & 0xFFFFFFFF); \
attribs.AppendElement( \
LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_HI_EXT); \
attribs.AppendElement(mBufferModifiers[0] >> 32); \
} \
}
MutexAutoLock lockFD(mSurfaceLock);
if (!OpenFileDescriptors(lockFD)) {
return false;
}
ADD_PLANE_ATTRIBS(0);
if (mBufferPlaneCount > 1) ADD_PLANE_ATTRIBS(1);
if (mBufferPlaneCount > 2) ADD_PLANE_ATTRIBS(2);
if (mBufferPlaneCount > 3) ADD_PLANE_ATTRIBS(3);
#undef ADD_PLANE_ATTRIBS
attribs.AppendElement(LOCAL_EGL_NONE);
if (!aGLContext) return false;
const auto& gle = gl::GLContextEGL::Cast(aGLContext);
const auto& egl = gle->mEgl;
mEGLImage =
egl->fCreateImage(LOCAL_EGL_NO_CONTEXT, LOCAL_EGL_LINUX_DMA_BUF_EXT,
nullptr, attribs.Elements());
CloseFileDescriptors(lockFD);
if (mEGLImage == LOCAL_EGL_NO_IMAGE) {
LOGDMABUF(("EGLImageKHR creation failed"));
return false;
}
if (!aGLContext->MakeCurrent()) {
LOGDMABUF(
("DMABufSurfaceRGBA::CreateTexture(): failed to make GL context "
"current"));
return false;
}
aGLContext->fGenTextures(1, &mTexture);
const ScopedBindTexture savedTex(aGLContext, mTexture);
aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
LOCAL_GL_CLAMP_TO_EDGE);
aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
LOCAL_GL_CLAMP_TO_EDGE);
aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
LOCAL_GL_LINEAR);
aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
LOCAL_GL_LINEAR);
aGLContext->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, mEGLImage);
mGL = aGLContext;
return true;
}
void DMABufSurfaceRGBA::ReleaseTextures() {
LOGDMABUF(("DMABufSurfaceRGBA::ReleaseTextures() UID %d\n", mUID));
FenceDelete();
if (!mTexture && !mEGLImage) {
return;
}
if (!mGL) {
#ifdef NIGHTLY_BUILD
MOZ_DIAGNOSTIC_ASSERT(mGL, "Missing GL context!");
#else
NS_WARNING(
"DMABufSurfaceRGBA::ReleaseTextures(): Missing GL context! We're "
"leaking textures!");
return;
#endif
}
const auto& gle = gl::GLContextEGL::Cast(mGL);
const auto& egl = gle->mEgl;
if (mTexture && mGL->MakeCurrent()) {
mGL->fDeleteTextures(1, &mTexture);
mTexture = 0;
}
if (mEGLImage != LOCAL_EGL_NO_IMAGE) {
egl->fDestroyImage(mEGLImage);
mEGLImage = LOCAL_EGL_NO_IMAGE;
}
mGL = nullptr;
}
void DMABufSurfaceRGBA::ReleaseSurface() {
MOZ_ASSERT(!IsMapped(), "We can't release mapped buffer!");
ReleaseTextures();
ReleaseDMABuf();
}
bool DMABufSurfaceRGBA::CreateWlBuffer() {
MutexAutoLock lockFD(mSurfaceLock);
if (!OpenFileDescriptors(lockFD)) {
return false;
}
RefPtr<nsWaylandDisplay> waylandDisplay = widget::WaylandDisplayGet();
if (!waylandDisplay->GetDmabuf()) {
CloseFileDescriptors(lockFD);
return false;
}
struct zwp_linux_buffer_params_v1* params =
zwp_linux_dmabuf_v1_create_params(waylandDisplay->GetDmabuf());
zwp_linux_buffer_params_v1_add(params, mDmabufFds[0], 0, mOffsets[0],
mStrides[0], mBufferModifiers[0] >> 32,
mBufferModifiers[0] & 0xffffffff);
mWlBuffer = zwp_linux_buffer_params_v1_create_immed(
params, GetWidth(), GetHeight(), mDrmFormats[0], 0);
CloseFileDescriptors(lockFD);
return mWlBuffer != nullptr;
}
void DMABufSurfaceRGBA::ReleaseWlBuffer() {
MozClearPointer(mWlBuffer, wl_buffer_destroy);
}
// We should synchronize DMA Buffer object access from CPU to avoid potential
// cache incoherency and data loss.
// See
// https://01.org/linuxgraphics/gfx-docs/drm/driver-api/dma-buf.html#cpu-access-to-dma-buffer-objects
struct dma_buf_sync {
uint64_t flags;
};
#define DMA_BUF_SYNC_READ (1 << 0)
#define DMA_BUF_SYNC_WRITE (2 << 0)
#define DMA_BUF_SYNC_START (0 << 2)
#define DMA_BUF_SYNC_END (1 << 2)
#define DMA_BUF_BASE 'b'
#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync)
static void SyncDmaBuf(int aFd, uint64_t aFlags) {
struct dma_buf_sync sync = {0};
sync.flags = aFlags | DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE;
while (true) {
int ret;
ret = ioctl(aFd, DMA_BUF_IOCTL_SYNC, &sync);
if (ret == -1 && errno == EINTR) {
continue;
} else if (ret == -1) {
LOGDMABUF(
("Failed to synchronize DMA buffer: %s FD %d", strerror(errno), aFd));
break;
} else {
break;
}
}
}
void* DMABufSurface::MapInternal(uint32_t aX, uint32_t aY, uint32_t aWidth,
uint32_t aHeight, uint32_t* aStride,
int aGbmFlags, int aPlane) {
NS_ASSERTION(!IsMapped(aPlane), "Already mapped!");
if (!mGbmBufferObject[aPlane]) {
NS_WARNING("We can't map DMABufSurfaceRGBA without mGbmBufferObject");
return nullptr;
}
LOGDMABUF(
("DMABufSurfaceRGBA::MapInternal() UID %d plane %d size %d x %d -> %d x "
"%d\n",
mUID, aPlane, aX, aY, aWidth, aHeight));
mMappedRegionStride[aPlane] = 0;
mMappedRegionData[aPlane] = nullptr;
mMappedRegion[aPlane] = nsGbmLib::Map(
mGbmBufferObject[aPlane], aX, aY, aWidth, aHeight, aGbmFlags,
&mMappedRegionStride[aPlane], &mMappedRegionData[aPlane]);
if (!mMappedRegion[aPlane]) {
LOGDMABUF((" Surface mapping failed: %s", strerror(errno)));
return nullptr;
}
if (aStride) {
*aStride = mMappedRegionStride[aPlane];
}
MutexAutoLock lockFD(mSurfaceLock);
if (OpenFileDescriptorForPlane(lockFD, aPlane)) {
SyncDmaBuf(mDmabufFds[aPlane], DMA_BUF_SYNC_START);
CloseFileDescriptorForPlane(lockFD, aPlane);
}
return mMappedRegion[aPlane];
}
void* DMABufSurfaceRGBA::MapReadOnly(uint32_t aX, uint32_t aY, uint32_t aWidth,
uint32_t aHeight, uint32_t* aStride) {
return MapInternal(aX, aY, aWidth, aHeight, aStride, GBM_BO_TRANSFER_READ);
}
void* DMABufSurfaceRGBA::MapReadOnly(uint32_t* aStride) {
return MapInternal(0, 0, mWidth, mHeight, aStride, GBM_BO_TRANSFER_READ);
}
void* DMABufSurfaceRGBA::Map(uint32_t aX, uint32_t aY, uint32_t aWidth,
uint32_t aHeight, uint32_t* aStride) {
return MapInternal(aX, aY, aWidth, aHeight, aStride,
GBM_BO_TRANSFER_READ_WRITE);
}
void* DMABufSurfaceRGBA::Map(uint32_t* aStride) {
return MapInternal(0, 0, mWidth, mHeight, aStride,
GBM_BO_TRANSFER_READ_WRITE);
}
void DMABufSurface::Unmap(int aPlane) {
if (mMappedRegion[aPlane]) {
LOGDMABUF(("DMABufSurface::Unmap() UID %d plane %d\n", mUID, aPlane));
MutexAutoLock lockFD(mSurfaceLock);
if (OpenFileDescriptorForPlane(lockFD, aPlane)) {
SyncDmaBuf(mDmabufFds[aPlane], DMA_BUF_SYNC_END);
CloseFileDescriptorForPlane(lockFD, aPlane);
}
nsGbmLib::Unmap(mGbmBufferObject[aPlane], mMappedRegionData[aPlane]);
mMappedRegion[aPlane] = nullptr;
mMappedRegionData[aPlane] = nullptr;
mMappedRegionStride[aPlane] = 0;
}
}
#ifdef DEBUG
void DMABufSurfaceRGBA::DumpToFile(const char* pFile) {
uint32_t stride;
if (!MapReadOnly(&stride)) {
return;
}
cairo_surface_t* surface = nullptr;
auto unmap = MakeScopeExit([&] {
if (surface) {
cairo_surface_destroy(surface);
}
Unmap();
});
surface = cairo_image_surface_create_for_data(
(unsigned char*)mMappedRegion[0], CAIRO_FORMAT_ARGB32, mWidth, mHeight,
stride);
if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
cairo_surface_write_to_png(surface, pFile);
}
}
#endif
#if 0
// Copy from source surface by GL
# include "GLBlitHelper.h"
bool DMABufSurfaceRGBA::CopyFrom(class DMABufSurface* aSourceSurface,
GLContext* aGLContext) {
MOZ_ASSERT(aSourceSurface->GetTexture());
MOZ_ASSERT(GetTexture());
gfx::IntSize size(GetWidth(), GetHeight());
aGLContext->BlitHelper()->BlitTextureToTexture(aSourceSurface->GetTexture(),
GetTexture(), size, size);
return true;
}
#endif
// TODO - Clear the surface by EGL
void DMABufSurfaceRGBA::Clear() {
uint32_t destStride;
void* destData = Map(&destStride);
memset(destData, 0, GetHeight() * destStride);
Unmap();
}
bool DMABufSurfaceRGBA::HasAlpha() {
return !mGmbFormat || mGmbFormat->mHasAlpha;
}
gfx::SurfaceFormat DMABufSurfaceRGBA::GetFormat() {
return HasAlpha() ? gfx::SurfaceFormat::B8G8R8A8
: gfx::SurfaceFormat::B8G8R8X8;
}
// GL uses swapped R and B components so report accordingly.
gfx::SurfaceFormat DMABufSurfaceRGBA::GetFormatGL() {
return HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8
: gfx::SurfaceFormat::R8G8B8X8;
}
already_AddRefed<DMABufSurfaceRGBA> DMABufSurfaceRGBA::CreateDMABufSurface(
int aWidth, int aHeight, int aDMABufSurfaceFlags) {
RefPtr<DMABufSurfaceRGBA> surf = new DMABufSurfaceRGBA();
if (!surf->Create(aWidth, aHeight, aDMABufSurfaceFlags)) {
return nullptr;
}
return surf.forget();
}
already_AddRefed<DMABufSurface> DMABufSurfaceRGBA::CreateDMABufSurface(
mozilla::gl::GLContext* aGLContext, const EGLImageKHR aEGLImage, int aWidth,
int aHeight) {
RefPtr<DMABufSurfaceRGBA> surf = new DMABufSurfaceRGBA();
if (!surf->Create(aGLContext, aEGLImage, aWidth, aHeight)) {
return nullptr;
}
return surf.forget();
}
already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface(
const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight) {
RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
LOGDMABUF(("DMABufSurfaceYUV::CreateYUVSurface() UID %d from desc\n",
surf->GetUID()));
if (!surf->UpdateYUVData(aDesc, aWidth, aHeight, /* aCopy */ false)) {
return nullptr;
}
return surf.forget();
}
already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CopyYUVSurface(
const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight) {
RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
LOGDMABUF(("DMABufSurfaceYUV::CreateYUVSurfaceCopy() UID %d from desc\n",
surf->GetUID()));
if (!surf->UpdateYUVData(aDesc, aWidth, aHeight, /* aCopy */ true)) {
return nullptr;
}
return surf.forget();
}
already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface(
int aWidth, int aHeight, void** aPixelData, int* aLineSizes) {
RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
LOGDMABUF(("DMABufSurfaceYUV::CreateYUVSurface() UID %d %d x %d\n",
surf->GetUID(), aWidth, aHeight));
if (!surf->Create(aWidth, aHeight, aPixelData, aLineSizes)) {
return nullptr;
}
return surf.forget();
}
DMABufSurfaceYUV::DMABufSurfaceYUV()
: DMABufSurface(SURFACE_NV12),
mWidth(),
mHeight(),
mWidthAligned(),
mHeightAligned(),
mTexture() {
for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
mEGLImage[i] = LOCAL_EGL_NO_IMAGE;
}
}
DMABufSurfaceYUV::~DMABufSurfaceYUV() { ReleaseSurface(); }
bool DMABufSurfaceYUV::OpenFileDescriptorForPlane(
const MutexAutoLock& aProofOfLock, int aPlane) {
// The fd is already opened, no need to reopen.
// This can happen when we import dmabuf surface from VA-API decoder,
// mGbmBufferObject is null and we don't close
// file descriptors for surface as they are our only reference to it.
if (mDmabufFds[aPlane] >= 0) {
return true;
}
if (mGbmBufferObject[aPlane] == nullptr) {
LOGDMABUF(
("DMABufSurfaceYUV::OpenFileDescriptorForPlane: Missing "
"mGbmBufferObject object!"));
return false;
}
mDmabufFds[aPlane] = nsGbmLib::GetFd(mGbmBufferObject[aPlane]);
if (mDmabufFds[aPlane] < 0) {
CloseFileDescriptors(aProofOfLock);
return false;
}
return true;
}
void DMABufSurfaceYUV::CloseFileDescriptorForPlane(
const MutexAutoLock& aProofOfLock, int aPlane, bool aForceClose = false) {
if ((aForceClose || mGbmBufferObject[aPlane]) && mDmabufFds[aPlane] >= 0) {
close(mDmabufFds[aPlane]);
mDmabufFds[aPlane] = -1;
}
}
bool DMABufSurfaceYUV::ImportPRIMESurfaceDescriptor(
const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight) {
LOGDMABUF(("DMABufSurfaceYUV::ImportPRIMESurfaceDescriptor() UID %d", mUID));
// Already exists?
MOZ_DIAGNOSTIC_ASSERT(mDmabufFds[0] < 0);
if (aDesc.num_layers > DMABUF_BUFFER_PLANES ||
aDesc.num_objects > DMABUF_BUFFER_PLANES) {
LOGDMABUF((" Can't import, wrong layers/objects number (%d, %d)",
aDesc.num_layers, aDesc.num_objects));
return false;
}
if (aDesc.fourcc == VA_FOURCC_NV12) {
mSurfaceType = SURFACE_NV12;
} else if (aDesc.fourcc == VA_FOURCC_P010) {
mSurfaceType = SURFACE_NV12;
} else if (aDesc.fourcc == VA_FOURCC_YV12) {
mSurfaceType = SURFACE_YUV420;
} else {
LOGDMABUF((" Can't import surface data of 0x%x format", aDesc.fourcc));
return false;
}
mBufferPlaneCount = aDesc.num_layers;
for (unsigned int i = 0; i < aDesc.num_layers; i++) {
unsigned int object = aDesc.layers[i].object_index[0];
mBufferModifiers[i] = aDesc.objects[object].drm_format_modifier;
mDrmFormats[i] = aDesc.layers[i].drm_format;
mOffsets[i] = aDesc.layers[i].offset[0];
mStrides[i] = aDesc.layers[i].pitch[0];
mWidthAligned[i] = aDesc.width >> i;
mHeightAligned[i] = aDesc.height >> i;
mWidth[i] = aWidth >> i;
mHeight[i] = aHeight >> i;
LOGDMABUF((" plane %d size %d x %d format %x", i, mWidth[i], mHeight[i],
mDrmFormats[i]));
}
return true;
}
bool DMABufSurfaceYUV::MoveYUVDataImpl(const VADRMPRIMESurfaceDescriptor& aDesc,
int aWidth, int aHeight) {
if (!ImportPRIMESurfaceDescriptor(aDesc, aWidth, aHeight)) {
return false;
}
for (unsigned int i = 0; i < aDesc.num_layers; i++) {
unsigned int object = aDesc.layers[i].object_index[0];
// Keep VADRMPRIMESurfaceDescriptor untouched and dup() dmabuf
// file descriptors.
mDmabufFds[i] = dup(aDesc.objects[object].fd);
}
return true;
}
void DMABufSurfaceYUV::ReleaseVADRMPRIMESurfaceDescriptor(
VADRMPRIMESurfaceDescriptor& aDesc) {
for (unsigned int i = 0; i < aDesc.num_layers; i++) {
unsigned int object = aDesc.layers[i].object_index[0];
if (aDesc.objects[object].fd != -1) {
close(aDesc.objects[object].fd);
aDesc.objects[object].fd = -1;
}
}
}
bool DMABufSurfaceYUV::CreateYUVPlane(int aPlane) {
LOGDMABUF(("DMABufSurfaceYUV::CreateYUVPlane() UID %d size %d x %d", mUID,
mWidth[aPlane], mHeight[aPlane]));
if (!GetAndConfigureDMABufDevice()->GetGbmDevice()) {
LOGDMABUF((" Missing GbmDevice!"));
return false;
}
MOZ_DIAGNOSTIC_ASSERT(mGbmBufferObject[aPlane] == nullptr);
bool useModifiers = (mBufferModifiers[aPlane] != DRM_FORMAT_MOD_INVALID);
if (useModifiers) {
LOGDMABUF((" Creating with modifiers"));
mGbmBufferObject[aPlane] = nsGbmLib::CreateWithModifiers(
GetDMABufDevice()->GetGbmDevice(), mWidth[aPlane], mHeight[aPlane],
mDrmFormats[aPlane], mBufferModifiers + aPlane, 1);
}
if (!mGbmBufferObject[aPlane]) {
LOGDMABUF((" Creating without modifiers"));
mGbmBufferObject[aPlane] = nsGbmLib::Create(
GetDMABufDevice()->GetGbmDevice(), mWidth[aPlane], mHeight[aPlane],
mDrmFormats[aPlane], GBM_BO_USE_RENDERING);
mBufferModifiers[aPlane] = DRM_FORMAT_MOD_INVALID;
}
if (!mGbmBufferObject[aPlane]) {
LOGDMABUF((" Failed to create GbmBufferObject: %s", strerror(errno)));
return false;
}
mStrides[aPlane] = nsGbmLib::GetStride(mGbmBufferObject[aPlane]);
mOffsets[aPlane] = nsGbmLib::GetOffset(mGbmBufferObject[aPlane], 0);
mWidthAligned[aPlane] = mWidth[aPlane];
mHeightAligned[aPlane] = mHeight[aPlane];
return true;
}
bool DMABufSurfaceYUV::CopyYUVDataImpl(const VADRMPRIMESurfaceDescriptor& aDesc,
int aWidth, int aHeight) {
RefPtr<DMABufSurfaceYUV> tmpSurf = CreateYUVSurface(aDesc, aWidth, aHeight);
if (!tmpSurf) {
return false;
}
if (!ImportPRIMESurfaceDescriptor(aDesc, aWidth, aHeight)) {
return false;
}
StaticMutexAutoLock lock(sSnapshotContextMutex);
RefPtr<GLContext> context = ClaimSnapshotGLContext();
auto releaseTextures = MakeScopeExit([&] {
tmpSurf->ReleaseTextures();
ReleaseTextures();
ReturnSnapshotGLContext(context);
});
for (int i = 0; i < mBufferPlaneCount; i++) {
if (!tmpSurf->CreateTexture(context, i)) {
return false;
}
if (!CreateYUVPlane(i) || !CreateTexture(context, i)) {
return false;
}
gfx::IntSize size(GetWidth(i), GetHeight(i));
context->BlitHelper()->BlitTextureToTexture(
tmpSurf->GetTexture(i), GetTexture(i), size, size, LOCAL_GL_TEXTURE_2D,
LOCAL_GL_TEXTURE_2D);
}
return true;
}
bool DMABufSurfaceYUV::UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc,
int aWidth, int aHeight, bool aCopy) {
LOGDMABUF(("DMABufSurfaceYUV::UpdateYUVData() UID %d copy %d", mUID, aCopy));
return aCopy ? CopyYUVDataImpl(aDesc, aWidth, aHeight)
: MoveYUVDataImpl(aDesc, aWidth, aHeight);
}
bool DMABufSurfaceYUV::CreateLinearYUVPlane(int aPlane, int aWidth, int aHeight,
int aDrmFormat) {
LOGDMABUF(("DMABufSurfaceYUV::CreateLinearYUVPlane() UID %d size %d x %d",
mUID, aWidth, aHeight));
if (!GetDMABufDevice()->GetGbmDevice()) {
LOGDMABUF((" Missing GbmDevice!"));
return false;
}
mWidth[aPlane] = aWidth;
mHeight[aPlane] = aHeight;
mDrmFormats[aPlane] = aDrmFormat;
mGbmBufferObject[aPlane] =
nsGbmLib::Create(GetDMABufDevice()->GetGbmDevice(), aWidth, aHeight,
aDrmFormat, GBM_BO_USE_LINEAR);
if (!mGbmBufferObject[aPlane]) {
LOGDMABUF((" Failed to create GbmBufferObject: %s", strerror(errno)));
return false;
}
mStrides[aPlane] = nsGbmLib::GetStride(mGbmBufferObject[aPlane]);
mDmabufFds[aPlane] = -1;
return true;
}
void DMABufSurfaceYUV::UpdateYUVPlane(int aPlane, void* aPixelData,
int aLineSize) {
LOGDMABUF(
("DMABufSurfaceYUV::UpdateYUVPlane() UID %d plane %d", mUID, aPlane));
if (aLineSize == mWidth[aPlane] &&
(int)mMappedRegionStride[aPlane] == mWidth[aPlane]) {
memcpy(mMappedRegion[aPlane], aPixelData, aLineSize * mHeight[aPlane]);
} else {
char* src = (char*)aPixelData;
char* dest = (char*)mMappedRegion[aPlane];
for (int i = 0; i < mHeight[aPlane]; i++) {
memcpy(dest, src, mWidth[aPlane]);
src += aLineSize;
dest += mMappedRegionStride[aPlane];
}
}
}
bool DMABufSurfaceYUV::UpdateYUVData(void** aPixelData, int* aLineSizes) {
LOGDMABUF(("DMABufSurfaceYUV::UpdateYUVData() UID %d", mUID));
if (mSurfaceType != SURFACE_YUV420) {
LOGDMABUF((" UpdateYUVData can upload YUV420 surface type only!"));
return false;
}
if (mBufferPlaneCount != 3) {
LOGDMABUF((" DMABufSurfaceYUV planes does not match!"));
return false;
}
auto unmapBuffers = MakeScopeExit([&] {
Unmap(0);
Unmap(1);
Unmap(2);
});
// Map planes
for (int i = 0; i < mBufferPlaneCount; i++) {
MapInternal(0, 0, mWidth[i], mHeight[i], nullptr, GBM_BO_TRANSFER_WRITE, i);
if (!mMappedRegion[i]) {
LOGDMABUF((" DMABufSurfaceYUV plane can't be mapped!"));
return false;
}
if ((int)mMappedRegionStride[i] < mWidth[i]) {
LOGDMABUF((" DMABufSurfaceYUV plane size stride does not match!"));
return false;
}
}
// Copy planes
for (int i = 0; i < mBufferPlaneCount; i++) {
UpdateYUVPlane(i, aPixelData[i], aLineSizes[i]);
}
return true;
}
bool DMABufSurfaceYUV::Create(int aWidth, int aHeight, void** aPixelData,
int* aLineSizes) {
LOGDMABUF(("DMABufSurfaceYUV::Create() UID %d size %d x %d", mUID, aWidth,
aHeight));
mSurfaceType = SURFACE_YUV420;
mBufferPlaneCount = 3;
if (!CreateLinearYUVPlane(0, aWidth, aHeight, GBM_FORMAT_R8)) {
return false;
}
if (!CreateLinearYUVPlane(1, aWidth >> 1, aHeight >> 1, GBM_FORMAT_R8)) {
return false;
}
if (!CreateLinearYUVPlane(2, aWidth >> 1, aHeight >> 1, GBM_FORMAT_R8)) {
return false;
}
if (!aPixelData || !aLineSizes) {
return true;
}
return UpdateYUVData(aPixelData, aLineSizes);
}
bool DMABufSurfaceYUV::Create(const SurfaceDescriptor& aDesc) {
return ImportSurfaceDescriptor(aDesc);
}
bool DMABufSurfaceYUV::ImportSurfaceDescriptor(
const SurfaceDescriptorDMABuf& aDesc) {
mBufferPlaneCount = aDesc.fds().Length();
mSurfaceType = (mBufferPlaneCount == 2) ? SURFACE_NV12 : SURFACE_YUV420;
mColorSpace = aDesc.yUVColorSpace();
mColorRange = aDesc.colorRange();
mUID = aDesc.uid();
LOGDMABUF(("DMABufSurfaceYUV::ImportSurfaceDescriptor() UID %d", mUID));
MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES);
for (int i = 0; i < mBufferPlaneCount; i++) {
mDmabufFds[i] = aDesc.fds()[i].ClonePlatformHandle().release();
if (mDmabufFds[i] < 0) {
LOGDMABUF((" failed to get DMABuf plane file descriptor: %s",
strerror(errno)));
return false;
}
mWidth[i] = aDesc.width()[i];
mHeight[i] = aDesc.height()[i];
mWidthAligned[i] = aDesc.widthAligned()[i];
mHeightAligned[i] = aDesc.heightAligned()[i];
mDrmFormats[i] = aDesc.format()[i];
mStrides[i] = aDesc.strides()[i];
mOffsets[i] = aDesc.offsets()[i];
mBufferModifiers[i] = aDesc.modifier()[i];
LOGDMABUF((" plane %d fd %d size %d x %d format %x", i, mDmabufFds[i],
mWidth[i], mHeight[i], mDrmFormats[i]));
}
if (aDesc.fence().Length() > 0) {
mSyncFd = aDesc.fence()[0].ClonePlatformHandle().release();
if (mSyncFd < 0) {
LOGDMABUF(
(" failed to get GL fence file descriptor: %s", strerror(errno)));
return false;
}
}
if (aDesc.refCount().Length() > 0) {
GlobalRefCountImport(aDesc.refCount()[0].ClonePlatformHandle().release());
}
return true;
}
bool DMABufSurfaceYUV::Serialize(
mozilla::layers::SurfaceDescriptor& aOutDescriptor) {
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> width;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> height;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> widthBytes;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> heightBytes;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> format;
AutoTArray<ipc::FileDescriptor, DMABUF_BUFFER_PLANES> fds;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> strides;
AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> offsets;
AutoTArray<uint64_t, DMABUF_BUFFER_PLANES> modifiers;
AutoTArray<ipc::FileDescriptor, 1> fenceFDs;
AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
LOGDMABUF(("DMABufSurfaceYUV::Serialize() UID %d", mUID));
MutexAutoLock lockFD(mSurfaceLock);
if (!OpenFileDescriptors(lockFD)) {
return false;
}
for (int i = 0; i < mBufferPlaneCount; i++) {
width.AppendElement(mWidth[i]);
height.AppendElement(mHeight[i]);
widthBytes.AppendElement(mWidthAligned[i]);
heightBytes.AppendElement(mHeightAligned[i]);
format.AppendElement(mDrmFormats[i]);
fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i]));
strides.AppendElement(mStrides[i]);
offsets.AppendElement(mOffsets[i]);
modifiers.AppendElement(mBufferModifiers[i]);
}
CloseFileDescriptors(lockFD);
if (mSync) {
fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd));
}
if (mGlobalRefCountFd) {
refCountFDs.AppendElement(ipc::FileDescriptor(GlobalRefCountExport()));
}
aOutDescriptor = SurfaceDescriptorDMABuf(
mSurfaceType, modifiers, 0, fds, width, height, widthBytes, heightBytes,
format, strides, offsets, GetYUVColorSpace(), mColorRange, fenceFDs, mUID,
refCountFDs);
return true;
}
bool DMABufSurfaceYUV::CreateEGLImage(GLContext* aGLContext, int aPlane) {
LOGDMABUF(
("DMABufSurfaceYUV::CreateEGLImage() UID %d plane %d", mUID, aPlane));
MOZ_ASSERT(mEGLImage[aPlane] == LOCAL_EGL_NO_IMAGE,
"EGLImage is already created!");
MOZ_ASSERT(aGLContext, "Missing GLContext!");
const auto& gle = gl::GLContextEGL::Cast(aGLContext);
const auto& egl = gle->mEgl;
MutexAutoLock lockFD(mSurfaceLock);
if (!OpenFileDescriptorForPlane(lockFD, aPlane)) {
LOGDMABUF((" failed to open dmabuf file descriptors"));
return false;
}
nsTArray<EGLint> attribs;
attribs.AppendElement(LOCAL_EGL_WIDTH);
attribs.AppendElement(mWidthAligned[aPlane]);
attribs.AppendElement(LOCAL_EGL_HEIGHT);
attribs.AppendElement(mHeightAligned[aPlane]);
attribs.AppendElement(LOCAL_EGL_LINUX_DRM_FOURCC_EXT);
attribs.AppendElement(mDrmFormats[aPlane]);
#define ADD_PLANE_ATTRIBS_NV12(plane_idx) \
attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_FD_EXT); \
attribs.AppendElement(mDmabufFds[aPlane]); \
attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_OFFSET_EXT); \
attribs.AppendElement((int)mOffsets[aPlane]); \
attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_PITCH_EXT); \
attribs.AppendElement((int)mStrides[aPlane]); \
if (mBufferModifiers[aPlane] != DRM_FORMAT_MOD_INVALID) { \
attribs.AppendElement( \
LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_LO_EXT); \
attribs.AppendElement(mBufferModifiers[aPlane] & 0xFFFFFFFF); \
attribs.AppendElement( \
LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_HI_EXT); \
attribs.AppendElement(mBufferModifiers[aPlane] >> 32); \
}
ADD_PLANE_ATTRIBS_NV12(0);
#undef ADD_PLANE_ATTRIBS_NV12
attribs.AppendElement(LOCAL_EGL_NONE);
mEGLImage[aPlane] =
egl->fCreateImage(LOCAL_EGL_NO_CONTEXT, LOCAL_EGL_LINUX_DMA_BUF_EXT,
nullptr, attribs.Elements());
CloseFileDescriptorForPlane(lockFD, aPlane);
if (mEGLImage[aPlane] == LOCAL_EGL_NO_IMAGE) {
LOGDMABUF((" EGLImageKHR creation failed"));
return false;
}
LOGDMABUF((" Success."));
return true;
}
void DMABufSurfaceYUV::ReleaseEGLImages(GLContext* aGLContext) {
LOGDMABUF(("DMABufSurfaceYUV::ReleaseEGLImages() UID %d", mUID));
MOZ_ASSERT(aGLContext, "Missing GLContext!");
const auto& gle = gl::GLContextEGL::Cast(aGLContext);
const auto& egl = gle->mEgl;
for (int i = 0; i < mBufferPlaneCount; i++) {
if (mEGLImage[i] != LOCAL_EGL_NO_IMAGE) {
egl->fDestroyImage(mEGLImage[i]);
mEGLImage[i] = LOCAL_EGL_NO_IMAGE;
}
}
}
bool DMABufSurfaceYUV::CreateTexture(GLContext* aGLContext, int aPlane) {
LOGDMABUF(
("DMABufSurfaceYUV::CreateTexture() UID %d plane %d", mUID, aPlane));
MOZ_ASSERT(!mTexture[aPlane], "Texture is already created!");
MOZ_ASSERT(aGLContext, "Missing GLContext!");
if (!CreateEGLImage(aGLContext, aPlane)) {
return false;
}
if (!aGLContext->MakeCurrent()) {
LOGDMABUF((" Failed to make GL context current."));
return false;
}
aGLContext->fGenTextures(1, &mTexture[aPlane]);
const ScopedBindTexture savedTex(aGLContext, mTexture[aPlane]);
aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
LOCAL_GL_CLAMP_TO_EDGE);
aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
LOCAL_GL_CLAMP_TO_EDGE);
aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
LOCAL_GL_LINEAR);
aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
LOCAL_GL_LINEAR);
aGLContext->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, mEGLImage[aPlane]);
mGL = aGLContext;
return true;
}
void DMABufSurfaceYUV::ReleaseTextures() {
LOGDMABUF(("DMABufSurfaceYUV::ReleaseTextures() UID %d", mUID));
FenceDelete();
bool textureActive = false;
for (int i = 0; i < mBufferPlaneCount; i++) {
if (mTexture[i] || mEGLImage[i]) {
textureActive = true;
break;
}
}
if (!textureActive) {
return;
}
if (!mGL) {
#ifdef NIGHTLY_BUILD
MOZ_DIAGNOSTIC_ASSERT(mGL, "Missing GL context!");
#else
NS_WARNING(
"DMABufSurfaceYUV::ReleaseTextures(): Missing GL context! We're "
"leaking textures!");
return;
#endif
}
if (!mGL->MakeCurrent()) {
NS_WARNING(
"DMABufSurfaceYUV::ReleaseTextures(): MakeCurrent failed. We're "
"leaking textures!");
return;
}
mGL->fDeleteTextures(DMABUF_BUFFER_PLANES, mTexture);
for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
mTexture[i] = 0;
}
ReleaseEGLImages(mGL);
mGL = nullptr;
}
bool DMABufSurfaceYUV::VerifyTextureCreation() {
LOGDMABUF(("DMABufSurfaceYUV::VerifyTextureCreation() UID %d", mUID));
StaticMutexAutoLock lock(sSnapshotContextMutex);
RefPtr<GLContext> context = ClaimSnapshotGLContext();
auto release = MakeScopeExit([&] {
ReleaseEGLImages(context);
ReturnSnapshotGLContext(context);
});
for (int i = 0; i < mBufferPlaneCount; i++) {
if (!CreateEGLImage(context, i)) {
LOGDMABUF((" failed to create EGL image!"));
return false;
}
}
LOGDMABUF((" success"));
return true;
}
gfx::SurfaceFormat DMABufSurfaceYUV::GetFormat() {
switch (mSurfaceType) {
case SURFACE_NV12:
return gfx::SurfaceFormat::NV12;
case SURFACE_YUV420:
return gfx::SurfaceFormat::YUV;
default:
NS_WARNING("DMABufSurfaceYUV::GetFormat(): Wrong surface format!");
return gfx::SurfaceFormat::UNKNOWN;
}
}
// GL uses swapped R and B components so report accordingly.
gfx::SurfaceFormat DMABufSurfaceYUV::GetFormatGL() { return GetFormat(); }
int DMABufSurfaceYUV::GetTextureCount() {
switch (mSurfaceType) {
case SURFACE_NV12:
return 2;
case SURFACE_YUV420:
return 3;
default:
NS_WARNING("DMABufSurfaceYUV::GetTextureCount(): Wrong surface format!");
return 1;
}
}
void DMABufSurfaceYUV::ReleaseSurface() {
LOGDMABUF(("DMABufSurfaceYUV::ReleaseSurface() UID %d", mUID));
ReleaseTextures();
ReleaseDMABuf();
}
already_AddRefed<gfx::DataSourceSurface>
DMABufSurfaceYUV::GetAsSourceSurface() {
LOGDMABUF(("DMABufSurfaceYUV::GetAsSourceSurface UID %d", mUID));
StaticMutexAutoLock lock(sSnapshotContextMutex);
RefPtr<GLContext> context = ClaimSnapshotGLContext();
auto releaseTextures = mozilla::MakeScopeExit([&] {
ReleaseTextures();
ReturnSnapshotGLContext(context);
});
for (int i = 0; i < GetTextureCount(); i++) {
if (!GetTexture(i) && !CreateTexture(context, i)) {
LOGDMABUF(("GetAsSourceSurface: Failed to create DMABuf textures."));
return nullptr;
}
}
ScopedTexture scopedTex(context);
ScopedBindTexture boundTex(context, scopedTex.Texture());
gfx::IntSize size(GetWidth(), GetHeight());
context->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, size.width,
size.height, 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
nullptr);
ScopedFramebufferForTexture autoFBForTex(context, scopedTex.Texture());
if (!autoFBForTex.IsComplete()) {
LOGDMABUF(("GetAsSourceSurface: ScopedFramebufferForTexture failed."));
return nullptr;
}
const gl::OriginPos destOrigin = gl::OriginPos::BottomLeft;
{
const ScopedBindFramebuffer bindFB(context, autoFBForTex.FB());
if (!context->BlitHelper()->Blit(this, size, destOrigin)) {
LOGDMABUF(("GetAsSourceSurface: Blit failed."));
return nullptr;
}
}
RefPtr<gfx::DataSourceSurface> source =
gfx::Factory::CreateDataSourceSurface(size, gfx::SurfaceFormat::B8G8R8A8);
if (NS_WARN_IF(!source)) {
LOGDMABUF(("GetAsSourceSurface: CreateDataSourceSurface failed."));
return nullptr;
}
ScopedBindFramebuffer bind(context, autoFBForTex.FB());
ReadPixelsIntoDataSurface(context, source);
return source.forget();
}