diff options
Diffstat (limited to 'widget/gtk/DMABufSurface.cpp')
-rw-r--r-- | widget/gtk/DMABufSurface.cpp | 1002 |
1 files changed, 1002 insertions, 0 deletions
diff --git a/widget/gtk/DMABufSurface.cpp b/widget/gtk/DMABufSurface.cpp new file mode 100644 index 0000000000..1e1719780f --- /dev/null +++ b/widget/gtk/DMABufSurface.cpp @@ -0,0 +1,1002 @@ +/* -*- 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 "mozilla/widget/gbm.h" +#include "mozilla/widget/va_drmcommon.h" +#include "GLContextTypes.h" // for GLContext, etc +#include "GLContextEGL.h" +#include "GLContextProvider.h" +#include "ScopedGLHelpers.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; + +#ifndef DRM_FORMAT_MOD_INVALID +# define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1) +#endif +#define BUFFER_FLAGS 0 + +#ifndef GBM_BO_USE_TEXTURING +# define GBM_BO_USE_TEXTURING (1 << 5) +#endif + +#ifndef VA_FOURCC_NV12 +# define VA_FOURCC_NV12 0x3231564E +#endif + +#ifndef VA_FOURCC_YV12 +# define VA_FOURCC_YV12 0x32315659 +#endif + +static Atomic<int> gNewSurfaceUID(1); + +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("Failed to unref dmabuf global ref count!"); + } + } +} + +void DMABufSurface::GlobalRefAdd() { + MOZ_ASSERT(mGlobalRefCountFd); + uint64_t counter = 1; + if (write(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) { + NS_WARNING("Failed to ref dmabuf global ref count!"); + } +} + +void DMABufSurface::GlobalRefCountCreate() { + MOZ_ASSERT(!mGlobalRefCountFd); + mGlobalRefCountFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE); + if (mGlobalRefCountFd < 0) { + NS_WARNING("Failed to create dmabuf global ref count!"); + mGlobalRefCountFd = 0; + return; + } +} + +void DMABufSurface::GlobalRefCountImport(int aFd) { + MOZ_ASSERT(!mGlobalRefCountFd); + mGlobalRefCountFd = aFd; + GlobalRefAdd(); +} + +void DMABufSurface::GlobalRefCountDelete() { + if (mGlobalRefCountFd) { + GlobalRefRelease(); + close(mGlobalRefCountFd); + mGlobalRefCountFd = 0; + } +} + +void DMABufSurface::ReleaseDMABuf() { + for (int i = 0; i < mBufferPlaneCount; i++) { + Unmap(i); + + if (mDmabufFds[i] >= 0) { + close(mDmabufFds[i]); + mDmabufFds[i] = -1; + } + } + + if (mGbmBufferObject[0]) { + nsGbmLib::Destroy(mGbmBufferObject[0]); + mGbmBufferObject[0] = nullptr; + } +} + +DMABufSurface::DMABufSurface(SurfaceType aSurfaceType) + : mSurfaceType(aSurfaceType), + mBufferModifier(DRM_FORMAT_MOD_INVALID), + mBufferPlaneCount(0), + mDrmFormats(), + mStrides(), + mOffsets(), + mGbmBufferObject(), + mMappedRegion(), + mMappedRegionStride(), + mSyncFd(-1), + mSync(0), + mGlobalRefCountFd(0), + mUID(gNewSurfaceUID++) { + for (auto& slot : mDmabufFds) { + slot = -1; + } +} + +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 (!mGL) return; + const auto& gle = gl::GLContextEGL::Cast(mGL); + const auto& egl = gle->mEgl; + + if (mSyncFd > 0) { + close(mSyncFd); + mSyncFd = -1; + } + + if (mSync) { + egl->fDestroySync(mSync); + mSync = nullptr; + } +} + +void DMABufSurface::FenceSet() { + if (!mGL || !mGL->MakeCurrent()) { + 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) return; + const auto& gle = gl::GLContextEGL::Cast(mGL); + const auto& egl = gle->mEgl; + + if (!mSync && mSyncFd > 0) { + FenceImportFromFd(); + } + + // Wait on the fence, because presumably we're going to want to read this + // surface + if (mSync) { + egl->fClientWaitSync(mSync, 0, LOCAL_EGL_FOREVER); + } +} + +bool DMABufSurface::FenceImportFromFd() { + if (!mGL) return false; + 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}; + mSync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, attribs); + close(mSyncFd); + mSyncFd = -1; + + if (!mSync) { + MOZ_ASSERT(false, "Failed to create GLFence!"); + return false; + } + + return true; +} + +DMABufSurfaceRGBA::DMABufSurfaceRGBA() + : DMABufSurface(SURFACE_RGBA), + mSurfaceFlags(0), + mWidth(0), + mHeight(0), + mGmbFormat(nullptr), + mEGLImage(LOCAL_EGL_NO_IMAGE), + mTexture(0), + mGbmBufferFlags(0) {} + +DMABufSurfaceRGBA::~DMABufSurfaceRGBA() { ReleaseSurface(); } + +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)); + + mGmbFormat = GetDMABufDevice()->GetGbmFormat(mSurfaceFlags & DMABUF_ALPHA); + if (!mGmbFormat) { + // Requested DRM format is not supported. + return false; + } + + bool useModifiers = (aDMABufSurfaceFlags & DMABUF_USE_MODIFIERS) && + mGmbFormat->mModifiersCount > 0; + if (useModifiers) { + LOGDMABUF((" Creating with modifiers\n")); + mGbmBufferObject[0] = nsGbmLib::CreateWithModifiers( + GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight, mGmbFormat->mFormat, + mGmbFormat->mModifiers, mGmbFormat->mModifiersCount); + if (mGbmBufferObject[0]) { + mBufferModifier = nsGbmLib::GetModifier(mGbmBufferObject[0]); + } + } + + // Create without modifiers - use plain/linear format. + if (!mGbmBufferObject[0]) { + LOGDMABUF((" Creating without modifiers\n")); + mGbmBufferFlags = (GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR); + if (mSurfaceFlags & DMABUF_TEXTURE) { + mGbmBufferFlags |= GBM_BO_USE_TEXTURING; + } + + if (!nsGbmLib::DeviceIsFormatSupported(GetDMABufDevice()->GetGbmDevice(), + mGmbFormat->mFormat, + mGbmBufferFlags)) { + mGbmBufferFlags &= ~GBM_BO_USE_SCANOUT; + } + + mGbmBufferObject[0] = + nsGbmLib::Create(GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight, + mGmbFormat->mFormat, mGbmBufferFlags); + + mBufferModifier = DRM_FORMAT_MOD_INVALID; + } + + if (!mGbmBufferObject[0]) { + LOGDMABUF((" Failed to create GbmBufferObject\n")); + return false; + } + + if (mBufferModifier != DRM_FORMAT_MOD_INVALID) { + mBufferPlaneCount = nsGbmLib::GetPlaneCount(mGbmBufferObject[0]); + if (mBufferPlaneCount > DMABUF_BUFFER_PLANES) { + NS_WARNING("There's too many dmabuf planes!"); + ReleaseSurface(); + return false; + } + + for (int i = 0; i < mBufferPlaneCount; i++) { + uint32_t handle = nsGbmLib::GetHandleForPlane(mGbmBufferObject[0], i).u32; + int ret = nsGbmLib::DrmPrimeHandleToFD( + GetDMABufDevice()->GetGbmDeviceFd(), handle, 0, &mDmabufFds[i]); + if (ret < 0 || mDmabufFds[i] < 0) { + ReleaseSurface(); + return false; + } + mStrides[i] = nsGbmLib::GetStrideForPlane(mGbmBufferObject[0], i); + mOffsets[i] = nsGbmLib::GetOffset(mGbmBufferObject[0], i); + } + } else { + mBufferPlaneCount = 1; + mStrides[0] = nsGbmLib::GetStride(mGbmBufferObject[0]); + mDmabufFds[0] = nsGbmLib::GetFd(mGbmBufferObject[0]); + if (mDmabufFds[0] < 0) { + ReleaseSurface(); + return false; + } + } + + LOGDMABUF((" Success\n")); + return true; +} + +void DMABufSurfaceRGBA::ImportSurfaceDescriptor( + const SurfaceDescriptor& aDesc) { + const SurfaceDescriptorDMABuf& desc = aDesc.get_SurfaceDescriptorDMABuf(); + + mWidth = desc.width()[0]; + mHeight = desc.height()[0]; + mBufferModifier = desc.modifier(); + if (mBufferModifier != DRM_FORMAT_MOD_INVALID) { + mGmbFormat = GetDMABufDevice()->GetExactGbmFormat(desc.format()[0]); + } else { + mDrmFormats[0] = desc.format()[0]; + } + mBufferPlaneCount = desc.fds().Length(); + mGbmBufferFlags = desc.flags(); + MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES); + mUID = desc.uid(); + + for (int i = 0; i < mBufferPlaneCount; i++) { + mDmabufFds[i] = desc.fds()[i].ClonePlatformHandle().release(); + mStrides[i] = desc.strides()[i]; + mOffsets[i] = desc.offsets()[i]; + } + + if (desc.fence().Length() > 0) { + mSyncFd = desc.fence()[0].ClonePlatformHandle().release(); + } + + if (desc.refCount().Length() > 0) { + GlobalRefCountImport(desc.refCount()[0].ClonePlatformHandle().release()); + } + + LOGDMABUF(("DMABufSurfaceRGBA::Import() UID %d\n", mUID)); +} + +bool DMABufSurfaceRGBA::Create(const SurfaceDescriptor& aDesc) { + ImportSurfaceDescriptor(aDesc); + return true; +} + +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<ipc::FileDescriptor, 1> fenceFDs; + AutoTArray<ipc::FileDescriptor, 1> refCountFDs; + + LOGDMABUF(("DMABufSurfaceRGBA::Serialize() UID %d\n", mUID)); + + width.AppendElement(mWidth); + height.AppendElement(mHeight); + format.AppendElement(mGmbFormat->mFormat); + for (int i = 0; i < mBufferPlaneCount; i++) { + fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i])); + strides.AppendElement(mStrides[i]); + offsets.AppendElement(mOffsets[i]); + } + + if (mSync) { + fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd)); + } + + if (mGlobalRefCountFd) { + refCountFDs.AppendElement(ipc::FileDescriptor(mGlobalRefCountFd)); + } + + aOutDescriptor = + SurfaceDescriptorDMABuf(mSurfaceType, mBufferModifier, mGbmBufferFlags, + fds, width, height, format, strides, offsets, + GetYUVColorSpace(), fenceFDs, mUID, refCountFDs); + return true; +} + +bool DMABufSurfaceRGBA::CreateTexture(GLContext* aGLContext, int aPlane) { + 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); + if (mGmbFormat) { + attribs.AppendElement(mGmbFormat->mFormat); + } else { + 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 (mBufferModifier != DRM_FORMAT_MOD_INVALID) { \ + attribs.AppendElement( \ + LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_LO_EXT); \ + attribs.AppendElement(mBufferModifier & 0xFFFFFFFF); \ + attribs.AppendElement( \ + LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_HI_EXT); \ + attribs.AppendElement(mBufferModifier >> 32); \ + } \ + } + 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()); + if (mEGLImage == LOCAL_EGL_NO_IMAGE) { + NS_WARNING("EGLImageKHR creation failed"); + return false; + } + + aGLContext->MakeCurrent(); + 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() { + FenceDelete(); + + if (!mGL) return; + const auto& gle = gl::GLContextEGL::Cast(mGL); + const auto& egl = gle->mEgl; + + if (mTexture && mGL->MakeCurrent()) { + mGL->fDeleteTextures(1, &mTexture); + mTexture = 0; + mGL = nullptr; + } + + if (mEGLImage) { + egl->fDestroyImage(mEGLImage); + mEGLImage = nullptr; + } +} + +void DMABufSurfaceRGBA::ReleaseSurface() { + MOZ_ASSERT(!IsMapped(), "We can't release mapped buffer!"); + + ReleaseTextures(); + ReleaseDMABuf(); +} + +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 size %d x %d -> %d x %d\n", + mUID, 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 (aStride) { + *aStride = mMappedRegionStride[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]) { + 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 : true; +} + +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<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface( + const VADRMPRIMESurfaceDescriptor& aDesc) { + RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV(); + if (!surf->UpdateYUVData(aDesc)) { + return nullptr; + } + return surf.forget(); +} + +already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface( + int aWidth, int aHeight, void** aPixelData, int* aLineSizes) { + RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV(); + if (!surf->Create(aWidth, aHeight, aPixelData, aLineSizes)) { + return nullptr; + } + return surf.forget(); +} + +DMABufSurfaceYUV::DMABufSurfaceYUV() + : DMABufSurface(SURFACE_NV12), + mWidth(), + mHeight(), + mTexture(), + mColorSpace(mozilla::gfx::YUVColorSpace::UNKNOWN) { + for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) { + mEGLImage[i] = LOCAL_EGL_NO_IMAGE; + } +} + +DMABufSurfaceYUV::~DMABufSurfaceYUV() { ReleaseSurface(); } + +bool DMABufSurfaceYUV::UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc) { + if (aDesc.num_layers > DMABUF_BUFFER_PLANES || + aDesc.num_objects > DMABUF_BUFFER_PLANES) { + return false; + } + if (mDmabufFds[0] >= 0) { + NS_WARNING("DMABufSurfaceYUV is already created!"); + return false; + } + if (aDesc.fourcc == VA_FOURCC_NV12) { + mSurfaceType = SURFACE_NV12; + } else if (aDesc.fourcc == VA_FOURCC_YV12) { + mSurfaceType = SURFACE_YUV420; + } else { + NS_WARNING( + nsPrintfCString( + "UpdateYUVData(): Can't import surface data of 0x%x format\n", + aDesc.fourcc) + .get()); + return false; + } + + mBufferPlaneCount = aDesc.num_layers; + mBufferModifier = aDesc.objects[0].drm_format_modifier; + + for (unsigned int i = 0; i < aDesc.num_layers; i++) { + // Intel exports VA-API surfaces in one object,planes have the same FD. + // AMD exports surfaces in two objects with different FDs. + bool dupFD = (aDesc.layers[i].object_index[0] != i); + int fd = aDesc.objects[aDesc.layers[i].object_index[0]].fd; + mDmabufFds[i] = dupFD ? dup(fd) : fd; + + mDrmFormats[i] = aDesc.layers[i].drm_format; + mOffsets[i] = aDesc.layers[i].offset[0]; + mStrides[i] = aDesc.layers[i].pitch[0]; + mWidth[i] = aDesc.width >> i; + mHeight[i] = aDesc.height >> i; + } + + return true; +} + +bool DMABufSurfaceYUV::CreateYUVPlane(int aPlane, int aWidth, int aHeight, + int aDrmFormat) { + mWidth[aPlane] = aWidth; + mHeight[aPlane] = aHeight; + mDrmFormats[aPlane] = aDrmFormat; + + mGbmBufferObject[aPlane] = + nsGbmLib::Create(GetDMABufDevice()->GetGbmDevice(), aWidth, aHeight, + aDrmFormat, GBM_BO_USE_LINEAR | GBM_BO_USE_TEXTURING); + if (!mGbmBufferObject[aPlane]) { + NS_WARNING("Failed to create GbmBufferObject!"); + return false; + } + + mStrides[aPlane] = nsGbmLib::GetStride(mGbmBufferObject[aPlane]); + mDmabufFds[aPlane] = nsGbmLib::GetFd(mGbmBufferObject[aPlane]); + + return true; +} + +void DMABufSurfaceYUV::UpdateYUVPlane(int aPlane, void* aPixelData, + int aLineSize) { + 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) { + if (mSurfaceType != SURFACE_YUV420) { + NS_WARNING("UpdateYUVData can upload YUV420 surface type only!"); + return false; + } + + if (mBufferPlaneCount != 3) { + NS_WARNING("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]) { + NS_WARNING("DMABufSurfaceYUV plane can't be mapped!"); + return false; + } + if ((int)mMappedRegionStride[i] < mWidth[i]) { + NS_WARNING("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) { + 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; + } + + return aPixelData != nullptr && aLineSizes != nullptr + ? UpdateYUVData(aPixelData, aLineSizes) + : true; +} + +bool DMABufSurfaceYUV::Create(const SurfaceDescriptor& aDesc) { + ImportSurfaceDescriptor(aDesc); + return true; +} + +void DMABufSurfaceYUV::ImportSurfaceDescriptor( + const SurfaceDescriptorDMABuf& aDesc) { + mBufferPlaneCount = aDesc.fds().Length(); + mSurfaceType = (mBufferPlaneCount == 2) ? SURFACE_NV12 : SURFACE_YUV420; + mBufferModifier = aDesc.modifier(); + mColorSpace = aDesc.yUVColorSpace(); + mUID = aDesc.uid(); + + MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES); + for (int i = 0; i < mBufferPlaneCount; i++) { + mDmabufFds[i] = aDesc.fds()[i].ClonePlatformHandle().release(); + mWidth[i] = aDesc.width()[i]; + mHeight[i] = aDesc.height()[i]; + mDrmFormats[i] = aDesc.format()[i]; + mStrides[i] = aDesc.strides()[i]; + mOffsets[i] = aDesc.offsets()[i]; + } + + if (aDesc.fence().Length() > 0) { + mSyncFd = aDesc.fence()[0].ClonePlatformHandle().release(); + } + + if (aDesc.refCount().Length() > 0) { + GlobalRefCountImport(aDesc.refCount()[0].ClonePlatformHandle().release()); + } +} + +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> format; + AutoTArray<ipc::FileDescriptor, DMABUF_BUFFER_PLANES> fds; + AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> strides; + AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> offsets; + AutoTArray<ipc::FileDescriptor, 1> fenceFDs; + AutoTArray<ipc::FileDescriptor, 1> refCountFDs; + + for (int i = 0; i < mBufferPlaneCount; i++) { + width.AppendElement(mWidth[i]); + height.AppendElement(mHeight[i]); + format.AppendElement(mDrmFormats[i]); + fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i])); + strides.AppendElement(mStrides[i]); + offsets.AppendElement(mOffsets[i]); + } + + if (mSync) { + fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd)); + } + + if (mGlobalRefCountFd) { + refCountFDs.AppendElement(ipc::FileDescriptor(mGlobalRefCountFd)); + } + + aOutDescriptor = SurfaceDescriptorDMABuf( + mSurfaceType, mBufferModifier, 0, fds, width, height, format, strides, + offsets, GetYUVColorSpace(), fenceFDs, mUID, refCountFDs); + return true; +} + +bool DMABufSurfaceYUV::CreateTexture(GLContext* aGLContext, int aPlane) { + MOZ_ASSERT(!mEGLImage[aPlane] && !mTexture[aPlane], + "EGLImage/Texture is already created!"); + + if (!aGLContext) return false; + const auto& gle = gl::GLContextEGL::Cast(aGLContext); + const auto& egl = gle->mEgl; + + nsTArray<EGLint> attribs; + attribs.AppendElement(LOCAL_EGL_WIDTH); + attribs.AppendElement(mWidth[aPlane]); + attribs.AppendElement(LOCAL_EGL_HEIGHT); + attribs.AppendElement(mHeight[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]); + 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()); + if (mEGLImage[aPlane] == LOCAL_EGL_NO_IMAGE) { + NS_WARNING("EGLImageKHR creation failed"); + return false; + } + + aGLContext->MakeCurrent(); + 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() { + FenceDelete(); + + bool textureActive = false; + for (int i = 0; i < mBufferPlaneCount; i++) { + if (mTexture[i]) { + textureActive = true; + break; + } + } + + if (!mGL) return; + const auto& gle = gl::GLContextEGL::Cast(mGL); + const auto& egl = gle->mEgl; + + if (textureActive && mGL->MakeCurrent()) { + mGL->fDeleteTextures(DMABUF_BUFFER_PLANES, mTexture); + for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) { + mTexture[i] = 0; + } + mGL = nullptr; + } + + for (int i = 0; i < mBufferPlaneCount; i++) { + if (mEGLImage[i]) { + egl->fDestroyImage(mEGLImage[i]); + mEGLImage[i] = nullptr; + } + } +} + +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(); } + +uint32_t 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() { + ReleaseTextures(); + ReleaseDMABuf(); +} |