Sign in to follow this  

JOGL Rendering many objects with VBO and Display Lists

This topic is 3484 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

This code draws a circular ball. xn and yn are the center of the ball, and POINTS_X and POINTS_Y are the coordinates of the points on the circle at the origin. RESOLUTION is 12, so this code actually draws a 12-sided polygon. I can currently draw about 20,000 bouncing balls at 85+ fps, but I feel that I should be able to draw millions of them considering the power of current video cards and processors. I have a very recently built computer. I have determined that the only part of my code that uses significant time is this draw function, which is called once per ball per frame. I was wondering if there was a more efficient way to draw all of the balls, or if 20,000 is a reasonable limit?
	public void draw(GL gl) {
		if (isWhirling) {
			gl.glColor3f(0.0f, 1.0f, 0.0f);
		} else {
			gl.glColor3f(red, green, blue);
		}
		gl.glBegin(GL.GL_POLYGON);
		for (int a = 0; a < RESOLUTION; a++) {
			gl.glVertex2d(xn+POINTS_X[a], yn+POINTS_Y[a]);
		}
		
		gl.glEnd();
	}


[Edited by - jamesdk2006 on May 29, 2008 8:52:00 PM]

Share this post


Link to post
Share on other sites
Is there a faster way? Absolutely. You need to use a display list. A display list is a collection of primitives (with accompanying attributes like normals, colors, texture coordinates etc.) stored in your graphics card's memory, which means that you can eliminate the slow AGP/PCIe transfer and simply render the entire mesh with just one number/handle.

Here's how you do it:

// Get one available object handle from OpenGL
unsigned int display_list_handle = gl.glGenLists(1);

// Attach a display list to this handle
gl.glNewList(display_list_handle, GL_COMPILE);
gl.glBegin(GL.GL_POLYGON);
for (int a = 0; a < RESOLUTION; a++)
{
gl.glVertex2d(POINTS_X[a], POINTS_Y[a]);
}
gl.glEnd();
gl.glEndList();

// The display list is now ready for use.
// Use glTranslate(x, y, z) to specify the center for each polygon

gl.glLoadIdentity();
gl.glTranslate(xn, yn, 0);
gl.glCallList(display_list_handle);





Here's some additional info from MSDN:

glNewList, glEndList
glCallList

Share this post


Link to post
Share on other sites
The better way would be to use VBO Vertex Buffer Objects, because display lists are not the standard. If you search for it, you will find many tutorials.

Share this post


Link to post
Share on other sites
Quote:
Original post by Ingrater
The better way would be to use VBO Vertex Buffer Objects, because display lists are not the standard. If you search for it, you will find many tutorials.


There's absolutely nothing wrong with using a display list for a simple 12-sided polygon.
If you don't need the extra functionality which comes with a VBO, then use a display list.

Share this post


Link to post
Share on other sites
If you are learning GL, then I suggest that you forget about immediate mode which is what you are doing. Display list is ok and should give plenty of speed. With display list, the driver will convert your data to a suitable format so that it runs fast. For example, glVertex2d might get converted to float. glColor3f might get an alpha. Memory might be padded. It might interleave vertices and color.

A Gf 8 should be able to do something like 30 billion triangles theoretically but keep in mind that most programs are fragment limited, not vertex transform limited.

I prefer VBO over display list since it seems cleaner, makes my engine simpler.

For VBO info
http://www.opengl.org/wiki/index.php/GL_ARB_vertex_buffer_object

Share this post


Link to post
Share on other sites
You need no more than one display list. That is one for every model/mesh you have. The model/mesh in the display list should be centered around <0, 0, 0>. For every model/mesh that you need to render, you modify the modelview matrix first and then call the display list.


// C++

for (int b = 0; b < balls_to_be_rendered; b++)
{
// Reset modelview matrix
glLoadIdentity();

// Modify modelview matrix for this ball's position
glTranslatef(ball[b].position.x, ball[b].position.y, ball[b].position.z);

// Call display list containing the ball mesh
glCallList(ball_mesh_displaylist);
}

Share this post


Link to post
Share on other sites
The display lists appear to be slower. I can only run a 35 FPS with 20000 bouncing balls now.

Did I do something wrong.

I have a list the draws a ball.


