After pilfering through some of the information on the LIBGDX documentation pages, I stumbled across something called a mesh, which looked promising. This page provided further information about drawing primitive shapes using meshes. By combining what I had learned from the two above sources, I was able to draw the points much more efficiently using GL_POINT to draw the mesh. Here's a file that you might copy to test this out and understand the approach:
package com.mygdx.game;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.*;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.MathUtils;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL32.GL_PROGRAM_POINT_SIZE;
public class MyGdxGame extends ApplicationAdapter {
private static final int NUM_POINTS = 100_000;
private OrthographicCamera camera;
private Mesh mesh;
private ShaderProgram shader;
private int values_per_vertex;
@Override
public void create() {
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera.update();
mesh = new Mesh(true, NUM_POINTS, 0, new VertexAttribute(VertexAttributes.Usage.Position, 2, "a_position"),
new VertexAttribute(VertexAttributes.Usage.ColorUnpacked, 3, "a_color"));
values_per_vertex = 2 + 3; // corresponds with the values given to the VertexAttribute constructor for size
final String vertex_shader = "attribute vec2 a_position;\n" +
"attribute vec3 a_color;\n" +
"varying vec3 v_color;\n" +
"uniform mat4 u_proj;\n" +
"void main() {\n" +
" v_color = a_color;\n" +
" gl_Position = u_proj * vec4(a_position, 0.0, 1.0);\n" +
" gl_PointSize = abs(sin(a_position / 200)) * 10;" +
"}\n";
final String fragment_shader = "varying vec3 v_color;" +
"void main() {\n" +
" gl_FragColor = vec4(v_color, 1.0);\n" +
"}";
ShaderProgram.pedantic = true;
shader = new ShaderProgram(vertex_shader, fragment_shader);
System.out.println(shader.getLog());
glEnable(GL_PROGRAM_POINT_SIZE); // allow the vertex shader to specify the size of the point
// glPointSize(int x); can be used if you don't want to define the size per-point
}
@Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
// generate an array of information about each vertex to be passed to the mesh and then the shader
final float[] vertices = new float[NUM_POINTS * values_per_vertex];
for (int i = 0; i < NUM_POINTS * values_per_vertex; i += values_per_vertex) {
// x value should be within the valid x range for the camera
vertices[i] = (int) MathUtils.random(-Gdx.graphics.getWidth() / 2f, Gdx.graphics.getWidth() / 2f);
// same idea with the y value
vertices[i + 1] = (int) MathUtils.random(-Gdx.graphics.getHeight() / 2f, Gdx.graphics.getHeight() / 2f);
// r component of color
vertices[i + 2] = MathUtils.random();
// g component of color
vertices[i + 3] = MathUtils.random();
// b component of color
vertices[i + 4] = MathUtils.random();
}
final long start = System.nanoTime();
mesh.setVertices(vertices); // send our array of vertex information to the mesh
camera.viewportWidth = Gdx.graphics.getWidth();
camera.viewportHeight = Gdx.graphics.getHeight();
camera.update();
shader.begin();
shader.setUniformMatrix("u_proj", camera.combined); // use the camera's perspective when drawing
mesh.render(shader, GL20.GL_POINTS); // draw the points in the mesh
shader.end();
System.out.println("transfer and render took: " + (System.nanoTime() - start) / 1e9 + " s");
}
@Override
public void dispose() {
mesh.dispose();
}
}
Here's an image of the output:
When using this method and changing vertex locations each frame, I've found the majority consumer of time per-render to be the population of the vertex array which is sent to the GPU for rendering. Even for 1,000,000 points, my mid-range graphics card can chug them out in under 16 ms, but the CPU struggles mightily to populate an array of 5,000,000 floats in a short (under 16 ms) amount of time. It seems that any 'optimizations' of this approach would do best to focus on the population of the vertex array.