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

#include "mozilla/Assertions.h"  // for MOZ_ASSERT, etc
#include "mozilla/EnumeratedArray.h"
#include "mozilla/RefPtr.h"  // for already_AddRefed, RefCounted
#include "mozilla/TypedEnumBits.h"
#include "mozilla/WidgetUtils.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/layers/CompositorTypes.h"
#include "mozilla/layers/LayersTypes.h"
#include "ImageTypes.h"
#include "MLGDeviceTypes.h"
#include "nsISupportsImpl.h"
#include "nsString.h"
#include "nsPrintfCString.h"

namespace mozilla {

namespace widget {
class CompositorWidget;
}  // namespace widget
namespace gfx {
class DrawTarget;
}  // namespace gfx

namespace layers {

struct GPUStats;
class BufferCache;
class ConstantBufferSection;
class DataTextureSource;
class MLGBufferD3D11;
class MLGDeviceD3D11;
class MLGRenderTargetD3D11;
class MLGResourceD3D11;
class MLGTexture;
class MLGTextureD3D11;
class SharedVertexBuffer;
class SharedConstantBuffer;
class TextureSource;
class VertexBufferSection;
struct ClearRegionHelper;

class MLGRenderTarget {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MLGRenderTarget)

  virtual gfx::IntSize GetSize() const = 0;
  virtual MLGRenderTargetD3D11* AsD3D11() { return nullptr; }

  // Returns the underlying texture of the render target.
  virtual MLGTexture* GetTexture() = 0;

  bool HasDepthBuffer() const {
    return (mFlags & MLGRenderTargetFlags::ZBuffer) ==
           MLGRenderTargetFlags::ZBuffer;
  }

  int32_t GetLastDepthStart() const { return mLastDepthStart; }
  void SetLastDepthStart(int32_t aDepthStart) { mLastDepthStart = aDepthStart; }

 protected:
  explicit MLGRenderTarget(MLGRenderTargetFlags aFlags);
  virtual ~MLGRenderTarget() = default;

 protected:
  MLGRenderTargetFlags mFlags;

  // When using a depth buffer, callers can track the range of depth values
  // that were last used.
  int32_t mLastDepthStart;
};

class MLGSwapChain {
 protected:
  virtual ~MLGSwapChain() = default;

 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MLGSwapChain)

  virtual RefPtr<MLGRenderTarget> AcquireBackBuffer() = 0;
  virtual bool ResizeBuffers(const gfx::IntSize& aSize) = 0;
  virtual gfx::IntSize GetSize() const = 0;

  // Present to the screen.
  virtual void Present() = 0;

  // Force a present without waiting for the previous frame's present to
  // complete.
  virtual void ForcePresent() = 0;

  // Copy an area of the backbuffer to a draw target.
  virtual void CopyBackbuffer(gfx::DrawTarget* aTarget,
                              const gfx::IntRect& aBounds) = 0;

  // Free any internal resources.
  virtual void Destroy() = 0;

  // Give the new invalid region to the swap chain in preparation for
  // acquiring the backbuffer. If the new invalid region is empty,
  // this returns false and no composite is required.
  //
  // The extra rect is used for the debug overlay, which is factored in
  // separately to avoid causing unnecessary composites.
  bool ApplyNewInvalidRegion(nsIntRegion&& aRegion,
                             const Maybe<gfx::IntRect>& aExtraRect);

  const nsIntRegion& GetBackBufferInvalidRegion() const {
    return mBackBufferInvalid;
  }

 protected:
  MLGSwapChain();

 protected:
  gfx::IntSize mLastPresentSize;
  // The swap chain tracks the invalid region of its buffers. After presenting,
  // the invalid region for the backbuffer is cleared. If using double
  // buffering, it is set to the area of the non-presented buffer that was not
  // painted this frame. The initial invalid region each frame comes from
  // LayerManagerMLGPU, and is combined with the back buffer's invalid region
  // before frame building begins.
  nsIntRegion mBackBufferInvalid;
  nsIntRegion mFrontBufferInvalid;
  bool mIsDoubleBuffered;
};

class MLGResource {
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MLGResource)

 public:
  enum class Type { Buffer, Texture };

  virtual Type GetType() const = 0;
  virtual MLGResourceD3D11* AsResourceD3D11() { return nullptr; }

 protected:
  virtual ~MLGResource() = default;
};

// A buffer for use as a shader input.
class MLGBuffer : public MLGResource {
 public:
  Type GetType() const override { return Type::Buffer; }
  virtual MLGBufferD3D11* AsD3D11() { return nullptr; }
  virtual size_t GetSize() const = 0;

