/* -*- 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 GFX_CLIPMANAGER_H
#define GFX_CLIPMANAGER_H

#include <stack>
#include <unordered_map>

#include "mozilla/Attributes.h"
#include "mozilla/webrender/WebRenderAPI.h"
#include "mozilla/layout/StickyScrollContainer.h"

namespace mozilla {

class nsDisplayItem;
class nsDisplayStickyPosition;
struct ActiveScrolledRoot;
struct DisplayItemClipChain;

namespace wr {
class DisplayListBuilder;
}

namespace layers {

class StackingContextHelper;
class WebRenderLayerManager;

/**
 * This class manages creating and assigning scroll layers and clips in
 * WebRender based on the gecko display list. It has a few public functions that
 * are intended to be invoked while traversing the Gecko display list, and it
 * uses the ASR and clip information from the display list to create the
 * necessary clip state in WebRender.
 *
 * The structure of the clip state in WebRender ends up quite similar to how
 * it is in Gecko. For each ASR in Gecko, we create a scroll layer (i.e. a
 * scrolling clip) in WebRender; these form a tree structure similar to the
 * ASR tree structure. Ancestors of scroll layers are always other scroll
 * layers, or the root scroll node.
 * The DisplayItemClipChain list of clips from the gecko display list is
 * converted to a WR clip chain and pushed on the stack prior to creating
 * any WR commands for that item, and is popped afterwards. In addition,
 * the WR clip chain has a parent pointer, which points to the clip chain for
 * any enclosing stacking context. This again results in a strucuture very
 * similar to that in Gecko, where the clips from container display items get
 * applied to the contained display items.
 */
class ClipManager {
 public:
  ClipManager();

  void BeginBuild(WebRenderLayerManager* aManager,
                  wr::DisplayListBuilder& aBuilder);
  void EndBuild();

  void BeginList(const StackingContextHelper& aStackingContext);
  void EndList(const StackingContextHelper& aStackingContext);

  wr::WrSpaceAndClipChain SwitchItem(nsDisplayListBuilder* aBuilder,
                                     nsDisplayItem* aItem);
  ~ClipManager();

  void PushOverrideForASR(const ActiveScrolledRoot* aASR,
                          const wr::WrSpatialId& aSpatialId);
  void PopOverrideForASR(const ActiveScrolledRoot* aASR);

 private:
  wr::WrSpatialId SpatialIdAfterOverride(const wr::WrSpatialId& aSpatialId);
  wr::WrSpatialId GetSpatialId(const ActiveScrolledRoot* aASR);

  static StickyScrollContainer* GetStickyScrollContainer(
      const ActiveScrolledRoot* aASR);
  Maybe<wr::WrSpatialId> DefineSpatialNodes(nsDisplayListBuilder* aBuilder,
                                            const ActiveScrolledRoot* aASR,
                                            nsDisplayItem* aItem);
  Maybe<wr::WrSpatialId> DefineStickyNode(
      nsDisplayListBuilder* aBuilder, Maybe<wr::WrSpatialId> aParentSpatialId,
      const ActiveScrolledRoot* aASR, nsDisplayItem* aItem);

  Maybe<wr::WrClipChainId> DefineClipChain(const DisplayItemClipChain* aChain,
                                           int32_t aAppUnitsPerDevPixel);

  WebRenderLayerManager* MOZ_NON_OWNING_REF mManager;
  wr::DisplayListBuilder* mBuilder;

  // Stack of clip caches. Each cache contains a map from gecko
  // DisplayItemClipChain objects to webrender WrClipIds, which allows us to
  // avoid redefining identical clips in WR. However, the gecko
  // DisplayItemClipChain items get deduplicated quite aggressively, without
  // regard to things like the enclosing reference frame or mask. On the WR
  // side, we cannot deduplicate clips that aggressively. So what we do is
  // any time we enter a new reference frame (for example) we create a new clip
  // cache on mCacheStack. This ensures we continue caching stuff within a given
  // reference frame, but disallow caching stuff across reference frames. In
  // general we need to do this anytime PushOverrideForASR is called, as that is
  // called for the same set of conditions for which we cannot deduplicate
  // clips.
  using ClipIdMap = std::unordered_map<const DisplayItemClipChain*,
                                       AutoTArray<wr::WrClipId, 4>>;
  std::stack<ClipIdMap> mCacheStack;

  // A map that holds the cache overrides created by (a) "out of band" clips,
  // i.e. clips that are generated by display items but that ClipManager
  // doesn't know about and (b) stacking contexts that affect clip positioning.
  // These are called "cache overrides" because while we're inside these things,
  // we cannot use the ASR from the gecko display list as-is. Fundamentally this
  // results from a mismatch between the ASR+clip items on the gecko side and
  // the ClipScrollTree on the WR side; the WR side incorporates things like
  // transforms and stacking context origins while the gecko side manages those
  // differently.
  // Any time ClipManager wants to define a new clip as a child of ASR X, it
  // should first check the cache overrides to see if there is a cache override
  // item ((a) or (b) above) that is already a child of X, and then define that
  // clip as a child of Y instead. This map stores X -> Y, which allows
  // ClipManager to do the necessary lookup. Note that there theoretically might
  // be multiple different "Y" clips (in case of nested cache overrides), which
  // is why we need a stack.
  std::unordered_map<wr::WrSpatialId, std::stack<wr::WrSpatialId>> mASROverride;

  // This holds some clip state for a single nsDisplayItem
  struct ItemClips {
    ItemClips(const ActiveScrolledRoot* aASR,
              const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel,
              bool aSeparateLeaf);

    // These are the "inputs" - they come from the nsDisplayItem
    const ActiveScrolledRoot* mASR;
    const DisplayItemClipChain* mChain;
    int32_t mAppUnitsPerDevPixel;
    bool mSeparateLeaf;

    // These are the "outputs" - they are pushed to WR as needed
    wr::WrSpatialId mScrollId;
    Maybe<wr::WrClipChainId> mClipChainId;

    void UpdateSeparateLeaf(wr::DisplayListBuilder& aBuilder,
                            int32_t aAppUnitsPerDevPixel);
    bool HasSameInputs(const ItemClips& aOther);
    wr::WrSpaceAndClipChain GetSpaceAndClipChain() const;
  };

  // A stack of ItemClips corresponding to the nsDisplayItem ancestry. Each
  // time we recurse into a nsDisplayItem's child list, this stack size
  // increases by one. The topmost item on the stack is for the display item
  // we are currently processing and items deeper on the stack are for that
  // display item's ancestors.
  std::stack<ItemClips> mItemClipStack;
};

}  // namespace layers
}  // namespace mozilla

#endif
