[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/dimr/processing/master/core/src/processing/opengl/Texture.java [Back]  [Original]

/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
  Part of the Processing project - http://processing.org

  Copyright (c) 2012-15 The Processing Foundation
  Copyright (c) 2004-12 Ben Fry and Casey Reas
  Copyright (c) 2001-04 Massachusetts Institute of Technology

  This library 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, version 2.1.

  This library 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 library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
*/

package processing.opengl;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PGraphics;
import processing.opengl.PGraphicsOpenGL.GLResourceTexture;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.NoSuchElementException;

/**
 * This class wraps an OpenGL texture.
 * By Andres Colubri
 *
 */
public class Texture implements PConstants {
  /**
   * Texture with normalized UV.
   */
  protected static final int TEX2D   = 0;
  /**
   * Texture with un-normalized UV.
   */
  protected static final int TEXRECT = 1;

  /** Point sampling: both magnification and minification filtering are set
   * to nearest */
  protected static final int POINT = 2;
  /** Linear sampling: magnification filtering is nearest, minification set
   * to linear */
  protected static final int LINEAR = 3;
  /** Bilinear sampling: both magnification filtering is set to linear and
   * minification either to linear-mipmap-nearest (linear interpolation is used
   * within a mipmap, but not between different mipmaps). */
  protected static final int BILINEAR = 4;
  /** Trilinear sampling: magnification filtering set to linear, minification to
   * linear-mipmap-linear, which offers the best mipmap quality since linear
   * interpolation to compute the value in each of two maps and then
   * interpolates linearly between these two values. */
  protected static final int TRILINEAR = 5;


  // This constant controls how many times pixelBuffer and rgbaPixels can be
  // accessed before they are not released anymore. The idea is that if they
  // have been used only a few times, it doesn't make sense to keep them around.
  protected static final int MAX_UPDATES = 10;

  // The minimum amount of free JVM's memory (in MB) before pixelBuffer and
  // rgbaPixels are released every time after they are used.
  protected static final int MIN_MEMORY = 5;

  public int width, height;

  public int glName;
  public int glTarget;
  public int glFormat;
  public int glMinFilter;
  public int glMagFilter;
  public int glWrapS;
  public int glWrapT;
  public int glWidth;
  public int glHeight;
  private GLResourceTexture glres;

  protected PGraphicsOpenGL pg;
  protected PGL pgl;                // The interface between Processing and OpenGL.
  protected int context;            // The context that created this texture.
  protected boolean colorBuffer;    // true if it is the color attachment of
                                    // FrameBuffer object.

  protected boolean usingMipmaps;
  protected boolean usingRepeat;
  protected float maxTexcoordU;
  protected float maxTexcoordV;
  protected boolean bound;

  protected boolean invertedX;
  protected boolean invertedY;

  protected int[] rgbaPixels = null;
  protected IntBuffer pixelBuffer = null;

  protected int[] edgePixels = null;
  protected IntBuffer edgeBuffer = null;

  protected FrameBuffer tempFbo = null;
  protected int pixBufUpdateCount = 0;
  protected int rgbaPixUpdateCount = 0;

  /** Modified portion of the texture */
  protected boolean modified;
  protected int mx1, my1, mx2, my2;

  protected Object bufferSource;
  protected LinkedList bufferCache = null;
  protected LinkedList usedBuffers = null;
  protected Method disposeBufferMethod;
  public static final int MAX_BUFFER_CACHE_SIZE = 3;

  ////////////////////////////////////////////////////////////

  // Constructors.


  public Texture(PGraphicsOpenGL pg) {
    this.pg = pg;
    pgl = pg.pgl;
    context = pgl.createEmptyContext();

    colorBuffer = false;

    glName = 0;
  }


  /**
   * Creates an instance of PTexture with size width x height. The texture is
   * initialized (empty) to that size.
   * @param width  int
   * @param height  int
   */
  public Texture(PGraphicsOpenGL pg, int width, int height) {
    this(pg, width, height, new Parameters());
  }


  /**
   * Creates an instance of PTexture with size width x height and with the
   * specified parameters. The texture is initialized (empty) to that size.
   * @param width int
   * @param height int
   * @param params Parameters
   */
  public Texture(PGraphicsOpenGL pg, int width, int height, Object params) {
    this.pg = pg;
    pgl = pg.pgl;
    context = pgl.createEmptyContext();

    colorBuffer = false;

    glName = 0;

    init(width, height, (Parameters)params);
  }


  ////////////////////////////////////////////////////////////

  // Init, resize methods


  /**
   * Sets the size of the image and texture to width x height. If the texture is
   * already initialized, it first destroys the current OpenGL texture object
   * and then creates a new one with the specified size.
   * @param width int
   * @param height int
   */
  public void init(int width, int height) {
    Parameters params;
    if (0 < glName) {
      // Re-initializing a pre-existing texture.
      // We use the current parameters as default:
      params = getParameters();
    } else {
      // Just built-in default parameters otherwise:
      params = new Parameters();
    }
    init(width, height, params);
  }


  /**
   * Sets the size of the image and texture to width x height, and the
   * parameters of the texture to params. If the texture is already initialized,
   * it first destroys the current OpenGL texture object and then creates a new
   * one with the specified size.
   * @param width int
   * @param height int
   * @param params GLTextureParameters
   */
  public void init(int width, int height, Parameters params)  {
    setParameters(params);
    setSize(width, height);
    allocate();
  }


  /**
   * Initializes the texture using GL parameters
   */
  public void init(int width, int height,
                   int glName, int glTarget, int glFormat,
                   int glWidth, int glHeight,
                   int glMinFilter, int glMagFilter,
                   int glWrapS, int glWrapT) {
    this.width = width;
    this.height = height;

    this.glName = glName;
    this.glTarget = glTarget;
    this.glFormat = glFormat;
    this.glWidth = glWidth;
    this.glHeight = glHeight;
    this.glMinFilter = glMinFilter;
    this.glMagFilter = glMagFilter;
    this.glWrapS = glWrapS;
    this.glWrapT = glWrapT;

    maxTexcoordU = (float)width / glWidth;
    maxTexcoordV = (float)height / glHeight;

    usingMipmaps = glMinFilter == PGL.LINEAR_MIPMAP_NEAREST ||
                   glMinFilter == PGL.LINEAR_MIPMAP_LINEAR;

    usingRepeat = glWrapS == PGL.REPEAT || glWrapT == PGL.REPEAT;
  }


  public void resize(int wide, int high) {
    // Disposing current resources.
    dispose();

    // Creating new texture with the appropriate size.
    Texture tex = new Texture(pg, wide, high, getParameters());

    // Copying the contents of this texture into tex.
    tex.set(this);

    // Now, overwriting "this" with tex.
    copyObject(tex);

    // Nullifying some utility objects so they are recreated with the
    // appropriate size when needed.
    tempFbo = null;
  }


  /**
   * Returns true if the texture has been initialized.
   * @return boolean
   */
  public boolean available()  {
    return 0 < glName;
  }


  ////////////////////////////////////////////////////////////

  // Set methods


  public void set(Texture tex) {
    copyTexture(tex, 0, 0, tex.width, tex.height, true);
  }


  public void set(Texture tex, int x, int y, int w, int h) {
    copyTexture(tex, x, y, w, h, true);
  }


  public void set(int texTarget, int texName, int texWidth, int texHeight,
                  int w, int h) {
    copyTexture(texTarget, texName, texWidth, texHeight, 0, 0, w, h, true);
  }


  public void set(int texTarget, int texName, int texWidth, int texHeight,
                  int target, int tex, int x, int y, int w, int h) {
    copyTexture(texTarget, texName, texWidth, texHeight, x, y, w, h, true);
  }


  public void set(int[] pixels) {
    set(pixels, 0, 0, width, height, ARGB);
  }


  public void set(int[] pixels, int format) {
    set(pixels, 0, 0, width, height, format);
  }


  public void set(int[] pixels, int x, int y, int w, int h) {
    set(pixels, x, y, w, h, ARGB);
  }


  public void set(int[] pixels, int x, int y, int w, int h, int format) {
    if (pixels == null) {
      PGraphics.showWarning("The pixels array is null.");
      return;
    }
    if (pixels.length < w * h) {
      PGraphics.showWarning("The pixel array has a length of " +
                            pixels.length + ", but it should be at least " +
                            w * h);
      return;
    }

    if (pixels.length == 0 || w == 0 || h == 0) {
      return;
    }

    boolean enabledTex = false;
    if (!pgl.texturingIsEnabled(glTarget)) {
      pgl.enableTexturing(glTarget);
      enabledTex = true;
    }
    pgl.bindTexture(glTarget, glName);

    loadPixels(w * h);
    convertToRGBA(pixels, format, w, h);
    updatePixelBuffer(rgbaPixels);
    pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE,
                      pixelBuffer);
    fillEdges(x, y, w, h);

    if (usingMipmaps) {
      if (PGraphicsOpenGL.autoMipmapGenSupported) {
        pgl.generateMipmap(glTarget);
      } else {
        manualMipmap();
      }
    }

    pgl.bindTexture(glTarget, 0);
    if (enabledTex) {
      pgl.disableTexturing(glTarget);
    }

    releasePixelBuffer();
    releaseRGBAPixels();

    updateTexels(x, y, w, h);
  }


  ////////////////////////////////////////////////////////////

  // Native set methods


  public void setNative(int[] pixels) {
    setNative(pixels, 0, 0, width, height);
  }


  public void setNative(int[] pixels, int x, int y, int w, int h) {
    updatePixelBuffer(pixels);
    setNative(pixelBuffer, x, y, w, h);
    releasePixelBuffer();
  }


  public void setNative(IntBuffer pixBuf, int x, int y, int w, int h) {
    if (pixBuf == null) {
      pixBuf = null;
      PGraphics.showWarning("The pixel buffer is null.");
      return;
    }
    if (pixBuf.capacity() < w * h) {
      PGraphics.showWarning("The pixel bufer has a length of " +
                            pixBuf.capacity()  + ", but it should be at least " +
                            w * h);
      return;
    }

    if (pixBuf.capacity()  == 0) {
      // Nothing to do (means that w == h == 0) but not an erroneous situation
      return;
    }

    boolean enabledTex = false;
    if (!pgl.texturingIsEnabled(glTarget)) {
      pgl.enableTexturing(glTarget);
      enabledTex = true;
    }
    pgl.bindTexture(glTarget, glName);

    pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE,
                      pixBuf);
    fillEdges(x, y, w, h);

    if (usingMipmaps) {
      if (PGraphicsOpenGL.autoMipmapGenSupported) {
        pgl.generateMipmap(glTarget);
      } else {
        manualMipmap();
      }
    }
    pgl.bindTexture(glTarget, 0);
    if (enabledTex) {
      pgl.disableTexturing(glTarget);
    }

    updateTexels(x, y, w, h);
  }


  ////////////////////////////////////////////////////////////

  // Get methods


  /**
   * Copy texture to pixels. Involves video memory to main memory transfer (slow).
   */
  public void get(int[] pixels) {
    if (pixels == null) {
      throw new RuntimeException("Trying to copy texture to null pixels array");
    }
    if (pixels.length != width * height) {
      throw new RuntimeException("Trying to copy texture to pixels array of " +
                                 "wrong size");
    }

    if (tempFbo == null) {
      tempFbo = new FrameBuffer(pg, glWidth, glHeight);
    }

    // Attaching the texture to the color buffer of a FBO, binding the FBO and
    // reading the pixels from the current draw buffer (which is the color
    // buffer of the FBO).
    tempFbo.setColorBuffer(this);
    pg.pushFramebuffer();
    pg.setFramebuffer(tempFbo);
    tempFbo.readPixels();
    pg.popFramebuffer();

    tempFbo.getPixels(pixels);
    convertToARGB(pixels);

    if (invertedX) flipArrayOnX(pixels, 1);
    if (invertedY) flipArrayOnY(pixels, 1);
  }


  ////////////////////////////////////////////////////////////

  // Put methods (the source texture is not resized to cover the entire
  // destination).


  public void put(Texture tex) {
    copyTexture(tex, 0, 0, tex.width, tex.height, false);
  }


  public void put(Texture tex, int x, int y, int w, int h) {
    copyTexture(tex, x, y, w, h, false);
  }


  public void put(int texTarget, int texName, int texWidth, int texHeight,
                  int w, int h) {
    copyTexture(texTarget, texName, texWidth, texHeight, 0, 0, w, h, false);
  }


  public void put(int texTarget, int texName, int texWidth, int texHeight,
                  int target, int tex, int x, int y, int w, int h) {
    copyTexture(texTarget, texName, texWidth, texHeight, x, y, w, h, false);
  }


  ////////////////////////////////////////////////////////////

  // Get OpenGL parameters


  /**
   * Returns true or false whether or not the texture is using mipmaps.
   * @return boolean
   */
  public boolean usingMipmaps()  {
    return usingMipmaps;
  }


  public void usingMipmaps(boolean mipmaps, int sampling)  {
    int glMagFilter0 = glMagFilter;
    int glMinFilter0 = glMinFilter;
    if (mipmaps) {
      if (sampling == POINT) {
        glMagFilter = PGL.NEAREST;
        glMinFilter = PGL.NEAREST;
        usingMipmaps = false;
      } else if (sampling == LINEAR)  {
        glMagFilter = PGL.NEAREST;
        glMinFilter =
          PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR;
        usingMipmaps = true;
      } else if (sampling == BILINEAR)  {
        glMagFilter = PGL.LINEAR;
        glMinFilter =
          PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR;
        usingMipmaps = true;
      } else if (sampling == TRILINEAR)  {
        glMagFilter = PGL.LINEAR;
        glMinFilter =
          PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_LINEAR : PGL.LINEAR;
        usingMipmaps = true;
      } else {
        throw new RuntimeException("Unknown texture filtering mode");
      }
    } else {
      usingMipmaps = false;
      if (sampling == POINT) {
        glMagFilter = PGL.NEAREST;
        glMinFilter = PGL.NEAREST;
      } else if (sampling == LINEAR)  {
        glMagFilter = PGL.NEAREST;
        glMinFilter = PGL.LINEAR;
      } else if (sampling == BILINEAR || sampling == TRILINEAR)  {
        glMagFilter = PGL.LINEAR;
        glMinFilter = PGL.LINEAR;
      } else {
        throw new RuntimeException("Unknown texture filtering mode");
      }
    }

    if (glMagFilter0 != glMagFilter || glMinFilter0 != glMinFilter) {
      bind();
      pgl.texParameteri(glTarget, PGL.TEXTURE_MIN_FILTER, glMinFilter);
      pgl.texParameteri(glTarget, PGL.TEXTURE_MAG_FILTER, glMagFilter);
      if (usingMipmaps) {
        if (PGraphicsOpenGL.autoMipmapGenSupported) {
          pgl.generateMipmap(glTarget);
        } else {
          manualMipmap();
        }
      }
      unbind();
    }
  }


  /**
   * Returns true or false whether or not the texture is using repeat wrap mode
   * along either U or V directions.
   * @return boolean
   */
  public boolean usingRepeat()  {
    return usingRepeat;
  }


  public void usingRepeat(boolean repeat)  {
    if (repeat) {
      glWrapS = PGL.REPEAT;
      glWrapT = PGL.REPEAT;
      usingRepeat = true;
    } else {
      glWrapS = PGL.CLAMP_TO_EDGE;
      glWrapT = PGL.CLAMP_TO_EDGE;
      usingRepeat = false;
    }

    bind();
    pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_S, glWrapS);
    pgl.texParameteri(glTarget, PGL.TEXTURE_WRAP_T, glWrapT);
    unbind();
  }


  /**
   * Returns the maximum possible value for the texture coordinate U
   * (horizontal).
   * @return float
   */
  public float maxTexcoordU() {
    return maxTexcoordU;
  }


  /**
   * Returns the maximum possible value for the texture coordinate V (vertical).
   * @return float
   */
  public float maxTexcoordV() {
    return maxTexcoordV;
  }


  /**
   * Returns true if the texture is inverted along the horizontal direction.
   * @return boolean;
   */
  public boolean invertedX() {
    return invertedX;
  }


  /**
   * Sets the texture as inverted or not along the horizontal direction.
   * @param v boolean;
   */
  public void invertedX(boolean v) {
    invertedX = v;
  }


  /**
   * Returns true if the texture is inverted along the vertical direction.
   * @return boolean;
   */
  public boolean invertedY() {
    return invertedY;
  }


  /**
   * Sets the texture as inverted or not along the vertical direction.
   * @param v boolean;
   */
  public void invertedY(boolean v) {
    invertedY = v;
  }


  public int currentSampling() {
    if (glMagFilter == PGL.NEAREST && glMinFilter == PGL.NEAREST) {
      return POINT;
    } else if (glMagFilter == PGL.NEAREST &&
               glMinFilter == (PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR)) {
      return LINEAR;
    } else if (glMagFilter == PGL.LINEAR &&
               glMinFilter == (PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR)) {
      return BILINEAR;
    } else if (glMagFilter == PGL.LINEAR &&
               glMinFilter == PGL.LINEAR_MIPMAP_LINEAR) {
      return TRILINEAR;
    } else {
      return -1;
    }
  }

  ////////////////////////////////////////////////////////////

  // Bind/unbind


  public void bind() {
    // Binding a texture automatically enables texturing for the
    // texture target from that moment onwards. Unbinding the texture
    // won't disable texturing.
    if (!pgl.texturingIsEnabled(glTarget)) {
      pgl.enableTexturing(glTarget);
    }
    pgl.bindTexture(glTarget, glName);
    bound = true;
  }


  public void unbind() {
    if (pgl.textureIsBound(glTarget, glName)) {
      // We don't want to unbind another texture
      // that might be bound instead of this one.
      if (!pgl.texturingIsEnabled(glTarget)) {
        pgl.enableTexturing(glTarget);
        pgl.bindTexture(glTarget, 0);
        pgl.disableTexturing(glTarget);
      } else {
        pgl.bindTexture(glTarget, 0);
      }
    }
    bound = false;
  }


  public boolean bound() {
    // A true result might not necessarily mean that texturing is enabled
    // (a texture can be bound to the target, but texturing is disabled).
    return bound;
  }


  //////////////////////////////////////////////////////////////

  // Modified flag


  public boolean isModified() {
    return modified;
  }


  public void setModified() {
    modified = true;
  }


  public void setModified(boolean m) {
    modified = m;
  }


  public int getModifiedX1() {
    return mx1;
  }


  public int getModifiedX2() {
    return mx2;
  }


  public int getModifiedY1() {
    return my1;
  }


  public int getModifiedY2() {
    return my2;
  }


  public void updateTexels() {
    updateTexelsImpl(0, 0, width, height);
  }


  public void updateTexels(int x, int y, int w, int h) {
    updateTexelsImpl(x, y, w, h);
  }


  protected void updateTexelsImpl(int x, int y, int w, int h) {
    int x2 = x + w;
    int y2 = y + h;

    if (!modified) {
      mx1 = PApplet.max(0, x);
      mx2 = PApplet.min(width - 1, x2);
      my1 = PApplet.max(0, y);
      my2 = PApplet.min(height - 1, y2);
      modified = true;

    } else {
      if (x < mx1) mx1 = PApplet.max(0, x);
      if (x > mx2) mx2 = PApplet.min(width - 1, x);
      if (y < my1) my1 = PApplet.max(0, y);
      if (y > my2) my2 = y;

      if (x2 < mx1) mx1 = PApplet.max(0, x2);
      if (x2 > mx2) mx2 = PApplet.min(width - 1, x2);
      if (y2 < my1) my1 = PApplet.max(0, y2);
      if (y2 > my2) my2 = PApplet.min(height - 1, y2);
    }
  }


  protected void loadPixels(int len) {
    if (rgbaPixels == null || rgbaPixels.length < len) {
      rgbaPixels = new int[len];
    }
  }


  protected void updatePixelBuffer(int[] pixels) {
    pixelBuffer = PGL.updateIntBuffer(pixelBuffer, pixels, true);
    pixBufUpdateCount++;
  }


  protected void manualMipmap() {
    // TODO: finish manual mipmap generation,
    // https://github.com/processing/processing/issues/3335
  }


  ////////////////////////////////////////////////////////////

  // Buffer sink interface.


  public void setBufferSource(Object source) {
    bufferSource = source;
    getSourceMethods();
  }


  public void copyBufferFromSource(Object natRef, ByteBuffer byteBuf,
                                   int w, int h) {
    if (bufferCache == null) {
      bufferCache = new LinkedList();
    }

    if (bufferCache.size() + 1 

Web Proxy Viewer  |  New URL  |  Original Page