TextureFactory.java :  » Game » rugl » com » rugl » texture » Java Open Source

Java Open Source » Game » rugl 
rugl » com » rugl » texture » TextureFactory.java
/*
 * Copyright (c) 2007, Ryan McNally All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following
 * disclaimer. Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of the <ORGANIZATION> nor
 * the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

package com.rugl.texture;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.OpenGLException;
import org.lwjgl.util.Dimension;
import org.lwjgl.util.ReadableDimension;
import org.lwjgl.util.WritableDimension;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Vector2f;

import com.rugl.DisplayConfigurable;
import com.rugl.GameBox;
import com.rugl.texture.Image.Format;
import com.rugl.util.GLUtil;
import com.rugl.util.RectanglePacker;

/**
 * Builds and manages {@link Texture}s. Textures build through this
 * class will be automagically restored in the event of the loss of
 * the opengl context
 * 
 * @author ryanm
 */
public class TextureFactory
{
  /**
   * A list of OpenGL textures
   */
  private static List<GLTexture> textures = new LinkedList<GLTexture>();

  /**
   * The default dimension of the OpenGL textures
   */
  private static int textureDimension = 1024;

  static
  {
    GameBox.dispConf.addListener( new DisplayConfigurable.Listener() {
      @Override
      public void displayChanged( boolean res, boolean fs, boolean vsync, boolean fr,
          boolean fsaa )
      {
        if( fsaa )
        {
          recreateTextures();
        }
      }
    } );
  }

  /**
   * Called in response to the opengl context going away, perhaps as
   * a result of the display mode changing
   */
  public static void recreateTextures()
  {
    IntBuffer texNames = BufferUtils.createIntBuffer( textures.size() );
    for( GLTexture glt : textures )
    {
      texNames.put( glt.id );
    }
    texNames.flip();

    GL11.glDeleteTextures( texNames );

    for( GLTexture glt : textures )
    {
      glt.recreate();
    }
  }

  /**
   * Gets the current list of textures
   * 
   * @return an unmodifiable list of {@link GLTexture} objects
   */
  public static List<GLTexture> getTextures()
  {
    return Collections.unmodifiableList( textures );
  }

  /**
   * Loads the specified image into OpenGL. Does not specify which
   * texture the image will reside on, and will create new OpenGL
   * textures as needed
   * 
   * @param image
   *           the texture image
   * @param lonesome
   *           <code>true</code> if this texture is to be allocated
   *           in a gl texture of minimal size, <code>false</code>
   *           for the default size
   * @param mipmap
   *           <code>true</code> to generate mipmaps, false otherwise
   * @return A {@link Texture} object, or null if it was not able to
   *         be constructed
   */
  public static Texture buildTexture( Image image, boolean lonesome, boolean mipmap )
  {
    if( lonesome )
    {
      try
      {
        GLTexture parent =
            new GLTexture( image.format, image, mipmap, lonesome ? 0 : 1 );
        textures.add( parent );
        Texture texture = parent.addImage( image );

        assert texture.getWidth() == image.getWidth();
        assert texture.getHeight() == image.getHeight();

        return texture;
      }
      catch( OpenGLException e )
      {
        e.printStackTrace();
        return null;
      }
    }
    else
    {
      for( GLTexture tex : textures )
      {
        // match formats
        if( tex.mipmap == mipmap && tex.format == image.format )
        {
          // try to insert
          Texture t = tex.addImage( image );

          if( t != null )
          { // it worked!
            return t;
          }
          // else there was no room
        }
      }

      // build a new texture
      try
      {
        GLTexture parent = new GLTexture( image.format, null, mipmap, 1 );
        textures.add( parent );
        return parent.addImage( image );
      }
      catch( OpenGLException e )
      { // we were not able to build the texture
        e.printStackTrace();
        return null;
      }
    }
  }

  /**
   * Requests that a new texture be built
   * 
   * @param format
   *           The desired format
   * @param minDimensions
   *           The minimum dimensions
   * @param mipmap
   *           <code>true</code> to build mipmaps, <code>false</code>
   *           otherwise
   * @param border
   *           The border, in pixels, that will be placed around
   *           images added to this texture
   * @return A dummy {@link Texture} object
   */
  public static GLTexture createTexture( Format format, ReadableDimension minDimensions,
      boolean mipmap, int border )
  {
    try
    {
      GLTexture parent = new GLTexture( format, minDimensions, mipmap, border );
      textures.add( parent );

      return parent;
    }
    catch( OpenGLException e )
    { // we were not able to build the texture
      e.printStackTrace();
      return null;
    }
  }