gl.glNewList(display_list_handle, GL_COMPILE);
gl.glBegin(GL.GL_POLYGON);
for (int a = 0; a < RESOLUTION; a++)
{
gl.glVertex2d(POINTS_X[a], POINTS_Y[a]);
}
gl.glEnd();
gl.glEndList();



Then I call this to draw each ball each frame.


public void draw(gl) {
gl.glPushMatrix();
gl.glTranslate(xn, yn, 0);
gl.glCallList(display_list_handle);
gl.glPopMatrix();
}

Share this post


Link to post
Share on other sites
Ok here is the class that handles all the display as well as the ball class. I have commented the parts you should look at.


import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;

import javax.media.opengl.*;
import javax.media.opengl.glu.*;

public class PongDisplay implements GLEventListener, KeyListener {

private static int NUM_BALLS = 20000; //number of balls
private static int PADDLE1_SIZE = 90;
private static int PADDLE2_SIZE = 90;
private static int PADDLE1_SIDE = 15;
private static int PADDLE2_SIDE = 585;
private static int PADDLE1_TOP = 320;
private static int PADDLE2_TOP = 320;

private static GL gl;
private static Court court = new Court();
private static TextOverlay text = new TextOverlay();
private static Ball ball[] = new Ball[NUM_BALLS];
private static Paddle paddleP1 = new Paddle(1, PADDLE1_TOP, PADDLE1_SIDE, PADDLE1_SIZE, 10);
private static Paddle paddleP2 = new Paddle(2, PADDLE2_TOP, PADDLE2_SIDE, PADDLE2_SIZE, 7);
private static CompAI AI = new CompAI();
private static CollisionDetector collisionDetector = new CollisionDetector();
private static Random gen = new Random();

static {
for (int i = 0; i < NUM_BALLS; i++) {
ball[i] = new Ball(300, 300, gen.nextDouble() * 3 + 1.5); //construct balls
}
}

enum KEY_STATE {
NOT_PRESSED, KEY_UP, KEY_DOWN
}

KEY_STATE keyState = KEY_STATE.NOT_PRESSED;

public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
keyState = KEY_STATE.KEY_UP;
} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
keyState = KEY_STATE.KEY_DOWN;
} else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
paddleP1.slap();
} else if (e.getKeyCode() == KeyEvent.VK_LEFT) {
paddleP1.power();
} else {
keyState = KEY_STATE.NOT_PRESSED;
}
}

public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP && keyState != KEY_STATE.KEY_DOWN) {
keyState = KEY_STATE.NOT_PRESSED;
}
if (e.getKeyCode() == KeyEvent.VK_DOWN && keyState != KEY_STATE.KEY_UP) {
keyState = KEY_STATE.NOT_PRESSED;
}
}

public void keyTyped(KeyEvent e) {
}

public void init(GLAutoDrawable gld) {
gl = gld.getGL();
GLU glu = new GLU();
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glPointSize(5.0f);
gl.glViewport(0, 0, 600, 600);
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glLoadIdentity();
glu.gluOrtho2D(0, 600, 0, 600);
for (int i = 0; i < NUM_BALLS; i++) {
ball[i].generateDisplayList(gl); //generate display list for each ball
}
}

