Texture.java :  » Java-3D » ferox-gl » com » ferox » resource » Java Open Source

Java Open Source » Java 3D » ferox gl 
ferox gl » com » ferox » resource » Texture.java
package com.ferox.resource;

import java.util.Arrays;

import com.ferox.renderer.Renderer.Comparison;
import com.ferox.resource.BufferData.DataType;

/**
 * <p>
 * Texture is a complex structure that organizes a set of {@link Mipmap mipmaps}
 * into a usable Resource for applying image data to rendered geometry. A
 * Texture has a Target which describes how the Texture can be accessed. The
 * target currently can be used to create 1D, 2D, 3D and cube map textures.
 * Texture has the concept of a "layer". Multiple layers of image data compose a
 * Texture. Each layer has a single set of image data stored within a
 * {@link Mipmap}. Every Mipmap layer of a Texture has the same dimensions, data
 * type and format. Currently, only the cube map target supports multiple
 * layers. The "layer" is meant to be a forward-compatible feature for use with
 * advanced and unsupported targets such as 2D-array or 3D-array.
 * </p>
 * <p>
 * The BufferData within a Texture's Mipmaps can be modified or changed without
 * reassigning the Mipmaps to a Texture. However, when this occurs, the Texture
 * must be notified via markImageDirty() so it can properly send change events
 * to any {@link ResourceListener listeners}. This is also useful in that many
 * updates can be performed to a Texture's data before issuing one dirty
 * notification.
 * </p>
 * <p>
 * When a Framework updates a Texture, it should not perform updates on any
 * updates on mipmap levels that have null BufferDatas or BufferDatas with null
 * arrays. Depending on the level ranged described by
 * {@link #getBaseMipmapLevel()} and {@link #getMaxMipmapLevel()}, it may be
 * necessary to allocate space for those levels, in which case the garbage
 * should remain unmodified. The primary purpose of this is to allow the
 * in-memory BufferDatas to be garbage collected when possible without
 * destroying their copy at the driver level. It's also used by TextureSurfaces,
 * which have no in-memory texture data at all.
 * </p>
 * <p>
 * When notifying ResourceListeners, Textures send a couple of different objects
 * depending on the change type. If parameters such as {@link #getFilter() the
 * filter} or {@link #getWrapModeR() wrap mode} are changed, the static object
 * PARAMETERS_CHANGED is sent. If the data within mipmap levels is marked as
 * dirty using the markDirty() methods, a {@link MipmapRegion} is sent
 * containing the dirty region. If {@link #setLayers(Mipmap[])} is called, a
 * FULL_UPDATE is sent.
 * </p>
 * 
 * @author Michael Ludwig
 */
public class Texture extends Resource {
    /**
     * Target represents the way in which the Texture's Mipmaps are
     * interpreted.
     */
    public static enum Target {
        /**
         * The image is stored within a single row of pixel data, measured by
         * the width of the image. The height and depth of each mipmap must be
         * 1. The texture is accessed by the first texture coordinate, from 0 to
         * 1.
         */
        T_1D,
        /**
         * The image is stored within a grid of pixel data, measured by the
         * width and height of the image. The depth must be 1. The texture is
         * accessed by the first and second texture coordinates, from (0,0) to
         * (1,1).
         */
        T_2D,
        /**
         * The image is an array of 2D images, where the number of images is
         * equal to the depth of the texture. The texture is accessed by all
         * three texture coordinates, from (0,0,0) to (1,1,1).
         */
        T_3D,
        /**
         * A cube map is composed of six 2D images, one for each side of a cube.
         * The size of each image is measured by the width and height, although
         * the width and height must be equal. The texture is accessed by all
         * three coordinates by projecting it onto a specific face.
         */
        T_CUBEMAP
    }

    /**
     * Describes the wrapping behavior of the texture image when pixels sample
     * beyond the normal region.
     */
    public static enum WrapMode {
        /**
         * Texture coordinates are clamped to be in the range [0, 1].
         */
        CLAMP,
        /**
         * Texture coordinates wrap around from 1 to 0 or 0 to 1, etc.
         */
        REPEAT,
        /**
         * Texture coordinates mirror from 0 to 1 and back to 0, etc.
         */
        MIRROR
    }