 protected:
  virtual ~MLGBuffer() = default;
};

// This is a lower-level resource than a TextureSource. It wraps
// a 2D texture.
class MLGTexture : public MLGResource {
 public:
  Type GetType() const override { return Type::Texture; }
  virtual MLGTextureD3D11* AsD3D11() { return nullptr; }
  const gfx::IntSize& GetSize() const { return mSize; }

 protected:
  gfx::IntSize mSize;
};

enum class VertexShaderID {
  TexturedQuad,
  TexturedVertex,
  ColoredQuad,
  ColoredVertex,
  BlendVertex,
  Clear,
  MaskCombiner,
  DiagnosticText,
  MaxShaders
};

enum class PixelShaderID {
  ColoredQuad,
  ColoredVertex,
  TexturedQuadRGB,
  TexturedQuadRGBA,
  TexturedVertexRGB,
  TexturedVertexRGBA,
  TexturedQuadIMC4,
  TexturedQuadIdentityIMC4,
  TexturedQuadNV12,
  TexturedVertexIMC4,
  TexturedVertexIdentityIMC4,
  TexturedVertexNV12,
  ComponentAlphaQuad,
  ComponentAlphaVertex,
  BlendMultiply,
  BlendScreen,
  BlendOverlay,
  BlendDarken,
  BlendLighten,
  BlendColorDodge,
  BlendColorBurn,
  BlendHardLight,
  BlendSoftLight,
  BlendDifference,
  BlendExclusion,
  BlendHue,
  BlendSaturation,
  BlendColor,
  BlendLuminosity,
  Clear,
  MaskCombiner,
  DiagnosticText,
  MaxShaders
};

