/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

#ifndef MOZILLA_LAYERS_WEBRENDERAPI_H
#define MOZILLA_LAYERS_WEBRENDERAPI_H

#include <queue>
#include <stdint.h>
#include <vector>
#include <unordered_map>
#include <unordered_set>

#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/gfx/CompositorHitTestInfo.h"
#include "mozilla/layers/IpcResourceUpdateQueue.h"
#include "mozilla/layers/RemoteTextureMap.h"
#include "mozilla/layers/ScrollableLayerGuid.h"
#include "mozilla/layers/SyncObject.h"
#include "mozilla/layers/CompositionRecorder.h"
#include "mozilla/MozPromise.h"
#include "mozilla/Range.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/VsyncDispatcher.h"
#include "mozilla/webrender/webrender_ffi.h"
#include "mozilla/webrender/WebRenderTypes.h"
#include "nsString.h"
#include "GLTypes.h"
#include "Units.h"

class gfxContext;

#undef None

namespace mozilla {

class nsDisplayItem;
class nsPaintedDisplayItem;
class nsDisplayTransform;
class nsDisplayListBuilder;
struct DisplayItemClipChain;

struct ActiveScrolledRoot;

namespace widget {
class CompositorWidget;
}

namespace layers {
class CompositorBridgeParent;
class DisplayItemCache;
class WebRenderBridgeParent;
class RenderRootStateManager;
class StackingContextHelper;
struct DisplayListData;
}  // namespace layers

namespace layout {
class TextDrawTarget;
}

namespace wr {

class DisplayListBuilder;
class RendererOGL;
class RendererEvent;
class WebRenderAPI;

// This isn't part of WR's API, but we define it here to simplify layout's
// logic and data plumbing.
struct Line {
  wr::LayoutRect bounds;
  float wavyLineThickness;
  wr::LineOrientation orientation;
  wr::ColorF color;
  wr::LineStyle style;
};

/// A handler that can be bundled into a transaction and notified at specific
/// points in the rendering pipeline, such as after scene building or after
/// frame building.
///
/// If for any reason the handler is dropped before reaching the requested
/// point, it is notified with the value Checkpoint::TransactionDropped.
/// So it is safe to assume that the handler will be notified "at some point".
class NotificationHandler {
 public:
  virtual void Notify(wr::Checkpoint aCheckpoint) = 0;
  virtual ~NotificationHandler() = default;
};

struct WrHitResult {
  layers::LayersId mLayersId;
  layers::ScrollableLayerGuid::ViewID mScrollId;
  gfx::CompositorHitTestInfo mHitInfo;
  SideBits mSideBits;
  Maybe<uint64_t> mAnimationId;
};

class TransactionBuilder final {
 public:
  explicit TransactionBuilder(WebRenderAPI* aApi,
                              bool aUseSceneBuilderThread = true);

  ~TransactionBuilder();

  void SetLowPriority(bool aIsLowPriority);

  void UpdateEpoch(PipelineId aPipelineId, Epoch aEpoch);

  void SetRootPipeline(PipelineId aPipelineId);

  void RemovePipeline(PipelineId aPipelineId);

  void SetDisplayList(Epoch aEpoch, wr::WrPipelineId pipeline_id,
                      wr::BuiltDisplayListDescriptor dl_descriptor,
                      wr::Vec<uint8_t>& dl_items_data,
                      wr::Vec<uint8_t>& dl_cache_data,
                      wr::Vec<uint8_t>& dl_spatial_tree);

  void ClearDisplayList(Epoch aEpoch, wr::WrPipelineId aPipeline);

  void GenerateFrame(const VsyncId& aVsyncId, wr::RenderReasons aReasons);

  void InvalidateRenderedFrame(wr::RenderReasons aReasons);

  void SetDocumentView(const LayoutDeviceIntRect& aDocRect);

  bool IsEmpty() const;

  bool IsResourceUpdatesEmpty() const;

  bool IsRenderedFrameInvalidated() const;

  void AddImage(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
                wr::Vec<uint8_t>& aBytes);

  void AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor,
                    uint16_t aTileSize, wr::Vec<uint8_t>& aBytes,
                    const wr::DeviceIntRect& aVisibleRect);

  void AddExternalImageBuffer(ImageKey key, const ImageDescriptor& aDescriptor,
                              ExternalImageId aHandle);

  void AddExternalImage(ImageKey key, const ImageDescriptor& aDescriptor,
                        ExternalImageId aExtID,
                        wr::ExternalImageType aImageType,
                        uint8_t aChannelIndex = 0);