    /**
     * Get the filter applied to the texture. NEAREST and LINEAR do not use any
     * mipmap data, even if present. MIPMAP_NEAREST and MIPMAP_LINEAR default to
     * their non-mipmap versions if a texture doesn't have mipmaps. Filter
     * describes both minification and magnification.
     */
    public static enum Filter {
        NEAREST, LINEAR, MIPMAP_NEAREST, MIPMAP_LINEAR
    }
    
    /**
     * Layer indices for Textures with target {@link Target#T_CUBEMAP}.
     */
    public static final int PX = 0;
    public static final int PY = 1;
    public static final int PZ = 2;
    public static final int NX = 3;
    public static final int NY = 4;
    public static final int NZ = 5;

    /**
     * A change flag sent to listeners when any parameter of a Texture is
     * changed that is not the actual mipmap image data of the Texture. Mipmap
     * changes send MipmapRegion instances.
     */
    public static final Object PARAMETERS_CHANGED = new Object();

    private WrapMode wrapS;
    private WrapMode wrapT;
    private WrapMode wrapR;

    private Filter filter;
    private float anisoLevel; // in [0, 1]

    private boolean enableDepthCompare;
    private Comparison depthCompareTest;
    
    private final Target target;
    private Mipmap[] layers;
    private int baseLevel, maxLevel;
    
    /**
     * Create a Texture that uses the specified target and uses the given
     * Mipmap as its source of image data. The created Texture has a single
     * layer, so this constructor should only be used for Targets that are not
     * {@link Target#T_CUBEMAP}, which requires 6 layers. This is equivalent to
     * <code>Texture(target, new Mipmap[] {mipmap})</code>. See
     * {@link #Texture(Target, Mipmap[])} for more details on the
     * validation performed.
     * 
     * @param target The Target which specifies how the Texture is accessed
     * @param mipmap The Mipmap data for use in the constructed Texture's
     *            single layer
     * @throws NullPointerException if target or mipmap are null
     */
    public Texture(Target target, Mipmap mipmap) {
        this(target, new Mipmap[] {mipmap});
    }

    /**
     * <p>
     * Create a Texture that uses the specified target and array of Mipmap
     * layers. The mipmap layers are interpreted differently depending on the
     * chosen Target for the Texture. If target is one of T_1D, T_2D or T_3D,
     * <tt>mipmaps</tt> must have a length of 1 because those targets only
     * expect a single layer. If the target is T_CUBEMAP, <tt>mipmaps</tt> must
     * have a length of 6, one for each face of a cube.
     * </p>
     * <p>
     * The provided mipmap layers cannot have any null elements within it.
     * Additionally, the Buffer type, dimensions and TextureFormat for each
     * Mipmap within <tt>mipmaps</tt> must match each other. Additionally,
     * mipmaps for T_1D must have a height and depth of 1; mipmaps for T_2D must
     * have a depth of 1; mipmaps for T_CUBEMAP must have a depth of 1 and must
     * be square. It is invalid to create a Texture with 0 layers. In the
     * future, new Targets may allow for different interpretations of the mipmap
     * layers.
     * </p>
     * <p>
     * The order of the mipmap layers when <tt>target</tt> is T_CUBEMAP is
     * important. See {@link #getMipmap(int)} for the required ordering.
     * </p>
     * 
     * @param target The Target which specifies how the Texture is accessed
     * @param mipmaps An array of Mipmaps representing the layers of the Texture
     * @throws NullPointerException if target or mipmaps is null, or if mipmaps
     *             contains any null elements
     * @throws IllegalArgumentException if any of the conditions described above
     *             are not met
     */
    public Texture(Target target, Mipmap[] mipmaps) {
        if (target == null)
            throw new NullPointerException("Target cannot be null");
        this.target = target;
        setLayers(mipmaps);
        
        // don't use setters to avoid creation of dirty states
        wrapS = WrapMode.CLAMP;
        wrapT = WrapMode.CLAMP;
        wrapR = WrapMode.CLAMP;
        
        filter = Filter.LINEAR;
        anisoLevel = 1f;
        
        enableDepthCompare = false;
        depthCompareTest = Comparison.GREATER;
    }