class MLGDevice {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MLGDevice)

  MLGDevice();

  virtual bool Initialize();

  virtual TextureFactoryIdentifier GetTextureFactoryIdentifier(
      widget::CompositorWidget* aWidget) const = 0;
  virtual int32_t GetMaxTextureSize() const = 0;
  virtual LayersBackend GetLayersBackend() const = 0;

  virtual RefPtr<MLGSwapChain> CreateSwapChainForWidget(
      widget::CompositorWidget* aWidget) = 0;

  // Markers for when we start and finish issuing "normal" (i.e., non-
  // diagnostic) draw commands for the frame.
  virtual void StartDiagnostics(uint32_t aInvalidPixels) = 0;
  virtual void EndDiagnostics() = 0;
  virtual void GetDiagnostics(GPUStats* aStats) = 0;

  // Layers interaction.
  virtual RefPtr<DataTextureSource> CreateDataTextureSource(
      TextureFlags aFlags) = 0;

  // Resource access
  virtual bool Map(MLGResource* aResource, MLGMapType aType,
                   MLGMappedResource* aMap) = 0;
  virtual void Unmap(MLGResource* aResource) = 0;
  virtual void UpdatePartialResource(MLGResource* aResource,
                                     const gfx::IntRect* aRect, void* aData,
                                     uint32_t aStride) = 0;
  virtual void CopyTexture(MLGTexture* aDest, const gfx::IntPoint& aTarget,
                           MLGTexture* aSource, const gfx::IntRect& aRect) = 0;

  // Begin a frame. This clears and resets all shared buffers.
  virtual void BeginFrame();
  virtual void EndFrame();

  // State setup commands.
  virtual void SetRenderTarget(MLGRenderTarget* aRT) = 0;
  virtual MLGRenderTarget* GetRenderTarget() = 0;
  virtual void SetViewport(const gfx::IntRect& aRT) = 0;
  virtual void SetScissorRect(const Maybe<gfx::IntRect>& aScissorRect) = 0;
  virtual void SetVertexShader(VertexShaderID aVertexShader) = 0;
  virtual void SetPixelShader(PixelShaderID aPixelShader) = 0;
  virtual void SetSamplerMode(uint32_t aIndex, SamplerMode aSamplerMode) = 0;
  virtual void SetBlendState(MLGBlendState aBlendState) = 0;
  virtual void SetVertexBuffer(uint32_t aSlot, MLGBuffer* aBuffer,
                               uint32_t aStride, uint32_t aOffset = 0) = 0;
  virtual void SetVSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer) = 0;
  virtual void SetPSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer) = 0;
  virtual void SetPSTextures(uint32_t aSlot, uint32_t aNumTextures,
                             TextureSource* const* aTextures) = 0;
  virtual void SetPSTexture(uint32_t aSlot, MLGTexture* aTexture) = 0;
  virtual void SetDepthTestMode(MLGDepthTestMode aMode) = 0;

  // If supported, bind constant buffers at a particular offset. These can only
  // be used if CanUseConstantBufferOffsetBinding returns true.
  virtual void SetVSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer,
                                   uint32_t aFirstConstant,
                                   uint32_t aNumConstants) = 0;
  virtual void SetPSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer,
                                   uint32_t aFirstConstant,
                                   uint32_t aNumConstants) = 0;

  // Set the topology. No API call is made if the topology has not changed.
  // The UnitQuad topology implicity binds a unit quad triangle strip as
  // vertex buffer #0.
  void SetTopology(MLGPrimitiveTopology aTopology);

  // Set textures that have special binding logic, and bind to multiple slots.
  virtual void SetPSTexturesNV12(uint32_t aSlot, TextureSource* aTexture) = 0;
  void SetPSTexturesYUV(uint32_t aSlot, TextureSource* aTexture);

  virtual RefPtr<MLGBuffer> CreateBuffer(
      MLGBufferType aType, uint32_t aSize, MLGUsage aUsage,
      const void* aInitialData = nullptr) = 0;

  virtual RefPtr<MLGTexture> CreateTexture(const gfx::IntSize& aSize,
                                           gfx::SurfaceFormat aFormat,
                                           MLGUsage aUsage,
                                           MLGTextureFlags aFlags) = 0;

  // Unwrap the underlying GPU texture in the given TextureSource, and re-wrap
  // it in an MLGTexture structure.
  virtual RefPtr<MLGTexture> CreateTexture(TextureSource* aSource) = 0;

  virtual RefPtr<MLGRenderTarget> CreateRenderTarget(
      const gfx::IntSize& aSize,
      MLGRenderTargetFlags aFlags = MLGRenderTargetFlags::Default) = 0;

  // Clear a render target to the given color, or clear a depth buffer.
  virtual void Clear(MLGRenderTarget* aRT, const gfx::DeviceColor& aColor) = 0;
  virtual void ClearDepthBuffer(MLGRenderTarget* aRT) = 0;

  // This is only available if CanUseClearView() returns true.
  virtual void ClearView(MLGRenderTarget* aRT, const gfx::DeviceColor& aColor,
                         const gfx::IntRect* aRects, size_t aNumRects) = 0;

  // Drawing Commands
  virtual void Draw(uint32_t aVertexCount, uint32_t aOffset) = 0;
  virtual void DrawInstanced(uint32_t aVertexCountPerInstance,
                             uint32_t aInstanceCount, uint32_t aVertexOffset,
                             uint32_t aInstanceOffset) = 0;
  virtual void Flush() = 0;

  // This unlocks any textures that were implicitly locked during drawing.
  virtual void UnlockAllTextures() = 0;

  virtual MLGDeviceD3D11* AsD3D11() { return nullptr; }

  // Helpers.
  void SetVertexBuffer(uint32_t aSlot, const VertexBufferSection* aSection);
  void SetPSConstantBuffer(uint32_t aSlot,
                           const ConstantBufferSection* aSection);
  void SetVSConstantBuffer(uint32_t aSlot,
                           const ConstantBufferSection* aSection);
  void SetPSTexture(uint32_t aSlot, TextureSource* aSource);
  void SetSamplerMode(uint32_t aIndex, gfx::SamplingFilter aFilter);

  // This creates or returns a previously created constant buffer, containing
  // a YCbCrShaderConstants instance.
  RefPtr<MLGBuffer> GetBufferForColorSpace(gfx::YUVColorSpace aColorSpace);
  // This creates or returns a previously created constant buffer, containing
  // a YCbCrBitDepthConstants instance.
  RefPtr<MLGBuffer> GetBufferForColorDepthCoefficient(
      gfx::ColorDepth aColorDepth);

  // A shared buffer that can be used to build VertexBufferSections.
  SharedVertexBuffer* GetSharedVertexBuffer() {
    return mSharedVertexBuffer.get();
  }
  // A shared buffer that can be used to build ConstantBufferSections. Intended
  // to be used with vertex shaders.
  SharedConstantBuffer* GetSharedVSBuffer() { return mSharedVSBuffer.get(); }
  // A shared buffer that can be used to build ConstantBufferSections. Intended
  // to be used with pixel shaders.
  SharedConstantBuffer* GetSharedPSBuffer() { return mSharedPSBuffer.get(); }
  // A cache for constant buffers, used when offset-based binding is not
  // supported.
  BufferCache* GetConstantBufferCache() { return mConstantBufferCache.get(); }

  // Unmap and upload all shared buffers to the GPU.
  void FinishSharedBufferUse();

  // These are used to detect and report initialization failure.
  virtual bool IsValid() const { return mInitialized && mIsValid; }
  const nsCString& GetFailureId() const { return mFailureId; }
  const nsCString& GetFailureMessage() const { return mFailureMessage; }

  // Prepare a clear-region operation to be run at a later time.
  void PrepareClearRegion(ClearRegionHelper* aOut,
                          nsTArray<gfx::IntRect>&& aRects,
                          const Maybe<int32_t>& aSortIndex);

  // Execute a clear-region operation. This may change shader state.
  void DrawClearRegion(const ClearRegionHelper& aHelper);

  // If supported, synchronize with the SyncObject given to clients.
  virtual bool Synchronize();

  // If this returns true, ClearView() can be called.
  bool CanUseClearView() const { return mCanUseClearView; }

  // If this returns true, constant buffers can be bound at specific offsets for
  // a given run of bytes. This is only supported on Windows 8+ for Direct3D 11.
  bool CanUseConstantBufferOffsetBinding() const {
    return mCanUseConstantBufferOffsetBinding;
  }

  // Return the maximum number of elements that can be bound to a constant
  // buffer. This is different than the maximum size of a buffer (there is
  // no such limit on Direct3D 11.1).
  //
  // The return value must be a power of two.
  size_t GetMaxConstantBufferBindSize() const {
    return mMaxConstantBufferBindSize;
  }

  // Helper function for unbinding textures since SetPSTexture is overloaded.
  void UnsetPSTexture(uint32_t aSlot) {
    TextureSource* nullTexture = nullptr;
    SetPSTexture(aSlot, nullTexture);
  }

  // Debugging helper function for dumping an MLGTexture to a file.
  void WriteAsPNG(MLGTexture* aTexture, const char* aPath);

  // Debugging helper function for copying a texture for later dumping to a
  // file.
  RefPtr<MLGTexture> CopyAndCreateReadbackTexture(MLGTexture* aTexture);

 protected:
  virtual ~MLGDevice();

  virtual void SetPrimitiveTopology(MLGPrimitiveTopology aTopology) = 0;

  // Optionally run a runtime test to determine if constant buffer offset
  // binding works.
  virtual bool VerifyConstantBufferOffsetting() { return true; }

  // Used during initialization to record failure reasons.
  bool Fail(const nsCString& aFailureId, const nsCString* aMessage);

  // Used during initialization to record failure reasons. Note: our
  // MOZ_FORMAT_PRINTF macro does not work on this function, so we
  // disable the warning.
