hi, starting to learn about vbo's and textures.
i can put about 1 million points into opengl in about .25 ms on my pc. but the fps is only about 170 for a texture and around 20-60 for a vbo. jvisualvm says that most of the time is spent in AWTAnimatorImpl.display() & GLDrawableHelper.displayImpl(). i am using a GLJPanel like http://www3.ntu.edu.sg/home/ehchua/programming/opengl/JOGL2.0.html and i only have the panel in the jframe. there are no other components. is this normal? thanks |
Administrator
|
Most of the time is spent in executing the OpenGL commands, it is not surprising. I don't remember which hardware you use and I have no access to your source code, I can't check if you do something really bad.
Julien Gouesse | Personal blog | Website
|
here is a self contained example: http://stackoverflow.com/questions/12324994/how-do-i-get-more-frames-per-second-from-this-jogl-program that reports render and frame times. it is the same as the code in http://forum.jogamp.org/vbo-DrawArrays-threading-question-td4026028.html without the update code on the timer and without the time reporting.
i am using windows 7 x64 with an intel i3 that has on-chip graphics, so that may be part of the problem. running the code from http://stackoverflow.com/questions/12324994/how-do-i-get-more-frames-per-second-from-this-jogl-program show about .3 ms. to wrap the opengl calls in display, but the actual frame rate is only about 70 fps. thanks |
Administrator
|
Just disable v-sync.
Julien Gouesse | Personal blog | Website
|
At 12:24 PM 9/8/2012, you wrote:
>Just disable v-sync. >Julien Gouesse http://tuer.sourceforge.nethttp://gouessej.wordpress.com i set the vsync on the intel graphics to application instead of "on". doing a setSwap() gives me about 80 fps instead of 70. thanks --- co-chair http://ocjug.org/ |
i ran a furmark. i get 2 fps runnung 1080 preset. typical scores for grahics cards http://www.geeks3d.com/20120413/furmark-opengl-benchmark-scores-comparative-charts/ indicate that most of my problem is in the on-chip graphics. i have ordered a graphics card.
thanks |
Administrator
|
Intel on-board graphics chips are known to be extremely slow, even old graphics cards sold in 2004 are sometimes faster. I think that even a very cheap ATI or Nvidia AGP graphics card would be faster.
If you really want to support Intel chips, you will have to elaborate powerful algorithmic optimizations to reduce the size of the VBOs and it will be really useful only if Intel drivers are not too buggy anyway. Edit.: FBOs are slow on Intel. If you could replace your GLJPanel by a GLCanvas or a GLWindow, you would probably get a better frame rate (but still disable v-sync).
Julien Gouesse | Personal blog | Website
|
At 01:47 AM 9/9/2012, you wrote:
>Intel on-board graphics chips are known to be extremely slow, .... >If you really want to support Intel chips, you will have to >elaborate powerful algorithmic optimizations i do not need to support this. the system will have a modern graphics card. i ordered a Asus - AMD Radeon HD 6450 1GB DDR3 PCI Express 2.1 Graphics Card for experiments. thanks --- co-chair http://ocjug.org/ |
Administrator
|
You will see a huge difference with such a nice graphics card :)
Julien Gouesse | Personal blog | Website
|
At 01:18 AM 9/10/2012, you wrote:
>You will see a huge difference with such a nice graphics card :) >Julien Gouesse http://tuer.sourceforge.nethttp://gouessej.wordpress.com yes, i am looking forward to it. in the meantime i am writing some tests and looking at the same kind of problem in a pure swing app. thanks --- co-chair http://ocjug.org/ |
Administrator
|
Do you mean that even a pure Swing application without any JOGL code is slow?
Julien Gouesse | Personal blog | Website
|
At 11:17 PM 9/10/2012, you wrote:
>Do you mean that even a pure Swing application without any JOGL code is slow? >Julien Gouesse http://tuer.sourceforge.nethttp://gouessej.wordpress.com slow is part of the problem. i have a new piece of data that comes in every 5 ms. and i would like to repaint just that new data, but the region is a radial wedge about 1 degree wide. repainting the whole region is slow (http://stackoverflow.com/questions/11832943/how-do-i-speed-up-this-swing-animation). using some clipping rectangle does not seem to help much. using the actual shape to clip does not seem to work (at least for me, http://stackoverflow.com/questions/12139461/how-to-use-clip-to-reduce-paint-time ) one issue is that i can't seem to get a sub millisecond timer to work in windoze without using a spinlock on system.nanos(), which burns up a cpu :( - but the real system will have 4 cores and 8 threads so that may not be a problem. i may experiment with full screen mode while i wait for my graphics card to arrive. but my intuition tells me i am missing something obvious. i only have one 1024x1024 buffered image to display and a sweeping radial line. all of the data in the buffered image changes every 2 seconds. so this feels like it could be done in a pure swing app. if my associate gets the contract, he wants to use c++ and opengl. my position is that it probably can be done in pure java, but may need to use opengl through jogl. thanks --- co-chair http://ocjug.org/ |
Administrator
|
Please use "our" hack to get some more precision under Windows, we just use a thread that calls Thread.sleep() with a very long time and it forces the OS to use an high precision timer.
If you have a sweeping radial line and a map, you can use a texture and a few primitives to draw this line instead of using a big buffer to contain all pixels.
Julien Gouesse | Personal blog | Website
|
At 01:25 AM 9/11/2012, you wrote:
>Please use "our" hack to get some more precision under Windows, ... >Julien Gouesse http://tuer.sourceforge.nethttp://gouessej.wordpress.com if you mean this hack (please see code below). it does not seem to make much difference. i get about a 1 ms. timer in either case; thanks import java.util.Arrays; class Runner implements Runnable { @Override public void run() { while(true) { try { Thread.sleep(1); } catch(InterruptedException e) { e.printStackTrace(); } long t1=System.nanoTime(); long dt=t1-t0; t0=t1; double millis=dt/1000000.; dts[i++]=millis; if(i>=10) { System.out.println(Arrays.asList(dts)); i=0; m++; if(m>10) break; } } } long t0; int i,m,n=10; Double[] dts=new Double[n]; } public class ThreadTimer2 { public static void main(String[] args) throws Exception { if(useHack) new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(Integer.MAX_VALUE); } catch(InterruptedException e) { e.printStackTrace(); } } }).start(); Runner r=new Runner(); r.t0=System.nanoTime(); new Thread(r).start(); } static boolean useHack=false; } --- co-chair http://ocjug.org/ |
In reply to this post by gouessej
At 01:25 AM 9/11/2012, you wrote:
>... If you have a sweeping radial line and a map, you can use a >texture and a few primitives to draw this line instead of using a >big buffer to contain all pixels. >Julien Gouesse http://tuer.sourceforge.nethttp://gouessej.wordpress.com the sweeping radial line is only 1 pixel wide. while the wedge of new data is about 1 degree wide, so i need the 1024x1024 buffered image whether i fraw the radial line or not. thanks --- co-chair http://ocjug.org/ |
Administrator
|
I don't understand why you don't just display the image in the background as a texture, you don't need to put all its pixels into a VBO.
Julien Gouesse | Personal blog | Website
|
At 11:01 PM 9/11/2012, you wrote:
>I don't understand why you don't just display the image in the >background as a texture, you don't need to put all its pixels into a VBO. >Julien Gouesse http://tuer.sourceforge.nethttp://gouessej.wordpress.com i get about 160 fps when i use a texture. this is probably good enough. and will just get better when i get my graphics card. but jvisualvm still says 80% of the time is spent in those two routines. maybe my graphics card will make that go away also. most of the code that runs my sample is below thanks package jogl; import static jogl.GLUtilities.*; import static javax.media.opengl.GL.*; import static javax.media.opengl.GL2.*; import static javax.media.opengl.GL2ES1.*; import static javax.media.opengl.fixedfunc.GLLightingFunc.*; import static javax.media.opengl.fixedfunc.GLMatrixFunc.*; import static javax.media.opengl.fixedfunc.GLPointerFunc.*; import java.awt.Color; import java.awt.Point; import java.io.File; import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import javax.media.opengl.GL; import javax.media.opengl.GL2; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLException; import javax.media.opengl.GLProfile; import javax.media.opengl.awt.GLJPanel; import javax.swing.SwingUtilities; import javax.vecmath.Vector3d; import com.jogamp.common.nio.Buffers; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureCoords; import com.jogamp.opengl.util.texture.TextureData; import com.jogamp.opengl.util.texture.TextureIO; import com.jogamp.opengl.util.texture.TextureData.Flusher; class MyVbo { MyVbo(GLAutoDrawable drawable,int n0) { this.n=2*n0; System.out.println(n*n+" vertices"); } void init(GLAutoDrawable drawable) { float[] v=new float[3*n*n]; for(int x=0;x<n;x++) for(int y=0;y<n;y++) { int index=3*(n*y+x); v[index+0]=x; v[index+1]=y; v[index+2]=0f; } int[] ia=new int[n*n]; for(int i=0;i<ia.length;i++) ia[i]=i; // System.out.println(print3f(v)); float[] vertexArray={-0.5f,0.5f,0,0.5f,0.5f,0,0.5f,-0.5f,0,-0.5f,-0.5f,0}; vertexArray=v; System.out.println(v.length); FloatBuffer vertices=Buffers.newDirectFloatBuffer(vertexArray.length); vertices.put(vertexArray); vertices.flip(); int[] indexArray={0,1,2,1/* 0 */,2,3}; indexArray=ia; IntBuffer indices=Buffers.newDirectIntBuffer(indexArray.length); indices.put(indexArray); indices.flip(); GL gl=drawable.getGL(); gl.glGenBuffers(1,VBOVertices,0); gl.glBindBuffer(GL.GL_ARRAY_BUFFER,VBOVertices[0]); gl.glBufferData(GL.GL_ARRAY_BUFFER,vertices.capacity()*Buffers.SIZEOF_FLOAT,vertices,GL.GL_DYNAMIC_DRAW); gl.glBindBuffer(GL.GL_ARRAY_BUFFER,0); gl.glGenBuffers(1,VBOIndices,0); gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER,VBOIndices[0]); gl.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER,indices.capacity()*Buffers.SIZEOF_INT,indices,GL.GL_DYNAMIC_DRAW); gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER,0); indicesCapacity=indices.capacity(); } void render(GLAutoDrawable drawable) { GL2 gl=drawable.getGL().getGL2(); final Color color=Color.white; setColor(color,gl); if(!once) { System.out.print("vbo color is "); printColor(gl); once=true; } gl.glPushMatrix(); gl.glTranslated(.5,+.5,0); gl.glScaled(.25/n,.25/n,1); gl.glEnableClientState(GL_VERTEX_ARRAY); gl.glBindBuffer(GL_ARRAY_BUFFER,VBOVertices[0]); gl.glVertexPointer(3,GL.GL_FLOAT,0,0); gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,VBOIndices[0]); gl.glDrawElements(GL_POINTS,indicesCapacity,GL.GL_UNSIGNED_INT,0); gl.glDisableClientState(GL_VERTEX_ARRAY); gl.glPopMatrix(); } private int[] VBOVertices=new int[1]; private int[] VBOIndices=new int[1]; private int indicesCapacity; final int n; boolean once; //final Point[] points; } class MyImage { MyImage(int radius) { this.radius=radius; System.out.println(4*radius*radius+" pixels"); } void init() { create(); putImageIntoBuffer(); } void create() { image=new Color[4*radius*radius]; for(int x=0;x<2*radius;x++) for(int y=0;y<2*radius;y++) { int index=2*radius*y+x; if(x<radius/2&&y<radius/2) image[index]=Color.red; else image[index]=Color.white; } } void putImageIntoBuffer() { byteBuffer=Buffers.newDirectByteBuffer(rgba*image.length); byte c=(byte)0xFF; for(int x=0;x<2*radius;x++) for(int y=0;y<2*radius;y++) { int index=2*radius*y+x; int argb=image[index].getRGB(); byteBuffer.put((byte)((argb>>16)&0xff)); byteBuffer.put((byte)((argb>>8)&0xff)); byteBuffer.put((byte)((argb>>0)&0xff)); byteBuffer.put((byte)((argb>>24)&0xff)); } byteBuffer.rewind(); } void render(GLAutoDrawable drawable,Point mousePoint,float zoomFactor,int height,int screeny) { GL2 gl=drawable.getGL().getGL2(); final Color color=Color.white; setColor(color,gl); if(!once) { System.out.print("image color is "); printColor(gl); once=true; } gl.glPushMatrix(); if(mousePoint!=null) { System.out.println(mousePoint); screeny=height-(int)mousePoint.getY(); gl.glRasterPos2i(mousePoint.x,screeny); gl.glPixelZoom(zoomFactor,zoomFactor); gl.glCopyPixels(0,0,2*radius,2*radius,GL2.GL_COLOR); // gl.glPixelZoom(1.0f, 1.0f); // mousePoint = null; } else gl.glRasterPos2d(.25,.25); gl.glPixelZoom(.1f,.1f); gl.glDrawPixels(2*radius,2*radius,GL.GL_RGBA,GL.GL_UNSIGNED_BYTE,byteBuffer); gl.glFlush(); gl.glPopMatrix(); } private Color[] image; private final int radius; private ByteBuffer byteBuffer; boolean once; } class T2 { T2(int radius) { this(radius,new Vector3d(0,0,0),1); } T2(int radius,Vector3d offset,double scaleFactor) { this.radius=radius; this.offset=offset; this.scaleFactor=scaleFactor; System.out.println(4*radius*radius+" pixels"); } void create() { image=new Color[4*radius*radius]; for(int x=0;x<2*radius;x++) for(int y=0;y<2*radius;y++) { int index=2*radius*y+x; if(x<radius/2&&y<radius/2) image[index]=Color.green; else image[index]=Color.white; } } void putImageIntoBuffer() { byteBuffer=Buffers.newDirectByteBuffer(4*image.length); for(int x=0;x<2*radius;x++) for(int y=0;y<2*radius;y++) { int index=2*radius*y+x; int argb=image[index].getRGB(); byteBuffer.put((byte)((argb>>16)&0xff)); byteBuffer.put((byte)((argb>>8)&0xff)); byteBuffer.put((byte)((argb>>0)&0xff)); byteBuffer.put((byte)((argb>>24)&0xff)); } byteBuffer.rewind(); } void init(GLAutoDrawable drawable) { create(); putImageIntoBuffer(); GL2 gl=drawable.getGL().getGL2(); // Buffer buffer=Buffers.newDirectIntBuffer(4*radius*radius); TextureData textureData=new TextureData(GLProfile.getDefault(),GL_RGBA,2*radius,2*radius,0,GL_RGBA,GL_UNSIGNED_BYTE,false,false,true,byteBuffer,(Flusher)null); texture=TextureIO.newTexture(textureData); gl.glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); gl.glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); TextureCoords textureCoords=texture.getImageTexCoords(); System.out.println("texture from ctor: "+textureCoords); textureTop=textureCoords.top(); textureBottom=textureCoords.bottom(); textureLeft=textureCoords.left(); textureRight=textureCoords.right(); } public void render(GLAutoDrawable drawable) { final GL2 gl=drawable.getGL().getGL2(); gl.glPushMatrix(); final Color color=Color.white; setColor(color,gl); if(!once) { System.out.print("t2 color is "); printColor(gl); } gl.glEnableClientState(GL_TEXTURE_COORD_ARRAY); texture.enable(gl); // same as gl.glEnable(texture.getTarget()); // gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE); // Binds this texture to the current GL context. texture.bind(gl); // same as gl.glBindTexture(texture.getTarget(), texture.getTextureObject()); Vector3d ll=new Vector3d(-1,-1,0); Vector3d lr=new Vector3d(1,-1,0); Vector3d ur=new Vector3d(1,1,0); Vector3d ul=new Vector3d(-1,1,0); if(!once) System.out.println("texture from ctor: "+ll+" "+lr+" "+ur+" "+ul); ll.scaleAdd(scaleFactor,offset); lr.scaleAdd(scaleFactor,offset); ur.scaleAdd(scaleFactor,offset); ul.scaleAdd(scaleFactor,offset); if(!once) { System.out.println("texture from ctor: "+ll+" "+lr+" "+ur+" "+ul); once=true; } gl.glBegin(GL_QUADS); gl.glTexCoord2f(textureLeft,textureBottom); gl.glVertex3d(ll.x,ll.y,ll.z); // bottom-left of the texture and quad gl.glTexCoord2f(textureRight,textureBottom); gl.glVertex3d(lr.x,lr.y,lr.z); // bottom-right of the texture and quad gl.glTexCoord2f(textureRight,textureTop); gl.glVertex3d(ur.x,ur.y,ur.z); // top-right of the texture and quad gl.glTexCoord2f(textureLeft,textureTop); gl.glVertex3d(ul.x,ul.y,ul.z); // top-left of the texture and quad gl.glEnd(); texture.disable(gl); gl.glDisableClientState(GL_TEXTURE_COORD_ARRAY); gl.glPopMatrix(); } final int radius; boolean once; private Color[] image; private Texture texture; double scaleFactor; Vector3d offset; private float textureTop,textureBottom,textureLeft,textureRight; private ByteBuffer byteBuffer; } class MyTexture { MyTexture(File file) { this.file=file; } void init(GLAutoDrawable drawable) { GL2 gl=drawable.getGL().getGL2(); try { texture=TextureIO.newTexture(file,false); // Use linear filter for texture if image is larger than the original texture gl.glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // Use linear filter for texture if image is smaller than the original texture gl.glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // Texture image flips vertically. Shall use TextureCoords class to retrieve // the top, bottom, left and right coordinates, instead of using 0.0f and 1.0f. TextureCoords textureCoords=texture.getImageTexCoords(); System.out.println("texture from file: "+textureCoords); textureTop=textureCoords.top(); textureBottom=textureCoords.bottom(); textureLeft=textureCoords.left(); textureRight=textureCoords.right(); } catch(GLException e) { e.printStackTrace(); } catch(IOException e) { e.printStackTrace(); } } public void render(GLAutoDrawable drawable) { GL2 gl=drawable.getGL().getGL2(); final Color color=Color.white; setColor(color,gl); if(!once) { System.out.print("t1 color is "); printColor(gl); } gl.glPushMatrix(); gl.glEnableClientState(GL_TEXTURE_COORD_ARRAY); texture.enable(gl); // same as gl.glEnable(texture.getTarget()); // gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE); // Binds this texture to the current GL context. texture.bind(gl); // same as gl.glBindTexture(texture.getTarget(), texture.getTextureObject()); Vector3d ll=new Vector3d(-1,-1,0); Vector3d lr=new Vector3d(1,-1,0); Vector3d ur=new Vector3d(1,1,0); Vector3d ul=new Vector3d(-1,1,0); Vector3d offset=new Vector3d(.75,.25,0); if(!once) System.out.println(ll+" "+lr+" "+ur+" "+ul); if(true) { ll.scaleAdd(.05,offset); lr.scaleAdd(.05,offset); ur.scaleAdd(.05,offset); ul.scaleAdd(.05,offset); } if(!once) { System.out.println(ll+" "+lr+" "+ur+" "+ul); once=true; } // System.out.println(ul+" "+ur+" "+ll+" "+lr); gl.glBegin(GL_QUADS); // gl.glNormal3f(0,0,1); gl.glTexCoord2f(textureLeft,textureBottom); gl.glVertex3d(ll.x,ll.y,ll.z); // bottom-left of the texture and quad gl.glTexCoord2f(textureRight,textureBottom); gl.glVertex3d(lr.x,lr.y,lr.z); // bottom-right of the texture and quad gl.glTexCoord2f(textureRight,textureTop); gl.glVertex3d(ur.x,ur.y,ur.z); // top-right of the texture and quad gl.glTexCoord2f(textureLeft,textureTop); gl.glVertex3d(ul.x,ul.y,ul.z); // top-left of the texture and quad gl.glEnd(); texture.disable(gl); gl.glDisableClientState(GL_TEXTURE_COORD_ARRAY); gl.glPopMatrix(); } File file; private Texture texture; int width,height; boolean once; private float textureTop,textureBottom,textureLeft,textureRight; } public class ComboGLJPanel extends JOGL2Setup { ComboGLJPanel(GLJPanel gljPanel) { super(gljPanel); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { GLJPanel panel=new GLJPanel(); new ComboGLJPanel(panel); JOGL2Setup.setupFrame(panel,200); } }); } void update() { angle+=.1*2.*360/fps; } @Override public void init(GLAutoDrawable drawable) { super.init(drawable); myVbo=new MyVbo(drawable,512); myVbo.init(drawable); myImage=new MyImage(512); myImage.init(); GL2 gl=drawable.getGL().getGL2(); gl.glShadeModel(GL_FLAT); gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT,1); myTexture=new MyTexture(new File(textureFileName)); myTexture.init(drawable); t2=new T2(512,new Vector3d(.75,.75,0),.1); t2.init(drawable); drawAxes=false; reportTime=true; subclassWillEndTimeReporting=true; } @Override public void display(GLAutoDrawable drawable) { super.display(drawable); GL2 gl=drawable.getGL().getGL2(); //myTexture.render(drawable); //myImage.render(drawable,mousePoint,zoomFactor,height2,screeny); //myVbo.render(drawable); t2.render(drawable); endTimeReporting(); //AWTAnimatorImpl.display() GLDrawableHelper.displayImpl(); // .22 ms/ 170 fps texture (just colors) // .26 ms/ 21 fps vbo (points and indices, no color) // 20 ms 40 fps image // .22 ms 60 fps wade's vbo (points and colors) } @Override public void reshape(GLAutoDrawable drawable,int x,int y,int width,int height) { super.reshape(drawable,x,y,width,height); height2=height; } MyImage myImage; float zoomFactor=1; int height2; int screeny; MyVbo myVbo; MyTexture myTexture; private String textureFileName="resources/nehe.png"; T2 t2; private Point mousePoint; } --- co-chair http://ocjug.org/ |
In reply to this post by gouessej
At 11:01 PM 9/11/2012, you wrote:
>I don't understand why you don't just display the image in the >background as a texture, you don't need to put all its pixels into a VBO. >Julien Gouesse http://tuer.sourceforge.nethttp://gouessej.wordpress.com another reason i am avoiding textures for the moment is that i will need to update that pie-wedge piece of the texture every 5 ms. or so. given my current understanding if the opengl api, it would be easier to update the colors of part of a vbo (or of an entire vbo if i split the vbo up into 400 pieces). thanks --- co-chair http://ocjug.org/ |
Administrator
|
Updating the texture would probably be more efficient and you should still drop the GLJPanel as it uses a FBO under the hood, it works fine on drivers and graphics cards with an excellent support of FBO. I had to wait for years to get excellent support of FBO on some Nvidia Quadro FX cards, even the 2000. If you could give a try to GLCanvas, you should notice a real difference.
Edit.: most of your work is done in the OpenGL code and you disable v-sync, you will spend most of the time in display() but it is not a problem, there is no need to worry. Just re-enable v-sync if you don't want to eat so much CPU time but with a better graphics card.
Julien Gouesse | Personal blog | Website
|
At 11:26 AM 9/12/2012, you wrote:
>Updating the texture would probably be more efficient and you should >still drop the GLJPanel as it uses a FBO under the hood,... If you >could give a try to GLCanvas, you should notice a real difference. >Julien Gouesse http://tuer.sourceforge.nethttp://gouessej.wordpress.com \ using a canvas increased the fps from 170 to 200 for the texture. thanks --- co-chair http://ocjug.org/ |
Free forum by Nabble | Edit this page |