// 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 GPU_COMMAND_BUFFER_SERVICE_TEXTURE_MANAGER_H_
#define GPU_COMMAND_BUFFER_SERVICE_TEXTURE_MANAGER_H_

#include <list>
#include <set>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/containers/hash_tables.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "gpu/command_buffer/service/async_pixel_transfer_delegate.h"
#include "gpu/command_buffer/service/gl_utils.h"
#include "gpu/command_buffer/service/memory_tracking.h"
#include "gpu/gpu_export.h"
#include "ui/gl/gl_image.h"

namespace gpu {
namespace gles2 {

class GLES2Decoder;
struct ContextState;
struct DecoderFramebufferState;
class Display;
class ErrorState;
class FeatureInfo;
class FramebufferManager;
class MailboxManager;
class TextureManager;
class TextureRef;

// Info about Textures currently in the system.
// This class wraps a real GL texture, keeping track of its meta-data. It is
// jointly owned by possibly multiple TextureRef.
class GPU_EXPORT Texture {
 public:
  explicit Texture(GLuint service_id);

  GLenum min_filter() const {
    return min_filter_;
  }

  GLenum mag_filter() const {
    return mag_filter_;
  }

  GLenum wrap_s() const {
    return wrap_s_;
  }

  GLenum wrap_t() const {
    return wrap_t_;
  }

  GLenum usage() const {
    return usage_;
  }

  GLenum pool() const {
    return pool_;
  }

  int num_uncleared_mips() const {
    return num_uncleared_mips_;
  }

  uint32 estimated_size() const {
    return estimated_size_;
  }

  bool CanRenderTo() const {
    return target_ != GL_TEXTURE_EXTERNAL_OES;
  }

  // The service side OpenGL id of the texture.
  GLuint service_id() const {
    return service_id_;
  }

  void SetServiceId(GLuint service_id) {
    DCHECK(service_id);
    service_id_ = service_id;
  }

  // Returns the target this texure was first bound to or 0 if it has not
  // been bound. Once a texture is bound to a specific target it can never be
  // bound to a different target.
  GLenum target() const {
    return target_;
  }

  bool SafeToRenderFrom() const {
    return cleared_;
  }

  // Get the width and height for a particular level. Returns false if level
  // does not exist.
  bool GetLevelSize(
      GLint target, GLint level, GLsizei* width, GLsizei* height) const;

  // Get the type of a level. Returns false if level does not exist.
  bool GetLevelType(
      GLint target, GLint level, GLenum* type, GLenum* internal_format) const;

  // Get the image bound to a particular level. Returns NULL if level
  // does not exist.
  gfx::GLImage* GetLevelImage(GLint target, GLint level) const;

  bool HasImages() const {
    return has_images_;
  }

  // Returns true of the given dimensions are inside the dimensions of the
  // level and if the type matches the level.
  bool ValidForTexture(
      GLint target,
      GLint level,
      GLint xoffset,
      GLint yoffset,
      GLsizei width,
      GLsizei height,
      GLenum type) const;

  bool IsValid() const {
    return !!target();
  }

  bool IsAttachedToFramebuffer() const {
    return framebuffer_attachment_count_ != 0;
  }

  void AttachToFramebuffer() {
    ++framebuffer_attachment_count_;
  }

  void DetachFromFramebuffer() {
    DCHECK_GT(framebuffer_attachment_count_, 0);
    --framebuffer_attachment_count_;
  }

  void SetImmutable(bool immutable) {
    immutable_ = immutable;
  }

  bool IsImmutable() const {
    return immutable_;
  }

  // Whether a particular level/face is cleared.
  bool IsLevelCleared(GLenum target, GLint level) const;

  // Whether the texture has been defined
  bool IsDefined() const {
    return estimated_size() > 0;
  }

  // Initialize TEXTURE_MAX_ANISOTROPY to 1 if we haven't done so yet.
  void InitTextureMaxAnisotropyIfNeeded(GLenum target);

  void OnWillModifyPixels();
  void OnDidModifyPixels();

 private:
  friend class MailboxManager;
  friend class MailboxManagerTest;
  friend class TextureDefinition;
  friend class TextureManager;
  friend class TextureRef;
  friend class TextureTestHelper;

