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

#include "gfxContext.h"
#include "mozilla/ScrollPositionUpdate.h"  // for ScrollPositionUpdate
#include "mozilla/dom/Animation.h"         // for Animation
#include "mozilla/gfx/GPUProcessListener.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/layers/ScrollableLayerGuid.h"  // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID
#include "mozilla/webrender/webrender_ffi.h"
#include "nsRefPtrHashtable.h"  // for nsRefPtrHashtable

namespace mozilla {
namespace layers {
class LayerManager;
class WebRenderLayerManager;
class KnowsCompositor;
class CompositorBridgeChild;
class FrameUniformityData;
class PersistentBufferProvider;
}  // namespace layers
class FallbackRenderer;
class nsDisplayListBuilder;
class nsDisplayList;

class FrameRecorder {
 public:
  /**
   * Record (and return) frame-intervals and paint-times for frames which were
   * presented between calling StartFrameTimeRecording and
   * StopFrameTimeRecording.
   *
   * - Uses a cyclic buffer and serves concurrent consumers, so if Stop is
   *   called too late
   *     (elements were overwritten since Start), result is considered invalid
   *      and hence empty.)
   * - Buffer is capable of holding 10 seconds @ 60fps (or more if frames were
   *   less frequent).
   *     Can be changed (up to 1 hour) via pref:
   *     toolkit.framesRecording.bufferSize.
   * - Note: the first frame-interval may be longer than expected because last
   *   frame
   *     might have been presented some time before calling
   *     StartFrameTimeRecording.
   */

  /**
   * Returns a handle which represents current recording start position.
   */
  virtual uint32_t StartFrameTimeRecording(int32_t aBufferSize);

  /**
   *  Clears, then populates aFrameIntervals with the recorded frame timing
   *  data. The array will be empty if data was overwritten since
   *  aStartIndex was obtained.
   */
  virtual void StopFrameTimeRecording(uint32_t aStartIndex,
                                      nsTArray<float>& aFrameIntervals);

  void RecordFrame();

 private:
  struct FramesTimingRecording {
    // Stores state and data for frame intervals and paint times recording.
    // see LayerManager::StartFrameTimeRecording() at Layers.cpp for more
    // details.
    FramesTimingRecording()
        : mNextIndex(0),
          mLatestStartIndex(0),
          mCurrentRunStartIndex(0),
          mIsPaused(true) {}
    nsTArray<float> mIntervals;
    TimeStamp mLastFrameTime;
    uint32_t mNextIndex;
    uint32_t mLatestStartIndex;
    uint32_t mCurrentRunStartIndex;
    bool mIsPaused;
  };
  FramesTimingRecording mRecording;
};

/**
 * WindowRenderer is the retained rendering object owned by an nsIWidget for
 * drawing the contents of that window, the role previously handled by
 * LayerManager.
 *
 * It can be WebRender, (deprecated) Layers, or an immediate-mode
 * FallbackRenderer.
 *
 * The intention is for LayerManager to be removed entirely in the near future,
 * with WebRender inheriting directly from this class. It is likely that more
 * cleanup can be done once that happens.
 */
class WindowRenderer : public FrameRecorder {
  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING

 public:
  // Cast to implementation types.
  virtual layers::WebRenderLayerManager* AsWebRender() { return nullptr; }
  virtual FallbackRenderer* AsFallback() { return nullptr; }

  // Required functionality

  /**
   * Start a new transaction. Nested transactions are not allowed so
   * there must be no transaction currently in progress.
   * This transaction will update the state of the window from which
   * this LayerManager was obtained.
   */
  virtual bool BeginTransaction(const nsCString& aURL = nsCString()) = 0;

  enum EndTransactionFlags {
    END_DEFAULT = 0,
    END_NO_IMMEDIATE_REDRAW = 1 << 0,  // Do not perform the drawing phase
    END_NO_COMPOSITE =
        1 << 1,  // Do not composite after drawing painted layer contents.
    END_NO_REMOTE_COMPOSITE = 1 << 2  // Do not schedule a composition with a
                                      // remote Compositor, if one exists.
  };

  /**
   * Attempts to end an "empty transaction". There must have been no
   * changes to the layer tree since the BeginTransaction().
   * It's possible for this to fail; PaintedLayers may need to be updated
   * due to VRAM data being lost, for example. In such cases this method
   * returns false, and the caller must proceed with a normal layer tree
   * update and EndTransaction.
   */
  virtual bool EndEmptyTransaction(
      EndTransactionFlags aFlags = END_DEFAULT) = 0;

  virtual void Destroy() {}

  /**
   * Type of layer manager this is. This is to be used sparsely in order to
   * avoid a lot of Layers backend specific code. It should be used only when
   * Layers backend specific functionality is necessary.
   */
  virtual layers::LayersBackend GetBackendType() = 0;

  /**
   * Type of layers backend that will be used to composite this layer tree.
   * When compositing is done remotely, then this returns the layers type
   * of the compositor.
   */
  virtual layers::LayersBackend GetCompositorBackendType() {
    return GetBackendType();
  }

