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

#include "GLContextTypes.h"             // for GLContext
#include "GLDefs.h"                     // for GLenum, LOCAL_GL_FRAMEBUFFER, etc
#include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
#include "mozilla/RefPtr.h"             // for RefPtr, already_AddRefed
#include "mozilla/gfx/Point.h"          // for IntSize, IntSizeTyped
#include "mozilla/gfx/Types.h"          // for SurfaceFormat, etc
#include "mozilla/layers/Compositor.h"  // for SurfaceInitMode, etc
#include "mozilla/layers/TextureHost.h"    // for CompositingRenderTarget
#include "mozilla/layers/CompositorOGL.h"  // for CompositorOGL
#include "mozilla/mozalloc.h"              // for operator new
#include "nsAString.h"
#include "nsCOMPtr.h"  // for already_AddRefed
#include "nsDebug.h"   // for NS_ERROR, NS_WARNING
#include "nsString.h"  // for nsAutoCString

namespace mozilla {
namespace gl {
class BindableTexture;
}  // namespace gl
namespace gfx {
class DataSourceSurface;
}  // namespace gfx

namespace layers {

class TextureSource;

class CompositingRenderTargetOGL : public CompositingRenderTarget {
  typedef mozilla::gl::GLContext GLContext;

  friend class CompositorOGL;

  enum class GLResourceOwnership : uint8_t {
    // Framebuffer and texture will be deleted when the RenderTarget is
    // destroyed.
    OWNED_BY_RENDER_TARGET,

    // Framebuffer and texture are only used by the RenderTarget, but never
    // deleted.
    EXTERNALLY_OWNED
  };

  struct InitParams {
    GLenum mFBOTextureTarget;
    SurfaceInitMode mInitMode;
  };

 public:
  ~CompositingRenderTargetOGL();

  const char* Name() const override { return "CompositingRenderTargetOGL"; }

  /**
   * Create a render target around the default FBO, for rendering straight to
   * the window.
   */
  static already_AddRefed<CompositingRenderTargetOGL> CreateForWindow(
      CompositorOGL* aCompositor, const gfx::IntSize& aSize) {
    RefPtr<CompositingRenderTargetOGL> result = new CompositingRenderTargetOGL(
        aCompositor, gfx::IntRect(gfx::IntPoint(), aSize), gfx::IntPoint(),
        aSize, GLResourceOwnership::EXTERNALLY_OWNED, 0, 0, Nothing());
    return result.forget();
  }

  static already_AddRefed<CompositingRenderTargetOGL>
  CreateForNewFBOAndTakeOwnership(CompositorOGL* aCompositor, GLuint aTexture,
                                  GLuint aFBO, const gfx::IntRect& aRect,
                                  const gfx::IntPoint& aClipSpaceOrigin,
                                  const gfx::IntSize& aPhySize,
                                  GLenum aFBOTextureTarget,
                                  SurfaceInitMode aInit) {
    RefPtr<CompositingRenderTargetOGL> result = new CompositingRenderTargetOGL(
        aCompositor, aRect, aClipSpaceOrigin, aPhySize,
        GLResourceOwnership::OWNED_BY_RENDER_TARGET, aTexture, aFBO,
        Some(InitParams{aFBOTextureTarget, aInit}));
    return result.forget();
  }

  static already_AddRefed<CompositingRenderTargetOGL>
  CreateForExternallyOwnedFBO(CompositorOGL* aCompositor, GLuint aFBO,
                              const gfx::IntRect& aRect,
                              const gfx::IntPoint& aClipSpaceOrigin) {
    RefPtr<CompositingRenderTargetOGL> result = new CompositingRenderTargetOGL(
        aCompositor, aRect, aClipSpaceOrigin, aRect.Size(),
        GLResourceOwnership::EXTERNALLY_OWNED, 0, aFBO, Nothing());
    return result.forget();
  }

  void BindTexture(GLenum aTextureUnit, GLenum aTextureTarget);

  /**
   * Call when we want to draw into our FBO
   */
  void BindRenderTarget();

  bool IsWindow() { return mFBO == 0; }

