chromium/components/exo/wayland/wayland_dmabuf_feedback_manager.cc

// Copyright 2022 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 "components/exo/wayland/wayland_dmabuf_feedback_manager.h"

#include <bits/types.h>
#include <drm_fourcc.h>
#include <linux-dmabuf-unstable-v1-server-protocol.h>
#include <sys/stat.h>

#include "ash/constants/ash_features.h"
#include "ash/shell.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "components/exo/buffer.h"
#include "components/exo/display.h"
#include "components/exo/shell_surface_base.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "components/exo/wayland/server_util.h"
#include "components/viz/common/gpu/context_provider.h"
#include "ui/aura/env.h"
#include "ui/compositor/compositor.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/manager/managed_display_info.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/linux/drm_util_linux.h"

namespace exo {
namespace wayland {
namespace {

enum TrancheFlags : uint32_t { kNone = 0, kScanout = 1 };

struct WaylandDmabufFeedbackFormat {
  uint32_t format;
  uint32_t padding;
  uint64_t modifier;
};

void feedbackDestroy(wl_client* client, wl_resource* resource) {
  wl_resource_destroy(resource);
}

const struct zwp_linux_dmabuf_feedback_v1_interface feedback_implementation = {
    feedbackDestroy,
};

// A tranche links to a subset of formats and modifiers which are supported on a
// specific device for a specific task. The most common use cases are the
// "default" tranche (targeting the main device, advertising all formats and
// modifiers supported by the compositor, for non-scanout cases) and "scanout"
// tranches (advertising the formats and modifiers on a specific device that, if
// used by the client, allows the compositor to offload some work to the
// hardware). "scanout" tranches should usually only get advertised by the
// compositor if there's a reasonable chance that composition can be skipped -
// e.g. when a surface is in fullscreen mode.
class WaylandDmabufFeedbackTranche {
 public:
  explicit WaylandDmabufFeedbackTranche(
      dev_t target_device_id,
      TrancheFlags flags,
      IndexedDrmFormatsAndModifiers drm_formats_and_modifiers)
      : target_device_id_(target_device_id),
        flags_(flags),
        drm_formats_and_modifiers_(drm_formats_and_modifiers) {}
  explicit WaylandDmabufFeedbackTranche(
      const std::unique_ptr<WaylandDmabufFeedbackTranche>& other)
      : target_device_id_(other->target_device_id_),
        flags_(other->flags_),
        drm_formats_and_modifiers_(other->drm_formats_and_modifiers_) {}

  WaylandDmabufFeedbackTranche(const WaylandDmabufFeedbackTranche&) = delete;
  WaylandDmabufFeedbackTranche& operator=(const WaylandDmabufFeedbackTranche&) =
      delete;

  ~WaylandDmabufFeedbackTranche() = default;

  dev_t GetTargetDeviceId() const { return target_device_id_; }
  TrancheFlags GetFlags() const { return flags_; }

  const IndexedDrmFormatsAndModifiers GetFormatsAndModifiers() const {
    return drm_formats_and_modifiers_;
  }

 private:
  const dev_t target_device_id_;
  const TrancheFlags flags_;
  const IndexedDrmFormatsAndModifiers drm_formats_and_modifiers_;
};

// A feedback is a set of information that is send to the client. It consists of
// a main device (the device the compositor uses for rendering), a format table
// (containing all formats and modifiers that may be advertised in one of the
// tranches, including those not supported by the main device) and one or more
// tranches (there must always be at least one for the main device). The
// compositor may choose to update a feedback at any time if necessary. The
// provided information should allow the client to pick the optimal combination
// of device, format and modifier for any given situation.
// A feedback object may be reused for several clients/requests in order to
// minimize resource usage.
// TODO: Updating a feedback is not yet implemented.
class WaylandDmabufFeedback {
 public:
  explicit WaylandDmabufFeedback(
      dev_t device_id,
      std::unique_ptr<WaylandDmabufFeedbackTranche> default_tranche)
      : main_device_id_(device_id),
        default_tranche_(std::move(default_tranche)) {}
  explicit WaylandDmabufFeedback(
      const std::unique_ptr<WaylandDmabufFeedback>& other)
      : main_device_id_(other->main_device_id_),
        default_tranche_(std::make_unique<WaylandDmabufFeedbackTranche>(
            other->default_tranche_)) {
    if (other->scanout_tranche_)
      scanout_tranche_ = std::make_unique<WaylandDmabufFeedbackTranche>(
          other->scanout_tranche_);
  }

