forked from mirrors/gecko-dev
In some non-standard configurations we unexpectedly end up in this paths without a GBM device - one example being the GPU process. Fail cleanly instead of crashing in those cases, triggering fallback paths. Context: in the past DMABuf usage was tightly coupled to GBM. Since the introduction of the surfaceless and device EGL platforms that is not longer the case, thus we can't make checks like `IsDMABufWebGLEnabled()` depend on the presence of a GBM device. Optimally all affected cases get fixed eventually. Until then and also for future cases it makes sense to fail softly. Differential Revision: https://phabricator.services.mozilla.com/D152173
1485 lines
46 KiB
C++
1485 lines
46 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(("GetAsSourceSurface: Failed to create snapshot GLContext."));
|
|
return nullptr;
|
|
}
|
|
sSnapshotContext->mOwningThreadId = Nothing(); // No singular owner.
|
|
}
|
|
if (!sSnapshotContext->MakeCurrent()) {
|
|
LOGDMABUF(("GetAsSourceSurface: 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()) {
|
|
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() {
|
|
MOZ_ASSERT(mGlobalRefCountFd);
|
|
uint64_t counter;
|
|
if (read(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
|
|
// 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() {
|
|
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);
|
|
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;
|
|
MOZ_DIAGNOSTIC_ASSERT(IsGlobalRefSet(),
|
|
"We're importing unreferenced surface!");
|
|
}
|
|
|
|
int DMABufSurface::GlobalRefCountExport() {
|
|
if (mGlobalRefCountFd) {
|
|
MOZ_DIAGNOSTIC_ASSERT(IsGlobalRefSet(),
|
|
"We're exporting unreferenced surface!");
|
|
GlobalRefAdd();
|
|
}
|
|
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;
|
|
}
|
|
if (mBufferPlaneCount == 1) {
|
|
MOZ_ASSERT(aPlane == 0, "DMABuf: wrong surface plane!");
|
|
mDmabufFds[0] = nsGbmLib::GetFd(mGbmBufferObject[0]);
|
|
} else {
|
|
uint32_t handle =
|
|
nsGbmLib::GetHandleForPlane(mGbmBufferObject[0], 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;
|
|
}
|
|
|
|
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)) {
|
|
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::UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc,
|
|
int aWidth, int aHeight) {
|
|
if (aDesc.num_layers > DMABUF_BUFFER_PLANES ||
|
|
aDesc.num_objects > DMABUF_BUFFER_PLANES) {
|
|
return false;
|
|
}
|
|
|
|
LOGDMABUF(("DMABufSurfaceYUV::UpdateYUVData() UID %d", mUID));
|
|
if (mDmabufFds[0] >= 0) {
|
|
LOGDMABUF((" Already created!"));
|
|
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(("UpdateYUVData(): 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];
|
|
// Intel exports VA-API surfaces in one object,planes have the same FD.
|
|
// AMD exports surfaces in two objects with different FDs.
|
|
int fd = aDesc.objects[object].fd;
|
|
bool dupFD = (object != i);
|
|
mDmabufFds[i] = dupFD ? dup(fd) : fd;
|
|
|
|
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::CreateYUVPlane(int aPlane, int aWidth, int aHeight,
|
|
int aDrmFormat) {
|
|
LOGDMABUF(("DMABufSurfaceYUV::CreateYUVPlane() 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 (!CreateYUVPlane(0, aWidth, aHeight, GBM_FORMAT_R8)) {
|
|
return false;
|
|
}
|
|
if (!CreateYUVPlane(1, aWidth >> 1, aHeight >> 1, GBM_FORMAT_R8)) {
|
|
return false;
|
|
}
|
|
if (!CreateYUVPlane(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();
|
|
}
|