// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_COMPOSITING_VIEW_MAC_H
#define CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_COMPOSITING_VIEW_MAC_H

#import <Cocoa/Cocoa.h>
#import <QuartzCore/CVDisplayLink.h>
#include <QuartzCore/QuartzCore.h>

#include "base/callback.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/memory/scoped_nsobject.h"
#include "base/synchronization/lock.h"
#include "base/time.h"
#include "base/timer.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"

class IOSurfaceSupport;

namespace gfx {
class Rect;
}

namespace content {

// This class manages an OpenGL context and IOSurface for the accelerated
// compositing code path. The GL context is attached to
// RenderWidgetHostViewCocoa for blitting the IOSurface.
class CompositingIOSurfaceMac {
 public:
  // Passed to Create() to specify the ordering of the surface relative to the
  // containing window.
  enum SurfaceOrder {
    SURFACE_ORDER_ABOVE_WINDOW,
    SURFACE_ORDER_BELOW_WINDOW
  };

  // Returns NULL if IOSurface support is missing or GL APIs fail. Specify in
  // |order| the desired ordering relationship of the surface to the containing
  // window.
  static CompositingIOSurfaceMac* Create(SurfaceOrder order);
  ~CompositingIOSurfaceMac();

  // Set IOSurface that will be drawn on the next NSView drawRect.
  void SetIOSurface(uint64 io_surface_handle,
                    const gfx::Size& size);

  // Blit the IOSurface at the upper-left corner of the |view|. If |view| window
  // size is larger than the IOSurface, the remaining right and bottom edges
  // will be white. |scaleFactor| is 1 in normal views, 2 in HiDPI views.
  void DrawIOSurface(NSView* view, float scale_factor);

  // Copy the data of the "live" OpenGL texture referring to this IOSurfaceRef
  // into |out|. The copied region is specified with |src_pixel_subrect| and
  // the data is transformed so that it fits in |dst_pixel_size|.
  // |src_pixel_subrect| and |dst_pixel_size| are not in DIP but in pixel.
  // Caller must ensure that |out| is allocated with the size no less than
  // |4 * dst_pixel_size.width() * dst_pixel_size.height()| bytes.
  // |callback| is invoked when the operation is completed or failed.
  // Do no call this method again before |callback| is invoked.
  void CopyTo(const gfx::Rect& src_pixel_subrect,
              const gfx::Size& dst_pixel_size,
              void* out,
              const base::Callback<void(bool)>& callback);

  // Unref the IOSurface and delete the associated GL texture. If the GPU
  // process is no longer referencing it, this will delete the IOSurface.
  void UnrefIOSurface();

  // Call when globalFrameDidChange is received on the NSView.
  void GlobalFrameDidChange();

  // Disassociate the GL context with the NSView and unref the IOSurface. Do
  // this to switch to software drawing mode.
  void ClearDrawable();

  bool HasIOSurface() { return !!io_surface_.get(); }

  const gfx::Size& pixel_io_surface_size() const {
    return pixel_io_surface_size_;
  }
  // In cocoa view units / DIPs.
  const gfx::Size& io_surface_size() const { return io_surface_size_; }

  bool is_vsync_disabled() const { return is_vsync_disabled_; }

  // Get vsync scheduling parameters.
  // |interval_numerator/interval_denominator| equates to fractional number of
  // seconds between vsyncs.
  void GetVSyncParameters(base::TimeTicks* timebase,
                          uint32* interval_numerator,
                          uint32* interval_denominator);

 private:
  friend CVReturn DisplayLinkCallback(CVDisplayLinkRef,
                                      const CVTimeStamp*,
                                      const CVTimeStamp*,
                                      CVOptionFlags,
                                      CVOptionFlags*,
                                      void*);

  // Vertex structure for use in glDraw calls.
  struct SurfaceVertex {
    SurfaceVertex() : x_(0.0f), y_(0.0f), tx_(0.0f), ty_(0.0f) { }
    void set(float x, float y, float tx, float ty) {
      x_ = x;
      y_ = y;
      tx_ = tx;
      ty_ = ty;
    }
    void set_position(float x, float y) {
      x_ = x;
      y_ = y;
    }
    void set_texcoord(float tx, float ty) {
      tx_ = tx;
      ty_ = ty;
    }
    float x_;
    float y_;
    float tx_;
    float ty_;
  };

