/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/layers/NativeLayerWayland.h"

#include <dlfcn.h>
#include <utility>
#include <algorithm>

#include "gfxUtils.h"
#include "nsGtkUtils.h"
#include "GLContextProvider.h"
#include "GLBlitHelper.h"
#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/layers/SurfacePoolWayland.h"
#include "mozilla/StaticPrefs_widget.h"
#include "mozilla/webrender/RenderThread.h"

namespace mozilla::layers {

using gfx::BackendType;
using gfx::DrawTarget;
using gfx::IntPoint;
using gfx::IntRect;
using gfx::IntRegion;
using gfx::IntSize;
using gfx::Matrix4x4;
using gfx::Point;
using gfx::Rect;
using gfx::SamplingFilter;
using gfx::Size;

static const struct wl_callback_listener sFrameListenerNativeLayerWayland = {
    NativeLayerWayland::FrameCallbackHandler};

CallbackMultiplexHelper::CallbackMultiplexHelper(CallbackFunc aCallbackFunc,
                                                 void* aCallbackData)
    : mCallbackFunc(aCallbackFunc), mCallbackData(aCallbackData) {}

void CallbackMultiplexHelper::Callback(uint32_t aTime) {
  if (!mActive) {
    return;
  }
  mActive = false;

  // This is likely the first of a batch of frame callbacks being processed and
  // may trigger the setup of a successive one. In order to avoid complexity,
  // defer calling the callback function until we had a chance to process
  // all pending frame callbacks.

  AddRef();
  nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<uint32_t>(
      "layers::CallbackMultiplexHelper::RunCallback", this,
      &CallbackMultiplexHelper::RunCallback, aTime);
  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThreadQueue(
      runnable.forget(), EventQueuePriority::Vsync));
}

void CallbackMultiplexHelper::RunCallback(uint32_t aTime) {
  mCallbackFunc(mCallbackData, aTime);
  Release();
}

/* static */
already_AddRefed<NativeLayerRootWayland>
NativeLayerRootWayland::CreateForMozContainer(MozContainer* aContainer) {
  RefPtr<NativeLayerRootWayland> layerRoot =
      new NativeLayerRootWayland(aContainer);
  return layerRoot.forget();
}

NativeLayerRootWayland::NativeLayerRootWayland(MozContainer* aContainer)
    : mMutex("NativeLayerRootWayland"), mContainer(aContainer) {
  g_object_ref(mContainer);
}

NativeLayerRootWayland::~NativeLayerRootWayland() {
  GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mContainer));
  if (gdkWindow) {
    GdkFrameClock* frameClock = gdk_window_get_frame_clock(gdkWindow);
    g_signal_handlers_disconnect_by_data(frameClock, this);
  }
  g_object_unref(mContainer);
}

already_AddRefed<NativeLayer> NativeLayerRootWayland::CreateLayer(
    const IntSize& aSize, bool aIsOpaque,
    SurfacePoolHandle* aSurfacePoolHandle) {
  RefPtr<NativeLayer> layer = new NativeLayerWayland(
      aSize, aIsOpaque, aSurfacePoolHandle->AsSurfacePoolHandleWayland());
  return layer.forget();
}

already_AddRefed<NativeLayer>
NativeLayerRootWayland::CreateLayerForExternalTexture(bool aIsOpaque) {
  RefPtr<NativeLayer> layer = new NativeLayerWayland(aIsOpaque);
  return layer.forget();
}

void NativeLayerRootWayland::AppendLayer(NativeLayer* aLayer) {
  MOZ_RELEASE_ASSERT(false);
  MutexAutoLock lock(mMutex);

  RefPtr<NativeLayerWayland> layerWayland = aLayer->AsNativeLayerWayland();
  MOZ_RELEASE_ASSERT(layerWayland);

  mSublayers.AppendElement(layerWayland);
}

void NativeLayerRootWayland::RemoveLayer(NativeLayer* aLayer) {
  MOZ_RELEASE_ASSERT(false);
  MutexAutoLock lock(mMutex);

  RefPtr<NativeLayerWayland> layerWayland = aLayer->AsNativeLayerWayland();
  MOZ_RELEASE_ASSERT(layerWayland);

  mSublayers.RemoveElement(layerWayland);
}