  WaylandDmabufFeedback(const WaylandDmabufFeedback&) = delete;
  WaylandDmabufFeedback& operator=(const WaylandDmabufFeedback&) = delete;

  ~WaylandDmabufFeedback() = default;

  dev_t GetMainDeviceId() const { return main_device_id_; }
  const WaylandDmabufFeedbackTranche* GetDefaultTranche() const {
    return default_tranche_.get();
  }
  const WaylandDmabufFeedbackTranche* GetScanoutTranche() const {
    return scanout_tranche_.get();
  }

  void MaybeAddScanoutTranche(Surface* surface) {
    DCHECK(!scanout_tranche_);

    const display::Display surface_display = surface->GetDisplay();
    display::DrmFormatsAndModifiers display_formats_and_modifiers =
        ash::Shell::Get()
            ->display_manager()
            ->GetDisplayInfo(surface_display.id())
            .GetDRMFormatsAndModifiers();
    IndexedDrmFormatsAndModifiers scanout_formats_and_modifiers;

    for (const auto& [format, modifier_entries] :
         default_tranche_->GetFormatsAndModifiers()) {
      if (base::Contains(display_formats_and_modifiers, format)) {
        base::flat_map<size_t, uint64_t> scanout_modifier_entries;

        if (modifier_entries.size() == 1) {
          auto it = modifier_entries.begin();
          DCHECK(it->second == DRM_FORMAT_MOD_INVALID);
          scanout_modifier_entries.emplace(it->first, it->second);
        } else {
          for (const auto& [table_index, modifier] : modifier_entries) {
            if (base::Contains(display_formats_and_modifiers.at(format),
                               modifier)) {
              scanout_modifier_entries.emplace(table_index, modifier);
            }
          }
        }

        if (!scanout_modifier_entries.empty())
          scanout_formats_and_modifiers.emplace(format,
                                                scanout_modifier_entries);
      }
    }
    if (scanout_formats_and_modifiers.empty())
      return;

    scanout_tranche_ = std::make_unique<WaylandDmabufFeedbackTranche>(
        main_device_id_, TrancheFlags::kScanout, scanout_formats_and_modifiers);
  }

  void ClearScanoutTranche() {
    DCHECK(scanout_tranche_);
    scanout_tranche_ = nullptr;
  }

 private:
  const dev_t main_device_id_;
  const std::unique_ptr<WaylandDmabufFeedbackTranche> default_tranche_;
  std::unique_ptr<WaylandDmabufFeedbackTranche> scanout_tranche_;
};

class WaylandDmabufSurfaceFeedbackResourceWrapper;

// A surface feedback can be requested by a client to get an optimized feedback
// for a specific surface - most importantly if the client wants to receive
// "scanout" tranches when possible. A client may request the surface feedback
// multiple times for the same surface (e.g. implicitly via Mesa and explicitly
// in the application). Thus a surface feedback can have multiple resource
// objects.
class WaylandDmabufSurfaceFeedback : public SurfaceObserver {
 public:
  explicit WaylandDmabufSurfaceFeedback(
      WaylandDmabufFeedbackManager* feedback_manager,
      Surface* surface,
      std::unique_ptr<WaylandDmabufFeedback> feedback)
      : feedback_manager_(feedback_manager),
        surface_(surface),
        feedback_(std::move(feedback)) {
    surface_->AddSurfaceObserver(this);
  }

  WaylandDmabufSurfaceFeedback(const WaylandDmabufSurfaceFeedback&) = delete;
  WaylandDmabufSurfaceFeedback& operator=(const WaylandDmabufSurfaceFeedback&) =
      delete;

  ~WaylandDmabufSurfaceFeedback() override;

  void OnSurfaceDestroying(Surface* surface) override {
    feedback_manager_->RemoveSurfaceFromScanoutCandidates(
        surface_, ScanoutReasonFlags::kNone);
    feedback_manager_->RemoveSurfaceFeedback(surface_);
  }

  void OnDisplayChanged(Surface* surface,
                        int64_t old_display,
                        int64_t new_display) override {
    feedback_manager_->MaybeResendFeedback(surface_);
  }

  void OnFullscreenStateChanged(bool fullscreen) override {
    if (fullscreen) {
      feedback_manager_->AddSurfaceToScanoutCandidates(
          surface_, ScanoutReasonFlags::kFullscreen);
    } else {
      feedback_manager_->RemoveSurfaceFromScanoutCandidates(
          surface_, ScanoutReasonFlags::kFullscreen);
    }
  }