    /**
     * Set the texture image data for the entire texture. This can be used to
     * change the dimensions of the texture because the previous mipmaps are
     * discarded when this is invoked. The provided mipmaps are subject to the
     * same validation rules as in {@link #Texture(Target, Mipmap[])}.
     * 
     * @param mipmaps The new set of Mipmaps for each layer of the Texture
     * @throws NullPointerException if target or mipmaps is null, or if mipmaps
     *             contains any null elements
     * @throws IllegalArgumentException if any of the conditions described in
     *             {@link #Texture(Target, Mipmap[])}
     */
    public void setLayers(Mipmap[] mipmaps) {
        if (mipmaps == null)
            throw new NullPointerException("Mipmap array cannot be null");

        // validate number of layers
        if (target == Target.T_CUBEMAP && mipmaps.length != 6)
            throw new IllegalArgumentException("Cube maps require exactly 6 layers, not: " + mipmaps.length);
        else if (target != Target.T_CUBEMAP && mipmaps.length != 1)
            throw new IllegalArgumentException("1D, 2D, and 3D textures require exactly 1 layer, not: " + mipmaps.length);
        
        int baseLevel = Integer.MAX_VALUE;
        int maxLevel = Integer.MIN_VALUE;
        // validate that each layer has correct dimensions, type and format, and not null
        for (int i = 0; i < mipmaps.length; i++) {
            if (mipmaps[i] == null)
                throw new NullPointerException("Mipmap layer cannot be null: " + i);
            
            if (i == 0) {
                // first pass through, assume 1st layer is representative
                if (target != Target.T_3D && mipmaps[i].getDepth(0) != 1)
                    throw new IllegalArgumentException("Textures with target=" + target + " must have a depth of 1");
                if (target == Target.T_1D && mipmaps[i].getHeight(0) != 1)
                    throw new IllegalArgumentException("Textures with target=" + target + " must have a height of 1");
                if (target == Target.T_CUBEMAP && mipmaps[i].getWidth(0) != mipmaps[i].getHeight(0))
                    throw new IllegalArgumentException("Textures with target=" + target + " must have square mipmap layers");
            } else {
                // remaining mipmaps must match properties of 1st mipmap
                if (mipmaps[i].getDepth(0) != mipmaps[0].getDepth(0) || 
                    mipmaps[i].getHeight(0) != mipmaps[0].getHeight(0) || 
                    mipmaps[i].getWidth(0) != mipmaps[0].getWidth(0))
                    throw new IllegalArgumentException("Mipmap layers must all have the same dimensions");
                if (mipmaps[i].getNumMipmaps() != mipmaps[0].getNumMipmaps())
                    throw new IllegalArgumentException("Mipmap layers must all have the same number of mipmaps");
                if (mipmaps[i].getFormat() != mipmaps[0].getFormat())
                    throw new IllegalArgumentException("Mipmap layers must all have the same TextureFormat");
                if (mipmaps[i].getDataType() != mipmaps[0].getDataType())
                    throw new IllegalArgumentException("Mipmap layers must all have the same Buffer data type");
            }
            
            // perform default detection of valid mipmap levels
            for (int j = 0; j < mipmaps[i].getNumMipmaps(); j++) {
                if (mipmaps[i].getData(j) != null) {
                    baseLevel = Math.min(baseLevel, j);
                    maxLevel = Math.max(maxLevel, j);
                }
            }
        }
        
        
        // at this point we know things are as valid as we can determine them,
        layers = Arrays.copyOf(mipmaps, mipmaps.length); // defensive copy
        
        if (baseLevel > maxLevel) {
            // weren't able to find a valid range, default to entire mipmap range
            baseLevel = 0;
            maxLevel = mipmaps[0].getNumMipmaps() - 1;
        }
        
        setValidMipmapLevels(baseLevel, maxLevel);
        notifyChange(Resource.FULL_UPDATE);
    }
    
    /**
     * @return The width of the top-most mipmap level of each layer in the
     *         Texture
     */
    public int getWidth() {
        return layers[0].getWidth(0);
    }
    
