// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include <drm_fourcc.h>
#include <gbm.h>
#include <sys/mman.h>
#include <cstdint>
#include <iterator>
#include <vector>
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/queue.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/test/task_environment.h"
#include "components/exo/wayland/clients/client_base.h"
#include "components/exo/wayland/clients/client_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/buffer_usage_util.h"
#include "ui/gfx/linux/drm_util_linux.h"
#include "ui/gfx/linux/gbm_util.h"
namespace {
std::string DrmCodeToString(uint32_t drm_format) {
return std::string{static_cast<char>(drm_format),
static_cast<char>(drm_format >> 8),
static_cast<char>(drm_format >> 16),
static_cast<char>(drm_format >> 24), 0};
}
std::string DrmCodeToBufferFormatString(int32_t drm_format) {
return gfx::BufferFormatToString(
ui::GetBufferFormatFromFourCCFormat(drm_format));
}
std::string DrmModifiersToString(std::vector<uint64_t> drm_modifiers) {
std::stringstream ss;
for (uint64_t drm_modifier : drm_modifiers)
ss << "0x" << std::hex << drm_modifier << " ";
return ss.str();
}
} // namespace
namespace exo {
namespace wayland {
namespace test {
namespace {
#define WL_ARRAY_FOR_EACH(pos, array, type) \
for (pos = (type)(array)->data; \
(const char*)pos < ((const char*)(array)->data + (array)->size); \
(pos)++)
class BufferCheckerTestClient : public ::exo::wayland::clients::ClientBase {
public:
explicit BufferCheckerTestClient() = default;
~BufferCheckerTestClient() override = default;
int GetNumSupportedUsages(uint32_t format) {
std::vector<gfx::BufferUsage> supported_usages;
bool callback_pending = false;
std::unique_ptr<wl_callback> frame_callback;
wl_callback_listener frame_listener = {
[](void* data, struct wl_callback*, uint32_t) {
*(static_cast<bool*>(data)) = false;
}};
base::queue<gfx::BufferUsage> usages_to_test;
for (int i = 0; i < static_cast<int>(gfx::BufferUsage::LAST); i++)
usages_to_test.push(static_cast<gfx::BufferUsage>(i));
gfx::BufferUsage current_usage;
std::unique_ptr<Buffer> current_buffer;
do {
if (callback_pending)
continue;
if (current_buffer) {
supported_usages.push_back(current_usage);
}
if (wl_display_get_error(display_.get())) {
LOG(ERROR) << "Wayland error encountered";
return -1;
}
// Buffers may fail to be created, so loop until we get one or return
// early if we run out. We can't loop in the outer loop because, without
// doing the rest of the code, we won't be dispatched to again.
do {
if (usages_to_test.size() == 0) {
std::vector<std::string> supported_usage_strings;
base::ranges::transform(supported_usages,
std::back_inserter(supported_usage_strings),
gfx::BufferUsageToString);
LOG(INFO) << "Successfully used buffer with format drm: "
<< DrmCodeToString(format) << " gfx::BufferFormat: "
<< DrmCodeToBufferFormatString(format)
<< " gfx::BufferUsages: ["
<< base::JoinString(supported_usage_strings, ", ") << "]";
return supported_usages.size();
}
current_usage = usages_to_test.front();
usages_to_test.pop();
current_buffer = CreateDrmBuffer(
gfx::Size(surface_size_.width(), surface_size_.height()), format,
nullptr, 0, ui::BufferUsageToGbmFlags(current_usage),
/*y_invert=*/false);
if (!current_buffer) {
LOG(ERROR) << "Unable to create buffer for drm: "
<< DrmCodeToString(format) << " gfx::BufferFormat: "
<< DrmCodeToBufferFormatString(format)
<< " gfx::BufferUsage "
<< gfx::BufferUsageToString(current_usage);
}
} while (current_buffer == nullptr);
LOG(INFO) << "Attempting to use buffer with format drm: "
<< DrmCodeToString(format)
<< " gfx::BufferFormat: " << DrmCodeToBufferFormatString(format)
<< " gfx::BufferUsage "
<< gfx::BufferUsageToString(current_usage);
wl_surface_damage(surface_.get(), 0, 0, surface_size_.width(),
surface_size_.height());
wl_surface_attach(surface_.get(), current_buffer->buffer.get(), 0, 0);
frame_callback.reset(wl_surface_frame(surface_.get()));
wl_callback_add_listener(frame_callback.get(), &frame_listener,
&callback_pending);
callback_pending = true;
wl_surface_commit(surface_.get());
wl_display_flush(display_.get());
} while (wl_display_dispatch(display_.get()) != -1);
LOG(ERROR)
<< "Expected to return from inside the loop. Wayland disconnected?";
return -1;
}
int GetNumSupportedFormatsAndModifier(uint32_t format,
std::vector<uint64_t> modifiers) {
std::vector<gfx::BufferUsage> supported_usages;
bool callback_pending = false;
std::unique_ptr<wl_callback> frame_callback;
wl_callback_listener frame_listener = {
[](void* data, struct wl_callback*, uint32_t) {
*(static_cast<bool*>(data)) = false;
}};
std::unique_ptr<Buffer> current_buffer;
do {
if (callback_pending)
continue;
if (current_buffer) {
LOG(INFO) << "Successfully used buffer with drm format: "
<< DrmCodeToString(format)
<< " drm modifiers: " << DrmModifiersToString(modifiers)
<< " gfx::BufferFormat: "
<< DrmCodeToBufferFormatString(format);
return 1;
}
if (wl_display_get_error(display_.get())) {
LOG(ERROR) << "Wayland error encountered";
return -1;
}
if (modifiers.size() == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
current_buffer = CreateDrmBuffer(
gfx::Size(surface_size_.width(), surface_size_.height()), format,
nullptr, 0, ui::BufferUsageToGbmFlags(gfx::BufferUsage::GPU_READ),
/*y_invert=*/false);
} else {
current_buffer = CreateDrmBuffer(
gfx::Size(surface_size_.width(), surface_size_.height()), format,
modifiers.data(), modifiers.size(),
ui::BufferUsageToGbmFlags(gfx::BufferUsage::GPU_READ),
/*y_invert=*/false);
}
if (!current_buffer) {
LOG(ERROR) << "Unable to create buffer for drm format: "
<< DrmCodeToString(format)
<< " drm modifiers: " << DrmModifiersToString(modifiers)
<< " gfx::BufferFormat: "
<< DrmCodeToBufferFormatString(format);
return 0;
}
LOG(INFO) << "Attempting to use buffer with format drm format: "
<< DrmCodeToString(format)
<< " drm modifiers: " << DrmModifiersToString(modifiers)
<< " gfx::BufferFormat: "
<< DrmCodeToBufferFormatString(format);
wl_surface_damage(surface_.get(), 0, 0, surface_size_.width(),
surface_size_.height());
wl_surface_attach(surface_.get(), current_buffer->buffer.get(), 0, 0);
frame_callback.reset(wl_surface_frame(surface_.get()));
wl_callback_add_listener(frame_callback.get(), &frame_listener,
&callback_pending);
callback_pending = true;
wl_surface_commit(surface_.get());
wl_display_flush(display_.get());
} while (wl_display_dispatch(display_.get()) != -1);
LOG(ERROR)
<< "Expected to return from inside the loop. Wayland disconnected?";
return -1;
}
std::vector<uint32_t> reported_formats;
base::flat_map<uint32_t, std::vector<uint64_t>> reported_format_modifier_map;
struct WaylandDmabufFeedbackFormat {
uint32_t format;
uint32_t padding;
uint64_t modifier;
};
struct DmabufFeedbackTranche {
dev_t target_device;
uint32_t flags;
base::flat_map<uint32_t, std::vector<uint64_t>> format_modifier_map;
};
struct DmabufFeedback {
dev_t main_device;
std::vector<WaylandDmabufFeedbackFormat> format_table;
std::vector<DmabufFeedbackTranche> tranches;
DmabufFeedbackTranche pending_tranche;
};
DmabufFeedback current_feedback_;
DmabufFeedback pending_feedback_;
void HandleFeedbackDone(zwp_linux_dmabuf_feedback_v1* dmabuf_feedback) {
current_feedback_ = pending_feedback_;
pending_feedback_ = {};
reported_formats.clear();
reported_format_modifier_map.clear();
for (DmabufFeedbackTranche tranche : current_feedback_.tranches) {
for (const auto& [format, modifiers] : tranche.format_modifier_map) {
if (!base::Contains(reported_formats, format)) {
reported_formats.push_back(format);
}
if (!reported_format_modifier_map.contains(format)) {
reported_format_modifier_map[format] = std::vector<uint64_t>();
}
for (uint64_t modifier : modifiers) {
if (!base::Contains(reported_format_modifier_map[format], modifier)) {
reported_format_modifier_map[format].push_back(modifier);
}
}
}
}
}
void HandleFeedbackFormatTable(
zwp_linux_dmabuf_feedback_v1* zwp_linux_dmabuf_feedback_v1,
int32_t fd,
uint32_t size) {
ASSERT_TRUE(pending_feedback_.format_table.empty());
WaylandDmabufFeedbackFormat* format_table =
static_cast<WaylandDmabufFeedbackFormat*>(
mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0));
uint32_t table_size = size / sizeof(WaylandDmabufFeedbackFormat);
for (uint32_t i = 0; i < table_size; i++) {
pending_feedback_.format_table.push_back(format_table[i]);
}
munmap(format_table, size);
close(fd);
}
void HandleFeedbackMainDevice(zwp_linux_dmabuf_feedback_v1* dmabuf_feedback,
wl_array* dev) {
memcpy(&pending_feedback_.main_device, dev->data, sizeof(dev));
}
void HandleFeedbackTrancheDone(
zwp_linux_dmabuf_feedback_v1* dmabuf_feedback) {
pending_feedback_.tranches.push_back(pending_feedback_.pending_tranche);
pending_feedback_.pending_tranche = {};
}
void HandleFeedbackTrancheTargetDevice(
zwp_linux_dmabuf_feedback_v1* dmabuf_feedback,
wl_array* dev) {
memcpy(&pending_feedback_.pending_tranche.target_device, dev->data,
sizeof(dev));
}
void HandleFeedbackTrancheFormats(
zwp_linux_dmabuf_feedback_v1* dmabuf_feedback,
wl_array* indices) {
std::vector<WaylandDmabufFeedbackFormat>* format_table =
&pending_feedback_.format_table;
if (format_table == nullptr)
format_table = ¤t_feedback_.format_table;
ASSERT_TRUE(format_table != nullptr);
uint16_t* index;
WL_ARRAY_FOR_EACH(index, indices, uint16_t*) {
uint32_t format = format_table->at(*index).format;
uint64_t modifier = format_table->at(*index).modifier;
if (!pending_feedback_.pending_tranche.format_modifier_map.contains(
format))
pending_feedback_.pending_tranche.format_modifier_map[format] =
std::vector<uint64_t>();
pending_feedback_.pending_tranche.format_modifier_map[format].push_back(
modifier);
}
}
void HandleFeedbackTrancheFlags(zwp_linux_dmabuf_feedback_v1* dmabuf_feedback,
uint32_t flags) {
pending_feedback_.pending_tranche.flags = flags;
}
void AddFeedbackListener(zwp_linux_dmabuf_feedback_v1* dmabuf_feedback_obj) {
static struct zwp_linux_dmabuf_feedback_v1_listener kLinuxFeedbackListener =
{
.done =
[](void* data,
struct zwp_linux_dmabuf_feedback_v1* dmabuf_feedback) {
static_cast<BufferCheckerTestClient*>(data)
->HandleFeedbackDone(dmabuf_feedback);
},
.format_table =
[](void* data, zwp_linux_dmabuf_feedback_v1* dmabuf_feedback,
int32_t fd, uint32_t size) {
static_cast<BufferCheckerTestClient*>(data)
->HandleFeedbackFormatTable(dmabuf_feedback, fd, size);
},
.main_device =
[](void* data,
struct zwp_linux_dmabuf_feedback_v1* dmabuf_feedback,
struct wl_array* dev) {
static_cast<BufferCheckerTestClient*>(data)
->HandleFeedbackMainDevice(dmabuf_feedback, dev);
},
.tranche_done =
[](void* data,
struct zwp_linux_dmabuf_feedback_v1* dmabuf_feedback) {
static_cast<BufferCheckerTestClient*>(data)
->HandleFeedbackTrancheDone(dmabuf_feedback);
},
.tranche_target_device =
[](void* data,
struct zwp_linux_dmabuf_feedback_v1* dmabuf_feedback,
struct wl_array* dev) {
static_cast<BufferCheckerTestClient*>(data)
->HandleFeedbackTrancheTargetDevice(dmabuf_feedback, dev);
},
.tranche_formats =
[](void* data,
struct zwp_linux_dmabuf_feedback_v1* dmabuf_feedback,
struct wl_array* indices) {
static_cast<BufferCheckerTestClient*>(data)
->HandleFeedbackTrancheFormats(dmabuf_feedback, indices);
},
.tranche_flags =
[](void* data,
struct zwp_linux_dmabuf_feedback_v1* dmabuf_feedback,
uint32_t flags) {
static_cast<BufferCheckerTestClient*>(data)
->HandleFeedbackTrancheFlags(dmabuf_feedback, flags);
},
};
zwp_linux_dmabuf_feedback_v1_add_listener(dmabuf_feedback_obj,
&kLinuxFeedbackListener, this);
wl_display_roundtrip(display_.get());
}
void GetDefaultFeedback() {
zwp_linux_dmabuf_feedback_v1* dmabuf_feedback_obj =
zwp_linux_dmabuf_v1_get_default_feedback(globals_.linux_dmabuf.get());
AddFeedbackListener(dmabuf_feedback_obj);
}
void GetSurfaceFeedback() {
zwp_linux_dmabuf_feedback_v1* dmabuf_feedback_obj =
zwp_linux_dmabuf_v1_get_surface_feedback(globals_.linux_dmabuf.get(),
surface_.get());
AddFeedbackListener(dmabuf_feedback_obj);
}
protected:
void HandleDmabufFormat(void* data,
struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
uint32_t format) override {
reported_formats.push_back(format);
}
void HandleDmabufModifier(void* data,
struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
uint32_t format,
uint32_t modifier_hi,
uint32_t modifier_lo) override {
if (!reported_format_modifier_map.contains(format)) {
reported_format_modifier_map[format] = std::vector<uint64_t>();
}
uint64_t modifier = static_cast<uint64_t>(modifier_hi) << 32 | modifier_lo;
reported_format_modifier_map[format].push_back(modifier);
}
};
} // namespace
} // namespace test
} // namespace wayland
} // namespace exo
class BufferCheckerClientTest : public testing::Test {
protected:
void SetUp() override {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
CHECK(base_params_.FromCommandLine(*command_line));
CHECK(base_params_.use_drm) << "Missing --use-drm parameter which is "
"required for gbm buffer allocation";
}
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::MainThreadType::UI};
exo::wayland::clients::ClientBase::InitParams base_params_;
};
void PrintReportedFormats(std::vector<uint32_t>& formats) {
std::vector<std::string> drm_names;
std::vector<std::string> buffer_names;
for (auto format : formats) {
drm_names.push_back(DrmCodeToString(format));
buffer_names.push_back(DrmCodeToBufferFormatString(format));
}
LOG(INFO) << "zwp_linux_dmabuf_v1 reported supported DRM formats: "
<< base::JoinString(drm_names, ", ");
LOG(INFO) << "zwp_linux_dmabuf_v1 reported supported gfx::BufferFormats: "
<< base::JoinString(buffer_names, ", ");
}
TEST_F(BufferCheckerClientTest, CanUseAnyReportedBufferFormatsLegacy) {
exo::wayland::test::BufferCheckerTestClient client;
auto params = base_params_;
// Initialize no buffers when we start, wait until we've gotten the list
params.num_buffers = 0;
params.linux_dmabuf_version =
ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED_SINCE_VERSION;
ASSERT_TRUE(client.Init(params));
EXPECT_TRUE(!client.reported_formats.empty());
PrintReportedFormats(client.reported_formats);
bool has_any_supported_formats = false;
for (auto format : client.reported_formats) {
int res = client.GetNumSupportedUsages(format);
EXPECT_TRUE(res != -1);
if (res > 0) {
has_any_supported_formats = true;
}
}
EXPECT_TRUE(has_any_supported_formats);
}
TEST_F(BufferCheckerClientTest, CanUseAnyReportedBufferModifiersLegacy) {
exo::wayland::test::BufferCheckerTestClient client;
auto params = base_params_;
// Initialize no buffers when we start, wait until we've gotten the list
params.num_buffers = 0;
params.linux_dmabuf_version = ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION;
ASSERT_TRUE(client.Init(params));
EXPECT_TRUE(!client.reported_format_modifier_map.empty());
bool has_any_supported_formats = false;
for (const auto& [format, modifiers] : client.reported_format_modifier_map) {
std::vector<uint64_t> valid_modifiers;
for (uint64_t modifier : modifiers) {
if (modifier != DRM_FORMAT_MOD_INVALID)
valid_modifiers.push_back(modifier);
}
if (!valid_modifiers.empty()) {
int res =
client.GetNumSupportedFormatsAndModifier(format, valid_modifiers);
EXPECT_TRUE(res != -1);
if (res > 0) {
has_any_supported_formats = true;
}
}
if (base::Contains(modifiers, DRM_FORMAT_MOD_INVALID)) {
int res = client.GetNumSupportedFormatsAndModifier(
format, std::vector<uint64_t>({DRM_FORMAT_MOD_INVALID}));
EXPECT_TRUE(res != -1);
if (res > 0) {
has_any_supported_formats = true;
}
}
}
EXPECT_TRUE(has_any_supported_formats);
}
TEST_F(BufferCheckerClientTest, CanUseAnyReportedBufferFormatsDefaultFeedback) {
exo::wayland::test::BufferCheckerTestClient client;
auto params = base_params_;
// Initialize no buffers when we start, wait until we've gotten the list
params.num_buffers = 0;
params.linux_dmabuf_version =
ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION;
ASSERT_TRUE(client.Init(params));
EXPECT_TRUE(client.reported_format_modifier_map.empty());
client.GetDefaultFeedback();
EXPECT_TRUE(!client.reported_format_modifier_map.empty());
PrintReportedFormats(client.reported_formats);
bool has_any_supported_formats = false;
for (auto format : client.reported_formats) {
int res = client.GetNumSupportedUsages(format);
EXPECT_TRUE(res != -1);
if (res > 0) {
has_any_supported_formats = true;
}
}
EXPECT_TRUE(has_any_supported_formats);
}
TEST_F(BufferCheckerClientTest,
CanUseAnyReportedBufferModifiersDefaultFeedback) {
exo::wayland::test::BufferCheckerTestClient client;
auto params = base_params_;
// Initialize no buffers when we start, wait until we've gotten the list
params.num_buffers = 0;
params.linux_dmabuf_version =
ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION;
ASSERT_TRUE(client.Init(params));
EXPECT_TRUE(client.reported_format_modifier_map.empty());
client.GetDefaultFeedback();
EXPECT_TRUE(!client.reported_format_modifier_map.empty());
bool has_any_supported_formats = false;
for (const auto& [format, modifiers] : client.reported_format_modifier_map) {
std::vector<uint64_t> valid_modifiers;
for (uint64_t modifier : modifiers) {
if (modifier != DRM_FORMAT_MOD_INVALID)
valid_modifiers.push_back(modifier);
}
if (!valid_modifiers.empty()) {
int res =
client.GetNumSupportedFormatsAndModifier(format, valid_modifiers);
EXPECT_TRUE(res != -1);
if (res > 0) {
has_any_supported_formats = true;
}
}
if (base::Contains(modifiers, DRM_FORMAT_MOD_INVALID)) {
int res = client.GetNumSupportedFormatsAndModifier(
format, std::vector<uint64_t>({DRM_FORMAT_MOD_INVALID}));
EXPECT_TRUE(res != -1);
if (res > 0) {
has_any_supported_formats = true;
}
}
}
EXPECT_TRUE(has_any_supported_formats);
}
TEST_F(BufferCheckerClientTest, CanUseAnyReportedBufferFormatsSurfaceFeedback) {
exo::wayland::test::BufferCheckerTestClient client;
auto params = base_params_;
// Initialize no buffers when we start, wait until we've gotten the list
params.num_buffers = 0;
params.linux_dmabuf_version =
ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION;
ASSERT_TRUE(client.Init(params));
EXPECT_TRUE(client.reported_format_modifier_map.empty());
client.GetSurfaceFeedback();
EXPECT_TRUE(!client.reported_format_modifier_map.empty());
PrintReportedFormats(client.reported_formats);
bool has_any_supported_formats = false;
for (auto format : client.reported_formats) {
int res = client.GetNumSupportedUsages(format);
EXPECT_TRUE(res != -1);
if (res > 0) {
has_any_supported_formats = true;
}
}
EXPECT_TRUE(has_any_supported_formats);
}
TEST_F(BufferCheckerClientTest,
CanUseAnyReportedBufferModifiersSurfaceFeedback) {
exo::wayland::test::BufferCheckerTestClient client;
auto params = base_params_;
// Initialize no buffers when we start, wait until we've gotten the list
params.num_buffers = 0;
params.linux_dmabuf_version =
ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK_SINCE_VERSION;
ASSERT_TRUE(client.Init(params));
EXPECT_TRUE(client.reported_format_modifier_map.empty());
client.GetSurfaceFeedback();
EXPECT_TRUE(!client.reported_format_modifier_map.empty());
bool has_any_supported_formats = false;
for (const auto& [format, modifiers] : client.reported_format_modifier_map) {
std::vector<uint64_t> valid_modifiers;
for (uint64_t modifier : modifiers) {
if (modifier != DRM_FORMAT_MOD_INVALID)
valid_modifiers.push_back(modifier);
}
if (!valid_modifiers.empty()) {
int res =
client.GetNumSupportedFormatsAndModifier(format, valid_modifiers);
EXPECT_TRUE(res != -1);
if (res > 0) {
has_any_supported_formats = true;
}
}
if (base::Contains(modifiers, DRM_FORMAT_MOD_INVALID)) {
int res = client.GetNumSupportedFormatsAndModifier(
format, std::vector<uint64_t>({DRM_FORMAT_MOD_INVALID}));
EXPECT_TRUE(res != -1);
if (res > 0) {
has_any_supported_formats = true;
}
}
}
EXPECT_TRUE(has_any_supported_formats);
}