void NativeLayerRootWayland::SetLayers(
    const nsTArray<RefPtr<NativeLayer>>& aLayers) {
  MutexAutoLock lock(mMutex);

  nsTArray<RefPtr<NativeLayerWayland>> newSublayers(aLayers.Length());
  for (const RefPtr<NativeLayer>& sublayer : aLayers) {
    RefPtr<NativeLayerWayland> layer = sublayer->AsNativeLayerWayland();
    newSublayers.AppendElement(layer);
  }

  if (newSublayers != mSublayers) {
    for (const RefPtr<NativeLayerWayland>& layer : mSublayers) {
      if (!newSublayers.Contains(layer)) {
        mOldSublayers.AppendElement(layer);
      }
    }
    mSublayers = std::move(newSublayers);
    mNewLayers = true;
  }
}

bool NativeLayerRootWayland::CommitToScreen() {
  MutexAutoLock lock(mMutex);
  return CommitToScreen(lock);
}

bool NativeLayerRootWayland::CommitToScreen(const MutexAutoLock& aProofOfLock) {
  mFrameInProcess = false;

  MozContainerSurfaceLock lock(mContainer);
  struct wl_surface* containerSurface = lock.GetSurface();
  if (!containerSurface) {
    if (!mCallbackRequested) {
      RefPtr<NativeLayerRootWayland> self(this);
      moz_container_wayland_add_initial_draw_callback_locked(
          mContainer, [self]() -> void {
            MutexAutoLock lock(self->mMutex);
            if (!self->mFrameInProcess) {
              self->CommitToScreen(lock);
            }
            self->mCallbackRequested = false;
          });
      mCallbackRequested = true;
    }
    return true;
  }

  wl_surface* previousSurface = nullptr;
  for (RefPtr<NativeLayerWayland>& layer : mSublayers) {
    layer->EnsureParentSurface(containerSurface);

    if (mNewLayers) {
      wl_subsurface_place_above(layer->mWlSubsurface, previousSurface
                                                          ? previousSurface
                                                          : containerSurface);
      previousSurface = layer->mWlSurface;
    }

    MOZ_RELEASE_ASSERT(layer->mTransform.Is2D());
    auto transform2D = layer->mTransform.As2D();

    Rect surfaceRectClipped =
        Rect(0, 0, (float)layer->mSize.width, (float)layer->mSize.height);
    surfaceRectClipped =
        surfaceRectClipped.Intersect(Rect(layer->mDisplayRect));

    transform2D.PostTranslate((float)layer->mPosition.x,
                              (float)layer->mPosition.y);
    surfaceRectClipped = transform2D.TransformBounds(surfaceRectClipped);

    if (layer->mClipRect) {
      surfaceRectClipped =
          surfaceRectClipped.Intersect(Rect(layer->mClipRect.value()));
    }

    if (roundf(surfaceRectClipped.width) > 0 &&
        roundf(surfaceRectClipped.height) > 0) {
      layer->SetBufferTransformFlipped(transform2D._11 < 0.0,
                                       transform2D._22 < 0.0);

      double bufferScale = moz_container_wayland_get_scale(mContainer);
      layer->SetSubsurfacePosition(floor(surfaceRectClipped.x / bufferScale),
                                   floor(surfaceRectClipped.y / bufferScale));
      layer->SetViewportDestinationSize(
          ceil(surfaceRectClipped.width / bufferScale),
          ceil(surfaceRectClipped.height / bufferScale));

      auto transform2DInversed = transform2D.Inverse();
      Rect bufferClip = transform2DInversed.TransformBounds(surfaceRectClipped);
      layer->SetViewportSourceRect(bufferClip);

      layer->Commit();
    } else {
      layer->Unmap();
    }
  }

  if (mNewLayers) {
    for (RefPtr<NativeLayerWayland>& layer : mOldSublayers) {
      layer->Unmap();
    }
    mOldSublayers.Clear();

    nsCOMPtr<nsIRunnable> updateLayersRunnable = NewRunnableMethod<>(
        "layers::NativeLayerRootWayland::UpdateLayersOnMainThread", this,
        &NativeLayerRootWayland::UpdateLayersOnMainThread);
    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThreadQueue(
        updateLayersRunnable.forget(), EventQueuePriority::Normal));
    mNewLayers = false;
  }

  if (containerSurface != mWlSurface) {
    if (!mShmBuffer) {
      mShmBuffer = widget::WaylandBufferSHM::Create(LayoutDeviceIntSize(1, 1));
      mShmBuffer->Clear();
    }
    mShmBuffer->AttachAndCommit(containerSurface);
    mWlSurface = containerSurface;
  } else {
    wl_surface_commit(containerSurface);
  }

  wl_display_flush(widget::WaylandDisplayGet()->GetDisplay());
  return true;
}

