point particles: improve performance by x3

* Use an interleaved VBO to reduce BufferData calls
 * Preload the VBO before rendering
 * Store the results into an intermediate float array to speed up copy
experimental
Kirill Vainer 9 years ago
parent 454e210d3d
commit d76cb99772
  1. 5
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  2. 3
      jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java
  3. 172
      jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java
  4. 3
      jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java
  5. 16
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  6. 16
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  7. 2
      jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert

@ -1092,7 +1092,8 @@ public class ParticleEmitter extends Geometry {
inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal();
}
particleMesh.updateParticleData(particles, cam, inverseRotation);
particleMesh.updateParticleData(rm, particles, cam, inverseRotation);
if (!worldSpace) {
vars.release();
}
@ -1100,7 +1101,7 @@ public class ParticleEmitter extends Geometry {
public void preload(RenderManager rm, ViewPort vp) {
this.updateParticleState(0);
particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY);
particleMesh.updateParticleData(rm, particles, vp.getCamera(), Matrix3f.IDENTITY);
}
@Override

@ -34,6 +34,7 @@ package com.jme3.effect;
import com.jme3.material.RenderState;
import com.jme3.math.Matrix3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Mesh;
/**
@ -80,6 +81,6 @@ public abstract class ParticleMesh extends Mesh {
/**
* Update the particle visual data. Typically called every frame.
*/
public abstract void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation);
public abstract void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation);
}

@ -33,15 +33,22 @@ package com.jme3.effect;
import com.jme3.math.Matrix3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
public class ParticlePointMesh extends ParticleMesh {
private static final int POS_SIZE = 3 * 4;
private static final int COLOR_SIZE = 4 * 1;
private static final int SIZE_SIZE = 1 * 4;
private static final int UV_SIZE = 4 * 4;
private static final int TOTAL_SIZE = POS_SIZE + COLOR_SIZE + SIZE_SIZE + UV_SIZE;
private ParticleEmitter emitter;
private int imagesX = 1;
@ -59,109 +66,86 @@ public class ParticlePointMesh extends ParticleMesh {
this.emitter = emitter;
// set positions
FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles);
//if the buffer is already set only update the data
VertexBuffer buf = getBuffer(VertexBuffer.Type.Position);
if (buf != null) {
buf.updateData(pb);
} else {
VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position);
pvb.setupData(Usage.Stream, 3, Format.Float, pb);
setBuffer(pvb);
}
// set colors
ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4);
buf = getBuffer(VertexBuffer.Type.Color);
if (buf != null) {
buf.updateData(cb);
} else {
VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color);
cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb);
cvb.setNormalized(true);
setBuffer(cvb);
}
ByteBuffer eb = BufferUtils.createByteBuffer(TOTAL_SIZE * numParticles);
VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.InterleavedData);
vb.setupData(Usage.Stream, 1, Format.Byte, eb);
setBuffer(vb);
VertexBuffer pb = new VertexBuffer(VertexBuffer.Type.Position);
pb.setupData(Usage.Stream, 3, Format.Float, eb);
pb.updateData(null);
pb.setOffset(0);
pb.setStride(TOTAL_SIZE);
setBuffer(pb);
VertexBuffer cb = new VertexBuffer(VertexBuffer.Type.Color);
cb.setupData(Usage.Stream, 4, Format.UnsignedByte, eb);
cb.updateData(null);
cb.setNormalized(true);
cb.setOffset(POS_SIZE);
cb.setStride(TOTAL_SIZE);
setBuffer(cb);
VertexBuffer sb = new VertexBuffer(VertexBuffer.Type.Size);
sb.setupData(Usage.Stream, 1, Format.Float, eb);
sb.updateData(null);
sb.setOffset(POS_SIZE + COLOR_SIZE);
sb.setStride(TOTAL_SIZE);
setBuffer(sb);
VertexBuffer tb = new VertexBuffer(VertexBuffer.Type.TexCoord);
tb.setupData(Usage.Stream, 4, Format.Float, eb);
tb.updateData(null);
tb.setOffset(POS_SIZE + COLOR_SIZE + SIZE_SIZE);
tb.setStride(TOTAL_SIZE);
setBuffer(tb);
// set sizes
FloatBuffer sb = BufferUtils.createFloatBuffer(numParticles);
buf = getBuffer(VertexBuffer.Type.Size);
if (buf != null) {
buf.updateData(sb);
} else {
VertexBuffer svb = new VertexBuffer(VertexBuffer.Type.Size);
svb.setupData(Usage.Stream, 1, Format.Float, sb);
setBuffer(svb);
}
// set UV-scale
FloatBuffer tb = BufferUtils.createFloatBuffer(numParticles*4);
buf = getBuffer(VertexBuffer.Type.TexCoord);
if (buf != null) {
buf.updateData(tb);
} else {
VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord);
tvb.setupData(Usage.Stream, 4, Format.Float, tb);
setBuffer(tvb);
}
updateCounts();
}
@Override
public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) {
VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position);
FloatBuffer positions = (FloatBuffer) pvb.getData();
public void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation) {
VertexBuffer eb = getBuffer(VertexBuffer.Type.InterleavedData);
ByteBuffer elements = (ByteBuffer) eb.getData();
VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color);
ByteBuffer colors = (ByteBuffer) cvb.getData();
float sizeScale = emitter.getWorldScale().x;
VertexBuffer svb = getBuffer(VertexBuffer.Type.Size);
FloatBuffer sizes = (FloatBuffer) svb.getData();
TempVars vars = TempVars.get();
try {
float[] temp = vars.skinTangents;
int index = 0;
VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord);
FloatBuffer texcoords = (FloatBuffer) tvb.getData();
for (int i = 0; i < particles.length; i++) {
Particle p = particles[i];
float sizeScale = emitter.getWorldScale().x;
temp[index++] = p.position.x;
temp[index++] = p.position.y;
temp[index++] = p.position.z;
temp[index++] = Float.intBitsToFloat(p.color.asIntABGR());
temp[index++] = p.size * sizeScale;
int imgX = p.imageIndex % imagesX;
int imgY = (p.imageIndex - imgX) / imagesY;
float startX = ((float) imgX) / imagesX;
float startY = ((float) imgY) / imagesY;
float endX = startX + (1f / imagesX);
float endY = startY + (1f / imagesY);
temp[index++] = startX;
temp[index++] = startY;
temp[index++] = endX;
temp[index++] = endY;
}
elements.asFloatBuffer().put(temp, 0, (TOTAL_SIZE / 4) * particles.length).flip();
eb.updateData(elements);
// update data in vertex buffers
positions.rewind();
colors.rewind();
sizes.rewind();
texcoords.rewind();
for (int i = 0; i < particles.length; i++){
Particle p = particles[i];
positions.put(p.position.x)
.put(p.position.y)
.put(p.position.z);
sizes.put(p.size * sizeScale);
colors.putInt(p.color.asIntABGR());
int imgX = p.imageIndex % imagesX;
int imgY = (p.imageIndex - imgX) / imagesY;
float startX = ((float) imgX) / imagesX;
float startY = ((float) imgY) / imagesY;
float endX = startX + (1f / imagesX);
float endY = startY + (1f / imagesY);
texcoords.put(startX).put(startY).put(endX).put(endY);
// cheating!
rm.getRenderer().updateBufferData(eb);
} finally {
vars.release();
}
positions.flip();
colors.flip();
sizes.flip();
texcoords.flip();
// force renderer to re-send data to GPU
pvb.updateData(positions);
cvb.updateData(colors);
svb.updateData(sizes);
tvb.updateData(texcoords);
}
}