  /**
   * Frees up the space allocated to the supplied texture
   * 
   * @param t
   *           The texture to delete
   * @return <code>true</code> if the texture was released,
   *         <code>false</code> if it could not be found
   */
  public static boolean deleteTexture( Texture t )
  {
    for( GLTexture tex : textures )
    {
      if( tex.release( t ) )
      {
        return true;
      }
    }

    return false;
  }

  /**
   * Regenerates any dirty mipmaps. e.g.: If there's a texture that
   * has has data added after creation, that data will not have made
   * it into the lower levels. This method makes sure that it does
   */
  public static void regenerateMipMaps()
  {
    for( GLTexture t : textures )
    {
      t.regenerateMipmaps();
    }
  }

  /**
   * Clears the state of all textures. Called in response to the
   * OpenGL context being recreated. Does not release OpenGL
   * resources
   */
  public static void clear()
  {
    textures.clear();
  }

  /**
   * Encapsulates an OpenGL texture object
   * 
   * @author ryanm
   */
  public static class GLTexture implements ReadableDimension
  {
    /**
     * The texture id
     */
    private int id;

    /**
     * The texture format
     */
    public final Format format;

    /**
     * The texture size
     */
    public final ReadableDimension size;

    /**
     * If mipmaps are build for the texture
     */
    public final boolean mipmap;

    private boolean mipmapDirty = false;

    private List<Texture> residentTextures = new LinkedList<Texture>();

    private RectanglePacker<Image> packer;

    private Texture pan = null;

    /**
     * Builds a new GLTexture
     * 
     * @param format
     *           The texture format
     * @param dim
     *           The texture size, or null for the default
     * @param mipmap
     *           Whether to mipmap or not
     */
    private GLTexture( Format format, ReadableDimension dim, boolean mipmap, int border )
        throws OpenGLException
    {
      this.format = format;
      size =
          dim != null ? new Dimension( GLUtil.nextPowerOf2( dim.getWidth() ),
              GLUtil.nextPowerOf2( dim.getHeight() ) ) : new Dimension(
              textureDimension, textureDimension );
      this.mipmap = mipmap;

      packer = new RectanglePacker<Image>( size.getWidth(), size.getHeight(), border );

      recreate();
    }

    private void recreate()
    {
      id = GL11.glGenTextures();

      GL11.glBindTexture( GL11.GL_TEXTURE_2D, id );

      GL11.glPixelStorei( GL11.GL_UNPACK_ALIGNMENT, format.bytes );

      ByteBuffer data =
          BufferUtils.createByteBuffer( size.getWidth() * size.getHeight()
              * format.bytes );

      if( mipmap )
      {
        GLU.gluBuild2DMipmaps( GL11.GL_TEXTURE_2D, format.glInternalFormat,
            size.getWidth(), size.getHeight(), format.glFormat,
            GL11.GL_UNSIGNED_BYTE, data );
      }
      else
      {
        GL11.glTexImage2D( GL11.GL_TEXTURE_2D, 0, format.glInternalFormat,
            size.getWidth(), size.getHeight(), 0, format.glFormat,
            GL11.GL_UNSIGNED_BYTE, data );
      }
      GLUtil.checkGLError();

      for( Texture t : residentTextures )
      {
        RectanglePacker.Rectangle rpr = packer.findRectangle( t.getSourceImage() );

        if( rpr != null )
        {
          writeToTexture( rpr, t.getSourceImage().getData() );
        }
      }

      regenerateMipmaps();
    }

    /**
     * Gets the texture ID
     * 
     * @return the opengl texture name
     */
    public int id()
    {
      return id;
    }

    /**
     * Gets the resident {@link Texture} objects
     * 
     * @return an unmodifiable list of the resident {@link Texture}
     *         objects
     */
    public List<Texture> getResidents()
    {
      return Collections.unmodifiableList( residentTextures );
    }

    /**
     * Gets the {@link Texture} that displays the whole of this
     * {@link GLTexture}
     * 
     * @return A {@link Texture} that encompasses the entirety of
     *         this {@link GLTexture}
     */
    public Texture getTexture()
    {
      if( pan == null )
      {
        pan = new Texture( this );
      }

      return pan;
    }