  void OnOverlayPriorityHintChanged(
      OverlayPriority overlay_priority_hint) override {
    if (overlay_priority_hint == OverlayPriority::REQUIRED) {
      feedback_manager_->AddSurfaceToScanoutCandidates(
          surface_, ScanoutReasonFlags::kOverlayPriorityHint);
    } else {
      feedback_manager_->RemoveSurfaceFromScanoutCandidates(
          surface_, ScanoutReasonFlags::kOverlayPriorityHint);
    }
  }

  void AddSurfaceFeedbackRef(
      WaylandDmabufSurfaceFeedbackResourceWrapper* surface_feedback_ref) {
    surface_feedback_refs_.insert(surface_feedback_ref);
  }
  void OnSurfaceFeedbackRefDestroyed(
      WaylandDmabufSurfaceFeedbackResourceWrapper* surface_feedback_ref) {
    DCHECK(base::Contains(surface_feedback_refs_, surface_feedback_ref));
    surface_feedback_refs_.erase(surface_feedback_ref);
    if (surface_feedback_refs_.empty())
      feedback_manager_->RemoveSurfaceFeedback(surface_);
  }

  Surface* GetSurface() { return surface_; }
  WaylandDmabufFeedback* GetFeedback() { return feedback_.get(); }
  std::set<
      raw_ptr<WaylandDmabufSurfaceFeedbackResourceWrapper, SetExperimental>>
  GetFeedbackRefs() {
    return surface_feedback_refs_;
  }

 private:
  const raw_ptr<WaylandDmabufFeedbackManager> feedback_manager_;
  const raw_ptr<Surface> surface_;
  std::unique_ptr<WaylandDmabufFeedback> const feedback_;
  std::set<
      raw_ptr<WaylandDmabufSurfaceFeedbackResourceWrapper, SetExperimental>>
      surface_feedback_refs_;
};

// Simple helper class to use a surface feedback with multiple resource objects
// when using SetImplementation()
class WaylandDmabufSurfaceFeedbackResourceWrapper {
 public:
  explicit WaylandDmabufSurfaceFeedbackResourceWrapper(
      WaylandDmabufSurfaceFeedback* surface_feedback,
      wl_resource* resource)
      : surface_feedback_(surface_feedback), resource_(resource) {
    surface_feedback->AddSurfaceFeedbackRef(this);
  }

  WaylandDmabufSurfaceFeedbackResourceWrapper(
      const WaylandDmabufSurfaceFeedbackResourceWrapper&) = delete;
  WaylandDmabufSurfaceFeedbackResourceWrapper& operator=(
      const WaylandDmabufSurfaceFeedbackResourceWrapper&) = delete;

  ~WaylandDmabufSurfaceFeedbackResourceWrapper() {
    if (surface_feedback_)
      surface_feedback_->OnSurfaceFeedbackRefDestroyed(this);
  }

  wl_resource* GetFeedbackResource() { return resource_; }
  void SetInert() { surface_feedback_ = nullptr; }

