diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /widget/gtk/DMABufSurface.cpp | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/gtk/DMABufSurface.cpp')
-rw-r--r-- | widget/gtk/DMABufSurface.cpp | 1503 |
1 files changed, 1503 insertions, 0 deletions
diff --git a/widget/gtk/DMABufSurface.cpp b/widget/gtk/DMABufSurface.cpp new file mode 100644 index 0000000000..0577e044a4 --- /dev/null +++ b/widget/gtk/DMABufSurface.cpp @@ -0,0 +1,1503 @@ +/* -*- 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; + } + 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)) { + 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(); +} |