  void UpdateImageBuffer(wr::ImageKey aKey, const ImageDescriptor& aDescriptor,
                         wr::Vec<uint8_t>& aBytes);

  void UpdateBlobImage(wr::BlobImageKey aKey,
                       const ImageDescriptor& aDescriptor,
                       wr::Vec<uint8_t>& aBytes,
                       const wr::DeviceIntRect& aVisibleRect,
                       const wr::LayoutIntRect& aDirtyRect);

  void UpdateExternalImage(ImageKey aKey, const ImageDescriptor& aDescriptor,
                           ExternalImageId aExtID,
                           wr::ExternalImageType aImageType,
                           uint8_t aChannelIndex = 0);

  void UpdateExternalImageWithDirtyRect(ImageKey aKey,
                                        const ImageDescriptor& aDescriptor,
                                        ExternalImageId aExtID,
                                        wr::ExternalImageType aImageType,
                                        const wr::DeviceIntRect& aDirtyRect,
                                        uint8_t aChannelIndex = 0);

  void SetBlobImageVisibleArea(BlobImageKey aKey,
                               const wr::DeviceIntRect& aArea);

  void DeleteImage(wr::ImageKey aKey);

  void DeleteBlobImage(wr::BlobImageKey aKey);

  void AddRawFont(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes, uint32_t aIndex);

  void AddFontDescriptor(wr::FontKey aKey, wr::Vec<uint8_t>& aBytes,
                         uint32_t aIndex);

  void DeleteFont(wr::FontKey aKey);

  void AddFontInstance(wr::FontInstanceKey aKey, wr::FontKey aFontKey,
                       float aGlyphSize,
                       const wr::FontInstanceOptions* aOptions,
                       const wr::FontInstancePlatformOptions* aPlatformOptions,
                       wr::Vec<uint8_t>& aVariations);

  void DeleteFontInstance(wr::FontInstanceKey aKey);

  void UpdateQualitySettings(bool aForceSubpixelAAWherePossible);

  void Notify(wr::Checkpoint aWhen, UniquePtr<NotificationHandler> aHandler);

  void Clear();

  Transaction* Take();

  bool UseSceneBuilderThread() const { return mUseSceneBuilderThread; }
  layers::WebRenderBackend GetBackendType() { return mApiBackend; }
  Transaction* Raw() { return mTxn; }

 protected:
  bool mUseSceneBuilderThread;
  layers::WebRenderBackend mApiBackend;
  Transaction* mTxn;
};

class TransactionWrapper final {
 public:
  explicit TransactionWrapper(Transaction* aTxn);

  void AppendDynamicProperties(
      const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
      const nsTArray<wr::WrTransformProperty>& aTransformArray,
      const nsTArray<wr::WrColorProperty>& aColorArray);
  void AppendTransformProperties(
      const nsTArray<wr::WrTransformProperty>& aTransformArray);
  void UpdateScrollPosition(
      const wr::WrPipelineId& aPipelineId,
      const layers::ScrollableLayerGuid::ViewID& aScrollId,
      const nsTArray<wr::SampledScrollOffset>& aSampledOffsets);
  void UpdateIsTransformAsyncZooming(uint64_t aAnimationId, bool aIsZooming);

 private:
  Transaction* mTxn;
};

class WebRenderAPI final {
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderAPI);

 public:
  /// This can be called on the compositor thread only.
  static already_AddRefed<WebRenderAPI> Create(
      layers::CompositorBridgeParent* aBridge,
      RefPtr<widget::CompositorWidget>&& aWidget,
      const wr::WrWindowId& aWindowId, LayoutDeviceIntSize aSize,
      layers::WindowKind aWindowKind, nsACString& aError);

  already_AddRefed<WebRenderAPI> Clone();

  void DestroyRenderer();

  wr::WindowId GetId() const { return mId; }

  /// Do a non-blocking hit-testing query on a shared version of the hit
  /// testing information.
  std::vector<WrHitResult> HitTest(const wr::WorldPoint& aPoint);

  void SendTransaction(TransactionBuilder& aTxn);

  void SetFrameStartTime(const TimeStamp& aTime);

  void RunOnRenderThread(UniquePtr<RendererEvent> aEvent);

  void Readback(const TimeStamp& aStartTime, gfx::IntSize aSize,
                const gfx::SurfaceFormat& aFormat,
                const Range<uint8_t>& aBuffer, bool* aNeedsYFlip);

  void ClearAllCaches();
  void EnableNativeCompositor(bool aEnable);
  void SetBatchingLookback(uint32_t aCount);
  void SetBool(wr::BoolParameter, bool value);
  void SetInt(wr::IntParameter, int32_t value);