  // Counter-clockwise verts starting from upper-left corner (0, 0).
  struct SurfaceQuad {
    void set_size(gfx::Size vertex_size, gfx::Size texcoord_size) {
      // Texture coordinates are flipped vertically so they can be drawn on
      // a projection with a flipped y-axis (origin is top left).
      float vw = static_cast<float>(vertex_size.width());
      float vh = static_cast<float>(vertex_size.height());
      float tw = static_cast<float>(texcoord_size.width());
      float th = static_cast<float>(texcoord_size.height());
      verts_[0].set(0.0f, 0.0f, 0.0f, th);
      verts_[1].set(0.0f, vh, 0.0f, 0.0f);
      verts_[2].set(vw, vh, tw, 0.0f);
      verts_[3].set(vw, 0.0f, tw, th);
    }
    void set_rect(float x1, float y1, float x2, float y2) {
      verts_[0].set_position(x1, y1);
      verts_[1].set_position(x1, y2);
      verts_[2].set_position(x2, y2);
      verts_[3].set_position(x2, y1);
    }
    void set_texcoord_rect(float tx1, float ty1, float tx2, float ty2) {
      // Texture coordinates are flipped vertically so they can be drawn on
      // a projection with a flipped y-axis (origin is top left).
      verts_[0].set_texcoord(tx1, ty2);
      verts_[1].set_texcoord(tx1, ty1);
      verts_[2].set_texcoord(tx2, ty1);
      verts_[3].set_texcoord(tx2, ty2);
    }
    SurfaceVertex verts_[4];
  };

  // Keeps track of states and buffers for asynchronous readback of IOSurface.
  struct CopyContext {
    CopyContext();
    ~CopyContext();

    void Reset() {
      started = false;
      cycles_elapsed = 0;
      frame_buffer = 0;
      frame_buffer_texture = 0;
      pixel_buffer = 0;
      use_fence = false;
      fence = 0;
      out_buf = NULL;
      callback.Reset();
    }

    bool started;
    int cycles_elapsed;
    GLuint frame_buffer;
    GLuint frame_buffer_texture;
    GLuint pixel_buffer;
    bool use_fence;
    GLuint fence;
    gfx::Rect src_rect;
    gfx::Size dest_size;
    void* out_buf;
    base::Callback<void(bool)> callback;
  };

  CompositingIOSurfaceMac(IOSurfaceSupport* io_surface_support,
                          NSOpenGLContext* glContext,
                          CGLContextObj cglContext,
                          GLuint shader_program_blit_rgb,
                          GLint blit_rgb_sampler_location,
                          GLuint shader_program_white,
                          bool is_vsync_disabled,
                          CVDisplayLinkRef display_link);

  // Returns true if IOSurface is ready to render. False otherwise.
  bool MapIOSurfaceToTexture(uint64 io_surface_handle);

  void UnrefIOSurfaceWithContextCurrent();

  void DrawQuad(const SurfaceQuad& quad);

  // Called on display-link thread.
  void DisplayLinkTick(CVDisplayLinkRef display_link,
                       const CVTimeStamp* time);

  void CalculateVsyncParametersLockHeld(const CVTimeStamp* time);

  // Prevent from spinning on CGLFlushDrawable when it fails to throttle to
  // VSync frequency.
  void RateLimitDraws();

  void StartOrContinueDisplayLink();
  void StopDisplayLink();

  // Two implementations of CopyTo() in synchronous and asynchronous mode.
  bool SynchronousCopyTo(const gfx::Rect& src_pixel_subrect,
                         const gfx::Size& dst_pixel_size,
                         void* out);
  bool AsynchronousCopyTo(const gfx::Rect& src_pixel_subrect,
                          const gfx::Size& dst_pixel_size,
                          void* out,
                          const base::Callback<void(bool)>& callback);
  void FinishCopy();
  void CleanupResourcesForCopy();

  // Cached pointer to IOSurfaceSupport Singleton.
  IOSurfaceSupport* io_surface_support_;

  // GL context
  scoped_nsobject<NSOpenGLContext> glContext_;
  CGLContextObj cglContext_;  // weak, backed by |glContext_|.

  // IOSurface data.
  uint64 io_surface_handle_;
  base::mac::ScopedCFTypeRef<CFTypeRef> io_surface_;

  // The width and height of the io surface.
  gfx::Size pixel_io_surface_size_;  // In pixels.
  gfx::Size io_surface_size_;  // In view units.

  // The "live" OpenGL texture referring to this IOSurfaceRef. Note
  // that per the CGLTexImageIOSurface2D API we do not need to
  // explicitly update this texture's contents once created. All we
  // need to do is ensure it is re-bound before attempting to draw
  // with it.
  GLuint texture_;

  CopyContext copy_context_;

  // Timer for finishing a copy operation.
  base::RepeatingTimer<CompositingIOSurfaceMac> copy_timer_;

  // Shader parameters.
  GLuint shader_program_blit_rgb_;
  GLint blit_rgb_sampler_location_;
  GLuint shader_program_white_;

  SurfaceQuad quad_;

  bool is_vsync_disabled_;

  // CVDisplayLink for querying Vsync timing info and throttling swaps.
  CVDisplayLinkRef display_link_;

  // Timer for stopping display link after a timeout with no swaps.
  base::DelayTimer<CompositingIOSurfaceMac> display_link_stop_timer_;

  // Lock for sharing data between UI thread and display-link thread.
  base::Lock lock_;

  // Counts for throttling swaps.
  int64 vsync_count_;
  int64 swap_count_;

  // Vsync timing data.
  base::TimeTicks vsync_timebase_;
  uint32 vsync_interval_numerator_;
  uint32 vsync_interval_denominator_;
};

}  // namespace content

#endif  // CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_COMPOSITING_VIEW_MAC_H