OpenGL and variable arrays

Tutorials concerning the OpenGL® ES cross-platform API for full-function 2D and 3D graphics on the Google-Android platform.

OpenGL and variable arrays

Postby cg0601 » Sun Mar 21, 2010 6:38 pm

One of the things I was looking at was trying to create "moving" graphics with OpenGL. All the examples that show you how to create OpenGL told me that I need to use a java.nio.Buffer (IntBuffer or FloatBuffer for example), because these buffers are allocated in native memory.

One of the problems I have is that this makes using variable arrays rather difficult. This would be needed for example to bring a ripple in water or create a "sinusoid" line. I used the FloatBuffer.wrap() call to work around this, but I wonder if there are any side-effects that might prohibit me to work this way ? On my HTC Magic it works fine, and I have attached a project which shows how to do it here.

Any comments from the graphics Gurus ?

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.  
  2.     private void initVertices()
  3.  
  4.     {
  5.  
  6.         int[] lineCoords =
  7.  
  8.         {
  9.  
  10.                 -ONE * 3, ONE / 10 + 4 * ONE, 0, -ONE * 3, -ONE / 10 + 4 * ONE, 0, ONE * 3, ONE / 10, 0, ONE * 3, -ONE / 10, 0
  11.  
  12.         };
  13.  
  14.         lineCoordsBuffer = makeIntBuffer(lineCoords);
  15.  
  16.  
  17.  
  18.         float[] lineCoordsF =
  19.  
  20.         {
  21.  
  22.                 0.0f, 0.01f, 0, 0.0f, -0.01f, 0, 0.0f, 0.01f, -300, 0.0f, -0.01f, -300
  23.  
  24.         };
  25.  
  26.         lineCoordsFBuffer = makeFloatBuffer(lineCoordsF);
  27.  
  28.  
  29.  
  30.         for (int x = 0; x < FLAG_LEN; x++)
  31.  
  32.         {
  33.  
  34.             flagCoords[x * 3] = x / 100.0f;
  35.  
  36.             flagCoords[x * 3 + 1] = 0;
  37.  
  38.             flagCoords[x * 3 + 2] = 0;
  39.  
  40.         }
  41.  
  42.         flagCoords[1] = 1.0f;
  43.  
  44.         flagCoordsBuffer = FloatBuffer.wrap(flagCoords);
  45.  
  46.     }
  47.  
  48.  
  49.  
  50.     @Override
  51.  
  52.     public void onDrawFrame(GL10 gl)
  53.  
  54.     {
  55.  
  56.         gl.glEnable(GL10.GL_DITHER);
  57.  
  58.         gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
  59.  
  60.         gl.glMatrixMode(GL10.GL_MODELVIEW);
  61.  
  62.         gl.glLoadIdentity();
  63.  
  64.         drawBeam(gl);
  65.  
  66.         drawSinoid(gl);
  67.  
  68.     }
  69.  
  70.  
  71.  
  72.     private void drawSinoid(GL10 gl)
  73.  
  74.     {
  75.  
  76.         gl.glShadeModel(GL10.GL_FLAT);
  77.  
  78.         gl.glColor4f(1.0f, 1.0f, 0.2f, 1.0f);
  79.  
  80.  
  81.  
  82.         gl.glVertexPointer(3, GL10.GL_FLOAT, 0, flagCoordsBuffer);
  83.  
  84.  
  85.  
  86.         gl.glPushMatrix();
  87.  
  88.         gl.glTranslatef(-010f, 0, -35.0f);
  89.  
  90.         gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, FLAG_LEN);
  91.  
  92.         gl.glPopMatrix();
  93.  
  94.     }
  95.  
  96.  
Parsed in 0.036 seconds, using GeSHi 1.0.8.4


The actual code which changes the values in the arrays:

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.     public void setSensorValues(float[] values)
  2.  
  3.     {
  4.  
  5.         float newVal, oldVal, diff;
  6.  
  7.         for (int x = 0; x < 3; x++)
  8.  
  9.         {
  10.  
  11.             newVal = values[x] * -2.0f;
  12.  
  13.             oldVal = this.sensorValues[x];
  14.  
  15.             diff = newVal - oldVal;
  16.  
  17.             this.sensorValues[x] = oldVal + diff / 3.0f;
  18.  
  19.         }
  20.  
  21.         for (int counter = FLAG_LEN - 2; counter >= 0; counter--)
  22.  
  23.         {
  24.  
  25.             flagCoords[counter * 3 + 4] = flagCoords[counter * 3 + 1] * 0.998f;
  26.  
  27.         }
  28.  
  29.         flagCoords[1] = (float) (Math.sin(System.currentTimeMillis() / 1000.0) * 8.0f);
  30.  
  31.     }
  32.  
  33.  
Parsed in 0.033 seconds, using GeSHi 1.0.8.4


I know that this code uses an indirect NIO buffer, but would it hurt ?
Attachments
starter.zip
Sample OpenGL project showing different ways to call OpenGL
(34.71 KiB) Downloaded 126 times
cg0601
Junior Developer
Junior Developer
 