  void SetClearColor(const gfx::DeviceColor& aColor);
  void SetProfilerUI(const nsACString& aUIString);

  void Pause();
  bool Resume();

  void WakeSceneBuilder();
  void FlushSceneBuilder();

  void NotifyMemoryPressure();
  void AccumulateMemoryReport(wr::MemoryReport*);

  wr::WrIdNamespace GetNamespace();
  layers::WebRenderBackend GetBackendType() { return mBackend; }
  layers::WebRenderCompositor GetCompositorType() { return mCompositor; }
  uint32_t GetMaxTextureSize() const { return mMaxTextureSize; }
  bool GetUseANGLE() const { return mUseANGLE; }
  bool GetUseDComp() const { return mUseDComp; }
  bool GetUseTripleBuffering() const { return mUseTripleBuffering; }
  bool SupportsExternalBufferTextures() const {
    return mSupportsExternalBufferTextures;
  }
  layers::SyncHandle GetSyncHandle() const { return mSyncHandle; }

  void Capture();

  void StartCaptureSequence(const nsACString& aPath, uint32_t aFlags);
  void StopCaptureSequence();

  void BeginRecording(const TimeStamp& aRecordingStart,
                      wr::PipelineId aRootPipelineId);

  typedef MozPromise<layers::FrameRecording, nsresult, true>
      EndRecordingPromise;

  RefPtr<EndRecordingPromise> EndRecording();

  layers::RemoteTextureInfoList* GetPendingRemoteTextureInfoList();

  void FlushPendingWrTransactionEventsWithoutWait();
  void FlushPendingWrTransactionEventsWithWait();

 protected:
  WebRenderAPI(wr::DocumentHandle* aHandle, wr::WindowId aId,
               layers::WebRenderBackend aBackend,
               layers::WebRenderCompositor aCompositor,
               uint32_t aMaxTextureSize, bool aUseANGLE, bool aUseDComp,
               bool aUseTripleBuffering, bool aSupportsExternalBufferTextures,
               layers::SyncHandle aSyncHandle);

  ~WebRenderAPI();
  // Should be used only for shutdown handling
  void WaitFlushed();

  void UpdateDebugFlags(uint32_t aFlags);
  bool CheckIsRemoteTextureReady(layers::RemoteTextureInfoList* aList);
  void WaitRemoteTextureReady(layers::RemoteTextureInfoList* aList);

  enum class RemoteTextureWaitType : uint8_t {
    AsyncWait = 0,
    FlushWithWait = 1,
    FlushWithoutWait = 2
  };

  void HandleWrTransactionEvents(RemoteTextureWaitType aType);

  class WrTransactionEvent {
   public:
    enum class Tag {
      Transaction,
      PendingRemoteTextures,
    };
    const Tag mTag;

    struct TransactionWrapper {
      TransactionWrapper(wr::Transaction* aTxn, bool aUseSceneBuilderThread)
          : mTxn(aTxn), mUseSceneBuilderThread(aUseSceneBuilderThread) {}

      ~TransactionWrapper() {
        if (mTxn) {
          wr_transaction_delete(mTxn);
        }
      }

      wr::Transaction* mTxn;
      const bool mUseSceneBuilderThread;
    };

   private:
    WrTransactionEvent(const Tag aTag,
                       UniquePtr<TransactionWrapper>&& aTransaction)
        : mTag(aTag), mTransaction(std::move(aTransaction)) {
      MOZ_ASSERT(mTag == Tag::Transaction);
    }
    WrTransactionEvent(
        const Tag aTag,
        UniquePtr<layers::RemoteTextureInfoList>&& aPendingRemoteTextures)
        : mTag(aTag),
          mPendingRemoteTextures(std::move(aPendingRemoteTextures)) {
      MOZ_ASSERT(mTag == Tag::PendingRemoteTextures);
    }

    UniquePtr<TransactionWrapper> mTransaction;
    UniquePtr<layers::RemoteTextureInfoList> mPendingRemoteTextures;

   public:
    static WrTransactionEvent Transaction(wr::Transaction* aTxn,
                                          bool aUseSceneBuilderThread) {
      auto transaction =
          MakeUnique<TransactionWrapper>(aTxn, aUseSceneBuilderThread);
      return WrTransactionEvent(Tag::Transaction, std::move(transaction));
    }

    static WrTransactionEvent PendingRemoteTextures(
        UniquePtr<layers::RemoteTextureInfoList>&& aPendingRemoteTextures) {
      return WrTransactionEvent(Tag::PendingRemoteTextures,
                                std::move(aPendingRemoteTextures));
    }