void NativeLayerRootWayland::RequestFrameCallback(CallbackFunc aCallbackFunc,
                                                  void* aCallbackData) {
  MutexAutoLock lock(mMutex);

  mCallbackMultiplexHelper =
      new CallbackMultiplexHelper(aCallbackFunc, aCallbackData);

  for (const RefPtr<NativeLayerWayland>& layer : mSublayersOnMainThread) {
    layer->RequestFrameCallback(mCallbackMultiplexHelper);
  }

  MozContainerSurfaceLock lockContainer(mContainer);
  struct wl_surface* wlSurface = lockContainer.GetSurface();
  if (wlSurface) {
    wl_surface_commit(wlSurface);
    wl_display_flush(widget::WaylandDisplayGet()->GetDisplay());
  }
}

static void sAfterFrameClockAfterPaint(
    GdkFrameClock* aClock, NativeLayerRootWayland* aNativeLayerRoot) {
  aNativeLayerRoot->AfterFrameClockAfterPaint();
}

void NativeLayerRootWayland::AfterFrameClockAfterPaint() {
  MutexAutoLock lock(mMutex);
  MozContainerSurfaceLock lockContainer(mContainer);
  struct wl_surface* containerSurface = lockContainer.GetSurface();
  for (const RefPtr<NativeLayerWayland>& layer : mSublayersOnMainThread) {
    wl_surface_commit(layer->mWlSurface);
  }
  if (containerSurface) {
    wl_surface_commit(containerSurface);
  }
}

void NativeLayerRootWayland::UpdateLayersOnMainThread() {
  AssertIsOnMainThread();
  MutexAutoLock lock(mMutex);

  static auto sGdkWaylandWindowAddCallbackSurface =
      (void (*)(GdkWindow*, struct wl_surface*))dlsym(
          RTLD_DEFAULT, "gdk_wayland_window_add_frame_callback_surface");
  static auto sGdkWaylandWindowRemoveCallbackSurface =
      (void (*)(GdkWindow*, struct wl_surface*))dlsym(
          RTLD_DEFAULT, "gdk_wayland_window_remove_frame_callback_surface");

  MozContainerSurfaceLock lockContainer(mContainer);
  struct wl_surface* containerSurface = lockContainer.GetSurface();
  GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mContainer));

  mSublayersOnMainThread.RemoveElementsBy([&](const auto& layer) {
    if (!mSublayers.Contains(layer)) {
      if (layer->IsOpaque() &&
          StaticPrefs::widget_wayland_opaque_region_enabled_AtStartup() &&
          sGdkWaylandWindowAddCallbackSurface && gdkWindow) {
        sGdkWaylandWindowRemoveCallbackSurface(gdkWindow, layer->mWlSurface);

        wl_compositor* compositor =
            widget::WaylandDisplayGet()->GetCompositor();
        wl_region* region = wl_compositor_create_region(compositor);
        wl_surface_set_opaque_region(layer->mWlSurface, region);
        wl_region_destroy(region);
        wl_surface_commit(layer->mWlSurface);
      }
      return true;
    }
    return false;
  });

  for (const RefPtr<NativeLayerWayland>& layer : mSublayers) {
    if (!mSublayersOnMainThread.Contains(layer)) {
      if (layer->IsOpaque() &&
          StaticPrefs::widget_wayland_opaque_region_enabled_AtStartup() &&
          sGdkWaylandWindowRemoveCallbackSurface && gdkWindow) {
        sGdkWaylandWindowAddCallbackSurface(gdkWindow, layer->mWlSurface);

        wl_compositor* compositor =
            widget::WaylandDisplayGet()->GetCompositor();
        wl_region* region = wl_compositor_create_region(compositor);
        wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
        wl_surface_set_opaque_region(layer->mWlSurface, region);
        wl_region_destroy(region);
        wl_surface_commit(layer->mWlSurface);
      }
      if (mCallbackMultiplexHelper && mCallbackMultiplexHelper->IsActive()) {
        layer->RequestFrameCallback(mCallbackMultiplexHelper);
      }
      mSublayersOnMainThread.AppendElement(layer);
    }
  }

  if (containerSurface) {
    wl_surface_commit(containerSurface);
  }

  if (!mGdkAfterPaintId && gdkWindow) {
    GdkFrameClock* frameClock = gdk_window_get_frame_clock(gdkWindow);
    mGdkAfterPaintId =
        g_signal_connect_after(frameClock, "after-paint",
                               G_CALLBACK(sAfterFrameClockAfterPaint), this);
  }
}