Posts: 16
Joined: Tue Aug 04, 2009 8:53 am

Top

Postby MichaelEGR » Mon Mar 22, 2010 1:08 am

Create the buffer once with the following code snippet.

All you have to do is do a put on the buffer that you save between frames with the local float[]. Also if you do any single value puts always do absolute puts instead of relative for speed. If memory is not a problem then keep a local float[] and do just one put per frame.

The following are a few methods in my BufferUtils class that creates a direct FloatBuffer.

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.  
  2.    /**
  3.  
  4.     * Construct a direct native-ordered bytebuffer with the specified size.
  5.  
  6.     *
  7.  
  8.     * @param size The size, in bytes
  9.  
  10.     * @return a ByteBuffer
  11.  
  12.     */
  13.  
  14.    public static ByteBuffer createByteBuffer(int size)
  15.  
  16.    {
  17.  
  18.       return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
  19.  
  20.    }
  21.  
  22.  
  23.  
  24.  
  25.  
  26.    /**
  27.  
  28.     * Construct a direct native-order floatbuffer with the specified number
  29.  
  30.     * of elements.
  31.  
  32.     *
  33.  
  34.     * @param size The size, in floats
  35.  
  36.     * @return a FloatBuffer
  37.  
  38.     */
  39.  
  40.    public static FloatBuffer createFloatBuffer(int size)
  41.  
  42.    {
  43.  
  44.       return createByteBuffer(size << 2).asFloatBuffer();
  45.  
  46.    }
  47.  
  48.  
Parsed in 0.035 seconds, using GeSHi 1.0.8.4


While you are using indirect buffers with wrap and it seemingly works there quite likely is some sort of conversion happening at a lower level in the Android GL implementation quite possibly creating a direct buffer each frame (one must verify this; just an educated guess!). Use the debugger (ddms / command line tool) and check it out. I didn't run your example, etc. Various GL bindings will do different things. If I recall correctly for instance LWJGL will throw an exception when the buffer is not direct.

You can improve this loop. You are already counting backwards with a >= 0 check which is the fastest, so that is good.

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.  
  2.         for (int counter = FLAG_LEN - 2; counter >= 0; counter--)
  3.  
  4.         {
  5.  
  6.             flagCoords[counter * 3 + 4] = flagCoords[counter * 3 + 1] * 0.998f;
  7.  
  8.         }
  9.  
  10.  
Parsed in 0.036 seconds, using GeSHi 1.0.8.4


You can do a few more things to speed up this loop. First perhaps look into "inflating" the counter. If the FLAG_LEN is 10 then make counter 30 and decrement by 3. This will remove the counter*3 multiplies (also if you ever have to do the same operation twice or more store it in a temporary variable). The next thing you can do is unroll the loop. Do 10 iterations in one loop cycle. If you don't know the length of the flagCoords before running the code look into something called Duff's device. If you are working with the same length object each time you can calculate any variables for Duff's device once or simply have a tailor made unrolled loop.

As it is I'm sure for one waving flag you get a nice frame rate, but with more dynamic objects per frame things can slow down fairly quickly. You can also test and see how adding more flag coordinates scales and simply place your camera / view further away.
Founder & Principal Architect; EGR Software LLC
http://www.typhonrt.org
http://www.egrsoftware.com
User avatar
MichaelEGR
Senior Developer
Senior Developer
 
Posts: 147
Joined: Thu Jan 21, 2010 5:30 am
Location: San Francisco, CA

Indirect buffers

Postby cg0601 » Mon Mar 22, 2010 8:32 am

Thanks for your reply, a real good explanation! I have used the direct buffers before, just like you suggest, but I had trouble in putting the updates back into the buffer. I know I can do a put(index, float) and will most likely adapt my program in using it.

The example code is far from optimized, I know, the main goal was to get something working in a "quick" and dirty way.

The sample program did not create any new objects, nor did it cause any GC happening. But ... the main reason for getting away from the indirect buffers, created by wrap is that indirect buffers can no longer be used. I read somewhere that Android will do checks in the future to avoid using them. Another reason is that although it seems like the indirect buffer is "fixed" in memory, there is no guarantee that the JVM will not reallocate it sooner or later. If this happens between the glVertexPointer call and the actual draw, you would be in trouble.

Coming back to the use of direct buffers and using "put" to change the values:

When I tried it first, I saw all kind of lines being drawn, which made no sense to me. Using "wrap" fixed this, but wrap is no option. I tried putting "synchronized" around the code, but it did not fix it either.

I realize now, that if to avoid these spikes, I need to do the copy from the float[] array into the buffer during the call to my "onDraw(gl)" call.

If I want to have a waving flag, I will need to update all vertices in most cases, so I wonder it it might be faster, if I do something like this:

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.  
  2. floatBuffer.position(0);
  3.  
  4. floatBuffer.put(flagCoords);
  5.  
  6. floatBuffer.position(0);
  7.  
  8.  