    wr::Transaction* Transaction() {
      if (mTag == Tag::Transaction) {
        return mTransaction->mTxn;
      }
      MOZ_ASSERT_UNREACHABLE("unexpected to be called");
      return nullptr;
    }

    bool UseSceneBuilderThread() {
      if (mTag == Tag::Transaction) {
        return mTransaction->mUseSceneBuilderThread;
      }
      MOZ_ASSERT_UNREACHABLE("unexpected to be called");
      return true;
    }

    layers::RemoteTextureInfoList* RemoteTextureInfoList() {
      if (mTag == Tag::PendingRemoteTextures) {
        MOZ_ASSERT(mPendingRemoteTextures);
        return mPendingRemoteTextures.get();
      }
      MOZ_ASSERT_UNREACHABLE("unexpected to be called");
      return nullptr;
    }
  };

  wr::DocumentHandle* mDocHandle;
  wr::WindowId mId;
  layers::WebRenderBackend mBackend;
  layers::WebRenderCompositor mCompositor;
  int32_t mMaxTextureSize;
  bool mUseANGLE;
  bool mUseDComp;
  bool mUseTripleBuffering;
  bool mSupportsExternalBufferTextures;
  bool mCaptureSequence;
  layers::SyncHandle mSyncHandle;
  bool mRendererDestroyed;

  UniquePtr<layers::RemoteTextureInfoList> mPendingRemoteTextureInfoList;
  std::queue<WrTransactionEvent> mPendingWrTransactionEvents;

  // We maintain alive the root api to know when to shut the render backend
  // down, and the root api for the document to know when to delete the
  // document. mRootApi is null for the api object that owns the channel (and is
  // responsible for shutting it down), and mRootDocumentApi is null for the api
  // object owning (and responsible for destroying) a given document. All api
  // objects in the same window use the same channel, and some api objects write
  // to the same document (but there is only one owner for each channel and for
  // each document).
  RefPtr<wr::WebRenderAPI> mRootApi;
  RefPtr<wr::WebRenderAPI> mRootDocumentApi;

  friend class DisplayListBuilder;
  friend class layers::WebRenderBridgeParent;
};

// This is a RAII class that automatically sends the transaction on
// destruction. This is useful for code that has multiple exit points and we
// want to ensure that the stuff accumulated in the transaction gets sent
// regardless of which exit we take. Note that if the caller explicitly calls
// mApi->SendTransaction() that's fine too because that empties out the
// TransactionBuilder and leaves it as a valid empty transaction, so calling
// SendTransaction on it again ends up being a no-op.
class MOZ_RAII AutoTransactionSender {
 public:
  AutoTransactionSender(WebRenderAPI* aApi, TransactionBuilder* aTxn)
      : mApi(aApi), mTxn(aTxn) {
    MOZ_RELEASE_ASSERT(mApi);
    MOZ_RELEASE_ASSERT(aTxn);
  }

  ~AutoTransactionSender() { mApi->SendTransaction(*mTxn); }

 private:
  WebRenderAPI* mApi;
  TransactionBuilder* mTxn;
};

/**
 * A set of optional parameters for stacking context creation.
 */
struct MOZ_STACK_CLASS StackingContextParams : public WrStackingContextParams {
  StackingContextParams()
      : WrStackingContextParams{
            WrStackingContextClip::None(),
            nullptr,
            nullptr,
            nullptr,
            wr::TransformStyle::Flat,
            wr::WrReferenceFrameKind::Transform,
            false,
            false,
            false,
            nullptr,
            /* prim_flags = */ wr::PrimitiveFlags::IS_BACKFACE_VISIBLE,
            wr::MixBlendMode::Normal,
            wr::StackingContextFlags{0}} {}

  void SetPreserve3D(bool aPreserve) {
    transform_style =
        aPreserve ? wr::TransformStyle::Preserve3D : wr::TransformStyle::Flat;
  }

  // Fill this in only if this is for the root StackingContextHelper.
  nsIFrame* mRootReferenceFrame = nullptr;
  nsTArray<wr::FilterOp> mFilters;
  nsTArray<wr::WrFilterData> mFilterDatas;
  wr::LayoutRect mBounds = wr::ToLayoutRect(LayoutDeviceRect());
  const gfx::Matrix4x4* mBoundTransform = nullptr;
  const wr::WrTransformInfo* mTransformPtr = nullptr;
  nsDisplayTransform* mDeferredTransformItem = nullptr;
  // Whether the stacking context is possibly animated. This alters how
  // coordinates are transformed/snapped to invalidate less when transforms
  // change frequently.
  bool mAnimated = false;
  // Whether items should be rasterized in a local space that is (mostly)
  // invariant to transforms, i.e. disabling subpixel AA and screen space pixel
  // snapping on text runs that would only make sense in screen space.
  bool mRasterizeLocally = false;
};