public void display(GLAutoDrawable gld) {

if (keyState == KEY_STATE.KEY_UP && paddleP1.getTop() < 550) {
paddleP1.moveUp();
}
if (keyState == KEY_STATE.KEY_DOWN
&& paddleP1.getTop() - paddleP1.getSize() > 3) {
paddleP1.moveDown();
}

gl.glClear(GL.GL_COLOR_BUFFER_BIT);

text.draw(gld, "Pong", 257, 532);
text.draw(gld, Integer.toString(paddleP1.getScore()), 7, 532);
text.draw(gld, Integer.toString(paddleP2.getScore()), 548, 532);

court.draw(gl);

paddleP1.draw(gl);
paddleP2.draw(gl);

for (int i = 0; i < NUM_BALLS; i++) {

if (AI.slap(ball[i].getXPos(), ball[i].getSpeed())) {
paddleP2.slap();
}

if (AI.power(ball[i].getXPos(), ball[i].getSpeed())) {
paddleP2.power();
}

if (AI.track(paddleP2.getCenter(), paddleP2.getSize(), ball[i]
.getYPos()) == 1) {
if (!paddleP2.isSlapping()) {
paddleP2.moveUp();
paddleP2.draw(gl);
}
} else if (AI.track(paddleP2.getCenter(), paddleP2.getSize(),
ball[i].getYPos()) == 2) {
if (!paddleP2.isSlapping()) {
paddleP2.moveDown();
paddleP2.draw(gl);
}
}

if (collisionDetector.detectPad1(ball[i].getXPos(), ball[i]
.getYPos(), ball[i].getXDirection(), paddleP1.getSide(),
paddleP1.getTop(), paddleP1.getSize())) {
ball[i].changeXDirection();
ball[i].whirl(false);
if (paddleP1.isSlapping()) {
ball[i].setSpeed(ball[i].getSpeed() * 2);
} else if (paddleP1.isPowered()) {
ball[i].setSlope((ball[i].getSlope()*100+ball[i].getYPos() - paddleP1.getCenter())/100);
ball[i].setSpeed(ball[i].getSpeed() * 1.2);
ball[i].whirl(true);
} else {
ball[i].setSlope((ball[i].getSlope()*100+ball[i].getYPos() - paddleP1.getCenter())/100);
ball[i].setSpeed(ball[i].getSpeed() * 1.05);
}
}

if (collisionDetector.detectPad2(ball[i].getXPos(), ball[i]
.getYPos(), ball[i].getXDirection(), paddleP2.getSide(),
paddleP2.getTop(), paddleP2.getSize())) {
ball[i].changeXDirection();
ball[i].whirl(false);
if (paddleP2.isSlapping()) {
ball[i].setSpeed(ball[i].getSpeed() * 2);
} else if (paddleP2.isPowered()) {
ball[i].setSlope((ball[i].getSlope()*100+ball[i].getYPos() - paddleP2.getCenter())/100);
ball[i].setSpeed(ball[i].getSpeed() * 1.2);
ball[i].whirl(true);
} else {
ball[i].setSlope((ball[i].getSlope()*100+ball[i].getYPos() - paddleP2.getCenter())/100);
ball[i].setSpeed(ball[i].getSpeed() * 1.05);
}
}

if (collisionDetector.detectTopWall(ball[i].getYDirection(),
ball[i].getYPos())) {
ball[i].changeYDirection();
}

if (collisionDetector.detectBottomWall(ball[i].getYDirection(),
ball[i].getYPos())) {
ball[i].changeYDirection();
}

if (collisionDetector.detectP1Score(ball[i].getXDirection(),
ball[i].getXPos())) {
paddleP1.score();
ball[i].changeXDirection();
ball[i].setSlope((gen.nextDouble() - .5) * 2 + .4);
ball[i].setPos(300, gen.nextDouble() * 400.0 + 20);
ball[i].setSpeed(2.5);
ball[i].whirl(false);
}

if (collisionDetector.detectP2Score(ball[i].getXDirection(),
ball[i].getXPos())) {
paddleP2.score();
ball[i].changeXDirection();
ball[i].setSlope((gen.nextDouble() - .5) * 2 + .4);
ball[i].setPos(300, gen.nextDouble() * 400.0 + 20);
ball[i].setSpeed(2.5);
ball[i].whirl(false);
}
ball[i].moveBall();
ball[i].draw(gl); //draw ball
}
}

public void reshape(GLAutoDrawable drawable, int x, int y, int width,
int height) {
}

public void displayChanged(GLAutoDrawable drawable, boolean modeChanged,
boolean deviceChanged) {
}
}




import java.util.Random;

import javax.media.opengl.GL;

