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

@ -34,6 +34,7 @@ package com.jme3.effect;
import com.jme3.material.RenderState; import com.jme3.material.RenderState;
import com.jme3.math.Matrix3f; import com.jme3.math.Matrix3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
/** /**
@ -80,6 +81,6 @@ public abstract class ParticleMesh extends Mesh {
/** /**
* Update the particle visual data. Typically called every frame. * 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.math.Matrix3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.util.BufferUtils; import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
public class ParticlePointMesh extends ParticleMesh { 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 ParticleEmitter emitter;
private int imagesX = 1; private int imagesX = 1;
@ -59,109 +66,86 @@ public class ParticlePointMesh extends ParticleMesh {
this.emitter = emitter; this.emitter = emitter;
// set positions ByteBuffer eb = BufferUtils.createByteBuffer(TOTAL_SIZE * numParticles);
FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles); VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.InterleavedData);
vb.setupData(Usage.Stream, 1, Format.Byte, eb);
//if the buffer is already set only update the data setBuffer(vb);
VertexBuffer buf = getBuffer(VertexBuffer.Type.Position);
if (buf != null) { VertexBuffer pb = new VertexBuffer(VertexBuffer.Type.Position);
buf.updateData(pb); pb.setupData(Usage.Stream, 3, Format.Float, eb);
} else { pb.updateData(null);
VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position); pb.setOffset(0);
pvb.setupData(Usage.Stream, 3, Format.Float, pb); pb.setStride(TOTAL_SIZE);
setBuffer(pvb); setBuffer(pb);
}
VertexBuffer cb = new VertexBuffer(VertexBuffer.Type.Color);
// set colors cb.setupData(Usage.Stream, 4, Format.UnsignedByte, eb);
ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4); cb.updateData(null);
cb.setNormalized(true);
buf = getBuffer(VertexBuffer.Type.Color); cb.setOffset(POS_SIZE);
if (buf != null) { cb.setStride(TOTAL_SIZE);
buf.updateData(cb); setBuffer(cb);
} else {
VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color); VertexBuffer sb = new VertexBuffer(VertexBuffer.Type.Size);
cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb); sb.setupData(Usage.Stream, 1, Format.Float, eb);
cvb.setNormalized(true); sb.updateData(null);
setBuffer(cvb); 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(); updateCounts();
} }
@Override @Override
public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) { public void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation) {
VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position); VertexBuffer eb = getBuffer(VertexBuffer.Type.InterleavedData);
FloatBuffer positions = (FloatBuffer) pvb.getData(); ByteBuffer elements = (ByteBuffer) eb.getData();
VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color); float sizeScale = emitter.getWorldScale().x;
ByteBuffer colors = (ByteBuffer) cvb.getData();
VertexBuffer svb = getBuffer(VertexBuffer.Type.Size); TempVars vars = TempVars.get();
FloatBuffer sizes = (FloatBuffer) svb.getData(); try {
float[] temp = vars.skinTangents;
int index = 0;
VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord); for (int i = 0; i < particles.length; i++) {
FloatBuffer texcoords = (FloatBuffer) tvb.getData(); 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 // cheating!
positions.rewind(); rm.getRenderer().updateBufferData(eb);
colors.rewind(); } finally {
sizes.rewind(); vars.release();
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);
} }
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.Matrix3f;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera; import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.VertexBuffer.Usage;
@ -145,7 +146,7 @@ public class ParticleTriMesh extends ParticleMesh {
} }
@Override @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); // System.arraycopy(particles, 0, particlesCopy, 0, particlesCopy.length);
// comparator.setCamera(cam); // comparator.setCamera(cam);
// Arrays.sort(particlesCopy, comparator); // Arrays.sort(particlesCopy, comparator);

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

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

@ -32,7 +32,7 @@ void main(){
#ifdef POINT_SPRITE #ifdef POINT_SPRITE
vec4 worldPos = g_WorldMatrix * pos; vec4 worldPos = g_WorldMatrix * pos;
float d = distance(g_CameraPosition.xyz, worldPos.xyz); 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); gl_PointSize = max(1.0, size);
//vec4 worldViewPos = g_WorldViewMatrix * pos; //vec4 worldViewPos = g_WorldViewMatrix * pos;

Loading…
Cancel
Save