NativeLayerWayland::NativeLayerWayland(
    const IntSize& aSize, bool aIsOpaque,
    SurfacePoolHandleWayland* aSurfacePoolHandle)
    : mMutex("NativeLayerWayland"),
      mSurfacePoolHandle(aSurfacePoolHandle),
      mSize(aSize),
      mIsOpaque(aIsOpaque) {
  MOZ_RELEASE_ASSERT(mSurfacePoolHandle,
                     "Need a non-null surface pool handle.");

  widget::nsWaylandDisplay* waylandDisplay = widget::WaylandDisplayGet();
  wl_compositor* compositor = waylandDisplay->GetCompositor();
  mWlSurface = wl_compositor_create_surface(compositor);

  wl_region* region = wl_compositor_create_region(compositor);
  wl_surface_set_input_region(mWlSurface, region);
  wl_region_destroy(region);

  wp_viewporter* viewporter = waylandDisplay->GetViewporter();
  mViewport = wp_viewporter_get_viewport(viewporter, mWlSurface);
}

NativeLayerWayland::NativeLayerWayland(bool aIsOpaque)
    : mMutex("NativeLayerWayland"),
      mSurfacePoolHandle(nullptr),
      mIsOpaque(aIsOpaque) {
  MOZ_RELEASE_ASSERT(false);  // external image
}

NativeLayerWayland::~NativeLayerWayland() {
  MutexAutoLock lock(mMutex);

  if (mInProgressBuffer) {
    mSurfacePoolHandle->ReturnBufferToPool(mInProgressBuffer);
    mInProgressBuffer = nullptr;
  }
  if (mFrontBuffer) {
    mSurfacePoolHandle->ReturnBufferToPool(mFrontBuffer);
    mFrontBuffer = nullptr;
  }
  MozClearPointer(mCallback, wl_callback_destroy);
  MozClearPointer(mViewport, wp_viewport_destroy);
  MozClearPointer(mWlSubsurface, wl_subsurface_destroy);
  MozClearPointer(mWlSurface, wl_surface_destroy);
}

void NativeLayerWayland::AttachExternalImage(
    wr::RenderTextureHost* aExternalImage) {
  MOZ_RELEASE_ASSERT(false);
}

void NativeLayerWayland::SetSurfaceIsFlipped(bool aIsFlipped) {
  MutexAutoLock lock(mMutex);

  if (aIsFlipped != mSurfaceIsFlipped) {
    mSurfaceIsFlipped = aIsFlipped;
  }
}

bool NativeLayerWayland::SurfaceIsFlipped() {
  MutexAutoLock lock(mMutex);

  return mSurfaceIsFlipped;
}

IntSize NativeLayerWayland::GetSize() {
  MutexAutoLock lock(mMutex);
  return mSize;
}

void NativeLayerWayland::SetPosition(const IntPoint& aPosition) {
  MutexAutoLock lock(mMutex);

  if (aPosition != mPosition) {
    mPosition = aPosition;
  }
}

