chromium/components/exo/wayland/zwp_linux_explicit_synchronization.cc

// Copyright 2018 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/zwp_linux_explicit_synchronization.h"
#include "base/memory/raw_ptr.h"

#include <linux-explicit-synchronization-unstable-v1-server-protocol.h>
#include <sync/sync.h>

#include "components/exo/surface.h"
#include "components/exo/surface_observer.h"
#include "components/exo/wayland/server_util.h"
#include "ui/gfx/gpu_fence.h"
#include "ui/gfx/gpu_fence_handle.h"

namespace exo {
namespace wayland {

namespace {

// A property key containing a pointer to the surface_synchronization resource
// associated with the surface object.
DEFINE_UI_CLASS_PROPERTY_KEY(wl_resource*,
                             kSurfaceSynchronizationResource,
                             nullptr)

////////////////////////////////////////////////////////////////////////////////
// linux_buffer_release_v1 interface:

// Implements the buffer release interface.
class LinuxBufferRelease {
 public:
  LinuxBufferRelease(wl_resource* resource, Surface* surface)
      : resource_(resource),
        release_callback_(
            base::BindOnce(&LinuxBufferRelease::HandleExplicitRelease,
                           base::Unretained(this))) {
    surface->SetPerCommitBufferReleaseCallback(release_callback_.callback());
  }

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

 private:
  void HandleExplicitRelease(gfx::GpuFenceHandle release_fence) {
    if (!release_fence.is_null()) {
      // Fd will be dup'd for us.
      zwp_linux_buffer_release_v1_send_fenced_release(resource_,
                                                      release_fence.Peek());
    } else {
      zwp_linux_buffer_release_v1_send_immediate_release(resource_);
    }
    // Protocol specifies that either of these events result in the buffer
    // release object's destruction.
    wl_client_flush(wl_resource_get_client(resource_));
    wl_resource_destroy(resource_);
  }

  raw_ptr<wl_resource> resource_;
  // Use a cancelable callback in case this object is destroyed while a commit
  // is still in flight.
  base::CancelableOnceCallback<void(gfx::GpuFenceHandle)> release_callback_;
};

////////////////////////////////////////////////////////////////////////////////
// linux_surface_synchronization_v1 interface:

// Implements the surface synchronization interface, providing explicit
// synchronization for surface buffers using dma-fences.
class LinuxSurfaceSynchronization : public SurfaceObserver {
 public:
  explicit LinuxSurfaceSynchronization(wl_resource* resource, Surface* surface)
      : surface_(surface) {
    surface_->AddSurfaceObserver(this);
    surface_->SetProperty(kSurfaceSynchronizationResource, resource);
  }

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

  ~LinuxSurfaceSynchronization() override {
    if (surface_) {
      surface_->RemoveSurfaceObserver(this);
      surface_->SetAcquireFence(nullptr);
      surface_->SetProperty(kSurfaceSynchronizationResource,
                            static_cast<wl_resource*>(nullptr));
    }
  }

  Surface* surface() { return surface_; }

  // Overridden from SurfaceObserver:
  void OnSurfaceDestroying(Surface* surface) override {
    surface->RemoveSurfaceObserver(this);
    surface_ = nullptr;
  }

