1
0
Fork 0
firefox/widget/gtk/DMABufSurface.cpp
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

2208 lines
70 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 "DMABufDevice.h"
#include "DMABufFormats.h"
#ifdef MOZ_WAYLAND
# include "nsWaylandDisplay.h"
#endif
#include <gbm.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>
#ifdef HAVE_EVENTFD
# include <sys/eventfd.h>
#endif
#include <poll.h>
#ifdef HAVE_SYSIOCCOM_H
# include <sys/ioccom.h>
#endif
#include <sys/ioctl.h>
// DMABufLibWrapper defines its own version of this which collides with the
// official version in drm_fourcc.h
#ifdef DRM_FORMAT_MOD_INVALID
# undef DRM_FORMAT_MOD_INVALID
#endif
#include <libdrm/drm_fourcc.h>
#include "mozilla/widget/va_drmcommon.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/FileHandleWrapper.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 "ImageContainer.h"
#include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/gfx/gfxVars.h"
/*
TODO:
- DRM device selection:
https://lists.freedesktop.org/archives/wayland-devel/2018-November/039660.html
- Use uint64_t mBufferModifiers / mGbmBufferObject for RGBA
- Remove file descriptors open/close?
*/
/* 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;
#undef LOGDMABUF
#undef LOGDMABUFREF
#ifdef MOZ_LOGGING
# include "mozilla/Logging.h"
# include "nsTArray.h"
# include "Units.h"
extern mozilla::LazyLogModule gDmabufLog;
# define LOGDMABUF(str, ...) \
MOZ_LOG(gDmabufLog, mozilla::LogLevel::Debug, \
("%s: " str, GetDebugTag().get(), ##__VA_ARGS__))
# define LOGDMABUFS(str, ...) \
MOZ_LOG(gDmabufLog, mozilla::LogLevel::Debug, (str, ##__VA_ARGS__))
static LazyLogModule gDmabufRefLog("DmabufRef");
# define LOGDMABUFREF(str, ...) \
MOZ_LOG(gDmabufRefLog, mozilla::LogLevel::Debug, \
("%s: " str, GetDebugTag().get(), ##__VA_ARGS__))
#else
# define LOGDMABUF(str, ...)
# define LOGDMABUFREF(str, ...)
#endif /* MOZ_LOGGING */
#define BUFFER_FLAGS 0
static const std::string FormatEGLError(EGLint err) {
switch (err) {
case LOCAL_EGL_NOT_INITIALIZED:
return "EGL_NOT_INITIALIZED";
case LOCAL_EGL_BAD_ACCESS:
return "EGL_BAD_ACCESS";
case LOCAL_EGL_BAD_ALLOC:
return "EGL_BAD_ALLOC";
case LOCAL_EGL_BAD_ATTRIBUTE:
return "EGL_BAD_ATTRIBUTE";
case LOCAL_EGL_BAD_CONTEXT:
return "EGL_BAD_CONTEXT";
case LOCAL_EGL_BAD_CONFIG:
return "EGL_BAD_CONFIG";
case LOCAL_EGL_BAD_CURRENT_SURFACE:
return "EGL_BAD_CURRENT_SURFACE";
case LOCAL_EGL_BAD_DISPLAY:
return "EGL_BAD_DISPLAY";
case LOCAL_EGL_BAD_SURFACE:
return "EGL_BAD_SURFACE";
case LOCAL_EGL_BAD_MATCH:
return "EGL_BAD_MATCH";
case LOCAL_EGL_BAD_PARAMETER:
return "EGL_BAD_PARAMETER";
case LOCAL_EGL_BAD_NATIVE_PIXMAP:
return "EGL_BAD_NATIVE_PIXMAP";
case LOCAL_EGL_BAD_NATIVE_WINDOW:
return "EGL_BAD_NATIVE_WINDOW";
case LOCAL_EGL_CONTEXT_LOST:
return "EGL_CONTEXT_LOST";
default:
return "EGL error code: " + std::to_string(err);
}
}
MOZ_RUNINIT static RefPtr<GLContext> sSnapshotContext;
static StaticMutex sSnapshotContextMutex MOZ_UNANNOTATED;
static Atomic<int> gNewSurfaceUID(1);
// We should release all resources allocated by SnapshotGLContext before
// ReturnSnapshotGLContext() call. Otherwise DMABufSurface references
// SnapshotGLContext and may colide with other SnapshotGLContext operations.
RefPtr<GLContext> ClaimSnapshotGLContext() {
if (!sSnapshotContext) {
nsCString discardFailureId;
sSnapshotContext = GLContextProvider::CreateHeadless({}, &discardFailureId);
if (!sSnapshotContext) {
LOGDMABUFS(
"ClaimSnapshotGLContext: Failed to create snapshot GLContext.");
return nullptr;
}
sSnapshotContext->mOwningThreadId = Nothing(); // No singular owner.
}
if (!sSnapshotContext->MakeCurrent()) {
LOGDMABUFS("ClaimSnapshotGLContext: Failed to make GLContext current.");
return nullptr;
}
return sSnapshotContext;
}
void ReturnSnapshotGLContext(RefPtr<GLContext> aGLContext) {
// direct eglMakeCurrent() call breaks current context caching so make sure
// it's not used.
MOZ_ASSERT(!aGLContext->mUseTLSIsCurrent);
if (!aGLContext->IsCurrent()) {
LOGDMABUFS("ReturnSnapshotGLContext() failed, is not current!");
return;
}
const auto& gle = gl::GLContextEGL::Cast(aGLContext);
const auto& egl = gle->mEgl;
egl->fMakeCurrent(EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
bool DMABufSurface::UseDmaBufGL(GLContext* aGLContext) {
if (!aGLContext) {
LOGDMABUFS("DMABufSurface::UseDmaBufGL(): Missing GLContext!");
return false;
}
static bool useDmabufGL = [&]() {
if (!aGLContext->IsExtensionSupported(gl::GLContext::OES_EGL_image)) {
gfxCriticalNote << "DMABufSurface::UseDmaBufGL(): no OES_EGL_image.";
return false;
}
return true;
}();
return useDmabufGL;
}
bool DMABufSurface::UseDmaBufExportExtension(GLContext* aGLContext) {
static bool useDmabufExport = [&]() {
if (!gfx::gfxVars::UseDMABufSurfaceExport()) {
return false;
}
if (!UseDmaBufGL(aGLContext)) {
return false;
}
if (!aGLContext->IsAtLeast(gl::ContextProfile::OpenGLCore, 300) &&
!aGLContext->IsAtLeast(gl::ContextProfile::OpenGLES, 300)) {
gfxCriticalNote
<< "DMABufSurface::UseDmaBufExportExtension(): old GL version!";
return false;
}
const auto& gle = gl::GLContextEGL::Cast(aGLContext);
const auto& egl = gle->mEgl;
bool extensionsAvailable =
egl->IsExtensionSupported(EGLExtension::EXT_image_dma_buf_import) &&
egl->IsExtensionSupported(
EGLExtension::EXT_image_dma_buf_import_modifiers) &&
egl->IsExtensionSupported(EGLExtension::MESA_image_dma_buf_export);
if (!extensionsAvailable) {
gfxCriticalNote << "DMABufSurface::UseDmaBufExportExtension(): "
"MESA_image_dma_buf import/export extensions!";
}
return extensionsAvailable;
}();
return aGLContext && useDmabufExport;
}
nsAutoCString DMABufSurface::GetDebugTag() const {
nsAutoCString tag;
tag.AppendPrintf("[%p]", this);
return tag;
}
bool DMABufSurface::IsGlobalRefSet() {
MutexAutoLock lock(mSurfaceLock);
if (!mGlobalRefCountFd) {
return false;
}
struct pollfd pfd;
pfd.fd = mGlobalRefCountFd;
pfd.events = POLLIN;
return poll(&pfd, 1, 0) == 1;
}
void DMABufSurface::GlobalRefRelease() {
#ifdef HAVE_EVENTFD
MutexAutoLock lock(mSurfaceLock);
if (!mGlobalRefCountFd) {
return;
}
LOGDMABUFREF("DMABufSurface::GlobalRefRelease UID %d", mUID);
uint64_t counter;
if (read(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
if (errno == EAGAIN) {
LOGDMABUFREF(" GlobalRefRelease failed: already zero reference! UID %d",
mUID);
}
// EAGAIN means the refcount is already zero. It happens when we release
// last reference to the surface.
if (errno != EAGAIN) {
NS_WARNING(nsPrintfCString("Failed to unref dmabuf global ref count: %s",
strerror(errno))
.get());
}
}
#endif
}
void DMABufSurface::GlobalRefAddLocked(const MutexAutoLock& aProofOfLock) {
#ifdef HAVE_EVENTFD
LOGDMABUFREF("DMABufSurface::GlobalRefAddLocked UID %d", mUID);
MOZ_DIAGNOSTIC_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());
}
#endif
}
void DMABufSurface::GlobalRefAdd() {
LOGDMABUFREF("DMABufSurface::GlobalRefAdd UID %d", mUID);
MutexAutoLock lock(mSurfaceLock);
GlobalRefAddLocked(lock);
}
void DMABufSurface::GlobalRefCountCreate() {
#ifdef HAVE_EVENTFD
LOGDMABUFREF("DMABufSurface::GlobalRefCountCreate UID %d", mUID);
MutexAutoLock lock(mSurfaceLock);
MOZ_DIAGNOSTIC_ASSERT(!mGlobalRefCountFd);
// Create global ref count initialized to 0,
// i.e. is not referenced after create.
mGlobalRefCountFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE);
if (mGlobalRefCountFd < 0) {
NS_WARNING(nsPrintfCString("Failed to create dmabuf global ref count: %s",
strerror(errno))
.get());
mGlobalRefCountFd = 0;
return;
}
#endif
}
void DMABufSurface::GlobalRefCountImport(int aFd) {
#ifdef HAVE_EVENTFD
MutexAutoLock lock(mSurfaceLock);
mGlobalRefCountFd = aFd;
if (mGlobalRefCountFd) {
LOGDMABUFREF("DMABufSurface::GlobalRefCountImport UID %d", mUID);
GlobalRefAddLocked(lock);
}
#endif
}
int DMABufSurface::GlobalRefCountExport() {
MutexAutoLock lock(mSurfaceLock);
#ifdef MOZ_LOGGING
if (mGlobalRefCountFd) {
LOGDMABUFREF("DMABufSurface::GlobalRefCountExport UID %d", mUID);
}
#endif
return mGlobalRefCountFd;
}
void DMABufSurface::GlobalRefCountDelete() {
MutexAutoLock lock(mSurfaceLock);
if (mGlobalRefCountFd) {
LOGDMABUFREF("DMABufSurface::GlobalRefCountDelete UID %d", mUID);
close(mGlobalRefCountFd);
mGlobalRefCountFd = 0;
}
}
void DMABufSurface::ReleaseDMABuf() {
LOGDMABUF("DMABufSurface::ReleaseDMABuf() UID %d", mUID);
#ifdef MOZ_LOGGING
for (int i = 0; i < mBufferPlaneCount; i++) {
Unmap(i);
}
#endif
CloseFileDescriptors();
for (int i = 0; i < mBufferPlaneCount; i++) {
if (mGbmBufferObject[i]) {
GbmLib::Destroy(mGbmBufferObject[i]);
mGbmBufferObject[i] = nullptr;
}
}
mBufferPlaneCount = 0;
}
DMABufSurface::DMABufSurface(SurfaceType aSurfaceType)
: mSurfaceType(aSurfaceType),
mBufferPlaneCount(0),
mStrides(),
mOffsets(),
mGbmBufferObject(),
mGbmBufferFlags(0),
#ifdef MOZ_LOGGING
mMappedRegion(),
mMappedRegionStride(),
#endif
mSync(nullptr),
mGlobalRefCountFd(0),
mUID(gNewSurfaceUID++),
mPID(0),
mCanRecycle(true),
mSurfaceLock("DMABufSurface") {
}
DMABufSurface::~DMABufSurface() {
FenceDelete();
GlobalRefRelease();
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_YUV:
surf = new DMABufSurfaceYUV();
break;
default:
return nullptr;
}
if (!surf->Create(desc)) {
return nullptr;
}
return surf.forget();
}
void DMABufSurface::FenceDelete() {
if (mSyncFd) {
mSyncFd = nullptr;
}
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) {
auto rawFd = egl->fDupNativeFenceFDANDROID(mSync);
mSyncFd = new gfx::FileHandleWrapper(UniqueFileHandle(rawFd));
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) {
MOZ_DIAGNOSTIC_ASSERT(mGL,
"DMABufSurface::FenceWait() missing GL context!");
return;
}
const auto& gle = gl::GLContextEGL::Cast(mGL);
const auto& egl = gle->mEgl;
auto syncFd = mSyncFd->ClonePlatformHandle();
// No need to try mSyncFd twice.
mSyncFd = nullptr;
const EGLint attribs[] = {LOCAL_EGL_SYNC_NATIVE_FENCE_FD_ANDROID,
syncFd.get(), 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!");
return;
}
// syncFd is owned by GLFence so clear local reference to avoid double.
Unused << syncFd.release();
egl->fClientWaitSync(sync, 0, LOCAL_EGL_FOREVER);
egl->fDestroySync(sync);
}
void DMABufSurface::MaybeSemaphoreWait(GLuint aGlTexture) {
MOZ_ASSERT(aGlTexture);
if (!mSemaphoreFd) {
return;
}
if (!mGL) {
MOZ_DIAGNOSTIC_ASSERT(mGL,
"DMABufSurface::SemaphoreWait() missing GL context!");
return;
}
if (!mGL->IsExtensionSupported(gl::GLContext::EXT_semaphore) ||
!mGL->IsExtensionSupported(gl::GLContext::EXT_semaphore_fd)) {
MOZ_ASSERT_UNREACHABLE("unexpected to be called");
gfxCriticalNoteOnce << "EXT_semaphore_fd is not suppored";
return;
}
auto fd = mSemaphoreFd->ClonePlatformHandle();
// No need to try mSemaphoreFd twice.
mSemaphoreFd = nullptr;
GLuint semaphoreHandle = 0;
mGL->fGenSemaphoresEXT(1, &semaphoreHandle);
mGL->fImportSemaphoreFdEXT(semaphoreHandle,
LOCAL_GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd.release());
auto error = mGL->fGetError();
if (error != LOCAL_GL_NO_ERROR) {
gfxCriticalNoteOnce << "glImportSemaphoreFdEXT failed: " << error;
return;
}
GLenum srcLayout = LOCAL_GL_LAYOUT_COLOR_ATTACHMENT_EXT;
mGL->fWaitSemaphoreEXT(semaphoreHandle, 0, nullptr, 1, &aGlTexture,
&srcLayout);
error = mGL->fGetError();
if (error != LOCAL_GL_NO_ERROR) {
gfxCriticalNoteOnce << "glWaitSemaphoreEXT failed: " << error;
return;
}
}
bool DMABufSurface::OpenFileDescriptors() {
for (int i = 0; i < mBufferPlaneCount; i++) {
if (!OpenFileDescriptorForPlane(i)) {
return false;
}
}
return true;
}
void DMABufSurface::CloseFileDescriptors() {
for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
if (mDmabufFds[i]) {
mDmabufFds[i] = nullptr;
}
}
}
nsresult DMABufSurface::ReadIntoBuffer(mozilla::gl::GLContext* aGLContext,
uint8_t* aData, int32_t aStride,
const gfx::IntSize& aSize,
gfx::SurfaceFormat aFormat) {
LOGDMABUF("DMABufSurface::ReadIntoBuffer UID %d", mUID);
// We're empty, nothing to copy
if (!GetTextureCount()) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(aSize.width == GetWidth());
MOZ_ASSERT(aSize.height == GetHeight());
for (int i = 0; i < GetTextureCount(); i++) {
if (!GetTexture(i) && !CreateTexture(aGLContext, i)) {
LOGDMABUF("ReadIntoBuffer: Failed to create DMABuf textures.");
return NS_ERROR_FAILURE;
}
}
ScopedTexture scopedTex(aGLContext);
ScopedBindTexture boundTex(aGLContext, scopedTex.Texture());
aGLContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, aSize.width,
aSize.height, 0, LOCAL_GL_RGBA,
LOCAL_GL_UNSIGNED_BYTE, nullptr);
ScopedFramebufferForTexture autoFBForTex(aGLContext, scopedTex.Texture());
if (!autoFBForTex.IsComplete()) {
LOGDMABUF("ReadIntoBuffer: ScopedFramebufferForTexture failed.");
return NS_ERROR_FAILURE;
}
const gl::OriginPos destOrigin = gl::OriginPos::BottomLeft;
{
const ScopedBindFramebuffer bindFB(aGLContext, autoFBForTex.FB());
if (!aGLContext->BlitHelper()->Blit(this, aSize, destOrigin)) {
LOGDMABUF("ReadIntoBuffer: Blit failed.");
return NS_ERROR_FAILURE;
}
}
ScopedBindFramebuffer bind(aGLContext, autoFBForTex.FB());
ReadPixelsIntoBuffer(aGLContext, aData, aStride, aSize, aFormat);
return NS_OK;
}
already_AddRefed<gfx::DataSourceSurface> DMABufSurface::GetAsSourceSurface() {
LOGDMABUF("DMABufSurface::GetAsSourceSurface UID %d", mUID);
gfx::IntSize size(GetWidth(), GetHeight());
const auto format = gfx::SurfaceFormat::B8G8R8A8;
RefPtr<gfx::DataSourceSurface> source =
gfx::Factory::CreateDataSourceSurface(size, format);
if (NS_WARN_IF(!source)) {
LOGDMABUF("GetAsSourceSurface: CreateDataSourceSurface failed.");
return nullptr;
}
gfx::DataSourceSurface::ScopedMap map(source,
gfx::DataSourceSurface::READ_WRITE);
if (NS_WARN_IF(!map.IsMapped())) {
LOGDMABUF("GetAsSourceSurface: Mapping surface failed.");
return nullptr;
}
if (mGL) {
if (NS_WARN_IF(NS_FAILED(ReadIntoBuffer(mGL, map.GetData(), map.GetStride(),
size, format)))) {
LOGDMABUF("GetAsSourceSurface: Reading into buffer failed.");
return nullptr;
}
} else {
// We're missing active GL context - take a snapshot one.
StaticMutexAutoLock lock(sSnapshotContextMutex);
RefPtr<GLContext> context = ClaimSnapshotGLContext();
auto releaseTextures = mozilla::MakeScopeExit([&] {
ReleaseTextures();
ReturnSnapshotGLContext(context);
});
if (NS_WARN_IF(NS_FAILED(ReadIntoBuffer(context, map.GetData(),
map.GetStride(), size, format)))) {
LOGDMABUF("GetAsSourceSurface: Reading into buffer failed.");
return nullptr;
}
}
return source.forget();
}
DMABufSurfaceRGBA::DMABufSurfaceRGBA()
: DMABufSurface(SURFACE_RGBA),
mWidth(0),
mHeight(0),
mEGLImage(LOCAL_EGL_NO_IMAGE),
mTexture(0),
mBufferModifier(DRM_FORMAT_MOD_INVALID) {}
DMABufSurfaceRGBA::~DMABufSurfaceRGBA() { ReleaseSurface(); }
bool DMABufSurfaceRGBA::OpenFileDescriptorForPlane(int aPlane) {
if (mDmabufFds[aPlane]) {
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!");
auto rawFd = GbmLib::GetFd(bo);
if (rawFd >= 0) {
mDmabufFds[0] = new gfx::FileHandleWrapper(UniqueFileHandle(rawFd));
} else {
gfxCriticalNoteOnce << "GbmLib::GetFd() failed";
LOGDMABUF(
"DMABufSurfaceRGBA::OpenFileDescriptorForPlane: GbmLib::GetFd() "
"failed");
}
} else {
auto rawFd = GetDMABufDevice()->GetDmabufFD(
GbmLib::GetHandleForPlane(bo, aPlane).u32);
if (rawFd >= 0) {
mDmabufFds[aPlane] = new gfx::FileHandleWrapper(UniqueFileHandle(rawFd));
} else {
gfxCriticalNoteOnce << "DMABufDevice::GetDmabufFD() failed";
LOGDMABUF(
"DMABufSurfaceRGBA::OpenFileDescriptorForPlane: "
"DMABufDevice::GetDmabufFD() failed");
}
}
if (!mDmabufFds[aPlane]) {
CloseFileDescriptors();
return false;
}
return true;
}
bool DMABufSurfaceRGBA::Create(mozilla::gl::GLContext* aGLContext, int aWidth,
int aHeight, int aDMABufSurfaceFlags,
RefPtr<DRMFormat> aFormat) {
bool useGLSnapshot = gfx::gfxVars::UseDMABufSurfaceExport() && !aGLContext;
if (useGLSnapshot) {
StaticMutexAutoLock lock(sSnapshotContextMutex);
RefPtr<GLContext> context = ClaimSnapshotGLContext();
auto releaseTextures = MakeScopeExit([&] {
ReleaseTextures();
ReturnSnapshotGLContext(context);
});
// If gfxVars::UseDMABufSurfaceExport() is set but we fail due to missing
// system support, don't try GBM.
if (!UseDmaBufExportExtension(context)) {
return false;
}
return CreateExport(context, aWidth, aHeight, aDMABufSurfaceFlags);
}
if (gfx::gfxVars::UseDMABufSurfaceExport()) {
if (!UseDmaBufExportExtension(aGLContext)) {
return false;
}
return CreateExport(aGLContext, aWidth, aHeight, aDMABufSurfaceFlags);
}
if (!aFormat) {
mFOURCCFormat = aDMABufSurfaceFlags & DMABUF_ALPHA ? GBM_FORMAT_ARGB8888
: GBM_FORMAT_XRGB8888;
aFormat = GetGlobalDMABufFormats()->GetDRMFormat(mFOURCCFormat);
if (!aFormat) {
LOGDMABUF("DMABufSurfaceRGBA::Create(): Missing drm format 0x%x!",
mFOURCCFormat);
return false;
}
}
return CreateGBM(aWidth, aHeight, aDMABufSurfaceFlags, aFormat);
}
bool DMABufSurfaceRGBA::CreateGBM(int aWidth, int aHeight,
int aDMABufSurfaceFlags,
RefPtr<DRMFormat> aFormat) {
MOZ_ASSERT(mGbmBufferObject[0] == nullptr, "Already created?");
if (!GetDMABufDevice()->GetGbmDevice()) {
LOGDMABUF("DMABufSurfaceRGBA::Create(): Missing GbmDevice!");
return false;
}
mWidth = aWidth;
mHeight = aHeight;
mFOURCCFormat = aFormat->GetFormat();
LOGDMABUF(
"DMABufSurfaceRGBA::Create() UID %d size %d x %d format 0x%x "
"modifiers %d\n",
mUID, mWidth, mHeight, mFOURCCFormat, aFormat->UseModifiers());
if (aDMABufSurfaceFlags & DMABUF_TEXTURE) {
mGbmBufferFlags = GBM_BO_USE_RENDERING;
} else if (aDMABufSurfaceFlags & DMABUF_SCANOUT) {
mGbmBufferFlags = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT;
}
bool useModifiers =
aFormat->UseModifiers() && (aDMABufSurfaceFlags & DMABUF_USE_MODIFIERS);
if (useModifiers) {
LOGDMABUF(" Creating with modifiers\n");
uint32_t modifiersNum = 0;
const uint64_t* modifiers = aFormat->GetModifiers(modifiersNum);
mGbmBufferObject[0] = GbmLib::CreateWithModifiers2(
GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight, mFOURCCFormat,
modifiers, modifiersNum, mGbmBufferFlags);
if (mGbmBufferObject[0]) {
mBufferModifier = GbmLib::GetModifier(mGbmBufferObject[0]);
}
}
if (!mGbmBufferObject[0]) {
LOGDMABUF(" Creating without modifiers\n");
mGbmBufferFlags = GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR;
mGbmBufferObject[0] =
GbmLib::Create(GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight,
mFOURCCFormat, mGbmBufferFlags);
mBufferModifier = DRM_FORMAT_MOD_INVALID;
}
if (!mGbmBufferObject[0]) {
LOGDMABUF(" Failed to create GbmBufferObject\n");
return false;
}
if (mBufferModifier != DRM_FORMAT_MOD_INVALID) {
mBufferPlaneCount = GbmLib::GetPlaneCount(mGbmBufferObject[0]);
LOGDMABUF(" Planes count %d", mBufferPlaneCount);
if (mBufferPlaneCount > DMABUF_BUFFER_PLANES) {
LOGDMABUF(" There's too many dmabuf planes! (%d)", mBufferPlaneCount);
mBufferPlaneCount = DMABUF_BUFFER_PLANES;
return false;
}
for (int i = 0; i < mBufferPlaneCount; i++) {
mStrides[i] = GbmLib::GetStrideForPlane(mGbmBufferObject[0], i);
mOffsets[i] = GbmLib::GetOffset(mGbmBufferObject[0], i);
}
} else {
mBufferPlaneCount = 1;
mStrides[0] = GbmLib::GetStride(mGbmBufferObject[0]);
}
if (!OpenFileDescriptors()) {
LOGDMABUF(" Failed to open Fd!");
return false;
}
LOGDMABUF(" Success\n");
return true;
}
bool DMABufSurfaceRGBA::CreateExport(mozilla::gl::GLContext* aGLContext,
int aWidth, int aHeight,
int aDMABufSurfaceFlags) {
LOGDMABUF("DMABufSurfaceRGBA::CreateExport() UID %d size %d x %d flags %d",
mUID, aWidth, aHeight, aDMABufSurfaceFlags);
MOZ_ASSERT(aGLContext);
MOZ_DIAGNOSTIC_ASSERT(!mTexture && !mEGLImage, "Already exported??");
MOZ_DIAGNOSTIC_ASSERT(!mGL || mGL == aGLContext);
mGL = aGLContext;
auto releaseTextures = MakeScopeExit([&] { ReleaseTextures(); });
if (!mGL->MakeCurrent()) {
LOGDMABUF(" failed to make GL context current");
return false;
}
mWidth = aWidth;
mHeight = aHeight;
mGL->fGenTextures(1, &mTexture);
const ScopedBindTexture savedTex(mGL, mTexture);
GLContext::LocalErrorScope errorScope(*mGL);
mGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, mWidth, mHeight, 0,
LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, nullptr);
const auto err = errorScope.GetError();
if (err) {
LOGDMABUF(" TexImage2D failed %x error %s", err,
GLContext::GLErrorToString(err).c_str());
return false;
}
const auto& gle = gl::GLContextEGL::Cast(mGL);
const auto& context = gle->mContext;
const auto& egl = gle->mEgl;
mEGLImage =
egl->fCreateImage(context, LOCAL_EGL_GL_TEXTURE_2D,
reinterpret_cast<EGLClientBuffer>(mTexture), nullptr);
if (mEGLImage == LOCAL_EGL_NO_IMAGE) {
LOGDMABUF(" EGLImageKHR creation failed, EGL error %s",
FormatEGLError(egl->mLib->fGetError()).c_str());
return false;
}
if (!egl->fExportDMABUFImageQuery(mEGLImage, &mFOURCCFormat,
&mBufferPlaneCount, &mBufferModifier)) {
LOGDMABUF(" ExportDMABUFImageQueryMESA failed, quit\n");
return false;
}
if (mBufferPlaneCount > DMABUF_BUFFER_PLANES) {
LOGDMABUF(" wrong plane count %d, quit\n", mBufferPlaneCount);
mBufferPlaneCount = DMABUF_BUFFER_PLANES;
return false;
}
int fds[DMABUF_BUFFER_PLANES] = {-1};
if (!egl->fExportDMABUFImage(mEGLImage, fds, mStrides, mOffsets)) {
LOGDMABUF(" ExportDMABUFImageMESA failed, quit\n");
return false;
}
for (int i = 0; i < mBufferPlaneCount; i++) {
if (fds[i] > 0) {
mDmabufFds[i] = new gfx::FileHandleWrapper(UniqueFileHandle(fds[i]));
}
}
// 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]) {
LOGDMABUF(
" ExportDMABUFImageMESA failed, mDmabufFds[%d] is invalid, quit", i);
return false;
}
}
if (GetFormat() == gfx::SurfaceFormat::UNKNOWN) {
LOGDMABUF(" failed, unsupported drm format %x", mFOURCCFormat);
return false;
}
LOGDMABUF(" created size %d x %d format %x planes %d modifiers %" PRIx64
" alpha %d",
mWidth, mHeight, mFOURCCFormat, mBufferPlaneCount, mBufferModifier,
HasAlpha());
releaseTextures.release();
return true;
}
bool DMABufSurfaceRGBA::Create(
RefPtr<mozilla::gfx::FileHandleWrapper>&& aFd,
const mozilla::webgpu::ffi::WGPUDMABufInfo& aDMABufInfo, int aWidth,
int aHeight) {
LOGDMABUF("DMABufSurfaceRGBA::Create() UID %d size %d x %d\n", mUID, mWidth,
mHeight);
mWidth = aWidth;
mHeight = aHeight;
mBufferModifier = aDMABufInfo.modifier;
// TODO: Read Vulkan modifiers from DMABufFormats?
mFOURCCFormat = GBM_FORMAT_ARGB8888;
mBufferPlaneCount = aDMABufInfo.plane_count;
RefPtr<gfx::FileHandleWrapper> fd = std::move(aFd);
for (uint32_t i = 0; i < aDMABufInfo.plane_count; i++) {
mDmabufFds[i] = fd;
mStrides[i] = aDMABufInfo.strides[i];
mOffsets[i] = aDMABufInfo.offsets[i];
}
LOGDMABUF(" imported size %d x %d format %x planes %d modifiers %" PRIx64,
mWidth, mHeight, mFOURCCFormat, mBufferPlaneCount, mBufferModifier);
return true;
}
bool DMABufSurfaceRGBA::ImportSurfaceDescriptor(
const SurfaceDescriptor& aDesc) {
const SurfaceDescriptorDMABuf& desc = aDesc.get_SurfaceDescriptorDMABuf();
mFOURCCFormat = desc.fourccFormat();
mWidth = desc.width()[0];
mHeight = desc.height()[0];
mBufferPlaneCount = desc.fds().Length();
mGbmBufferFlags = desc.flags();
mBufferModifier = desc.modifier()[0];
MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES);
mUID = desc.uid();
mPID = desc.pid();
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];
mStrides[i] = desc.strides()[i];
mOffsets[i] = desc.offsets()[i];
}
if (desc.fence().Length() > 0) {
mSyncFd = desc.fence()[0];
}
if (desc.semaphoreFd()) {
mSemaphoreFd = desc.semaphoreFd();
}
if (desc.refCount().Length() > 0) {
GlobalRefCountImport(desc.refCount()[0].ClonePlatformHandle().release());
}
LOGDMABUF(" imported size %d x %d format %x planes %d", mWidth, mHeight,
mFOURCCFormat, 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<NotNull<RefPtr<gfx::FileHandleWrapper>>, 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<NotNull<RefPtr<gfx::FileHandleWrapper>>, 1> fenceFDs;
AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
LOGDMABUF("DMABufSurfaceRGBA::Serialize() UID %d\n", mUID);
width.AppendElement(mWidth);
height.AppendElement(mHeight);
modifiers.AppendElement(mBufferModifier);
for (int i = 0; i < mBufferPlaneCount; i++) {
fds.AppendElement(WrapNotNull(mDmabufFds[i]));
strides.AppendElement(mStrides[i]);
offsets.AppendElement(mOffsets[i]);
}
if (mSync && mSyncFd) {
fenceFDs.AppendElement(WrapNotNull(mSyncFd));
}
if (mGlobalRefCountFd) {
refCountFDs.AppendElement(ipc::FileDescriptor(GlobalRefCountExport()));
}
// GCC needs it (Bug 1959653).
AutoTArray<uint32_t, 1> tmp;
aOutDescriptor = SurfaceDescriptorDMABuf(
mSurfaceType, mFOURCCFormat, modifiers, mGbmBufferFlags, fds, width,
height, width, height, tmp, strides, offsets, GetYUVColorSpace(),
mColorRange, mozilla::gfx::ColorSpace2::UNKNOWN,
mozilla::gfx::TransferFunction::Default, fenceFDs, mUID,
mCanRecycle ? getpid() : 0, refCountFDs,
/* semaphoreFd */ nullptr);
return true;
}
bool DMABufSurfaceRGBA::CreateTexture(GLContext* aGLContext, int aPlane) {
if (mTexture) {
MOZ_DIAGNOSTIC_ASSERT(mGL == aGLContext);
return true;
}
LOGDMABUF("DMABufSurfaceRGBA::CreateTexture() UID %d plane %d\n", mUID,
aPlane);
if (!UseDmaBufGL(aGLContext)) {
LOGDMABUF(" UseDmaBufGL() failed");
return false;
}
mGL = aGLContext;
auto releaseTextures = MakeScopeExit([&] { ReleaseTextures(); });
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(mFOURCCFormat);
#define ADD_PLANE_ATTRIBS(plane_idx) \
{ \
attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_FD_EXT); \
attribs.AppendElement(mDmabufFds[plane_idx]->GetHandle()); \
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->MakeCurrent()) {
LOGDMABUF(
"DMABufSurfaceRGBA::CreateTexture(): failed to make GL context "
"current");
return false;
}
const auto& gle = gl::GLContextEGL::Cast(aGLContext);
const auto& egl = gle->mEgl;
MOZ_ASSERT(!mEGLImage);
mEGLImage =
egl->fCreateImage(LOCAL_EGL_NO_CONTEXT, LOCAL_EGL_LINUX_DMA_BUF_EXT,
nullptr, attribs.Elements());
if (mEGLImage == LOCAL_EGL_NO_IMAGE) {
LOGDMABUF(" EGLImageKHR creation failed, EGL error %s",
FormatEGLError(egl->mLib->fGetError()).c_str());
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);
releaseTextures.release();
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();
}
#ifdef MOZ_WAYLAND
wl_buffer* DMABufSurfaceRGBA::CreateWlBuffer() {
nsWaylandDisplay* waylandDisplay = widget::WaylandDisplayGet();
auto* dmabuf = waylandDisplay->GetDmabuf();
if (!dmabuf) {
gfxCriticalNoteOnce
<< "DMABufSurfaceRGBA::CreateWlBuffer(): Missing DMABuf support!";
return nullptr;
}
LOGDMABUF(
"DMABufSurfaceRGBA::CreateWlBuffer() UID %d format %s size [%d x %d]",
mUID, GetSurfaceTypeName(), GetWidth(), GetHeight());
struct zwp_linux_buffer_params_v1* params =
zwp_linux_dmabuf_v1_create_params(dmabuf);
LOGDMABUF(" layer [0] modifier %" PRIx64, mBufferModifier);
for (int i = 0; i < mBufferPlaneCount; i++) {
zwp_linux_buffer_params_v1_add(
params, mDmabufFds[i]->GetHandle(), i, mOffsets[i], mStrides[i],
mBufferModifier >> 32, mBufferModifier & 0xffffffff);
}
LOGDMABUF(
" zwp_linux_buffer_params_v1_create_immed() [%d x %d], fourcc [%x]",
GetWidth(), GetHeight(), GetFOURCCFormat());
wl_buffer* buffer = zwp_linux_buffer_params_v1_create_immed(
params, GetWidth(), GetHeight(), GetFOURCCFormat(), 0);
if (!buffer) {
LOGDMABUF(
" zwp_linux_buffer_params_v1_create_immed(): failed to create "
"wl_buffer!");
} else {
LOGDMABUF(" created wl_buffer [%p]", buffer);
}
zwp_linux_buffer_params_v1_destroy(params);
return buffer;
}
#endif
#ifdef MOZ_LOGGING
// 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) {
LOGDMABUFS("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 DMABufSurface without mGbmBufferObject");
return nullptr;
}
LOGDMABUF(
"DMABufSurface::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] =
GbmLib::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];
}
SyncDmaBuf(mDmabufFds[aPlane]->GetHandle(), DMA_BUF_SYNC_START);
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);
SyncDmaBuf(mDmabufFds[aPlane]->GetHandle(), DMA_BUF_SYNC_END);
GbmLib::Unmap(mGbmBufferObject[aPlane], mMappedRegionData[aPlane]);
mMappedRegion[aPlane] = nullptr;
mMappedRegionData[aPlane] = nullptr;
mMappedRegionStride[aPlane] = 0;
}
}
#endif // MOZ_LOGGING
nsresult DMABufSurface::BuildSurfaceDescriptorBuffer(
SurfaceDescriptorBuffer& aSdBuffer, Image::BuildSdbFlags aFlags,
const std::function<MemoryOrShmem(uint32_t)>& aAllocate) {
return NS_ERROR_NOT_IMPLEMENTED;
}
#ifdef MOZ_LOGGING
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;
}
void DMABufSurfaceRGBA::Clear() {
uint32_t destStride;
void* destData = Map(&destStride);
memset(destData, 0, GetHeight() * destStride);
Unmap();
}
#endif
#ifdef MOZ_LOGGING
void DMABufSurfaceRGBA::Clear(unsigned int aValue) {
uint32_t destStride;
void* destData = Map(&destStride);
unsigned int* data = (unsigned int*)destData;
for (unsigned int i = 0; i < (GetHeight() * destStride) >> 2; i++) {
*data++ = aValue;
}
Unmap();
}
#endif
bool DMABufSurfaceRGBA::HasAlpha() {
return mFOURCCFormat == GBM_FORMAT_ARGB8888 ||
mFOURCCFormat == GBM_FORMAT_ABGR8888 ||
mFOURCCFormat == GBM_FORMAT_RGBA8888 ||
mFOURCCFormat == GBM_FORMAT_BGRA8888;
}
gfx::SurfaceFormat DMABufSurfaceRGBA::GetFormat() {
switch (mFOURCCFormat) {
case GBM_FORMAT_ARGB8888:
return gfx::SurfaceFormat::B8G8R8A8;
case GBM_FORMAT_ABGR8888:
return gfx::SurfaceFormat::R8G8B8A8;
case GBM_FORMAT_BGRA8888:
return gfx::SurfaceFormat::A8R8G8B8;
case GBM_FORMAT_RGBA8888:
gfxCriticalError() << "DMABufSurfaceRGBA::GetFormat(): Unsupported "
"format GBM_FORMAT_RGBA8888";
return gfx::SurfaceFormat::UNKNOWN;
case GBM_FORMAT_XRGB8888:
return gfx::SurfaceFormat::B8G8R8X8;
case GBM_FORMAT_XBGR8888:
return gfx::SurfaceFormat::R8G8B8X8;
case GBM_FORMAT_BGRX8888:
return gfx::SurfaceFormat::X8R8G8B8;
case GBM_FORMAT_RGBX8888:
gfxCriticalError() << "DMABufSurfaceRGBA::GetFormat(): Unsupported "
"format GBM_FORMAT_RGBX8888";
return gfx::SurfaceFormat::UNKNOWN;
default:
gfxCriticalError() << "DMABufSurfaceRGBA::GetFormat(): Unknown format"
<< gfx::hexa(mFOURCCFormat);
return gfx::SurfaceFormat::UNKNOWN;
}
}
already_AddRefed<DMABufSurfaceRGBA> DMABufSurfaceRGBA::CreateDMABufSurface(
mozilla::gl::GLContext* aGLContext, int aWidth, int aHeight,
int aDMABufSurfaceFlags, RefPtr<mozilla::widget::DRMFormat> aFormat) {
RefPtr<DMABufSurfaceRGBA> surf = new DMABufSurfaceRGBA();
if (!surf->Create(aGLContext, aWidth, aHeight, aDMABufSurfaceFlags,
aFormat)) {
return nullptr;
}
return surf.forget();
}
already_AddRefed<DMABufSurface> DMABufSurfaceRGBA::CreateDMABufSurface(
RefPtr<mozilla::gfx::FileHandleWrapper>&& aFd,
const mozilla::webgpu::ffi::WGPUDMABufInfo& aDMABufInfo, int aWidth,
int aHeight) {
RefPtr<DMABufSurfaceRGBA> surf = new DMABufSurfaceRGBA();
if (!surf->Create(std::move(aFd), aDMABufInfo, aWidth, aHeight)) {
return nullptr;
}
return surf.forget();
}
already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface(
const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight) {
RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
LOGDMABUFS("[%p] DMABufSurfaceYUV::CreateYUVSurface() UID %d from desc\n",
surf.get(), surf->GetUID());
if (!surf->UpdateYUVData(aDesc, aWidth, aHeight, /* aCopy */ false)) {
return nullptr;
}
return surf.forget();
}
already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CopyYUVSurface(
const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight) {
RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
LOGDMABUFS("[%p] DMABufSurfaceYUV::CreateYUVSurfaceCopy() UID %d from desc\n",
surf.get(), surf->GetUID());
if (!surf->UpdateYUVData(aDesc, aWidth, aHeight, /* aCopy */ true)) {
return nullptr;
}
return surf.forget();
}
DMABufSurfaceYUV::DMABufSurfaceYUV()
: DMABufSurface(SURFACE_YUV),
mWidth(),
mHeight(),
mWidthAligned(),
mHeightAligned(),
mDrmFormats(),
mTexture() {
for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
mEGLImage[i] = LOCAL_EGL_NO_IMAGE;
mBufferModifiers[i] = DRM_FORMAT_MOD_INVALID;
}
}
DMABufSurfaceYUV::~DMABufSurfaceYUV() { ReleaseSurface(); }
bool DMABufSurfaceYUV::OpenFileDescriptorForPlane(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]) {
return true;
}
if (mGbmBufferObject[aPlane] == nullptr) {
LOGDMABUF(
"DMABufSurfaceYUV::OpenFileDescriptorForPlane: Missing "
"mGbmBufferObject object!");
return false;
}
auto rawFd = GbmLib::GetFd(mGbmBufferObject[aPlane]);
if (rawFd < 0) {
CloseFileDescriptors();
return false;
}
mDmabufFds[aPlane] = new gfx::FileHandleWrapper(UniqueFileHandle(rawFd));
return true;
}
bool DMABufSurfaceYUV::ImportPRIMESurfaceDescriptor(
const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight) {
LOGDMABUF("DMABufSurfaceYUV::ImportPRIMESurfaceDescriptor() UID %d FOURCC %x",
mUID, aDesc.fourcc);
// Already exists?
MOZ_DIAGNOSTIC_ASSERT(!mDmabufFds[0]);
if (aDesc.num_layers > DMABUF_BUFFER_PLANES ||
aDesc.num_objects > DMABUF_BUFFER_PLANES) {
LOGDMABUF(" Can't import, wrong layers/objects number (%d, %d)",
aDesc.num_layers, aDesc.num_objects);
return false;
}
mSurfaceType = SURFACE_YUV;
mFOURCCFormat = aDesc.fourcc;
mBufferPlaneCount = aDesc.num_layers;
for (unsigned int i = 0; i < aDesc.num_layers; i++) {
// All supported formats have 4:2:0 chroma sub-sampling.
unsigned int subsample = i == 0 ? 0 : 1;
unsigned int object = aDesc.layers[i].object_index[0];
mBufferModifiers[i] = aDesc.objects[object].drm_format_modifier;
mDrmFormats[i] = aDesc.layers[i].drm_format;
mOffsets[i] = aDesc.layers[i].offset[0];
mStrides[i] = aDesc.layers[i].pitch[0];
mWidthAligned[i] = aDesc.width >> subsample;
mHeightAligned[i] = aDesc.height >> subsample;
mWidth[i] = aWidth >> subsample;
mHeight[i] = aHeight >> subsample;
LOGDMABUF(" plane %d size %d x %d format %x", i, mWidth[i], mHeight[i],
mDrmFormats[i]);
}
return true;
}
bool DMABufSurfaceYUV::MoveYUVDataImpl(const VADRMPRIMESurfaceDescriptor& aDesc,
int aWidth, int aHeight) {
if (!ImportPRIMESurfaceDescriptor(aDesc, aWidth, aHeight)) {
return false;
}
for (unsigned int i = 0; i < aDesc.num_layers; i++) {
unsigned int object = aDesc.layers[i].object_index[0];
// Keep VADRMPRIMESurfaceDescriptor untouched and dup() dmabuf
// file descriptors.
auto rawFd = dup(aDesc.objects[object].fd);
mDmabufFds[i] = new gfx::FileHandleWrapper(UniqueFileHandle(rawFd));
}
return true;
}
void DMABufSurfaceYUV::ReleaseVADRMPRIMESurfaceDescriptor(
VADRMPRIMESurfaceDescriptor& aDesc) {
for (unsigned int i = 0; i < aDesc.num_layers; i++) {
unsigned int object = aDesc.layers[i].object_index[0];
if (aDesc.objects[object].fd != -1) {
close(aDesc.objects[object].fd);
aDesc.objects[object].fd = -1;
}
}
}
bool DMABufSurfaceYUV::CreateYUVPlaneGBM(int aPlane, DRMFormat* aFormat) {
LOGDMABUF(
"DMABufSurfaceYUV::CreateYUVPlaneGBM() UID %d size %d x %d plane %d",
mUID, mWidth[aPlane], mHeight[aPlane], aPlane);
if (!GetDMABufDevice()->GetGbmDevice()) {
LOGDMABUF(" Missing GbmDevice!");
return false;
}
MOZ_DIAGNOSTIC_ASSERT(mGbmBufferObject[aPlane] == nullptr);
if (aFormat && aFormat->UseModifiers()) {
LOGDMABUF(" Creating with modifiers from DRMFormat");
uint32_t modifiersNum = 0;
const uint64_t* modifiers = aFormat->GetModifiers(modifiersNum);
mGbmBufferObject[aPlane] = GbmLib::CreateWithModifiers2(
GetDMABufDevice()->GetGbmDevice(), mWidth[aPlane], mHeight[aPlane],
mDrmFormats[aPlane], modifiers, modifiersNum, mGbmBufferFlags);
if (mGbmBufferObject[aPlane]) {
mBufferModifiers[aPlane] = GbmLib::GetModifier(mGbmBufferObject[aPlane]);
}
} else if (mBufferModifiers[aPlane] != DRM_FORMAT_MOD_INVALID) {
LOGDMABUF(
" Creating with modifiers from DMABufSurface mBufferModifiers");
mGbmBufferObject[aPlane] = GbmLib::CreateWithModifiers2(
GetDMABufDevice()->GetGbmDevice(), mWidth[aPlane], mHeight[aPlane],
mDrmFormats[aPlane], mBufferModifiers + aPlane, 1, mGbmBufferFlags);
}
if (!mGbmBufferObject[aPlane]) {
LOGDMABUF(" Creating without modifiers");
mGbmBufferObject[aPlane] = GbmLib::Create(
GetDMABufDevice()->GetGbmDevice(), mWidth[aPlane], mHeight[aPlane],
mDrmFormats[aPlane], GBM_BO_USE_RENDERING);
mBufferModifiers[aPlane] = DRM_FORMAT_MOD_INVALID;
}
if (!mGbmBufferObject[aPlane]) {
LOGDMABUF(" Failed to create GbmBufferObject: %s", strerror(errno));
return false;
}
mStrides[aPlane] = GbmLib::GetStride(mGbmBufferObject[aPlane]);
mOffsets[aPlane] = GbmLib::GetOffset(mGbmBufferObject[aPlane], 0);
mWidthAligned[aPlane] = mWidth[aPlane];
mHeightAligned[aPlane] = mHeight[aPlane];
if (!OpenFileDescriptorForPlane(aPlane)) {
return false;
}
return true;
}
bool DMABufSurfaceYUV::CreateYUVPlaneExport(GLContext* aGLContext, int aPlane) {
LOGDMABUF(
"DMABufSurfaceYUV::CreateYUVPlaneExport() UID %d size %d x %d plane %d",
mUID, mWidth[aPlane], mHeight[aPlane], aPlane);
mGL = aGLContext;
auto releaseTextures = MakeScopeExit([&] { ReleaseTextures(); });
mGL->fGenTextures(1, &mTexture[aPlane]);
const ScopedBindTexture savedTex(mGL, mTexture[aPlane]);
GLenum internalFormat;
GLenum unpackFormat;
GLenum sizeFormat;
switch (mDrmFormats[aPlane]) {
case GBM_FORMAT_R8:
internalFormat = LOCAL_GL_R8;
unpackFormat = LOCAL_GL_RED;
sizeFormat = LOCAL_GL_UNSIGNED_BYTE;
break;
case GBM_FORMAT_GR88:
internalFormat = LOCAL_GL_RG8;
unpackFormat = LOCAL_GL_RG;
sizeFormat = LOCAL_GL_UNSIGNED_BYTE;
break;
case GBM_FORMAT_R16:
internalFormat = LOCAL_GL_R16;
unpackFormat = LOCAL_GL_RED;
sizeFormat = LOCAL_GL_UNSIGNED_SHORT;
break;
case GBM_FORMAT_GR1616:
internalFormat = LOCAL_GL_RG16;
unpackFormat = LOCAL_GL_RG;
sizeFormat = LOCAL_GL_UNSIGNED_SHORT;
break;
default:
gfxCriticalError()
<< "DMABufSurfaceYUV::CreateYUVPlaneExport(): Unsupported format";
return false;
}
GLContext::LocalErrorScope errorScope(*mGL);
mGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat, mWidth[aPlane],
mHeight[aPlane], 0, unpackFormat, sizeFormat, nullptr);
const auto err = errorScope.GetError();
if (err) {
if (err != LOCAL_GL_OUT_OF_MEMORY) {
LOGDMABUF(" failed %x error %s", err,
GLContext::GLErrorToString(err).c_str());
}
return false;
}
const auto buffer = reinterpret_cast<EGLClientBuffer>(mTexture[aPlane]);
const auto& gle = gl::GLContextEGL::Cast(mGL);
const auto& context = gle->mContext;
const auto& egl = gle->mEgl;
mEGLImage[aPlane] =
egl->fCreateImage(context, LOCAL_EGL_GL_TEXTURE_2D, buffer, nullptr);
if (mEGLImage[aPlane] == LOCAL_EGL_NO_IMAGE) {
LOGDMABUF(" EGLImageKHR creation failed, EGL error %s",
FormatEGLError(egl->mLib->fGetError()).c_str());
return false;
}
int bufferPlaneCount = 0;
if (!egl->fExportDMABUFImageQuery(mEGLImage[aPlane], mDrmFormats + aPlane,
&bufferPlaneCount,
mBufferModifiers + aPlane)) {
LOGDMABUF(" ExportDMABUFImageQueryMESA failed, quit\n");
return false;
}
if (bufferPlaneCount != 1) {
LOGDMABUF(" wrong plane count %d, quit\n", bufferPlaneCount);
return false;
}
int fds[DMABUF_BUFFER_PLANES] = {-1};
if (!egl->fExportDMABUFImage(mEGLImage[aPlane], fds, mStrides + aPlane,
mOffsets + aPlane)) {
LOGDMABUF(" ExportDMABUFImageMESA failed, quit\n");
return false;
}
mDmabufFds[aPlane] = new gfx::FileHandleWrapper(UniqueFileHandle(fds[0]));
if (!mDmabufFds[aPlane]) {
LOGDMABUF(" ExportDMABUFImageMESA failed, mDmabufFds[%d] is invalid, quit",
aPlane);
return false;
}
LOGDMABUF(" imported size %d x %d format %x planes %d modifier %" PRIx64,
mWidth[aPlane], mHeight[aPlane], mFOURCCFormat, mBufferPlaneCount,
mBufferModifiers[aPlane]);
releaseTextures.release();
return true;
}
bool DMABufSurfaceYUV::CreateYUVPlane(GLContext* aGLContext, int aPlane,
DRMFormat* aFormat) {
if (gfx::gfxVars::UseDMABufSurfaceExport()) {
if (!UseDmaBufExportExtension(aGLContext)) {
return false;
}
return CreateYUVPlaneExport(aGLContext, aPlane);
}
return CreateYUVPlaneGBM(aPlane, aFormat);
}
bool DMABufSurfaceYUV::CopyYUVDataImpl(const VADRMPRIMESurfaceDescriptor& aDesc,
int aWidth, int aHeight) {
RefPtr<DMABufSurfaceYUV> tmpSurf = CreateYUVSurface(aDesc, aWidth, aHeight);
if (!tmpSurf) {
return false;
}
if (!ImportPRIMESurfaceDescriptor(aDesc, aWidth, aHeight)) {
return false;
}
StaticMutexAutoLock lock(sSnapshotContextMutex);
RefPtr<GLContext> context = ClaimSnapshotGLContext();
auto releaseTextures = MakeScopeExit([&] {
tmpSurf->ReleaseTextures();
ReleaseTextures();
ReturnSnapshotGLContext(context);
});
for (int i = 0; i < mBufferPlaneCount; i++) {
if (!tmpSurf->CreateTexture(context, i)) {
return false;
}
if (!CreateYUVPlane(context, i) || !CreateTexture(context, i)) {
return false;
}
gfx::IntSize size(GetWidth(i), GetHeight(i));
context->BlitHelper()->BlitTextureToTexture(
tmpSurf->GetTexture(i), GetTexture(i), size, size, LOCAL_GL_TEXTURE_2D,
LOCAL_GL_TEXTURE_2D);
}
return true;
}
bool DMABufSurfaceYUV::UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc,
int aWidth, int aHeight, bool aCopy) {
LOGDMABUF("DMABufSurfaceYUV::UpdateYUVData() UID %d copy %d", mUID, aCopy);
return aCopy ? CopyYUVDataImpl(aDesc, aWidth, aHeight)
: MoveYUVDataImpl(aDesc, aWidth, aHeight);
}
bool DMABufSurfaceYUV::UpdateYUVData(
const mozilla::layers::PlanarYCbCrData& aData,
gfx::SurfaceFormat aImageFormat) {
LOGDMABUF("DMABufSurfaceYUV::UpdateYUVData() PlanarYCbCrData.");
gfx::SurfaceFormat targetFormat = GetHWFormat(aImageFormat);
if (targetFormat == gfx::SurfaceFormat::UNKNOWN) {
LOGDMABUF("DMABufSurfaceYUV::UpdateYUVData() wrong format!");
return false;
}
StaticMutexAutoLock lock(sSnapshotContextMutex);
RefPtr<GLContext> context = ClaimSnapshotGLContext();
auto releaseTextures = MakeScopeExit([&] {
ReleaseTextures();
ReturnSnapshotGLContext(context);
});
gfx::IntSize size = aData.YPictureSize();
mWidthAligned[0] = mWidth[0] = size.width;
mHeightAligned[0] = mHeight[0] = size.height;
mWidthAligned[1] = mWidth[1] = (size.width + 1) >> 1;
mHeightAligned[1] = mHeight[1] = (size.height + 1) >> 1;
mBufferPlaneCount = 2;
// We use this YUV plane for direct rendering of YUV video as wl_buffer
// for ask for scanout modifiers.
mGbmBufferFlags = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT;
switch (targetFormat) {
case gfx::SurfaceFormat::P010:
mFOURCCFormat = VA_FOURCC_P010;
mDrmFormats[0] = GBM_FORMAT_R16;
mDrmFormats[1] = GBM_FORMAT_GR1616;
break;
case gfx::SurfaceFormat::NV12:
mFOURCCFormat = VA_FOURCC_NV12;
mDrmFormats[0] = GBM_FORMAT_R8;
mDrmFormats[1] = GBM_FORMAT_GR88;
break;
default:
MOZ_DIAGNOSTIC_CRASH("Unsupported target format!");
return false;
}
auto format = GetGlobalDMABufFormats()->GetDRMFormat(mFOURCCFormat);
for (int i = 0; i < mBufferPlaneCount; i++) {
if (!CreateYUVPlane(context, i, format)) {
return false;
}
if (!CreateTexture(context, i)) {
return false;
}
}
return context->BlitHelper()->BlitYCbCrImageToDMABuf(aData, this);
}
bool DMABufSurfaceYUV::Create(const SurfaceDescriptor& aDesc) {
return ImportSurfaceDescriptor(aDesc);
}
bool DMABufSurfaceYUV::ImportSurfaceDescriptor(
const SurfaceDescriptorDMABuf& aDesc) {
mBufferPlaneCount = aDesc.fds().Length();
mSurfaceType = SURFACE_YUV;
mFOURCCFormat = aDesc.fourccFormat();
mColorSpace = aDesc.yUVColorSpace();
mColorRange = aDesc.colorRange();
mColorPrimaries = aDesc.colorPrimaries();
mTransferFunction = aDesc.transferFunction();
mGbmBufferFlags = aDesc.flags();
mUID = aDesc.uid();
mPID = aDesc.pid();
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];
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 modifier %" PRIx64, i,
mDmabufFds[i]->GetHandle(), mWidth[i], mHeight[i], mDrmFormats[i],
mBufferModifiers[i]);
}
if (aDesc.fence().Length() > 0) {
mSyncFd = aDesc.fence()[0];
}
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<NotNull<RefPtr<gfx::FileHandleWrapper>>, 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<NotNull<RefPtr<gfx::FileHandleWrapper>>, 1> fenceFDs;
AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
LOGDMABUF("DMABufSurfaceYUV::Serialize() UID %d", mUID);
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(WrapNotNull(mDmabufFds[i]));
strides.AppendElement(mStrides[i]);
offsets.AppendElement(mOffsets[i]);
modifiers.AppendElement(mBufferModifiers[i]);
}
if (mSync && mSyncFd) {
fenceFDs.AppendElement(WrapNotNull(mSyncFd));
}
if (mGlobalRefCountFd) {
refCountFDs.AppendElement(ipc::FileDescriptor(GlobalRefCountExport()));
}
aOutDescriptor = SurfaceDescriptorDMABuf(
mSurfaceType, mFOURCCFormat, modifiers, mGbmBufferFlags, fds, width,
height, widthBytes, heightBytes, format, strides, offsets,
GetYUVColorSpace(), mColorRange, mColorPrimaries, mTransferFunction,
fenceFDs, mUID, mCanRecycle ? getpid() : 0, refCountFDs,
/* semaphoreFd */ nullptr);
return true;
}
bool DMABufSurfaceYUV::CreateTexture(GLContext* aGLContext, int aPlane) {
if (mTexture[aPlane]) {
MOZ_DIAGNOSTIC_ASSERT(mGL == aGLContext);
return true;
}
LOGDMABUF("DMABufSurfaceYUV::CreateTexture() UID %d plane %d", mUID, aPlane);
if (!UseDmaBufGL(aGLContext)) {
LOGDMABUF(" UseDmaBufGL() failed");
return false;
}
MOZ_DIAGNOSTIC_ASSERT(!mGL || mGL == aGLContext);
mGL = aGLContext;
auto releaseTextures = MakeScopeExit([&] { ReleaseTextures(); });
if (!aGLContext->MakeCurrent()) {
LOGDMABUF(" Failed to make GL context current.");
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]->GetHandle()); \
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);
const auto& gle = gl::GLContextEGL::Cast(aGLContext);
const auto& egl = gle->mEgl;
mEGLImage[aPlane] =
egl->fCreateImage(LOCAL_EGL_NO_CONTEXT, LOCAL_EGL_LINUX_DMA_BUF_EXT,
nullptr, attribs.Elements());
if (mEGLImage[aPlane] == LOCAL_EGL_NO_IMAGE) {
LOGDMABUF(" EGLImageKHR creation failed, EGL error %s",
FormatEGLError(egl->mLib->fGetError()).c_str());
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]);
releaseTextures.release();
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;
}
const auto& gle = gl::GLContextEGL::Cast(mGL);
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;
}
}
mGL = nullptr;
}
bool DMABufSurfaceYUV::VerifyTextureCreation() {
LOGDMABUF("DMABufSurfaceYUV::VerifyTextureCreation() UID %d", mUID);
StaticMutexAutoLock lock(sSnapshotContextMutex);
RefPtr<GLContext> context = ClaimSnapshotGLContext();
auto release = MakeScopeExit([&] {
ReleaseTextures();
ReturnSnapshotGLContext(context);
});
for (int i = 0; i < mBufferPlaneCount; i++) {
if (!CreateTexture(context, i)) {
LOGDMABUF(" failed to create EGL image!");
return false;
}
}
LOGDMABUF(" success");
return true;
}
gfx::SurfaceFormat DMABufSurfaceYUV::GetFormat() {
switch (mFOURCCFormat) {
case VA_FOURCC_P010:
return gfx::SurfaceFormat::P010;
case VA_FOURCC_P016:
return gfx::SurfaceFormat::P016;
case VA_FOURCC_NV12:
return gfx::SurfaceFormat::NV12;
case VA_FOURCC_YV12:
case VA_FOURCC_I420:
return gfx::SurfaceFormat::YUV420;
default:
gfxCriticalNoteOnce << "DMABufSurfaceYUV::GetFormat() unknown format: "
<< mFOURCCFormat;
return gfx::SurfaceFormat::UNKNOWN;
}
}
gfx::SurfaceFormat DMABufSurfaceYUV::GetHWFormat(gfx::SurfaceFormat aSWFormat) {
switch (aSWFormat) {
case gfx::SurfaceFormat::YUV420P10:
return gfx::SurfaceFormat::P010;
case gfx::SurfaceFormat::YUV420:
return gfx::SurfaceFormat::NV12;
default:
return gfx::SurfaceFormat::UNKNOWN;
}
}
int DMABufSurfaceYUV::GetTextureCount() { return mBufferPlaneCount; }
void DMABufSurfaceYUV::ReleaseSurface() {
LOGDMABUF("DMABufSurfaceYUV::ReleaseSurface() UID %d", mUID);
ReleaseTextures();
ReleaseDMABuf();
}
nsresult DMABufSurfaceYUV::BuildSurfaceDescriptorBuffer(
SurfaceDescriptorBuffer& aSdBuffer, Image::BuildSdbFlags aFlags,
const std::function<MemoryOrShmem(uint32_t)>& aAllocate) {
LOGDMABUF("DMABufSurfaceYUV::BuildSurfaceDescriptorBuffer UID %d", mUID);
gfx::IntSize size(GetWidth(), GetHeight());
const auto format = gfx::SurfaceFormat::B8G8R8A8;
uint8_t* buffer = nullptr;
int32_t stride = 0;
nsresult rv = Image::AllocateSurfaceDescriptorBufferRgb(
size, format, buffer, aSdBuffer, stride, aAllocate);
if (NS_WARN_IF(NS_FAILED(rv))) {
LOGDMABUF("BuildSurfaceDescriptorBuffer allocate descriptor failed");
return rv;
}
if (mGL) {
return ReadIntoBuffer(mGL, buffer, stride, size, format);
} else {
// We're missing active GL context - take a snapshot one.
StaticMutexAutoLock lock(sSnapshotContextMutex);
RefPtr<GLContext> context = ClaimSnapshotGLContext();
auto releaseTextures = mozilla::MakeScopeExit([&] {
ReleaseTextures();
ReturnSnapshotGLContext(context);
});
return ReadIntoBuffer(context, buffer, stride, size, format);
}
}
#if 0
// Debugging / testing only
void DMABufSurfaceYUV::ClearPlane(int aPlane, int aValue) {
if (!MapInternal(0, 0, mWidth[aPlane], mHeight[aPlane], nullptr,
GBM_BO_TRANSFER_WRITE, aPlane)) {
return;
}
if ((int)mMappedRegionStride[aPlane] < mWidth[aPlane]) {
return;
}
unsigned short* data = (unsigned short*)mMappedRegion[aPlane];
for (unsigned int i = 0; i < (mMappedRegionStride[aPlane] * mHeight[aPlane]) >> 1; i++) {
*data++ = aValue;
}
Unmap(aPlane);
}
void DMABufSurfaceYUV::CopyPlane(int aPlane, char* aData) {
if (!MapInternal(0, 0, mWidth[aPlane], mHeight[aPlane], nullptr,
GBM_BO_TRANSFER_WRITE, aPlane)) {
return;
}
if ((int)mMappedRegionStride[aPlane] < mWidth[aPlane]) {
return;
}
/*
memcpy((char*)mMappedRegion[aPlane], aData,
mMappedRegionStride[aPlane] * mHeight[aPlane]);
*/
unsigned short* dst = (unsigned short*)mMappedRegion[aPlane];
unsigned short* src = (unsigned short*)aData;
for (unsigned int i = 0; i < (mMappedRegionStride[aPlane] * mHeight[aPlane]) >> 1; i++) {
// YUV -> P010 biteshift
*dst++ = *src++ << 6;
}
Unmap(aPlane);
}
#endif
#ifdef MOZ_WAYLAND
wl_buffer* DMABufSurfaceYUV::CreateWlBuffer() {
nsWaylandDisplay* waylandDisplay = widget::WaylandDisplayGet();
auto* dmabuf = waylandDisplay->GetDmabuf();
if (!dmabuf) {
gfxCriticalNoteOnce
<< "DMABufSurfaceYUV::CreateWlBuffer(): Missing DMABuf support!";
return nullptr;
}
LOGDMABUF(
"DMABufSurfaceYUV::CreateWlBuffer() UID %d format %s size [%d x %d]",
mUID, GetSurfaceTypeName(), GetWidth(), GetHeight());
struct zwp_linux_buffer_params_v1* params =
zwp_linux_dmabuf_v1_create_params(dmabuf);
for (int i = 0; i < GetTextureCount(); i++) {
LOGDMABUF(" layer [%d] modifier %" PRIx64, i, mBufferModifiers[i]);
zwp_linux_buffer_params_v1_add(
params, mDmabufFds[i]->GetHandle(), i, mOffsets[i], mStrides[i],
mBufferModifiers[i] >> 32, mBufferModifiers[i] & 0xffffffff);
}
// The format passed to wayland needs to be a DRM_FORMAT_* enum. These are
// largely the same as VA_FOURCC_* values except for I420/YUV420
uint32_t format = GetFOURCCFormat();
if (format == VA_FOURCC_I420) {
format = DRM_FORMAT_YUV420;
}
LOGDMABUF(
" zwp_linux_buffer_params_v1_create_immed() [%d x %d], fourcc [%x]",
GetWidth(), GetHeight(), format);
wl_buffer* buffer = zwp_linux_buffer_params_v1_create_immed(
params, GetWidth(), GetHeight(), format, 0);
if (!buffer) {
LOGDMABUF(
" zwp_linux_buffer_params_v1_create_immed(): failed to create "
"wl_buffer!");
} else {
LOGDMABUF(" created wl_buffer [%p]", buffer);
}
return buffer;
}
#endif
#if 0
void DMABufSurfaceYUV::ClearPlane(int aPlane) {
if (!MapInternal(0, 0, mWidth[aPlane], mHeight[aPlane], nullptr,
GBM_BO_TRANSFER_WRITE, aPlane)) {
return;
}
if ((int)mMappedRegionStride[aPlane] < mWidth[aPlane]) {
return;
}
memset((char*)mMappedRegion[aPlane], 0,
mMappedRegionStride[aPlane] * mHeight[aPlane]);
Unmap(aPlane);
}
# include "gfxUtils.h"
void DMABufSurfaceYUV::DumpToFile(const char* aFile) {
RefPtr<gfx::DataSourceSurface> surf = GetAsSourceSurface();
gfxUtils::WriteAsPNG(surf, aFile);
}
#endif