blob: 933abf033940ebde72f747ebd2e1f6557c056de9 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/exo/wayland/clients/client_base.h"
#include <aura-shell-client-protocol.h>
#include <chrome-color-management-client-protocol.h>
#include <fcntl.h>
#include <fullscreen-shell-unstable-v1-client-protocol.h>
#include <linux-dmabuf-unstable-v1-client-protocol.h>
#include <linux-explicit-synchronization-unstable-v1-client-protocol.h>
#include <presentation-time-client-protocol.h>
#include <stylus-unstable-v2-client-protocol.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>
#include <memory>
#include <string>
#include <utility>
#include "base/bits.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/platform_shared_memory_region.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/shared_memory_mapper.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/posix/eintr_wrapper.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/unguessable_token.h"
#include "skia/ext/legacy_display_globals.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrDirectContext.h"
#include "third_party/skia/include/gpu/gl/GrGLAssembleInterface.h"
#include "third_party/skia/include/gpu/gl/GrGLInterface.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_enums.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/init/gl_factory.h"
#if defined(USE_GBM)
#include <drm_fourcc.h>
#include <gbm.h>
#include <xf86drm.h>
#if defined(USE_VULKAN)
#include "gpu/vulkan/init/vulkan_factory.h"
#include "gpu/vulkan/vulkan_function_pointers.h"
#endif
#include "ui/ozone/public/ozone_platform.h" // nogncheck
#endif
namespace exo {
namespace wayland {
namespace clients {
namespace switches {
// Specifies the client buffer size.
const char kSize[] = "size";
// Specifies the client scale factor (ie. number of physical pixels per DIP).
const char kScale[] = "scale";
// Specifies the client transform (ie. rotation).
const char kTransform[] = "transform";
// Specifies if the background should be transparent.
const char kTransparentBackground[] = "transparent-background";
// Use drm buffer instead of shared memory.
const char kUseDrm[] = "use-drm";
// Use memfd backed buffer instead of shared memory.
const char kUseMemfd[] = "use-memfd";
// Specifies if client should be fullscreen.
const char kFullscreen[] = "fullscreen";
// Specifies if client should y-invert the dmabuf surfaces.
const char kYInvert[] = "y-invert";
// Specifies if client should use xdg or zxdg_v6 or rely on wl_shell.
const char kXdg[] = "xdg";
// In cases where we can't easily change the environment variables, such as tast
// tests, this flag specifies the socket to pass to wl_display_connect.
//
// When attaching to exo, this will usually be: /var/run/chrome/wayland-0
const char kWaylandSocket[] = "wayland_socket";
} // namespace switches
namespace {
// Buffer format.
const int32_t kShmFormat = WL_SHM_FORMAT_ARGB8888;
const SkColorType kColorType = kBGRA_8888_SkColorType;
#if defined(USE_GBM)
const GLenum kSizedInternalFormat = GL_BGRA8_EXT;
#endif
const size_t kBytesPerPixel = 4;
#if defined(USE_GBM)
// DRI render node path template.
const char kDriRenderNodeTemplate[] = "/dev/dri/renderD%u";
#endif
ClientBase* CastToClientBase(void* data) {
return static_cast<ClientBase*>(data);
}
class MemfdMemoryMapping : public base::SharedMemoryMapping {
public:
MemfdMemoryMapping(base::span<uint8_t> mapped_span)
: base::SharedMemoryMapping(
mapped_span,
mapped_span.size(),
base::UnguessableToken::Create(),
base::SharedMemoryMapper::GetDefaultInstance()) {}
};
void BufferRelease(void* data, wl_buffer* /* buffer */) {
ClientBase::Buffer* buffer = static_cast<ClientBase::Buffer*>(data);
buffer->busy = false;
}
wl_buffer_listener g_buffer_listener = {BufferRelease};
#if defined(USE_GBM)
#if defined(USE_VULKAN)
uint32_t VulkanChooseGraphicsQueueFamily(VkPhysicalDevice device) {
uint32_t properties_number = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &properties_number, nullptr);
std::vector<VkQueueFamilyProperties> properties(properties_number);
vkGetPhysicalDeviceQueueFamilyProperties(device, &properties_number,
properties.data());
// Choose the first graphics queue.
for (uint32_t i = 0; i < properties_number; ++i) {
if ((properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) {
DCHECK_GT(properties[i].queueCount, 0u);
return i;
}
}
return UINT32_MAX;
}
std::unique_ptr<ScopedVkInstance> CreateVkInstance() {
VkApplicationInfo application_info{
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = nullptr,
.applicationVersion = 0,
.pEngineName = nullptr,
.engineVersion = 0,
.apiVersion = VK_MAKE_VERSION(1, 0, 0),
};
VkInstanceCreateInfo create_info{
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.flags = 0,
.pApplicationInfo = &application_info,
.enabledLayerCount = 0,
.ppEnabledLayerNames = nullptr,
.enabledExtensionCount = 0,
.ppEnabledExtensionNames = nullptr,
};
std::unique_ptr<ScopedVkInstance> vk_instance(new ScopedVkInstance());
VkResult result = vkCreateInstance(
&create_info, nullptr, ScopedVkInstance::Receiver(*vk_instance).get());
CHECK_EQ(VK_SUCCESS, result)
<< "Failed to create a Vulkan instance. Do you have an ICD "
"driver (e.g: "
"/usr/share/vulkan/icd.d/intel_icd.x86_64.json). Does it "
"point to a valid .so? Try to set export VK_LOADER_DEBUG=all "
"for more debuggining info.";
return vk_instance;
}
std::unique_ptr<ScopedVkDevice> CreateVkDevice(VkInstance vk_instance,
uint32_t* queue_family_index) {
uint32_t physical_devices_number = 1;
VkPhysicalDevice physical_device;
VkResult result = vkEnumeratePhysicalDevices(
vk_instance, &physical_devices_number, &physical_device);
CHECK(result == VK_SUCCESS || result == VK_INCOMPLETE)
<< "Failed to enumerate physical devices.";
*queue_family_index = VulkanChooseGraphicsQueueFamily(physical_device);
CHECK_NE(UINT32_MAX, *queue_family_index);
float priority = 1.0f;
VkDeviceQueueCreateInfo device_queue_create_info{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = *queue_family_index,
.queueCount = 1,
.pQueuePriorities = &priority,
};
VkDeviceCreateInfo device_create_info{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &device_queue_create_info,
};
std::unique_ptr<ScopedVkDevice> vk_device(new ScopedVkDevice());
result = vkCreateDevice(physical_device, &device_create_info, nullptr,
ScopedVkDevice::Receiver(*vk_device).get());
CHECK_EQ(VK_SUCCESS, result);
return vk_device;
}
std::unique_ptr<ScopedVkRenderPass> CreateVkRenderPass(VkDevice vk_device) {
VkAttachmentDescription attach_description[]{
{
.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
.samples = static_cast<VkSampleCountFlagBits>(1),
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
},
};
VkAttachmentReference attachment_reference[]{
{
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
},
};
VkSubpassDescription subpass_description[]{
{
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = 1,
.pColorAttachments = attachment_reference,
},
};
VkRenderPassCreateInfo render_pass_create_info{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = attach_description,
.subpassCount = 1,
.pSubpasses = subpass_description,
};
std::unique_ptr<ScopedVkRenderPass> vk_render_pass(
new ScopedVkRenderPass(VK_NULL_HANDLE, {vk_device}));
VkResult result =
vkCreateRenderPass(vk_device, &render_pass_create_info, nullptr,
ScopedVkRenderPass::Receiver(*vk_render_pass).get());
CHECK_EQ(VK_SUCCESS, result);
return vk_render_pass;
}
std::unique_ptr<ScopedVkCommandPool> CreateVkCommandPool(
VkDevice vk_device,
uint32_t queue_family_index) {
VkCommandPoolCreateInfo command_pool_create_info{
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = queue_family_index,
};
std::unique_ptr<ScopedVkCommandPool> vk_command_pool(
new ScopedVkCommandPool(VK_NULL_HANDLE, {vk_device}));
VkResult result = vkCreateCommandPool(
vk_device, &command_pool_create_info, nullptr,
ScopedVkCommandPool::Receiver(*vk_command_pool).get());
CHECK_EQ(VK_SUCCESS, result);
return vk_command_pool;
}
#endif // defined(USE_VULKAN)
#endif // defined(USE_GBM)
} // namespace
////////////////////////////////////////////////////////////////////////////////
// ClientBase::InitParams, public:
ClientBase::InitParams::InitParams() {
#if defined(USE_GBM)
drm_format = DRM_FORMAT_ARGB8888;
bo_usage = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING | GBM_BO_USE_TEXTURING;
#endif
}
ClientBase::InitParams::~InitParams() {}
ClientBase::InitParams::InitParams(const InitParams& params) = default;
bool ClientBase::InitParams::FromCommandLine(
const base::CommandLine& command_line) {
if (command_line.HasSwitch(switches::kSize)) {
std::string size_str = command_line.GetSwitchValueASCII(switches::kSize);
if (sscanf(size_str.c_str(), "%zdx%zd", &width, &height) != 2) {
LOG(ERROR) << "Invalid value for " << switches::kSize;
return false;
}
}
if (command_line.HasSwitch(switches::kScale) &&
!base::StringToInt(command_line.GetSwitchValueASCII(switches::kScale),
&scale)) {
LOG(ERROR) << "Invalid value for " << switches::kScale;
return false;
}
if (command_line.HasSwitch(switches::kTransform)) {
std::string transform_str =
command_line.GetSwitchValueASCII(switches::kTransform);
if (transform_str == "0") {
transform = WL_OUTPUT_TRANSFORM_NORMAL;
} else if (transform_str == "90") {
transform = WL_OUTPUT_TRANSFORM_90;
} else if (transform_str == "180") {
transform = WL_OUTPUT_TRANSFORM_180;
} else if (transform_str == "270") {
transform = WL_OUTPUT_TRANSFORM_270;
} else {
LOG(ERROR) << "Invalid value for " << switches::kTransform;
return false;
}
has_transform = true;
}
use_drm = command_line.HasSwitch(switches::kUseDrm);
if (use_drm)
use_drm_value = command_line.GetSwitchValueASCII(switches::kUseDrm);
use_memfd = command_line.HasSwitch(switches::kUseMemfd);
fullscreen = command_line.HasSwitch(switches::kFullscreen);
transparent_background =
command_line.HasSwitch(switches::kTransparentBackground);
y_invert = command_line.HasSwitch(switches::kYInvert);
use_xdg = command_line.HasSwitch(switches::kXdg);
if (command_line.HasSwitch(switches::kWaylandSocket))
wayland_socket = command_line.GetSwitchValueASCII(switches::kWaylandSocket);
return true;
}
////////////////////////////////////////////////////////////////////////////////
// ClientBase::Buffer, public:
ClientBase::Buffer::Buffer() {}
ClientBase::Buffer::~Buffer() {}
////////////////////////////////////////////////////////////////////////////////
// ClientBase, public:
bool ClientBase::Init(const InitParams& params) {
size_.SetSize(params.width, params.height);
scale_ = params.scale;
transform_ = params.transform;
switch (params.transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
case WL_OUTPUT_TRANSFORM_180:
surface_size_.SetSize(params.width, params.height);
break;
case WL_OUTPUT_TRANSFORM_90:
case WL_OUTPUT_TRANSFORM_270:
surface_size_.SetSize(params.height, params.width);
break;
default:
NOTREACHED();
break;
}
surface_size_ = gfx::ToCeiledSize(
gfx::ScaleSize(gfx::SizeF(surface_size_), 1.0f / params.scale));
fullscreen_ = params.fullscreen;
transparent_background_ = params.transparent_background;
y_invert_ = params.y_invert;
has_transform_ = params.has_transform;
display_.reset(wl_display_connect(
params.wayland_socket ? params.wayland_socket->c_str() : nullptr));
if (!display_) {
LOG(ERROR) << "wl_display_connect failed";
return false;
}
base::flat_map<std::string, uint32_t> requested_versions;
requested_versions[zwp_linux_dmabuf_v1_interface.name] =
params.linux_dmabuf_version;
globals_.Init(display_.get(), std::move(requested_versions));
if (!globals_.compositor) {
LOG(ERROR) << "Can't find compositor interface";
return false;
}
if (!globals_.shm) {
LOG(ERROR) << "Can't find shm interface";
return false;
}
if (!globals_.presentation) {
LOG(ERROR) << "Can't find presentation interface";
return false;
}
if (params.use_drm && !globals_.linux_dmabuf) {
LOG(ERROR) << "Can't find linux_dmabuf interface";
return false;
}
if (!globals_.seat) {
LOG(ERROR) << "Can't find seat interface";
return false;
}
#if defined(USE_GBM)
if (params.use_drm) {
static struct zwp_linux_dmabuf_v1_listener kLinuxDmabufListener = {
.format =
[](void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
uint32_t format) {
CastToClientBase(data)->HandleDmabufFormat(
data, zwp_linux_dmabuf_v1, format);
},
.modifier =
[](void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) {
CastToClientBase(data)->HandleDmabufModifier(
data, zwp_linux_dmabuf_v1, format, modifier_hi, modifier_lo);
}};
zwp_linux_dmabuf_v1_add_listener(globals_.linux_dmabuf.get(),
&kLinuxDmabufListener, this);
wl_display_roundtrip(display_.get());
// Number of files to look for when discovering DRM devices.
const uint32_t kDrmMaxMinor = 15;
const uint32_t kRenderNodeStart = 128;
const uint32_t kRenderNodeEnd = kRenderNodeStart + kDrmMaxMinor + 1;
for (uint32_t i = kRenderNodeStart; i < kRenderNodeEnd; i++) {
std::string dri_render_node(
base::StringPrintf(kDriRenderNodeTemplate, i));
base::ScopedFD drm_fd(open(dri_render_node.c_str(), O_RDWR));
if (drm_fd.get() < 0)
continue;
drmVersionPtr drm_version = drmGetVersion(drm_fd.get());
if (!drm_version) {
LOG(ERROR) << "Can't get version for device: '" << dri_render_node
<< "'";
return false;
}
// We can't actually use the virtual GEM, so discard it like we do in CrOS
if (base::EqualsCaseInsensitiveASCII("vgem", drm_version->name))
continue;
if (strstr(drm_version->name, params.use_drm_value.c_str())) {
drm_fd_ = std::move(drm_fd);
break;
}
}
if (drm_fd_.get() < 0) {
LOG_IF(ERROR, params.use_drm)
<< "Can't find drm device: '" << params.use_drm_value << "'";
LOG_IF(ERROR, !params.use_drm) << "Can't find drm device";
return false;
}
device_.reset(gbm_create_device(drm_fd_.get()));
if (!device_) {
LOG(ERROR) << "Can't create gbm device";
return false;
}
ui::OzonePlatform::InitParams ozone_params;
ozone_params.single_process = true;
ui::OzonePlatform::InitializeForGPU(ozone_params);
egl_display_ = static_cast<gl::GLDisplayEGL*>(gl::init::InitializeGLOneOff(
/*gpu_preference=*/gl::GpuPreference::kDefault));
DCHECK(egl_display_);
gl_surface_ = gl::init::CreateOffscreenGLSurface(egl_display_, gfx::Size());
gl_context_ =
gl::init::CreateGLContext(nullptr, // share_group
gl_surface_.get(), gl::GLContextAttribs());
make_current_ = std::make_unique<ui::ScopedMakeCurrent>(gl_context_.get(),
gl_surface_.get());
if (egl_display_->ext->b_EGL_ARM_implicit_external_sync) {
egl_sync_type_ = EGL_SYNC_FENCE_KHR;
}
if (egl_display_->ext->b_EGL_ANDROID_native_fence_sync) {
egl_sync_type_ = EGL_SYNC_NATIVE_FENCE_ANDROID;
}
sk_sp<const GrGLInterface> native_interface = GrGLMakeAssembledInterface(
nullptr,
[](void* ctx, const char name[]) { return eglGetProcAddress(name); });
DCHECK(native_interface);
gr_context_ = GrDirectContext::MakeGL(std::move(native_interface));
DCHECK(gr_context_);
#if defined(USE_VULKAN)
if (params.use_vulkan) {
vk_implementation_ = gpu::CreateVulkanImplementation();
CHECK(vk_implementation_) << "Can't create VulkanImplementation";
bool ret = vk_implementation_->InitializeVulkanInstance(false);
CHECK(ret) << "Failed to initialize VulkanImplementation";
vk_instance_ = CreateVkInstance();
uint32_t queue_family_index = UINT32_MAX;
vk_device_ = CreateVkDevice(vk_instance_->get(), &queue_family_index);
CHECK(gpu::GetVulkanFunctionPointers()->BindDeviceFunctionPointers(
vk_device_->get(), VK_VERSION_1_0, gfx::ExtensionSet()));
vk_render_pass_ = CreateVkRenderPass(vk_device_->get());
vkGetDeviceQueue(vk_device_->get(), queue_family_index, 0, &vk_queue_);
vk_command_pool_ =
CreateVkCommandPool(vk_device_->get(), queue_family_index);
}
#endif // defined(USE_VULKAN)
}
#endif // defined(USE_GBM)
surface_.reset(static_cast<wl_surface*>(
wl_compositor_create_surface(globals_.compositor.get())));
if (!surface_) {
LOG(ERROR) << "Can't create surface";
return false;
}
if (!transparent_background_) {
std::unique_ptr<wl_region> opaque_region(static_cast<wl_region*>(
wl_compositor_create_region(globals_.compositor.get())));
if (!opaque_region) {
LOG(ERROR) << "Can't create region";
return false;
}
wl_region_add(opaque_region.get(), 0, 0, size_.width(), size_.height());
wl_surface_set_opaque_region(surface_.get(), opaque_region.get());
}
use_memfd_ = params.use_memfd;
if (params.allocate_buffers_with_output_mode) {
static wl_output_listener kOutputListener = {
[](void* data, struct wl_output* wl_output, int32_t x, int32_t y,
int32_t physical_width, int32_t physical_height, int32_t subpixel,
const char* make, const char* model, int32_t transform) {
CastToClientBase(data)->HandleGeometry(
data, wl_output, x, y, physical_width, physical_height, subpixel,
make, model, transform);
},
[](void* data, struct wl_output* wl_output, uint32_t flags,
int32_t width, int32_t height, int32_t refresh) {
CastToClientBase(data)->HandleMode(data, wl_output, flags, width,
height, refresh);
},
[](void* data, struct wl_output* wl_output) {
CastToClientBase(data)->HandleDone(data, wl_output);
},
[](void* data, struct wl_output* wl_output, int32_t factor) {
CastToClientBase(data)->HandleScale(data, wl_output, factor);
}};
wl_output_add_listener(globals_.output.get(), &kOutputListener, this);
} else {
for (size_t i = 0; i < params.num_buffers; ++i) {
auto buffer =
CreateBuffer(size_, params.drm_format, params.bo_usage,
/*add_buffer_listener=*/!params.use_release_fences);
if (!buffer) {
LOG(ERROR) << "Failed to create buffer";
return false;
}
buffers_.push_back(std::move(buffer));
}
for (size_t i = 0; i < buffers_.size(); ++i) {
// If the buffer handle doesn't exist, we would either be killed by the
// server or die here.
if (!buffers_[i]->buffer) {
LOG(ERROR) << "buffer handle uninitialized.";
return false;
}
}
}
if (params.use_fullscreen_shell) {
zwp_fullscreen_shell_v1_present_surface(globals_.fullscreen_shell.get(),
surface_.get(), 0, nullptr);
} else if (!params.use_xdg) {
if (!globals_.shell) {
LOG(ERROR) << "Can't find shell interface";
return false;
}
std::unique_ptr<wl_shell_surface> shell_surface(
static_cast<wl_shell_surface*>(
wl_shell_get_shell_surface(globals_.shell.get(), surface_.get())));
if (!shell_surface) {
LOG(ERROR) << "Can't get shell surface";
return false;
}
wl_shell_surface_set_title(shell_surface.get(), params.title.c_str());
SetupAuraShellIfAvailable();
if (fullscreen_) {
wl_shell_surface_set_fullscreen(
shell_surface.get(), WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, 0,
nullptr);
} else {
wl_shell_surface_set_toplevel(shell_surface.get());
}
} else {
if (!globals_.xdg_wm_base) {
// use zxdg
if (!globals_.xdg_shell_v6) {
LOG(ERROR) << "Can't find xdg_shell or zxdg_shell_v6 interface";
return false;
}
zxdg_surface_.reset(zxdg_shell_v6_get_xdg_surface(
globals_.xdg_shell_v6.get(), surface_.get()));
if (!zxdg_surface_) {
LOG(ERROR) << "Can't get zxdg surface";
return false;
}
static const zxdg_surface_v6_listener zxdg_surface_v6_listener = {
[](void* data, struct zxdg_surface_v6* zxdg_surface_v6,
uint32_t layout_mode) {
zxdg_surface_v6_ack_configure(zxdg_surface_v6, layout_mode);
},
};
zxdg_surface_v6_add_listener(zxdg_surface_.get(),
&zxdg_surface_v6_listener, this);
zxdg_toplevel_.reset(zxdg_surface_v6_get_toplevel(zxdg_surface_.get()));
if (!zxdg_toplevel_) {
LOG(ERROR) << "Can't get zxdg toplevel";
return false;
}
static const zxdg_toplevel_v6_listener zxdg_toplevel_v6_listener = {
[](void* data, struct zxdg_toplevel_v6* zxdg_toplevel_v6,
int32_t width, int32_t height, struct wl_array* states) {},
[](void* data, struct zxdg_toplevel_v6* zxdg_toplevel_v6) {}};
zxdg_toplevel_v6_add_listener(zxdg_toplevel_.get(),
&zxdg_toplevel_v6_listener, this);
} else {
// use xdg
xdg_surface_.reset(xdg_wm_base_get_xdg_surface(globals_.xdg_wm_base.get(),
surface_.get()));
if (!xdg_surface_) {
LOG(ERROR) << "Can't get xdg surface";
return false;
}
static const xdg_surface_listener xdg_surface_listener = {
[](void* data, struct xdg_surface* xdg_surface,
uint32_t layout_mode) {
xdg_surface_ack_configure(xdg_surface, layout_mode);
},
};
xdg_surface_add_listener(xdg_surface_.get(), &xdg_surface_listener, this);
xdg_toplevel_.reset(xdg_surface_get_toplevel(xdg_surface_.get()));
if (!xdg_toplevel_) {
LOG(ERROR) << "Can't get xdg toplevel";
return false;
}
static const xdg_toplevel_listener xdg_toplevel_listener = {
[](void* data, struct xdg_toplevel* xdg_toplevel, int32_t width,
int32_t height, struct wl_array* states) {},
[](void* data, struct xdg_toplevel* xdg_toplevel) {}};
xdg_toplevel_add_listener(xdg_toplevel_.get(), &xdg_toplevel_listener,
this);
}
if (fullscreen_) {
LOG(ERROR) << "full screen not supported yet.";
return false;
}
SetupAuraShellIfAvailable();
wl_surface_commit(surface_.get());
wl_display_flush(display_.get());
}
if (params.use_touch) {
static wl_touch_listener kTouchListener = {
[](void* data, struct wl_touch* wl_touch, uint32_t serial,
uint32_t time, struct wl_surface* surface, int32_t id, wl_fixed_t x,
wl_fixed_t y) {
CastToClientBase(data)->HandleDown(data, wl_touch, serial, time,
surface, id, x, y);
},
[](void* data, struct wl_touch* wl_touch, uint32_t serial,
uint32_t time, int32_t id) {
CastToClientBase(data)->HandleUp(data, wl_touch, serial, time, id);
},
[](void* data, struct wl_touch* wl_touch, uint32_t time, int32_t id,
wl_fixed_t x, wl_fixed_t y) {
CastToClientBase(data)->HandleMotion(data, wl_touch, time, id, x, y);
},
[](void* data, struct wl_touch* wl_touch) {
CastToClientBase(data)->HandleFrame(data, wl_touch);
},
[](void* data, struct wl_touch* wl_touch) {
CastToClientBase(data)->HandleCancel(data, wl_touch);
},
[](void* data, struct wl_touch* wl_touch, int32_t id, wl_fixed_t major,
wl_fixed_t minor) {
CastToClientBase(data)->HandleShape(data, wl_touch, id, major, minor);
},
[](void* data, struct wl_touch* wl_touch, int32_t id,
wl_fixed_t orientation) {
CastToClientBase(data)->HandleOrientation(data, wl_touch, id,
orientation);
}};
wl_touch* touch = wl_seat_get_touch(globals_.seat.get());
wl_touch_add_listener(touch, &kTouchListener, this);
}
if (params.use_stylus)
SetupPointerStylus();
return true;
}
////////////////////////////////////////////////////////////////////////////////
// ClientBase, protected:
ClientBase::ClientBase() {}
ClientBase::~ClientBase() {
make_current_ = nullptr;
gl_context_ = nullptr;
gl_surface_ = nullptr;
#if defined(USE_GBM)
if (egl_display_) {
gl::init::ShutdownGL(egl_display_, false);
}
#endif
}
////////////////////////////////////////////////////////////////////////////////
// wl_touch_listener
void ClientBase::HandleDown(void* data,
struct wl_touch* wl_touch,
uint32_t serial,
uint32_t time,
struct wl_surface* surface,
int32_t id,
wl_fixed_t x,
wl_fixed_t y) {}
void ClientBase::HandleUp(void* data,
struct wl_touch* wl_touch,
uint32_t serial,
uint32_t time,
int32_t id) {}
void ClientBase::HandleMotion(void* data,
struct wl_touch* wl_touch,
uint32_t time,
int32_t id,
wl_fixed_t x,
wl_fixed_t y) {}
void ClientBase::HandleFrame(void* data, struct wl_touch* wl_touch) {}
void ClientBase::HandleCancel(void* data, struct wl_touch* wl_touch) {}
void ClientBase::HandleShape(void* data,
struct wl_touch* wl_touch,
int32_t id,
wl_fixed_t major,
wl_fixed_t minor) {}
void ClientBase::HandleOrientation(void* data,
struct wl_touch* wl_touch,
int32_t id,
wl_fixed_t orientation) {}
////////////////////////////////////////////////////////////////////////////////
// wl_output_listener
void ClientBase::HandleGeometry(void* data,
struct wl_output* wl_output,
int32_t x,
int32_t y,
int32_t physical_width,
int32_t physical_height,
int32_t subpixel,
const char* make,
const char* model,
int32_t transform) {
if (has_transform_)
return;
// |transform| describes the display transform. In order to take advantage of
// hardware overlays, content needs to be rotated in the opposite direction to
// show right-side up on the display.
switch (transform) {
case WL_OUTPUT_TRANSFORM_90:
transform_ = WL_OUTPUT_TRANSFORM_270;
break;
case WL_OUTPUT_TRANSFORM_270:
transform_ = WL_OUTPUT_TRANSFORM_90;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
transform_ = WL_OUTPUT_TRANSFORM_FLIPPED_270;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
transform_ = WL_OUTPUT_TRANSFORM_FLIPPED_90;
break;
default:
transform_ = transform;
}
}
void ClientBase::HandleMode(void* data,
struct wl_output* wl_output,
uint32_t flags,
int32_t width,
int32_t height,
int32_t refresh) {}
void ClientBase::HandleDone(void* data, struct wl_output* wl_output) {}
void ClientBase::HandleScale(void* data,
struct wl_output* wl_output,
int32_t factor) {}
////////////////////////////////////////////////////////////////////////////////
// zwp_linux_dmabuf_v1_listener
void ClientBase::HandleDmabufFormat(
void* data,
struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
uint32_t format) {}
void ClientBase::HandleDmabufModifier(
void* data,
struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
uint32_t format,
uint32_t modifier_hi,
uint32_t modifier_lo) {}
////////////////////////////////////////////////////////////////////////////////
// zaura_output_listener
void ClientBase::HandleInsets(const gfx::Insets& insets) {}
void ClientBase::HandleLogicalTransform(int32_t transform) {}
////////////////////////////////////////////////////////////////////////////////
// helper functions
std::unique_ptr<ClientBase::Buffer> ClientBase::CreateBuffer(
const gfx::Size& size,
int32_t drm_format,
int32_t bo_usage,
bool add_buffer_listener) {
std::unique_ptr<Buffer> buffer;
#if defined(USE_GBM)
if (device_) {
buffer = CreateDrmBuffer(size, drm_format, nullptr, 0, bo_usage, y_invert_);
CHECK(buffer) << "Can't create drm buffer";
}
#endif
if (!buffer) {
buffer = std::make_unique<Buffer>();
size_t stride = size.width() * kBytesPerPixel;
size_t length = size.height() * stride;
uint8_t* mapped_data;
if (use_memfd_) {
// udmabuf_create requires a page aligned buffer.
length = base::bits::AlignUp(length,
base::checked_cast<size_t>(getpagesize()));
int memfd = memfd_create("memfd", MFD_ALLOW_SEALING);
if (memfd < 0) {
PLOG(ERROR) << "memfd_create failed";
return nullptr;
}
// Truncate the chunk of memory to be page aligned so that the server
// has the option of using the shared memory region as a dma-buf through
// udmabuf_create.
int res = HANDLE_EINTR(ftruncate(memfd, length));
if (res < 0) {
PLOG(ERROR) << "ftruncate failed";
return nullptr;
}
// Seal the fd with F_SEAL_SHRINK so that the server has the option of
// using the shared memory region as a dma-buf through udmabuf_create.
if (fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK) < 0) {
PLOG(ERROR) << "Failed to seal memfd";
return nullptr;
}
mapped_data = static_cast<uint8_t*>(
mmap(nullptr, length, PROT_WRITE | PROT_READ, MAP_SHARED, memfd, 0));
if (mapped_data == MAP_FAILED) {
PLOG(ERROR) << "Failed to mmap";
return nullptr;
}
base::span<uint8_t> mapped_span = base::make_span(mapped_data, length);
buffer->shared_memory_mapping = MemfdMemoryMapping(mapped_span);
buffer->shm_pool.reset(
wl_shm_create_pool(globals_.shm.get(), memfd, length));
close(memfd);
} else {
base::UnsafeSharedMemoryRegion shared_memory_region =
base::UnsafeSharedMemoryRegion::Create(length);
if (!shared_memory_region.IsValid()) {
LOG(ERROR) << "Shared Memory Region is not valid";
return nullptr;
}
base::WritableSharedMemoryMapping map = shared_memory_region.Map();
if (!map.IsValid()) {
LOG(ERROR) << "WritableSharedMemoryMapping is not valid";
return nullptr;
}
mapped_data = map.GetMemoryAs<uint8_t>();
buffer->shared_memory_mapping = std::move(map);
base::subtle::PlatformSharedMemoryRegion platform_shared_memory =
base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
std::move(shared_memory_region));
buffer->shm_pool.reset(wl_shm_create_pool(
globals_.shm.get(), platform_shared_memory.GetPlatformHandle().fd,
buffer->shared_memory_mapping.size()));
}
buffer->buffer.reset(static_cast<wl_buffer*>(
wl_shm_pool_create_buffer(buffer->shm_pool.get(), 0, size.width(),
size.height(), stride, kShmFormat)));
if (!buffer->buffer) {
LOG(ERROR) << "Can't create buffer";
return nullptr;
}
SkSurfaceProps props = skia::LegacyDisplayGlobals::GetSkSurfaceProps();
buffer->sk_surface = SkSurface::MakeRasterDirect(
SkImageInfo::Make(size.width(), size.height(), kColorType,
kOpaque_SkAlphaType),
mapped_data, stride, &props);
DCHECK(buffer->sk_surface);
}
if (add_buffer_listener) {
wl_buffer_add_listener(buffer->buffer.get(), &g_buffer_listener,
buffer.get());
}
return buffer;
}
std::unique_ptr<ClientBase::Buffer> ClientBase::CreateDrmBuffer(
const gfx::Size& size,
int32_t drm_format,
const uint64_t* drm_modifiers,
const unsigned int drm_modifiers_count,
int32_t bo_usage,
bool y_invert) {
std::unique_ptr<Buffer> buffer;
#if defined(USE_GBM)
if (device_) {
if (!gbm_device_is_format_supported(device_.get(), drm_format, bo_usage)) {
LOG(ERROR) << "Format/usage not supported";
return nullptr;
}
buffer = std::make_unique<Buffer>();
if (drm_modifiers_count == 0) {
buffer->bo.reset(gbm_bo_create(device_.get(), size.width(), size.height(),
drm_format, bo_usage));
} else {
buffer->bo.reset(gbm_bo_create_with_modifiers(
device_.get(), size.width(), size.height(), drm_format, drm_modifiers,
drm_modifiers_count));
}
if (!buffer->bo) {
LOG(ERROR) << "Can't create gbm buffer";
return nullptr;
}
base::ScopedFD fd(gbm_bo_get_plane_fd(buffer->bo.get(), 0));
buffer->params.reset(
zwp_linux_dmabuf_v1_create_params(globals_.linux_dmabuf.get()));
uint64_t modifier = gbm_bo_get_modifier(buffer->bo.get());
for (size_t i = 0;
i < static_cast<size_t>(gbm_bo_get_plane_count(buffer->bo.get()));
++i) {
base::ScopedFD plane_i_fd(gbm_bo_get_plane_fd(buffer->bo.get(), i));
uint32_t stride = gbm_bo_get_stride_for_plane(buffer->bo.get(), i);
uint32_t offset = gbm_bo_get_offset(buffer->bo.get(), i);
zwp_linux_buffer_params_v1_add(buffer->params.get(), plane_i_fd.get(), i,
offset, stride, modifier >> 32, modifier);
}
uint32_t flags = 0;
if (y_invert)
flags |= ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT;
buffer->buffer.reset(zwp_linux_buffer_params_v1_create_immed(
buffer->params.get(), size.width(), size.height(), drm_format, flags));
if (gbm_bo_get_plane_count(buffer->bo.get()) != 1)
return buffer;
EGLint khr_image_attrs[] = {
EGL_DMA_BUF_PLANE0_FD_EXT,
fd.get(),
EGL_WIDTH,
size.width(),
EGL_HEIGHT,
size.height(),
EGL_LINUX_DRM_FOURCC_EXT,
drm_format,
EGL_DMA_BUF_PLANE0_PITCH_EXT,
static_cast<EGLint>(gbm_bo_get_stride_for_plane(buffer->bo.get(), 0)),
EGL_DMA_BUF_PLANE0_OFFSET_EXT,
0,
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
static_cast<EGLint>(modifier),
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
static_cast<EGLint>(modifier >> 32),
EGL_NONE};
EGLImageKHR image = eglCreateImageKHR(
eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
nullptr /* no client buffer */, khr_image_attrs);
buffer->egl_image = std::make_unique<ScopedEglImage>(image);
GLuint texture = 0;
glGenTextures(1, &texture);
buffer->texture = std::make_unique<ScopedTexture>(texture);
glBindTexture(GL_TEXTURE_2D, buffer->texture->get());
glEGLImageTargetTexture2DOES(
GL_TEXTURE_2D, static_cast<GLeglImageOES>(buffer->egl_image->get()));
glBindTexture(GL_TEXTURE_2D, 0);
GrGLTextureInfo texture_info;
texture_info.fID = buffer->texture->get();
texture_info.fTarget = GL_TEXTURE_2D;
texture_info.fFormat = kSizedInternalFormat;
GrBackendTexture backend_texture(size.width(), size.height(),
GrMipMapped::kNo, texture_info);
buffer->sk_surface = SkSurface::MakeFromBackendTexture(
gr_context_.get(), backend_texture, kTopLeft_GrSurfaceOrigin,
/* sampleCnt */ 0, kColorType, /* colorSpace */ nullptr,
/* props */ nullptr);
DCHECK(buffer->sk_surface);
#if defined(USE_VULKAN)
if (!vk_implementation_)
return buffer;
// TODO(dcastagna): remove this hack as soon as the extension
// "VK_EXT_external_memory_dma_buf" is available.
#define VK_STRUCTURE_TYPE_DMA_BUF_IMAGE_CREATE_INFO_INTEL 1024
typedef struct VkDmaBufImageCreateInfo_ {
VkStructureType
sType; // Must be VK_STRUCTURE_TYPE_DMA_BUF_IMAGE_CREATE_INFO_INTEL
raw_ptr<const void, ExperimentalAsh> pNext; // Pointer to next structure.
int fd;
VkFormat format;
VkExtent3D extent; // Depth must be 1
uint32_t strideInBytes;
} VkDmaBufImageCreateInfo;
typedef VkResult(VKAPI_PTR * PFN_vkCreateDmaBufImageINTEL)(
VkDevice device, const VkDmaBufImageCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMem,
VkImage* pImage);
PFN_vkCreateDmaBufImageINTEL create_dma_buf_image_intel =
reinterpret_cast<PFN_vkCreateDmaBufImageINTEL>(
vkGetDeviceProcAddr(vk_device_->get(), "vkCreateDmaBufImageINTEL"));
if (!create_dma_buf_image_intel) {
LOG(ERROR) << "Vulkan wayland clients work only where "
"vkCreateDmaBufImageINTEL is available.";
return nullptr;
}
base::ScopedFD vk_image_fd(gbm_bo_get_plane_fd(buffer->bo.get(), 0));
CHECK(vk_image_fd.is_valid());
VkDmaBufImageCreateInfo dma_buf_image_create_info{
.sType = static_cast<VkStructureType>(
VK_STRUCTURE_TYPE_DMA_BUF_IMAGE_CREATE_INFO_INTEL),
.fd = vk_image_fd.release(),
.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
.extent = (VkExtent3D){static_cast<uint32_t>(size.width()),
static_cast<uint32_t>(size.height()), 1},
.strideInBytes = gbm_bo_get_stride(buffer->bo.get()),
};
buffer->vk_memory.reset(
new ScopedVkDeviceMemory(VK_NULL_HANDLE, {vk_device_->get()}));
buffer->vk_image.reset(
new ScopedVkImage(VK_NULL_HANDLE, {vk_device_->get()}));
VkResult result = create_dma_buf_image_intel(
vk_device_->get(), &dma_buf_image_create_info, nullptr,
ScopedVkDeviceMemory::Receiver(*buffer->vk_memory).get(),
ScopedVkImage::Receiver(*buffer->vk_image).get());
if (result != VK_SUCCESS) {
LOG(ERROR) << "Failed to create a Vulkan image from a dmabuf.";
return buffer;
}
VkImageViewCreateInfo vk_image_view_create_info{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = buffer->vk_image->get(),
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
.components =
{
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
.g = VK_COMPONENT_SWIZZLE_IDENTITY,
.b = VK_COMPONENT_SWIZZLE_IDENTITY,
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
},
.subresourceRange =
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
buffer->vk_image_view.reset(
new ScopedVkImageView(VK_NULL_HANDLE, {vk_device_->get()}));
result = vkCreateImageView(
vk_device_->get(), &vk_image_view_create_info, nullptr,
ScopedVkImageView::Receiver(*buffer->vk_image_view).get());
if (result != VK_SUCCESS) {
LOG(ERROR) << "Failed to create a Vulkan image view.";
return buffer;
}
VkFramebufferCreateInfo vk_framebuffer_create_info{
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = vk_render_pass_->get(),
.attachmentCount = 1,
.pAttachments = &buffer->vk_image_view->get(),
.width = static_cast<uint32_t>(size.width()),
.height = static_cast<uint32_t>(size.height()),
.layers = 1,
};
buffer->vk_framebuffer.reset(
new ScopedVkFramebuffer(VK_NULL_HANDLE, {vk_device_->get()}));
result = vkCreateFramebuffer(
vk_device_->get(), &vk_framebuffer_create_info, nullptr,
ScopedVkFramebuffer::Receiver(*buffer->vk_framebuffer).get());
if (result != VK_SUCCESS) {
LOG(ERROR) << "Failed to create a Vulkan framebuffer.";
return buffer;
}
#endif // defined(USE_VULKAN)
}
#endif // defined(USE_GBM)
return buffer;
}
ClientBase::Buffer* ClientBase::DequeueBuffer() {
auto buffer_it =
base::ranges::find_if_not(buffers_, &ClientBase::Buffer::busy);
if (buffer_it == buffers_.end())
return nullptr;
Buffer* buffer = buffer_it->get();
buffer->busy = true;
return buffer;
}
void ClientBase::SetupAuraShellIfAvailable() {
if (!globals_.aura_shell) {
LOG(ERROR) << "Can't find aura shell interface";
return;
}
static zaura_shell_listener kAuraShellListener = {
[](void* data, struct zaura_shell* zaura_shell, uint32_t layout_mode) {},
[](void* data, struct zaura_shell* zaura_shell, uint32_t id) {
CastToClientBase(data)->bug_fix_ids_.insert(id);
},
[](void* data, struct zaura_shell* zaura_shell,
struct wl_array* desk_names) {},
[](void* data, struct zaura_shell* zaura_shell,
int32_t active_desk_index) {},
[](void* data, struct zaura_shell* zaura_shell,
struct wl_surface* gained_active, struct wl_surface* lost_active) {}};
zaura_shell_add_listener(globals_.aura_shell.get(), &kAuraShellListener,
this);
std::unique_ptr<zaura_surface> aura_surface(static_cast<zaura_surface*>(
zaura_shell_get_aura_surface(globals_.aura_shell.get(), surface_.get())));
if (!aura_surface) {
LOG(ERROR) << "Can't get aura surface";
return;
}
zaura_surface_set_frame(aura_surface.get(), ZAURA_SURFACE_FRAME_TYPE_NORMAL);
static zaura_output_listener kAuraOutputListener = {
[](void* data, struct zaura_output* zaura_output, uint32_t flags,
uint32_t scale) {},
[](void* data, struct zaura_output* zaura_output, uint32_t connection) {},
[](void* data, struct zaura_output* zaura_output, uint32_t scale) {},
[](void* data, struct zaura_output* zaura_output, int32_t top,
int32_t left, int32_t bottom, int32_t right) {
CastToClientBase(data)->HandleInsets(
gfx::Insets::TLBR(top, left, bottom, right));
},
[](void* data, struct zaura_output* zaura_output, int32_t transform) {
CastToClientBase(data)->HandleLogicalTransform(transform);
},
[](void* data, struct zaura_output* zaura_output, uint32_t display_id_hi,
uint32_t display_id_lo) {},
};
std::unique_ptr<zaura_output> aura_output(zaura_shell_get_aura_output(
globals_.aura_shell.get(), globals_.output.get()));
zaura_output_add_listener(aura_output.get(), &kAuraOutputListener, this);
globals_.aura_output = std::move(aura_output);
}
void ClientBase::SetupPointerStylus() {
if (!globals_.stylus) {
LOG(ERROR) << "Can't find stylus interface";
return;
}
wl_pointer_ = base::WrapUnique(
static_cast<wl_pointer*>(wl_seat_get_pointer(globals_.seat.get())));
zcr_pointer_stylus_ = base::WrapUnique(
static_cast<zcr_pointer_stylus_v2*>(zcr_stylus_v2_get_pointer_stylus(
globals_.stylus.get(), wl_pointer_.get())));
if (!zcr_pointer_stylus_) {
LOG(ERROR) << "Can't get pointer stylus";
return;
}
static zcr_pointer_stylus_v2_listener kPointerStylusV2Listener = {
[](void* data, struct zcr_pointer_stylus_v2* x, uint32_t y) {},
[](void* data, struct zcr_pointer_stylus_v2* x, uint32_t y,
wl_fixed_t z) {},
[](void* data, struct zcr_pointer_stylus_v2* x, uint32_t y, wl_fixed_t z,
wl_fixed_t a) {},
};
zcr_pointer_stylus_v2_add_listener(zcr_pointer_stylus_.get(),
&kPointerStylusV2Listener, this);
}
} // namespace clients
} // namespace wayland
} // namespace exo