  ~Texture();
  void AddTextureRef(TextureRef* ref);
  void RemoveTextureRef(TextureRef* ref, bool have_context);
  MemoryTypeTracker* GetMemTracker();

  // Condition on which this texture is renderable. Can be ONLY_IF_NPOT if it
  // depends on context support for non-power-of-two textures (i.e. will be
  // renderable if NPOT support is in the context, otherwise not, e.g. texture
  // with a NPOT level). ALWAYS means it doesn't depend on context features
  // (e.g. complete POT), NEVER means it's not renderable regardless (e.g.
  // incomplete).
  enum CanRenderCondition {
    CAN_RENDER_ALWAYS,
    CAN_RENDER_NEVER,
    CAN_RENDER_ONLY_IF_NPOT
  };

  struct LevelInfo {
    LevelInfo();
    LevelInfo(const LevelInfo& rhs);
    ~LevelInfo();

    bool cleared;
    GLenum target;
    GLint level;
    GLenum internal_format;
    GLsizei width;
    GLsizei height;
    GLsizei depth;
    GLint border;
    GLenum format;
    GLenum type;
    scoped_refptr<gfx::GLImage> image;
    uint32 estimated_size;
  };

  // Set the info for a particular level.
  void SetLevelInfo(
      const FeatureInfo* feature_info,
      GLenum target,
      GLint level,
      GLenum internal_format,
      GLsizei width,
      GLsizei height,
      GLsizei depth,
      GLint border,
      GLenum format,
      GLenum type,
      bool cleared);

  // In GLES2 "texture complete" means it has all required mips for filtering
  // down to a 1x1 pixel texture, they are in the correct order, they are all
  // the same format.
  bool texture_complete() const {
    return texture_complete_;
  }

  // In GLES2 "cube complete" means all 6 faces level 0 are defined, all the
  // same format, all the same dimensions and all width = height.
  bool cube_complete() const {
    return cube_complete_;
  }

  // Whether or not this texture is a non-power-of-two texture.
  bool npot() const {
    return npot_;
  }

  // Marks a particular level as cleared or uncleared.
  void SetLevelCleared(GLenum target, GLint level, bool cleared);

  // Updates the cleared flag for this texture by inspecting all the mips.
  void UpdateCleared();

  // Clears any renderable uncleared levels.
  // Returns false if a GL error was generated.
  bool ClearRenderableLevels(GLES2Decoder* decoder);

  // Clears the level.
  // Returns false if a GL error was generated.
  bool ClearLevel(GLES2Decoder* decoder, GLenum target, GLint level);

  // Sets a texture parameter.
  // TODO(gman): Expand to SetParameteriv,fv
  // Returns GL_NO_ERROR on success. Otherwise the error to generate.
  GLenum SetParameteri(
      const FeatureInfo* feature_info, GLenum pname, GLint param);
  GLenum SetParameterf(
      const FeatureInfo* feature_info, GLenum pname, GLfloat param);

  // Makes each of the mip levels as though they were generated.
  bool MarkMipmapsGenerated(const FeatureInfo* feature_info);

  bool NeedsMips() const {
    return min_filter_ != GL_NEAREST && min_filter_ != GL_LINEAR;
  }

  // True if this texture meets all the GLES2 criteria for rendering.
  // See section 3.8.2 of the GLES2 spec.
  bool CanRender(const FeatureInfo* feature_info) const;

  // Returns true if mipmaps can be generated by GL.
  bool CanGenerateMipmaps(const FeatureInfo* feature_info) const;

  // Sets the Texture's target
  // Parameters:
  //   target: GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP or
  //           GL_TEXTURE_EXTERNAL_OES or GL_TEXTURE_RECTANGLE_ARB
  //   max_levels: The maximum levels this type of target can have.
  void SetTarget(
      const FeatureInfo* feature_info, GLenum target, GLint max_levels);

  // Update info about this texture.
  void Update(const FeatureInfo* feature_info);

  // Set the image for a particular level.
  void SetLevelImage(
      const FeatureInfo* feature_info,
      GLenum target,
      GLint level,
      gfx::GLImage* image);

  // Appends a signature for the given level.
  void AddToSignature(
      const FeatureInfo* feature_info,
      GLenum target, GLint level, std::string* signature) const;