    /**
     * @return The height of the top-most mipmap level of each layer in the
     *         Texture
     */
    public int getHeight() {
        return layers[0].getHeight(0);
    }
    
    /**
     * @return The depth of the top-most mipmap level of each layer in the
     *         Texture
     */
    public int getDepth() {
        return layers[0].getDepth(0);
    }

    /**
     * @return The TextureFormat of every Mipmap layer within this Texture
     */
    public TextureFormat getFormat() {
        return layers[0].getFormat();
    }
    
    /**
     * @return The Buffer class type of all the Buffer data within each Mipmap
     *         layer within this Texture
     */
    public DataType getDataType() {
        return layers[0].getDataType();
    }
    
    /**
     * Set the S, T, and R coordinate's WrapMode to <tt>wrap</tt>.
     * 
     * @param wrap The new WrapMode for every coordinate
     * @throws NullPointerException if wrap is null
     */
    public void setWrapMode(WrapMode wrap) {
        setWrapMode(wrap, wrap, wrap);
    }

    /**
     * Set the S, T and R coordinate's WrapMode to <tt>s</tt>, <tt>t</tt>,
     * <tt>r</tt>, respectively.
     * 
     * @param s The new WrapMode for the S coordinate
     * @param t The new WrapMode for the T coordinate
     * @param r The new WrapMode for the R coordinate
     * @throws NullPointerException if s, t, or r are null
     */
    public void setWrapMode(WrapMode s, WrapMode t, WrapMode r) {
        if (s == null || t == null || r == null)
            throw new NullPointerException("WrapModes cannot be null: " + s + ", " + t + ", " + r);
        wrapS = s;
        wrapT = t;
        wrapR = r;
        markParametersDirty();
    }
    
    /**
     * Get the type of texture coordinate wrapping when coordinates go beyond
     * the edge of the image, along the S texture coordinate.
     * 
     * @return The WrapMode for the S coordinate
     */
    public WrapMode getWrapModeS() {
        return wrapS;
    }
    
    /**
     * Get the type of texture coordinate wrapping when coordinates go beyond
     * the edge of the image, along the T texture coordinate.
     * 
     * @return The WrapMode for the T coordinate
     */
    public WrapMode getWrapModeT() {
        return wrapT;
    }
    
    /**
     * Get the type of texture coordinate wrapping when coordinates go beyond
     * the edge of the image, along the R texture coordinate.
     * 
     * @return The WrapMode for the R coordinate
     */
    public WrapMode getWrapModeR() {
        return wrapR;
    }

    /**
     * Set the Filter to use when minimizing or magnifying a texel when
     * rendering it to a Surface. The MIPMAP_x values are only valid if this
     * Texture has more than 1 level in its mipmap layers. If its layers
     * are not mipmapped, then MIPMAP_LINEAR becomes LINEAR and MIPMAP_NEAREST
     * becomes NEAREST.
     * 
     * @param filter The new Filter
     * @throws NullPointerException if filter is null
     */
    public void setFilter(Filter filter) {
        if (filter == null)
            throw new NullPointerException("Filter cannot be null");
        if (!layers[0].isMipmapped()) {
            if (filter == Filter.MIPMAP_LINEAR)
                filter = Filter.LINEAR;
            else if (filter == Filter.MIPMAP_NEAREST)
                filter = Filter.NEAREST;
        }
        
        this.filter = filter;
        markParametersDirty();
    }
    
    /**
     * @return The Filter to apply to the texels when rendering the texture
     */
    public Filter getFilter() {
        return filter;
    }

    /**
     * Set the amount of anisotropic filtering to apply when rasterizing the
     * texture. This is measured as a number from 0 to 1, where 0 represents no
     * anisotropic filtering and 1 represents the maximum filtering allowed for
     * the running hardware.
     * 
     * @param level The amount of anisotropic filtering to use
     * @throws IllegalArgumentException if level is outside of [0, 1]
     */
    public void setAnisotropicFilterLevel(float level) {
        if (level < 0f || level > 1f)
            throw new IllegalArgumentException("Invalid level, must be in [0, 1], not: " + level);
        anisoLevel = level;
        markParametersDirty();
    }
    