#if defined(__GNUC__)
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wformat-security"
#endif
  template <typename... T>
  bool Fail(const char* aFailureId) {
    nsCString failureId(aFailureId);
    return Fail(failureId, nullptr);
  }
  template <typename... T>
  bool Fail(const char* aFailureId, const char* aMessage, const T&... args) {
    nsCString failureId(aFailureId);
    nsPrintfCString message(aMessage, args...);
    return Fail(failureId, &message);
  }
#if defined(__GNUC__)
#  pragma GCC diagnostic pop
#endif

  void UnmapSharedBuffers();

 private:
  MLGPrimitiveTopology mTopology;
  UniquePtr<SharedVertexBuffer> mSharedVertexBuffer;
  UniquePtr<SharedConstantBuffer> mSharedVSBuffer;
  UniquePtr<SharedConstantBuffer> mSharedPSBuffer;
  UniquePtr<BufferCache> mConstantBufferCache;

  nsCString mFailureId;
  nsCString mFailureMessage;
  bool mInitialized;

  typedef EnumeratedArray<gfx::YUVColorSpace, gfx::YUVColorSpace::UNKNOWN,
                          RefPtr<MLGBuffer>>
      ColorSpaceArray;
  ColorSpaceArray mColorSpaceBuffers;
  typedef EnumeratedArray<gfx::ColorDepth, gfx::ColorDepth::UNKNOWN,
                          RefPtr<MLGBuffer>>
      ColorDepthArray;
  ColorDepthArray mColorDepthBuffers;

 protected:
  bool mIsValid;
  bool mCanUseClearView;
  bool mCanUseConstantBufferOffsetBinding;
  size_t mMaxConstantBufferBindSize;

  RefPtr<MLGRenderTarget> mCurrentRT;
};

}  // namespace layers
}  // namespace mozilla

#endif  // mozilla_gfx_layers_mlgpu_MLGDevice_h