/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * 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_DEVICEMANAGERD3D9_H
#define GFX_DEVICEMANAGERD3D9_H

#include "gfxTypes.h"
#include "nsAutoPtr.h"
#include "d3d9.h"
#include "nsTArray.h"
#include "mozilla/layers/CompositorTypes.h"
#include "mozilla/RefPtr.h"

struct nsIntRect;

namespace mozilla {
namespace layers {

class DeviceManagerD3D9;
class LayerD3D9;
class Nv3DVUtils;
class Layer;
class TextureSourceD3D9;

// Shader Constant locations
const int CBmLayerTransform = 0;
const int CBmProjection = 4;
const int CBvRenderTargetOffset = 8;
const int CBvTextureCoords = 9;
const int CBvLayerQuad = 10;
// we don't use opacity with solid color shaders
const int CBfLayerOpacity = 0;
const int CBvColor = 0;

enum DeviceManagerState {
  // The device and swap chain are OK.
  DeviceOK,
  // The device or swap chain are in a bad state, and we should not render.
  DeviceFail,
  // The device is lost and cannot be reset, the user should forget the
  // current device manager and create a new one.
  DeviceMustRecreate,
};


/**
 * This structure is used to pass rectangles to our shader constant. We can use
 * this for passing rectangular areas to SetVertexShaderConstant. In the format
 * of a 4 component float(x,y,width,height). Our vertex shader can then use
 * this to construct rectangular positions from the 0,0-1,1 quad that we source
 * it with.
 */
struct ShaderConstantRect
{
  float mX, mY, mWidth, mHeight;

  // Provide all the commonly used argument types to prevent all the local
  // casts in the code.
  ShaderConstantRect(float aX, float aY, float aWidth, float aHeight)
    : mX(aX), mY(aY), mWidth(aWidth), mHeight(aHeight)
  { }

  ShaderConstantRect(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight)
    : mX((float)aX), mY((float)aY)
    , mWidth((float)aWidth), mHeight((float)aHeight)
  { }

  ShaderConstantRect(int32_t aX, int32_t aY, float aWidth, float aHeight)
    : mX((float)aX), mY((float)aY), mWidth(aWidth), mHeight(aHeight)
  { }

  // For easy passing to SetVertexShaderConstantF.
  operator float* () { return &mX; }
};

/**
 * SwapChain class, this class manages the swap chain belonging to a
 * LayerManagerD3D9.
 */
class SwapChainD3D9 MOZ_FINAL
{
  NS_INLINE_DECL_REFCOUNTING(SwapChainD3D9)
public:

  /**
   * This function will prepare the device this swap chain belongs to for
   * rendering to this swap chain. Only after calling this function can the
   * swap chain be drawn to, and only until this function is called on another
   * swap chain belonging to this device will the device draw to it. Passed in
   * is the size of the swap chain. If the window size differs from the size
   * during the last call to this function the swap chain will resize. Note that
   * in no case does this function guarantee the backbuffer to still have its
   * old content.
   */
  DeviceManagerState PrepareForRendering();

  already_AddRefed<IDirect3DSurface9> GetBackBuffer();

  /**
   * This function will present the selected rectangle of the swap chain to
   * its associated window.
   */
  void Present(const nsIntRect &aRect);
  void Present();

private:
  friend class DeviceManagerD3D9;

  SwapChainD3D9(DeviceManagerD3D9 *aDeviceManager);

  // Private destructor, to discourage deletion outside of Release():
  ~SwapChainD3D9();
  
  bool Init(HWND hWnd);

  /**
   * This causes us to release our swap chain, clearing out our resource usage
   * so the master device may reset.
   */
  void Reset();

  nsRefPtr<IDirect3DSwapChain9> mSwapChain;
  nsRefPtr<DeviceManagerD3D9> mDeviceManager;
  HWND mWnd;
};

/**
 * Device manager, this class is used by the layer managers to share the D3D9
 * device and create swap chains for the individual windows the layer managers
 * belong to.
 */
class DeviceManagerD3D9 MOZ_FINAL
{
public:
  DeviceManagerD3D9();
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DeviceManagerD3D9)

  /**
   * Initialises the device manager, the underlying device, and everything else
   * the manager needs.
   * Returns true if initialisation succeeds, false otherwise.
   * Note that if initisalisation fails, you cannot try again - you must throw
   * away the DeviceManagerD3D9 and create a new one.
   */
  bool Init();

  /**
   * Sets up the render state for the device for layer rendering.
   */
  void SetupRenderState();

  /**
   * Create a swap chain setup to work with the specified window.
   */
  already_AddRefed<SwapChainD3D9> CreateSwapChain(HWND hWnd);

  IDirect3DDevice9 *device() { return mDevice; }

  bool IsD3D9Ex() { return mDeviceEx; }

  bool HasDynamicTextures() { return mHasDynamicTextures; }

  enum ShaderMode {
    RGBLAYER,
    RGBALAYER,
    COMPONENTLAYERPASS1,
    COMPONENTLAYERPASS2,
    YCBCRLAYER,
    SOLIDCOLORLAYER
  };

  // returns the register to be used for the mask texture, if appropriate
  uint32_t SetShaderMode(ShaderMode aMode, MaskType aMaskType);

  /** 
   * Return pointer to the Nv3DVUtils instance 
   */ 
  Nv3DVUtils *GetNv3DVUtils()  { return mNv3DVUtils; }

  /**
   * Returns true if this device was removed.
   */
  bool DeviceWasRemoved() { return mDeviceWasRemoved; }

  uint32_t GetDeviceResetCount() { return mDeviceResetCount; }