/// This is a simple C++ wrapper around WrState defined in the rust bindings.
/// We may want to turn this into a direct wrapper on top of
/// WebRenderFrameBuilder instead, so the interface may change a bit.
class DisplayListBuilder final {
 public:
  explicit DisplayListBuilder(wr::PipelineId aId,
                              layers::WebRenderBackend aBackend);
  DisplayListBuilder(DisplayListBuilder&&) = default;

  ~DisplayListBuilder();

  void Save();
  void Restore();
  void ClearSave();

  usize Dump(usize aIndent, const Maybe<usize>& aStart,
             const Maybe<usize>& aEnd);
  void DumpSerializedDisplayList();

  void Begin(layers::DisplayItemCache* aCache = nullptr);
  void End(wr::BuiltDisplayList& aOutDisplayList);
  void End(layers::DisplayListData& aOutTransaction);

  Maybe<wr::WrSpatialId> PushStackingContext(
      const StackingContextParams& aParams, const wr::LayoutRect& aBounds,
      const wr::RasterSpace& aRasterSpace);
  void PopStackingContext(bool aIsReferenceFrame);

  wr::WrClipChainId DefineClipChain(const nsTArray<wr::WrClipId>& aClips,
                                    bool aParentWithCurrentChain = false);

  wr::WrClipId DefineImageMaskClip(const wr::ImageMask& aMask,
                                   const nsTArray<wr::LayoutPoint>&,
                                   wr::FillRule);
  wr::WrClipId DefineRoundedRectClip(Maybe<wr::WrSpatialId> aSpace,
                                     const wr::ComplexClipRegion& aComplex);
  wr::WrClipId DefineRectClip(Maybe<wr::WrSpatialId> aSpace,
                              wr::LayoutRect aClipRect);

  wr::WrSpatialId DefineStickyFrame(
      const wr::LayoutRect& aContentRect, const float* aTopMargin,
      const float* aRightMargin, const float* aBottomMargin,
      const float* aLeftMargin, const StickyOffsetBounds& aVerticalBounds,
      const StickyOffsetBounds& aHorizontalBounds,
      const wr::LayoutVector2D& aAppliedOffset, wr::SpatialTreeItemKey aKey);

  Maybe<wr::WrSpatialId> GetScrollIdForDefinedScrollLayer(
      layers::ScrollableLayerGuid::ViewID aViewId) const;
  wr::WrSpatialId DefineScrollLayer(
      const layers::ScrollableLayerGuid::ViewID& aViewId,
      const Maybe<wr::WrSpatialId>& aParent, const wr::LayoutRect& aContentRect,
      const wr::LayoutRect& aClipRect, const wr::LayoutVector2D& aScrollOffset,
      wr::APZScrollGeneration aScrollOffsetGeneration,
      wr::HasScrollLinkedEffect aHasScrollLinkedEffect,
      wr::SpatialTreeItemKey aKey);

  void PushRect(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                bool aIsBackfaceVisible, bool aForceAntiAliasing,
                bool aIsCheckerboard, const wr::ColorF& aColor);
  void PushRectWithAnimation(const wr::LayoutRect& aBounds,
                             const wr::LayoutRect& aClip,
                             bool aIsBackfaceVisible, const wr::ColorF& aColor,
                             const WrAnimationProperty* aAnimation);
  void PushRoundedRect(const wr::LayoutRect& aBounds,
                       const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                       const wr::ColorF& aColor);
  void PushHitTest(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                   bool aIsBackfaceVisible,
                   const layers::ScrollableLayerGuid::ViewID& aScrollId,
                   const gfx::CompositorHitTestInfo& aHitInfo,
                   SideBits aSideBits);
  void PushClearRect(const wr::LayoutRect& aBounds);

  void PushBackdropFilter(const wr::LayoutRect& aBounds,
                          const wr::ComplexClipRegion& aRegion,
                          const nsTArray<wr::FilterOp>& aFilters,
                          const nsTArray<wr::WrFilterData>& aFilterDatas,
                          bool aIsBackfaceVisible);

  void PushLinearGradient(const wr::LayoutRect& aBounds,
                          const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                          const wr::LayoutPoint& aStartPoint,
                          const wr::LayoutPoint& aEndPoint,
                          const nsTArray<wr::GradientStop>& aStops,
                          wr::ExtendMode aExtendMode,
                          const wr::LayoutSize aTileSize,
                          const wr::LayoutSize aTileSpacing);