  void SetMailboxManager(MailboxManager* mailbox_manager);

  // Updates the unsafe textures count in all the managers referencing this
  // texture.
  void UpdateSafeToRenderFrom(bool cleared);

  // Updates the uncleared mip count in all the managers referencing this
  // texture.
  void UpdateMipCleared(LevelInfo* info, bool cleared);

  // Computes the CanRenderCondition flag.
  CanRenderCondition GetCanRenderCondition() const;

  // Updates the unrenderable texture count in all the managers referencing this
  // texture.
  void UpdateCanRenderCondition();

  // Updates the images count in all the managers referencing this
  // texture.
  void UpdateHasImages();

  // Increment the framebuffer state change count in all the managers
  // referencing this texture.
  void IncAllFramebufferStateChangeCount();

  MailboxManager* mailbox_manager_;

  // Info about each face and level of texture.
  std::vector<std::vector<LevelInfo> > level_infos_;

  // The texture refs that point to this Texture.
  typedef std::set<TextureRef*> RefSet;
  RefSet refs_;

  // The single TextureRef that accounts for memory for this texture. Must be
  // one of refs_.
  TextureRef* memory_tracking_ref_;

  // The id of the texure
  GLuint service_id_;

  // Whether all renderable mips of this texture have been cleared.
  bool cleared_;

  int num_uncleared_mips_;

  // The target. 0 if unset, otherwise GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP.
  GLenum target_;

  // Texture parameters.
  GLenum min_filter_;
  GLenum mag_filter_;
  GLenum wrap_s_;
  GLenum wrap_t_;
  GLenum usage_;
  GLenum pool_;

  // The maximum level that has been set.
  GLint max_level_set_;

  // Whether or not this texture is "texture complete"
  bool texture_complete_;

  // Whether or not this texture is "cube complete"
  bool cube_complete_;

  // Whether or not this texture is non-power-of-two
  bool npot_;

  // Whether this texture has ever been bound.
  bool has_been_bound_;

  // The number of framebuffers this texture is attached to.
  int framebuffer_attachment_count_;

  // Whether the texture is immutable and no further changes to the format
  // or dimensions of the texture object can be made.
  bool immutable_;

  // Whether or not this texture has images.
  bool has_images_;

  // Size in bytes this texture is assumed to take in memory.
  uint32 estimated_size_;

  // Cache of the computed CanRenderCondition flag.
  CanRenderCondition can_render_condition_;

  // Whether we have initialized TEXTURE_MAX_ANISOTROPY to 1.
  bool texture_max_anisotropy_initialized_;

  DISALLOW_COPY_AND_ASSIGN(Texture);
};

// This class represents a texture in a client context group. It's mostly 1:1
// with a client id, though it can outlive the client id if it's still bound to
// a FBO or another context when destroyed.
// Multiple TextureRef can point to the same texture with cross-context sharing.
class GPU_EXPORT TextureRef : public base::RefCounted<TextureRef> {
 public:
  TextureRef(TextureManager* manager, GLuint client_id, Texture* texture);
  static scoped_refptr<TextureRef> Create(TextureManager* manager,
                                          GLuint client_id,
                                          GLuint service_id);

  void AddObserver() { num_observers_++; }
  void RemoveObserver() { num_observers_--; }

  const Texture* texture() const { return texture_; }
  Texture* texture() { return texture_; }
  GLuint client_id() const { return client_id_; }
  GLuint service_id() const { return texture_->service_id(); }
  GLint num_observers() const { return num_observers_; }

 private:
  friend class base::RefCounted<TextureRef>;
  friend class Texture;
  friend class TextureManager;

  ~TextureRef();
  const TextureManager* manager() const { return manager_; }
  TextureManager* manager() { return manager_; }
  void reset_client_id() { client_id_ = 0; }

  TextureManager* manager_;
  Texture* texture_;
  GLuint client_id_;
  GLint num_observers_;

  DISALLOW_COPY_AND_ASSIGN(TextureRef);
};

// Holds data that is per gles2_cmd_decoder, but is related to to the
// TextureManager.
struct DecoderTextureState {
  // total_texture_upload_time automatically initialized to 0 in default
  // constructor.
  DecoderTextureState(bool texsubimage2d_faster_than_teximage2d)
      : tex_image_2d_failed(false),
        texture_upload_count(0),
        texsubimage2d_faster_than_teximage2d(
            texsubimage2d_faster_than_teximage2d) {}

