content: /*
   * Copyright 2012, 2013 Hannes Janetzek
   * Copyright 2016 devemux86
   * Copyright 2017 Longri
   *
   * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
   *
   * This program is free software: you can redistribute it and/or modify it under the
   * terms of the GNU Lesser General Public License as published by the Free Software
   * Foundation, either version 3 of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful, but WITHOUT ANY
   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
   * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License along with
   * this program. If not, see <http://www.gnu.org/licenses/>.
   */
  package org.oscim.renderer.bucket;
  
  import org.oscim.backend.CanvasAdapter;
  import org.oscim.backend.GL;
  import org.oscim.backend.canvas.Bitmap;
  import org.oscim.backend.canvas.Color;
  import org.oscim.renderer.GLState;
  import org.oscim.renderer.GLUtils;
  import org.oscim.utils.pool.Inlist;
  import org.oscim.utils.pool.SyncPool;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.ArrayList;
  
  import javax.annotation.CheckReturnValue;
  
  import static org.oscim.backend.GLAdapter.gl;
  
  public class TextureItem extends Inlist<TextureItem> {
      static final Logger log = LoggerFactory.getLogger(TextureItem.class);
  
      static final boolean dbg = false;
  
      /**
       * texture ID
       */
      public int id;
  
      /**
       * current settings
       */
      public final int width;
      public final int height;
      public final boolean repeat;
  
      /**
       * vertex offset from which this texture is referenced
       */
      /* FIXME dont put this here! */
      public int offset;
      public int indices;
  
      /**
       * temporary Bitmap
       */
      public Bitmap bitmap;
  
      /**
       * do not release the texture when TextureItem is released.
       */
      private TextureItem ref;
      private int used = 0;
  
      /**
       * texture data is ready
       */
      boolean loaded;
  
      final TexturePool pool;
  
      public boolean mipmap;
  
      private TextureItem(TexturePool pool, int id) {
          this(pool, id, pool.mWidth, pool.mHeight, false);
      }
  
      public TextureItem(Bitmap bitmap) {
          this(bitmap, false);
      }
  
      public TextureItem(Bitmap bitmap, boolean repeat) {
          this(NOPOOL, -1, bitmap.getWidth(), bitmap.getHeight(), repeat);
          this.bitmap = bitmap;
      }
  
      private TextureItem(TexturePool pool, int id, int width, int height, boolean repeat) {
          this.id = id;
          this.width = width;
          this.height = height;
          this.pool = pool;
          this.repeat = repeat;
      }
  
      public static TextureItem clone(TextureItem ti) {
  
          TextureItem clone = new TextureItem(NOPOOL, ti.id, ti.width, ti.height, ti.repeat);
          clone.id = ti.id;
          clone.ref = (ti.ref == null) ? ti : ti.ref;
          clone.loaded = ti.loaded;
  
          clone.ref.used++;
  
          return clone;
      }
  
      public int hashCode() {
          int result = 17;
          result = 31 * result + id;
          return result;
      }
  
      /**
       * Upload Image to Texture
       * [on GL-Thread]
       */
      public void upload() {
          if (loaded)
              return;
  
          if (ref == null) {
              pool.uploadTexture(this);
  
          } else {
              /* load referenced texture */
              ref.upload();
              id = ref.id;
  
          }
          loaded = true;
      }
  
      /**
       * Bind Texture for rendering
       * [on GL-Thread]
       */
      public void bind() {
          if (loaded)
              GLState.bindTex2D(id);
          else
              upload();
      }
  
      /**
       * Dispose TextureItem
       * [Threadsafe]
       *
       * @return this.next
       */
      @CheckReturnValue
      public TextureItem dispose() {
          TextureItem n = this.next;
          this.next = null;
          pool.release(this);
          return n;
      }
  
      public static class TexturePool extends SyncPool<TextureItem> {
          private final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(10);
  
          private final int mHeight;
          private final int mWidth;
          private final boolean mUseBitmapPool;
          private final boolean mMipmaps;
  
          //private final int mBitmapFormat;
          //private final int mBitmapType;
  
          protected int mTexCnt = 0;
  
          public TexturePool(int maxFill, int width, int height, boolean mipmap) {
              super(maxFill);
              mWidth = width;
              mHeight = height;
              mUseBitmapPool = true;
              mMipmaps = mipmap;
          }
  
          public TexturePool(int maxFill) {
              super(maxFill);
              mWidth = 0;
              mHeight = 0;
              mUseBitmapPool = false;
              mMipmaps = false;
          }
  
          @Override
          public TextureItem releaseAll(TextureItem t) {
              throw new RuntimeException("use TextureItem.dispose()");
          }
  
          /**
           * Retrieve a TextureItem from pool.
           */
          @Override
          public synchronized TextureItem get() {
              TextureItem t = super.get();
  
              if (!mUseBitmapPool)
                  return t;
  
              synchronized (mBitmaps) {
                  int size = mBitmaps.size();
                  if (size == 0)
                      t.bitmap = CanvasAdapter.newBitmap(mWidth, mHeight, 0);
                  else {
                      t.bitmap = mBitmaps.remove(size - 1);
                      t.bitmap.eraseColor(Color.TRANSPARENT);
                  }
              }
  
              return t;
          }
  
          public synchronized TextureItem get(Bitmap bitmap) {
              TextureItem t = super.get();
              t.bitmap = bitmap;
  
              return t;
          }
  
          @Override
          protected TextureItem createItem() {
              return new TextureItem(this, -1);
          }
  
          @Override
          protected boolean clearItem(TextureItem t) {
  
              if (t.used > 0)
                  return false;
  
              if (t.ref != null) {
                  /* dispose texture if this clone holds the last handle */
                  if (t.ref.used == 0) {
                      t.ref.dispose();
                      return false;
                  }
                  t.ref.used--;
                  return false;
              }
  
              t.loaded = false;
  
              if (mUseBitmapPool)
                  releaseBitmap(t);
  
              return t.id >= 0;
          }
  
          @Override
          protected void freeItem(TextureItem t) {
  
              if (t.ref == null && t.used == 0 && t.id >= 0) {
                  mTexCnt--;
                  synchronized (disposedTextures) {
                      disposedTextures.add(Integer.valueOf(t.id));
                      t.id = -1;
                  }
              }
          }
  
          protected void releaseBitmap(TextureItem t) {
  
              if (t.bitmap == null)
                  return;
  
              synchronized (mBitmaps) {
                  mBitmaps.add(t.bitmap);
                  t.bitmap = null;
              }
          }
  
          private void uploadTexture(TextureItem t) {
  
              if (t.bitmap == null)
                  throw new RuntimeException("Missing bitmap for texture");
  
              if (t.id < 0) {
                  int[] textureIds = GLUtils.glGenTextures(1);
                  t.id = textureIds[0];
  
                  t.mipmap |= mMipmaps;
  
                  initTexture(t);
  
                  if (dbg)
                      log.debug("fill:" + getFill()
                              + " count:" + mTexCnt
                              + " new texture " + t.id);
  
                  mTexCnt++;
  
                  t.bitmap.uploadToTexture(false);
              } else {
                  GLState.bindTex2D(t.id);
  
                  /* use faster subimage upload */
                  t.bitmap.uploadToTexture(true);
              }
  
              if (t.mipmap)
                  gl.generateMipmap(GL.TEXTURE_2D);
  
              if (dbg)
                  GLUtils.checkGlError(getClass().getName());
  
              if (mUseBitmapPool)
                  releaseBitmap(t);
          }
  
          protected void initTexture(TextureItem t) {
              GLState.bindTex2D(t.id);
  
              if (t.mipmap) {
                  gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER,
                          GL.LINEAR_MIPMAP_LINEAR);
              } else {
                  gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER,
                          GL.LINEAR);
              }
  
              gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER,
                      GL.LINEAR);
  
              if (t.repeat) {
                  gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S,
                          GL.REPEAT);
                  gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T,
                          GL.REPEAT);
              } else {
                  gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S,
                          GL.CLAMP_TO_EDGE);
                  gl.texParameterf(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T,
                          GL.CLAMP_TO_EDGE);
              }
          }
      }
  
      /* Pool for not-pooled textures. Disposed items will only be released
       * on the GL-Thread and will not be put back in any pool. */
      static final TexturePool NOPOOL = new TexturePool(0);
      static final ArrayList<Integer> disposedTextures = new ArrayList<Integer>();
  
      /**
       * Disposed textures are released by MapRenderer after each frame
       */
      public static void disposeTextures() {
          synchronized (disposedTextures) {
  
              int size = disposedTextures.size();
              if (size > 0) {
                  int[] tmp = new int[size];
                  for (int i = 0; i < size; i++)
                      tmp[i] = disposedTextures.get(i).intValue();
  
                  disposedTextures.clear();
                  GLUtils.glDeleteTextures(size, tmp);
              }
          }
      }
  }
  