  void PushRadialGradient(const wr::LayoutRect& aBounds,
                          const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                          const wr::LayoutPoint& aCenter,
                          const wr::LayoutSize& aRadius,
                          const nsTArray<wr::GradientStop>& aStops,
                          wr::ExtendMode aExtendMode,
                          const wr::LayoutSize aTileSize,
                          const wr::LayoutSize aTileSpacing);

  void PushConicGradient(const wr::LayoutRect& aBounds,
                         const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                         const wr::LayoutPoint& aCenter, const float aAngle,
                         const nsTArray<wr::GradientStop>& aStops,
                         wr::ExtendMode aExtendMode,
                         const wr::LayoutSize aTileSize,
                         const wr::LayoutSize aTileSpacing);

  void PushImage(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                 bool aIsBackfaceVisible, bool aForceAntiAliasing,
                 wr::ImageRendering aFilter, wr::ImageKey aImage,
                 bool aPremultipliedAlpha = true,
                 const wr::ColorF& aColor = wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f},
                 bool aPreferCompositorSurface = false,
                 bool aSupportsExternalCompositing = false);

  void PushRepeatingImage(
      const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
      bool aIsBackfaceVisible, const wr::LayoutSize& aStretchSize,
      const wr::LayoutSize& aTileSpacing, wr::ImageRendering aFilter,
      wr::ImageKey aImage, bool aPremultipliedAlpha = true,
      const wr::ColorF& aColor = wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f});

  void PushYCbCrPlanarImage(
      const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
      bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
      wr::ImageKey aImageChannel1, wr::ImageKey aImageChannel2,
      wr::WrColorDepth aColorDepth, wr::WrYuvColorSpace aColorSpace,
      wr::WrColorRange aColorRange, wr::ImageRendering aFilter,
      bool aPreferCompositorSurface = false,
      bool aSupportsExternalCompositing = false);

  void PushNV12Image(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                     bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
                     wr::ImageKey aImageChannel1, wr::WrColorDepth aColorDepth,
                     wr::WrYuvColorSpace aColorSpace,
                     wr::WrColorRange aColorRange, wr::ImageRendering aFilter,
                     bool aPreferCompositorSurface = false,
                     bool aSupportsExternalCompositing = false);

  void PushP010Image(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                     bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
                     wr::ImageKey aImageChannel1, wr::WrColorDepth aColorDepth,
                     wr::WrYuvColorSpace aColorSpace,
                     wr::WrColorRange aColorRange, wr::ImageRendering aFilter,
                     bool aPreferCompositorSurface = false,
                     bool aSupportsExternalCompositing = false);

  void PushYCbCrInterleavedImage(
      const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
      bool aIsBackfaceVisible, wr::ImageKey aImageChannel0,
      wr::WrColorDepth aColorDepth, wr::WrYuvColorSpace aColorSpace,
      wr::WrColorRange aColorRange, wr::ImageRendering aFilter,
      bool aPreferCompositorSurface = false,
      bool aSupportsExternalCompositing = false);

  void PushIFrame(const LayoutDeviceRect& aDevPxBounds, bool aIsBackfaceVisible,
                  wr::PipelineId aPipeline, bool aIgnoreMissingPipeline);

  // XXX WrBorderSides are passed with Range.
  // It is just to bypass compiler bug. See Bug 1357734.
  void PushBorder(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                  bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths,
                  const Range<const wr::BorderSide>& aSides,
                  const wr::BorderRadius& aRadius,
                  wr::AntialiasBorder = wr::AntialiasBorder::Yes);

  void PushBorderImage(const wr::LayoutRect& aBounds,
                       const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                       const wr::WrBorderImage& aParams);

  void PushBorderGradient(const wr::LayoutRect& aBounds,
                          const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                          const wr::LayoutSideOffsets& aWidths,
                          const int32_t aWidth, const int32_t aHeight,
                          bool aFill, const wr::DeviceIntSideOffsets& aSlice,
                          const wr::LayoutPoint& aStartPoint,
                          const wr::LayoutPoint& aEndPoint,
                          const nsTArray<wr::GradientStop>& aStops,
                          wr::ExtendMode aExtendMode);

  void PushBorderRadialGradient(
      const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
      bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths, bool aFill,
      const wr::LayoutPoint& aCenter, const wr::LayoutSize& aRadius,
      const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode);

  void PushBorderConicGradient(
      const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
      bool aIsBackfaceVisible, const wr::LayoutSideOffsets& aWidths, bool aFill,
      const wr::LayoutPoint& aCenter, const float aAngle,
      const nsTArray<wr::GradientStop>& aStops, wr::ExtendMode aExtendMode);

  void PushText(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                bool aIsBackfaceVisible, const wr::ColorF& aColor,
                wr::FontInstanceKey aFontKey,
                Range<const wr::GlyphInstance> aGlyphBuffer,
                const wr::GlyphOptions* aGlyphOptions = nullptr);

  void PushLine(const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
                const wr::Line& aLine);

  void PushShadow(const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
                  bool aIsBackfaceVisible, const wr::Shadow& aShadow,
                  bool aShouldInflate);

  void PopAllShadows();

  void PushBoxShadow(const wr::LayoutRect& aRect, const wr::LayoutRect& aClip,
                     bool aIsBackfaceVisible, const wr::LayoutRect& aBoxBounds,
                     const wr::LayoutVector2D& aOffset,
                     const wr::ColorF& aColor, const float& aBlurRadius,
                     const float& aSpreadRadius,
                     const wr::BorderRadius& aBorderRadius,
                     const wr::BoxShadowClipMode& aClipMode);

  /**
   * Notifies the DisplayListBuilder that it can group together WR display items
   * that are pushed until |CancelGroup()| or |FinishGroup()| call.
   */
  void StartGroup(nsPaintedDisplayItem* aItem);

  /**
   * Cancels grouping of the display items and discards all the display items
   * pushed between the |StartGroup()| and |CancelGroup()| calls.
   */
  void CancelGroup(const bool aDiscard = false);

  /**
   * Finishes the display item group. The group is stored in WebRender backend,
   * and can be reused with |ReuseItem()|, if the Gecko display item is reused.
   */
  void FinishGroup();

  /**
   * Try to reuse the previously created WebRender display items for the given
   * Gecko display item |aItem|.
   * Returns true if the items were reused, otherwise returns false.
   */
  bool ReuseItem(nsPaintedDisplayItem* aItem);

  uint64_t CurrentClipChainId() const {
    return mCurrentSpaceAndClipChain.clip_chain;
  }

  const wr::WrSpaceAndClipChain& CurrentSpaceAndClipChain() const {
    return mCurrentSpaceAndClipChain;
  }

  const wr::PipelineId& CurrentPipelineId() const { return mPipelineId; }
  layers::WebRenderBackend GetBackendType() const { return mBackend; }

  // Checks to see if the innermost enclosing fixed pos item has the same
  // ASR. If so, it returns the scroll target for that fixed-pos item.
  // Otherwise, it returns Nothing().
  Maybe<layers::ScrollableLayerGuid::ViewID> GetContainingFixedPosScrollTarget(
      const ActiveScrolledRoot* aAsr);

  Maybe<SideBits> GetContainingFixedPosSideBits(const ActiveScrolledRoot* aAsr);

  gfxContext* GetTextContext(wr::IpcResourceUpdateQueue& aResources,
                             const layers::StackingContextHelper& aSc,
                             layers::RenderRootStateManager* aManager,
                             nsDisplayItem* aItem, nsRect& aBounds,
                             const gfx::Point& aDeviceOffset);

  // Try to avoid using this when possible.
  wr::WrState* Raw() { return mWrState; }

  void SetClipChainLeaf(const Maybe<wr::LayoutRect>& aClipRect) {
    mClipChainLeaf = aClipRect;
  }

  // Used for opacity flattening. When we flatten away an opacity item,
  // we push the opacity value onto the builder.
  // Descendant items should pull the inherited opacity during
  // their CreateWebRenderCommands implementation. This can only happen if all
  // descendant items reported supporting this functionality, via
  // nsDisplayItem::CanApplyOpacity.
  float GetInheritedOpacity() { return mInheritedOpacity; }
  void SetInheritedOpacity(float aOpacity) { mInheritedOpacity = aOpacity; }
  const DisplayItemClipChain* GetInheritedClipChain() {
    return mInheritedClipChain;
  }
  void PushInheritedClipChain(nsDisplayListBuilder* aBuilder,
                              const DisplayItemClipChain* aClipChain);
  void SetInheritedClipChain(const DisplayItemClipChain* aClipChain) {
    mInheritedClipChain = aClipChain;
  }

  layers::DisplayItemCache* GetDisplayItemCache() { return mDisplayItemCache; }

  // A chain of RAII objects, each holding a (ASR, ViewID, SideBits) tuple of
  // data. The topmost object is pointed to by the mActiveFixedPosTracker
  // pointer in the wr::DisplayListBuilder.
  class MOZ_RAII FixedPosScrollTargetTracker final {
   public:
    FixedPosScrollTargetTracker(DisplayListBuilder& aBuilder,
                                const ActiveScrolledRoot* aAsr,
                                layers::ScrollableLayerGuid::ViewID aScrollId,
                                SideBits aSideBits);
    ~FixedPosScrollTargetTracker();
    Maybe<layers::ScrollableLayerGuid::ViewID> GetScrollTargetForASR(
        const ActiveScrolledRoot* aAsr);
    Maybe<SideBits> GetSideBitsForASR(const ActiveScrolledRoot* aAsr);

   private:
    FixedPosScrollTargetTracker* mParentTracker;
    DisplayListBuilder& mBuilder;
    const ActiveScrolledRoot* mAsr;
    layers::ScrollableLayerGuid::ViewID mScrollId;
    SideBits mSideBits;
  };

 protected:
  wr::LayoutRect MergeClipLeaf(const wr::LayoutRect& aClip) {
    if (mClipChainLeaf) {
      return wr::IntersectLayoutRect(*mClipChainLeaf, aClip);
    }
    return aClip;
  }

  // See the implementation of PushShadow for details on these methods.
  void SuspendClipLeafMerging();
  void ResumeClipLeafMerging();

  wr::WrState* mWrState;

  // Track each scroll id that we encountered. We use this structure to
  // ensure that we don't define a particular scroll layer multiple times,
  // as that results in undefined behaviour in WR.
  std::unordered_map<layers::ScrollableLayerGuid::ViewID, wr::WrSpatialId>
      mScrollIds;

  wr::WrSpaceAndClipChain mCurrentSpaceAndClipChain;

  // Contains the current leaf of the clip chain to be merged with the
  // display item's clip rect when pushing an item. May be set to Nothing() if
  // there is no clip rect to merge with.
  Maybe<wr::LayoutRect> mClipChainLeaf;

  // Versions of the above that are on hold while SuspendClipLeafMerging is on
  // (see the implementation of PushShadow for details).
  Maybe<wr::WrSpaceAndClipChain> mSuspendedSpaceAndClipChain;
  Maybe<wr::LayoutRect> mSuspendedClipChainLeaf;

  RefPtr<layout::TextDrawTarget> mCachedTextDT;
  mozilla::UniquePtr<gfxContext> mCachedContext;

  FixedPosScrollTargetTracker* mActiveFixedPosTracker;

  wr::PipelineId mPipelineId;
  layers::WebRenderBackend mBackend;

  nsTArray<wr::PipelineId> mRemotePipelineIds;

  layers::DisplayItemCache* mDisplayItemCache;
  Maybe<uint16_t> mCurrentCacheSlot;
  float mInheritedOpacity = 1.0f;
  const DisplayItemClipChain* mInheritedClipChain = nullptr;

  friend class WebRenderAPI;
  friend class SpaceAndClipChainHelper;
};