  // This indicates all the following texSubImage2D calls that are part of the
  // failed texImage2D call should be ignored.
  bool tex_image_2d_failed;

  // Command buffer stats.
  int texture_upload_count;
  base::TimeDelta total_texture_upload_time;

  bool texsubimage2d_faster_than_teximage2d;
};

// This class keeps track of the textures and their sizes so we can do NPOT and
// texture complete checking.
//
// NOTE: To support shared resources an instance of this class will need to be
// shared by multiple GLES2Decoders.
class GPU_EXPORT TextureManager {
 public:
  class GPU_EXPORT DestructionObserver {
   public:
    DestructionObserver();
    virtual ~DestructionObserver();

    // Called in ~TextureManager.
    virtual void OnTextureManagerDestroying(TextureManager* manager) = 0;

    // Called via ~TextureRef.
    virtual void OnTextureRefDestroying(TextureRef* texture) = 0;

   private:
    DISALLOW_COPY_AND_ASSIGN(DestructionObserver);
  };

  enum DefaultAndBlackTextures {
    kTexture2D,
    kCubeMap,
    kExternalOES,
    kRectangleARB,
    kNumDefaultTextures
  };

  TextureManager(MemoryTracker* memory_tracker,
                 FeatureInfo* feature_info,
                 GLsizei max_texture_size,
                 GLsizei max_cube_map_texture_size,
                 bool use_default_textures);
  ~TextureManager();

  void set_framebuffer_manager(FramebufferManager* manager) {
    framebuffer_manager_ = manager;
  }

  // Init the texture manager.
  bool Initialize();

  // Must call before destruction.
  void Destroy(bool have_context);

  // Returns the maximum number of levels.
  GLint MaxLevelsForTarget(GLenum target) const {
    switch (target) {
      case GL_TEXTURE_2D:
        return  max_levels_;
      case GL_TEXTURE_EXTERNAL_OES:
        return 1;
      default:
        return max_cube_map_levels_;
    }
  }

  // Returns the maximum size.
  GLsizei MaxSizeForTarget(GLenum target) const {
    switch (target) {
      case GL_TEXTURE_2D:
      case GL_TEXTURE_EXTERNAL_OES:
        return max_texture_size_;
      default:
        return max_cube_map_texture_size_;
    }
  }

  // Returns the maxium number of levels a texture of the given size can have.
  static GLsizei ComputeMipMapCount(GLenum target,
                                    GLsizei width,
                                    GLsizei height,
                                    GLsizei depth);

  // Checks if a dimensions are valid for a given target.
  bool ValidForTarget(
      GLenum target, GLint level,
      GLsizei width, GLsizei height, GLsizei depth);

  // True if this texture meets all the GLES2 criteria for rendering.
  // See section 3.8.2 of the GLES2 spec.
  bool CanRender(const TextureRef* ref) const {
    return ref->texture()->CanRender(feature_info_.get());
  }

  // Returns true if mipmaps can be generated by GL.
  bool CanGenerateMipmaps(const TextureRef* ref) const {
    return ref->texture()->CanGenerateMipmaps(feature_info_.get());
  }

  // Sets the Texture's target
  // Parameters:
  //   target: GL_TEXTURE_2D or GL_TEXTURE_CUBE_MAP
  //   max_levels: The maximum levels this type of target can have.
  void SetTarget(
      TextureRef* ref,
      GLenum target);

  // Set the info for a particular level in a TexureInfo.
  void SetLevelInfo(
      TextureRef* ref,
      GLenum target,
      GLint level,
      GLenum internal_format,
      GLsizei width,
      GLsizei height,
      GLsizei depth,
      GLint border,
      GLenum format,
      GLenum type,
      bool cleared);

  // Adapter to call above function.
  void SetLevelInfoFromParams(TextureRef* ref,
                              const gpu::AsyncTexImage2DParams& params) {
    SetLevelInfo(
        ref, params.target, params.level, params.internal_format,
        params.width, params.height, 1 /* depth */,
        params.border, params.format,
        params.type, true /* cleared */ );
  }