 private:
  raw_ptr<Surface> surface_;
};

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

void linux_surface_synchronization_set_acquire_fence(wl_client* client,
                                                     wl_resource* resource,
                                                     int32_t fd) {
  auto fence_fd = base::ScopedFD(fd);
  auto* surface =
      GetUserDataAs<LinuxSurfaceSynchronization>(resource)->surface();

  if (!surface) {
    wl_resource_post_error(
        resource, ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE,
        "Associated surface has been destroyed");
    return;
  }

  if (surface->HasPendingAcquireFence()) {
    wl_resource_post_error(
        resource, ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_FENCE,
        "surface already has an acquire fence");
    return;
  }

  auto fence_info =
      std::unique_ptr<sync_fence_info_data, void (*)(sync_fence_info_data*)>{
          sync_fence_info(fence_fd.get()), sync_fence_info_free};
  if (!fence_info) {
    wl_resource_post_error(
        resource, ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_INVALID_FENCE,
        "the provided acquire fence is invalid");
    return;
  }

  gfx::GpuFenceHandle handle;
  if (fence_info->status != 1) {
    // Not signalled yet.
    handle.Adopt(std::move(fence_fd));
  }
  surface->SetAcquireFence(std::make_unique<gfx::GpuFence>(std::move(handle)));
}

void linux_surface_synchronization_get_release(wl_client* client,
                                               wl_resource* resource,
                                               uint32_t id) {
  auto* surface =
      GetUserDataAs<LinuxSurfaceSynchronization>(resource)->surface();

  if (!surface) {
    wl_resource_post_error(
        resource, ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_SURFACE,
        "surface no longer exists");
    return;
  }

  if (surface->HasPendingPerCommitBufferReleaseCallback()) {
    wl_resource_post_error(
        resource, ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_DUPLICATE_RELEASE,
        "already has a buffer release");
    return;
  }

  auto* linux_buffer_release_resource =
      wl_resource_create(client, &zwp_linux_buffer_release_v1_interface,
                         wl_resource_get_version(resource), id);

  SetImplementation(linux_buffer_release_resource, nullptr,
                    std::make_unique<LinuxBufferRelease>(
                        linux_buffer_release_resource, surface));
}

const struct zwp_linux_surface_synchronization_v1_interface
    linux_surface_synchronization_implementation = {
        linux_surface_synchronization_destroy,
        linux_surface_synchronization_set_acquire_fence,
        linux_surface_synchronization_get_release,
};

////////////////////////////////////////////////////////////////////////////////
// linux_explicit_synchronization_v1 interface:

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

void linux_explicit_synchronization_get_synchronization(
    wl_client* client,
    wl_resource* resource,
    uint32_t id,
    wl_resource* surface_resource) {
  Surface* surface = GetUserDataAs<Surface>(surface_resource);
  if (surface->GetProperty(kSurfaceSynchronizationResource) != nullptr) {
    wl_resource_post_error(
        resource,
        ZWP_LINUX_EXPLICIT_SYNCHRONIZATION_V1_ERROR_SYNCHRONIZATION_EXISTS,
        "a synchronization object for the surface already exists");
    return;
  }

  wl_resource* linux_surface_synchronization_resource = wl_resource_create(
      client, &zwp_linux_surface_synchronization_v1_interface,
      wl_resource_get_version(resource), id);

  SetImplementation(linux_surface_synchronization_resource,
                    &linux_surface_synchronization_implementation,
                    std::make_unique<LinuxSurfaceSynchronization>(
                        linux_surface_synchronization_resource, surface));
}

const struct zwp_linux_explicit_synchronization_v1_interface
    linux_explicit_synchronization_implementation = {
        linux_explicit_synchronization_destroy,
        linux_explicit_synchronization_get_synchronization};

}  // namespace

void bind_linux_explicit_synchronization(wl_client* client,
                                         void* data,
                                         uint32_t version,
                                         uint32_t id) {
  wl_resource* resource = wl_resource_create(
      client, &zwp_linux_explicit_synchronization_v1_interface, version, id);

  wl_resource_set_implementation(resource,
                                 &linux_explicit_synchronization_implementation,
                                 nullptr, nullptr);
}

bool linux_surface_synchronization_validate_commit(Surface* surface) {
  if (surface->HasPendingAttachedBuffer())
    return true;

  if (surface->HasPendingAcquireFence()) {
    wl_resource* linux_surface_synchronization_resource =
        surface->GetProperty(kSurfaceSynchronizationResource);
    DCHECK(linux_surface_synchronization_resource);

    wl_resource_post_error(
        linux_surface_synchronization_resource,
        ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER,
        "surface has acquire fence but no buffer for synchronization");

    return false;
  }

  if (surface->HasPendingPerCommitBufferReleaseCallback()) {
    wl_resource* linux_surface_synchronization_resource =
        surface->GetProperty(kSurfaceSynchronizationResource);
    DCHECK(linux_surface_synchronization_resource);

    wl_resource_post_error(
        linux_surface_synchronization_resource,
        ZWP_LINUX_SURFACE_SYNCHRONIZATION_V1_ERROR_NO_BUFFER,
        "surface has buffer_release but no buffer for synchronization");

    return false;
  }

  return true;
}

}  // namespace wayland
}  // namespace exo