  int32_t GetMaxTextureSize() { return mMaxTextureSize; }

  // Removes aHost from our list of texture hosts if it is the head.
  void RemoveTextureListHead(TextureSourceD3D9* aHost);

  /**
   * Creates a texture using our device.
   * If needed, we keep a record of the new texture, so the texture can be
   * released. In this case, aTextureHostIDirect3DTexture9 must be non-null.
   */
  TemporaryRef<IDirect3DTexture9> CreateTexture(const gfx::IntSize &aSize,
                                                _D3DFORMAT aFormat,
                                                D3DPOOL aPool,
                                                TextureSourceD3D9* aTextureHostIDirect3DTexture9);
#ifdef DEBUG
  // Looks for aFind in the list of texture hosts.
  // O(n) so only use for assertions.
  bool IsInTextureHostList(TextureSourceD3D9* aFind);
#endif

  /**
   * This function verifies the device is ready for rendering, internally this
   * will test the cooperative level of the device and reset the device if
   * needed. If this returns false subsequent rendering calls may return errors.
   */
  DeviceManagerState VerifyReadyForRendering();

  static uint32_t sMaskQuadRegister;

private:
  friend class SwapChainD3D9;

  ~DeviceManagerD3D9();
  void DestroyDevice();

  /**
   * This will fill our vertex buffer with the data of our quad, it may be
   * called when the vertex buffer is recreated.
   */
  bool CreateVertexBuffer();

  /**
   * Release all textures created by this device manager.
   */
  void ReleaseTextureResources();
  /**
   * Add aHost to our list of texture hosts.
   */
  void RegisterTextureHost(TextureSourceD3D9* aHost);

  /* Array used to store all swap chains for device resets */
  nsTArray<SwapChainD3D9*> mSwapChains;

  /* The D3D device we use */
  nsRefPtr<IDirect3DDevice9> mDevice;

  /* The D3D9Ex device - only valid on Vista+ with WDDM */
  nsRefPtr<IDirect3DDevice9Ex> mDeviceEx;

  /* An instance of the D3D9 object */
  nsRefPtr<IDirect3D9> mD3D9;

  /* An instance of the D3D9Ex object - only valid on Vista+ with WDDM */
  nsRefPtr<IDirect3D9Ex> mD3D9Ex;

  /* Vertex shader used for layer quads */
  nsRefPtr<IDirect3DVertexShader9> mLayerVS;

  /* Pixel shader used for RGB textures */
  nsRefPtr<IDirect3DPixelShader9> mRGBPS;

  /* Pixel shader used for RGBA textures */
  nsRefPtr<IDirect3DPixelShader9> mRGBAPS;

  /* Pixel shader used for component alpha textures (pass 1) */
  nsRefPtr<IDirect3DPixelShader9> mComponentPass1PS;

  /* Pixel shader used for component alpha textures (pass 2) */
  nsRefPtr<IDirect3DPixelShader9> mComponentPass2PS;

  /* Pixel shader used for RGB textures */
  nsRefPtr<IDirect3DPixelShader9> mYCbCrPS;

  /* Pixel shader used for solid colors */
  nsRefPtr<IDirect3DPixelShader9> mSolidColorPS;

  /* As above, but using a mask layer */
  nsRefPtr<IDirect3DVertexShader9> mLayerVSMask;
  nsRefPtr<IDirect3DVertexShader9> mLayerVSMask3D;
  nsRefPtr<IDirect3DPixelShader9> mRGBPSMask;
  nsRefPtr<IDirect3DPixelShader9> mRGBAPSMask;
  nsRefPtr<IDirect3DPixelShader9> mRGBAPSMask3D;
  nsRefPtr<IDirect3DPixelShader9> mComponentPass1PSMask;
  nsRefPtr<IDirect3DPixelShader9> mComponentPass2PSMask;
  nsRefPtr<IDirect3DPixelShader9> mYCbCrPSMask;
  nsRefPtr<IDirect3DPixelShader9> mSolidColorPSMask;

  /* Vertex buffer containing our basic vertex structure */
  nsRefPtr<IDirect3DVertexBuffer9> mVB;

  /* Our vertex declaration */
  nsRefPtr<IDirect3DVertexDeclaration9> mVD;

  /* We maintain a doubly linked list of all d3d9 texture hosts which host
   * d3d9 textures created by this device manager.
   * Texture hosts must remove themselves when they disappear (i.e., we
   * expect all hosts in the list to be valid).
   * The list is cleared when we release the textures.
   */
  TextureSourceD3D9* mTextureHostList;

  /* Our focus window - this is really a dummy window we can associate our
   * device with.
   */
  HWND mFocusWnd;

  /* we use this to help track if our device temporarily or permanently lost */
  HMONITOR mDeviceMonitor;

  uint32_t mDeviceResetCount;

  uint32_t mMaxTextureSize;

  /**
   * Wrap (repeat) or clamp textures. We prefer the former so we can do buffer
   * rotation, but some older hardware doesn't support it.
   */
  D3DTEXTUREADDRESS mTextureAddressingMode;

  /* If this device supports dynamic textures */
  bool mHasDynamicTextures;

  /* If this device was removed */
  bool mDeviceWasRemoved;

  /* Nv3DVUtils instance */ 
  nsAutoPtr<Nv3DVUtils> mNv3DVUtils; 

  /**
   * Verifies all required device capabilities are present.
   */
  bool VerifyCaps();
};

} /* namespace layers */
} /* namespace mozilla */

#endif /* GFX_DEVICEMANAGERD3D9_H */