  GLuint GetFBO() const;

  GLuint GetTextureHandle() const {
    MOZ_ASSERT(!mNeedInitialization);
    return mTextureHandle;
  }

  // TextureSourceOGL
  TextureSourceOGL* AsSourceOGL() override {
    // XXX - Bug 900770
    MOZ_ASSERT(
        false,
        "CompositingRenderTargetOGL should not be used as a TextureSource");
    return nullptr;
  }
  gfx::IntSize GetSize() const override { return mSize; }

  // The point that DrawGeometry's aClipRect is relative to. Will be (0, 0) for
  // root render targets and equal to GetOrigin() for non-root render targets.
  gfx::IntPoint GetClipSpaceOrigin() const { return mClipSpaceOrigin; }

  gfx::SurfaceFormat GetFormat() const override {
    // XXX - Should it be implemented ? is the above assert true ?
    MOZ_ASSERT(false, "Not implemented");
    return gfx::SurfaceFormat::UNKNOWN;
  }

  // In render target coordinates, i.e. the same space as GetOrigin().
  // NOT relative to mClipSpaceOrigin!
  void SetClipRect(const Maybe<gfx::IntRect>& aRect) { mClipRect = aRect; }
  const Maybe<gfx::IntRect>& GetClipRect() const { return mClipRect; }

#ifdef MOZ_DUMP_PAINTING
  already_AddRefed<gfx::DataSourceSurface> Dump(
      Compositor* aCompositor) override;
#endif

  const gfx::IntSize& GetInitSize() const { return mSize; }
  const gfx::IntSize& GetPhysicalSize() const { return mPhySize; }

 protected:
  CompositingRenderTargetOGL(CompositorOGL* aCompositor,
                             const gfx::IntRect& aRect,
                             const gfx::IntPoint& aClipSpaceOrigin,
                             const gfx::IntSize& aPhySize,
                             GLResourceOwnership aGLResourceOwnership,
                             GLuint aTexure, GLuint aFBO,
                             const Maybe<InitParams>& aNeedInitialization)
      : CompositingRenderTarget(aRect.TopLeft()),
        mNeedInitialization(aNeedInitialization),
        mSize(aRect.Size()),
        mPhySize(aPhySize),
        mCompositor(aCompositor),
        mGL(aCompositor->gl()),
        mClipSpaceOrigin(aClipSpaceOrigin),
        mGLResourceOwnership(aGLResourceOwnership),
        mTextureHandle(aTexure),
        mFBO(aFBO) {
    MOZ_ASSERT(mGL);
  }

  /**
   * Actually do the initialisation.
   * We do this lazily so that when we first set this render target on the
   * compositor we do not have to re-bind the FBO after unbinding it, or
   * alternatively leave the FBO bound after creation. Note that we leave our
   * FBO bound, and so calling this method is only suitable when about to use
   * this render target.
   */
  void Initialize(GLenum aFBOTextureTarget);

  /**
   * Some() between construction and Initialize, if initialization was
   * requested.
   */
  Maybe<InitParams> mNeedInitialization;

  /*
   * Users of render target would draw in logical size, but it is
   * actually drawn to a surface in physical size.  GL surfaces have
   * a limitation on their size, a smaller surface would be
   * allocated for the render target if the caller requests in a
   * size too big.
   */
  gfx::IntSize mSize;     // Logical size, the expected by callers.
  gfx::IntSize mPhySize;  // Physical size, the real size of the surface.

  /**
   * There is temporary a cycle between the compositor and the render target,
   * each having a strong ref to the other. The compositor's reference to
   * the target is always cleared at the end of a frame.
   */
  RefPtr<CompositorOGL> mCompositor;
  RefPtr<GLContext> mGL;
  Maybe<gfx::IntRect> mClipRect;
  gfx::IntPoint mClipSpaceOrigin;
  GLResourceOwnership mGLResourceOwnership;
  GLuint mTextureHandle;
  GLuint mFBO;
};

}  // namespace layers
}  // namespace mozilla

#endif /* MOZILLA_GFX_SURFACEOGL_H */