IntPoint NativeLayerWayland::GetPosition() {
  MutexAutoLock lock(mMutex);
  return mPosition;
}

void NativeLayerWayland::SetTransform(const Matrix4x4& aTransform) {
  MutexAutoLock lock(mMutex);
  MOZ_ASSERT(aTransform.IsRectilinear());

  if (aTransform != mTransform) {
    mTransform = aTransform;
  }
}

void NativeLayerWayland::SetSamplingFilter(SamplingFilter aSamplingFilter) {
  MutexAutoLock lock(mMutex);

  if (aSamplingFilter != mSamplingFilter) {
    mSamplingFilter = aSamplingFilter;
  }
}

Matrix4x4 NativeLayerWayland::GetTransform() {
  MutexAutoLock lock(mMutex);
  return mTransform;
}

IntRect NativeLayerWayland::GetRect() {
  MutexAutoLock lock(mMutex);
  return IntRect(mPosition, mSize);
}

bool NativeLayerWayland::IsOpaque() {
  MutexAutoLock lock(mMutex);
  return mIsOpaque;
}

void NativeLayerWayland::SetClipRect(const Maybe<IntRect>& aClipRect) {
  MutexAutoLock lock(mMutex);

  if (aClipRect != mClipRect) {
    mClipRect = aClipRect;
  }
}

Maybe<IntRect> NativeLayerWayland::ClipRect() {
  MutexAutoLock lock(mMutex);
  return mClipRect;
}

IntRect NativeLayerWayland::CurrentSurfaceDisplayRect() {
  MutexAutoLock lock(mMutex);
  return mDisplayRect;
}

RefPtr<DrawTarget> NativeLayerWayland::NextSurfaceAsDrawTarget(
    const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
    BackendType aBackendType) {
  MutexAutoLock lock(mMutex);

  mDisplayRect = IntRect(aDisplayRect);
  mDirtyRegion = IntRegion(aUpdateRegion);

  MOZ_ASSERT(!mInProgressBuffer);
  if (mFrontBuffer && !mFrontBuffer->IsAttached()) {
    // the Wayland compositor released the buffer early, we can reuse it
    mInProgressBuffer = std::move(mFrontBuffer);
  } else {
    mInProgressBuffer = mSurfacePoolHandle->ObtainBufferFromPool(mSize);
    if (mFrontBuffer) {
      HandlePartialUpdate(lock);
      mSurfacePoolHandle->ReturnBufferToPool(mFrontBuffer);
    }
  }
  mFrontBuffer = nullptr;

  if (!mInProgressBuffer) {
    gfxCriticalError() << "Failed to obtain buffer";
    wr::RenderThread::Get()->HandleWebRenderError(
        wr::WebRenderError::NEW_SURFACE);
    return nullptr;
  }

  return mInProgressBuffer->Lock();
}

Maybe<GLuint> NativeLayerWayland::NextSurfaceAsFramebuffer(
    const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
    bool aNeedsDepth) {
  MutexAutoLock lock(mMutex);

  mDisplayRect = IntRect(aDisplayRect);
  mDirtyRegion = IntRegion(aUpdateRegion);

  MOZ_ASSERT(!mInProgressBuffer);
  if (mFrontBuffer && !mFrontBuffer->IsAttached()) {
    // the Wayland compositor released the buffer early, we can reuse it
    mInProgressBuffer = std::move(mFrontBuffer);
    mFrontBuffer = nullptr;
  } else {
    mInProgressBuffer = mSurfacePoolHandle->ObtainBufferFromPool(mSize);
  }

  if (!mInProgressBuffer) {
    gfxCriticalError() << "Failed to obtain buffer";
    wr::RenderThread::Get()->HandleWebRenderError(
        wr::WebRenderError::NEW_SURFACE);
    return Nothing();
  }

  // get the framebuffer before handling partial damage so we don't accidently
  // create one without depth buffer
  Maybe<GLuint> fbo = mSurfacePoolHandle->GetFramebufferForBuffer(
      mInProgressBuffer, aNeedsDepth);
  MOZ_RELEASE_ASSERT(fbo, "GetFramebufferForBuffer failed.");

  if (mFrontBuffer) {
    HandlePartialUpdate(lock);
    mSurfacePoolHandle->ReturnBufferToPool(mFrontBuffer);
    mFrontBuffer = nullptr;
  }

  return fbo;
}