Parsed in 0.036 seconds, using GeSHi 1.0.8.4


Or should I do individual "puts" for all float values in the array?
cg0601
Junior Developer
Junior Developer
 
Posts: 16
Joined: Tue Aug 04, 2009 8:53 am

Re: Indirect buffers

Postby MichaelEGR » Mon Mar 22, 2010 9:02 am

cg0601 wrote:I have used the direct buffers before, just like you suggest, but I had trouble in putting the updates back into the buffer. I know I can do a put(index, float) and will most likely adapt my program in using it.


Doing put(float[]) will be fastest quite likely, but don't forget to call "rewind" after the put. I can't recall off the top of my head if you have to have the direct buffer in the rewind state before sending it to the Android GL implementation, but that is what I do and I do recall LWJGL and/or JOGL complaining / not working if the buffer is not rewound. "position(0)" should effectively be the same thing, but I always have used "rewind".

Try both the absolute single puts and filling a float[] and a single put. If you can spare the memory overhead of having a local float[] for your app quite likely that will be the fastest over absolute single puts.

cg0601 wrote:The sample program did not create any new objects, nor did it cause any GC happening.


Your program may not be creating new objects, but the underlying Android GL implementation might be doing that. There is a nasty FloatBuffer to ByteBuffer conversion or something screwy under the hood as far as object / memory creation with glDrawElements and other methods. Something similar could be happening when you are using indirect buffers. You'd see it when profiling with the debugger / ddms.

cg0601 wrote:Coming back to the use of direct buffers and using "put" to change the values:
When I tried it first, I saw all kind of lines being drawn, which made no sense to me. Using "wrap" fixed this, but wrap is no option. I tried putting "synchronized" around the code, but it did not fix it either.

I realize now, that if to avoid these spikes, I need to do the copy from the float[] array into the buffer during the call to my "onDraw(gl)" call.

If I want to have a waving flag, I will need to update all vertices in most cases, so I wonder it it might be faster, if I do something like this:

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. floatBuffer.position(0);
  2. floatBuffer.put(flagCoords);
  3. floatBuffer.position(0);
  4.  
Parsed in 0.035 seconds, using GeSHi 1.0.8.4

Or should I do individual "puts" for all float values in the array?


Yep.. I do something like this:

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.  
  2. floatBuffer.put(flagCoords);
  3.  
  4. floatBuffer.rewind();
  5.  
  6.  
Parsed in 0.036 seconds, using GeSHi 1.0.8.4


You don't need the "rewind" in front or "position(0)" if the buffer is already rewound. The individual absolute puts shouldn't be faster than filling a local array and one put, but it might not be too bad; relative puts is where performance can degrade with dynamic buffer filling.

Quite possibly the errors / lines and other artifacts you had previously is due to improper rewind state / buffer filling.

Best of luck! :)
Founder & Principal Architect; EGR Software LLC
http://www.typhonrt.org
http://www.egrsoftware.com
User avatar
MichaelEGR
Senior Developer
Senior Developer
 
Posts: 147
Joined: Thu Jan 21, 2010 5:30 am
Location: San Francisco, CA

Waving flag

Postby cg0601 » Tue Mar 23, 2010 2:43 pm

Thanks for your tips, I managed to get the waving flag working and can easily obtain 20 fps, even with 2 flags waving.
The main reason why I saw the lines, etc was due to the fact that I let my GLSurfaceView do a continuous rendering, which is the default. Somehow it seemed to decide to do them in the middle of my updates.

Setting the mode to RENDERMODE_WHEN_DIRTY and call "requestRender()" from a timer got rid of these problems.

Next project is rain drops in water, but here the CPU speed is the main problem. I got something working alright, but the calculations to do the graphics need > 1.5 second, so I can do only 0.5 FPS, which is no fun at all.
Doing the Graphics, even using Canvas takes like 8-10 ms, so I did not bother using OpenGL yet.
Guess, I'll have to wait till I upgrade my Magic to something faster.
BTW, the calculations is all int- based, so not even need for doing float calculations.

(Side-note 1: I wonder how they do it on the iPhone, I don't think their CPU is so much faster ? Unless they have a math co-processor)
(Side-note 2: Maybe I need to go to JNI code to do the arithmetic ?)
cg0601
Junior Developer
Junior Developer
 
Posts: 16
Joined: Tue Aug 04, 2009 8:53 am

Postby zorro » Tue Apr 27, 2010 8:08 am

iPhone does have a co-processor, floating point calculations are more friendly there.
On Android, for such heavy calculations as yours, JNI will increase performance for sure. I heard that in the next Android release (2.2) there will be a JIT compiler, so all applications will run much faster in heavy-math scenarios.
User avatar
zorro
Experienced Developer
Experienced Developer
 
Posts: 71
Joined: Mon Aug 10, 2009 3:11 pm
Location: Romania

Top

Return to Android 2D/3D Graphics - OpenGL Tutorials

Who is online

Users browsing this forum: No registered users and 4 guests