// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/ozone/platform/drm/gpu/gbm_surface_factory.h"
#include <gbm.h>
#include <xf86drm.h>
#include <memory>
#include <utility>
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/not_fatal_until.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "third_party/khronos/EGL/egl.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/extension_set.h"
#include "ui/gfx/linux/drm_util_linux.h"
#include "ui/gfx/linux/gbm_defines.h"
#include "ui/gfx/linux/scoped_gbm_device.h"
#include "ui/gfx/native_pixmap.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/presenter.h"
#include "ui/ozone/common/egl_util.h"
#include "ui/ozone/common/gl_ozone_egl.h"
#include "ui/ozone/common/native_pixmap_egl_binding.h"
#include "ui/ozone/platform/drm/common/drm_util.h"
#include "ui/ozone/platform/drm/common/scoped_drm_types.h"
#include "ui/ozone/platform/drm/gpu/drm_gpu_util.h"
#include "ui/ozone/platform/drm/gpu/drm_thread_proxy.h"
#include "ui/ozone/platform/drm/gpu/drm_window_proxy.h"
#include "ui/ozone/platform/drm/gpu/gbm_overlay_surface.h"
#include "ui/ozone/platform/drm/gpu/gbm_pixmap.h"
#include "ui/ozone/platform/drm/gpu/gbm_surfaceless.h"
#include "ui/ozone/platform/drm/gpu/proxy_helpers.h"
#include "ui/ozone/platform/drm/gpu/screen_manager.h"
#include "ui/ozone/public/surface_ozone_canvas.h"
#if BUILDFLAG(ENABLE_VULKAN)
#include "gpu/vulkan/vulkan_function_pointers.h"
#include "ui/ozone/platform/drm/gpu/vulkan_implementation_gbm.h"
#define VK_STRUCTURE_TYPE_DMA_BUF_IMAGE_CREATE_INFO_INTEL 1024
typedef struct VkDmaBufImageCreateInfo_ {
VkStructureType sType;
raw_ptr<const void> pNext;
int fd;
VkFormat format;
VkExtent3D extent;
uint32_t strideInBytes;
} VkDmaBufImageCreateInfo;
typedef VkResult(VKAPI_PTR* PFN_vkCreateDmaBufImageINTEL)(
VkDevice device,
const VkDmaBufImageCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDeviceMemory* pMem,
VkImage* pImage);
#endif
namespace ui {
class ScopedAllowBlockingForGbmSurface : public base::ScopedAllowBlocking {};
namespace {
EGLDeviceEXT GetPreferredEGLDevice() {
std::vector<EGLDeviceEXT> devices(DRM_MAX_MINOR, EGL_NO_DEVICE_EXT);
EGLint num_devices = 0;
eglQueryDevicesEXT(DRM_MAX_MINOR, devices.data(), &num_devices);
devices.resize(num_devices);
std::map<EGLDeviceEXT, std::string> device_drivers;
for (EGLDeviceEXT device : devices) {
const char* filename =
eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT);
if (!filename) // Not a DRM device.
continue;
const auto driver_name = GetDrmDriverNameFromPath(filename);
if (driver_name)
device_drivers.insert({device, driver_name.value()});
}
// Find the device with the most preferred driver.
const auto preferred_drivers = GetPreferredDrmDrivers();
for (const auto* preferred_driver : preferred_drivers) {
for (EGLDeviceEXT device : devices) {
const auto driver = device_drivers.find(device);
if (driver != device_drivers.end() &&
driver->second == preferred_driver) {
return device;
}
}
}
// Fall back to the first device.
if (!devices.empty())
return devices[0];
return EGL_NO_DEVICE_EXT;
}
class GLOzoneEGLGbm : public GLOzoneEGL {
public:
GLOzoneEGLGbm(GbmSurfaceFactory* surface_factory,
DrmThreadProxy* drm_thread_proxy)
: surface_factory_(surface_factory),
drm_thread_proxy_(drm_thread_proxy) {}
GLOzoneEGLGbm(const GLOzoneEGLGbm&) = delete;
GLOzoneEGLGbm& operator=(const GLOzoneEGLGbm&) = delete;
~GLOzoneEGLGbm() override = default;
bool CanImportNativePixmap(gfx::BufferFormat format) override {
if (!gl::GLSurfaceEGL::GetGLDisplayEGL()
->ext->b_EGL_EXT_image_dma_buf_import) {
return false;
}
return NativePixmapEGLBinding::IsBufferFormatSupported(format);
}
std::unique_ptr<NativePixmapGLBinding> ImportNativePixmap(
scoped_refptr<gfx::NativePixmap> pixmap,
gfx::BufferFormat plane_format,
gfx::BufferPlane plane,
gfx::Size plane_size,
const gfx::ColorSpace& color_space,
GLenum target,
GLuint texture_id) override {
return NativePixmapEGLBinding::Create(pixmap, plane_format, plane,
plane_size, color_space, target,
texture_id);
}
scoped_refptr<gl::GLSurface> CreateViewGLSurface(
gl::GLDisplay* display,
gfx::AcceleratedWidget window) override {
return nullptr;
}
scoped_refptr<gl::Presenter> CreateSurfacelessViewGLSurface(
gl::GLDisplay* display,
gfx::AcceleratedWidget window) override {
return base::MakeRefCounted<GbmSurfaceless>(
surface_factory_, display->GetAs<gl::GLDisplayEGL>(),
drm_thread_proxy_->CreateDrmWindowProxy(window), window);
}
scoped_refptr<gl::GLSurface> CreateOffscreenGLSurface(
gl::GLDisplay* display,
const gfx::Size& size) override {
DCHECK_EQ(size.width(), 0);
DCHECK_EQ(size.height(), 0);
return gl::InitializeGLSurface(
new gl::SurfacelessEGL(display->GetAs<gl::GLDisplayEGL>(), size));
}
protected:
gl::EGLDisplayPlatform GetNativeDisplay() override {
if (native_display_.Valid())
return native_display_;
// Default to null platform
native_display_ = gl::EGLDisplayPlatform(EGL_DEFAULT_DISPLAY);
if (gl::g_driver_egl.client_ext.b_EGL_MESA_platform_surfaceless) {
native_display_ = gl::EGLDisplayPlatform(EGL_DEFAULT_DISPLAY,
EGL_PLATFORM_SURFACELESS_MESA);
}
if (!(gl::g_driver_egl.client_ext.b_EGL_EXT_device_query &&
gl::g_driver_egl.client_ext.b_EGL_EXT_platform_device &&
gl::g_driver_egl.client_ext.b_EGL_EXT_device_enumeration)) {
LOG(WARNING) << "Platform device extensions not found.";
return native_display_;
}
const EGLDeviceEXT preferred_device = GetPreferredEGLDevice();
if (preferred_device != EGL_NO_DEVICE_EXT) {
native_display_ = gl::EGLDisplayPlatform(
reinterpret_cast<EGLNativeDisplayType>(preferred_device),
EGL_PLATFORM_DEVICE_EXT);
}
return native_display_;
}
bool LoadGLES2Bindings(const gl::GLImplementationParts& impl) override {
return LoadDefaultEGLGLES2Bindings(impl);
}
private:
raw_ptr<GbmSurfaceFactory> surface_factory_;
raw_ptr<DrmThreadProxy> drm_thread_proxy_;
gl::EGLDisplayPlatform native_display_;
};
std::vector<gfx::BufferFormat> EnumerateSupportedBufferFormatsForTexturing() {
std::vector<gfx::BufferFormat> supported_buffer_formats;
// We cannot use FileEnumerator here because the sandbox is already closed.
constexpr char kRenderNodeFilePattern[] = "/dev/dri/renderD%d";
for (int i = 128; /* end on first card# that does not exist */; i++) {
base::FilePath dev_path(FILE_PATH_LITERAL(
base::StringPrintf(kRenderNodeFilePattern, i).c_str()));
ScopedAllowBlockingForGbmSurface scoped_allow_blocking;
base::File dev_path_file(dev_path,
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!dev_path_file.IsValid())
break;
// Skip the virtual graphics memory manager device.
ScopedDrmVersionPtr version(drmGetVersion(dev_path_file.GetPlatformFile()));
if (!version || base::EqualsCaseInsensitiveASCII(version->name, "vgem")) {
continue;
}
ScopedGbmDevice device(gbm_create_device(dev_path_file.GetPlatformFile()));
if (!device) {
LOG(ERROR) << "Couldn't create Gbm Device at " << dev_path.MaybeAsASCII();
continue;
}
VLOG(1) << "Found Gbm Device at " << dev_path.MaybeAsASCII();
for (int j = 0; j <= static_cast<int>(gfx::BufferFormat::LAST); ++j) {
const gfx::BufferFormat buffer_format = static_cast<gfx::BufferFormat>(j);
if (base::Contains(supported_buffer_formats, buffer_format))
continue;
if (gbm_device_is_format_supported(
device.get(), GetFourCCFormatFromBufferFormat(buffer_format),
GBM_BO_USE_TEXTURING)) {
supported_buffer_formats.push_back(buffer_format);
}
}
}
return supported_buffer_formats;
}
void OnNativePixmapCreated(GbmSurfaceFactory::NativePixmapCallback callback,
base::WeakPtr<GbmSurfaceFactory> weak_ptr,
std::unique_ptr<GbmBuffer> buffer,
scoped_refptr<DrmFramebuffer> framebuffer) {
if (!weak_ptr || !buffer) {
std::move(callback).Run(nullptr);
} else {
std::move(callback).Run(base::MakeRefCounted<GbmPixmap>(
weak_ptr.get(), std::move(buffer), std::move(framebuffer)));
}
}
} // namespace
GbmSurfaceFactory::GbmSurfaceFactory(DrmThreadProxy* drm_thread_proxy)
: egl_implementation_(
std::make_unique<GLOzoneEGLGbm>(this, drm_thread_proxy)),
drm_thread_proxy_(drm_thread_proxy) {}
GbmSurfaceFactory::~GbmSurfaceFactory() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void GbmSurfaceFactory::RegisterSurface(gfx::AcceleratedWidget widget,
GbmSurfaceless* surface) {
DCHECK(thread_checker_.CalledOnValidThread());
widget_to_surface_map_.emplace(widget, surface);
}
void GbmSurfaceFactory::UnregisterSurface(gfx::AcceleratedWidget widget) {
DCHECK(thread_checker_.CalledOnValidThread());
widget_to_surface_map_.erase(widget);
}
GbmSurfaceless* GbmSurfaceFactory::GetSurface(
gfx::AcceleratedWidget widget) const {
DCHECK(thread_checker_.CalledOnValidThread());
auto it = widget_to_surface_map_.find(widget);
CHECK(it != widget_to_surface_map_.end(), base::NotFatalUntil::M130);
return it->second;
}
std::vector<gl::GLImplementationParts>
GbmSurfaceFactory::GetAllowedGLImplementations() {
DCHECK(thread_checker_.CalledOnValidThread());
return std::vector<gl::GLImplementationParts>{
gl::GLImplementationParts(gl::kGLImplementationEGLANGLE),
gl::GLImplementationParts(gl::ANGLEImplementation::kSwiftShader)};
}
GLOzone* GbmSurfaceFactory::GetGLOzone(
const gl::GLImplementationParts& implementation) {
switch (implementation.gl) {
case gl::kGLImplementationEGLGLES2:
case gl::kGLImplementationEGLANGLE:
return egl_implementation_.get();
default:
return nullptr;
}
}
#if BUILDFLAG(ENABLE_VULKAN)
std::unique_ptr<gpu::VulkanImplementation>
GbmSurfaceFactory::CreateVulkanImplementation(bool use_swiftshader,
bool allow_protected_memory) {
DCHECK(!use_swiftshader)
<< "Vulkan Swiftshader is not supported on this platform.";
return std::make_unique<VulkanImplementationGbm>(allow_protected_memory);
}
scoped_refptr<gfx::NativePixmap> GbmSurfaceFactory::CreateNativePixmapForVulkan(
gfx::AcceleratedWidget widget,
gfx::Size size,
gfx::BufferFormat format,
gfx::BufferUsage usage,
VkDevice vk_device,
VkDeviceMemory* vk_device_memory,
VkImage* vk_image) {
std::unique_ptr<GbmBuffer> buffer;
scoped_refptr<DrmFramebuffer> framebuffer;
drm_thread_proxy_->CreateBuffer(widget, size, /*framebuffer_size=*/size,
format, usage, GbmPixmap::kFlagNoModifiers,
&buffer, &framebuffer);
if (!buffer)
return nullptr;
PFN_vkCreateDmaBufImageINTEL create_dma_buf_image_intel =
reinterpret_cast<PFN_vkCreateDmaBufImageINTEL>(
vkGetDeviceProcAddr(vk_device, "vkCreateDmaBufImageINTEL"));
if (!create_dma_buf_image_intel) {
LOG(ERROR) << "Scanout buffers can only be imported into vulkan when "
"vkCreateDmaBufImageINTEL is available.";
return nullptr;
}
DCHECK(buffer->AreFdsValid());
DCHECK_EQ(buffer->GetNumPlanes(), 1U);
base::ScopedFD vk_image_fd(dup(buffer->GetPlaneFd(0)));
DCHECK(vk_image_fd.is_valid());
// TODO(spang): Fix this for formats other than gfx::BufferFormat::BGRA_8888
DCHECK_EQ(format, display::DisplaySnapshot::PrimaryFormat());
VkFormat vk_format = VK_FORMAT_B8G8R8A8_SRGB;
VkDmaBufImageCreateInfo dma_buf_image_create_info = {
/* .sType = */ static_cast<VkStructureType>(
VK_STRUCTURE_TYPE_DMA_BUF_IMAGE_CREATE_INFO_INTEL),
/* .pNext = */ nullptr,
/* .fd = */ vk_image_fd.release(),
/* .format = */ vk_format,
/* .extent = */
{
/* .width = */ static_cast<uint32_t>(size.width()),
/* .height = */ static_cast<uint32_t>(size.height()),
/* .depth = */ 1,
},
/* .strideInBytes = */ buffer->GetPlaneStride(0),
};
VkResult result =
create_dma_buf_image_intel(vk_device, &dma_buf_image_create_info, nullptr,
vk_device_memory, vk_image);
if (result != VK_SUCCESS) {
LOG(ERROR) << "Failed to create a Vulkan image from a dmabuf.";
return nullptr;
}
return base::MakeRefCounted<GbmPixmap>(this, std::move(buffer),
std::move(framebuffer));
}
#endif
std::unique_ptr<OverlaySurface> GbmSurfaceFactory::CreateOverlaySurface(
gfx::AcceleratedWidget window) {
return std::make_unique<GbmOverlaySurface>(
drm_thread_proxy_->CreateDrmWindowProxy(window));
}
std::unique_ptr<SurfaceOzoneCanvas> GbmSurfaceFactory::CreateCanvasForWidget(
gfx::AcceleratedWidget widget) {
DCHECK(thread_checker_.CalledOnValidThread());
LOG(ERROR) << "Software rendering mode is not supported with GBM platform";
return nullptr;
}
scoped_refptr<gfx::NativePixmap> GbmSurfaceFactory::CreateNativePixmap(
gfx::AcceleratedWidget widget,
gpu::VulkanDeviceQueue* device_queue,
gfx::Size size,
gfx::BufferFormat format,
gfx::BufferUsage usage,
std::optional<gfx::Size> framebuffer_size) {
if (framebuffer_size &&
!gfx::Rect(size).Contains(gfx::Rect(*framebuffer_size))) {
return nullptr;
}
std::unique_ptr<GbmBuffer> buffer;
scoped_refptr<DrmFramebuffer> framebuffer;
drm_thread_proxy_->CreateBuffer(
widget, size, framebuffer_size ? *framebuffer_size : size, format, usage,
0 /* flags */, &buffer, &framebuffer);
if (!buffer)
return nullptr;
return base::MakeRefCounted<GbmPixmap>(this, std::move(buffer),
std::move(framebuffer));
}
void GbmSurfaceFactory::CreateNativePixmapAsync(
gfx::AcceleratedWidget widget,
gpu::VulkanDeviceQueue* device_queue,
gfx::Size size,
gfx::BufferFormat format,
gfx::BufferUsage usage,
NativePixmapCallback callback) {
drm_thread_proxy_->CreateBufferAsync(
widget, size, format, usage, 0 /* flags */,
base::BindOnce(OnNativePixmapCreated, std::move(callback),
weak_factory_.GetWeakPtr()));
}
scoped_refptr<gfx::NativePixmap>
GbmSurfaceFactory::CreateNativePixmapFromHandleInternal(
gfx::AcceleratedWidget widget,
gfx::Size size,
gfx::BufferFormat format,
gfx::NativePixmapHandle handle) {
if (handle.planes.size() > GBM_MAX_PLANES) {
return nullptr;
}
std::unique_ptr<GbmBuffer> buffer;
scoped_refptr<DrmFramebuffer> framebuffer;
drm_thread_proxy_->CreateBufferFromHandle(
widget, size, format, std::move(handle), &buffer, &framebuffer);
if (!buffer)
return nullptr;
return base::MakeRefCounted<GbmPixmap>(this, std::move(buffer),
std::move(framebuffer));
}
scoped_refptr<gfx::NativePixmap>
GbmSurfaceFactory::CreateNativePixmapFromHandle(
gfx::AcceleratedWidget widget,
gfx::Size size,
gfx::BufferFormat format,
gfx::NativePixmapHandle handle) {
// Query the external service (if available), whether it recognizes this
// NativePixmapHandle, and whether it can provide a corresponding NativePixmap
// backing it. If so, the handle is consumed. Otherwise, the handle remains
// valid and can be further importer by standard means.
if (!get_protected_native_pixmap_callback_.is_null()) {
auto protected_pixmap = get_protected_native_pixmap_callback_.Run(handle);
if (protected_pixmap)
return protected_pixmap;
}
return CreateNativePixmapFromHandleInternal(widget, size, format,
std::move(handle));
}
scoped_refptr<gfx::NativePixmap>
GbmSurfaceFactory::CreateNativePixmapForProtectedBufferHandle(
gfx::AcceleratedWidget widget,
gfx::Size size,
gfx::BufferFormat format,
gfx::NativePixmapHandle handle) {
// Create a new NativePixmap without querying the external service for any
// existing mappings.
return CreateNativePixmapFromHandleInternal(widget, size, format,
std::move(handle));
}
bool GbmSurfaceFactory::SupportsDrmModifiersFilter() const {
return true;
}
void GbmSurfaceFactory::SetDrmModifiersFilter(
std::unique_ptr<DrmModifiersFilter> filter) {
drm_thread_proxy_->SetDrmModifiersFilter(std::move(filter));
}
void GbmSurfaceFactory::SetGetProtectedNativePixmapDelegate(
const GetProtectedNativePixmapCallback&
get_protected_native_pixmap_callback) {
get_protected_native_pixmap_callback_ = get_protected_native_pixmap_callback;
}
std::vector<gfx::BufferFormat>
GbmSurfaceFactory::GetSupportedFormatsForTexturing() const {
return EnumerateSupportedBufferFormatsForTexturing();
}
} // namespace ui