void NativeLayerWayland::HandlePartialUpdate(
    const MutexAutoLock& aProofOfLock) {
  IntRegion copyRegion = IntRegion(mDisplayRect);
  copyRegion.SubOut(mDirtyRegion);

  if (!copyRegion.IsEmpty()) {
    if (mSurfacePoolHandle->gl()) {
      mSurfacePoolHandle->gl()->MakeCurrent();
      for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
        gfx::IntRect r = iter.Get();
        Maybe<GLuint> sourceFB =
            mSurfacePoolHandle->GetFramebufferForBuffer(mFrontBuffer, false);
        Maybe<GLuint> destFB = mSurfacePoolHandle->GetFramebufferForBuffer(
            mInProgressBuffer, false);
        MOZ_RELEASE_ASSERT(sourceFB && destFB);
        mSurfacePoolHandle->gl()->BlitHelper()->BlitFramebufferToFramebuffer(
            sourceFB.value(), destFB.value(), r, r, LOCAL_GL_NEAREST);
      }
    } else {
      RefPtr<gfx::DataSourceSurface> dataSourceSurface =
          gfx::CreateDataSourceSurfaceFromData(
              mSize, mFrontBuffer->GetSurfaceFormat(),
              (const uint8_t*)mFrontBuffer->GetImageData(),
              mSize.width * BytesPerPixel(mFrontBuffer->GetSurfaceFormat()));
      RefPtr<DrawTarget> dt = mInProgressBuffer->Lock();

      for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
        IntRect r = iter.Get();
        dt->CopySurface(dataSourceSurface, r, IntPoint(r.x, r.y));
      }
    }
  }
}

void NativeLayerWayland::NotifySurfaceReady() {
  MOZ_ASSERT(!mFrontBuffer);
  MOZ_ASSERT(mInProgressBuffer);
  mFrontBuffer = mInProgressBuffer;
  mInProgressBuffer = nullptr;
}

void NativeLayerWayland::DiscardBackbuffers() {}

void NativeLayerWayland::Commit() {
  MutexAutoLock lock(mMutex);

  if (mDirtyRegion.IsEmpty() && mHasBufferAttached) {
    wl_surface_commit(mWlSurface);
    return;
  }

  for (auto iter = mDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
    IntRect r = iter.Get();
    wl_surface_damage_buffer(mWlSurface, r.x, r.y, r.width, r.height);
  }

  MOZ_ASSERT(mFrontBuffer);
  mFrontBuffer->AttachAndCommit(mWlSurface);
  mHasBufferAttached = true;
  mDirtyRegion.SetEmpty();
}

void NativeLayerWayland::Unmap() {
  MutexAutoLock lock(mMutex);

  if (!mHasBufferAttached) {
    return;
  }

  wl_surface_attach(mWlSurface, nullptr, 0, 0);
  wl_surface_commit(mWlSurface);
  mHasBufferAttached = false;
}

void NativeLayerWayland::EnsureParentSurface(wl_surface* aParentSurface) {
  MutexAutoLock lock(mMutex);

  if (aParentSurface != mParentWlSurface) {
    MozClearPointer(mWlSubsurface, wl_subsurface_destroy);
    mSubsurfacePosition = IntPoint(0, 0);

    if (aParentSurface) {
      wl_subcompositor* subcompositor =
          widget::WaylandDisplayGet()->GetSubcompositor();
      mWlSubsurface = wl_subcompositor_get_subsurface(subcompositor, mWlSurface,
                                                      aParentSurface);
    }
    mParentWlSurface = aParentSurface;
  }
}

