blob: a29c9c67a7a370516a09151261d140af2c39d09f [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/gl/gl_image_native_pixmap.h"
#include <vector>
#include "base/files/scoped_file.h"
#include "base/stl_util.h"
#include "build/build_config.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/gpu_fence.h"
#include "ui/gl/egl_util.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_enums.h"
#include "ui/gl/gl_surface_egl.h"
#define FOURCC(a, b, c, d) \
((static_cast<uint32_t>(a)) | (static_cast<uint32_t>(b) << 8) | \
(static_cast<uint32_t>(c) << 16) | (static_cast<uint32_t>(d) << 24))
#define DRM_FORMAT_R8 FOURCC('R', '8', ' ', ' ')
#define DRM_FORMAT_R16 FOURCC('R', '1', '6', ' ')
#define DRM_FORMAT_GR88 FOURCC('G', 'R', '8', '8')
#define DRM_FORMAT_RGB565 FOURCC('R', 'G', '1', '6')
#define DRM_FORMAT_ARGB8888 FOURCC('A', 'R', '2', '4')
#define DRM_FORMAT_ABGR8888 FOURCC('A', 'B', '2', '4')
#define DRM_FORMAT_XRGB8888 FOURCC('X', 'R', '2', '4')
#define DRM_FORMAT_XBGR8888 FOURCC('X', 'B', '2', '4')
#define DRM_FORMAT_ABGR2101010 FOURCC('A', 'B', '3', '0')
#define DRM_FORMAT_YVU420 FOURCC('Y', 'V', '1', '2')
#define DRM_FORMAT_NV12 FOURCC('N', 'V', '1', '2')
namespace gl {
namespace {
// Returns corresponding internalformat if supported, and GL_NONE otherwise.
unsigned GetInternalFormatFromFormat(gfx::BufferFormat format) {
switch (format) {
case gfx::BufferFormat::R_8:
return GL_RED_EXT;
case gfx::BufferFormat::R_16:
return GL_R16_EXT;
case gfx::BufferFormat::RG_88:
return GL_RG_EXT;
case gfx::BufferFormat::BGR_565:
case gfx::BufferFormat::RGBX_8888:
case gfx::BufferFormat::BGRX_8888:
return GL_RGB;
case gfx::BufferFormat::RGBA_8888:
return GL_RGBA;
case gfx::BufferFormat::RGBX_1010102:
return GL_RGB10_A2_EXT;
case gfx::BufferFormat::BGRA_8888:
return GL_BGRA_EXT;
case gfx::BufferFormat::YVU_420:
return GL_RGB_YCRCB_420_CHROMIUM;
case gfx::BufferFormat::YUV_420_BIPLANAR:
return GL_RGB_YCBCR_420V_CHROMIUM;
case gfx::BufferFormat::RGBA_4444:
case gfx::BufferFormat::BGRX_1010102:
case gfx::BufferFormat::RGBA_F16:
case gfx::BufferFormat::UYVY_422:
return GL_NONE;
}
NOTREACHED();
return GL_NONE;
}
EGLint FourCC(gfx::BufferFormat format) {
switch (format) {
case gfx::BufferFormat::R_8:
return DRM_FORMAT_R8;
case gfx::BufferFormat::R_16:
return DRM_FORMAT_R16;
case gfx::BufferFormat::RG_88:
return DRM_FORMAT_GR88;
case gfx::BufferFormat::BGR_565:
return DRM_FORMAT_RGB565;
case gfx::BufferFormat::RGBA_8888:
return DRM_FORMAT_ABGR8888;
case gfx::BufferFormat::RGBX_8888:
return DRM_FORMAT_XBGR8888;
case gfx::BufferFormat::BGRA_8888:
return DRM_FORMAT_ARGB8888;
case gfx::BufferFormat::BGRX_8888:
return DRM_FORMAT_XRGB8888;
case gfx::BufferFormat::RGBX_1010102:
// We should use here DRM_FORMAT_XBGR2101010 format, but EGL on Intel
// doesn't support it for scanout, see https://crbug.com/776093#c14.
return DRM_FORMAT_ABGR2101010;
case gfx::BufferFormat::YVU_420:
return DRM_FORMAT_YVU420;
case gfx::BufferFormat::YUV_420_BIPLANAR:
return DRM_FORMAT_NV12;
case gfx::BufferFormat::RGBA_4444:
case gfx::BufferFormat::BGRX_1010102:
case gfx::BufferFormat::RGBA_F16:
case gfx::BufferFormat::UYVY_422:
NOTREACHED();
return 0;
}
NOTREACHED();
return 0;
}
gfx::BufferFormat GetBufferFormatFromFourCCFormat(int format) {
switch (format) {
case DRM_FORMAT_R8:
return gfx::BufferFormat::R_8;
case DRM_FORMAT_GR88:
return gfx::BufferFormat::RG_88;
case DRM_FORMAT_ABGR8888:
return gfx::BufferFormat::RGBA_8888;
case DRM_FORMAT_XBGR8888:
return gfx::BufferFormat::RGBX_8888;
case DRM_FORMAT_ARGB8888:
return gfx::BufferFormat::BGRA_8888;
case DRM_FORMAT_XRGB8888:
return gfx::BufferFormat::BGRX_8888;
case DRM_FORMAT_ABGR2101010:
// We should support DRM_FORMAT_XBGR2101010 format instead, but EGL on
// Intel doesn't support it for scanout, see https://crbug.com/776093#c14.
return gfx::BufferFormat::RGBX_1010102;
case DRM_FORMAT_RGB565:
return gfx::BufferFormat::BGR_565;
case DRM_FORMAT_NV12:
return gfx::BufferFormat::YUV_420_BIPLANAR;
case DRM_FORMAT_YVU420:
return gfx::BufferFormat::YVU_420;
default:
NOTREACHED();
return gfx::BufferFormat::BGRA_8888;
}
}
} // namespace
GLImageNativePixmap::GLImageNativePixmap(const gfx::Size& size,
gfx::BufferFormat format)
: GLImageEGL(size),
format_(format),
has_image_flush_external_(
gl::GLSurfaceEGL::HasEGLExtension("EGL_EXT_image_flush_external")),
has_image_dma_buf_export_(
gl::GLSurfaceEGL::HasEGLExtension("EGL_MESA_image_dma_buf_export")) {}
GLImageNativePixmap::~GLImageNativePixmap() {}
bool GLImageNativePixmap::Initialize(gfx::NativePixmap* pixmap) {
DCHECK(!pixmap_);
if (GetInternalFormatFromFormat(format_) == GL_NONE) {
LOG(ERROR) << "Unsupported format: " << gfx::BufferFormatToString(format_);
return false;
}
if (pixmap->AreDmaBufFdsValid()) {
// Note: If eglCreateImageKHR is successful for a EGL_LINUX_DMA_BUF_EXT
// target, the EGL will take a reference to the dma_buf.
std::vector<EGLint> attrs;
attrs.push_back(EGL_WIDTH);
attrs.push_back(size_.width());
attrs.push_back(EGL_HEIGHT);
attrs.push_back(size_.height());
attrs.push_back(EGL_LINUX_DRM_FOURCC_EXT);
attrs.push_back(FourCC(format_));
const EGLint kLinuxDrmModifiers[] = {EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT};
bool has_dma_buf_import_modifier = gl::GLSurfaceEGL::HasEGLExtension(
"EGL_EXT_image_dma_buf_import_modifiers");
for (size_t attrs_plane = 0;
attrs_plane <
gfx::NumberOfPlanesForBufferFormat(pixmap->GetBufferFormat());
++attrs_plane) {
attrs.push_back(EGL_DMA_BUF_PLANE0_FD_EXT + attrs_plane * 3);
size_t pixmap_plane = attrs_plane;
attrs.push_back(pixmap->GetDmaBufFd(
pixmap_plane < pixmap->GetDmaBufFdCount() ? pixmap_plane : 0));
attrs.push_back(EGL_DMA_BUF_PLANE0_OFFSET_EXT + attrs_plane * 3);
attrs.push_back(pixmap->GetDmaBufOffset(pixmap_plane));
attrs.push_back(EGL_DMA_BUF_PLANE0_PITCH_EXT + attrs_plane * 3);
attrs.push_back(pixmap->GetDmaBufPitch(pixmap_plane));
if (has_dma_buf_import_modifier &&
pixmap->GetDmaBufModifier(0) != gfx::NativePixmapPlane::kNoModifier) {
uint64_t modifier = pixmap->GetDmaBufModifier(pixmap_plane);
DCHECK(attrs_plane < base::size(kLinuxDrmModifiers));
attrs.push_back(kLinuxDrmModifiers[attrs_plane]);
attrs.push_back(modifier & 0xffffffff);
attrs.push_back(kLinuxDrmModifiers[attrs_plane] + 1);
attrs.push_back(static_cast<uint32_t>(modifier >> 32));
}
}
attrs.push_back(EGL_NONE);
if (!GLImageEGL::Initialize(EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
static_cast<EGLClientBuffer>(nullptr),
&attrs[0])) {
return false;
}
}
pixmap_ = pixmap;
return true;
}
bool GLImageNativePixmap::InitializeFromTexture(uint32_t texture_id) {
if (GetInternalFormatFromFormat(format_) == GL_NONE) {
LOG(ERROR) << "Unsupported format: " << gfx::BufferFormatToString(format_);
return false;
}
GLContext* current_context = GLContext::GetCurrent();
if (!current_context || !current_context->IsCurrent(nullptr)) {
LOG(ERROR) << "No gl context bound to the current thread";
return false;
}
EGLContext context_handle =
reinterpret_cast<EGLContext>(current_context->GetHandle());
DCHECK_NE(context_handle, EGL_NO_CONTEXT);
return GLImageEGL::Initialize(context_handle, EGL_GL_TEXTURE_2D_KHR,
reinterpret_cast<EGLClientBuffer>(texture_id),
nullptr);
}
gfx::NativePixmapHandle GLImageNativePixmap::ExportHandle() {
DCHECK(!pixmap_);
DCHECK(thread_checker_.CalledOnValidThread());
// Must use GLImageEGL::Initialize.
if (egl_image_ == EGL_NO_IMAGE_KHR) {
LOG(ERROR) << "GLImageEGL is not initialized";
return gfx::NativePixmapHandle();
}
if (!has_image_dma_buf_export_) {
LOG(ERROR) << "Error no extension EGL_MESA_image_dma_buf_export";
return gfx::NativePixmapHandle();
}
int fourcc = 0;
int num_planes = 0;
EGLuint64KHR modifiers = 0;
if (!eglExportDMABUFImageQueryMESA(GLSurfaceEGL::GetHardwareDisplay(),
egl_image_, &fourcc, &num_planes,
&modifiers)) {
LOG(ERROR) << "Error querying EGLImage: " << ui::GetLastEGLErrorString();
return gfx::NativePixmapHandle();
}
if (num_planes <= 0 || num_planes > 4) {
LOG(ERROR) << "Invalid number of planes: " << num_planes;
return gfx::NativePixmapHandle();
}
gfx::BufferFormat format = GetBufferFormatFromFourCCFormat(fourcc);
if (num_planes > 0 && static_cast<size_t>(num_planes) !=
gfx::NumberOfPlanesForBufferFormat(format)) {
LOG(ERROR) << "Invalid number of planes: " << num_planes
<< " for format: " << gfx::BufferFormatToString(format);
return gfx::NativePixmapHandle();
}
if (format != format_) {
// A driver has returned a format different than what has been requested.
// This can happen if RGBX is implemented using RGBA. Otherwise there is
// a real mistake from the user and we have to fail.
if (GetInternalFormat() == GL_RGB &&
format != gfx::BufferFormat::RGBA_8888) {
LOG(ERROR) << "Invalid driver format: "
<< gfx::BufferFormatToString(format)
<< " for requested format: "
<< gfx::BufferFormatToString(format_);
return gfx::NativePixmapHandle();
}
}
#if defined(OS_FUCHSIA)
// TODO(crbug.com/852011): Implement image handle export on Fuchsia.
NOTIMPLEMENTED();
return gfx::NativePixmapHandle();
#else // defined(OS_FUCHSIA)
std::vector<int> fds(num_planes);
std::vector<EGLint> strides(num_planes);
std::vector<EGLint> offsets(num_planes);
// It is specified for eglExportDMABUFImageMESA that the app is responsible
// for closing any fds retrieved.
if (!eglExportDMABUFImageMESA(GLSurfaceEGL::GetHardwareDisplay(), egl_image_,
&fds[0], &strides[0], &offsets[0])) {
LOG(ERROR) << "Error exporting EGLImage: " << ui::GetLastEGLErrorString();
return gfx::NativePixmapHandle();
}
gfx::NativePixmapHandle handle;
for (int i = 0; i < num_planes; ++i) {
// Sanity check. In principle all the fds are meant to be valid when
// eglExportDMABUFImageMESA succeeds.
base::ScopedFD scoped_fd(fds[i]);
if (!scoped_fd.is_valid()) {
LOG(ERROR) << "Invalid dmabuf";
return gfx::NativePixmapHandle();
}
// scoped_fd.release() transfers ownership to the caller so it will not
// call close when going out of scope. base::FileDescriptor never closes
// the fd when going out of scope. The auto_close flag is just a hint for
// the user. When true it means the user has ownership of it so they are
// responsible for closing the fd.
handle.fds.emplace_back(
base::FileDescriptor(scoped_fd.release(), true /* auto_close */));
handle.planes.emplace_back(strides[i], offsets[i], 0 /* size opaque */,
modifiers);
}
return handle;
#endif // !defined(OS_FUCHSIA)
}
unsigned GLImageNativePixmap::GetInternalFormat() {
return GetInternalFormatFromFormat(format_);
}
bool GLImageNativePixmap::CopyTexImage(unsigned target) {
if (egl_image_ != EGL_NO_IMAGE_KHR)
return false;
// Pass-through image type fails to bind and copy; make sure we
// don't draw with uninitialized texture.
std::vector<unsigned char> data(size_.width() * size_.height() * 4);
glTexImage2D(target, 0, GL_RGBA, size_.width(), size_.height(), 0, GL_RGBA,
GL_UNSIGNED_BYTE, data.data());
return true;
}
bool GLImageNativePixmap::CopyTexSubImage(unsigned target,
const gfx::Point& offset,
const gfx::Rect& rect) {
return false;
}
bool GLImageNativePixmap::ScheduleOverlayPlane(
gfx::AcceleratedWidget widget,
int z_order,
gfx::OverlayTransform transform,
const gfx::Rect& bounds_rect,
const gfx::RectF& crop_rect,
bool enable_blend,
std::unique_ptr<gfx::GpuFence> gpu_fence) {
DCHECK(pixmap_);
return pixmap_->ScheduleOverlayPlane(widget, z_order, transform, bounds_rect,
crop_rect, enable_blend,
std::move(gpu_fence));
}
void GLImageNativePixmap::Flush() {
if (!has_image_flush_external_)
return;
EGLDisplay display = gl::GLSurfaceEGL::GetHardwareDisplay();
const EGLAttrib attribs[] = {
EGL_NONE,
};
if (!eglImageFlushExternalEXT(display, egl_image_, attribs)) {
// TODO(reveman): Investigate why we're hitting the following log
// statement on ARM devices. b/63364517
DLOG(WARNING) << "Failed to flush rendering";
return;
}
}
void GLImageNativePixmap::OnMemoryDump(
base::trace_event::ProcessMemoryDump* pmd,
uint64_t process_tracing_id,
const std::string& dump_name) {
// TODO(ericrk): Implement GLImage OnMemoryDump. crbug.com/514914
}
} // namespace gl