  Texture* Produce(TextureRef* ref);

  // Maps an existing texture into the texture manager, at a given client ID.
  TextureRef* Consume(GLuint client_id, Texture* texture);

  // Sets a mip as cleared.
  void SetLevelCleared(TextureRef* ref, GLenum target,
                       GLint level, bool cleared);

  // Sets a texture parameter of a Texture
  // Returns GL_NO_ERROR on success. Otherwise the error to generate.
  // TODO(gman): Expand to SetParameteriv,fv
  void SetParameteri(
      const char* function_name, ErrorState* error_state,
      TextureRef* ref, GLenum pname, GLint param);
  void SetParameterf(
      const char* function_name, ErrorState* error_state,
      TextureRef* ref, GLenum pname, GLfloat param);

  // Makes each of the mip levels as though they were generated.
  // Returns false if that's not allowed for the given texture.
  bool MarkMipmapsGenerated(TextureRef* ref);

  // Clears any uncleared renderable levels.
  bool ClearRenderableLevels(GLES2Decoder* decoder, TextureRef* ref);

  // Clear a specific level.
  bool ClearTextureLevel(
      GLES2Decoder* decoder, TextureRef* ref, GLenum target, GLint level);

  // Creates a new texture info.
  TextureRef* CreateTexture(GLuint client_id, GLuint service_id);

  // Gets the texture info for the given texture.
  TextureRef* GetTexture(GLuint client_id) const;

  // Removes a texture info.
  void RemoveTexture(GLuint client_id);

  // Gets a Texture for a given service id (note: it assumes the texture object
  // is still mapped in this TextureManager).
  Texture* GetTextureForServiceId(GLuint service_id) const;

  TextureRef* GetDefaultTextureInfo(GLenum target) {
    switch (target) {
      case GL_TEXTURE_2D:
        return default_textures_[kTexture2D].get();
      case GL_TEXTURE_CUBE_MAP:
        return default_textures_[kCubeMap].get();
      case GL_TEXTURE_EXTERNAL_OES:
        return default_textures_[kExternalOES].get();
      case GL_TEXTURE_RECTANGLE_ARB:
        return default_textures_[kRectangleARB].get();
      default:
        NOTREACHED();
        return NULL;
    }
  }

  bool HaveUnrenderableTextures() const {
    return num_unrenderable_textures_ > 0;
  }

  bool HaveUnsafeTextures() const {
    return num_unsafe_textures_ > 0;
  }

  bool HaveUnclearedMips() const {
    return num_uncleared_mips_ > 0;
  }

  bool HaveImages() const {
    return num_images_ > 0;
  }

  GLuint black_texture_id(GLenum target) const {
    switch (target) {
      case GL_SAMPLER_2D:
        return black_texture_ids_[kTexture2D];
      case GL_SAMPLER_CUBE:
        return black_texture_ids_[kCubeMap];
      case GL_SAMPLER_EXTERNAL_OES:
        return black_texture_ids_[kExternalOES];
      case GL_SAMPLER_2D_RECT_ARB:
        return black_texture_ids_[kRectangleARB];
      default:
        NOTREACHED();
        return 0;
    }
  }

  size_t mem_represented() const {
    return
        memory_tracker_managed_->GetMemRepresented() +
        memory_tracker_unmanaged_->GetMemRepresented();
  }

  void SetLevelImage(
      TextureRef* ref,
      GLenum target,
      GLint level,
      gfx::GLImage* image);

  void AddToSignature(
      TextureRef* ref,
      GLenum target,
      GLint level,
      std::string* signature) const;

  void AddObserver(DestructionObserver* observer) {
    destruction_observers_.push_back(observer);
  }

  void RemoveObserver(DestructionObserver* observer) {
    for (unsigned int i = 0; i < destruction_observers_.size(); i++) {
      if (destruction_observers_[i] == observer) {
        std::swap(destruction_observers_[i], destruction_observers_.back());
        destruction_observers_.pop_back();
        return;
      }
    }
    NOTREACHED();
  }

  struct DoTextImage2DArguments {
    GLenum target;
    GLint level;
    GLenum internal_format;
    GLsizei width;
    GLsizei height;
    GLint border;
    GLenum format;
    GLenum type;
    const void* pixels;
    uint32 pixels_size;
  };

