aboutsummaryrefslogtreecommitdiffstats
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/org/jogamp/java3d/Jogl2es2Context.java1213
-rw-r--r--src/main/java/org/jogamp/java3d/Jogl2es2Pipeline.java76
2 files changed, 684 insertions, 605 deletions
diff --git a/src/main/java/org/jogamp/java3d/Jogl2es2Context.java b/src/main/java/org/jogamp/java3d/Jogl2es2Context.java
index b3f94f4..93bf5f1 100644
--- a/src/main/java/org/jogamp/java3d/Jogl2es2Context.java
+++ b/src/main/java/org/jogamp/java3d/Jogl2es2Context.java
@@ -1,604 +1,609 @@
-/* Copyright (c) 2016 JogAmp Community. 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.
- *
- */
-
-package org.jogamp.java3d;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-import org.jogamp.vecmath.Matrix3d;
-import org.jogamp.vecmath.Matrix4d;
-import org.jogamp.vecmath.Vector3f;
-import org.jogamp.vecmath.Vector4f;
-
-import com.jogamp.opengl.GL2ES2;
-import com.jogamp.opengl.GL2ES3;
-import com.jogamp.opengl.GLContext;
-
-
-public class Jogl2es2Context extends JoglContext
-{
-
- public Jogl2es2Context(GLContext context)
- {
- super(context);
- }
-
- public GL2ES2 gl2es2()
- {
- return context.getGL().getGL2ES2();
- }
-
- public GL2ES3 gl2es3()
- {
- return context.getGL().getGL2ES3();
- }
-
- public JoglShaderObject shaderProgram;
- public int shaderProgramId = -1;
- public ProgramData programData;
-
- @Override
- void setShaderProgram(JoglShaderObject object)
- {
- super.setShaderProgram(object);
- shaderProgram = object;
- shaderProgramId = object == null ? -1 : object.getValue();
- programData = allProgramData.get(shaderProgramId);
- if (programData == null)
- {
- programData = new ProgramData();
- allProgramData.put(shaderProgramId, programData);
- }
-
- }
-
- // all buffers created are recorded for each render pass, and for cleanup
- public ArrayList<GeometryArrayRetained> geoToClearBuffers = new ArrayList<GeometryArrayRetained>();
-
- public SparseArray<GeometryData> allGeometryData = new SparseArray<GeometryData>();
-
- public static class GeometryData
- {
- public int nativeId = -1;
-
- public int geoToIndBuf = -1;
- public int geoToIndBufSize = -1;
- public int[] geoToIndStripBuf = null;
- //public int geoToIndStripSwappedSize = -1;// removed into j3dnotristrips
- public int geoToCoordBuf = -1;
- public int geoToCoordBuf1 = -2;// double buffered for updates
- public int geoToCoordBuf2 = -3;
- public int geoToCoordBuf3 = -3;
- public int geoToCoordBufSize = -1;
- public int geoToColorBuf = -1;
- public int geoToNormalBuf = -1;
- public SparseArray<Integer> geoToTexCoordsBuf = new SparseArray<Integer>();
- public SparseArray<Integer> geoToVertAttribBuf = new SparseArray<Integer>();
-
- //Every thing below relates to interleaved data
- public int coordBufId = -1; // if separate
- public int interleavedBufId = -1;
- public int interleavedStride = 0;
- public int geoToCoordOffset = -1;
- public int geoToColorsOffset = -1;
- public int geoToNormalsOffset = -1;
- public int[] geoToVattrOffset = new int[10];
- public int[] geoToTexCoordOffset = new int[10];
-
- // vertex array object id for this geom
- public int vaoId = -1;
-
- //used to identify each geometry as we see it
- private static int nextNativeId = 0;
-
- public GeometryData()
- {
- nativeId = nextNativeId++;
- nextNativeId = nextNativeId > Integer.MAX_VALUE - 10 ? 0 : nextNativeId;// desperate loop
- }
-
- }
-
- public SparseArray<ProgramData> allProgramData = new SparseArray<ProgramData>();
-
- public static class ProgramData
- {
- public HashMap<String, Integer> progToGenVertAttNameToGenVertAttIndex = new HashMap<String, Integer>();
- public LocationData programToLocationData = null;// null to indicate need to load
- public ByteBuffer programToUBOBB = null;
- public int programToUBOBuf = -1;
- }
-
- public fogData fogData = new fogData();
-
- public glFrontMaterial materialData = new glFrontMaterial();
- // should use getMaximumLights() in pipeline? though it's up to the shader
- public static int MAX_LIGHTS = 32;
- public int maxLights;
- public int numberOfLights;
- public glLightSource[] glLightSource = new glLightSource[MAX_LIGHTS];
-
- //See here http://download.java.net/media/java3d/javadoc/1.3.2/javax/media/j3d/RenderingAttributes.html
- // For coloring implementation details
-
- //only for no lighting, materialDiffuse or vertex colors otherwise
- public Vector4f objectColor = new Vector4f();
-
- public float pointSize = 0;
-
- public int polygonMode = PolygonAttributes.POLYGON_FILL;
-
- public static class RenderingData
- {
- public boolean alphaTestEnabled = false;
- public int alphaTestFunction = RenderingAttributes.ALWAYS;
- public float alphaTestValue = 0;
- public int ignoreVertexColors; //-1 is not set 1,0 bool
- }
-
- public RenderingData renderingData = new RenderingData();
-
- public Vector4f currentAmbientColor = new Vector4f();
-
- public Matrix4d textureTransform = new Matrix4d();
-
- //various ffp matrixes
- public Matrix4d currentModelMat = new Matrix4d();
- public Matrix4d currentViewMat = new Matrix4d();
- public Matrix4d currentModelViewMat = new Matrix4d();
- public Matrix4d currentModelViewMatInverse = new Matrix4d();
- public Matrix4d currentModelViewProjMat = new Matrix4d();
- public Matrix3d currentNormalMat = new Matrix3d();
- public Matrix4d currentProjMat = new Matrix4d();
- public Matrix4d currentProjMatInverse = new Matrix4d();
-
- /**
- * On shader creation the various FFP locations are discovered and recorded for use later
- * @author phil
- *
- */
- public static class LocationData
- {
- //normal uniform data
- public int glProjectionMatrix = -1;
- public int glProjectionMatrixInverse = -1;
- public int glModelMatrix = -1;
- public int glViewMatrix = -1;
- public int glModelViewMatrix = -1;
- public int glModelViewMatrixInverse = -1;
- public int glModelViewProjectionMatrix = -1;
- public int glNormalMatrix = -1;
- public int ignoreVertexColors = -1;
- public int glLightModelambient = -1;
- public int objectColor = -1;
- public int alphaTestEnabled = -1;
- public int alphaTestFunction = -1;
- public int alphaTestValue = -1;
- public int textureTransform = -1;
-
- public fogDataLocs fogData = new fogDataLocs();
-
- public glFrontMaterialLocs glFrontMaterial = new glFrontMaterialLocs();
- public int numberOfLights = -1;
- public glLightSourceLocs[] glLightSource = new glLightSourceLocs[MAX_LIGHTS];
-
- public int glVertex = -1;
- public int glColor = -1;
- public int glNormal = -1;
-
- public int[] glMultiTexCoord = new int[16];
- public SparseArray<Integer> genAttIndexToLoc = new SparseArray<Integer>();
-
- }
-
- /**
- * below here are openGL state tracking to reduce unnecessary native calls
- * Note this is NOT like the "new" or so called current staet above taht needs to be st in the FFP
- * call, this is the old or previously set data, that might not need to be updated
- * @author phil
- *
- */
- public static class GL_State
- {
- public boolean depthBufferEnableOverride;
- public boolean depthBufferEnable;
- public int depthTestFunction;
- public boolean depthBufferWriteEnableOverride;
- public boolean depthBufferWriteEnable;
- public boolean userStencilAvailable;
- public boolean stencilEnable;
- public boolean glDepthMask;
- public boolean glEnableGL_STENCIL_TEST;
- public int stencilFailOp;
- public int stencilZFailOp;
- public int stencilZPassOp;
- public int stencilFunction;
- public int stencilReferenceValue;
- public int stencilCompareMask;
- public int stencilWriteMask;
- public int[] setGLSLUniform1i = new int[500];
- public int[] clearer1 = new int[500];
- public float[] setGLSLUniform1f = new float[500];
- public float[] clearer2 = new float[500];
- public boolean glEnableGL_BLEND;
- public int srcBlendFunction;
- public int dstBlendFunction;
- public int glActiveTexture;
- public int currentProgramId;
- public int[] glBindTextureGL_TEXTURE_2D = new int[35000];// indexed based on current glActiveTexture
- public int[] clearer3 = new int[35000];
- public int cullFace;
- public float polygonOffsetFactor;
- public float polygonOffset;
-
- public int ignoreVertexColors; //-1 indicates not set yet, always set
- public Vector4f glLightModelambient = new Vector4f();
- public Vector4f objectColor = new Vector4f();
- public Matrix4d textureTransform = new Matrix4d();
- public Matrix4d modelMatrix = new Matrix4d();
- public Matrix4d glModelViewMatrix = new Matrix4d();
- public Matrix4d glModelViewMatrixInverse = new Matrix4d();
- public Matrix4d glModelViewProjectionMatrix = new Matrix4d();
- public Matrix3d glNormalMatrix = new Matrix3d();
- public boolean alphaTestEnabled = false;
- public int alphaTestFunction;
- public float alphaTestValue;
-
- public fogData fogData = new fogData();
- public glFrontMaterial glFrontMaterial = new glFrontMaterial();
- public int numberOfLights = -1;
- public glLightSource[] glLightSource = new glLightSource[MAX_LIGHTS];
-
- public void clear()
- {
- depthBufferEnableOverride = false;
- depthBufferEnable = false;
- depthTestFunction = -1;
- depthBufferWriteEnableOverride = false;
- depthBufferWriteEnable = false;
- userStencilAvailable = false;
- stencilEnable = false;
- glDepthMask = false;
- glEnableGL_STENCIL_TEST = false;
- stencilFailOp = -1;
- stencilZFailOp = -1;
- stencilZPassOp = -1;
- stencilFunction = -1;
- stencilReferenceValue = -1;
- stencilCompareMask = -1;
- stencilWriteMask = -1;
- System.arraycopy(clearer1, 0, setGLSLUniform1i, 0, setGLSLUniform1i.length);
- System.arraycopy(clearer2, 0, setGLSLUniform1f, 0, setGLSLUniform1f.length);
- glEnableGL_BLEND = false;
- srcBlendFunction = -1;
- dstBlendFunction = -1;
- glActiveTexture = -1;
- currentProgramId = -1;
- System.arraycopy(clearer3, 0, glBindTextureGL_TEXTURE_2D, 0, glBindTextureGL_TEXTURE_2D.length);
- cullFace = -1;
- polygonOffsetFactor = -1;
- polygonOffset = -1;
- ignoreVertexColors = -1;
- glLightModelambient.set(-999f, -999f, -999f, -999f);
- objectColor.set(-999f, -999f, -999f, -999f);
- textureTransform.setIdentity();
- modelMatrix.setIdentity();
- glModelViewMatrix.setIdentity();
- glModelViewMatrixInverse.setIdentity();
- glModelViewProjectionMatrix.setIdentity();
- glNormalMatrix.setIdentity();
- alphaTestEnabled = false;
- alphaTestFunction = -1;
- alphaTestValue = -99f;
-
- fogData.clear();
- glFrontMaterial.clear();
- for (int i = 0; i < MAX_LIGHTS; i++)
- {
- if (glLightSource[i] != null)
- glLightSource[i].clear();
- }
- }
- }
-
- public GL_State gl_state = new GL_State();
-
- /**
- * program used on last run through of FFP, so nearly like gl_state above, just a desperate attempt
- * to see if the uniform locations have changed even if a new shader is being used
- */
- public int prevShaderProgram;
-
- // The per frame stats
- public Jogl2es2PerFrameStats perFrameStats = new Jogl2es2PerFrameStats();
-
- private int statsFrame = 0;
- private int STATS_OUTPUT_FRAME_FREQ = 50;
-
- public void outputPerFrameData()
- {
- statsFrame++;
- if (statsFrame % STATS_OUTPUT_FRAME_FREQ == 0)
- {
- statsFrame = 0;
- System.out.println("======================================================");
- perFrameStats.outputPerFrameData();
- }
- // clear for next frame
- perFrameStats = new Jogl2es2PerFrameStats();
- perFrameStats.endOfPrevFrameTime = System.nanoTime();
- }
-
- // texture and raster fill variables
-
- // raster vao and buf are not in the by geom bucket because I don't get given geom
- // background has to be created and destroyed
-
- public int simpleTextureShaderProgramId = -1;
- public int simpleTextureShaderProgramVertLoc = -1;
- public int simpleTextureShaderProgramTexCoordLoc = -1;
- public int simpleTextureShaderProgramBaseMapLoc = -1;
-
- // just a singleton of the handy matrix/array operations
- public Jogl2es2MatrixUtil matrixUtil = new Jogl2es2MatrixUtil();
-
- /////////////////////////////////////S H A D E R S T R U C T S /////////////////////////////////////////////////////
-
- // in the shader as follows
- // struct material
- // {
- // int lightEnabled;
- // vec4 ambient;
- // vec4 diffuse;
- // vec4 emission;
- // vec3 specular;
- // float shininess;
- // };
- // uniform material glFrontMaterial;
- public static class glFrontMaterial
- {
- public int lightEnabled = -1;
- public Vector4f ambient = new Vector4f();
- public Vector4f diffuse = new Vector4f();
- public Vector3f emission = new Vector3f();
- public Vector3f specular = new Vector3f();
- public float shininess;
-
- public void clear()
- {
- lightEnabled = -1;
- ambient.set(-999f, -999f, -999f, -999f);
- diffuse.set(-999f, -999f, -999f, -999f);
- emission.set(-999f, -999f, -999f);
- specular.set(-999f, -999f, -999f);
- shininess = -99;
- }
-
- public void set(glFrontMaterial ogfm)
- {
- lightEnabled = ogfm.lightEnabled;
- ambient.set(ogfm.ambient);
- diffuse.set(ogfm.diffuse);
- emission.set(ogfm.emission);
- specular.set(ogfm.specular);
- shininess = ogfm.shininess;
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (o instanceof glFrontMaterial)
- {
- glFrontMaterial ogfm = (glFrontMaterial) o;
- return ogfm.lightEnabled == lightEnabled && ogfm.ambient.equals(ambient) && ogfm.diffuse.equals(diffuse)
- && ogfm.emission.equals(emission) && ogfm.specular.equals(specular) && ogfm.shininess == shininess;
- }
- else
- {
- return false;
- }
- }
- }
-
- public static class glFrontMaterialLocs
- {
- public int lightEnabled = -1;
- public int ambient = -1;
- public int diffuse = -1;
- public int emission = -1;
- public int specular = -1;
- public int shininess = -1;
-
- public boolean present()
- {
- return lightEnabled != -1 || ambient != -1 || diffuse != -1 || emission != -1 || specular != -1 || shininess != -1;
- }
- }
- // struct lightSource
- // {
- // vec4 position;
- // vec4 diffuse;
- // vec4 specular;
- // float constantAttenuation, linearAttenuation, quadraticAttenuation;
- // float spotCutoff, spotExponent;
- // vec3 spotDirection;
- // };
- //
- // uniform int numberOfLights;
- // const int maxLights = 2;
- // uniform lightSource glLightSource[maxLights];
-
- //see https://en.wikibooks.org/wiki/GLSL_Programming/GLUT/Multiple_Lights
- public static class glLightSource
- {
- public int enabled = -1;
- public int prevLightSlot = -1;
- public Vector4f position = new Vector4f();
- //public Vector4f ambient = new Vector4f();//removed as an oddity
- public Vector4f diffuse = new Vector4f();
- public Vector4f specular = new Vector4f();
- public float constantAttenuation;
- public float linearAttenuation;
- public float quadraticAttenuation;
- public float spotCutoff;
- public float spotExponent;
- public Vector3f spotDirection = new Vector3f();
-
- public void clear()
- {
- enabled = -1;
- prevLightSlot = -1;
- position.set(-999f, -999f, -999f, -999f);
- diffuse.set(-999f, -999f, -999f, -999f);
- specular.set(-999f, -999f, -999f, -999f);
- constantAttenuation = -99;
- linearAttenuation = -99;
- quadraticAttenuation = -99;
- spotCutoff = -99;
- spotExponent = -99;
- spotDirection.set(-999f, -999f, -999f);
- }
-
- public void set(glLightSource ogfm)
- {
- enabled = ogfm.enabled;
- prevLightSlot = ogfm.prevLightSlot;
- position.set(ogfm.position);
- diffuse.set(ogfm.diffuse);
- specular.set(ogfm.specular);
- constantAttenuation = ogfm.constantAttenuation;
- linearAttenuation = ogfm.linearAttenuation;
- quadraticAttenuation = ogfm.quadraticAttenuation;
- spotCutoff = ogfm.spotCutoff;
- spotExponent = ogfm.spotExponent;
- spotDirection.set(ogfm.spotDirection);
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (o instanceof glLightSource)
- {
- glLightSource ogfm = (glLightSource) o;
- return enabled == ogfm.enabled && prevLightSlot == ogfm.prevLightSlot && ogfm.position.equals(position)
- && ogfm.diffuse.equals(diffuse) && ogfm.specular.equals(specular) && ogfm.constantAttenuation == constantAttenuation
- && ogfm.linearAttenuation == linearAttenuation && ogfm.quadraticAttenuation == quadraticAttenuation
- && ogfm.spotCutoff == spotCutoff && ogfm.spotExponent == spotExponent && ogfm.spotDirection.equals(spotDirection);
- }
- else
- {
- return false;
- }
- }
- }
-
- public static class glLightSourceLocs
- {
- public int position = -1;
- public int diffuse = -1;
- public int specular = -1;
- public int constantAttenuation = -1;
- public int linearAttenuation = -1;
- public int quadraticAttenuation = -1;
- public int spotCutoff = -1;
- public int spotExponent = -1;
- public int spotDirection = -1;
-
- public boolean present()
- {
- return position != -1 || diffuse != -1 || specular != -1 || constantAttenuation != -1 || linearAttenuation != -1
- || quadraticAttenuation != -1 || spotCutoff != -1 || spotExponent != -1 || spotDirection != -1;
- }
- }
-
- // in the shader as follows
- // struct fogDataStruct
- // {
- // int fogEnabled;
- // vec4 expColor;
- // float expDensity;
- // vec4 linearColor;
- // float linearStart;
- // float linearEnd;
- // };
- // uniform fogDataStruct fogData;
- public static class fogData
- {
- public int fogEnabled = -1;
- public Vector3f expColor = new Vector3f();
- public float expDensity;
- public Vector3f linearColor = new Vector3f();
- public float linearStart;
- public float linearEnd;
-
- public void clear()
- {
- fogEnabled = -1;
- expColor.set(-999f, -999f, -999f);
- expDensity = -99;
- linearColor.set(-999f, -999f, -999f);
- linearStart = -99;
- linearEnd = -99;
- }
-
- public void set(fogData ogfm)
- {
- fogEnabled = ogfm.fogEnabled;
- expColor.set(ogfm.expColor);
- expDensity = ogfm.expDensity;
- linearColor.set(ogfm.linearColor);
- linearStart = ogfm.linearStart;
- linearEnd = ogfm.linearEnd;
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (o instanceof fogData)
- {
- fogData ogfm = (fogData) o;
- return ogfm.fogEnabled == fogEnabled && ogfm.expColor.equals(expColor) && ogfm.expDensity == expDensity
- && ogfm.linearColor.equals(linearColor) && ogfm.linearStart == linearStart && ogfm.linearEnd == linearEnd;
- }
- else
- {
- return false;
- }
- }
- }
-
- public static class fogDataLocs
- {
- public int fogEnabled = -1;
- public int expColor = -1;
- public int expDensity = -1;
- public int linearColor = -1;
- public int linearStart = -1;
- public int linearEnd = -1;
-
- public boolean present()
- {
- return fogEnabled != -1 || expColor != -1 || expDensity != -1 || linearColor != -1 || linearStart != -1 || linearEnd != -1;
- }
- }
-}
+/* Copyright (c) 2016 JogAmp Community. 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.
+ *
+ */
+
+package org.jogamp.java3d;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.jogamp.vecmath.Matrix3d;
+import org.jogamp.vecmath.Matrix4d;
+import org.jogamp.vecmath.Vector3f;
+import org.jogamp.vecmath.Vector4f;
+
+import com.jogamp.opengl.GL2ES2;
+import com.jogamp.opengl.GL2ES3;
+import com.jogamp.opengl.GLContext;
+
+
+public class Jogl2es2Context extends JoglContext
+{
+
+ public Jogl2es2Context(GLContext context)
+ {
+ super(context);
+ }
+
+ public GL2ES2 gl2es2()
+ {
+ return context.getGL().getGL2ES2();
+ }
+
+ public GL2ES3 gl2es3()
+ {
+ return context.getGL().getGL2ES3();
+ }
+
+ public JoglShaderObject shaderProgram;
+ public int shaderProgramId = -1;
+ public ProgramData programData;
+
+ @Override
+ void setShaderProgram(JoglShaderObject object)
+ {
+ super.setShaderProgram(object);
+ shaderProgram = object;
+ shaderProgramId = object == null ? -1 : object.getValue();
+ programData = allProgramData.get(shaderProgramId);
+ if (programData == null)
+ {
+ programData = new ProgramData();
+ allProgramData.put(shaderProgramId, programData);
+ }
+
+ }
+
+ // all buffers created are recorded for each render pass, and for cleanup
+ public ArrayList<GeometryArrayRetained> geoToClearBuffers = new ArrayList<GeometryArrayRetained>();
+
+ public SparseArray<GeometryData> allGeometryData = new SparseArray<GeometryData>();
+
+ public static class GeometryData
+ {
+ public int nativeId = -1;
+
+ public int geoToIndBuf = -1;
+ public int geoToIndBufSize = -1;
+ public int[] geoToIndStripBuf = null;
+ //public int geoToIndStripSwappedSize = -1;// removed into j3dnotristrips
+ public int geoToCoordBuf = -1;
+ public int geoToCoordBuf1 = -2;// double buffered for updates
+ public int geoToCoordBuf2 = -3;
+ public int geoToCoordBuf3 = -3;
+ public int geoToCoordBufSize = -1;
+ public int geoToColorBuf = -1;
+ public int geoToNormalBuf = -1;
+ public SparseArray<Integer> geoToTexCoordsBuf = new SparseArray<Integer>();
+ public SparseArray<Integer> geoToVertAttribBuf = new SparseArray<Integer>();
+
+ //Every thing below relates to interleaved data
+ public int coordBufId = -1; // if separate
+ public int interleavedBufId = -1;
+ public int interleavedStride = 0;
+ public int geoToCoordOffset = -1;
+ public int geoToColorsOffset = -1;
+ public int geoToNormalsOffset = -1;
+ public int[] geoToVattrOffset = new int[10];
+ public int[] geoToTexCoordOffset = new int[10];
+
+ // vertex array object id for this geom
+ public int vaoId = -1;
+
+ //used to identify each geometry as we see it
+ private static int nextNativeId = 0;
+
+ public GeometryData()
+ {
+ nativeId = nextNativeId++;
+ nextNativeId = nextNativeId > Integer.MAX_VALUE - 10 ? 0 : nextNativeId;// desperate loop
+ }
+
+ }
+
+ public SparseArray<ProgramData> allProgramData = new SparseArray<ProgramData>();
+
+ public static class ProgramData
+ {
+ public HashMap<String, Integer> progToGenVertAttNameToGenVertAttIndex = new HashMap<String, Integer>();
+ public LocationData programToLocationData = null;// null to indicate need to load
+ public ByteBuffer programToUBOBB = null;
+ public int programToUBOBuf = -1;
+ }
+
+ public fogData fogData = new fogData();
+
+ public glFrontMaterial materialData = new glFrontMaterial();
+ // should use getMaximumLights() in pipeline? though it's up to the shader
+ public static int MAX_LIGHTS = 32;
+ public int maxLights;
+ public int numberOfLights;
+ public glLightSource[] glLightSource = new glLightSource[MAX_LIGHTS];
+
+ //See here http://download.java.net/media/java3d/javadoc/1.3.2/javax/media/j3d/RenderingAttributes.html
+ // For coloring implementation details
+
+ //only for no lighting, materialDiffuse or vertex colors otherwise
+ public Vector4f objectColor = new Vector4f();
+
+ public float pointSize = 0;
+
+ public int polygonMode = PolygonAttributes.POLYGON_FILL;
+
+ public static class RenderingData
+ {
+ public boolean alphaTestEnabled = false;
+ public int alphaTestFunction = RenderingAttributes.ALWAYS;
+ public float alphaTestValue = 0;
+ public int ignoreVertexColors; //-1 is not set 1,0 bool
+ }
+
+ public RenderingData renderingData = new RenderingData();
+
+ public float transparencyAlpha = 0;
+
+ public Vector4f currentAmbientColor = new Vector4f();
+
+ public Matrix4d textureTransform = new Matrix4d();
+
+ //various ffp matrixes
+ public Matrix4d currentModelMat = new Matrix4d();
+ public Matrix4d currentViewMat = new Matrix4d();
+ public Matrix4d currentModelViewMat = new Matrix4d();
+ public Matrix4d currentModelViewMatInverse = new Matrix4d();
+ public Matrix4d currentModelViewProjMat = new Matrix4d();
+ public Matrix3d currentNormalMat = new Matrix3d();
+ public Matrix4d currentProjMat = new Matrix4d();
+ public Matrix4d currentProjMatInverse = new Matrix4d();
+
+ /**
+ * On shader creation the various FFP locations are discovered and recorded for use later
+ * @author phil
+ *
+ */
+ public static class LocationData
+ {
+ //normal uniform data
+ public int glProjectionMatrix = -1;
+ public int glProjectionMatrixInverse = -1;
+ public int glModelMatrix = -1;
+ public int glViewMatrix = -1;
+ public int glModelViewMatrix = -1;
+ public int glModelViewMatrixInverse = -1;
+ public int glModelViewProjectionMatrix = -1;
+ public int glNormalMatrix = -1;
+ public int ignoreVertexColors = -1;
+ public int glLightModelambient = -1;
+ public int objectColor = -1;
+ public int transparencyAlpha = -1;
+ public int alphaTestEnabled = -1;
+ public int alphaTestFunction = -1;
+ public int alphaTestValue = -1;
+ public int textureTransform = -1;
+
+ public fogDataLocs fogData = new fogDataLocs();
+
+ public glFrontMaterialLocs glFrontMaterial = new glFrontMaterialLocs();
+ public int numberOfLights = -1;
+ public glLightSourceLocs[] glLightSource = new glLightSourceLocs[MAX_LIGHTS];
+
+ public int glVertex = -1;
+ public int glColor = -1;
+ public int glNormal = -1;
+
+ public int[] glMultiTexCoord = new int[16];
+ public SparseArray<Integer> genAttIndexToLoc = new SparseArray<Integer>();
+
+ }
+
+ /**
+ * below here are openGL state tracking to reduce unnecessary native calls
+ * Note this is NOT like the "new" or so called current staet above taht needs to be st in the FFP
+ * call, this is the old or previously set data, that might not need to be updated
+ * @author phil
+ *
+ */
+ public static class GL_State
+ {
+ public boolean depthBufferEnableOverride;
+ public boolean depthBufferEnable;
+ public int depthTestFunction;
+ public boolean depthBufferWriteEnableOverride;
+ public boolean depthBufferWriteEnable;
+ public boolean userStencilAvailable;
+ public boolean stencilEnable;
+ public boolean glDepthMask;
+ public boolean glEnableGL_STENCIL_TEST;
+ public int stencilFailOp;
+ public int stencilZFailOp;
+ public int stencilZPassOp;
+ public int stencilFunction;
+ public int stencilReferenceValue;
+ public int stencilCompareMask;
+ public int stencilWriteMask;
+ public int[] setGLSLUniform1i = new int[500];
+ public int[] clearer1 = new int[500];
+ public float[] setGLSLUniform1f = new float[500];
+ public float[] clearer2 = new float[500];
+ public boolean glEnableGL_BLEND;
+ public int srcBlendFunction;
+ public int dstBlendFunction;
+ public int glActiveTexture;
+ public int currentProgramId;
+ public int[] glBindTextureGL_TEXTURE_2D = new int[35000];// indexed based on current glActiveTexture
+ public int[] clearer3 = new int[35000];
+ public int cullFace;
+ public float polygonOffsetFactor;
+ public float polygonOffset;
+
+ public int ignoreVertexColors; //-1 indicates not set yet, always set
+ public Vector4f glLightModelambient = new Vector4f();
+ public Vector4f objectColor = new Vector4f();
+ public float transparencyAlpha;
+ public Matrix4d textureTransform = new Matrix4d();
+ public Matrix4d modelMatrix = new Matrix4d();
+ public Matrix4d glModelViewMatrix = new Matrix4d();
+ public Matrix4d glModelViewMatrixInverse = new Matrix4d();
+ public Matrix4d glModelViewProjectionMatrix = new Matrix4d();
+ public Matrix3d glNormalMatrix = new Matrix3d();
+ public boolean alphaTestEnabled = false;
+ public int alphaTestFunction;
+ public float alphaTestValue;
+
+ public fogData fogData = new fogData();
+ public glFrontMaterial glFrontMaterial = new glFrontMaterial();
+ public int numberOfLights = -1;
+ public glLightSource[] glLightSource = new glLightSource[MAX_LIGHTS];
+
+ public void clear()
+ {
+ depthBufferEnableOverride = false;
+ depthBufferEnable = false;
+ depthTestFunction = -1;
+ depthBufferWriteEnableOverride = false;
+ depthBufferWriteEnable = false;
+ userStencilAvailable = false;
+ stencilEnable = false;
+ glDepthMask = false;
+ glEnableGL_STENCIL_TEST = false;
+ stencilFailOp = -1;
+ stencilZFailOp = -1;
+ stencilZPassOp = -1;
+ stencilFunction = -1;
+ stencilReferenceValue = -1;
+ stencilCompareMask = -1;
+ stencilWriteMask = -1;
+ System.arraycopy(clearer1, 0, setGLSLUniform1i, 0, setGLSLUniform1i.length);
+ System.arraycopy(clearer2, 0, setGLSLUniform1f, 0, setGLSLUniform1f.length);
+ glEnableGL_BLEND = false;
+ srcBlendFunction = -1;
+ dstBlendFunction = -1;
+ glActiveTexture = -1;
+ currentProgramId = -1;
+ System.arraycopy(clearer3, 0, glBindTextureGL_TEXTURE_2D, 0, glBindTextureGL_TEXTURE_2D.length);
+ cullFace = -1;
+ polygonOffsetFactor = -1;
+ polygonOffset = -1;
+ ignoreVertexColors = -1;
+ glLightModelambient.set(-999f, -999f, -999f, -999f);
+ objectColor.set(-999f, -999f, -999f, -999f);
+ transparencyAlpha = -1;
+ textureTransform.setIdentity();
+ modelMatrix.setIdentity();
+ glModelViewMatrix.setIdentity();
+ glModelViewMatrixInverse.setIdentity();
+ glModelViewProjectionMatrix.setIdentity();
+ glNormalMatrix.setIdentity();
+ alphaTestEnabled = false;
+ alphaTestFunction = -1;
+ alphaTestValue = -99f;
+
+ fogData.clear();
+ glFrontMaterial.clear();
+ for (int i = 0; i < MAX_LIGHTS; i++)
+ {
+ if (glLightSource[i] != null)
+ glLightSource[i].clear();
+ }
+ }
+ }
+
+ public GL_State gl_state = new GL_State();
+
+ /**
+ * program used on last run through of FFP, so nearly like gl_state above, just a desperate attempt
+ * to see if the uniform locations have changed even if a new shader is being used
+ */
+ public int prevShaderProgram;
+
+ // The per frame stats
+ public Jogl2es2PerFrameStats perFrameStats = new Jogl2es2PerFrameStats();
+
+ private int statsFrame = 0;
+ private int STATS_OUTPUT_FRAME_FREQ = 50;
+
+ public void outputPerFrameData()
+ {
+ statsFrame++;
+ if (statsFrame % STATS_OUTPUT_FRAME_FREQ == 0)
+ {
+ statsFrame = 0;
+ System.out.println("======================================================");
+ perFrameStats.outputPerFrameData();
+ }
+ // clear for next frame
+ perFrameStats = new Jogl2es2PerFrameStats();
+ perFrameStats.endOfPrevFrameTime = System.nanoTime();
+ }
+
+ // texture and raster fill variables
+
+ // raster vao and buf are not in the by geom bucket because I don't get given geom
+ // background has to be created and destroyed
+
+ public int simpleTextureShaderProgramId = -1;
+ public int simpleTextureShaderProgramVertLoc = -1;
+ public int simpleTextureShaderProgramTexCoordLoc = -1;
+ public int simpleTextureShaderProgramBaseMapLoc = -1;
+
+ // just a singleton of the handy matrix/array operations
+ public Jogl2es2MatrixUtil matrixUtil = new Jogl2es2MatrixUtil();
+
+ /////////////////////////////////////S H A D E R S T R U C T S /////////////////////////////////////////////////////
+
+ // in the shader as follows
+ // struct material
+ // {
+ // int lightEnabled;
+ // vec4 ambient;
+ // vec4 diffuse;
+ // vec4 emission;
+ // vec3 specular;
+ // float shininess;
+ // };
+ // uniform material glFrontMaterial;
+ public static class glFrontMaterial
+ {
+ public int lightEnabled = -1;
+ public Vector4f ambient = new Vector4f();
+ public Vector4f diffuse = new Vector4f();
+ public Vector3f emission = new Vector3f();
+ public Vector3f specular = new Vector3f();
+ public float shininess;
+
+ public void clear()
+ {
+ lightEnabled = -1;
+ ambient.set(-999f, -999f, -999f, -999f);
+ diffuse.set(-999f, -999f, -999f, -999f);
+ emission.set(-999f, -999f, -999f);
+ specular.set(-999f, -999f, -999f);
+ shininess = -99;
+ }
+
+ public void set(glFrontMaterial ogfm)
+ {
+ lightEnabled = ogfm.lightEnabled;
+ ambient.set(ogfm.ambient);
+ diffuse.set(ogfm.diffuse);
+ emission.set(ogfm.emission);
+ specular.set(ogfm.specular);
+ shininess = ogfm.shininess;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o instanceof glFrontMaterial)
+ {
+ glFrontMaterial ogfm = (glFrontMaterial) o;
+ return ogfm.lightEnabled == lightEnabled && ogfm.ambient.equals(ambient) && ogfm.diffuse.equals(diffuse)
+ && ogfm.emission.equals(emission) && ogfm.specular.equals(specular) && ogfm.shininess == shininess;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ public static class glFrontMaterialLocs
+ {
+ public int lightEnabled = -1;
+ public int ambient = -1;
+ public int diffuse = -1;
+ public int emission = -1;
+ public int specular = -1;
+ public int shininess = -1;
+
+ public boolean present()
+ {
+ return lightEnabled != -1 || ambient != -1 || diffuse != -1 || emission != -1 || specular != -1 || shininess != -1;
+ }
+ }
+ // struct lightSource
+ // {
+ // vec4 position;
+ // vec4 diffuse;
+ // vec4 specular;
+ // float constantAttenuation, linearAttenuation, quadraticAttenuation;
+ // float spotCutoff, spotExponent;
+ // vec3 spotDirection;
+ // };
+ //
+ // uniform int numberOfLights;
+ // const int maxLights = 2;
+ // uniform lightSource glLightSource[maxLights];
+
+ //see https://en.wikibooks.org/wiki/GLSL_Programming/GLUT/Multiple_Lights
+ public static class glLightSource
+ {
+ public int enabled = -1;
+ public int prevLightSlot = -1;
+ public Vector4f position = new Vector4f();
+ //public Vector4f ambient = new Vector4f();//removed as an oddity
+ public Vector4f diffuse = new Vector4f();
+ public Vector4f specular = new Vector4f();
+ public float constantAttenuation;
+ public float linearAttenuation;
+ public float quadraticAttenuation;
+ public float spotCutoff;
+ public float spotExponent;
+ public Vector3f spotDirection = new Vector3f();
+
+ public void clear()
+ {
+ enabled = -1;
+ prevLightSlot = -1;
+ position.set(-999f, -999f, -999f, -999f);
+ diffuse.set(-999f, -999f, -999f, -999f);
+ specular.set(-999f, -999f, -999f, -999f);
+ constantAttenuation = -99;
+ linearAttenuation = -99;
+ quadraticAttenuation = -99;
+ spotCutoff = -99;
+ spotExponent = -99;
+ spotDirection.set(-999f, -999f, -999f);
+ }
+
+ public void set(glLightSource ogfm)
+ {
+ enabled = ogfm.enabled;
+ prevLightSlot = ogfm.prevLightSlot;
+ position.set(ogfm.position);
+ diffuse.set(ogfm.diffuse);
+ specular.set(ogfm.specular);
+ constantAttenuation = ogfm.constantAttenuation;
+ linearAttenuation = ogfm.linearAttenuation;
+ quadraticAttenuation = ogfm.quadraticAttenuation;
+ spotCutoff = ogfm.spotCutoff;
+ spotExponent = ogfm.spotExponent;
+ spotDirection.set(ogfm.spotDirection);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o instanceof glLightSource)
+ {
+ glLightSource ogfm = (glLightSource) o;
+ return enabled == ogfm.enabled && prevLightSlot == ogfm.prevLightSlot && ogfm.position.equals(position)
+ && ogfm.diffuse.equals(diffuse) && ogfm.specular.equals(specular) && ogfm.constantAttenuation == constantAttenuation
+ && ogfm.linearAttenuation == linearAttenuation && ogfm.quadraticAttenuation == quadraticAttenuation
+ && ogfm.spotCutoff == spotCutoff && ogfm.spotExponent == spotExponent && ogfm.spotDirection.equals(spotDirection);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ public static class glLightSourceLocs
+ {
+ public int position = -1;
+ public int diffuse = -1;
+ public int specular = -1;
+ public int constantAttenuation = -1;
+ public int linearAttenuation = -1;
+ public int quadraticAttenuation = -1;
+ public int spotCutoff = -1;
+ public int spotExponent = -1;
+ public int spotDirection = -1;
+
+ public boolean present()
+ {
+ return position != -1 || diffuse != -1 || specular != -1 || constantAttenuation != -1 || linearAttenuation != -1
+ || quadraticAttenuation != -1 || spotCutoff != -1 || spotExponent != -1 || spotDirection != -1;
+ }
+ }
+
+ // in the shader as follows
+ // struct fogDataStruct
+ // {
+ // int fogEnabled;
+ // vec4 expColor;
+ // float expDensity;
+ // vec4 linearColor;
+ // float linearStart;
+ // float linearEnd;
+ // };
+ // uniform fogDataStruct fogData;
+ public static class fogData
+ {
+ public int fogEnabled = -1;
+ public Vector3f expColor = new Vector3f();
+ public float expDensity;
+ public Vector3f linearColor = new Vector3f();
+ public float linearStart;
+ public float linearEnd;
+
+ public void clear()
+ {
+ fogEnabled = -1;
+ expColor.set(-999f, -999f, -999f);
+ expDensity = -99;
+ linearColor.set(-999f, -999f, -999f);
+ linearStart = -99;
+ linearEnd = -99;
+ }
+
+ public void set(fogData ogfm)
+ {
+ fogEnabled = ogfm.fogEnabled;
+ expColor.set(ogfm.expColor);
+ expDensity = ogfm.expDensity;
+ linearColor.set(ogfm.linearColor);
+ linearStart = ogfm.linearStart;
+ linearEnd = ogfm.linearEnd;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o instanceof fogData)
+ {
+ fogData ogfm = (fogData) o;
+ return ogfm.fogEnabled == fogEnabled && ogfm.expColor.equals(expColor) && ogfm.expDensity == expDensity
+ && ogfm.linearColor.equals(linearColor) && ogfm.linearStart == linearStart && ogfm.linearEnd == linearEnd;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ public static class fogDataLocs
+ {
+ public int fogEnabled = -1;
+ public int expColor = -1;
+ public int expDensity = -1;
+ public int linearColor = -1;
+ public int linearStart = -1;
+ public int linearEnd = -1;
+
+ public boolean present()
+ {
+ return fogEnabled != -1 || expColor != -1 || expDensity != -1 || linearColor != -1 || linearStart != -1 || linearEnd != -1;
+ }
+ }
+}
diff --git a/src/main/java/org/jogamp/java3d/Jogl2es2Pipeline.java b/src/main/java/org/jogamp/java3d/Jogl2es2Pipeline.java
index 3e2ba74..e3ed085 100644
--- a/src/main/java/org/jogamp/java3d/Jogl2es2Pipeline.java
+++ b/src/main/java/org/jogamp/java3d/Jogl2es2Pipeline.java
@@ -3149,6 +3149,20 @@ class Jogl2es2Pipeline extends Jogl2es2DEPPipeline
}
}
+ // always bind object color, the shader can decide to use it if it's no lighting and no vertex colors
+ if (locs.transparencyAlpha != -1)
+ {
+ if (!MINIMISE_NATIVE_CALLS_FFP
+ || (shaderProgramId != ctx.prevShaderProgram || ctx.gl_state.transparencyAlpha != ctx.transparencyAlpha))
+ {
+ gl.glUniform1f(locs.transparencyAlpha, ctx.transparencyAlpha);
+ if (DO_OUTPUT_ERRORS)
+ outputErrors(ctx);
+ if (MINIMISE_NATIVE_CALLS_FFP)
+ ctx.gl_state.transparencyAlpha = ctx.transparencyAlpha;
+ }
+ }
+
// count of enabled lights currentEnabledLights
if (locs.numberOfLights != -1)
{
@@ -3322,6 +3336,7 @@ class Jogl2es2Pipeline extends Jogl2es2DEPPipeline
locs.ignoreVertexColors = gl.glGetUniformLocation(shaderProgramId, "ignoreVertexColors");
locs.glLightModelambient = gl.glGetUniformLocation(shaderProgramId, "glLightModelambient");
locs.objectColor = gl.glGetUniformLocation(shaderProgramId, "objectColor");
+ locs.transparencyAlpha = gl.glGetUniformLocation(shaderProgramId, "transparencyAlpha");
locs.alphaTestEnabled = gl.glGetUniformLocation(shaderProgramId, "alphaTestEnabled");
locs.alphaTestFunction = gl.glGetUniformLocation(shaderProgramId, "alphaTestFunction");
locs.alphaTestValue = gl.glGetUniformLocation(shaderProgramId, "alphaTestValue");
@@ -5301,6 +5316,8 @@ class Jogl2es2Pipeline extends Jogl2es2DEPPipeline
GL2ES2 gl = ((Jogl2es2Context) ctx).gl2es2();
Jogl2es2Context joglesctx = ((Jogl2es2Context) ctx);
+ joglesctx.transparencyAlpha = alpha;
+
if ((transparencyMode < TransparencyAttributes.SCREEN_DOOR)
|| ((((geometryType & RenderMolecule.LINE) != 0) || (polygonMode == PolygonAttributes.POLYGON_LINE)) && lineAA)
|| ((((geometryType & RenderMolecule.POINT) != 0) || (polygonMode == PolygonAttributes.POLYGON_POINT)) && pointAA))
@@ -5348,6 +5365,9 @@ class Jogl2es2Pipeline extends Jogl2es2DEPPipeline
GL2ES2 gl = ((Jogl2es2Context) ctx).gl2es2();
Jogl2es2Context joglesctx = ((Jogl2es2Context) ctx);
+
+ joglesctx.transparencyAlpha = 1.0f;
+
if (((((geometryType & RenderMolecule.LINE) != 0) || (polygonMode == PolygonAttributes.POLYGON_LINE)) && lineAA)
|| ((((geometryType & RenderMolecule.POINT) != 0) || (polygonMode == PolygonAttributes.POLYGON_POINT)) && pointAA))
{
@@ -7901,7 +7921,61 @@ class Jogl2es2Pipeline extends Jogl2es2DEPPipeline
throw new AssertionError("illegal format " + format);
}
- gl.glReadPixels(0, 0, width, height, type, GL.GL_UNSIGNED_BYTE, ByteBuffer.wrap((byte[]) data));
+ ByteBuffer buf = null;
+ if (dataType == ImageComponentRetained.IMAGE_DATA_TYPE_BYTE_ARRAY)
+ {
+ buf = ByteBuffer.wrap((byte[]) data);
+ }
+ else
+ {
+ buf = (ByteBuffer) data;
+ }
+
+ gl.glReadPixels(0, 0, width, height, type, GL.GL_UNSIGNED_BYTE, buf);
+ if (DO_OUTPUT_ERRORS)
+ outputErrors(ctx);
+ }
+ else if ((dataType == ImageComponentRetained.IMAGE_DATA_TYPE_INT_ARRAY)
+ || (dataType == ImageComponentRetained.IMAGE_DATA_TYPE_INT_BUFFER))
+ {
+ switch (format)
+ {
+ // GL_BGR
+ case ImageComponentRetained.TYPE_INT_BGR:
+ type = GL2.GL_BGR;// not ok
+ break;
+ case ImageComponentRetained.TYPE_INT_RGB:
+ type = GL.GL_RGB;//ok
+ break;
+ case ImageComponentRetained.TYPE_INT_ARGB:
+ type = GL.GL_RGBA;// this a valid case for GL2ES2
+ break;
+
+ /*
+ * This method only supports 3 and 4 components formats and INT
+ * types.
+ */
+ case ImageComponentRetained.TYPE_BYTE_LA:
+ case ImageComponentRetained.TYPE_BYTE_GRAY:
+ case ImageComponentRetained.TYPE_USHORT_GRAY:
+ case ImageComponentRetained.TYPE_BYTE_BGR:
+ case ImageComponentRetained.TYPE_BYTE_RGB:
+ case ImageComponentRetained.TYPE_BYTE_RGBA:
+ default:
+ throw new AssertionError("illegal format " + format);
+ }
+
+ IntBuffer buf = null;
+ if (dataType == ImageComponentRetained.IMAGE_DATA_TYPE_INT_ARRAY)
+ {
+ buf = IntBuffer.wrap((int[]) data);
+ }
+ else
+ {
+ buf = (IntBuffer) data;
+ }
+
+ gl.glReadPixels(0, 0, width, height, type, GL.GL_UNSIGNED_BYTE, buf);
if (DO_OUTPUT_ERRORS)
outputErrors(ctx);
}
f='#n2154'>2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653
/*
 * 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.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.logging.Level;


/**
 * Abstract class that is used to define 2D or 3D ImageComponent classes
 * used in a Java 3D scene graph.
 * This is used for texture images, background images and raster components
 * of Shape3D nodes.
 */

abstract class ImageComponentRetained extends NodeComponentRetained {

    // change flag
    static final int IMAGE_CHANGED       = 0x01;
    static final int SUBIMAGE_CHANGED    = 0x02;

    static final int TYPE_BYTE_BGR     =  0x1;
    static final int TYPE_BYTE_RGB     =  0x2;
    static final int TYPE_BYTE_ABGR    =  0x4;
    static final int TYPE_BYTE_RGBA    =  0x8;
    static final int TYPE_BYTE_LA      =  0x10;
    static final int TYPE_BYTE_GRAY    =  0x20;
    static final int TYPE_USHORT_GRAY  =  0x40;
    static final int TYPE_INT_BGR      =  0x80;
    static final int TYPE_INT_RGB      =  0x100;
    static final int TYPE_INT_ARGB     =  0x200;

    static final int  IMAGE_SIZE_512X512 = 262144;

    enum ImageFormatType {
        TYPE_UNKNOWN,
        TYPE_BYTE_BGR,
        TYPE_BYTE_RGB,
        TYPE_BYTE_ABGR,
        TYPE_BYTE_RGBA,
        TYPE_BYTE_LA,
        TYPE_BYTE_GRAY,
        TYPE_USHORT_GRAY,
        TYPE_INT_BGR,
        TYPE_INT_RGB,
        TYPE_INT_ARGB
    }

    static final int IMAGE_DATA_TYPE_BYTE_ARRAY     =  0x1000;
    static final int IMAGE_DATA_TYPE_INT_ARRAY      =  0x2000;
    static final int IMAGE_DATA_TYPE_BYTE_BUFFER    =  0x4000;
    static final int IMAGE_DATA_TYPE_INT_BUFFER     =  0x8000;

    enum ImageDataType {
        TYPE_NULL,
        TYPE_BYTE_ARRAY,
        TYPE_INT_ARRAY,
        TYPE_BYTE_BUFFER,
        TYPE_INT_BUFFER
    }

    private int	apiFormat; // The format set by user.
    int		width;		// Width of PixelArray
    int		height;		// Height of PixelArray
    int         depth;          // Depth of PixelArray
    boolean     byReference = false;   // Is the imageComponent by reference
    boolean     yUp = false;
    boolean imageTypeIsSupported;
    boolean abgrSupported = true;
    boolean npotSupported = true;
    private int unitsPerPixel;
    private int numberOfComponents;

    // Note : This is unuse for NioImageBuffer.
    // The image type of the input image. Using the constant in BufferedImage
    private int imageType;

    private ImageFormatType imageFormatType = ImageFormatType.TYPE_UNKNOWN;
    ImageData imageData;
    private ImageComponent.ImageClass imageClass = ImageComponent.ImageClass.BUFFERED_IMAGE;

    // To support Non power of 2 (NPOT) image
    // if enforceNonPowerOfTwoSupport is true (for examples Raster and Background)
    // and imageData is a non power of 2 image
    // and graphics driver doesn't support NPOT extension.
    private ImageData imageDataPowerOfTwo;
    private AffineTransformOp powerOfTwoATOp;
    // The following flag means that if the image is non-power-of-two and the
    // card doesn't support NPOT texture, we will scale the image to a power
    // of two.
    private boolean enforceNonPowerOfTwoSupport = false;
    private boolean usedByOffScreenCanvas = false;

    // This will store the referenced Images for reference case.
    // private RenderedImage refImage[] = null;
    private Object refImage[] = null;

    // Issue 366: Lock for evaluateExtensions
    Object evaluateExtLock = new Object();

    // Lock used in the "by ref case"
    GeometryLock geomLock = new GeometryLock();

    int tilew = 0;
    int tileh = 0;
    int numXTiles = 0;
    int numYTiles = 0;

// lists of Node Components that are referencing this ImageComponent
// object. This list is used to notify the referencing node components
// of any changes of this ImageComponent.
private ArrayList<NodeComponentRetained> userList = new ArrayList<NodeComponentRetained>();

    /**
     * Retrieves the width of this image component object.
     * @return the width of this image component object
     */
    int getWidth() {
        return width;
    }

    /**
     * Retrieves the height of this image component object.
     * @return the height of this image component object
     */
    int getHeight() {
        return height;
    }

    /**
     * Retrieves the apiFormat of this image component object.
     *
     * @return the apiFormat of this image component object
     */
    int getFormat() {
        return apiFormat;
    }

    void setFormat(int format) {
        this.apiFormat = format;
    }

    void setByReference(boolean byReference) {
        this.byReference = byReference;
    }

    boolean isByReference() {
        return byReference;
    }

    void setYUp( boolean yUp) {
        this.yUp = yUp;
    }

    boolean isYUp() {
        return yUp;
    }

    int getUnitsPerPixel() {
        return unitsPerPixel;
    }

    void setUnitsPerPixel(int ipp) {
        unitsPerPixel = ipp;
    }

    ImageComponent.ImageClass getImageClass() {
        return imageClass;
    }

    void setImageClass(RenderedImage image) {
        if(image instanceof BufferedImage) {
            imageClass = ImageComponent.ImageClass.BUFFERED_IMAGE;
        } else  {
            imageClass = ImageComponent.ImageClass.RENDERED_IMAGE;
        }
    }

    void setImageClass(NioImageBuffer image) {
        imageClass = ImageComponent.ImageClass.NIO_IMAGE_BUFFER;
    }

    void setEnforceNonPowerOfTwoSupport(boolean npot) {
        this.enforceNonPowerOfTwoSupport = npot;
    }

    void setUsedByOffScreen(boolean used) {
        usedByOffScreenCanvas = used;
    }

    boolean getUsedByOffScreen() {
        return usedByOffScreenCanvas;
    }

    int getNumberOfComponents() {
        return numberOfComponents;
    }

    void setNumberOfComponents(int numberOfComponents) {
        this.numberOfComponents = numberOfComponents;
    }

    int getImageDataTypeIntValue() {
        int idtValue = -1;
        switch(imageData.imageDataType) {
            case TYPE_BYTE_ARRAY:
                idtValue = IMAGE_DATA_TYPE_BYTE_ARRAY;
                break;
            case TYPE_INT_ARRAY:
                idtValue = IMAGE_DATA_TYPE_INT_ARRAY;
                break;
            case TYPE_BYTE_BUFFER:
                idtValue = IMAGE_DATA_TYPE_BYTE_BUFFER;
                break;
            case TYPE_INT_BUFFER:
                idtValue = IMAGE_DATA_TYPE_INT_BUFFER;
                break;
            default :
                assert false;
        }
        return idtValue;

    }

    int getImageFormatTypeIntValue(boolean powerOfTwoData) {
        int iftValue = -1;
        switch(imageFormatType) {
            case TYPE_BYTE_BGR:
                iftValue = TYPE_BYTE_BGR;
                break;
            case TYPE_BYTE_RGB:
                iftValue = TYPE_BYTE_RGB;
                break;
            case TYPE_BYTE_ABGR:
                iftValue = TYPE_BYTE_ABGR;
                break;
            case TYPE_BYTE_RGBA:
                if((imageDataPowerOfTwo != null) && (powerOfTwoData)) {
                    iftValue = TYPE_BYTE_ABGR;
                }
                else {
                    iftValue = TYPE_BYTE_RGBA;
                }
                break;
            case TYPE_BYTE_LA:
                iftValue = TYPE_BYTE_LA;
                break;
            case TYPE_BYTE_GRAY:
                iftValue = TYPE_BYTE_GRAY;
                break;
            case TYPE_USHORT_GRAY:
                iftValue = TYPE_USHORT_GRAY;
                break;
            case TYPE_INT_BGR:
                iftValue = TYPE_INT_BGR;
                break;
            case TYPE_INT_RGB:
                iftValue = TYPE_INT_RGB;
                break;
            case TYPE_INT_ARGB:
                iftValue = TYPE_INT_ARGB;
                break;
            default:
                throw new AssertionError();
        }
        return iftValue;
    }

    // Note: This method for RenderedImage, can't be used by NioImageBuffer.
    int getImageType() {
        return imageType;
    }

    void setImageFormatType(ImageFormatType ift) {
        this.imageFormatType = ift;
    }

    ImageFormatType getImageFormatType() {
        return this.imageFormatType;
    }

    void setRefImage(Object image, int index) {
        this.refImage[index] = image;
    }

    Object getRefImage(int index) {
        return this.refImage[index];
    }

    ImageData getImageData(boolean npotSupportNeeded) {
        if(npotSupportNeeded) {
            assert enforceNonPowerOfTwoSupport;
            if(imageDataPowerOfTwo != null) {
                return imageDataPowerOfTwo;
            }
        }
        return imageData;
    }

    boolean useBilinearFilter() {
        if(imageDataPowerOfTwo != null) {
            return true;
        }

        return false;
    }

    boolean isImageTypeSupported() {
        return imageTypeIsSupported;
    }

    /**
     * Check if ImageComponent parameters have valid values.
     */
    void processParams(int format, int width, int height, int depth) {
        if (width < 1)
            throw new IllegalArgumentException(J3dI18N.getString("ImageComponentRetained0"));

        if (height < 1)
            throw new IllegalArgumentException(J3dI18N.getString("ImageComponentRetained1"));

        if (depth < 1)
            throw new IllegalArgumentException(J3dI18N.getString("ImageComponentRetained2"));

        // If the format is 8bit per component, we may send it down
        // to OpenGL directly if its by ref case
        switch (format) {
            case ImageComponent.FORMAT_RGB:// same as ImageComponent.FORMAT_RGB8
            case ImageComponent.FORMAT_RGB4:      // Need to be Deprecated
            case ImageComponent.FORMAT_RGB5:      // Need to be Deprecated
            case ImageComponent.FORMAT_R3_G3_B2:  // Need to be Deprecated
                numberOfComponents = 3;
                break;
            case ImageComponent.FORMAT_RGBA:// same as ImageComponent.FORMAT_RGBA8
            case ImageComponent.FORMAT_RGB5_A1:   // Need to be Deprecated
            case ImageComponent.FORMAT_RGBA4:     // Need to be Deprecated
                numberOfComponents = 4;
                break;
            case ImageComponent.FORMAT_LUM4_ALPHA4: // Need to be Deprecated
            case ImageComponent.FORMAT_LUM8_ALPHA8:
                numberOfComponents = 2;
                break;
            case ImageComponent.FORMAT_CHANNEL8:
                numberOfComponents = 1;
                break;
            default:
                throw new IllegalArgumentException(J3dI18N.getString("ImageComponentRetained3"));
        }

        this.setFormat(format);
        this.width = width;
        this.height = height;
        this.depth = depth;
        refImage = new Object[depth];
    }

    int evaluateImageType(RenderedImage ri) {
        int imageType = BufferedImage.TYPE_CUSTOM;

        if (ri instanceof BufferedImage) {
            imageType = ((BufferedImage)ri).getType();

            if(imageType != BufferedImage.TYPE_CUSTOM) {
                return imageType;
            }
        }
        else {
            // Fix to Issue 412. Force copy for RenderedImage of type not equal to BufferedImage.
            return imageType;
        }

        // System.err.println("This is a RenderedImage or BufferedImage with TYPE_CUSTOM. It imageType classification may not be correct.");
        ColorModel cm = ri.getColorModel();
        ColorSpace cs = cm.getColorSpace();
        SampleModel sm = ri.getSampleModel();

        int csType = cs.getType();
        boolean isAlphaPre = cm.isAlphaPremultiplied();


        if (csType == ColorSpace.TYPE_GRAY && cm instanceof ComponentColorModel) {
            if (sm.getDataType() == DataBuffer.TYPE_BYTE) {
                imageType = BufferedImage.TYPE_BYTE_GRAY;
            } else if (sm.getDataType() == DataBuffer.TYPE_USHORT) {
                imageType = BufferedImage.TYPE_USHORT_GRAY;
            }
        }

        // RGB , only interested in BYTE ABGR and BGR for now
        // all others will be copied to a buffered image
        else if(csType == ColorSpace.TYPE_RGB) {
            int comparedBit = 0;
            int smDataType = sm.getDataType();
            if(smDataType == DataBuffer.TYPE_BYTE) {
                comparedBit = 8;
            } else if(smDataType == DataBuffer.TYPE_INT) {
                comparedBit = 32;
            }

            if(comparedBit != 0) {
                int numBands = sm.getNumBands();
                if (cm instanceof ComponentColorModel &&
                        sm instanceof PixelInterleavedSampleModel) {
                    PixelInterleavedSampleModel csm =
                            (PixelInterleavedSampleModel) sm;
                    int[] offs = csm.getBandOffsets();
                    ComponentColorModel ccm = (ComponentColorModel)cm;
                    int[] nBits = ccm.getComponentSize();
                    boolean isNBit = true;
                    for (int i=0; i < numBands; i++) {
                        if (nBits[i] != comparedBit) {
                            isNBit = false;
                            break;
                        }
                    }

                    // Handle TYPE_BYTE
                    if( comparedBit == 8) {
                        if (isNBit &&
                                offs[0] == numBands-1 &&
                                offs[1] == numBands-2 &&
                                offs[2] == numBands-3) {
                            if (numBands == 3) {
                                imageType = BufferedImage.TYPE_3BYTE_BGR;
                            } else if (offs[3] == 0) {
                                imageType = (isAlphaPre
                                        ? BufferedImage.TYPE_4BYTE_ABGR_PRE
                                        : BufferedImage.TYPE_4BYTE_ABGR);
                            }
                        }
                    }
                    //Handle TYPE_INT
                    else {
                        if (isNBit) {
                            if (numBands == 3) {
                                if(offs[0] == numBands-1 &&
                                        offs[1] == numBands-2 &&
                                        offs[2] == numBands-3) {
                                    imageType = BufferedImage.TYPE_INT_BGR;
                                } else if(offs[0] == 0 &&
                                        offs[1] == 1 &&
                                        offs[2] == 2) {
                                    imageType = BufferedImage.TYPE_INT_RGB;
                                }
                            } else if(offs[0] == 3 &&
                                    offs[1] == 0 &&
                                    offs[2] == 1 &&
                                    offs[3] == 2) {
                                imageType = (isAlphaPre
                                        ? BufferedImage.TYPE_INT_ARGB_PRE
                                        : BufferedImage.TYPE_INT_ARGB);
                            }
                        }
                    }
                }
            }
        }

        return imageType;
    }

    // Assume ri's imageType is BufferedImage.TYPE_CUSTOM
    boolean is3ByteRGB(RenderedImage ri) {
        boolean value = false;
        int i;
        ColorModel cm = ri.getColorModel();
        ColorSpace cs = cm.getColorSpace();
        SampleModel sm = ri.getSampleModel();
        boolean isAlphaPre = cm.isAlphaPremultiplied();
        int csType = cs.getType();
        if ( csType == ColorSpace.TYPE_RGB) {
            int numBands = sm.getNumBands();
            if ((numBands == 3) && (sm.getDataType() == DataBuffer.TYPE_BYTE)) {
                if (cm instanceof ComponentColorModel &&
                        sm instanceof PixelInterleavedSampleModel) {
                    PixelInterleavedSampleModel csm =
                            (PixelInterleavedSampleModel) sm;
                    int[] offs = csm.getBandOffsets();
                    ComponentColorModel ccm = (ComponentColorModel)cm;
                    int[] nBits = ccm.getComponentSize();
                    boolean is8Bit = true;
                    for (i=0; i < numBands; i++) {
                        if (nBits[i] != 8) {
                            is8Bit = false;
                            break;
                        }
                    }
                    if (is8Bit &&
                            offs[0] == 0 &&
                            offs[1] == 1 &&
                            offs[2] == 2) {
                        value = true;
                    }
                }
            }
        }
        return value;
    }

    // Assume ri's imageType is BufferedImage.TYPE_CUSTOM
    boolean is4ByteRGBA(RenderedImage ri) {
        boolean value = false;
        int i;
        ColorModel cm = ri.getColorModel();
        ColorSpace cs = cm.getColorSpace();
        SampleModel sm = ri.getSampleModel();
        boolean isAlphaPre = cm.isAlphaPremultiplied();
        int csType = cs.getType();
        if ( csType == ColorSpace.TYPE_RGB) {
            int numBands = sm.getNumBands();
            if ((numBands == 4) && (sm.getDataType() == DataBuffer.TYPE_BYTE)) {
                if (cm instanceof ComponentColorModel &&
                        sm instanceof PixelInterleavedSampleModel) {
                    PixelInterleavedSampleModel csm =
                            (PixelInterleavedSampleModel) sm;
                    int[] offs = csm.getBandOffsets();
                    ComponentColorModel ccm = (ComponentColorModel)cm;
                    int[] nBits = ccm.getComponentSize();
                    boolean is8Bit = true;
                    for (i=0; i < numBands; i++) {
                        if (nBits[i] != 8) {
                            is8Bit = false;
                            break;
                        }
                    }
                    if (is8Bit &&
                            offs[0] == 0 &&
                            offs[1] == 1 &&
                            offs[2] == 2 &&
                            offs[3] == 3 && !isAlphaPre) {
                        value = true;
                    }
                }
            }
        }
        return value;
    }

    // Note: This method for RenderedImage, can't be used by NioImageBuffer.
    /* Check if sub-image type matches image type */
    boolean isSubImageTypeEqual(RenderedImage ri) {
        int subImageType = evaluateImageType(ri);

        // This test is likely too loose, but the specification isn't clear either.
        // Assuming TYPE_CUSTOM of sub-image == the TYPE_CUSTOM of existing image.
        if(imageType == subImageType) {
            return true;
        } else {
            return false;
        }

    }

    // This method only support caller of offScreenBuffer and readRaster.
    void createBlankImageData() {

        assert (imageData == null);

        switch(numberOfComponents) {
            case 4:
                imageType = BufferedImage.TYPE_INT_ARGB;
                imageFormatType = ImageFormatType.TYPE_INT_ARGB;
                unitsPerPixel = 1;
                break;

            case 3:
                imageType = BufferedImage.TYPE_INT_RGB;
                imageFormatType = ImageFormatType.TYPE_INT_RGB;
                unitsPerPixel = 1;
                break;
            default:
                // Only valid for 3 and 4 channel case. ( Read back from framebuffer )
                assert false;
        }

        imageTypeIsSupported = true;
        imageData = createRenderedImageDataObject(null);

    }

    // This method will set imageType, imageFormatType, and unitsPerPixel
    // as it evaluates NioImageBuffer is supported. It will also reset
    // abgrSupported.
    boolean isImageTypeSupported(NioImageBuffer nioImgBuf) {

        boolean isSupported = true;
        NioImageBuffer.ImageType nioImageType = nioImgBuf.getImageType();

        switch(numberOfComponents) {
            case 4:
                switch(nioImageType) {
                    case TYPE_4BYTE_ABGR:
                        // TODO : This approach will lead to a very slow path
                        // for unsupported case.
                        if(abgrSupported) {
                            imageFormatType = ImageFormatType.TYPE_BYTE_ABGR;
                        } else {
                            // Unsupported format on HW, switch to slow copy.
                            imageFormatType = ImageFormatType.TYPE_BYTE_RGBA;
                            isSupported = false;
                        }
                        unitsPerPixel = 4;
                        break;
                    case TYPE_4BYTE_RGBA:
                        imageFormatType = ImageFormatType.TYPE_BYTE_RGBA;
                        unitsPerPixel = 4;
                        break;
                    case TYPE_INT_ARGB:
                        imageFormatType = ImageFormatType.TYPE_INT_ARGB;
                        unitsPerPixel = 1;
                        break;
                    default:
                        throw new IllegalArgumentException(J3dI18N.getString("ImageComponent5"));

                }
                break;
            case 3:
                switch(nioImageType) {
                    case TYPE_3BYTE_BGR:
                        imageFormatType = ImageFormatType.TYPE_BYTE_BGR;
                        unitsPerPixel = 3;
                        break;
                    case TYPE_3BYTE_RGB:
                        imageFormatType = ImageFormatType.TYPE_BYTE_RGB;
                        unitsPerPixel = 3;
                        break;
                    case TYPE_INT_BGR:
                        imageFormatType = ImageFormatType.TYPE_INT_BGR;
                        unitsPerPixel = 1;
                        break;
                    case TYPE_INT_RGB:
                        imageFormatType = ImageFormatType.TYPE_INT_RGB;
                        unitsPerPixel = 1;
                        break;
                    default:
                        throw new IllegalArgumentException(J3dI18N.getString("ImageComponent5"));
                }
                break;

            case 2:
                throw new IllegalArgumentException(J3dI18N.getString("ImageComponent5"));
            case 1:
                if(nioImageType == NioImageBuffer.ImageType.TYPE_BYTE_GRAY) {
                    imageFormatType = ImageFormatType.TYPE_BYTE_GRAY;
                    unitsPerPixel = 1;
                } else {
                    throw new IllegalArgumentException(J3dI18N.getString("ImageComponent5"));
                }
                break;

            default:
                throw new AssertionError();
        }

        return isSupported;
    }

    // This method will set imageType, imageFormatType, and unitsPerPixel
    // as it evaluates RenderedImage is supported. It will also reset
    // abgrSupported.
    boolean isImageTypeSupported(RenderedImage ri) {

        boolean isSupported = true;
        imageType = evaluateImageType(ri);

        switch(numberOfComponents) {
            case 4:
                if(imageType == BufferedImage.TYPE_4BYTE_ABGR) {

                    // TODO : This approach will lead to a very slow path
                    // for unsupported case.
                    if(abgrSupported) {
                        imageFormatType = ImageFormatType.TYPE_BYTE_ABGR;
                    } else {
                        // Unsupported format on HW, switch to slow copy.
                        imageFormatType = ImageFormatType.TYPE_BYTE_RGBA;
                        isSupported = false;
                    }
                    unitsPerPixel = 4;
                } else if(imageType == BufferedImage.TYPE_INT_ARGB) {
                    imageFormatType = ImageFormatType.TYPE_INT_ARGB;
                    unitsPerPixel = 1;
                } else if(is4ByteRGBA(ri)) {
                    imageFormatType = ImageFormatType.TYPE_BYTE_RGBA;
                    unitsPerPixel = 4;
                } else {
                    // System.err.println("Image format is unsupported --- Case 4");
                    // Convert unsupported format to TYPE_BYTE_RGBA.
                    imageFormatType = ImageFormatType.TYPE_BYTE_RGBA;
                    isSupported = false;
                    unitsPerPixel = 4;
                }
                break;

            case 3:
                if(imageType == BufferedImage.TYPE_3BYTE_BGR) {
                    imageFormatType = ImageFormatType.TYPE_BYTE_BGR;
                    unitsPerPixel = 3;
                } else if(imageType == BufferedImage.TYPE_INT_BGR) {
                    imageFormatType = ImageFormatType.TYPE_INT_BGR;
                    unitsPerPixel = 1;
                } else if(imageType == BufferedImage.TYPE_INT_RGB) {
                    imageFormatType = ImageFormatType.TYPE_INT_RGB;
                    unitsPerPixel = 1;
                } else if(is3ByteRGB(ri)) {
                    imageFormatType = ImageFormatType.TYPE_BYTE_RGB;
                    unitsPerPixel = 3;
                } else {
                    // System.err.println("Image format is unsupported --- Case 3");
                    // Convert unsupported format to TYPE_BYTE_RGB.
                    imageFormatType = ImageFormatType.TYPE_BYTE_RGB;
                    isSupported = false;
                    unitsPerPixel = 3;
                }
                break;

            case 2:
                // System.err.println("Image format is unsupported --- Case 2");
                // Convert unsupported format to TYPE_BYTE_LA.
                imageFormatType = ImageFormatType.TYPE_BYTE_LA;
                isSupported = false;
                unitsPerPixel = 2;
                break;

            case 1:
                if(imageType == BufferedImage.TYPE_BYTE_GRAY) {
                    imageFormatType = ImageFormatType.TYPE_BYTE_GRAY;
                    unitsPerPixel = 1;
                } else {
                    // System.err.println("Image format is unsupported --- Case 1");
                    // Convert unsupported format to TYPE_BYTE_GRAY.
                    imageFormatType = ImageFormatType.TYPE_BYTE_GRAY;
                    isSupported = false;
                    unitsPerPixel = 1;
                }
                break;

            default:
                throw new AssertionError();
        }

        return isSupported;
    }

    /*
     * This method assume that the following members have been initialized :
     * width, height, depth, imageFormatType, and unitsPerPixel.
     */
    ImageData createNioImageBufferDataObject(NioImageBuffer nioImageBuffer) {

        switch(imageFormatType) {
            case TYPE_BYTE_GRAY:
            case TYPE_BYTE_LA:
            case TYPE_BYTE_RGB:
            case TYPE_BYTE_BGR:
            case TYPE_BYTE_RGBA:
            case TYPE_BYTE_ABGR:
                if(nioImageBuffer != null) {
                    return new ImageData(ImageDataType.TYPE_BYTE_BUFFER,
                            width * height * depth * unitsPerPixel,
                            width, height, nioImageBuffer);
                } else {
                    // This is needed only if abgr is unsupported.
                    return new ImageData(ImageDataType.TYPE_BYTE_BUFFER,
                            width * height * depth * unitsPerPixel,
                            width, height);
                }
            case TYPE_INT_RGB:
            case TYPE_INT_BGR:
            case TYPE_INT_ARGB:
                return new ImageData(ImageDataType.TYPE_INT_BUFFER,
                        width * height * depth * unitsPerPixel,
                        width, height, nioImageBuffer);
            default:
                throw new AssertionError();
        }
    }

    /*
     * This method assume that the following members have been initialized :
     * depth, imageType, imageFormatType, and unitsPerPixel.
     */
    ImageData createRenderedImageDataObject(RenderedImage byRefImage, int dataWidth, int dataHeight) {
        switch(imageFormatType) {
            case TYPE_BYTE_GRAY:
            case TYPE_BYTE_LA:
            case TYPE_BYTE_RGB:
            case TYPE_BYTE_BGR:
            case TYPE_BYTE_RGBA:
            case TYPE_BYTE_ABGR:
                if(byRefImage != null) {
                    return new ImageData(ImageDataType.TYPE_BYTE_ARRAY,
                            dataWidth * dataHeight * depth * unitsPerPixel,
                            dataWidth, dataHeight, byRefImage);
                } else {
                    return new ImageData(ImageDataType.TYPE_BYTE_ARRAY,
                            dataWidth * dataHeight * depth * unitsPerPixel,
                            dataWidth, dataHeight);
                }
            case TYPE_INT_RGB:
            case TYPE_INT_BGR:
            case TYPE_INT_ARGB:
                if(byRefImage != null) {
                    return new ImageData(ImageDataType.TYPE_INT_ARRAY,
                            dataWidth * dataHeight * depth * unitsPerPixel,
                            dataWidth, dataHeight, byRefImage);
                } else {
                    return new ImageData(ImageDataType.TYPE_INT_ARRAY,
                            dataWidth * dataHeight * depth * unitsPerPixel,
                            dataWidth, dataHeight);
                }
            default:
                throw new AssertionError();
        }
    }

    private void updateImageDataPowerOfTwo(int depthIndex) {
        assert enforceNonPowerOfTwoSupport;
        BufferedImage bufImage = imageData.createBufferedImage(depthIndex);
        BufferedImage scaledImg = powerOfTwoATOp.filter(bufImage, null);
        copySupportedImageToImageData(scaledImg, 0, imageDataPowerOfTwo);
    }

    /*
     * This method assume that the following members have been initialized :
     *  width, height, depth, imageType, imageFormatType, and bytesPerPixel.
     */
    ImageData createRenderedImageDataObject(RenderedImage byRefImage) {

        return createRenderedImageDataObject(byRefImage, width, height);

    }


    /**
     * Copy specified region of image data from RenderedImage to
     * ImageComponent's imageData object
     */
    void copySupportedImageToImageData(RenderedImage ri, int srcX, int srcY,
            int dstX, int dstY, int depthIndex, int copyWidth, int copyHeight, ImageData data) {

        assert (data != null);

        ColorModel cm = ri.getColorModel();

        int xoff = ri.getTileGridXOffset();	// tile origin x offset
        int yoff = ri.getTileGridYOffset();	// tile origin y offset
        int minTileX = ri.getMinTileX();	// min tile x index
        int minTileY = ri.getMinTileY();	// min tile y index
        tilew = ri.getTileWidth();		// tile width in pixels
        tileh = ri.getTileHeight();		// tile height in pixels

        // determine the first tile of the image
        float mt;

        mt = (float)(srcX - xoff) / (float)tilew;
        if (mt < 0) {
            minTileX = (int)(mt - 1);
        } else {
            minTileX = (int)mt;
        }

        mt = (float)(srcY - yoff) / (float)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 * tilew + xoff;
        int startYTile = minTileY * tileh + yoff;

        // image dimension in the first tile
        int curw = (startXTile + tilew - srcX);
        int curh = (startYTile + tileh - srcY);

        // 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 > copyWidth) {
            curw = copyWidth;
        }

        if (curh > copyHeight) {
            curh = copyHeight;
        }

        // 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 = copyWidth;
        int tmph = copyHeight;

        // offset of the first pixel of the tile to be copied; offset is
        // relative to the upper left corner of the title
        int x = srcX - startXTile;
        int y = srcY - startYTile;

        // determine the number of tiles in each direction that the
        // image spans
        numXTiles = (copyWidth + x) / tilew;
        numYTiles = (copyHeight + y) / tileh;

        if (((float)(copyWidth + x ) % (float)tilew) > 0) {
            numXTiles += 1;
        }

        if (((float)(copyHeight + y ) % (float)tileh) > 0) {
            numYTiles += 1;
        }

        int offset;
        int w, h, i, j, m, n;
        int dstBegin;
        Object pixel = null;
        java.awt.image.Raster ras;
        int lineUnits;          // nbytes per line in dst image buffer
        int sign;		// -1 for going down
        int dstLineUnits;	// sign * lineUnits
        int tileStart;		// destination buffer offset
        // at the next left most tile

        byte[] dstByteBuffer = null;
        int[] dstIntBuffer = null;

        switch(data.getType()) {
            case TYPE_BYTE_ARRAY:
                dstByteBuffer = data.getAsByteArray();
                break;
            case TYPE_INT_ARRAY:
                dstIntBuffer = data.getAsIntArray();
                break;
            default:
                assert false;
        }

        int dataWidth = data.dataWidth;
        int dataHeight = data.dataHeight;

        lineUnits = dataWidth * unitsPerPixel;
        if (yUp) {
            // destination buffer offset
            tileStart = (depthIndex * dataWidth * dataHeight + dstY * dataWidth + dstX) * unitsPerPixel;
            sign = 1;
            dstLineUnits = lineUnits;
        } else {
            // destination buffer offset
            tileStart = (depthIndex * dataWidth * dataHeight + (dataHeight - dstY - 1) * dataWidth + dstX) * unitsPerPixel;
            sign = -1;
            dstLineUnits = -lineUnits;
        }

/*
        System.err.println("tileStart= " + tileStart + " dstLineUnits= " + dstLineUnits);
        System.err.println("startw= " + startw);
 */

        // allocate memory for a pixel
        ras = ri.getTile(minTileX,minTileY);
        pixel = getDataElementBuffer(ras);

        int srcOffset, dstOffset;
        int tileLineUnits = tilew * unitsPerPixel;
        int copyUnits;

        for (n = minTileY; n < minTileY+numYTiles; n++) {

            dstBegin = tileStart;	// destination buffer offset
            tmpw = copyWidth;	        // reset the width to be copied
            curw = startw;		// reset the width to be copied of
            // the left most tile
            x = srcX - startXTile;	// reset the starting x offset of
            // the left most tile

            for (m = minTileX; m < minTileX+numXTiles; m++) {

                // retrieve the raster for the next tile
                ras = ri.getTile(m,n);

                srcOffset = (y * tilew + x) * unitsPerPixel;
                dstOffset = dstBegin;

                copyUnits = curw * unitsPerPixel;

                //System.err.println("curh = "+curh+" curw = "+curw);
                //System.err.println("x = "+x+" y = "+y);

                switch(data.getType()) {
                    case TYPE_BYTE_ARRAY:
                        byte[] srcByteBuffer = ((DataBufferByte)ras.getDataBuffer()).getData();
                        for (h = 0; h < curh; h++) {
                            System.arraycopy(srcByteBuffer, srcOffset, dstByteBuffer, dstOffset,
                                    copyUnits);
                            srcOffset += tileLineUnits;
                            dstOffset += dstLineUnits;
                        }
                        break;
                    case TYPE_INT_ARRAY:
                        int[] srcIntBuffer = ((DataBufferInt)ras.getDataBuffer()).getData();
                        for (h = 0; h < curh; h++) {
                            System.arraycopy(srcIntBuffer, srcOffset, dstIntBuffer, dstOffset,
                                    copyUnits);
                            srcOffset += tileLineUnits;
                            dstOffset += dstLineUnits;
                        }
                        break;
                    default:
                        assert false;
                }

                // advance the destination buffer offset
                dstBegin += curw * unitsPerPixel;

                // move to the next tile in x direction
                x = 0;

                // determine the width of copy region of the next tile

                tmpw -= curw;
                if (tmpw < tilew) {
                    curw = tmpw;
                } else {
                    curw = tilew;
                }
            }

            // we are done copying an array of tiles in the x direction
            // advance the tileStart offset
            tileStart += dataWidth * unitsPerPixel * curh * sign;

            // move to the next set of tiles in y direction
            y = 0;

            // determine the height of copy region for the next set
            // of tiles
            tmph -= curh;
            if (tmph < tileh) {
                curh = tmph;
            } else {
                curh = tileh;
            }
        }

        if((imageData == data) && (imageDataPowerOfTwo != null)) {
            updateImageDataPowerOfTwo(depthIndex);
        }
    }

    // Quick line by line copy
    void copyImageLineByLine(BufferedImage bi, int srcX, int srcY,
            int dstX, int dstY, int depthIndex, int copyWidth, int copyHeight, ImageData data) {

        assert (data != null);

        int h;
        int rowBegin,		// src begin row index
                srcBegin,		// src begin offset
                dstBegin;		// dst begin offset

        int dataWidth = data.dataWidth;
        int dataHeight = data.dataHeight;
        int dstUnitsPerRow = dataWidth * unitsPerPixel; // bytes per row in dst image
        rowBegin = srcY;

        if (yUp) {
            dstBegin = (depthIndex * dataWidth * dataHeight + dstY * dataWidth + dstX) * unitsPerPixel;
        } else {
            dstBegin = (depthIndex * dataWidth * dataHeight + (dataHeight - dstY - 1) * dataWidth + dstX) * unitsPerPixel;
            dstUnitsPerRow = - 1 * dstUnitsPerRow;
        }

        int copyUnits = copyWidth * unitsPerPixel;
        int srcWidth = bi.getWidth();
        int srcUnitsPerRow = srcWidth * unitsPerPixel;
        srcBegin = (rowBegin * srcWidth + srcX) * unitsPerPixel;

        switch(data.getType()) {
            case TYPE_BYTE_ARRAY:
                byte[] srcByteBuffer = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData();
                byte[] dstByteBuffer = data.getAsByteArray();
                for (h = 0; h < copyHeight; h++) {
                    System.arraycopy(srcByteBuffer, srcBegin, dstByteBuffer, dstBegin, copyUnits);
                    dstBegin += dstUnitsPerRow;
                    srcBegin += srcUnitsPerRow;
                }
                break;

            case TYPE_INT_ARRAY:
                int[] srcIntBuffer = ((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
                int[] dstIntBuffer = data.getAsIntArray();
                for (h = 0; h < copyHeight; h++) {
                    System.arraycopy(srcIntBuffer, srcBegin, dstIntBuffer, dstBegin, copyUnits);
                    dstBegin += dstUnitsPerRow;
                    srcBegin += srcUnitsPerRow;
                }
                break;
            default:
                assert false;
        }

        if((imageData == data) && (imageDataPowerOfTwo != null)) {
            updateImageDataPowerOfTwo(depthIndex);
        }
    }

    // Quick block copy for yUp image
    void copyImageByBlock(BufferedImage bi, int depthIndex, ImageData data) {

        assert ((data != null) && yUp);

        int dataWidth = data.dataWidth;
        int dataHeight = data.dataHeight;

        int dstBegin;	// dst begin offset
        dstBegin = depthIndex * dataWidth * dataHeight * unitsPerPixel;

        switch(imageData.getType()) {
            case TYPE_BYTE_ARRAY:
                byte[] srcByteBuffer = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData();
                byte[] dstByteBuffer = data.getAsByteArray();
                System.arraycopy(srcByteBuffer, 0, dstByteBuffer, dstBegin, (dataWidth * dataHeight * unitsPerPixel));
                break;
            case TYPE_INT_ARRAY:
                int[] srcIntBuffer = ((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
                int[] dstIntBuffer = data.getAsIntArray();
                System.arraycopy(srcIntBuffer, 0, dstIntBuffer, dstBegin, (dataWidth * dataHeight * unitsPerPixel));
                break;
            default:
                assert false;
        }

        if((imageData == data) && (imageDataPowerOfTwo != null)) {
            updateImageDataPowerOfTwo(depthIndex);
        }

    }

    /**
     * copy complete region of a RenderedImage to ImageComponent's imageData object.
     */
    void copySupportedImageToImageData(RenderedImage ri, int depthIndex, ImageData data) {

        if (ri instanceof BufferedImage) {
            if(yUp) {
                /*  Use quick block copy when  ( format is OK, Yup is true, and byRef is false). */
                // System.err.println("ImageComponentRetained.copySupportedImageToImageData() : (imageTypeSupported && !byReference && yUp) --- (2 BI)");
                copyImageByBlock((BufferedImage)ri, depthIndex, data);
            } else {
                /*  Use quick inverse line by line copy when (format is OK and Yup is false). */
                // System.err.println("ImageComponentRetained.copySupportedImageToImageData() : (imageTypeSupported && !yUp) --- (3 BI)");
                copyImageLineByLine((BufferedImage)ri, 0, 0, 0, 0, depthIndex, data.dataWidth, data.dataHeight, data);
            }
        } else {
            // System.err.println("ImageComponentRetained.copySupportedImageToImageData() : (imageTypeSupported && !byReference ) --- (2 RI)");
            copySupportedImageToImageData(ri, ri.getMinX(), ri.getMinY(), 0, 0, depthIndex, data.dataWidth, data.dataHeight, data);

             /*
              * An alternative approach.
              *
            // Create a buffered image from renderImage
            ColorModel cm = ri.getColorModel();
            WritableRaster wRaster = ri.copyData(null);
            BufferedImage bi = new BufferedImage(cm,
                                                 wRaster,
                                                 cm.isAlphaPremultiplied()
                                                 ,null);

            copySupportedImageToImageData((BufferedImage)ri, 0, 0, 0, 0, depthIndex, data.dataWidth, data.dataHeight, data);

              *
              *
              */
        }
    }

     /*
     * copy the complete unsupported NioImageBuffer into a supported BYTE_BUFFER format
     */
    void copyUnsupportedNioImageToImageData(NioImageBuffer nioImage, int srcX, int srcY,
            int dstX, int dstY, int copyWidth, int copyHeight, ImageData iData) {

        if (MasterControl.isDevLoggable(Level.INFO)) {
            MasterControl.getDevLogger().info("ImageComponent - Copying Unsupported NioImage, use a different image type");
        }

        assert (iData.getType() == ImageDataType.TYPE_BYTE_BUFFER);
        assert (getImageFormatType() == ImageFormatType.TYPE_BYTE_RGBA);

        int length = copyWidth * copyHeight;
        ByteBuffer srcBuffer = (ByteBuffer) nioImage.getDataBuffer();
        srcBuffer.rewind();
        ByteBuffer dstBuffer = iData.getAsByteBuffer();
        dstBuffer.rewind();

        // Do copy and swap.
        for(int i = 0; i < length; i +=4) {
            dstBuffer.put(i, srcBuffer.get(i+3));
            dstBuffer.put(i+1, srcBuffer.get(i+2));
            dstBuffer.put(i+2, srcBuffer.get(i+1));
            dstBuffer.put(i+3, srcBuffer.get(i));
        }
    }

    /*
     * copy the complete unsupported image into a supported BYTE_ARRAY format
     */
    void copyUnsupportedImageToImageData(RenderedImage ri, int depthIndex, ImageData data) {

        assert (data.getType() == ImageDataType.TYPE_BYTE_ARRAY);

        if (MasterControl.isDevLoggable(Level.INFO)) {
            MasterControl.getDevLogger().info("ImageComponent - Copying Unsupported Image, use a different image type");
        }

        if (ri instanceof BufferedImage) {
            copyUnsupportedImageToImageData((BufferedImage)ri, 0, 0, 0, 0,
                    depthIndex, data.dataWidth, data.dataHeight, data);
        } else {
            copyUnsupportedImageToImageData(ri, ri.getMinX(), ri.getMinY(),
                    0, 0, depthIndex, data.dataWidth, data.dataHeight, data);
        }
    }

    void copyUnsupportedImageToImageData(BufferedImage bi, int srcX, int srcY,
            int dstX, int dstY, int depthIndex, int copyWidth, int copyHeight, ImageData data) {

        int w, h, i, j;
        int rowBegin,		// src begin row index
                srcBegin,		// src begin offset
                dstBegin,		// dst begin offset
                rowInc,		// row increment
                // -1 --- ydown
                //  1 --- yup
                row;

        rowBegin = srcY;
        rowInc = 1;

        assert (data != null);

        int dataWidth = data.dataWidth;
        int dataHeight = data.dataHeight;
        int dstBytesPerRow = dataWidth * unitsPerPixel; // bytes per row in dst image

        if (yUp) {
            dstBegin = (depthIndex * dataWidth * dataHeight + dstY * dataWidth + dstX) * unitsPerPixel;
        } else {
            dstBegin = (depthIndex * dataWidth * dataHeight + (dataHeight - dstY - 1) * dataWidth + dstX) * unitsPerPixel;
            dstBytesPerRow = - 1 * dstBytesPerRow;
        }

        WritableRaster ras = bi.getRaster();
        ColorModel cm = bi.getColorModel();
        Object pixel = getDataElementBuffer(ras);

        byte[] dstBuffer = data.getAsByteArray();

        switch(numberOfComponents) {
            case 4: {
                for (row = rowBegin, h = 0;
                h < copyHeight; h++, row += rowInc) {
                    j = dstBegin;
                    for (w = srcX; w < (copyWidth + srcX); w++) {
                        ras.getDataElements(w, row, pixel);
                        dstBuffer[j++] = (byte)cm.getRed(pixel);
                        dstBuffer[j++] = (byte)cm.getGreen(pixel);
                        dstBuffer[j++] = (byte)cm.getBlue(pixel);
                        dstBuffer[j++] = (byte)cm.getAlpha(pixel);
                    }
                    dstBegin += dstBytesPerRow;
                }
            }
            break;

            case 3: {
                for (row = rowBegin, h = 0;
                h < copyHeight; h++, row += rowInc) {
                    j = dstBegin;
                    for (w = srcX; w < (copyWidth + srcX); w++) {
                        ras.getDataElements(w, row, pixel);
                        dstBuffer[j++] = (byte)cm.getRed(pixel);
                        dstBuffer[j++] = (byte)cm.getGreen(pixel);
                        dstBuffer[j++] = (byte)cm.getBlue(pixel);
                    }
                    dstBegin += dstBytesPerRow;
                }
            }
            break;

            case 2: {
                for (row = rowBegin, h = 0;
                h < copyHeight; h++, row += rowInc) {
                    j = dstBegin;
                    for (w = srcX; w < (copyWidth + srcX); w++) {
                        ras.getDataElements(w, row, pixel);
                        dstBuffer[j++] = (byte)cm.getRed(pixel);
                        dstBuffer[j++] = (byte)cm.getAlpha(pixel);
                    }
                    dstBegin += dstBytesPerRow;
                }
            }
            break;

            case 1: {
                for (row = rowBegin, h = 0;
                h < copyHeight; h++, row += rowInc) {
                    j = dstBegin;
                    for (w = srcX; w < (copyWidth + srcX); w++) {
                        ras.getDataElements(w, row, pixel);
                        dstBuffer[j++] = (byte)cm.getRed(pixel);
                    }
                    dstBegin += dstBytesPerRow;
                }
            }
            break;
            default:
                assert false;
        }

        if((imageData == data) && (imageDataPowerOfTwo != null)) {
            updateImageDataPowerOfTwo(depthIndex);
        }
    }

    void copyUnsupportedImageToImageData(RenderedImage ri, int srcX, int srcY,
            int dstX, int dstY, int depthIndex, int copyWidth, int copyHeight, ImageData data) {

        int w, h, i, j, m, n;
        int dstBegin;
        Object pixel = null;
        java.awt.image.Raster ras;
        // dst image buffer
        int sign;				// -1 for going down
        int dstLineBytes;			// sign * lineBytes
        int tileStart;				// destination buffer offset
        // at the next left most tile

        int offset;

        ColorModel cm = ri.getColorModel();

        int xoff = ri.getTileGridXOffset();	// tile origin x offset
        int yoff = ri.getTileGridYOffset();	// tile origin y offset
        int minTileX = ri.getMinTileX();	// min tile x index
        int minTileY = ri.getMinTileY();	// min tile y index
        tilew = ri.getTileWidth();		// tile width in pixels
        tileh = ri.getTileHeight();		// tile height in pixels

        // determine the first tile of the image

        float mt;

        mt = (float)(srcX - xoff) / (float)tilew;
        if (mt < 0) {
            minTileX = (int)(mt - 1);
        } else {
            minTileX = (int)mt;
        }

        mt = (float)(srcY - yoff) / (float)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 * tilew + xoff;
        int startYTile = minTileY * tileh + yoff;


        // image dimension in the first tile
        int curw = (startXTile + tilew - srcX);
        int curh = (startYTile + tileh - srcY);

        // 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 > copyWidth) {
            curw = copyWidth;
        }

        if (curh > copyHeight) {
            curh = copyHeight;
        }

        // 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 = copyWidth;
        int tmph = copyHeight;


        // offset of the first pixel of the tile to be copied; offset is
        // relative to the upper left corner of the title
        int x = srcX - startXTile;
        int y = srcY - startYTile;


        // determine the number of tiles in each direction that the
        // image spans

        numXTiles = (copyWidth + x) / tilew;
        numYTiles = (copyHeight + y) / tileh;

        if (((float)(copyWidth + x ) % (float)tilew) > 0) {
            numXTiles += 1;
        }

        if (((float)(copyHeight + y ) % (float)tileh) > 0) {
            numYTiles += 1;
        }

        assert (data != null);
        int dataWidth = data.dataWidth;
        int dataHeight = data.dataHeight;
        int lineBytes = dataWidth * unitsPerPixel;  // nbytes per line in

        if (yUp) {
            // destination buffer offset
            tileStart = (depthIndex * dataWidth * dataHeight + dstY * dataWidth + dstX) * unitsPerPixel;
            sign = 1;
            dstLineBytes = lineBytes;
        } else {
            // destination buffer offset
            tileStart = (depthIndex * dataWidth * dataHeight + (dataHeight - dstY - 1) * dataWidth + dstX) * unitsPerPixel;
            sign = -1;
            dstLineBytes = -lineBytes;
        }

/*
        System.err.println("tileStart= " + tileStart + " dstLineBytes= " + dstLineBytes);
        System.err.println("startw= " + startw);
 */

        // allocate memory for a pixel
        ras = ri.getTile(minTileX,minTileY);
        pixel = getDataElementBuffer(ras);
        byte[] dstBuffer = imageData.getAsByteArray();

        switch(numberOfComponents) {
            case 4: {
                //	    System.err.println("Case 1: byReference = "+byReference);
                for (n = minTileY; n < minTileY+numYTiles; n++) {

                    dstBegin = tileStart;	// destination buffer offset
                    tmpw = copyWidth;	// reset the width to be copied
                    curw = startw;		// reset the width to be copied of
                    // the left most tile
                    x = srcX - startXTile;	// reset the starting x offset of
                    // the left most tile

                    for (m = minTileX; m < minTileX+numXTiles; m++) {

                        // retrieve the raster for the next tile
                        ras = ri.getTile(m,n);

                        j = dstBegin;
                        offset = 0;

                        //System.err.println("curh = "+curh+" curw = "+curw);
                        //System.err.println("x = "+x+" y = "+y);

                        for (h = y; h < (y + curh); h++) {
                            // System.err.println("j = "+j);
                            for (w = x; w < (x + curw); w++) {
                                ras.getDataElements(w, h, pixel);
                                dstBuffer[j++] = (byte)cm.getRed(pixel);
                                dstBuffer[j++] = (byte)cm.getGreen(pixel);
                                dstBuffer[j++] = (byte)cm.getBlue(pixel);
                                dstBuffer[j++] = (byte)cm.getAlpha(pixel);
                            }
                            offset += dstLineBytes;
                            j = dstBegin + offset;
                        }

                        // advance the destination buffer offset
                        dstBegin += curw * unitsPerPixel;

                        // move to the next tile in x direction
                        x = 0;

                        // determine the width of copy region of the next tile

                        tmpw -= curw;
                        if (tmpw < tilew) {
                            curw = tmpw;
                        } else {
                            curw = tilew;
                        }
                    }

                    // we are done copying an array of tiles in the x direction
                    // advance the tileStart offset

                    tileStart += dataWidth * unitsPerPixel * curh * sign;

                    // move to the next set of tiles in y direction
                    y = 0;

                    // determine the height of copy region for the next set
                    // of tiles
                    tmph -= curh;
                    if (tmph < tileh) {
                        curh = tmph;
                    } else {
                        curh = tileh;
                    }
                }
            }
            break;
            case 3: {
                for (n = minTileY; n < minTileY+numYTiles; n++) {

                    dstBegin = tileStart;	// destination buffer offset
                    tmpw = copyWidth;	// reset the width to be copied
                    curw = startw;		// reset the width to be copied of
                    // the left most tile
                    x = srcX - startXTile;	// reset the starting x offset of
                    // the left most tile

                    for (m = minTileX; m < minTileX+numXTiles; m++) {

                        // retrieve the raster for the next tile
                        ras = ri.getTile(m,n);

                        j = dstBegin;
                        offset = 0;

                        //System.err.println("curh = "+curh+" curw = "+curw);
                        //System.err.println("x = "+x+" y = "+y);

                        for (h = y; h < (y + curh); h++) {
                            //			System.err.println("j = "+j);
                            for (w = x; w < (x + curw); w++) {
                                ras.getDataElements(w, h, pixel);
                                dstBuffer[j++]   = (byte)cm.getRed(pixel);
                                dstBuffer[j++] = (byte)cm.getGreen(pixel);
                                dstBuffer[j++] = (byte)cm.getBlue(pixel);
                            }
                            offset += dstLineBytes;
                            j = dstBegin + offset;
                        }

                        // advance the destination buffer offset
                        dstBegin += curw * unitsPerPixel;

                        // move to the next tile in x direction
                        x = 0;

                        // determine the width of copy region of the next tile

                        tmpw -= curw;
                        if (tmpw < tilew) {
                            curw = tmpw;
                        } else {
                            curw = tilew;
                        }
                    }

                    // we are done copying an array of tiles in the x direction
                    // advance the tileStart offset

                    tileStart += dataWidth * unitsPerPixel * curh * sign;

                    // move to the next set of tiles in y direction
                    y = 0;

                    // determine the height of copy region for the next set
                    // of tiles
                    tmph -= curh;
                    if (tmph < tileh) {
                        curh = tmph;
                    } else {
                        curh = tileh;
                    }
                }
            }
            break;
            case 2: {
                for (n = minTileY; n < minTileY+numYTiles; n++) {

                    dstBegin = tileStart;	// destination buffer offset
                    tmpw = copyWidth;	// reset the width to be copied
                    curw = startw;		// reset the width to be copied of
                    // the left most tile
                    x = srcX - startXTile;	// reset the starting x offset of
                    // the left most tile

                    for (m = minTileX; m < minTileX+numXTiles; m++) {

                        // retrieve the raster for the next tile
                        ras = ri.getTile(m,n);

                        j = dstBegin;
                        offset = 0;

                        //System.err.println("curh = "+curh+" curw = "+curw);
                        //System.err.println("x = "+x+" y = "+y);

                        for (h = y; h < (y + curh); h++) {
                            //			System.err.println("j = "+j);
                            for (w = x; w < (x + curw); w++) {
                                ras.getDataElements(w, h, pixel);
                                dstBuffer[j++] = (byte)cm.getRed(pixel);
                                dstBuffer[j++] = (byte)cm.getAlpha(pixel);
                            }
                            offset += dstLineBytes;
                            j = dstBegin + offset;
                        }

                        // advance the destination buffer offset
                        dstBegin += curw * unitsPerPixel;

                        // move to the next tile in x direction
                        x = 0;

                        // determine the width of copy region of the next tile

                        tmpw -= curw;
                        if (tmpw < tilew) {
                            curw = tmpw;
                        } else {
                            curw = tilew;
                        }
                    }


                    // we are done copying an array of tiles in the x direction
                    // advance the tileStart offset

                    tileStart += dataWidth * unitsPerPixel * curh * sign;


                    // move to the next set of tiles in y direction
                    y = 0;

                    // determine the height of copy region for the next set
                    // of tiles
                    tmph -= curh;
                    if (tmph < tileh) {
                        curh = tmph;
                    } else {
                        curh = tileh;
                    }
                }
            }
            break;
            case 1: {
                for (n = minTileY; n < minTileY+numYTiles; n++) {

                    dstBegin = tileStart;	// destination buffer offset
                    tmpw = copyWidth;	// reset the width to be copied
                    curw = startw;		// reset the width to be copied of
                    // the left most tile
                    x = srcX - startXTile;	// reset the starting x offset of
                    // the left most tile

                    for (m = minTileX; m < minTileX+numXTiles; m++) {

                        // retrieve the raster for the next tile
                        ras = ri.getTile(m,n);

                        j = dstBegin;
                        offset = 0;

                        //System.err.println("curh = "+curh+" curw = "+curw);
                        //System.err.println("x = "+x+" y = "+y);

                        for (h = y; h < (y + curh); h++) {
                            //			System.err.println("j = "+j);
                            for (w = x; w < (x + curw); w++) {
                                ras.getDataElements(w, h, pixel);
                                dstBuffer[j++] = (byte)cm.getRed(pixel);
                            }
                            offset += dstLineBytes;
                            j = dstBegin + offset;
                        }

                        // advance the destination buffer offset
                        dstBegin += curw * unitsPerPixel;

                        // move to the next tile in x direction
                        x = 0;

                        // determine the width of copy region of the next tile

                        tmpw -= curw;
                        if (tmpw < tilew) {
                            curw = tmpw;
                        } else {
                            curw = tilew;
                        }
                    }

                    // we are done copying an array of tiles in the x direction
                    // advance the tileStart offset
                    tileStart += dataWidth * unitsPerPixel * curh * sign;

                    // move to the next set of tiles in y direction
                    y = 0;

                    // determine the height of copy region for the next set
                    // of tiles
                    tmph -= curh;
                    if (tmph < tileh) {
                        curh = tmph;
                    } else {
                        curh = tileh;
                    }
                }
            }
            break;

            default:
                assert false;
        }

        if((imageData == data) && (imageDataPowerOfTwo != null)) {
            updateImageDataPowerOfTwo(depthIndex);
        }
    }


    void evaluateExtensions(Canvas3D canvas) {
        // Issue 366: need to synchronize since it could be called concurrently
        // from multiple renderers (and maybe the renderer(s) and renderbin)
        synchronized (evaluateExtLock) {
            // For performance reason the ordering of the following 2 statements is intentional.
            // So that we only need to do format conversion for imageData only
            evaluateExtABGR(canvas.extensionsSupported);
            evaluateExtNonPowerOfTwo(canvas.textureExtendedFeatures);
        }

    }


    void evaluateExtABGR(int ext) {


        // If abgrSupported is false, a copy has been created so
        // we don't have to check again.
        if(!abgrSupported) {
            return;
        }

        if(getImageFormatType() != ImageFormatType.TYPE_BYTE_ABGR) {
            return;
        }

        if((ext & Canvas3D.EXT_ABGR) != 0) {
            return;
        }

        // ABGR is unsupported, set flag to false.
        abgrSupported = false;
        convertImageDataFromABGRToRGBA();

    }

    private int getClosestPowerOf2(int value) {

        if (value < 1)
            return value;

        int powerValue = 1;
        for (;;) {
            powerValue *= 2;
            if (value < powerValue) {
                // Found max bound of power, determine which is closest
                int minBound = powerValue/2;
                if ((powerValue - value) >
                        (value - minBound))
                    return minBound;
                else
                    return powerValue;
            }
        }
    }

    private int getCeilPowerOf2(int value) {

        if (value < 1)
            return value;

        int powerValue = 1;
        for (;;) {
            powerValue *= 2;
            if (value <= powerValue) {
                // Found max bound of power
                return powerValue;
            }
        }
    }

    void evaluateExtNonPowerOfTwo(int ext) {
        // Only need to enforce for Raster or Background.
        if(!enforceNonPowerOfTwoSupport) {
            return;
        }

        // If npotSupported is false, a copy power of two image has been created
        // so we don't have to check again.
        if(!npotSupported) {
            return;
        }

        if (imageData == null && !isByReference()) {
            return;
        }

        if((ext & Canvas3D.TEXTURE_NON_POWER_OF_TWO) != 0) {
            return;
        }

        // NPOT is unsupported, set flag to false.
        npotSupported = false;

        int npotWidth;
        int npotHeight;
        // Always scale up if image size is smaller 512*512.
        if((width * height) < IMAGE_SIZE_512X512) {
            npotWidth = getCeilPowerOf2(width);
            npotHeight = getCeilPowerOf2(height);
        } else {
            npotWidth = getClosestPowerOf2(width);
            npotHeight = getClosestPowerOf2(height);
        }

        // System.err.println("width " + width + " height " + height + " npotWidth " + npotWidth + " npotHeight " + npotHeight);

        float xScale = (float)npotWidth/(float)width;
        float yScale = (float)npotHeight/(float)height;

        // scale if scales aren't 1.0
        if (!(xScale == 1.0f && yScale == 1.0f)) {

            if (imageData == null) {
                // This is a byRef, support format and is a RenderedImage case.
                // See ImageComponent2DRetained.set(RenderedImage image)
                RenderedImage ri = (RenderedImage) getRefImage(0);

                assert !(ri instanceof BufferedImage);

                 // Create a buffered image from renderImage
                ColorModel cm = ri.getColorModel();
                WritableRaster wRaster = ri.copyData(null);
                ri = new BufferedImage(cm,
                        wRaster,
                        cm.isAlphaPremultiplied()
                        ,null);


                // Create image data object with buffer for image. */
                imageData = createRenderedImageDataObject(null);
                copySupportedImageToImageData(ri, 0, imageData);

            }

            assert imageData != null;

            // Create a supported BufferedImage type.
            BufferedImage bi = imageData.createBufferedImage(0);

            int imageType = bi.getType();
            BufferedImage scaledImg = new BufferedImage(npotWidth, npotHeight, imageType);

            AffineTransform at = AffineTransform.getScaleInstance(xScale,
                    yScale);

            powerOfTwoATOp = new AffineTransformOp(at,
                    AffineTransformOp.TYPE_BILINEAR);

            powerOfTwoATOp.filter(bi, scaledImg);

            // System.err.println("bi " + bi.getColorModel());
            // System.err.println("scaledImg " + scaledImg.getColorModel());

            imageDataPowerOfTwo = createRenderedImageDataObject(null, npotWidth, npotHeight);
            // Since bi is created from imageData, it's imageType is supported.
            copySupportedImageToImageData(scaledImg, 0, imageDataPowerOfTwo);

        } else {
            imageDataPowerOfTwo = null;
        }
    }

    void convertImageDataFromABGRToRGBA() {

        // Unsupported format on HW, switch to slow copy.
        imageFormatType = ImageFormatType.TYPE_BYTE_RGBA;
        imageTypeIsSupported = false;

        // Only need to convert imageData
        imageData.convertFromABGRToRGBA();

    }

    /**
     * Copy supported ImageType from ImageData to the user defined bufferedImage
     */
    void copyToRefImage(int depth) {
        int h;
        int rowBegin,		// src begin row index
                srcBegin,		// src begin offset
                dstBegin;		// dst begin offset

        // refImage has to be a BufferedImage for off screen and read raster
        assert refImage[depth] != null;
        assert (refImage[depth] instanceof BufferedImage);

        BufferedImage bi = (BufferedImage)refImage[depth];
        int dstUnitsPerRow = width * unitsPerPixel; // bytes per row in dst image
        rowBegin = 0;

        if (yUp) {
            dstBegin = (depth * width * height) * unitsPerPixel;
        } else {
            dstBegin = (depth * width * height + (height - 1) * width ) * unitsPerPixel;
            dstUnitsPerRow = - 1 * dstUnitsPerRow;
        }

        int scanline = width * unitsPerPixel;
        srcBegin = (rowBegin * width ) * unitsPerPixel;

        switch(imageData.getType()) {
            case TYPE_BYTE_ARRAY:
                byte[] dstByteBuffer = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData();
                byte[] srcByteBuffer = imageData.getAsByteArray();
                for (h = 0; h < height; h++) {
                    System.arraycopy(srcByteBuffer, srcBegin, dstByteBuffer, dstBegin, scanline);
                    dstBegin += dstUnitsPerRow;
                    srcBegin += scanline;
                }
                break;

            case TYPE_INT_ARRAY:
                int[] dstIntBuffer = ((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
                int[] srcIntBuffer = imageData.getAsIntArray();
                for (h = 0; h < height; h++) {
                    System.arraycopy(srcIntBuffer, srcBegin, dstIntBuffer, dstBegin, scanline);
                    dstBegin += dstUnitsPerRow;
                    srcBegin += scanline;
                }
                break;
            default:
                assert false;
        }

    }

    /**
     * Copy image to the user defined bufferedImage ( 3 or 4 components only )
     */
    void copyToRefImageWithFormatConversion(int depth) {
        int w, h, i, j;
        int dstBegin, dstInc, dstIndex, dstIndexInc;
        // refImage has to be a BufferedImage for off screen and read raster
        assert refImage[depth] != null;
        assert (refImage[depth] instanceof BufferedImage);

        BufferedImage bi = (BufferedImage)refImage[depth];
        int biType = bi.getType();
        byte[] buf = imageData.getAsByteArray();

        // convert from Ydown to Yup for texture
        if (!yUp) {
            dstInc = -1 * width;
            dstBegin = (height - 1) * width;
            dstIndex = height -1;
            dstIndexInc = -1;
        } else {
            dstInc = width;
            dstBegin = 0;
            dstIndex = 0;
            dstIndexInc = 1;
        }

        switch (biType) {
            case BufferedImage.TYPE_INT_ARGB:
                int[] intData =
                        ((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
                // Multiply by 4 to get the byte incr and start point
                j = 0;
                switch (imageFormatType) {
                    case TYPE_BYTE_RGBA:
                        for(h = 0; h < height; h++, dstBegin += dstInc) {
                            i = dstBegin;
                            for (w = 0; w < width; w++, j+=4, i++) {
                                intData[i] = (((buf[j+3] &0xff) << 24) | // a
                                        ((buf[j] &0xff) << 16) | // r
                                        ((buf[j+1] &0xff) << 8) | // g
                                        (buf[j+2] & 0xff)); // b
                            }
                        }
                        break;
                    case TYPE_BYTE_RGB:
                        for(h = 0; h < height; h++, dstBegin += dstInc) {
                            i = dstBegin;
                            for (w = 0; w < width; w++, j+=3, i++) {
                                intData[i] = (0xff000000 | // a
                                        ((buf[j] &0xff) << 16) | // r
                                        ((buf[j+1] &0xff) << 8) | // g
                                        (buf[j+2] & 0xff)); // b
                            }
                        }
                        break;
                    default:
                        assert false;
                }
                break;

            case BufferedImage.TYPE_INT_RGB:
                intData =
                        ((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
                // Multiply by 4 to get the byte incr and start point
                j = 0;
                for(h = 0; h < height; h++, dstBegin += dstInc) {
                    i = dstBegin;
                    for (w = 0; w < width; w++, j+=4, i++) {
                        intData[i] = (0xff000000 | // a
                                ((buf[j] &0xff) << 16) | // r
                                ((buf[j+1] &0xff) << 8) | // g
                                (buf[j+2] & 0xff)); // b
                    }
                }
                break;

            case BufferedImage.TYPE_4BYTE_ABGR:
                byte[] byteData =
                        ((DataBufferByte)bi.getRaster().getDataBuffer()).getData();
                // Multiply by 4 to get the byte incr and start point
                j = 0;

                //Issue 381: dstBegin contains pixel count, but we are looping over byte count. In case of YDown, it contains a count that is decremented and if we do not multiply, we have an AIOOB thrown at 25% of the copy.
                dstBegin <<= 2;

                switch (imageFormatType) {
                    case TYPE_BYTE_RGBA:
                        for(h = 0; h < height; h++, dstBegin += (dstInc << 2)) {
                            i = dstBegin;
                            for (w = 0; w < width; w++, j+=4) {
                                byteData[i++] = buf[j+3]; // a
                                byteData[i++] = buf[j+2]; // b
                                byteData[i++] = buf[j+1];// g
                                byteData[i++] = buf[j]; // r
                            }
                        }
                        break;
                    case TYPE_BYTE_RGB:
                        for(h = 0; h < height; h++, dstBegin += (dstInc << 2)) {
                            i = dstBegin;
                            for (w = 0; w < width; w++, j+=3) {
                                byteData[i++] = (byte) 0xff; // a
                                byteData[i++] = buf[j+2]; // b
                                byteData[i++] = buf[j+1];// g
                                byteData[i++] = buf[j]; // r
                            }
                        }
                        break;
                    default:
                        assert false;
                }
                break;

            case BufferedImage.TYPE_INT_BGR:
                intData =
                        ((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
                // Multiply by 4 to get the byte incr and start point
                j = 0;

                for(h = 0; h < height; h++, dstBegin += dstInc) {
                    i = dstBegin;
                    for (w = 0; w < width; w++, j+=4, i++) {
                        intData[i] = (0xff000000 | // a
                                ((buf[j] &0xff) ) | // r
                                ((buf[j+1] &0xff) << 8) | // g
                                (buf[j+2] & 0xff)<< 16); // b
                    }
                }
                break;
            default:
                assert false;
        }

    }

    // Add a user to the userList
    synchronized void addUser(NodeComponentRetained node) {
        userList.add(node);
    }

    // Add a user to the  userList
    synchronized void removeUser(NodeComponentRetained node) {
        int i = userList.indexOf(node);
        if (i >= 0) {
            userList.remove(i);
        }
    }

    /*
     *
     * @exception IllegalSharingException if this image is
     * being used by a Canvas3D as an off-screen buffer.
     */
    @Override
    void setLive(boolean inBackgroundGroup, int refCount) {
        // Do illegalSharing check.
        if(getUsedByOffScreen()) {
            throw new IllegalSharingException(J3dI18N.getString("ImageComponent3"));
        }
        super.setLive(inBackgroundGroup, refCount);
    }

    /**
     * ImageComponent object doesn't really have mirror object.
     * But it's using the updateMirrorObject interface to propagate
     * the changes to the users
     */
    @Override
    synchronized void updateMirrorObject(int component, Object value) {
		// System.err.println("ImageComponent.updateMirrorObject");
		if ((component & IMAGE_CHANGED) == 0 &&
		    (component & SUBIMAGE_CHANGED) == 0)
			return;

		for (int i = userList.size() - 1; i >= 0; i--) {
			NodeComponentRetained user = userList.get(i);
			if (user == null)
				continue;

			if (user instanceof TextureRetained) {
				((TextureRetained)user).notifyImageComponentImageChanged(this, (ImageComponentUpdateInfo)value);
			}
			else if (user instanceof RasterRetained) {
				((RasterRetained)user).notifyImageComponentImageChanged(this, (ImageComponentUpdateInfo)value);
			}
		}
    }

    final void sendMessage(int attrMask, Object attr) {

        J3dMessage createMessage = new J3dMessage();
        createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES |
                J3dThread.UPDATE_RENDER;
        createMessage.type = J3dMessage.IMAGE_COMPONENT_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);
    }

    @Override
    void handleFrequencyChange(int bit) {
        if (bit == ImageComponent.ALLOW_IMAGE_WRITE) {
            setFrequencyChangeMask(ImageComponent.ALLOW_IMAGE_WRITE, 0x1);
        }
    }

    static Object getDataElementBuffer(java.awt.image.Raster ras) {
        int nc = ras.getNumDataElements();

        switch (ras.getTransferType()) {
            case DataBuffer.TYPE_INT:
                return new int[nc];
            case DataBuffer.TYPE_BYTE:
                return new byte[nc];
            case DataBuffer.TYPE_USHORT:
            case DataBuffer.TYPE_SHORT:
                return new short[nc];
            case DataBuffer.TYPE_FLOAT:
                return new float[nc];
            case DataBuffer.TYPE_DOUBLE:
                return new double[nc];
        }
        // Should not happen
        return null;
    }

    /**
     * Wrapper class for image data.
     * Currently supports byte array and int array.
     * Will eventually support NIO ByteBuffer and IntBuffer.
     */
    class ImageData {

        private Object data = null;
        private ImageDataType imageDataType = ImageDataType.TYPE_NULL;
        private int length = 0;
        private boolean dataIsByRef = false;
        private int dataWidth, dataHeight;

        /**
         * Constructs a new ImageData buffer of the specified type with the
         * specified length.
         */
        ImageData(ImageDataType imageDataType, int length, int dataWidth, int dataHeight) {
            this.imageDataType = imageDataType;
            this.length = length;
            this.dataWidth = dataWidth;
            this.dataHeight = dataHeight;
            this.dataIsByRef = false;

            switch (imageDataType) {
                case TYPE_BYTE_ARRAY:
                    data = new byte[length];
                    break;
                case TYPE_INT_ARRAY:
                    data = new int[length];
                    break;
                case TYPE_BYTE_BUFFER:
                    ByteOrder order =  ByteOrder.nativeOrder();
                    data = ByteBuffer.allocateDirect(length).order(order);
                    break;
                case TYPE_INT_BUFFER:
                default:
                    throw new AssertionError();
            }
        }

        /**
         * Constructs a new ImageData buffer of the specified type with the
         * specified length and the specified byRefImage as data.
         */
        ImageData(ImageDataType imageDataType, int length, int dataWidth, int dataHeight,
                Object byRefImage) {
            BufferedImage bi;
            NioImageBuffer nio;

            this.imageDataType = imageDataType;
            this.length = length;
            this.dataWidth = dataWidth;
            this.dataHeight = dataHeight;
            this.dataIsByRef = true;

            switch (imageDataType) {
                case TYPE_BYTE_ARRAY:
                    bi = (BufferedImage) byRefImage;
                    data = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData();
                    break;
                case TYPE_INT_ARRAY:
                    bi = (BufferedImage) byRefImage;
                    data = ((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
                    break;
                case TYPE_BYTE_BUFFER:
                case TYPE_INT_BUFFER:
                    nio = (NioImageBuffer) byRefImage;
                    data = nio.getDataBuffer();
                    break;
                default:
                    throw new AssertionError();
            }
        }

        /**
         * Constructs a new ImageData buffer from the specified
         * object. This object stores a reference to the input image data.
         */
        ImageData(Object data, boolean isByRef) {
            this.data = data;
            dataIsByRef = isByRef;
            dataWidth = ((ImageData) data).dataWidth;
            dataHeight = ((ImageData) data).dataHeight;

            if (data == null) {
                imageDataType = ImageDataType.TYPE_NULL;
                length = 0;
            } else if (data instanceof byte[]) {
                imageDataType = ImageDataType.TYPE_BYTE_ARRAY;
                length = ((byte[]) data).length;
            } else if (data instanceof int[]) {
                imageDataType = ImageDataType.TYPE_INT_ARRAY;
                length = ((int[]) data).length;
            } else if (data instanceof ByteBuffer) {
                imageDataType = ImageDataType.TYPE_BYTE_BUFFER;
                length = ((ByteBuffer) data).limit();
            } else if (data instanceof IntBuffer) {
                imageDataType = ImageDataType.TYPE_INT_BUFFER;
                length = ((IntBuffer) data).limit();
            } else {
                assert false;
            }
        }

        /**
         * Returns the type of this DataBuffer.
         */
        ImageDataType getType() {
            return imageDataType;
        }

        /**
         * Returns the number of elements in this DataBuffer.
         */
        int length() {
            return length;
        }

        /**
         * Returns the width of this DataBuffer.
         */
        int getWidth() {
            return dataWidth;
        }

        /**
         * Returns the height of this DataBuffer.
         */
        int getHeight() {
            return dataHeight;
        }

        /**
         * Returns this DataBuffer as an Object.
         */
        Object get() {
            return data;
        }

        /**
         * Returns is this data is byRef. No internal data is made.
         */
        boolean isDataByRef() {
            return dataIsByRef;
        }


        /**
         * Returns this DataBuffer as a byte array.
         */
        byte[] getAsByteArray() {
            return (byte[]) data;
        }

        /**
         * Returns this DataBuffer as an int array.
         */
        int[] getAsIntArray() {
            return (int[]) data;
        }

        /**
         * Returns this DataBuffer as an nio ByteBuffer.
         */
        ByteBuffer getAsByteBuffer() {
            return (ByteBuffer) data;
        }

        /**
         * Returns this DataBuffer as an nio IntBuffer.
         */
        IntBuffer getAsIntBuffer() {
            return (IntBuffer) data;
        }

        // Handle TYPE_BYTE_LA only
        void copyByLineAndExpand(BufferedImage bi, int depthIndex) {
            int h;
            int srcBegin,		// src begin offset
                    dstBegin;		// dst begin offset

            assert (imageData.getType() == ImageDataType.TYPE_BYTE_ARRAY);
            assert (imageFormatType == ImageFormatType.TYPE_BYTE_LA);

            int unitsPerRow = width * unitsPerPixel; // bytes per row
            int scanline = unitsPerRow;
            if (yUp) {
                srcBegin = (depthIndex * width * height) * unitsPerPixel;
            } else {
                srcBegin = (depthIndex * width * height + (height - 1) * width) * unitsPerPixel;
                unitsPerRow = - 1 * unitsPerRow;
            }

            dstBegin = 0;
            // ABGR is 4 bytes per pixel
            int dstUnitsPerRow = width * 4;

            byte[] dstByteBuffer = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData();
            byte[] srcByteBuffer = imageData.getAsByteArray();
            for (h = 0; h < height; h++) {
                for( int v = 0, w = 0; w < scanline; w += unitsPerPixel, v += 4) {
                    dstByteBuffer[dstBegin+v] = srcByteBuffer[srcBegin+w+1]; // Alpha
                    dstByteBuffer[dstBegin+v+1] = 0;
                    dstByteBuffer[dstBegin+v+2] = 0;
                    dstByteBuffer[dstBegin+v+3] = srcByteBuffer[srcBegin+w]; // Red
                }

                dstBegin += dstUnitsPerRow;
                srcBegin += unitsPerRow;
            }

        }

        // Quick line by line copy
        void copyByLine(BufferedImage bi, int depthIndex, boolean swapNeeded) {

            int h;
            int srcBegin,		// src begin offset
                    dstBegin;		// dst begin offset

            int unitsPerRow = width * unitsPerPixel; // bytes per row
            int copyUnits = unitsPerRow;
            if (yUp) {
                srcBegin = (depthIndex * width * height) * unitsPerPixel;
            } else {
                srcBegin = (depthIndex * width * height + (height - 1) * width) * unitsPerPixel;
                unitsPerRow = - 1 * unitsPerRow;
            }

            dstBegin = 0;

            switch(imageData.getType()) {
                case TYPE_BYTE_ARRAY:
                    byte[] dstByteBuffer = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData();
                    byte[] srcByteBuffer = imageData.getAsByteArray();
                    for (h = 0; h < height; h++) {
                        if(!swapNeeded) {
                            System.arraycopy(srcByteBuffer, srcBegin,
                                    dstByteBuffer, dstBegin, copyUnits);
                        } else {
                            if(imageFormatType == ImageFormatType.TYPE_BYTE_RGB) {
                                assert (unitsPerPixel == 3);
                                for(int w = 0; w < copyUnits; w += unitsPerPixel) {
                                    dstByteBuffer[dstBegin+w] = srcByteBuffer[srcBegin+w+2];
                                    dstByteBuffer[dstBegin+w+1] = srcByteBuffer[srcBegin+w+1];
                                    dstByteBuffer[dstBegin+w+2] = srcByteBuffer[srcBegin+w];
                                }
                            } else if(imageFormatType == ImageFormatType.TYPE_BYTE_RGBA) {
                                assert (unitsPerPixel == 4);
                                for(int w = 0; w < copyUnits; w += unitsPerPixel) {
                                    dstByteBuffer[dstBegin+w] = srcByteBuffer[srcBegin+w+3];
                                    dstByteBuffer[dstBegin+w+1] = srcByteBuffer[srcBegin+w+2];
                                    dstByteBuffer[dstBegin+w+2] = srcByteBuffer[srcBegin+w+1];
                                    dstByteBuffer[dstBegin+w+3] = srcByteBuffer[srcBegin+w];
                                }
                            } else {
                                assert false;
                            }
                        }
                        dstBegin += copyUnits;
                        srcBegin += unitsPerRow;
                    }
                    break;

                    // INT case doesn't required to handle swapNeeded
                case TYPE_INT_ARRAY:
                    assert (!swapNeeded);
                    int[] dstIntBuffer = ((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
                    int[] srcIntBuffer = imageData.getAsIntArray();
                    for (h = 0; h < height; h++) {
                        System.arraycopy(srcIntBuffer, srcBegin, dstIntBuffer, dstBegin, copyUnits);
                        dstBegin += copyUnits;
                        srcBegin += unitsPerRow;
                    }
                    break;
                default:
                    assert false;
            }
        }

        void copyByBlock(BufferedImage bi, int depthIndex) {
            // src begin offset
            int srcBegin = depthIndex * width * height * unitsPerPixel;

            switch(imageData.getType()) {
                case TYPE_BYTE_ARRAY:
                    byte[] dstByteBuffer = ((DataBufferByte)bi.getRaster().getDataBuffer()).getData();
                    byte[] srcByteBuffer = imageData.getAsByteArray();
                    System.arraycopy(srcByteBuffer, srcBegin, dstByteBuffer, 0, (height * width * unitsPerPixel));
                    break;
                case TYPE_INT_ARRAY:
                    int[] dstIntBuffer = ((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
                    int[] srcIntBuffer = imageData.getAsIntArray();
                    System.arraycopy(srcIntBuffer, srcBegin, dstIntBuffer, 0, (height * width * unitsPerPixel));
                    break;
                default:
                    assert false;
            }
        }

        // Need to check for imageData is null. if it is null return null.
        BufferedImage createBufferedImage(int depthIndex) {
            if(data != null) {
                int bufferType = BufferedImage.TYPE_CUSTOM;
                boolean swapNeeded = false;

                switch(imageFormatType) {
                    case TYPE_BYTE_BGR:
                        bufferType = BufferedImage.TYPE_3BYTE_BGR;
                        break;
                    case TYPE_BYTE_RGB:
                        bufferType = BufferedImage.TYPE_3BYTE_BGR;
                        swapNeeded = true;
                        break;
                    case TYPE_BYTE_ABGR:
                        bufferType = BufferedImage.TYPE_4BYTE_ABGR;
                        break;
                    case TYPE_BYTE_RGBA:
                        bufferType = BufferedImage.TYPE_4BYTE_ABGR;
                        swapNeeded = true;
                        break;
                        // This is a special case. Need to handle separately.
                    case TYPE_BYTE_LA:
                        bufferType = BufferedImage.TYPE_4BYTE_ABGR;
                        break;
                    case TYPE_BYTE_GRAY:
                        bufferType = BufferedImage.TYPE_BYTE_GRAY;
                        break;
                    case TYPE_INT_BGR:
                        bufferType = BufferedImage.TYPE_INT_BGR;
                        break;
                    case TYPE_INT_RGB:
                        bufferType = BufferedImage.TYPE_INT_RGB;
                        break;
                    case TYPE_INT_ARGB:
                        bufferType = BufferedImage.TYPE_INT_ARGB;
                        break;
                        // Unsupported case, so shouldn't be here.
                    case TYPE_USHORT_GRAY:
                        bufferType = BufferedImage.TYPE_USHORT_GRAY;
                    default:
                        assert false;

                }

                BufferedImage bi = new BufferedImage(width, height, bufferType);
                if((!swapNeeded) && (imageFormatType != ImageFormatType.TYPE_BYTE_LA)) {
                    if(yUp) {
                        copyByBlock(bi, depthIndex);
                    } else {
                        copyByLine(bi, depthIndex, false);
                    }
                } else if(swapNeeded) {
                    copyByLine(bi, depthIndex, swapNeeded);
                } else if(imageFormatType == ImageFormatType.TYPE_BYTE_LA) {
                    copyByLineAndExpand(bi, depthIndex);
                } else {
                    assert false;
                }

                return bi;

            }
            return null;
        }

        void convertFromABGRToRGBA() {
            int i;

            if(imageDataType == ImageComponentRetained.ImageDataType.TYPE_BYTE_ARRAY) {
                // Note : Highly inefficient for depth > 0 case.
                // This method doesn't take into account of depth, it is assuming that
                // depth == 0, which is true for ImageComponent2D.
                byte[] srcBuffer, dstBuffer;
                srcBuffer = getAsByteArray();

                if(dataIsByRef) {
                    dstBuffer = new byte[length];
                    // Do copy and swap.
                    for(i = 0; i < length; i +=4) {
                        dstBuffer[i] = srcBuffer[i+3];
                        dstBuffer[i+1] = srcBuffer[i+2];
                        dstBuffer[i+2] = srcBuffer[i+1];
                        dstBuffer[i+3] = srcBuffer[i];
                    }
                    data = dstBuffer;
                    dataIsByRef = false;

                } else {
                    byte a, b;
                    // Do swap in place.
                    for(i = 0; i < length; i +=4) {
                        a = srcBuffer[i];
                        b = srcBuffer[i+1];
                        srcBuffer[i] = srcBuffer[i+3];
                        srcBuffer[i+1]  = srcBuffer[i+2];
                        srcBuffer[i+2] = b;
                        srcBuffer[i+3] = a;
                    }
                }
            }
            else if(imageDataType == ImageComponentRetained.ImageDataType.TYPE_BYTE_BUFFER) {

                assert dataIsByRef;
                ByteBuffer srcBuffer, dstBuffer;

                srcBuffer = getAsByteBuffer();
                srcBuffer.rewind();

                ByteOrder order =  ByteOrder.nativeOrder();
                dstBuffer = ByteBuffer.allocateDirect(length).order(order);
                dstBuffer.rewind();

                // Do copy and swap.
                for(i = 0; i < length; i +=4) {
                    dstBuffer.put(i, srcBuffer.get(i+3));
                    dstBuffer.put(i+1, srcBuffer.get(i+2));
                    dstBuffer.put(i+2, srcBuffer.get(i+1));
                    dstBuffer.put(i+3, srcBuffer.get(i));
                }

                dataIsByRef = false;

            }
        }
    }
}