 private:
  // Dangling when starting Borealis and Steam starts updating.
  raw_ptr<WaylandDmabufSurfaceFeedback, DanglingUntriaged> surface_feedback_;
  raw_ptr<wl_resource> resource_;
};

WaylandDmabufSurfaceFeedback::~WaylandDmabufSurfaceFeedback() {
  for (WaylandDmabufSurfaceFeedbackResourceWrapper* surface_feedback_ref :
       surface_feedback_refs_) {
    surface_feedback_ref->SetInert();
  }
  surface_->RemoveSurfaceObserver(this);
}

}  // namespace

WaylandDmabufFeedbackManager::WaylandDmabufFeedbackManager(Display* display)
    : display_(display) {
  scoped_refptr<viz::RasterContextProvider> context_provider =
      aura::Env::GetInstance()
          ->context_factory()
          ->SharedMainThreadRasterContextProvider();
  gpu::Capabilities caps = context_provider->ContextCapabilities();

  // Intel CCS modifiers leak memory on gbm to gl buffer import. Block these
  // modifiers for now. See crbug.com/1445252, crbug.com/1458575
  // Also blocking |DRM_FORMAT_YVU420| for a specific modifier based a test
  // failing in minigbm. See b/289714323
  const base::flat_set<std::pair<uint64_t, uint64_t>> modifier_block_list = {
      {DRM_FORMAT_INVALID, I915_FORMAT_MOD_Y_TILED_CCS},
      {DRM_FORMAT_INVALID, I915_FORMAT_MOD_Yf_TILED_CCS},
      {DRM_FORMAT_INVALID, I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS},
      {DRM_FORMAT_INVALID, I915_FORMAT_MOD_Y_TILED_GEN12_MC_CCS},
      {DRM_FORMAT_INVALID, I915_FORMAT_MOD_Y_TILED_GEN12_RC_CCS_CC},
      {DRM_FORMAT_INVALID, I915_FORMAT_MOD_4_TILED_DG2_RC_CCS},
      {DRM_FORMAT_INVALID, I915_FORMAT_MOD_4_TILED_DG2_MC_CCS},
      {DRM_FORMAT_INVALID, I915_FORMAT_MOD_4_TILED_DG2_RC_CCS_CC},
      {DRM_FORMAT_YVU420, DRM_FORMAT_MOD_QCOM_COMPRESSED}};

  size_t format_table_index = 0;
  for (const auto& [drm_format, modifiers] : caps.drm_formats_and_modifiers) {
    if (!ui::IsValidBufferFormat(drm_format))
      continue;

    if (!caps.gpu_memory_buffer_formats.Has(
            ui::GetBufferFormatFromFourCCFormat(drm_format))) {
      continue;
    }

    base::flat_map<size_t, uint64_t> modifier_entries;
    modifier_entries.emplace(format_table_index++, DRM_FORMAT_MOD_INVALID);

    if (base::FeatureList::IsEnabled(ash::features::kExoLinuxDmabufModifiers)) {
      for (uint64_t modifier : modifiers) {
        // Check for generic blocking first then format specific blocking.
        if (!modifier_block_list.contains({DRM_FORMAT_INVALID, modifier}) &&
            !modifier_block_list.contains({drm_format, modifier})) {
          modifier_entries.emplace(format_table_index++, modifier);
        }
      }
    }

    drm_formats_and_modifiers_.emplace(drm_format, modifier_entries);
  }
  if (drm_formats_and_modifiers_.empty()) {
    // Fallback path, to be removed ASAP. We should not advertise the protocol
    // at all.
    gpu::GpuMemoryBufferFormatSet format_set = caps.gpu_memory_buffer_formats;
    for (int i = 0; i <= static_cast<int>(gfx::BufferFormat::LAST); i++) {
      gfx::BufferFormat buffer_format = static_cast<gfx::BufferFormat>(i);
      if (format_set.Has(buffer_format)) {
        int drm_format = ui::GetFourCCFormatFromBufferFormat(buffer_format);
        if (ui::IsValidBufferFormat(drm_format)) {
          base::flat_map<size_t, uint64_t> modifier_entries;
          modifier_entries.emplace(format_table_index++,
                                   DRM_FORMAT_MOD_INVALID);
          drm_formats_and_modifiers_.emplace(drm_format, modifier_entries);
        }
      }
    }
    version_ = ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED_SINCE_VERSION;
    return;
  }

  if (!base::FeatureList::IsEnabled(ash::features::kExoLinuxDmabufV3) &&
      !base::FeatureList::IsEnabled(ash::features::kExoLinuxDmabufV4)) {
    version_ = ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED_SINCE_VERSION;
    return;
  }

  if (!base::FeatureList::IsEnabled(ash::features::kExoLinuxDmabufV4) ||
      !caps.drm_device_id) {
    version_ = ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION;
    return;
  }

  auto tranche = std::make_unique<WaylandDmabufFeedbackTranche>(
      caps.drm_device_id, TrancheFlags::kNone, drm_formats_and_modifiers_);
  default_feedback_ = std::make_unique<WaylandDmabufFeedback>(
      caps.drm_device_id, std::move(tranche));

  size_t size = sizeof(WaylandDmabufFeedbackFormat) * format_table_index;
  base::MappedReadOnlyRegion mapped_region =
      base::ReadOnlySharedMemoryRegion::Create(size);
  CHECK(mapped_region.IsValid());

  shared_memory_region_ = std::make_unique<base::ReadOnlySharedMemoryRegion>(
      std::move(mapped_region.region));

  WaylandDmabufFeedbackFormat* format_table =
      static_cast<WaylandDmabufFeedbackFormat*>(mapped_region.mapping.memory());

  for (const auto& [format, modifier_entries] : drm_formats_and_modifiers_) {
    for (const auto& [table_index, modifier] : modifier_entries) {
      format_table[table_index].format = format;
      format_table[table_index].modifier = modifier;
    }
  }

  version_ = ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION;
}

WaylandDmabufFeedbackManager::~WaylandDmabufFeedbackManager() = default;

bool WaylandDmabufFeedbackManager::IsFormatSupported(uint32_t format) const {
  return base::Contains(drm_formats_and_modifiers_, format);
}

void WaylandDmabufFeedbackManager::SendFormatsAndModifiers(
    wl_resource* resource) const {
  for (const auto& [format, modifier_entries] : drm_formats_and_modifiers_) {
    zwp_linux_dmabuf_v1_send_format(resource, format);
    if (wl_resource_get_version(resource) >=
        ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
      for (const auto& [table_index, modifier] : modifier_entries) {
        zwp_linux_dmabuf_v1_send_modifier(resource, format, modifier >> 32,
                                          modifier & 0xffffffff);
      }
    }
  }
}

void WaylandDmabufFeedbackManager::GetDefaultFeedback(
    wl_client* client,
    wl_resource* dma_buf_resource,
    uint32_t feedback_id) {
  wl_resource* feedback_resource = wl_resource_create(
      client, &zwp_linux_dmabuf_feedback_v1_interface,
      wl_resource_get_version(dma_buf_resource), feedback_id);

  SetImplementation(feedback_resource, &feedback_implementation);

  SendFeedback(default_feedback_.get(), feedback_resource);
}

void WaylandDmabufFeedbackManager::GetSurfaceFeedback(
    wl_client* client,
    wl_resource* dma_buf_resource,
    uint32_t feedback_id,
    wl_resource* surface_resource) {
  Surface* surface = GetUserDataAs<Surface>(surface_resource);

  auto it = surface_feedbacks_.find(surface);
  if (it == surface_feedbacks_.end()) {
    auto defaut_feedback_copy =
        std::make_unique<WaylandDmabufFeedback>(default_feedback_);
    auto new_surface_feedback = std::make_unique<WaylandDmabufSurfaceFeedback>(
        this, surface, std::move(defaut_feedback_copy));

    ShellSurfaceBase* shell_surface_base = nullptr;
    for (auto* window = surface->window(); window && !shell_surface_base;
         window = window->parent()) {
      shell_surface_base = GetShellSurfaceBaseForWindow(window);
    }
    if (shell_surface_base) {
      bool fullscreen = shell_surface_base->GetWidget()->IsFullscreen();
      new_surface_feedback->OnFullscreenStateChanged(fullscreen);
    }

    new_surface_feedback->OnOverlayPriorityHintChanged(
        surface->GetOverlayPriorityHint());

    it = surface_feedbacks_.emplace_hint(it, surface,
                                         std::move(new_surface_feedback));
  }
  WaylandDmabufSurfaceFeedback* surface_feedback = it->second.get();
  DCHECK(surface_feedback);

  wl_resource* feedback_resource = wl_resource_create(
      client, &zwp_linux_dmabuf_feedback_v1_interface,
      wl_resource_get_version(dma_buf_resource), feedback_id);

  auto surface_feedback_ref =
      std::make_unique<WaylandDmabufSurfaceFeedbackResourceWrapper>(
          surface_feedback, feedback_resource);
  SetImplementation(feedback_resource, &feedback_implementation,
                    std::move(surface_feedback_ref));

  auto* feedback = surface_feedback->GetFeedback();
  if (base::Contains(scanout_candidates_, surface))
    feedback->MaybeAddScanoutTranche(surface);

  SendFeedback(feedback, feedback_resource);
}

void WaylandDmabufFeedbackManager::RemoveSurfaceFeedback(Surface* surface) {
  DCHECK(base::Contains(surface_feedbacks_, surface));
  surface_feedbacks_.erase(surface);
}

void WaylandDmabufFeedbackManager::AddSurfaceToScanoutCandidates(
    Surface* surface,
    ScanoutReasonFlags reason) {
  auto search = scanout_candidates_.find(surface);
  if (search != scanout_candidates_.end()) {
    search->second = static_cast<ScanoutReasonFlags>(
        static_cast<uint32_t>(search->second) | static_cast<uint32_t>(reason));
    return;
  }

  scanout_candidates_.emplace(surface, reason);

  if (!base::Contains(surface_feedbacks_, surface)) {
    return;
  }

  const auto& surface_feedback = surface_feedbacks_[surface];
  auto* feedback = surface_feedback->GetFeedback();
  if (feedback->GetScanoutTranche()) {
    return;
  }

  feedback->MaybeAddScanoutTranche(surface);
  if (!feedback->GetScanoutTranche()) {
    return;
  }

  for (WaylandDmabufSurfaceFeedbackResourceWrapper* feedback_ref :
       surface_feedback->GetFeedbackRefs()) {
    SendFeedback(feedback, feedback_ref->GetFeedbackResource());
  }
}

void WaylandDmabufFeedbackManager::RemoveSurfaceFromScanoutCandidates(
    Surface* surface,
    ScanoutReasonFlags reason) {
  auto search = scanout_candidates_.find(surface);
  if (search == scanout_candidates_.end()) {
    return;
  }

  search->second = static_cast<ScanoutReasonFlags>(
      static_cast<uint32_t>(search->second) & ~static_cast<uint32_t>(reason));
  if (search->second == ScanoutReasonFlags::kNone ||
      reason == ScanoutReasonFlags::kNone) {
    scanout_candidates_.erase(surface);
  } else {
    return;
  }

  if (!base::Contains(surface_feedbacks_, surface)) {
    return;
  }

  const auto& surface_feedback = surface_feedbacks_[surface];
  auto* feedback = surface_feedback->GetFeedback();
  if (!feedback->GetScanoutTranche()) {
    return;
  }

  feedback->ClearScanoutTranche();
  for (WaylandDmabufSurfaceFeedbackResourceWrapper* feedback_ref :
       surface_feedback->GetFeedbackRefs()) {
    SendFeedback(feedback, feedback_ref->GetFeedbackResource());
  }
}

void WaylandDmabufFeedbackManager::MaybeResendFeedback(Surface* surface) {
  if (!base::Contains(scanout_candidates_, surface) ||
      !base::Contains(surface_feedbacks_, surface)) {
    return;
  }

  const auto& surface_feedback = surface_feedbacks_[surface];
  auto* feedback = surface_feedback->GetFeedback();
  feedback->MaybeAddScanoutTranche(surface);

  for (WaylandDmabufSurfaceFeedbackResourceWrapper* feedback_ref :
       surface_feedback->GetFeedbackRefs()) {
    SendFeedback(feedback, feedback_ref->GetFeedbackResource());
  }
}

void WaylandDmabufFeedbackManager::SendFeedback(WaylandDmabufFeedback* feedback,
                                                wl_resource* resource) {
  wl_array main_device_buf;
  wl_array_init(&main_device_buf);
  dev_t* device_id_ptr =
      static_cast<dev_t*>(wl_array_add(&main_device_buf, sizeof(dev_t)));
  *device_id_ptr = feedback->GetMainDeviceId();
  zwp_linux_dmabuf_feedback_v1_send_main_device(resource, &main_device_buf);
  wl_array_release(&main_device_buf);

  base::subtle::FDPair fd_pair = shared_memory_region_->GetPlatformHandle();
  zwp_linux_dmabuf_feedback_v1_send_format_table(
      resource, fd_pair.fd, shared_memory_region_->GetSize());

  if (feedback->GetScanoutTranche())
    SendTranche(feedback->GetScanoutTranche(), resource);

  SendTranche(feedback->GetDefaultTranche(), resource);

  zwp_linux_dmabuf_feedback_v1_send_done(resource);
}

void WaylandDmabufFeedbackManager::SendTranche(
    const WaylandDmabufFeedbackTranche* tranche,
    wl_resource* resource) {
  wl_array target_device_buf;
  wl_array_init(&target_device_buf);
  dev_t* device_id_ptr =
      static_cast<dev_t*>(wl_array_add(&target_device_buf, sizeof(dev_t)));
  *device_id_ptr = tranche->GetTargetDeviceId();
  zwp_linux_dmabuf_feedback_v1_send_tranche_target_device(resource,
                                                          &target_device_buf);
  wl_array_release(&target_device_buf);

  zwp_linux_dmabuf_feedback_v1_send_tranche_flags(resource,
                                                  tranche->GetFlags());

  wl_array formats_array;
  wl_array_init(&formats_array);
  for (const auto& [format, modifier_entries] :
       tranche->GetFormatsAndModifiers()) {
    for (const auto& [table_index, modifier] : modifier_entries) {
      uint16_t* format_index_ptr = static_cast<uint16_t*>(
          wl_array_add(&formats_array, sizeof(uint16_t)));
      *format_index_ptr = table_index;
    }
  }
  zwp_linux_dmabuf_feedback_v1_send_tranche_formats(resource, &formats_array);
  wl_array_release(&formats_array);

  zwp_linux_dmabuf_feedback_v1_send_tranche_done(resource);
}

}  // namespace wayland
}  // namespace exo