  bool ValidateTexImage2D(
    ContextState* state,
    const char* function_name,
    const DoTextImage2DArguments& args,
    // Pointer to TextureRef filled in if validation successful.
    // Presumes the pointer is valid.
    TextureRef** texture_ref);

  void ValidateAndDoTexImage2D(
    DecoderTextureState* texture_state,
    ContextState* state,
    DecoderFramebufferState* framebuffer_state,
    const DoTextImage2DArguments& args);

  // TODO(kloveless): Make GetTexture* private once this is no longer called
  // from gles2_cmd_decoder.
  TextureRef* GetTextureInfoForTarget(ContextState* state, GLenum target);
  TextureRef* GetTextureInfoForTargetUnlessDefault(
      ContextState* state, GLenum target);

  bool ValidateFormatAndTypeCombination(
    ErrorState* error_state, const char* function_name,
    GLenum format, GLenum type);

  // Note that internal_format is only checked in relation to the format
  // parameter, so that this function may be used to validate texSubImage2D.
  bool ValidateTextureParameters(
    ErrorState* error_state, const char* function_name,
    GLenum format, GLenum type, GLenum internal_format, GLint level);

 private:
  friend class Texture;
  friend class TextureRef;

  // Helper for Initialize().
  scoped_refptr<TextureRef> CreateDefaultAndBlackTextures(
      GLenum target,
      GLuint* black_texture);

  void DoTexImage2D(
    DecoderTextureState* texture_state,
    ErrorState* error_state,
    DecoderFramebufferState* framebuffer_state,
    TextureRef* texture_ref,
    const DoTextImage2DArguments& args);

  void StartTracking(TextureRef* texture);
  void StopTracking(TextureRef* texture);

  void UpdateSafeToRenderFrom(int delta);
  void UpdateUnclearedMips(int delta);
  void UpdateCanRenderCondition(Texture::CanRenderCondition old_condition,
                                Texture::CanRenderCondition new_condition);
  void UpdateNumImages(int delta);
  void IncFramebufferStateChangeCount();

  MemoryTypeTracker* GetMemTracker(GLenum texture_pool);
  scoped_ptr<MemoryTypeTracker> memory_tracker_managed_;
  scoped_ptr<MemoryTypeTracker> memory_tracker_unmanaged_;

  scoped_refptr<FeatureInfo> feature_info_;

  FramebufferManager* framebuffer_manager_;

  // Info for each texture in the system.
  typedef base::hash_map<GLuint, scoped_refptr<TextureRef> > TextureMap;
  TextureMap textures_;

  GLsizei max_texture_size_;
  GLsizei max_cube_map_texture_size_;
  GLint max_levels_;
  GLint max_cube_map_levels_;

  const bool use_default_textures_;

  int num_unrenderable_textures_;
  int num_unsafe_textures_;
  int num_uncleared_mips_;
  int num_images_;

  // Counts the number of Textures allocated with 'this' as its manager.
  // Allows to check no Texture will outlive this.
  unsigned int texture_count_;

  bool have_context_;

  // Black (0,0,0,1) textures for when non-renderable textures are used.
  // NOTE: There is no corresponding Texture for these textures.
  // TextureInfos are only for textures the client side can access.
  GLuint black_texture_ids_[kNumDefaultTextures];

  // The default textures for each target (texture name = 0)
  scoped_refptr<TextureRef> default_textures_[kNumDefaultTextures];

  std::vector<DestructionObserver*> destruction_observers_;

  DISALLOW_COPY_AND_ASSIGN(TextureManager);
};

// This class records texture upload time when in scope.
class ScopedTextureUploadTimer {
 public:
  explicit ScopedTextureUploadTimer(DecoderTextureState* texture_state);
  ~ScopedTextureUploadTimer();

 private:
  DecoderTextureState* texture_state_;
  base::TimeTicks begin_time_;
  DISALLOW_COPY_AND_ASSIGN(ScopedTextureUploadTimer);
};

}  // namespace gles2
}  // namespace gpu

#endif  // GPU_COMMAND_BUFFER_SERVICE_TEXTURE_MANAGER_H_