    /**
     * @return The amount of anisotropic filtering from 0 to 1
     */
    public float getAnisotropicFilterLevel() {
        return anisoLevel;
    }

    /**
     * <p>
     * Set the range of mipmap levels to use when rasterizing a mipmapped
     * Texture. Frameworks do not need to allocate driver-level texture
     * data for mipmaps that are not needed. This can be used to do progressive
     * texture loading or to free up resources when textures are applied to far
     * away objects.
     * </p>
     * <p>
     * Initially, the base level is set to the lowest level that has non-null
     * data and the max level is set to the highest level with non-null data. If
     * there is no data in any layer, then it uses
     * <code>(0, # mipmaps - 1)</code> for the range.
     * </p>
     * 
     * @param base The lowest valid mipmap level, across all layers
     * @param max The highest valid mipmap level, across all layers
     * @throws IllegalArgumentException if base < 0, if max >= # mipmaps, or if
     *             base > max
     */
    public void setValidMipmapLevels(int base, int max) {
        if (base < 0)
            throw new IllegalArgumentException("Base level must be at least 0, not: " + base);
        if (max >= layers[0].getNumMipmaps())
            throw new IllegalArgumentException("Max level must be at most the number of levels in the image (" + layers[0].getNumMipmaps() + "), not: " + max);
        if (base > max)
            throw new IllegalArgumentException("Base level must be less than or equal to max, not: " + base + " > " + max);
        
        baseLevel = base;
        maxLevel = max;
        markParametersDirty();
    }
    
    /**
     * @return The lowest valid mipmap level to use during rasterization
     */
    public int getBaseMipmapLevel() {
        return baseLevel;
    }
    
    /**
     * @return The highest valid mipmap level to use during rasterization
     */
    public int getMaxMipmapLevel() {
        return maxLevel;
    }

    /**
     * Return the chosen Target for this Texture. The Target imposes a
     * number of restrictions on the dimensions of the Texture. For
     * example:
     * <ol>
     * <li>T_1D requires a height and depth of 1 for the root mipmap, and one layer.</li>
     * <li>T_2D requires a depth of 1 for the root mipmap, and one layer.</li>
     * <li>T_3D requires there to be only one layer.</li>
     * <li>T_CUBEMAP requires a depth of 1, its width and height to be equal,
     * and to have 6 layers all with matching dimensions.</li>
     * </ol>
     * 
     * @return The Target of this Texture
     */
    public Target getTarget() {
        return target;
    }

    /**
     * <p>
     * Return the {@link Mipmap} containing the hierarchical image data for a
     * given layer of the Texture. In many cases there will only be 1 layer
     * (for targets T_1D, T_2D and T_3D). For cube maps there are 6 layers with
     * layers arranged as PX, PY, PZ, NX, NY, NZ (one layer for each cube face).
     * </p>
     * <p>
     * Each Mipmap layer within a Texture will have the same dimensions,
     * the same number of mipmap levels, the same Buffer data type, and same
     * TextureFormat. There is always at least one layer in a Texture.
     * </p>
     * 
     * @param layer The layer to fetch (not to be confused with mipmap level),
     *            starting at 0
     * @return The Mipmap data for the specific layer
     * @throws IndexOutOfBoundsException if layer < 0 or layer >=
     *             {@link #getNumLayers()}
     */
    public Mipmap getMipmap(int layer) {
        return layers[layer];
    }
    
    /**
     * @return The number of layers present in this Texture
     */
    public int getNumLayers() {
        return layers.length;
    }

    /**
     * @see #setDepthCompareEnabled(boolean)
     * @return True if depth comparison is enabled when the TextureFormat of the
     *         Texture is {@link TextureFormat#DEPTH}
     */
    public boolean isDepthCompareEnabled() {
        return enableDepthCompare;
    }

