/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.worldwind.common.layers.curtain; import gov.nasa.worldwind.cache.GpuResourceCache; import gov.nasa.worldwind.cache.MemoryCache; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.TileKey; import javax.media.opengl.GL2; import javax.media.opengl.GLContext; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureData; import com.jogamp.opengl.util.texture.TextureIO; /** * Extension of the {@link CurtainTile} class which contains texture data for * the tile. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class CurtainTextureTile extends CurtainTile { private volatile TextureData textureData; // if non-null, then must be converted to a Texture private CurtainTextureTile fallbackTile = null; // holds texture to use if own texture not available private boolean hasMipmapData = false; private long updateTime = 0; public static synchronized MemoryCache getMemoryCache() { //share TextureTile memory cache for now return gov.nasa.worldwind.layers.TextureTile.getMemoryCache(); } public CurtainTextureTile(Segment segment, CurtainLevel level, int row, int column) { super(segment, level, row, column); } @Override public final long getSizeInBytes() { long size = super.getSizeInBytes(); if (this.textureData != null) { size += this.textureData.getEstimatedMemorySize(); } return size; } public CurtainTextureTile getFallbackTile() { return this.fallbackTile; } public void setFallbackTile(CurtainTextureTile fallbackTile) { this.fallbackTile = fallbackTile; } /** * Returns the texture data most recently specified for the tile. New * texture data is typically specified when a new image is read, either * initially or in response to image expiration. * <p/> * If texture data is non-null, a new texture is created from the texture * data when the tile is next bound or otherwise initialized. The texture * data field is then set to null. Subsequently setting texture data to be * non-null causes a new texture to be created when the tile is next bound * or initialized. * * @return the texture data, which may be null. */ public TextureData getTextureData() { return this.textureData; } /** * Specifies new texture data for the tile. New texture data is typically * specified when a new image is read, either initially or in response to * image expiration. * <p/> * If texture data is non-null, a new texture is created from the texture * data when the tile is next bound or otherwise initialized. The texture * data field is then set to null. Subsequently setting texture data to be * non-null causes a new texture to be created when the tile is next bound * or initialized. * <p/> * When a texture is created from the texture data, the texture data field * is set to null to indicate that the data has been converted to a texture * and its resources may be released. * * @param textureData * the texture data, which may be null. */ public void setTextureData(TextureData textureData) { this.textureData = textureData; if (textureData.getMipmapData() != null) { this.hasMipmapData = true; } } public Texture getTexture(GpuResourceCache tc) { if (tc == null) { String message = Logging.getMessage("nullValue.TextureCacheIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } return tc.getTexture(this.getTileKey()); } public boolean isTextureInMemory(GpuResourceCache tc) { if (tc == null) { String message = Logging.getMessage("nullValue.TextureCacheIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } return this.getTexture(tc) != null || this.getTextureData() != null; } public boolean isTextureExpired() { return this.isTextureExpired(this.getLevel().getExpiryTime()); } public boolean isTextureExpired(long expiryTime) { return this.updateTime > 0 && this.updateTime < expiryTime; } public void setTexture(GpuResourceCache tc, Texture texture) { if (tc == null) { String message = Logging.getMessage("nullValue.TextureCacheIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } tc.put(this.getTileKey(), texture); this.updateTime = System.currentTimeMillis(); // No more need for texture data; allow garbage collector and memory cache to reclaim it. // This also signals that new texture data has been converted. this.textureData = null; this.updateMemoryCache(); } // public Vec4 getCentroidPoint(Globe globe) // { // if (globe == null) // { // String msg = Logging.getMessage("nullValue.GlobeIsNull"); // Logging.logger().severe(msg); // throw new IllegalArgumentException(msg); // } // // return globe.computePointFromLocation(this.getSector().getCentroid()); // } // // public Extent getExtent(DrawContext dc) // { // if (dc == null) // { // String msg = Logging.getMessage("nullValue.DrawContextIsNull"); // Logging.logger().severe(msg); // throw new IllegalArgumentException(msg); // } // // return Sector.computeBoundingBox(dc.getGlobe(), dc.getVerticalExaggeration(), // this.getSector()); // } public CurtainTextureTile[] createSubTiles(CurtainLevel nextLevel) { int[] columns, rows; int rowMultiplier, columnMultiplier; if (this.getLevel().getRowCount() == nextLevel.getRowCount()) { columns = new int[] { 0, 1, 2, 3 }; rows = new int[] { 0, 0, 0, 0 }; columnMultiplier = 4; rowMultiplier = 1; } else if (this.getLevel().getColumnCount() == nextLevel.getColumnCount()) { columns = new int[] { 0, 0, 0, 0 }; rows = new int[] { 0, 1, 2, 3 }; columnMultiplier = 1; rowMultiplier = 4; } else { columns = new int[] { 0, 1, 0, 1 }; rows = new int[] { 0, 0, 1, 1 }; columnMultiplier = 2; rowMultiplier = 2; } String nextLevelCacheName = nextLevel.getCacheName(); int nextLevelNum = nextLevel.getLevelNumber(); CurtainTextureTile[] subTiles = new CurtainTextureTile[4]; for (int i = 0; i < 4; i++) { int row = this.getRow() * rowMultiplier + rows[i]; int column = this.getColumn() * columnMultiplier + columns[i]; CurtainTileKey key = new CurtainTileKey(nextLevelNum, row, column, nextLevelCacheName); CurtainTextureTile subTile = this.getTileFromMemoryCache(key); if (subTile == null) { Segment segment = nextLevel.computeSegmentForKey(key); subTile = createTextureTile(segment, nextLevel, key.getRow(), key.getColumn()); } subTiles[i] = subTile; } return subTiles; } protected CurtainTextureTile createTextureTile(Segment segment, CurtainLevel level, int row, int column) { return new CurtainTextureTile(segment, level, row, column); } protected CurtainTextureTile getTileFromMemoryCache(TileKey tileKey) { return (CurtainTextureTile) getMemoryCache().getObject(tileKey); } protected void updateMemoryCache() { if (this.getTileFromMemoryCache(this.getTileKey()) != null) { getMemoryCache().add(this.getTileKey(), this); } } protected Texture initializeTexture(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } Texture t = this.getTexture(dc.getTextureCache()); // Return texture if found and there is no new texture data if (t != null && this.getTextureData() == null) { return t; } if (this.getTextureData() == null) // texture not in cache yet texture data is null, can't initialize { String msg = Logging.getMessage("nullValue.TextureDataIsNull"); Logging.logger().severe(msg); throw new IllegalStateException(msg); } try { t = TextureIO.newTexture(this.getTextureData()); } catch (Exception e) { String msg = Logging.getMessage("layers.TextureLayer.ExceptionAttemptingToReadTextureFile", ""); Logging.logger().log(java.util.logging.Level.SEVERE, msg, e); return null; } this.setTexture(dc.getTextureCache(), t); t.bind(dc.getGL()); this.setTextureParameters(dc, t); return t; } protected void setTextureParameters(DrawContext dc, Texture t) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } GL2 gl = dc.getGL().getGL2(); // Use a mipmap minification filter when either of the following is true: // a. The texture has mipmap data. This is typically true for formats with embedded mipmaps, such as DDS. // b. The texture is setup to have GL automatically generate mipmaps. This is typically true when a texture is // loaded from a standard image type, such as PNG or JPEG, and the caller instructed JOGL to generate // mipmaps. boolean useMipmapFilter = (this.hasMipmapData || t.isUsingAutoMipmapGeneration()); // Set the texture minification filter. If the texture qualifies for mipmaps, apply a minification filter that // will access the mipmap data using the highest quality algorithm. If the anisotropic texture filter is // available, we will enable it. This will sharpen the appearance of the mipmap filter when the textured // surface is at a high slope to the eye. if (useMipmapFilter) { gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR_MIPMAP_LINEAR); // If the maximum degree of anisotropy is 2.0 or greater, then we know this graphics context supports // the anisotropic texture filter. double maxAnisotropy = dc.getGLRuntimeCapabilities().getMaxTextureAnisotropy(); if (dc.getGLRuntimeCapabilities().isUseAnisotropicTextureFilter() && maxAnisotropy >= 2.0) { gl.glTexParameterf(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAX_ANISOTROPY_EXT, (float) maxAnisotropy); } } // If the texture does not qualify for mipmaps, then apply a linear minification filter. else { gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR); } // Set the texture magnification filter to a linear filter. This will blur the texture as the eye gets very // near, but this is still a better choice than nearest neighbor filtering. gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR); // Set the S and T wrapping modes to clamp to the texture edge. This way no border pixels will be sampled by // either the minification or magnification filters. gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP_TO_EDGE); } public boolean bind(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } // Reinitialize texture if new texture data if (this.getTextureData() != null) { Texture t = this.initializeTexture(dc); if (t != null) { return true; // texture was bound during initialization. } } Texture t = this.getTexture(dc.getTextureCache()); if (t == null && this.getFallbackTile() != null) { CurtainTextureTile resourceTile = this.getFallbackTile(); t = resourceTile.getTexture(dc.getTextureCache()); if (t == null) { t = resourceTile.initializeTexture(dc); if (t != null) { return true; // texture was bound during initialization. } } } if (t != null) { t.bind(dc.getGL()); } return t != null; } public void applyInternalTransform(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } Texture t; if (this.getTextureData() != null) { t = this.initializeTexture(dc); } else { t = this.getTexture(dc.getTextureCache()); // Use the tile's texture if available } if (t != null) { if (t.getMustFlipVertically()) { GL2 gl = GLContext.getCurrent().getGL().getGL2(); gl.glMatrixMode(GL2.GL_TEXTURE); gl.glLoadIdentity(); gl.glScaled(1, -1, 1); gl.glTranslated(0, -1, 0); } return; } // Use the tile's fallback texture if its primary texture is not available. CurtainTextureTile resourceTile = this.getFallbackTile(); if (resourceTile == null) { return; } t = resourceTile.getTexture(dc.getTextureCache()); if (t == null && resourceTile.getTextureData() != null) { t = resourceTile.initializeTexture(dc); } if (t == null) { return; } // Apply necessary transforms to the fallback texture. GL2 gl = GLContext.getCurrent().getGL().getGL2(); gl.glMatrixMode(GL2.GL_TEXTURE); gl.glLoadIdentity(); if (t.getMustFlipVertically()) { gl.glScaled(1, -1, 1); gl.glTranslated(0, -1, 0); } this.applyResourceTextureTransform(dc); } private void applyResourceTextureTransform(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalStateException(message); } if (this.getLevel() == null) { return; } int levelDelta = this.getLevelNumber() - this.getFallbackTile().getLevelNumber(); if (levelDelta <= 0) { return; } Segment segment = getSegment(); Segment fallbackSegment = this.getFallbackTile().getSegment(); double fhd = fallbackSegment.getHorizontalDelta(); double fvd = fallbackSegment.getVerticalDelta(); double shd = segment.getHorizontalDelta(); double svd = segment.getVerticalDelta(); double xScale = shd / fhd; double yScale = svd / fvd; double xShift = (segment.getStart() - fallbackSegment.getStart()) / fhd; double yShift = (segment.getBottom() - fallbackSegment.getBottom()) / fvd; dc.getGL().getGL2().glTranslated(xShift, yShift, 0); dc.getGL().getGL2().glScaled(xScale, yScale, 1); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final CurtainTextureTile tile = (CurtainTextureTile) o; return !(this.getTileKey() != null ? !this.getTileKey().equals(tile.getTileKey()) : tile.getTileKey() != null); } @Override public int hashCode() { return (this.getTileKey() != null ? this.getTileKey().hashCode() : 0); } @Override public String toString() { return super.toString() + " " + this.getSegment().toString(); } }