/* * Copyright 1998-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. * */ package javax.media.j3d; import java.awt.image.DataBufferByte; import java.awt.image.RenderedImage; import java.util.ArrayList; import java.util.HashMap; import javax.vecmath.Color4f; import javax.vecmath.Point2f; import javax.vecmath.Point3f; import javax.vecmath.Tuple3f; /** * The Texture object is a component object of an Appearance object * that defines the texture properties used when texture mapping is * enabled. Texture object is an abstract class and all texture * objects must be created as either a Texture2D object or a * Texture3D object. */ abstract class TextureRetained extends NodeComponentRetained { // A list of pre-defined bits to indicate which component // in this Texture object changed. static final int ENABLE_CHANGED = 0x001; static final int COLOR_CHANGED = 0x002; static final int IMAGE_CHANGED = 0x004; static final int STATE_CHANGED = 0x008; static final int UPDATE_IMAGE = 0x010; static final int IMAGES_CHANGED = 0x020; static final int BASE_LEVEL_CHANGED = 0x040; static final int MAX_LEVEL_CHANGED = 0x080; static final int MIN_LOD_CHANGED = 0x100; static final int MAX_LOD_CHANGED = 0x200; static final int LOD_OFFSET_CHANGED = 0x400; // constants for min and mag filter static final int MIN_FILTER = 0; static final int MAG_FILTER = 1; // Boundary width int boundaryWidth = 0; // Boundary modes (wrap, clamp, clamp_to_edge, clamp_to_boundary) int boundaryModeS = Texture.WRAP; int boundaryModeT = Texture.WRAP; // Filter modes int minFilter = Texture.BASE_LEVEL_POINT; int magFilter = Texture.BASE_LEVEL_POINT; // Integer flag that contains bitset to indicate // which field changed. int isDirty = 0xffff; // Texture boundary color Color4f boundaryColor = new Color4f(0.0f, 0.0f, 0.0f, 0.0f); // Texture Object Id used by native code. int objectId = -1; int mipmapMode = Texture.BASE_LEVEL; // Type of mip-mapping int format = Texture.RGB; // Texture format int width = 1; // Width in pixels (2**n) int height = 1; // Height in pixels (2**m) // true if width or height is non power of two private boolean widthOrHeightIsNPOT = false; // Array of images (one for each mipmap level) ImageComponentRetained images[][]; // maximum number of levels needed for the mipmapMode of this texture int maxLevels = 0; // maximum number of mipmap levels that can be defined for this texture private int maxMipMapLevels = 0; int numFaces = 1; // For CubeMap, it is 6 int baseLevel = 0; int maximumLevel = 0; float minimumLod = -1000.0f; float maximumLod = 1000.0f; Point3f lodOffset = null; private boolean useAsRaster = false; // true if used by Raster or Background. // Texture mapping enable switch // This enable is derived from the user specified enable // and whether the buf image in the imagecomp is null boolean enable = true; // User specified enable boolean userSpecifiedEnable = true; // true if alpha channel need update during rendering boolean isAlphaNeedUpdate = false; // sharpen texture info int numSharpenTextureFuncPts = 0; float sharpenTextureFuncPts[] = null; // array of pairs of floats // first value for LOD // second value for the fcn value // filter4 info float filter4FuncPts[] = null; // anisotropic filter info int anisotropicFilterMode = Texture.ANISOTROPIC_NONE; float anisotropicFilterDegree = 1.0f; // Each bit corresponds to a unique renderer if shared context // or a unique canvas otherwise. // This mask specifies which renderer/canvas has loaded the // texture images. 0 means no renderer/canvas has loaded the texture. // 1 at the particular bit means that renderer/canvas has loaded the // texture. 0 means otherwise. int resourceCreationMask = 0x0; // Each bit corresponds to a unique renderer if shared context // or a unique canvas otherwise // This mask specifies if texture images are up-to-date. // 0 at a particular bit means texture images are not up-to-date. // 1 means otherwise. If it specifies 0, then it needs to go // through the imageUpdateInfo to update the images accordingly. // int resourceUpdatedMask = 0x0; // Each bit corresponds to a unique renderer if shared context // or a unique canvas otherwise // This mask specifies if texture lod info is up-to-date. // 0 at a particular bit means texture lod info is not up-to-date. // 1 means otherwise. // int resourceLodUpdatedMask = 0x0; // Each bit corresponds to a unique renderer if shared context // or a unique canvas otherwise // This mask specifies if texture is in the resource reload list // 0 at a particular bit means texture is not in reload list // 1 means otherwise. // int resourceInReloadList = 0x0; // image update info ArrayList imageUpdateInfo[][]; int imageUpdatePruneMask[]; // Issue 357 - we need a separate reference counter per RenderBin, since // each RenderBin keeps an independent list of Texture objects to be freed. // Since this is accessed infrequently, we will use a HashMap for the // textureBin reference counter private HashMap textureBinRefCount = new HashMap(); // need to synchronize access from multiple rendering threads Object resourceLock = new Object(); private static boolean isPowerOfTwo(int val) { return ((val & (val - 1)) == 0); } void initialize(int format, int width, int widLevels, int height, int heiLevels, int mipmapMode, int boundaryWidth) { this.mipmapMode = mipmapMode; this.format = format; this.width = width; this.height = height; this.boundaryWidth = boundaryWidth; if(!isPowerOfTwo(width) || !isPowerOfTwo(height)) { this.widthOrHeightIsNPOT = true; } // determine the maximum number of mipmap levels that can be // defined from the specified dimension if (widLevels > heiLevels) { maxMipMapLevels = widLevels + 1; } else { maxMipMapLevels = heiLevels + 1; } // determine the maximum number of mipmap levels that will be // needed with the current mipmapMode if (mipmapMode != Texture.BASE_LEVEL) { baseLevel = 0; maximumLevel = maxMipMapLevels - 1; maxLevels = maxMipMapLevels; } else { baseLevel = 0; maximumLevel = 0; maxLevels = 1; } images = new ImageComponentRetained[numFaces][maxLevels]; for (int j = 0; j < numFaces; j++) { for (int i = 0; i < maxLevels; i++) { images[j][i] = null; } } } final int getFormat() { return this.format; } final int getWidth() { return this.width; } final int getHeight() { return this.height; } final int numMipMapLevels() { return (maximumLevel - baseLevel + 1); } /** * Sets the boundary mode for the S coordinate in this texture object. * @param boundaryModeS the boundary mode for the S coordinate, * one of: CLAMP or WRAP. * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. */ final void initBoundaryModeS(int boundaryModeS) { this.boundaryModeS = boundaryModeS; } /** * Retrieves the boundary mode for the S coordinate. * @return the current boundary mode for the S coordinate. * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. */ final int getBoundaryModeS() { return boundaryModeS; } /** * Sets the boundary mode for the T coordinate in this texture object. * @param boundaryModeT the boundary mode for the T coordinate, * one of: CLAMP or WRAP. * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. */ final void initBoundaryModeT(int boundaryModeT) { this.boundaryModeT = boundaryModeT; } /** * Retrieves the boundary mode for the T coordinate. * @return the current boundary mode for the T coordinate. * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. */ final int getBoundaryModeT() { return boundaryModeT; } /** * Retrieves the boundary width. * @return the boundary width of this texture. */ final int getBoundaryWidth() { return boundaryWidth; } /** * Sets the minification filter function. This * function is used when the pixel being rendered maps to an area * greater than one texel. * @param minFilter the minification filter, one of: * FASTEST, NICEST, BASE_LEVEL_POINT, BASE_LEVEL_LINEAR, * MULTI_LEVEL_POINT, MULTI_LEVEL_LINEAR. * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. */ final void initMinFilter(int minFilter) { this.minFilter = minFilter; } /** * Retrieves the minification filter. * @return the current minification filter function. * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. */ final int getMinFilter() { return minFilter; } /** * Sets the magnification filter function. This * function is used when the pixel being rendered maps to an area * less than or equal to one texel. * @param magFilter the magnification filter, one of: * FASTEST, NICEST, BASE_LEVEL_POINT, or BASE_LEVEL_LINEAR. * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. */ final void initMagFilter(int magFilter) { this.magFilter = magFilter; } /** * Retrieves the magnification filter. * @return the current magnification filter function. * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. */ final int getMagFilter() { return magFilter; } /** * Sets a specified mipmap level. * @param level mipmap level to set: 0 is the base level * @param image pixel array object containing the texture image * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. * @exception IllegalArgumentException if an ImageComponent3D * is used in a Texture2D or ImageComponent2D in Texture3D * power of 2 OR invalid format/mipmapMode is specified. */ void initImage(int level, ImageComponent image) { // Issue 172 : call checkImageSize even for non-live setImage calls checkImageSize(level, image); if (this.images == null) { throw new IllegalArgumentException(J3dI18N.getString("TextureRetained0")); } if (this.source instanceof Texture2D) { if (image instanceof ImageComponent3D) throw new IllegalArgumentException(J3dI18N.getString("Texture8")) ; } else { if (image instanceof ImageComponent2D) throw new IllegalArgumentException(J3dI18N.getString("Texture14") ); } if (this.source.isLive()) { if (this.images[0][level] != null) { this.images[0][level].clearLive(refCount); } if (image != null) { ((ImageComponentRetained)image.retained).setLive(inBackgroundGroup, refCount); } } if (image != null) { this.images[0][level] = (ImageComponentRetained)image.retained; } else { this.images[0][level] = null; } } final void checkImageSize(int level, ImageComponent image) { if (image != null) { int imgWidth = ((ImageComponentRetained)image.retained).width; int imgHeight = ((ImageComponentRetained)image.retained).height; int wdh = width; int hgt = height; for (int i = 0; i < level; i++) { wdh >>= 1; hgt >>= 1; } if (wdh < 1) wdh = 1; if (hgt < 1) hgt = 1; if ((wdh != (imgWidth - 2*boundaryWidth)) || (hgt != (imgHeight - 2*boundaryWidth))) { throw new IllegalArgumentException( J3dI18N.getString("TextureRetained1")); } } } final void checkSizes(ImageComponentRetained images[]) { // Issue 172 : this method is now redundant // Assertion check that the image at each level is the correct size // This shouldn't be needed since we already should have checked the // size at each level, and verified that all levels are set. if (images != null) { int hgt = height; int wdh = width; for (int level = 0; level < images.length; level++) { int imgWidth = images[level].width; int imgHeight = images[level].height; assert (wdh == (imgWidth - 2*boundaryWidth)) && (hgt == (imgHeight - 2*boundaryWidth)); wdh /= 2; hgt /= 2; if (wdh < 1) wdh = 1; if (hgt < 1) hgt = 1; } } } final void setImage(int level, ImageComponent image) { initImage(level, image); Object arg[] = new Object[3]; arg[0] = new Integer(level); arg[1] = image; arg[2] = new Integer(0); sendMessage(IMAGE_CHANGED, arg); // If the user has set enable to true, then if the image is null // turn off texture enable if (userSpecifiedEnable) { enable = userSpecifiedEnable; if (image != null && level >= baseLevel && level <= maximumLevel) { ImageComponentRetained img= (ImageComponentRetained)image.retained; if (img.isByReference()) { if (img.getRefImage(0) == null) { enable = false; } } else { if (img.getImageData(isUseAsRaster()).get() == null) { enable = false; } } if (!enable) sendMessage(ENABLE_CHANGED, Boolean.FALSE); } } } void initImages(ImageComponent[] images) { if (images.length != maxLevels) throw new IllegalArgumentException(J3dI18N.getString("Texture20")); for (int i = 0; i < images.length; i++) { initImage(i, images[i]); } } final void setImages(ImageComponent[] images) { int i; initImages(images); ImageComponent [] imgs = new ImageComponent[images.length]; for (i = 0; i < images.length; i++) { imgs[i] = images[i]; } Object arg[] = new Object[2]; arg[0] = imgs; arg[1] = new Integer(0); sendMessage(IMAGES_CHANGED, arg); // If the user has set enable to true, then if the image is null // turn off texture enable if (userSpecifiedEnable) { enable = userSpecifiedEnable; for (i = baseLevel; i <= maximumLevel && enable; i++) { if (images[i] != null) { ImageComponentRetained img= (ImageComponentRetained)images[i].retained; if (img.isByReference()) { if (img.getRefImage(0) == null) { enable = false; } } else { if (img.getImageData(isUseAsRaster()).get() == null) { enable = false; } } } } if (!enable) { sendMessage(ENABLE_CHANGED, Boolean.FALSE); } } } /** * Gets a specified mipmap level. * @param level mipmap level to get: 0 is the base level * @return the pixel array object containing the texture image * @exception RestrictedAccessException if the method is called * when this object is part of live or compiled scene graph. */ final ImageComponent getImage(int level) { return (((images != null) && (images[0][level] != null)) ? (ImageComponent)images[0][level].source : null); } final ImageComponent[] getImages() { if (images == null) return null; ImageComponent [] rImages = new ImageComponent[images[0].length]; for (int i = 0; i < images[0].length; i++) { if (images[0][i] != null) rImages[i] = (ImageComponent)images[0][i].source; else rImages[i] = null; } return rImages; } /** * Sets mipmap mode for texture mapping for this texture object. * @param mipMapMode the new mipmap mode for this object. One of: * BASE_LEVEL or MULTI_LEVEL_MIPMAP. * @exception RestrictedAccessException if the method is called */ final void initMipMapMode(int mipmapMode) { if (this.mipmapMode == mipmapMode) return; int prevMaxLevels = maxLevels; // previous maxLevels this.mipmapMode = mipmapMode; if (mipmapMode != Texture.BASE_LEVEL) { maxLevels = maxMipMapLevels; } else { baseLevel = 0; maximumLevel = 0; maxLevels = 1; } ImageComponentRetained[][] newImages = new ImageComponentRetained[numFaces][maxLevels]; if (prevMaxLevels < maxLevels) { for (int f = 0; f < numFaces; f++) { for (int i = 0; i < prevMaxLevels; i++) { newImages[f][i] = images[f][i]; } for (int j = prevMaxLevels; j < maxLevels; j++) { newImages[f][j] = null; } } } else { for (int f = 0; f < numFaces; f++) { for (int i = 0; i < maxLevels; i++) newImages[f][i] = images[f][i]; } } images = newImages; } /** * Retrieves current mipmap mode. * @return current mipmap mode of this texture object. * @exception RestrictedAccessException if the method is called */ final int getMipMapMode() { return this.mipmapMode; } /** * Enables or disables texture mapping for this * appearance component object. * @param state true or false to enable or disable texture mapping */ final void initEnable(boolean state) { userSpecifiedEnable = state; } /** * Enables or disables texture mapping for this * appearance component object and sends a * message notifying the interested structures of the change. * @param state true or false to enable or disable texture mapping */ final void setEnable(boolean state) { initEnable(state); if (state == enable) { // if enable flag is same as user specified one // this is only possible when enable is false // because one of the images is null and user specifies false return; } enable = state; for (int j = 0; j < numFaces && enable; j++) { for (int i = baseLevel; i <= maximumLevel && enable; i++) { if (images[j][i].isByReference()) { if (images[j][i].getRefImage(0) == null) { enable = false; } } else { if (images[j][i].getImageData(isUseAsRaster()).get() == null) { enable = false; } } } } sendMessage(ENABLE_CHANGED, (enable ? Boolean.TRUE: Boolean.FALSE)); } /** * Retrieves the state of the texture enable flag. * @return true if texture mapping is enabled, * false if texture mapping is disabled */ final boolean getEnable() { return userSpecifiedEnable; } final void initBaseLevel(int level) { if ((level < 0) || (level > maximumLevel)) { throw new IllegalArgumentException( J3dI18N.getString("Texture36")); } baseLevel = level; } final void setBaseLevel(int level) { if (level == baseLevel) return; initBaseLevel(level); sendMessage(BASE_LEVEL_CHANGED, new Integer(level)); } final int getBaseLevel() { return baseLevel; } final void initMaximumLevel(int level) { if ((level < baseLevel) || (level >= maxMipMapLevels)) { throw new IllegalArgumentException( J3dI18N.getString("Texture37")); } if((mipmapMode == Texture.BASE_LEVEL) && (level != 0)) { throw new IllegalArgumentException( J3dI18N.getString("Texture48")); } maximumLevel = level; } final void setMaximumLevel(int level) { if (level == maximumLevel) return; initMaximumLevel(level); sendMessage(MAX_LEVEL_CHANGED, new Integer(level)); } final int getMaximumLevel() { return maximumLevel; } final void initMinimumLOD(float lod) { if (lod > maximumLod) { throw new IllegalArgumentException( J3dI18N.getString("Texture42")); } minimumLod = lod; } final void setMinimumLOD(float lod) { initMinimumLOD(lod); sendMessage(MIN_LOD_CHANGED, new Float(lod)); } final float getMinimumLOD() { return minimumLod; } final void initMaximumLOD(float lod) { if (lod < minimumLod) { throw new IllegalArgumentException( J3dI18N.getString("Texture42")); } maximumLod = lod; } final void setMaximumLOD(float lod) { initMaximumLOD(lod); sendMessage(MAX_LOD_CHANGED, new Float(lod)); } final float getMaximumLOD() { return maximumLod; } final void initLodOffset(float s, float t, float r) { if (lodOffset == null) { lodOffset = new Point3f(s, t, r); } else { lodOffset.set(s, t, r); } } final void setLodOffset(float s, float t, float r) { initLodOffset(s, t, r); sendMessage(LOD_OFFSET_CHANGED, new Point3f(s, t, r)); } final void getLodOffset(Tuple3f offset) { if (lodOffset == null) { offset.set(0.0f, 0.0f, 0.0f); } else { offset.set(lodOffset); } } /** * Sets the texture boundary color for this texture object. The * texture boundary color is used when boundaryModeS or boundaryModeT * is set to CLAMP. * @param boundaryColor the new texture boundary color. */ final void initBoundaryColor(Color4f boundaryColor) { this.boundaryColor.set(boundaryColor); } /** * Sets the texture boundary color for this texture object. The * texture boundary color is used when boundaryModeS or boundaryModeT * is set to CLAMP. * @param r the red component of the color. * @param g the green component of the color. * @param b the blue component of the color. * @param a the alpha component of the color. */ final void initBoundaryColor(float r, float g, float b, float a) { this.boundaryColor.set(r, g, b, a); } /** * Retrieves the texture boundary color for this texture object. * @param boundaryColor the vector that will receive the * current texture boundary color. */ final void getBoundaryColor(Color4f boundaryColor) { boundaryColor.set(this.boundaryColor); } /** * Set Anisotropic Filter */ final void initAnisotropicFilterMode(int mode) { anisotropicFilterMode = mode; } final int getAnisotropicFilterMode() { return anisotropicFilterMode; } final void initAnisotropicFilterDegree(float degree) { anisotropicFilterDegree = degree; } final float getAnisotropicFilterDegree() { return anisotropicFilterDegree; } /** * Set Sharpen Texture function */ final void initSharpenTextureFunc(float[] lod, float[] pts) { if (lod == null) { // pts will be null too. sharpenTextureFuncPts = null; numSharpenTextureFuncPts = 0; } else { numSharpenTextureFuncPts = lod.length; if ((sharpenTextureFuncPts == null) || (sharpenTextureFuncPts.length != lod.length * 2)) { sharpenTextureFuncPts = new float[lod.length * 2]; } for (int i = 0, j = 0; i < lod.length; i++) { sharpenTextureFuncPts[j++] = lod[i]; sharpenTextureFuncPts[j++] = pts[i]; } } } final void initSharpenTextureFunc(Point2f[] pts) { if (pts == null) { sharpenTextureFuncPts = null; numSharpenTextureFuncPts = 0; } else { numSharpenTextureFuncPts = pts.length; if ((sharpenTextureFuncPts == null) || (sharpenTextureFuncPts.length != pts.length * 2)) { sharpenTextureFuncPts = new float[pts.length * 2]; } for (int i = 0, j = 0; i < pts.length; i++) { sharpenTextureFuncPts[j++] = pts[i].x; sharpenTextureFuncPts[j++] = pts[i].y; } } } final void initSharpenTextureFunc(float[] pts) { if (pts == null) { sharpenTextureFuncPts = null; numSharpenTextureFuncPts = 0; } else { numSharpenTextureFuncPts = pts.length / 2; if ((sharpenTextureFuncPts == null) || (sharpenTextureFuncPts.length != pts.length)) { sharpenTextureFuncPts = new float[pts.length]; } for (int i = 0; i < pts.length; i++) { sharpenTextureFuncPts[i] = pts[i]; } } } /** * Get number of points in the sharpen texture LOD function */ final int getSharpenTextureFuncPointsCount() { return numSharpenTextureFuncPts; } /** * Copies the array of sharpen texture LOD function points into the * specified arrays */ final void getSharpenTextureFunc(float[] lod, float[] pts) { if (sharpenTextureFuncPts != null) { for (int i = 0, j = 0; i < numSharpenTextureFuncPts; i++) { lod[i] = sharpenTextureFuncPts[j++]; pts[i] = sharpenTextureFuncPts[j++]; } } } final void getSharpenTextureFunc(Point2f[] pts) { if (sharpenTextureFuncPts != null) { for (int i = 0, j = 0; i < numSharpenTextureFuncPts; i++) { pts[i].x = sharpenTextureFuncPts[j++]; pts[i].y = sharpenTextureFuncPts[j++]; } } } final void initFilter4Func(float[] weights) { if (weights == null) { filter4FuncPts = null; } else { if ((filter4FuncPts == null) || (filter4FuncPts.length != weights.length)) { filter4FuncPts = new float[weights.length]; } for (int i = 0; i < weights.length; i++) { filter4FuncPts[i] = weights[i]; } } } final int getFilter4FuncPointsCount() { if (filter4FuncPts == null) { return 0; } else { return filter4FuncPts.length; } } final void getFilter4Func(float[] weights) { if (filter4FuncPts != null) { for (int i = 0; i < filter4FuncPts.length; i++) { weights[i] = filter4FuncPts[i]; } } } /** * internal method only -- returns internal function points */ final float[] getSharpenTextureFunc() { return sharpenTextureFuncPts; } final float[] getFilter4Func(){ return filter4FuncPts; } @Override void setLive(boolean backgroundGroup, int refCount) { // This line should be assigned before calling doSetLive, so that // the mirror object's enable is assigned correctly! enable = userSpecifiedEnable; super.doSetLive(backgroundGroup, refCount); // XXXX: for now, do setLive for all the defined images. // But in theory, we only need to setLive those within the // baseLevel and maximumLevel range. But then we'll need // setLive and clearLive image when the range changes. if (images != null) { for (int j = 0; j < numFaces; j++) { for (int i = 0; i < maxLevels; i++){ if (images[j][i] == null) { throw new IllegalArgumentException( J3dI18N.getString("TextureRetained3") + i); } images[j][i].setLive(backgroundGroup, refCount); } } } // Issue 172 : assertion check the sizes of the images after we have // checked for all mipmap levels being set if (images != null) { for (int j = 0; j < numFaces; j++) { checkSizes(images[j]); } } // Send a message to Rendering Attr stucture to update the resourceMask J3dMessage createMessage = new J3dMessage(); createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; createMessage.type = J3dMessage.TEXTURE_CHANGED; createMessage.args[0] = this; createMessage.args[1]= new Integer(UPDATE_IMAGE); createMessage.args[2] = null; createMessage.args[3] = new Integer(changedFrequent); VirtualUniverse.mc.processMessage(createMessage); // If the user has set enable to true, then if the image is null // turn off texture enable if (userSpecifiedEnable) { if (images != null) { for (int j = 0; j < numFaces && enable; j++) { for (int i = baseLevel; i <= maximumLevel && enable; i++){ if (images[j][i].isByReference()) { if (images[j][i].getRefImage(0) == null) { enable = false; } } else { if (images[j][i].getImageData(isUseAsRaster()).get() == null) { enable = false; } } } } } else { enable = false; } if (!enable) sendMessage(ENABLE_CHANGED, Boolean.FALSE); } super.markAsLive(); } @Override void clearLive(int refCount) { super.clearLive(refCount); if (images != null) { for (int j = 0; j < numFaces; j++) { for (int i = 0; i < maxLevels; i++) { images[j][i].clearLive(refCount); images[j][i].removeUser(mirror); } } } } /* * The following methods update the native context. * The implementation for Texture2D happens here. * Texture3D and TextureCubeMap implement their own versions. */ void bindTexture(Context ctx, int objectId, boolean enable) { Pipeline.getPipeline().bindTexture2D(ctx, objectId, enable); } void updateTextureBoundary(Context ctx, int boundaryModeS, int boundaryModeT, float boundaryRed, float boundaryGreen, float boundaryBlue, float boundaryAlpha) { Pipeline.getPipeline().updateTexture2DBoundary(ctx, boundaryModeS, boundaryModeT, boundaryRed, boundaryGreen, boundaryBlue, boundaryAlpha); } void updateTextureFilterModes(Context ctx, int minFilter, int magFilter) { Pipeline.getPipeline().updateTexture2DFilterModes(ctx, minFilter, magFilter); } void updateTextureSharpenFunc(Context ctx, int numSharpenTextureFuncPts, float[] sharpenTextureFuncPts) { Pipeline.getPipeline().updateTexture2DSharpenFunc(ctx, numSharpenTextureFuncPts, sharpenTextureFuncPts); } void updateTextureFilter4Func(Context ctx, int numFilter4FuncPts, float[] filter4FuncPts) { Pipeline.getPipeline().updateTexture2DFilter4Func(ctx, numFilter4FuncPts, filter4FuncPts); } void updateTextureAnisotropicFilter(Context ctx, float degree) { Pipeline.getPipeline().updateTexture2DAnisotropicFilter(ctx, degree); } void updateTextureLodRange(Context ctx, int baseLevel, int maximumLevel, float minimumLod, float maximumLod) { Pipeline.getPipeline().updateTexture2DLodRange(ctx, baseLevel, maximumLevel, minimumLod, maximumLod); } void updateTextureLodOffset(Context ctx, float lodOffsetX, float lodOffsetY, float lodOffsetZ) { Pipeline.getPipeline().updateTexture2DLodOffset(ctx, lodOffsetX, lodOffsetY, lodOffsetZ); } // free a Texture2D id void freeTextureId(int id) { synchronized (resourceLock) { if (objectId == id) objectId = -1; } } private boolean isEnabled(Canvas3D cv) { if(widthOrHeightIsNPOT && !isUseAsRaster() && ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_NON_POWER_OF_TWO ) == 0)) { return false; } return enable; } // bind a named texture to a texturing target void bindTexture(Canvas3D cv) { synchronized(resourceLock) { if (objectId == -1) objectId = Canvas3D.generateTexID(cv.ctx); cv.addTextureResource(objectId, this); } bindTexture(cv.ctx, objectId, isEnabled(cv)); } /** * load level 0 explicitly with null pointer to enable * mipmapping when level 0 is not the base level */ void updateTextureDimensions(Canvas3D cv) { if(images[0][0] != null) { updateTextureImage(cv, 0, maxLevels, 0, format, images[0][0].getImageFormatTypeIntValue(false), width, height, boundaryWidth, images[0][0].getImageDataTypeIntValue(), null); } } void updateTextureLOD(Canvas3D cv) { if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_LOD_RANGE) != 0 ) { int max = 0; if( mipmapMode == Texture.BASE_LEVEL ) { max = maxMipMapLevels; } else { max = maximumLevel; } updateTextureLodRange(cv.ctx, baseLevel, max, minimumLod, maximumLod); } if ((lodOffset != null) && ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_LOD_OFFSET) != 0)) { updateTextureLodOffset(cv.ctx, lodOffset.x, lodOffset.y, lodOffset.z); } } void updateTextureBoundary(Canvas3D cv) { updateTextureBoundary(cv.ctx, boundaryModeS, boundaryModeT, boundaryColor.x, boundaryColor.y, boundaryColor.z, boundaryColor.w); } void updateTextureFields(Canvas3D cv) { int magnificationFilter = magFilter; int minificationFilter = minFilter; // update sharpen texture function if applicable if ((magnificationFilter >= Texture.LINEAR_SHARPEN) && (magnificationFilter <= Texture.LINEAR_SHARPEN_ALPHA)) { if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_SHARPEN) != 0 ) { // send down sharpen texture LOD function // updateTextureSharpenFunc(cv.ctx, numSharpenTextureFuncPts, sharpenTextureFuncPts); } else { // sharpen texture is not supported by the underlying // library, fallback to BASE_LEVEL_LINEAR magnificationFilter = Texture.BASE_LEVEL_LINEAR; } } else if ((magnificationFilter >= Texture2D.LINEAR_DETAIL) && (magnificationFilter <= Texture2D.LINEAR_DETAIL_ALPHA)) { if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_DETAIL) == 0) { // detail texture is not supported by the underlying // library, fallback to BASE_LEVEL_LINEAR magnificationFilter = Texture.BASE_LEVEL_LINEAR; } } if (minificationFilter == Texture.FILTER4 || magnificationFilter == Texture.FILTER4) { boolean noFilter4 = false; if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_FILTER4) != 0) { if (filter4FuncPts == null) { // filter4 function is not defined, // fallback to BASE_LEVEL_LINEAR noFilter4 = true; } else { updateTextureFilter4Func(cv.ctx, filter4FuncPts.length, filter4FuncPts); } } else { // filter4 is not supported by the underlying // library, fallback to BASE_LEVEL_LINEAR noFilter4 = true; } if (noFilter4) { if (minificationFilter == Texture.FILTER4) { minificationFilter = Texture.BASE_LEVEL_LINEAR; } if (magnificationFilter == Texture.FILTER4) { magnificationFilter = Texture.BASE_LEVEL_LINEAR; } } } // Fallback to BASE mode if hardware mipmap generation is not supported. if ((mipmapMode == Texture.BASE_LEVEL) && ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_AUTO_MIPMAP_GENERATION) == 0)) { if (minificationFilter == Texture.NICEST || minificationFilter == Texture.MULTI_LEVEL_LINEAR) { minificationFilter = Texture.BASE_LEVEL_LINEAR; } else if (minificationFilter == Texture.MULTI_LEVEL_POINT) { minificationFilter = Texture.BASE_LEVEL_POINT; } } // update texture filtering modes updateTextureFilterModes(cv.ctx, minificationFilter, magnificationFilter); if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_ANISOTROPIC_FILTER) != 0) { if (anisotropicFilterMode == Texture.ANISOTROPIC_NONE) { updateTextureAnisotropicFilter(cv.ctx, 1.0f); } else { updateTextureAnisotropicFilter(cv.ctx, anisotropicFilterDegree); } } // update texture boundary modes, boundary color updateTextureBoundary(cv); } // Wrapper around the native call for 2D textures; overridden for // TextureCureMap void updateTextureImage(Canvas3D cv, int face, int numLevels, int level, int textureFormat, int imageFormat, int width, int height, int boundaryWidth, int imageDataType, Object data) { Pipeline.getPipeline().updateTexture2DImage(cv.ctx, numLevels, level, textureFormat, imageFormat, width, height, boundaryWidth, imageDataType, data, useAutoMipMapGeneration(cv)); } // Wrapper around the native call for 2D textures; overridden for // TextureCureMap void updateTextureSubImage(Canvas3D cv, int face, int level, int xoffset, int yoffset, int textureFormat, int imageFormat, int imgXOffset, int imgYOffset, int tilew, int width, int height, int imageDataType, Object data) { Pipeline.getPipeline().updateTexture2DSubImage(cv.ctx, level, xoffset, yoffset, textureFormat, imageFormat, imgXOffset, imgYOffset, tilew, width, height, imageDataType, data, useAutoMipMapGeneration(cv)); } /** * reloadTextureImage is used to load a particular level of image * This method needs to take care of RenderedImage as well as * BufferedImage */ void reloadTextureImage(Canvas3D cv, int face, int level, ImageComponentRetained image, int numLevels) { boolean useAsRaster = isUseAsRaster(); ImageComponentRetained.ImageData imageData = image.getImageData(useAsRaster); assert imageData != null; updateTextureImage(cv, face, numLevels, level, format, image.getImageFormatTypeIntValue(useAsRaster), imageData.getWidth(), imageData.getHeight(), boundaryWidth, image.getImageDataTypeIntValue(), imageData.get()); // TODO : Dead code - need to clean up for 1.6 // Now take care of the RenderedImage (byRef and yUp) case. Note, if image // is a RenderedImage ( byRef and yUp), then imageData will be null if (imageData == null) { // System.err.println("==========. subImage"); // Download all the tiles for this texture int xoffset = 0, yoffset = 0; int tmpw = image.width; int tmph = image.height; int endXTile = image.tilew; int endYTile = image.tileh; int curw = endXTile; int curh = endYTile; if (tmpw < curw) { curw = tmpw; } if (tmph < curh) { curh = tmph; } int startw = curw; int imageXOffset = image.tilew - curw; int imageYOffset = image.tileh - curh; for (int m = 0; m < image.numYTiles; m++) { xoffset = 0; tmpw = width; curw = startw; imageXOffset = image.tilew - curw; for (int n = 0; n < image.numXTiles; n++) { java.awt.image.Raster ras; ras = ((RenderedImage)image.getRefImage(0)).getTile(n,m); byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData(); updateTextureSubImage(cv, face, level, xoffset, yoffset, format, image.getImageFormatTypeIntValue(false), imageXOffset, imageYOffset, image.tilew, curw, curh, ImageComponentRetained.IMAGE_DATA_TYPE_BYTE_ARRAY, (Object) data); xoffset += curw; imageXOffset = 0; tmpw -= curw; if (tmpw < image.tilew) curw = tmpw; else curw = image.tilew; } yoffset += curh; imageYOffset = 0; tmph -= curh; if (tmph < image.tileh) curh = tmph; else curh = image.tileh; } } } /** * update a subregion of the texture image * This method needs to take care of RenderedImage as well as * BufferedImage */ void reloadTextureSubImage(Canvas3D cv, int face, int level, ImageComponentUpdateInfo info, ImageComponentRetained image) { int x = info.x, y = info.y, width = info.width, height = info.height; //The x and y here specifies the subregion of the imageData of //the associated RenderedImage. //System.err.println("\nupdateTextureSubImage: x= " + x + " y= " + y + // " width= " + width + " height= " + height + // " format= " + format); ImageComponentRetained.ImageData imageData = image.getImageData(isUseAsRaster()); if(imageData != null) { int xoffset = x; int yoffset = y; // TODO Check this logic : If !yUp adjust yoffset if (!image.yUp) { yoffset = image.height - yoffset - height; } updateTextureSubImage(cv, face, level, xoffset, yoffset, format, image.getImageFormatTypeIntValue(false), xoffset, yoffset, image.width, width, height, image.getImageDataTypeIntValue(), imageData.get()); } else { assert false; // TODO : Dead code - need to clean up for 1.6 // System.err.println("RenderedImage subImage update"); // determine the first tile of the image float mt; int minTileX, minTileY; int rx = x; int ry = y; mt = (float)(rx) / (float)image.tilew; if (mt < 0) { minTileX = (int)(mt - 1); } else { minTileX = (int)mt; } mt = (float)(ry) / (float)image.tileh; if (mt < 0) { minTileY = (int)(mt - 1); } else { minTileY = (int)mt; } // determine the pixel offset of the upper-left corner of the // first tile int startXTile = minTileX * image.tilew; int startYTile = minTileY * image.tilew; // image dimension in the first tile int curw = (startXTile + image.tilew - rx); int curh = (startYTile + image.tileh - ry); // check if the to-be-copied region is less than the tile image // if so, update the to-be-copied dimension of this tile if (curw > width) { curw = width; } if (curh > height) { curh = height; } // save the to-be-copied width of the left most tile int startw = curw; // temporary variable for dimension of the to-be-copied region int tmpw = width; int tmph = height; // offset of the first pixel of the tile to be copied; offset is // relative to the upper left corner of the title int imgX = rx - startXTile; int imgY = ry - startYTile; // determine the number of tiles in each direction that the // image spans int numXTiles = (width + imgX) / image.tilew; int numYTiles = (height + imgY) / image.tileh; if (((float)(width + imgX ) % (float)image.tilew) > 0) { numXTiles += 1; } if (((float)(height + imgY ) % (float)image.tileh) > 0) { numYTiles += 1; } java.awt.image.Raster ras; int textureX = x; // x offset in the texture int textureY = y; // y offset in the texture for (int yTile = minTileY; yTile < minTileY + numYTiles; yTile++) { tmpw = width; curw = startw; imgX = rx - startXTile; for (int xTile = minTileX; xTile < minTileX + numXTiles; xTile++) { ras = ((RenderedImage)image.getRefImage(0)).getTile(xTile, yTile); byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData(); updateTextureSubImage(cv, face, level, textureX, textureY, format, image.getImageFormatTypeIntValue(false), imgX, imgY, image.tilew, curw, curh, ImageComponentRetained.IMAGE_DATA_TYPE_BYTE_ARRAY, (Object)data); // move to the next tile in x direction textureX += curw; imgX = 0; // determine the width of copy region of the next tile tmpw -= curw; if (tmpw < image.tilew) { curw = tmpw; } else { curw = image.tilew; } } // move to the next set of tiles in y direction textureY += curh; imgY = 0; // determine the height of copy region for the next set // of tiles tmph -= curh; if (tmph < image.tileh) { curh = tmph; } else { curh = image.tileh; } } } } // reload texture mipmap void reloadTexture(Canvas3D cv) { int blevel, mlevel; //System.err.println("reloadTexture: baseLevel= " + baseLevel + // " maximumLevel= " + maximumLevel); if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_LOD_RANGE) == 0 ) { blevel = 0; mlevel = maxLevels - 1; } else { blevel = baseLevel; mlevel = maximumLevel; } if (blevel != 0) { // level 0 is not the base level, hence, need // to load level 0 explicitly with a null pointer in order // for mipmapping to be active. updateTextureDimensions(cv); } for (int j = 0; j < numFaces; j++) { for (int i = blevel; i <= mlevel; i++) { // it is possible to have null pointer if only a subset // of mipmap levels is defined but the canvas does not // support lod_range extension ImageComponentRetained image = images[j][i]; if (image != null) { // Issue 366: call evaluateExtensions, since it may not // have been called yet in all cases image.evaluateExtensions(cv); reloadTextureImage(cv, j, i, image, maxLevels); } } } } // update texture mipmap based on the imageUpdateInfo void updateTexture(Canvas3D cv, int resourceBit) { //System.err.println("updateTexture\n"); ImageComponentUpdateInfo info; for (int k = 0; k < numFaces; k++) { for (int i = baseLevel; i <= maximumLevel; i++) { if (imageUpdateInfo[k][i] != null) { for (int j = 0; j < imageUpdateInfo[k][i].size(); j++) { info = (ImageComponentUpdateInfo) imageUpdateInfo[k][i].get(j); synchronized(resourceLock) { // if this info is updated, move on to the next one if ((info.updateMask & resourceBit) == 0) continue; // check if all canvases have processed this update info.updateMask &= ~resourceBit; // all the current resources have updated this // info, so this info can be removed from the // update list if ((info.updateMask & resourceCreationMask) == 0) { info.updateMask = 0; // mark this update as // done // mark the prune flag so as to prune the // update list next time when the update // list is to be modified. // Don't want to clean up the list at // rendering time because (1) MT issue, // other renderer could be processing // the update list now; // (2) takes up rendering time. if (imageUpdatePruneMask == null) { imageUpdatePruneMask = new int[numFaces]; } imageUpdatePruneMask[k] = 1 << i; } } if (info.entireImage == true) { reloadTextureImage(cv, k, i, images[k][i], maxLevels); } else { reloadTextureSubImage(cv, k, i, info, images[k][i]); } } } } } } /** * reloadTextureSharedContext is called to reload texture * on a shared context. It is invoked from the Renderer * before traversing the RenderBin. The idea is to reload * all necessary textures up front for all shared contexts * in order to minimize the context switching overhead. */ void reloadTextureSharedContext(Canvas3D cv) { // if texture is not enabled, don't bother downloading the // the texture state if (!isEnabled(cv)) { return; } bindTexture(cv); // reload all levels of texture image // update texture parameters such as boundary modes, filtering updateTextureFields(cv); // update texture Lod parameters updateTextureLOD(cv); // update all texture images reloadTexture(cv); synchronized(resourceLock) { resourceCreationMask |= cv.screen.renderer.rendererBit; resourceUpdatedMask |= cv.screen.renderer.rendererBit; resourceLodUpdatedMask |= cv.screen.renderer.rendererBit; resourceInReloadList &= ~cv.screen.renderer.rendererBit; } } /** * updateNative is called while traversing the RenderBin to * update the texture state */ void updateNative(Canvas3D cv) { boolean reloadTexture = false; // true - reload all levels of texture boolean updateTexture = false; // true - update a portion of texture boolean updateTextureLod = false; // true - update texture Lod info //System.err.println("Texture/updateNative: " + this + "object= " + objectId + " enable= " + enable); bindTexture(cv); // if texture is not enabled, don't bother downloading the // the texture state if (!isEnabled(cv)) { return; } if (cv.useSharedCtx && cv.screen.renderer.sharedCtx != null) { if ((resourceCreationMask & cv.screen.renderer.rendererBit) == 0) { reloadTexture = true; } else { if (((resourceUpdatedMask & cv.screen.renderer.rendererBit) == 0) && (imageUpdateInfo != null)) { updateTexture = true; } if ((resourceLodUpdatedMask & cv.screen.renderer.rendererBit) == 0) { updateTextureLod = true; } } if (reloadTexture || updateTexture || updateTextureLod) { cv.makeCtxCurrent(cv.screen.renderer.sharedCtx); bindTexture(cv); } } else { if ((resourceCreationMask & cv.canvasBit) == 0) { reloadTexture = true; } else { if (((resourceUpdatedMask & cv.canvasBit) == 0) && (imageUpdateInfo != null)) { updateTexture = true; } if ((resourceLodUpdatedMask & cv.canvasBit) == 0) { updateTextureLod = true; } } } //System.err.println("......... reloadTexture= " + reloadTexture + // " updateTexture= " + updateTexture + // " updateTextureLod= " + updateTextureLod); //System.err.println("......... resourceCreationMask= " + resourceCreationMask + // " resourceUpdatedMask= " + resourceUpdatedMask); if (reloadTexture) { // reload all levels of texture image // update texture parameters such as boundary modes, filtering updateTextureFields(cv); // update texture Lod parameters updateTextureLOD(cv); // update all texture images reloadTexture(cv); if (cv.useSharedCtx) { cv.makeCtxCurrent(cv.ctx); synchronized(resourceLock) { resourceCreationMask |= cv.screen.renderer.rendererBit; resourceUpdatedMask |= cv.screen.renderer.rendererBit; resourceLodUpdatedMask |= cv.screen.renderer.rendererBit; } } else { synchronized(resourceLock) { resourceCreationMask |= cv.canvasBit; resourceUpdatedMask |= cv.canvasBit; resourceLodUpdatedMask |= cv.canvasBit; } } } else if (updateTextureLod || updateTexture) { if (updateTextureLod) { updateTextureLOD(cv); } if (updateTexture) { // update texture image int resourceBit = 0; if (cv.useSharedCtx) { resourceBit = cv.screen.renderer.rendererBit; } else { resourceBit = cv.canvasBit; } // update texture based on the imageComponent update info updateTexture(cv, resourceBit); } // set the appropriate bit in the resource update masks showing // that the resource is up-to-date if (cv.useSharedCtx) { cv.makeCtxCurrent(cv.ctx); synchronized(resourceLock) { resourceUpdatedMask |= cv.screen.renderer.rendererBit; resourceLodUpdatedMask |= cv.screen.renderer.rendererBit; } } else { synchronized(resourceLock) { resourceUpdatedMask |= cv.canvasBit; resourceLodUpdatedMask |= cv.canvasBit; } } } } @Override synchronized void createMirrorObject() { if (mirror == null) { if (this instanceof Texture3DRetained) { Texture3DRetained t3d = (Texture3DRetained)this; Texture3D tex = new Texture3D(t3d.mipmapMode, t3d.format, t3d.width, t3d.height, t3d.depth, t3d.boundaryWidth); mirror = (Texture3DRetained)tex.retained;; } else if (this instanceof TextureCubeMapRetained) { TextureCubeMap tex = new TextureCubeMap(mipmapMode, format, width, boundaryWidth); mirror = (TextureCubeMapRetained)tex.retained; } else { Texture2D tex = new Texture2D(mipmapMode, format, width, height, boundaryWidth); mirror = (Texture2DRetained)tex.retained; } ((TextureRetained)mirror).objectId = -1; } initMirrorObject(); } /** * Initializes a mirror object, point the mirror object to the retained * object if the object is not editable */ @Override synchronized void initMirrorObject() { mirror.source = source; if (this instanceof Texture3DRetained) { Texture3DRetained t3d = (Texture3DRetained)this; ((Texture3DRetained)mirror).boundaryModeR = t3d.boundaryModeR; ((Texture3DRetained)mirror).depth = t3d.depth; } TextureRetained mirrorTexture = (TextureRetained)mirror; mirrorTexture.boundaryModeS = boundaryModeS; mirrorTexture.boundaryModeT = boundaryModeT; mirrorTexture.minFilter = minFilter; mirrorTexture.magFilter = magFilter; mirrorTexture.boundaryColor.set(boundaryColor); mirrorTexture.enable = enable; mirrorTexture.userSpecifiedEnable = enable; mirrorTexture.enable = enable; mirrorTexture.numFaces = numFaces; mirrorTexture.resourceCreationMask = 0x0; mirrorTexture.resourceUpdatedMask = 0x0; mirrorTexture.resourceLodUpdatedMask = 0x0; mirrorTexture.resourceInReloadList = 0x0; // LOD information mirrorTexture.baseLevel = baseLevel; mirrorTexture.maximumLevel = maximumLevel; mirrorTexture.minimumLod = minimumLod; mirrorTexture.maximumLod = maximumLod; mirrorTexture.lodOffset = lodOffset; // sharpen texture LOD function mirrorTexture.numSharpenTextureFuncPts = numSharpenTextureFuncPts; if (sharpenTextureFuncPts == null) { mirrorTexture.sharpenTextureFuncPts = null; } else { if ((mirrorTexture.sharpenTextureFuncPts == null) || (mirrorTexture.sharpenTextureFuncPts.length != sharpenTextureFuncPts.length)) { mirrorTexture.sharpenTextureFuncPts = new float[sharpenTextureFuncPts.length]; } for (int i = 0; i < sharpenTextureFuncPts.length; i++) { mirrorTexture.sharpenTextureFuncPts[i] = sharpenTextureFuncPts[i]; } } // filter4 function if (filter4FuncPts == null) { mirrorTexture.filter4FuncPts = null; } else { if ((mirrorTexture.filter4FuncPts == null) || (mirrorTexture.filter4FuncPts.length != filter4FuncPts.length)) { mirrorTexture.filter4FuncPts = new float[filter4FuncPts.length]; } for (int i = 0; i < filter4FuncPts.length; i++) { mirrorTexture.filter4FuncPts[i] = filter4FuncPts[i]; } } // Anisotropic Filter mirrorTexture.anisotropicFilterMode = anisotropicFilterMode; mirrorTexture.anisotropicFilterDegree = anisotropicFilterDegree; mirrorTexture.maxLevels = maxLevels; if (images != null) { for (int j = 0; j < numFaces; j++) { for (int i = 0; i < maxLevels; i++) { mirrorTexture.images[j][i] = images[j][i]; // add texture to the userList of the images if (images[j][i] != null) { images[j][i].addUser(mirrorTexture); } } } } } boolean useAutoMipMapGeneration(Canvas3D cv) { if (mipmapMode == Texture.BASE_LEVEL && (minFilter == Texture.NICEST || minFilter == Texture.MULTI_LEVEL_POINT || minFilter == Texture.MULTI_LEVEL_LINEAR) && ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_AUTO_MIPMAP_GENERATION) != 0)) { return true; } return false; } /** * Go through the image update info list * and remove those that are already done * by all the resources */ void pruneImageUpdateInfo() { ImageComponentUpdateInfo info; //System.err.println("Texture.pruneImageUpdateInfo"); for (int k = 0; k < numFaces; k++) { for (int i = baseLevel; i <= maximumLevel; i++) { if ((imageUpdatePruneMask[k] & (1<= width/2) && (arg.height >= height/2)) { // // // if the subimage dimension is close to the complete dimension, // // use the full update (it's more efficient) // info.entireImage = true; } else { info.entireImage = false; } if (info.entireImage) { // the entire image update supercedes all the subimage update; // hence, remove all the existing updates from the list imageUpdateInfo[face][level].clear(); // reset the update prune mask for this level if (imageUpdatePruneMask != null) { imageUpdatePruneMask[face] &= ~(1 << level); } } else { // subimage update, needs to save the subimage info info.x = arg.x; info.y = arg.y; info.z = arg.z; info.width = arg.width; info.height = arg.height; } // save the mask which shows the canvases that have created resources // for this image, aka, these are the resources that need to be // updated. info.updateMask = resourceCreationMask; // add the image update to the list imageUpdateInfo[face][level].add(info); // check if the update list stills need to be pruned if (imageUpdatePruneMask != null) { pruneImageUpdateInfo(); } } void validate() { enable = true; for (int j = 0; j < numFaces && enable; j++) { for (int i = baseLevel; i <= maximumLevel && enable; i++) { if (images[j][i] == null) { enable = false; } } } } /** * Update the "component" field of the mirror object with the * given "value" */ @Override synchronized void updateMirrorObject(int component, Object value) { TextureRetained mirrorTexture = (TextureRetained)mirror; if ((component & ENABLE_CHANGED) != 0) { mirrorTexture.enable = ((Boolean)value).booleanValue(); } else if ((component & IMAGE_CHANGED) != 0) { Object [] arg = (Object []) value; int level = ((Integer)arg[0]).intValue(); ImageComponent image = (ImageComponent)arg[1]; int face = ((Integer)arg[2]).intValue(); // first remove texture from the userList of the current // referencing image and if (mirrorTexture.images[face][level] != null) { mirrorTexture.images[face][level].removeUser(mirror); } // assign the new image and add texture to the userList if (image == null) { mirrorTexture.images[face][level] = null; } else { mirrorTexture.images[face][level] = (ImageComponentRetained)image.retained; mirrorTexture.images[face][level].addUser(mirror); } // NOTE: the old image has to be removed from the // renderBins' NodeComponentList and new image has to be // added to the lists. This will be taken care of // in the RenderBin itself in response to the // IMAGE_CHANGED message // mark that texture images need to be updated mirrorTexture.resourceUpdatedMask = 0; // add update info to the update list mirrorTexture.addImageUpdateInfo(level, face, null); } else if ((component & IMAGES_CHANGED) != 0) { Object [] arg = (Object []) value; ImageComponent [] images = (ImageComponent[])arg[0]; int face = ((Integer)arg[1]).intValue(); for (int i = 0; i < images.length; i++) { // first remove texture from the userList of the current // referencing image if (mirrorTexture.images[face][i] != null) { mirrorTexture.images[face][i].removeUser(mirror); } // assign the new image and add texture to the userList if (images[i] == null) { mirrorTexture.images[face][i] = null; } else { mirrorTexture.images[face][i] = (ImageComponentRetained)images[i].retained; mirrorTexture.images[face][i].addUser(mirror); } } mirrorTexture.updateResourceCreationMask(); // NOTE: the old images have to be removed from the // renderBins' NodeComponentList and new image have to be // added to the lists. This will be taken care of // in the RenderBin itself in response to the // IMAGES_CHANGED message } else if ((component & BASE_LEVEL_CHANGED) != 0) { int level = ((Integer)value).intValue(); if (level < mirrorTexture.baseLevel) { // add texture to the userList of those new levels of // enabling images for (int j = 0; j < numFaces; j++) { for (int i = level; i < mirrorTexture.baseLevel; i++) { if (mirrorTexture.images[j][i] == null) { mirrorTexture.enable = false; } else { mirrorTexture.addImageUpdateInfo(i, j, null); } } } mirrorTexture.baseLevel = level; // mark that texture images need to be updated mirrorTexture.resourceUpdatedMask = 0; } else { mirrorTexture.baseLevel = level; if (userSpecifiedEnable && (mirrorTexture.enable == false)) { // if texture is to be enabled but is currently // disabled, it's probably disabled because // some of the images are missing. Now that // the baseLevel is modified, validate the // texture images again mirrorTexture.validate(); } } // mark that texture lod info needs to be updated mirrorTexture.resourceLodUpdatedMask = 0; } else if ((component & MAX_LEVEL_CHANGED) != 0) { int level = ((Integer)value).intValue(); if (level > mirrorTexture.maximumLevel) { // add texture to the userList of those new levels of // enabling images for (int j = 0; j < numFaces; j++) { for (int i = mirrorTexture.maximumLevel; i < level; i++) { if (mirrorTexture.images[j][i] == null) { mirrorTexture.enable = false; } else { mirrorTexture.addImageUpdateInfo(i, j, null); } } } mirrorTexture.maximumLevel = level; // mark that texture images need to be updated mirrorTexture.resourceUpdatedMask = 0; } else { mirrorTexture.maximumLevel = level; if (userSpecifiedEnable && (mirrorTexture.enable == false)) { // if texture is to be enabled but is currently // disabled, it's probably disabled because // some of the images are missing. Now that // the baseLevel is modified, validate the // texture images again mirrorTexture.validate(); } } // mark that texture lod info needs to be updated mirrorTexture.resourceLodUpdatedMask = 0; } else if ((component & MIN_LOD_CHANGED) != 0) { mirrorTexture.minimumLod = ((Float)value).floatValue(); // mark that texture lod info needs to be updated mirrorTexture.resourceLodUpdatedMask = 0; } else if ((component & MAX_LOD_CHANGED) != 0) { mirrorTexture.maximumLod = ((Float)value).floatValue(); // mark that texture lod info needs to be updated mirrorTexture.resourceLodUpdatedMask = 0; } else if ((component & LOD_OFFSET_CHANGED) != 0) { if ((mirrorTexture.lodOffset) == null) { mirrorTexture.lodOffset = new Point3f((Point3f)value); } else { mirrorTexture.lodOffset.set((Point3f)value); } // mark that texture lod info needs to be updated mirrorTexture.resourceLodUpdatedMask = 0; } else if ((component & UPDATE_IMAGE) != 0) { mirrorTexture.updateResourceCreationMask(); } } // notifies the Texture mirror object that the image data in a referenced // ImageComponent object is changed. Need to update the texture image // accordingly. // Note: this is called from mirror object only void notifyImageComponentImageChanged(ImageComponentRetained image, ImageComponentUpdateInfo value) { //System.err.println("Texture.notifyImageComponentImageChanged"); // if this texture is to be reloaded, don't bother to keep // the update info if (resourceCreationMask == 0) { if (imageUpdateInfo != null) { //remove all the existing updates from the list for (int face = 0; face < numFaces; face++) { for (int level = 0; level < maxLevels; level++) { if (imageUpdateInfo[face][level] != null) { imageUpdateInfo[face][level].clear(); } } // reset the update prune mask for this level if (imageUpdatePruneMask != null) { imageUpdatePruneMask[face] = 0; } } } return; } // first find which texture image is being affected boolean done; for (int j = 0; j < numFaces; j++) { done = false; for (int i = baseLevel; i <= maximumLevel && !done; i++) { if (images[j][i] == image) { // reset the resourceUpdatedMask to tell the // rendering method to update the resource resourceUpdatedMask = 0; // add update info to the update list addImageUpdateInfo(i, j, value); // set done to true for this face because no two levels // can reference the same ImageComponent object done = true; } } } } // reset the resourceCreationMask // Note: called from the mirror object only void updateResourceCreationMask() { resourceCreationMask = 0x0; } void incTextureBinRefCount(TextureBin tb) { ImageComponentRetained image; setTextureBinRefCount(tb, getTextureBinRefCount(tb) + 1); // check to see if there is any modifiable images, // if yes, add those images to nodeComponentList in RenderBin // so that RenderBin can acquire a lock before rendering // to prevent updating of image data while rendering for (int j = 0; j < numFaces; j++) { for (int i = 0; i < maxLevels; i++) { image = images[j][i]; // it is possible that image.source == null because // the mipmap could have been created by the library, and // hence don't have source and therefore they are // guaranteed not modifiable if (image != null && (image.isByReference() || (image.source != null && image.source.getCapability( ImageComponent.ALLOW_IMAGE_WRITE)))) { tb.renderBin.addNodeComponent(image); } } } } void decTextureBinRefCount(TextureBin tb) { ImageComponentRetained image; setTextureBinRefCount(tb, getTextureBinRefCount(tb) - 1); // remove any modifiable images from RenderBin nodeComponentList for (int j = 0; j < numFaces; j++) { for (int i = 0; i < maxLevels; i++) { image = images[j][i]; if (image != null && (image.isByReference() || (image.source != null && image.source.getCapability( ImageComponent.ALLOW_IMAGE_WRITE)))) { tb.renderBin.removeNodeComponent(image); } } } } final void sendMessage(int attrMask, Object attr) { ArrayList univList = new ArrayList(); ArrayList> gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); // Send to rendering attribute structure, regardless of // whether there are users or not (alternate appearance case ..) J3dMessage createMessage = new J3dMessage(); createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; createMessage.type = J3dMessage.TEXTURE_CHANGED; createMessage.universe = null; createMessage.args[0] = this; createMessage.args[1] = new Integer(attrMask); createMessage.args[2] = attr; createMessage.args[3] = new Integer(changedFrequent); VirtualUniverse.mc.processMessage(createMessage); // System.err.println("univList.size is " + univList.size()); for(int i=0; i gL = gaList.get(i); GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; gL.toArray(gaArr); createMessage.args[3] = gaArr; VirtualUniverse.mc.processMessage(createMessage); } } @Override void handleFrequencyChange(int bit) { switch (bit) { case Texture.ALLOW_ENABLE_WRITE: case Texture.ALLOW_IMAGE_WRITE: case Texture.ALLOW_LOD_RANGE_WRITE: { setFrequencyChangeMask(bit, bit); } default: break; } } void setUseAsRaster(boolean useAsRaster) { this.useAsRaster = useAsRaster; } boolean isUseAsRaster() { return this.useAsRaster; } // Issue 357 - {get/set}TextureBinRefCount now uses a separate reference // counter per RenderBin. The absence of the RenderBin key in the hash map // is used to indicate a value of 0. This makes initialization easier, and // prevents a small amount of garbage accumulating for inactive RenderBins. int getTextureBinRefCount(TextureBin tb) { Integer i = textureBinRefCount.get(tb.renderBin); return i == null ? 0 : i.intValue(); } private void setTextureBinRefCount(TextureBin tb, int refCount) { if (refCount == 0) { textureBinRefCount.remove(tb.renderBin); } else { textureBinRefCount.put(tb.renderBin, new Integer(refCount)); } } }