public class Ball {
Random gen = new Random();

private int display_list; //display list

private double speed;
private double xi;
private double yi;
private double xn;
private double yn;
private double slope;
private boolean movingRight;
private boolean movingUp;
private boolean isWhirling = false;

private final static double radius = 5;
private final float red = 1.0f;
private final float green = 1.0f;
private final float blue = 1.0f;
private final static double THREE_SIXTY = 2 * Math.PI;

private final static int RESOLUTION = 12;

private static double[] POINTS_X = new double[RESOLUTION];
private static double[] POINTS_Y = new double[RESOLUTION];
static {
for (int a = 0; a < RESOLUTION; a++) {
POINTS_X[a] = radius
* (Math.cos((double) a / (double) RESOLUTION * THREE_SIXTY));
POINTS_Y[a] = radius
* (Math.sin((double) a / (double) RESOLUTION * THREE_SIXTY));
}
}

public Ball(double x, double y, double s) {
xi = x;
yi = y;
xn = xi;
yn = yi;
slope = (gen.nextDouble() - .5) / gen.nextDouble();
speed = s;
movingRight = true;

if (slope > 0 && movingRight) {
movingUp = true;
} else if (slope > 0 && !movingRight) {
movingUp = false;
} else if (slope < 0 && movingRight) {
movingUp = false;
} else if (slope < 0 && !movingRight) {
movingUp = true;
}
}

public void generateDisplayList(GL gl) { //generate display list
display_list = gl.glGenLists(1);
gl.glNewList(display_list, GL.GL_COMPILE);
gl.glBegin(GL.GL_POLYGON);
for (int a = 0; a < RESOLUTION; a++)
{
gl.glVertex2d(POINTS_X[a], POINTS_Y[a]);
}
gl.glEnd();
gl.glEndList();
}

public void moveBall() {
if (speed > 12) {
speed = 12;
}

xi = xn;
yi = yn;
if (movingRight) {
xn += speed;
} else {
xn -= speed;
}
if (isWhirling) {
yn = (slope * (xn - xi) + yi);
yn = yn += (gen.nextDouble() - .5) * 35;
} else {
yn = (slope * (xn - xi) + yi);
}

if (yn > 545) {
yn = 546;
}

if (yn < 6) {
yn = 5;
}
}

public void changeXDirection() {
if (movingRight) {
movingRight = false;
} else if (!movingRight) {
movingRight = true;
}

slope *= -1;
xi = xn;
yi = yn;
}

public void changeYDirection() {
if (movingUp) {
movingUp = false;
}

else if (!movingUp) {
movingUp = true;
}

slope *= -1;
xi = xn;
yi = yn;
}

public void setXDirection(boolean d) {
movingRight = d;
}

public void setYDirection(boolean d) {
movingUp = d;
}

public boolean getXDirection() {
return movingRight;
}

public boolean getYDirection() {
return movingUp;
}

public void setPos(double x, double y) {
xn = x;
yn = y;
}

public double getXPos() {
return xn;
}

public double getYPos() {
return yn;
}

public void setSpeed(double s) {
speed = s;
}

public double getSpeed() {
return speed;
}

public double getSlope() {
return slope;
}

public void whirl(boolean w) {
isWhirling = w;
}

public void setSlope(double sl) {
slope = sl;
if (sl > 0 && movingRight) {
movingUp = true;
} else if (sl > 0 && !movingRight) {
movingUp = false;
} else if (sl < 0 && movingRight) {
movingUp = false;
} else if (sl < 0 && !movingRight) {
movingUp = true;
}
}

public void draw(GL gl) { //draw ball
if (isWhirling) {
gl.glColor3f(0.0f, 1.0f, 0.0f);
} else {
gl.glColor3f(red, green, blue);
}
gl.glPushMatrix();
gl.glTranslated(xn, yn, 0);
gl.glCallList(display_list);
gl.glPopMatrix();
}
}

Share this post


Link to post
Share on other sites
Ok, I've looked at it, and you've made a small mistake [smile]

You just need one display list for the ball model, not one for every ball object.
What you should do is create the display list in the PongDisplay class.
Then you can pass the display list as a parameter to Ball::Draw.


//
// Ball::Draw
//

public void draw(GL gl, unsigned int ball_dlist) { //draw ball
if (isWhirling) {
gl.glColor3f(0.0f, 1.0f, 0.0f);
} else {
gl.glColor3f(red, green, blue);
}
gl.glPushMatrix();
gl.glTranslated(xn, yn, 0);
gl.glCallList(ball_dlist);
gl.glPopMatrix();
}






//
// This function is now located at
// PongDisplay::generateDisplayList
//

public void generateDisplayList(GL gl) { //generate display list
display_list = gl.glGenLists(1);
gl.glNewList(display_list, GL.GL_COMPILE);
gl.glBegin(GL.GL_POLYGON);
for (int a = 0; a < RESOLUTION; a++)
{
gl.glVertex2d(POINTS_X[a], POINTS_Y[a]);
}
gl.glEnd();
gl.glEndList();
}