    /**
     * Regenerates any dirty mimaps.
     */
    public void regenerateMipmaps()
    {
      if( mipmapDirty )
      {
        ByteBuffer data1 =
            BufferUtils.createByteBuffer( size.getWidth() * size.getHeight()
                * format.bytes );
        ByteBuffer data = data1;

        GL11.glBindTexture( GL11.GL_TEXTURE_2D, id );

        // read the texture out
        GL11.glGetTexImage( GL11.GL_TEXTURE_2D, 0, format.glFormat,
            GL11.GL_UNSIGNED_BYTE, data );

        data.rewind();

        // regenerate the mipmaps
        GLU.gluBuild2DMipmaps( GL11.GL_TEXTURE_2D, format.glInternalFormat,
            size.getWidth(), size.getHeight(), format.glFormat,
            GL11.GL_UNSIGNED_BYTE, data );

        mipmapDirty = false;
      }
    }

    /**
     * Attempts to add an image to this {@link GLTexture}
     * 
     * @param image
     *           The {@link Image} to add
     * @return The resulting {@link Texture}, or <code>null</code>
     *         if it didn't fit
     */
    public Texture addImage( Image image )
    {
      assert image.format == format;

      RectanglePacker.Rectangle rpr =
          packer.insert( image.getWidth(), image.getHeight(), image );

      if( rpr != null )
      {
        assert rpr.width == image.getWidth();
        assert rpr.height == image.getHeight();

        writeToTexture( rpr, image.getData() );

        Vector2f bottomleft = new Vector2f( rpr.x, rpr.y );
        Vector2f topRight = new Vector2f( rpr.x + rpr.width, rpr.y + rpr.height );

        bottomleft.x /= size.getWidth();
        bottomleft.y /= size.getHeight();
        topRight.x /= size.getWidth();
        topRight.y /= size.getHeight();

        Texture t = new Texture( this, bottomleft, topRight, image );
        residentTextures.add( t );

        return t;
      }
      else
      {
        return null;
      }
    }

    /**
     * Releases the space reserved for the supplied texture. Note
     * that the data will still be resident on this
     * {@link GLTexture} until it is overwritten or the mipmap is
     * regenerated
     * 
     * @param t
     *           The {@link Texture} to release
     * @return <code>true</code> if the texture was released,
     *         <code>false</code> if was not resident on this
     *         {@link GLTexture}
     */
    private boolean release( Texture t )
    {
      if( residentTextures.remove( t ) )
      {
        packer.remove( t.getSourceImage() );
        return true;
      }
      else
      {
        return false;
      }
    }

    private void writeToTexture( RectanglePacker.Rectangle r, ByteBuffer data )
    {
      assert r.width * r.height * format.bytes == data.capacity() : r + " * "
          + format.bytes + " != " + data.capacity();

      GL11.glBindTexture( GL11.GL_TEXTURE_2D, id );

      GL11.glPixelStorei( GL11.GL_UNPACK_ALIGNMENT, format.bytes );

      GL11.glTexSubImage2D( GL11.GL_TEXTURE_2D, 0, r.x, r.y, r.width, r.height,
          format.glFormat, GL11.GL_UNSIGNED_BYTE, data );

      GLUtil.checkGLError();

      if( mipmap )
      {
        mipmapDirty = true;
      }
    }

    @Override
    public String toString()
    {
      StringBuilder buff = new StringBuilder( "GLTexture id = " );
      buff.append( id );
      buff.append( " format = " );
      buff.append( format );
      buff.append( " mimap = " );
      buff.append( mipmap );
      buff.append( " size = [" );
      buff.append( size.getWidth() );
      buff.append( "," );
      buff.append( size.getHeight() );
      buff.append( "]" );
      buff.append( " residents: " );
      buff.append( residentTextures.size() );

      if( residentTextures.size() < 10 )
      {
        for( Texture t : residentTextures )
        {
          buff.append( "\n\t" );
          buff.append( t.toString() );
        }
      }

      return buff.toString();
    }

    @Override
    public int getHeight()
    {
      return size.getHeight();
    }

    @Override
    public void getSize( WritableDimension dest )
    {
      size.getSize( dest );
    }

    @Override
    public int getWidth()
    {
      return size.getWidth();
    }
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.