    /**
     * <p>
     * Set whether or not depth comparisons should be used when rendering with
     * this Texture. This parameter is ignored if the image's TextureFormat is
     * not DEPTH. When a texture is a depth texture, the depth values can be
     * interpreted in multiple ways. When depth comparison is disabled, each
     * depth value is treated as a grayscale color that's rendered like any
     * other texture value.
     * </p>
     * <p>
     * When comparisons are enabled, the depth value is compared to the R
     * coordinate of the texture coordinate used to access the image. Based on
     * this, it takes the value 0 or 1 based on the result of the comparison
     * function, which is configured via {@link #setDepthComparison(Comparison)}
     * .
     * </p>
     * 
     * @param enable True if depth comparisons are enabled
     */
    public void setDepthCompareEnabled(boolean enable) {
        enableDepthCompare = enable;
        markParametersDirty();
    }
    
    /**
     * @return The Comparison function used when depth comparisons are enabled
     *         for depth textures
     */
    public Comparison getDepthComparison() {
        return depthCompareTest;
    }

    /**
     * Set the Comparison function to use when depth comparisons are enabled.
     * See {@link #setDepthCompareEnabled(boolean)} for more information on
     * depth comparisons with depth textures. This value is ignored for
     * non-depth textures.
     * 
     * @param compare The new Comparison function
     * @throws NullPointerException if compare is null
     */
    public void setDepthComparison(Comparison compare) {
        if (compare == null)
            throw new NullPointerException("Comparison cannot be null");
        depthCompareTest = compare;
        markParametersDirty();
    }

    /**
     * Mark the specified image region as dirty within the image data pointed to
     * by layer and level. The specified <tt>region</tt> will automatically be
     * constrained to the maximum dimensions of the given level.
     * 
     * @param region The MipmapRegion representing the dirty pixels in layer and
     *            level
     * @param layer The layer whose mipmap is being marked dirty
     * @param level The level which is being marked dirty
     * @throws NullPointerException if region is null
     * @throws IndexOutOfBoundsException if layer < 0 or layer >=
     *             {@link #getNumLayers()}, or if level < 0 or level >= #
     *             mipmaps
     */
    public void markDirty(MipmapRegion region, int layer, int level) {
        Mipmap m = getMipmap(layer);
        int levelWidth = m.getWidth(level);
        int levelHeight = m.getHeight(level);
        int levelDepth = m.getDepth(level);
        
        MipmapRegion constrain = new MipmapRegion(region, levelWidth, levelHeight, levelDepth, layer, level);
        notifyChange(constrain);
    }

    /**
     * Mark the specified mipmap <tt>level</tt> dirty within the given
     * <tt>layer</tt>. This is a convenience for invoking
     * {@link #markDirty(MipmapRegion, int, int)} with an MipmapRegion that
     * spans from (0,0,0) to the dimensions of the requested level.
     * 
     * @param layer The layer whose mipmap will be marked dirty
     * @param level The level within layer that will be marked dirty
     * @throws IndexOutOfBoundsException if layer < 0 or layer >=
     *             {@link #getNumLayers()}, or if level < 0 or level >= #
     *             mipmaps
     */
    public void markDirty(int layer, int level) {
        Mipmap m = getMipmap(layer);
        markDirty(new MipmapRegion(0, 0, 0, m.getWidth(level), m.getHeight(level), m.getDepth(level)), 
                  layer, level);
    }

    /**
     * Mark every mipmap level dirty for the given <tt>layer</tt> of the
     * Texture. This can be used to mark a single face of a cube map dirty
     * for example. This is a convenience for invoking
     * {@link #markDirty(int, int)} for every mipmap level within the given
     * <tt>layer</tt>
     * 
     * @param layer The layer to mark completely dirty
     * @throws IndexOutOfBoundsException if layer < 0 or layer >=
     *             {@link #getNumLayers()}
     */
    public void markDirty(int layer) {
        for (int i = 0; i < layers[layer].getNumMipmaps(); i++)
            markDirty(layer, i);
    }

    /**
     * Mark the entirety of the Texture's image data dirty. This is a
     * convenience for invoking {@link #markDirty(int)} for each layer of
     * the Texture.
     */
    public void markDirty() {
        for (int i = 0; i < layers.length; i++)
            markDirty(i);
    }
    
    private void markParametersDirty() {
        notifyChange(PARAMETERS_CHANGED);
    }
}
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.