@ -35,6 +35,7 @@ import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage;
@ -145,7 +146,7 @@ public class ParticleTriMesh extends ParticleMesh {
}
@Override
public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) {
public void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation) {
// System.arraycopy(particles, 0, particlesCopy, 0, particlesCopy.length);
// comparator.setCamera(cam);
// Arrays.sort(particlesCopy, comparator);

@ -2680,6 +2680,10 @@ public final class GLRenderer implements Renderer {
private void setupVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);
}
if (instanceData != null) {
for (VertexBuffer vb : instanceData) {
setVertexAttribVAO(vb, null);
@ -2707,9 +2711,7 @@ public final class GLRenderer implements Renderer {
private void updateVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);
}
if (instanceData != null) {
for (VertexBuffer vb : instanceData) {
if (vb.isUpdateNeeded()) {
@ -2717,6 +2719,14 @@ public final class GLRenderer implements Renderer {
}
}
}
if (interleavedData != null) {
if (interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);
}
return;
}
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers

@ -713,13 +713,21 @@ public class Mesh extends NativeObject implements Savable {
* {@link #setInterleaved() interleaved} format.
*/
public void updateCounts(){
if (getBuffer(Type.InterleavedData) != null)
throw new IllegalStateException("Should update counts before interleave");
// if (getBuffer(Type.InterleavedData) != null) {
// throw new IllegalStateException("Should update counts before interleave");
// }
VertexBuffer pb = getBuffer(Type.Position);
VertexBuffer ib = getBuffer(Type.Index);
if (pb != null){
vertCount = pb.getData().limit() / pb.getNumComponents();
if (pb != null) {
VertexBuffer ip = getBuffer(Type.InterleavedData);
if (ip != null) {
int limitBytes = ip.getData().limit();
int elementSizeWithOthers = pb.getStride();
vertCount = limitBytes / elementSizeWithOthers;
} else {
vertCount = pb.getData().limit() / pb.getNumComponents();
}
}
if (ib != null){
elementCount = computeNumElements(ib.getData().limit());

@ -32,7 +32,7 @@ void main(){
#ifdef POINT_SPRITE
vec4 worldPos = g_WorldMatrix * pos;
float d = distance(g_CameraPosition.xyz, worldPos.xyz);
float size = (inSize * SIZE_MULTIPLIER * m_Quadratic) / d);
float size = (inSize * SIZE_MULTIPLIER * m_Quadratic) / d;
gl_PointSize = max(1.0, size);
//vec4 worldViewPos = g_WorldViewMatrix * pos;

Loading…
Cancel
Save