//
// PongDisplay::init
//

public void init(GLAutoDrawable gld) {
gl = gld.getGL();
GLU glu = new GLU();
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glPointSize(5.0f);
gl.glViewport(0, 0, 600, 600);
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glLoadIdentity();
glu.gluOrtho2D(0, 600, 0, 600);
generateDisplayList(gl);
}





//
// inside PongDisplay::display
//

ball[i].draw(gl, display_list); //draw ball




I'm not sure if this is going to fix the speed issue, but this is how you should use the display list.
If this doesn't solve it, try removing glPushMatrix() and glPopMatrix() from Ball::Draw and see if that does anything.

Share this post


Link to post
Share on other sites
It doesn't change the speed. Romoving the push and pop messes up the drawing and doesn't change speed either. There has to be a way to get millions of ball i would think. I have a Geforce 8600.

Would VBOs really give me the kinda speed I need?

Share this post


Link to post
Share on other sites
I believe a display list is marginally faster than a VBO. A VBO shouldn't outperform a display list enough to make a difference anyway. What's good about VBOs is that they provide you with a much greater degree of control over the actual vertex attributes. But AFAIK, they're not faster than display lists, just a lot more flexible.

When I think about it, the slowdown is probably caused by the large amount of matrix multiplications caused by using glTranslate for every ball. Perhaps using glBegin() and glEnd() is the optimal solution for rendering a really big amount of very simple meshes.


I guess another option is using a Vertex Array.
You'll have to create some form of shared vertex list. Every time you render a ball, the ball add its 12 vertices to this shared list. When all the balls which should be rendered have added its vertices to the list, you simply render 12 and 12 vertices at a time with GL_POLYGON. Then you clear the list and repeat the same process for the next frame.

Here's a small example which renders a quad:


float vert[12] = {-1, -1, -5, 1, -1, -5, 1, 1, -5, -1, 1, -5};
float color[12] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

glVertexPointer(3, GL_FLOAT, 0, vert); //3 floats per vertex
glColorPointer(3, GL_FLOAT, 0, color); // 3 floats per color

glDrawArrays(GL_QUADS, 0, 4); // Render one quad with the data specified with glVertexPointer and glColorPointer






Vertex Array tutorial




EDIT: The code below won't work, I didn't think that through properly at all.
Obviously you can't render multiple polygons with one glBegin(GL_POLYGON), because a polygon is defined as everything between a glBegin() and glEnd(). My bad. [smile]
-----------------------------------------------------

You could revert back to your original glBegin() and glEnd() method, but you should make as few glBegin() and glEnd() calls as possible. So, you should put those calls outside Ball::Draw.

Something like this:


//
// PongDisplay::display
//


public void display(GLAutoDrawable gld) {

// ...

gl.glBegin(GL.GL_POLYGONS);
for (int i = 0; i < NUM_BALLS; i++) {

// ...

ball[i].draw(gl); //draw ball
}
gl.glEnd();
}











//
// Ball::draw
//


public void draw(GL gl) {
if (isWhirling) {
gl.glColor3f(0.0f, 1.0f, 0.0f);
} else {
gl.glColor3f(red, green, blue);
}

for (int a = 0; a < RESOLUTION; a++) {
gl.glVertex2f(xn+POINTS_X[a], yn+POINTS_Y[a]);
}
}










[Edited by - polymorphed on May 30, 2008 7:06:36 PM]

Share this post


Link to post
Share on other sites
I always recommending interleaving and using supported formats


struct MyVertex
{
float x, y, z;
uint color;
float garbage[4]; //For padding, to make structure multiple of 32 byte
};

MyVertex myvertex[4];

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

glVertexPointer(3, GL_FLOAT, sizeof(MyVertex), &myvertex[0].x);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(MyVertex), &myvertex[0].color);

glDrawArrays(GL_QUADS, 0, 4); // Render one quad with the data specified with glVertexPointer and glColorPointer



You can even convert to GL_TRIANGLES since GL_QUADS aren't directly supported.

Share this post


Link to post
Share on other sites

This topic is 3484 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this