void NativeLayerWayland::SetBufferTransformFlipped(bool aFlippedX,
                                                   bool aFlippedY) {
  MutexAutoLock lock(mMutex);

  if (aFlippedX == mBufferTransformFlippedX &&
      aFlippedY == mBufferTransformFlippedY) {
    return;
  }

  mBufferTransformFlippedX = aFlippedX;
  mBufferTransformFlippedY = aFlippedY;

  if (mBufferTransformFlippedY) {
    if (mBufferTransformFlippedX) {
      wl_surface_set_buffer_transform(mWlSurface, WL_OUTPUT_TRANSFORM_180);
    } else {
      wl_surface_set_buffer_transform(mWlSurface,
                                      WL_OUTPUT_TRANSFORM_FLIPPED_180);
    }
  } else {
    if (mBufferTransformFlippedX) {
      wl_surface_set_buffer_transform(mWlSurface, WL_OUTPUT_TRANSFORM_FLIPPED);
    } else {
      wl_surface_set_buffer_transform(mWlSurface, WL_OUTPUT_TRANSFORM_NORMAL);
    }
  }
}

void NativeLayerWayland::SetSubsurfacePosition(int aX, int aY) {
  MutexAutoLock lock(mMutex);

  if ((aX == mSubsurfacePosition.x && aY == mSubsurfacePosition.y) ||
      !mWlSubsurface) {
    return;
  }

  mSubsurfacePosition.x = aX;
  mSubsurfacePosition.y = aY;
  wl_subsurface_set_position(mWlSubsurface, mSubsurfacePosition.x,
                             mSubsurfacePosition.y);
}

void NativeLayerWayland::SetViewportSourceRect(const Rect aSourceRect) {
  MutexAutoLock lock(mMutex);

  Rect bufferRect = Rect(0, 0, mSize.width, mSize.height);
  Rect sourceRect = aSourceRect.Intersect(bufferRect);

  if (mViewportSourceRect == sourceRect) {
    return;
  }

  mViewportSourceRect = sourceRect;
  wp_viewport_set_source(mViewport, wl_fixed_from_double(mViewportSourceRect.x),
                         wl_fixed_from_double(mViewportSourceRect.y),
                         wl_fixed_from_double(mViewportSourceRect.width),
                         wl_fixed_from_double(mViewportSourceRect.height));
}

void NativeLayerWayland::SetViewportDestinationSize(int aWidth, int aHeight) {
  MutexAutoLock lock(mMutex);

  if (aWidth == mViewportDestinationSize.width &&
      aHeight == mViewportDestinationSize.height) {
    return;
  }

  mViewportDestinationSize.width = aWidth;
  mViewportDestinationSize.height = aHeight;
  wp_viewport_set_destination(mViewport, mViewportDestinationSize.width,
                              mViewportDestinationSize.height);
}

void NativeLayerWayland::RequestFrameCallback(
    const RefPtr<CallbackMultiplexHelper>& aMultiplexHelper) {
  MutexAutoLock lock(mMutex);
  MOZ_ASSERT(aMultiplexHelper->IsActive());

  // Avoid piling up old helpers if this surface does not receive callbacks
  // for a longer time
  mCallbackMultiplexHelpers.RemoveElementsBy(
      [&](const auto& object) { return !object->IsActive(); });

  mCallbackMultiplexHelpers.AppendElement(aMultiplexHelper);
  if (!mCallback) {
    mCallback = wl_surface_frame(mWlSurface);
    wl_callback_add_listener(mCallback, &sFrameListenerNativeLayerWayland,
                             this);
    wl_surface_commit(mWlSurface);
  }
}

void NativeLayerWayland::FrameCallbackHandler(wl_callback* aCallback,
                                              uint32_t aTime) {
  MutexAutoLock lock(mMutex);

  MOZ_RELEASE_ASSERT(aCallback == mCallback);
  MozClearPointer(mCallback, wl_callback_destroy);

  for (const RefPtr<CallbackMultiplexHelper>& callbackMultiplexHelper :
       mCallbackMultiplexHelpers) {
    callbackMultiplexHelper->Callback(aTime);
  }
  mCallbackMultiplexHelpers.Clear();
}

/* static */
void NativeLayerWayland::FrameCallbackHandler(void* aData,
                                              wl_callback* aCallback,
                                              uint32_t aTime) {
  auto surface = reinterpret_cast<NativeLayerWayland*>(aData);
  surface->FrameCallbackHandler(aCallback, aTime);
}

}  // namespace mozilla::layers