// This is a RAII class that overrides the current Wr's SpatialId and
// ClipChainId.
class MOZ_RAII SpaceAndClipChainHelper final {
 public:
  SpaceAndClipChainHelper(DisplayListBuilder& aBuilder,
                          wr::WrSpaceAndClipChain aSpaceAndClipChain)
      : mBuilder(aBuilder),
        mOldSpaceAndClipChain(aBuilder.mCurrentSpaceAndClipChain) {
    aBuilder.mCurrentSpaceAndClipChain = aSpaceAndClipChain;
  }
  SpaceAndClipChainHelper(DisplayListBuilder& aBuilder,
                          wr::WrSpatialId aSpatialId)
      : mBuilder(aBuilder),
        mOldSpaceAndClipChain(aBuilder.mCurrentSpaceAndClipChain) {
    aBuilder.mCurrentSpaceAndClipChain.space = aSpatialId;
  }
  SpaceAndClipChainHelper(DisplayListBuilder& aBuilder,
                          wr::WrClipChainId aClipChainId)
      : mBuilder(aBuilder),
        mOldSpaceAndClipChain(aBuilder.mCurrentSpaceAndClipChain) {
    aBuilder.mCurrentSpaceAndClipChain.clip_chain = aClipChainId.id;
  }

  ~SpaceAndClipChainHelper() {
    mBuilder.mCurrentSpaceAndClipChain = mOldSpaceAndClipChain;
  }

 private:
  SpaceAndClipChainHelper(const SpaceAndClipChainHelper&) = delete;

  DisplayListBuilder& mBuilder;
  wr::WrSpaceAndClipChain mOldSpaceAndClipChain;
};

Maybe<wr::ImageFormat> SurfaceFormatToImageFormat(gfx::SurfaceFormat aFormat);

}  // namespace wr
}  // namespace mozilla

#endif