  /**
   * Checks if we need to invalidate the OS widget to trigger
   * painting when updating this renderer.
   */
  virtual bool NeedsWidgetInvalidation() { return true; }

  /**
   * Make sure that the previous transaction has been entirely
   * completed.
   *
   * Note: This may sychronously wait on a remote compositor
   * to complete rendering.
   */
  virtual void FlushRendering(wr::RenderReasons aReasons) {}

  /**
   * Make sure that the previous transaction has been
   * received. This will synchronsly wait on a remote compositor.
   */
  virtual void WaitOnTransactionProcessed() {}

  /**
   * returns the maximum texture size on this layer backend, or INT32_MAX
   * if there is no maximum
   */
  virtual int32_t GetMaxTextureSize() const { return INT32_MAX; }

  /**
   * Return the name of the layer manager's backend.
   */
  virtual void GetBackendName(nsAString& aName) = 0;

  virtual void GetFrameUniformity(layers::FrameUniformityData* aOutData) {}

  virtual bool AddPendingScrollUpdateForNextTransaction(
      layers::ScrollableLayerGuid::ViewID aScrollId,
      const ScrollPositionUpdate& aUpdateInfo) {
    return false;
  }

  /**
   * Creates a PersistentBufferProvider for use with canvas which is optimized
   * for inter-operating with this layermanager.
   */
  virtual already_AddRefed<layers::PersistentBufferProvider>
  CreatePersistentBufferProvider(const mozilla::gfx::IntSize& aSize,
                                 mozilla::gfx::SurfaceFormat aFormat,
                                 bool aWillReadFrequently = false);

  // Helper wrappers around cast to impl and then cast again.

  virtual layers::KnowsCompositor* AsKnowsCompositor() { return nullptr; }

  virtual layers::CompositorBridgeChild* GetCompositorBridgeChild() {
    return nullptr;
  }

  // Provided functionality

  void AddPartialPrerenderedAnimation(uint64_t aCompositorAnimationId,
                                      dom::Animation* aAnimation);
  void RemovePartialPrerenderedAnimation(uint64_t aCompositorAnimationId,
                                         dom::Animation* aAnimation);
  void UpdatePartialPrerenderedAnimations(
      const nsTArray<uint64_t>& aJankedAnimations);

 protected:
  virtual ~WindowRenderer() = default;

  // Transform animations which are not fully pre-rendered because it's on a
  // large frame.  We need to update the pre-rendered area once after we tried
  // to composite area which is outside of the pre-rendered area on the
  // compositor.
  nsRefPtrHashtable<nsUint64HashKey, dom::Animation>
      mPartialPrerenderedAnimations;
};

/**
 * FallbackRenderer is non-retained renderer that acts as a direct wrapper
 * around calling Paint on the provided DisplayList. This is used for cases
 * where initializing WebRender is too costly, and we don't need
 * retaining/invalidation (like small popup windows).
 *
 * It doesn't support any sort of EmptyTransaction, and only draws during
 * EndTransaction if a composite is requested (no END_NO_COMPOSITE flag
 * provided)
 */
class FallbackRenderer : public WindowRenderer {
 public:
  FallbackRenderer* AsFallback() final { return this; }

  void SetTarget(gfxContext* aContext);

  bool BeginTransaction(const nsCString& aURL = nsCString()) final;

  bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) final {
    return false;
  }

  layers::LayersBackend GetBackendType() final {
    return layers::LayersBackend::LAYERS_NONE;
  }

  void GetBackendName(nsAString& name) final { name.AssignLiteral("Fallback"); }

  void EndTransactionWithColor(const nsIntRect& aRect,
                               const gfx::DeviceColor& aColor);
  void EndTransactionWithList(nsDisplayListBuilder* aBuilder,
                              nsDisplayList* aList,
                              int32_t aAppUnitsPerDevPixel,
                              EndTransactionFlags aFlags);

  gfxContext* mTarget = nullptr;

 protected:
  FallbackRenderer() = default;
};

/**
 * DefaultFallbackRenderer is intended to be used when the caller does not want
 * it to be destroyed/recreated based on the compositing state.
 */
class DefaultFallbackRenderer final : public FallbackRenderer {
  NS_INLINE_DECL_REFCOUNTING(DefaultFallbackRenderer, final)

 public:
  DefaultFallbackRenderer() = default;

 private:
  ~DefaultFallbackRenderer() final = default;
};

/**
 * BackgroundedFallbackRenderer is intended to be used as a placeholder while we
 * wait for compositing to be reinstantiated.
 */
class BackgroundedFallbackRenderer final : public FallbackRenderer,
                                           public gfx::GPUProcessListener {
  NS_INLINE_DECL_REFCOUNTING(BackgroundedFallbackRenderer, final)

 public:
  explicit BackgroundedFallbackRenderer(nsIWidget* aWidget);

  void Destroy() final;

  void OnCompositorDestroyBackgrounded() final;

 private:
  ~BackgroundedFallbackRenderer() final;

  // The widget to notify when compositing is reinstantiated.
  nsIWidget* mWidget;
};

}  // namespace mozilla

#endif /* MOZILLA_PAINTING_WINDOWRENDERER_H */
