diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java
index 7c90e0969..919c9dd03 100644
--- a/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java
+++ b/jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java
@@ -2362,7 +2362,7 @@ public class OGLESShaderRenderer implements Renderer {
clearTextureUnits();
}
- public void renderMesh(Mesh mesh, int lod, int count) {
+ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}
diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java
index 9faae039b..7bef12e62 100644
--- a/jme3-core/src/main/java/com/jme3/material/Material.java
+++ b/jme3-core/src/main/java/com/jme3/material/Material.java
@@ -47,6 +47,8 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.instancing.InstancedGeometry;
import com.jme3.shader.Shader;
import com.jme3.shader.Uniform;
import com.jme3.shader.UniformBindingManager;
@@ -684,6 +686,17 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
return ambientLightColor;
}
+ private static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
+ Mesh mesh = geom.getMesh();
+ int lodLevel = geom.getLodLevel();
+ if (geom instanceof InstancedGeometry) {
+ InstancedGeometry instGeom = (InstancedGeometry) geom;
+ renderer.renderMesh(mesh, lodLevel, instGeom.getCurrentNumInstances(), instGeom.getAllInstanceData());
+ } else {
+ renderer.renderMesh(mesh, lodLevel, 1, null);
+ }
+ }
+
/**
* Uploads the lights in the light list as two uniform arrays.
*
*
@@ -858,7 +871,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
}
vars.release();
r.setShader(shader);
- r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+ renderMeshFromGeometry(r, g);
}
if (isFirstLight && lightList.size() > 0) {
@@ -868,7 +881,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
lightPos.setValue(VarType.Vector4, nullDirLight);
r.setShader(shader);
- r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+ renderMeshFromGeometry(r, g);
}
}
@@ -1139,7 +1152,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
r.setShader(shader);
}
- r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1);
+ renderMeshFromGeometry(r, geom);
}
public void write(JmeExporter ex) throws IOException {
diff --git a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java
index bef528f51..4c1b3605b 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java
@@ -267,18 +267,23 @@ public interface Renderer {
public void deleteBuffer(VertexBuffer vb);
/**
- * Renders count
meshes, with the geometry data supplied.
+ * Renders count
meshes, with the geometry data supplied and
+ * per-instance data supplied.
* The shader which is currently set with setShader
is
* responsible for transforming the input vertices into clip space
* and shading it based on the given vertex attributes.
- * The int variable gl_InstanceID can be used to access the current
+ * The integer variable gl_InstanceID can be used to access the current
* instance of the mesh being rendered inside the vertex shader.
+ * If the instance data is non-null, then it is submitted as a
+ * per-instance vertex attribute to the shader.
*
* @param mesh The mesh to render
* @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
* @param count Number of mesh instances to render
+ * @param instanceData When count is greater than 1, these buffers provide
+ * the per-instance attributes.
*/
- public void renderMesh(Mesh mesh, int lod, int count);
+ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData);
/**
* Resets all previously used {@link NativeObject Native Objects} on this Renderer.
diff --git a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
index 4158be132..9400dc466 100644
--- a/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
+++ b/jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
@@ -206,6 +206,14 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* either an int or float buffer due to shader attribute types restrictions.
*/
HWBoneIndex,
+
+ /**
+ * Information about this instance.
+ *
+ * Format should be {@link Format#Float} and number of components
+ * should be 16.
+ */
+ InstanceData
}
/**
@@ -325,6 +333,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
protected Type bufType;
protected Format format;
protected boolean normalized = false;
+ protected transient boolean instanced = false;
protected transient boolean dataSizeChanged = false;
/**
@@ -365,10 +374,14 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
throw new AssertionError();
}
// Are components between 1 and 4?
- if (components < 1 || components > 4) {
- throw new AssertionError();
- }
+ // Are components between 1 and 4 and not InstanceData?
+ if (bufType != Type.InstanceData) {
+ if (components < 1 || components > 4) {
+ throw new AssertionError();
+ }
+ }
+
// Does usage comply with buffer directness?
//if (usage == Usage.CpuOnly && data.isDirect()) {
// throw new AssertionError();
@@ -531,6 +544,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
return normalized;
}
+ /**
+ * TODO:
+ */
+ public void setInstanced(boolean instanced) {
+ this.instanced = instanced;
+ }
+
+ public boolean isInstanced() {
+ return instanced;
+ }
+
/**
* @return The type of information that this buffer has.
*/
@@ -585,9 +609,12 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
if (data.isReadOnly())
throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
- if (components < 1 || components > 4)
- throw new IllegalArgumentException("components must be between 1 and 4");
-
+ if (bufType != Type.InstanceData) {
+ if (components < 1 || components > 4) {
+ throw new IllegalArgumentException("components must be between 1 and 4");
+ }
+ }
+
this.data = data;
this.components = components;
this.usage = usage;
@@ -982,6 +1009,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
vb.handleRef = new Object();
vb.id = -1;
vb.normalized = normalized;
+ vb.instanced = instanced;
vb.offset = offset;
vb.stride = stride;
vb.updateNeeded = true;
@@ -1030,7 +1058,12 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
return ((long)OBJTYPE_VERTEXBUFFER << 32) | ((long)id);
}
+ @Override
public void write(JmeExporter ex) throws IOException {
+ if (instanced) {
+ throw new IOException("Serialization of instanced data not allowed");
+ }
+
OutputCapsule oc = ex.getCapsule(this);
oc.write(components, "components", 0);
oc.write(usage, "usage", Usage.Dynamic);
@@ -1064,6 +1097,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
}
}
+ @Override
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
components = ic.readInt("components", 0);
diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
new file mode 100644
index 000000000..1a573ea84
--- /dev/null
+++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
@@ -0,0 +1,502 @@
+package com.jme3.scene.instancing;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * InstancedGeometry
allows rendering many similar
+ * geometries efficiently through a feature called geometry
+ * instancing.
+ *
+ *
+ * All rendered geometries share material, mesh, and lod level
+ * but have different world transforms or possibly other parameters.
+ * The settings for all instances are inherited from this geometry's
+ * {@link #setMesh(com.jme3.scene.Mesh) mesh},
+ * {@link #setMaterial(com.jme3.material.Material) material} and
+ * {@link #setLodLevel(int) lod level} and cannot be changed per-instance.
+ *
+ *
+ *
+ * In order to receive any per-instance parameters, the material's shader
+ * must be changed to retrieve per-instance data via
+ * {@link VertexBuffer#setInstanced(boolean) instanced vertex attributes}
+ * or uniform arrays indexed with the GLSL built-in uniform
+ * gl_InstanceID
. At the very least, they should use the
+ * functions specified in Instancing.glsllib
shader library
+ * to transform vertex positions and normals instead of multiplying by the
+ * built-in matrix uniforms.
+ *
+ *
+ *
+ * This class can operate in two modes, {@link InstancedGeometry.Mode#Auto}
+ * and {@link InstancedGeometry.Mode#Manual}. See the respective enums
+ * for more information
+ *
+ *
+ * Prior to usage, the maximum number of instances must be set via
+ * {@link #setMaxNumInstances(int) } and the current number of instances set
+ * via {@link #setCurrentNumInstances(int) }. The user is then
+ * expected to provide transforms for all instances up to the number
+ * of current instances.
+ *
+ *
+ * @author Kirill Vainer
+ */
+public class InstancedGeometry extends Geometry {
+
+ /**
+ * Indicates how the per-instance data is to be specified.
+ */
+ public static enum Mode {
+
+ /**
+ * The user must specify all per-instance transforms and
+ * parameters manually via
+ * {@link InstancedGeometry#setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }
+ * or
+ * {@link InstancedGeometry#setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }.
+ */
+ Manual,
+
+ /**
+ * The user
+ * {@link InstancedGeometry#setInstanceTransform(int, com.jme3.math.Transform) provides world transforms}
+ * and then uses the Instancing.glsllib
transform functions in the
+ * shader to transform vertex attributes to the respective spaces.
+ * Additional per-instance data can be specified via
+ * {@link InstancedGeometry#setManualGlobalInstanceData(com.jme3.scene.VertexBuffer[]) }.
+ * {@link #setManualCameraInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }
+ * cannot be used at this mode since it is computed automatically.
+ */
+ Auto
+ }
+
+ private static class InstancedGeometryControl extends AbstractControl {
+
+ private InstancedGeometry geom;
+
+ public InstancedGeometryControl() {
+ }
+
+ public InstancedGeometryControl(InstancedGeometry geom) {
+ this.geom = geom;
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ geom.renderFromControl(vp.getCamera());
+ }
+ }
+
+ private static final int INSTANCE_SIZE = 16;
+
+ private InstancedGeometry.Mode mode;
+ private InstancedGeometryControl control;
+ private int currentNumInstances = 1;
+ private Camera lastCamera = null;
+ private Matrix4f[] worldMatrices = new Matrix4f[1];
+ private VertexBuffer[] globalInstanceData;
+
+ private final HashMap instanceDataPerCam
+ = new HashMap();
+
+ // TODO: determine if perhaps its better to use TempVars here.
+
+ private final Matrix4f tempMat4 = new Matrix4f();
+ private final Matrix4f tempMat4_2 = new Matrix4f();
+ private final Matrix3f tempMat3 = new Matrix3f();
+ private final Quaternion tempQuat = new Quaternion();
+ private final float[] tempFloatArray = new float[16];
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public InstancedGeometry() {
+ super();
+ setIgnoreTransform(true);
+ }
+
+ /**
+ * Creates instanced geometry with the specified mode and name.
+ *
+ * @param mode The {@link Mode} at which the instanced geometry operates at.
+ * @param name The name of the spatial.
+ *
+ * @see Mode
+ * @see Spatial#Spatial(java.lang.String)
+ */
+ public InstancedGeometry(InstancedGeometry.Mode mode, String name) {
+ super(name);
+ this.mode = mode;
+ setIgnoreTransform(true);
+ if (mode == InstancedGeometry.Mode.Auto) {
+ control = new InstancedGeometryControl(this);
+ addControl(control);
+ }
+ }
+
+ /**
+ * The mode with which this instanced geometry was initialized
+ * with. Cannot be changed after initialization.
+ *
+ * @return instanced geometry mode.
+ */
+ public InstancedGeometry.Mode getMode() {
+ return mode;
+ }
+
+ /**
+ * Global user specified per-instance data.
+ *
+ * By default set to null
, specify an array of VertexBuffers
+ * via {@link #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }.
+ *
+ * @return global user specified per-instance data.
+ * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[])
+ */
+ public VertexBuffer[] getGlobalUserInstanceData() {
+ return globalInstanceData;
+ }
+
+ /**
+ * Specify global user per-instance data.
+ *
+ * By default set to null
, specify an array of VertexBuffers
+ * that contain per-instance vertex attributes.
+ *
+ * @param globalInstanceData global user per-instance data.
+ *
+ * @throws IllegalArgumentException If one of the VertexBuffers is not
+ * {@link VertexBuffer#setInstanced(boolean) instanced}.
+ */
+ public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) {
+ this.globalInstanceData = globalInstanceData;
+ }
+
+ /**
+ * Specify camera specific user per-instance data.
+ *
+ * Only applies when operating in {@link Mode#Manual}.
+ * When operating in {@link Mode#Auto}, this data is computed automatically,
+ * and using this method is not allowed.
+ *
+ * @param camera The camera for which per-instance data is to be set.
+ * @param cameraInstanceData The camera's per-instance data.
+ *
+ * @throws IllegalArgumentException If camera is null.
+ * @throws IllegalStateException If {@link #getMode() mode} is set to
+ * {@link Mode#Auto}.
+ *
+ * @see Mode
+ * @see #getCameraUserInstanceData(com.jme3.renderer.Camera)
+ */
+ public void setCameraUserInstanceData(Camera camera, VertexBuffer cameraInstanceData) {
+ if (mode == Mode.Auto) {
+ throw new IllegalStateException("Not allowed in auto mode");
+ }
+ if (camera == null) {
+ throw new IllegalArgumentException("camera cannot be null");
+ }
+ instanceDataPerCam.put(camera, cameraInstanceData);
+ }
+
+ /**
+ * Return camera specific user per-instance data.
+ *
+ * Only applies when operating in {@link Mode#Manual}.
+ * When operating in {@link Mode#Auto}, this data is computed automatically,
+ * and using this method is not allowed.
+ *
+ * @param camera The camera to look up the per-instance data for.
+ * @return The per-instance data, or null
if none was specified
+ * for the given camera.
+ *
+ * @throws IllegalArgumentException If camera is null.
+ * @throws IllegalStateException If {@link #getMode() mode} is set to
+ * {@link Mode#Auto}.
+ *
+ * @see Mode
+ * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer)
+ */
+ public VertexBuffer getCameraUserInstanceData(Camera camera) {
+ if (mode == Mode.Auto) {
+ throw new IllegalStateException("Not allowed in auto mode");
+ }
+ if (camera == null) {
+ throw new IllegalArgumentException("camera cannot be null");
+ }
+ return instanceDataPerCam.get(camera);
+ }
+
+ /**
+ * Return a read only map with the mappings between cameras and camera
+ * specific per-instance data.
+ *
+ * Only applies when operating in {@link Mode#Manual}.
+ * When operating in {@link Mode#Auto}, this data is computed automatically,
+ * and using this method is not allowed.
+ *
+ * @return read only map with the mappings between cameras and camera
+ * specific per-instance data.
+ *
+ * @throws IllegalStateException If {@link #getMode() mode} is set to
+ * {@link Mode#Auto}.
+ *
+ * @see Mode
+ * @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer)
+ */
+ public Map getAllCameraUserInstanceData() {
+ if (mode == Mode.Auto) {
+ throw new IllegalStateException("Not allowed in auto mode");
+ }
+ return Collections.unmodifiableMap(instanceDataPerCam);
+ }
+
+ private void updateInstance(Matrix4f viewMatrix, Matrix4f worldMatrix, float[] store, int offset) {
+ viewMatrix.mult(worldMatrix, tempMat4);
+ tempMat4.toRotationMatrix(tempMat3);
+ tempMat3.invertLocal();
+
+ // NOTE: No need to take the transpose in order to encode
+ // into quaternion, the multiplication in the shader is vec * quat
+ // apparently...
+ tempQuat.fromRotationMatrix(tempMat3);
+
+ // Column-major encoding. The "W" field in each of the encoded
+ // vectors represents the quaternion.
+ store[offset + 0] = tempMat4.m00;
+ store[offset + 1] = tempMat4.m10;
+ store[offset + 2] = tempMat4.m20;
+ store[offset + 3] = tempQuat.getX();
+ store[offset + 4] = tempMat4.m01;
+ store[offset + 5] = tempMat4.m11;
+ store[offset + 6] = tempMat4.m21;
+ store[offset + 7] = tempQuat.getY();
+ store[offset + 8] = tempMat4.m02;
+ store[offset + 9] = tempMat4.m12;
+ store[offset + 10] = tempMat4.m22;
+ store[offset + 11] = tempQuat.getZ();
+ store[offset + 12] = tempMat4.m03;
+ store[offset + 13] = tempMat4.m13;
+ store[offset + 14] = tempMat4.m23;
+ store[offset + 15] = tempQuat.getW();
+ }
+
+ private void renderFromControl(Camera cam) {
+ if (mode != Mode.Auto) {
+ return;
+ }
+
+ // Get the instance data VBO for this camera.
+ VertexBuffer instanceDataVB = instanceDataPerCam.get(cam);
+ FloatBuffer instanceData;
+
+ if (instanceDataVB == null) {
+ // This is a new camera, create instance data VBO for it.
+ instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
+ instanceDataVB = new VertexBuffer(Type.InstanceData);
+ instanceDataVB.setInstanced(true);
+ instanceDataVB.setupData(Usage.Stream, INSTANCE_SIZE, Format.Float, instanceData);
+ instanceDataPerCam.put(cam, instanceDataVB);
+ } else {
+ // Retrieve the current instance data buffer.
+ instanceData = (FloatBuffer) instanceDataVB.getData();
+ }
+
+ Matrix4f viewMatrix = cam.getViewMatrix();
+
+ instanceData.limit(instanceData.capacity());
+ instanceData.position(0);
+
+ assert currentNumInstances <= worldMatrices.length;
+
+ for (int i = 0; i < currentNumInstances; i++) {
+ Matrix4f worldMatrix = worldMatrices[i];
+ if (worldMatrix == null) {
+ worldMatrix = Matrix4f.IDENTITY;
+ }
+ updateInstance(viewMatrix, worldMatrix, tempFloatArray, 0);
+ instanceData.put(tempFloatArray);
+ }
+
+ instanceData.flip();
+
+ this.lastCamera = cam;
+ instanceDataVB.updateData(instanceDataVB.getData());
+ }
+
+ /**
+ * Set the current number of instances to be rendered.
+ *
+ * @param currentNumInstances the current number of instances to be rendered.
+ *
+ * @throws IllegalArgumentException If current number of instances is
+ * greater than the maximum number of instances.
+ */
+ public void setCurrentNumInstances(int currentNumInstances) {
+ if (currentNumInstances > worldMatrices.length) {
+ throw new IllegalArgumentException("currentNumInstances cannot be larger than maxNumInstances");
+ }
+ this.currentNumInstances = currentNumInstances;
+ }
+
+ /**
+ * Set the maximum amount of instances that can be rendered by this
+ * instanced geometry when mode is set to auto.
+ *
+ * This re-allocates internal structures and therefore should be called
+ * only when necessary.
+ *
+ * @param maxNumInstances The maximum number of instances that can be
+ * rendered.
+ *
+ * @throws IllegalStateException If mode is set to manual.
+ * @throws IllegalArgumentException If maxNumInstances is zero or negative
+ */
+ public void setMaxNumInstances(int maxNumInstances) {
+ if (mode == Mode.Manual) {
+ throw new IllegalStateException("Not allowed in manual mode");
+ }
+ if (maxNumInstances < 1) {
+ throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
+ }
+
+ this.worldMatrices = new Matrix4f[maxNumInstances];
+
+ if (currentNumInstances > maxNumInstances) {
+ currentNumInstances = maxNumInstances;
+ }
+
+ // Resize instance data for each of the cameras.
+ for (VertexBuffer instanceDataVB : instanceDataPerCam.values()) {
+ FloatBuffer instanceData = (FloatBuffer) instanceDataVB.getData();
+ if (instanceData.capacity() / INSTANCE_SIZE != worldMatrices.length) {
+ // Delete old data.
+ BufferUtils.destroyDirectBuffer(instanceData);
+
+ // Resize instance data for this camera.
+ // Create new data with new length.
+ instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
+ instanceDataVB.updateData(instanceData);
+ }
+ }
+ }
+
+ public int getMaxNumInstances() {
+ return worldMatrices.length;
+ }
+
+ public int getCurrentNumInstances() {
+ return currentNumInstances;
+ }
+
+ public void setInstanceTransform(int instanceIndex, Matrix4f worldTransform) {
+ if (mode == Mode.Manual) {
+ throw new IllegalStateException("Not allowed in manual mode");
+ }
+ if (worldTransform == null) {
+ throw new IllegalArgumentException("worldTransform cannot be null");
+ }
+ if (instanceIndex < 0) {
+ throw new IllegalArgumentException("instanceIndex cannot be smaller than zero");
+ }
+ if (instanceIndex >= currentNumInstances) {
+ throw new IllegalArgumentException("instanceIndex cannot be larger than currentNumInstances");
+ }
+ // TODO: Determine if need to make a copy of matrix or just doing this
+ // is fine.
+ worldMatrices[instanceIndex] = worldTransform;
+ }
+
+ public void setInstanceTransform(int instanceIndex, Transform worldTransform) {
+ if (worldTransform == null) {
+ throw new IllegalArgumentException("worldTransform cannot be null");
+ }
+
+ // Compute the world transform matrix.
+ tempMat4.loadIdentity();
+ tempMat4.setRotationQuaternion(worldTransform.getRotation());
+ tempMat4.setTranslation(worldTransform.getTranslation());
+ tempMat4_2.loadIdentity();
+ tempMat4_2.scale(worldTransform.getScale());
+ tempMat4.multLocal(tempMat4_2);
+
+ setInstanceTransform(instanceIndex, tempMat4.clone());
+ }
+
+ public VertexBuffer[] getAllInstanceData() {
+ VertexBuffer instanceDataForCam = instanceDataPerCam.get(lastCamera);
+ ArrayList allData = new ArrayList();
+
+ if (instanceDataForCam != null) {
+ allData.add(instanceDataForCam);
+ }
+ if (globalInstanceData != null) {
+ allData.addAll(Arrays.asList(globalInstanceData));
+ }
+
+ return allData.toArray(new VertexBuffer[allData.size()]);
+ }
+
+ @Override
+ public void write(JmeExporter exporter) throws IOException {
+ super.write(exporter);
+ OutputCapsule capsule = exporter.getCapsule(this);
+ capsule.write(currentNumInstances, "cur_num_instances", 1);
+ capsule.write(mode, "instancing_mode", InstancedGeometry.Mode.Auto);
+ if (mode == Mode.Auto) {
+ capsule.write(worldMatrices, "world_matrices", null);
+ }
+ }
+
+ @Override
+ public void read(JmeImporter importer) throws IOException {
+ super.read(importer);
+ InputCapsule capsule = importer.getCapsule(this);
+ currentNumInstances = capsule.readInt("cur_num_instances", 1);
+ mode = capsule.readEnum("instancing_mode", InstancedGeometry.Mode.class,
+ InstancedGeometry.Mode.Auto);
+
+ if (mode == Mode.Auto) {
+ Savable[] matrixSavables = capsule.readSavableArray("world_matrices", null);
+ worldMatrices = new Matrix4f[matrixSavables.length];
+ for (int i = 0; i < worldMatrices.length; i++) {
+ worldMatrices[i] = (Matrix4f) matrixSavables[i];
+ }
+
+ control = getControl(InstancedGeometryControl.class);
+ control.geom = this;
+ }
+ }
+}
diff --git a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java
index 6a93c8d34..94be6c7c7 100644
--- a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java
+++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java
@@ -137,7 +137,7 @@ public class NullRenderer implements Renderer {
public void deleteBuffer(VertexBuffer vb) {
}
- public void renderMesh(Mesh mesh, int lod, int count) {
+ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
}
public void resetGLObjects() {
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md
index db480b75f..a2e2172c1 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md
@@ -1,10 +1,20 @@
MaterialDef Debug Normals {
+ MaterialParameters {
+ // For instancing
+ Boolean UseInstancing
+ }
+
Technique {
VertexShader GLSL100: Common/MatDefs/Misc/ShowNormals.vert
FragmentShader GLSL100: Common/MatDefs/Misc/ShowNormals.frag
WorldParameters {
WorldViewProjectionMatrix
+ ProjectionMatrix
}
+
+ Defines {
+ INSTANCING : UseInstancing
+ }
}
}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.vert
index 3813043b0..93cbdc361 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.vert
+++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.vert
@@ -1,4 +1,4 @@
-uniform mat4 g_WorldViewProjectionMatrix;
+#import "Common/ShaderLib/Instancing.glsllib"
attribute vec3 inPosition;
attribute vec3 inNormal;
@@ -6,6 +6,6 @@ attribute vec3 inNormal;
varying vec3 normal;
void main(){
- gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);
+ gl_Position = TransformWorldViewProjection(vec4(inPosition,1.0));
normal = inNormal;
}
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
index f3711e6bc..5a57426d4 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
+++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
@@ -12,6 +12,9 @@ MaterialDef Unshaded {
// The glow color of the object
Color GlowColor
+ // For instancing
+ Boolean UseInstancing
+
// For hardware skinning
Int NumberOfBones
Matrix4Array BoneMatrices
@@ -56,9 +59,11 @@ MaterialDef Unshaded {
WorldParameters {
WorldViewProjectionMatrix
+ ProjectionMatrix
}
Defines {
+ INSTANCING : UseInstancing
SEPARATE_TEXCOORD : SeparateTexCoord
HAS_COLORMAP : ColorMap
HAS_LIGHTMAP : LightMap
diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert
index 1d47bb395..63b05ef88 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert
+++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert
@@ -1,6 +1,6 @@
#import "Common/ShaderLib/Skinning.glsllib"
+#import "Common/ShaderLib/Instancing.glsllib"
-uniform mat4 g_WorldViewProjectionMatrix;
attribute vec3 inPosition;
#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))
@@ -33,5 +33,6 @@ void main(){
#ifdef NUM_BONES
Skinning_Compute(modelSpacePos);
#endif
- gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
+
+ gl_Position = TransformWorldViewProjection(modelSpacePos);
}
\ No newline at end of file
diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
new file mode 100644
index 000000000..4bf3fc1e9
--- /dev/null
+++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
@@ -0,0 +1,87 @@
+// Instancing GLSL library.
+//
+// When the INSTANCING define is set in the shader,
+// all global matrices are replaced with "instanced" versions.
+// One exception is g_NormalMatrix which becomes unusable,
+// instead the function ApplyNormalTransform is used to transform
+// the normal and tangent vectors into world view space.
+
+// The world matrix and normal transform quaternion need to be passed
+// as vertex attributes "inWorldMatrix" and "inNormalRotationQuaternion"
+// respectively.
+// The VertexBuffers for those two attributes
+// need to be configured into instanced mode (VertexBuffer.setInstanced(true)).
+// - inWorldMatrix should have 12 * numInstances floats.
+// - inNormalRotationQuaternion should have 4 * numInstances.
+// Thus, instancing data occupies 4 vertex attributes (16 / 4 = 4).
+//
+// The GL_ARB_draw_instanced and GL_ARB_instanced_arrays extensions
+// are required (OGL 3.3).
+
+uniform mat4 g_WorldMatrix;
+uniform mat4 g_ViewMatrix;
+uniform mat4 g_ProjectionMatrix;
+uniform mat4 g_WorldViewMatrix;
+uniform mat4 g_WorldViewProjectionMatrix;
+uniform mat3 g_NormalMatrix;
+
+#if defined INSTANCING
+
+// World Matrix + Normal Rotation Quaternion.
+// The World Matrix is the top 3 rows -
+// since the bottom row is always 0,0,0,1 for this transform.
+// The bottom row is the transpose of the inverse of WorldView Transform
+// as a quaternion. i.e. g_NormalMatrix converted to a quaternion.
+//
+// Using a quaternion instead of a matrix here allows saving approximately
+// 2 vertex attributes which now can be used for additional per-vertex data.
+attribute mat4 inInstanceData;
+
+// Extract the world view matrix out of the instance data, leaving out the
+// quaternion at the end.
+mat4 worldViewMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0),
+ vec4(inInstanceData[1].xyz, 0.0),
+ vec4(inInstanceData[2].xyz, 0.0),
+ vec4(inInstanceData[3].xyz, 1.0));
+
+
+vec4 TransformWorldView(vec4 position)
+{
+ return worldViewMatrix * position;
+}
+
+vec4 TransformWorldViewProjection(vec4 position)
+{
+ return g_ProjectionMatrix * TransformWorldView(position);
+}
+
+vec3 TransformNormal(vec3 vec)
+{
+ vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
+ inInstanceData[2].w, inInstanceData[3].w);
+ return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
+}
+
+// Prevent user from using g_** matrices which will have invalid data in this case.
+#define g_WorldMatrix Use_the_instancing_functions_for_this
+#define g_WorldViewMatrix Use_the_instancing_functions_for_this
+#define g_WorldViewProjectionMatrix Use_the_instancing_functions_for_this
+#define g_NormalMatrix Use_the_instancing_functions_for_this
+
+#else
+
+vec4 TransformWorldView(vec4 position)
+{
+ return g_WorldViewMatrix * position;
+}
+
+vec4 TransformWorldViewProjection(vec4 position)
+{
+ return g_WorldViewProjectionMatrix * position;
+}
+
+vec3 TransformNormal(vec3 normal) {
+ return g_NormalMatrix * normal;
+}
+
+#endif
\ No newline at end of file
diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java
new file mode 100644
index 000000000..816f99d61
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package jme3test.scene.instancing;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.instancing.InstancedGeometry;
+import com.jme3.scene.shape.Sphere;
+
+public class TestInstancing extends SimpleApplication {
+
+ private InstancedGeometry instancedGeometry;
+
+ public static void main(String[] args){
+ TestInstancing app = new TestInstancing();
+ //app.setShowSettings(false);
+ //app.setDisplayFps(false);
+ //app.setDisplayStatView(false);
+ app.start();
+ }
+
+ private Geometry createInstance(float x, float z) {
+ // Note: it doesn't matter what mesh or material we set here.
+ Geometry geometry = new Geometry("randomGeom", instancedGeometry.getMesh());
+ geometry.setMaterial(instancedGeometry.getMaterial());
+ geometry.setLocalTranslation(x, 0, z);
+ return geometry;
+ }
+
+ @Override
+ public void simpleInitApp() {
+ Sphere sphere = new Sphere(10, 10, 0.5f, true, false);
+ Material material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
+ material.setBoolean("UseInstancing", true);
+
+ instancedGeometry = new InstancedGeometry(InstancedGeometry.Mode.Auto, "instanced_geom");
+ instancedGeometry.setMaxNumInstances(60 * 60);
+ instancedGeometry.setCurrentNumInstances(60 * 60);
+ instancedGeometry.setCullHint(CullHint.Never);
+ instancedGeometry.setMesh(sphere);
+ instancedGeometry.setMaterial(material);
+ rootNode.attachChild(instancedGeometry);
+
+ Node instancedGeoms = new Node("instances_node");
+
+ // Important: Do not render these geometries, only
+ // use their world transforms to instance them via
+ // InstancedGeometry.
+ instancedGeoms.setCullHint(CullHint.Always);
+
+ for (int y = -30; y < 30; y++) {
+ for (int x = -30; x < 30; x++) {
+ Geometry instance = createInstance(x, y);
+ instancedGeoms.attachChild(instance);
+ }
+ }
+
+ rootNode.attachChild(instancedGeoms);
+ rootNode.setCullHint(CullHint.Never);
+
+ int instanceIndex = 0;
+ for (Spatial child : instancedGeoms.getChildren()) {
+ if (instanceIndex < instancedGeometry.getMaxNumInstances()) {
+ instancedGeometry.setInstanceTransform(instanceIndex++, child.getWorldTransform());
+ }
+ }
+
+ instancedGeometry.setCurrentNumInstances(instanceIndex);
+
+ cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f));
+ cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f));
+ flyCam.setMoveSpeed(15);
+ }
+
+}
diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java
index 351b92e67..d9bad5e32 100644
--- a/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java
+++ b/jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java
@@ -936,7 +936,7 @@ public class IGLESShaderRenderer implements Renderer {
* @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
* @param count Number of mesh instances to render
*/
- public void renderMesh(Mesh mesh, int lod, int count) {
+ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
logger.log(Level.FINE, "IGLESShaderRenderer renderMesh");
if (mesh.getVertexCount() == 0) {
return;
diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL1Renderer.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL1Renderer.java
index e31b1e0d4..491f6df21 100644
--- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL1Renderer.java
+++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL1Renderer.java
@@ -1181,7 +1181,7 @@ public class JoglGL1Renderer implements GL1Renderer {
resetFixedFuncBindings();
}
- public void renderMesh(Mesh mesh, int lod, int count) {
+ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}
diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java
index e57c2c0c9..4e48ca409 100644
--- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java
+++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java
@@ -2554,7 +2554,7 @@ public class JoglRenderer implements Renderer {
clearTextureUnits();
}
- public void renderMesh(Mesh mesh, int lod, int count) {
+ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}
diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java
index 22c1a359c..54ff4fd8d 100644
--- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java
+++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java
@@ -1121,7 +1121,7 @@ public class LwjglGL1Renderer implements GL1Renderer {
resetFixedFuncBindings();
}
- public void renderMesh(Mesh mesh, int lod, int count) {
+ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}
diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java
index 4db530ead..1403112ce 100644
--- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java
+++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java
@@ -281,7 +281,7 @@ public class LwjglRenderer implements Renderer {
caps.add(Caps.PackedDepthStencilBuffer);
}
- if (ctxCaps.GL_ARB_draw_instanced) {
+ if (ctxCaps.GL_ARB_draw_instanced && ctxCaps.GL_ARB_instanced_arrays) {
caps.add(Caps.MeshInstancing);
}
@@ -316,7 +316,8 @@ public class LwjglRenderer implements Renderer {
caps.add(Caps.TextureCompressionLATC);
}
- if (ctxCaps.GL_EXT_packed_float) {
+ if (ctxCaps.GL_EXT_packed_float || ctxCaps.OpenGL30) {
+ // This format is part of the OGL3 specification
caps.add(Caps.PackedFloatColorBuffer);
if (ctxCaps.GL_ARB_half_float_pixel) {
// because textures are usually uploaded as RGB16F
@@ -329,7 +330,7 @@ public class LwjglRenderer implements Renderer {
caps.add(Caps.TextureArray);
}
- if (ctxCaps.GL_EXT_texture_shared_exponent) {
+ if (ctxCaps.GL_EXT_texture_shared_exponent || ctxCaps.OpenGL30) {
caps.add(Caps.SharedExponentTexture);
}
@@ -392,8 +393,8 @@ public class LwjglRenderer implements Renderer {
caps.add(Caps.Multisample);
}
- //supports sRGB pipeline
- if (ctxCaps.GL_ARB_framebuffer_sRGB && ctxCaps.GL_EXT_texture_sRGB){
+ // Supports sRGB pipeline.
+ if ( (ctxCaps.GL_ARB_framebuffer_sRGB && ctxCaps.GL_EXT_texture_sRGB ) || ctxCaps.OpenGL30 ) {
caps.add(Caps.Srgb);
}
@@ -1529,7 +1530,7 @@ public class LwjglRenderer implements Renderer {
}
}
- if (fb == null) {
+ if (fb == null) {
// unbind any fbos
if (context.boundFBO != 0) {
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
@@ -2168,6 +2169,9 @@ public class LwjglRenderer implements Renderer {
for (int i = 0; i < attribList.oldLen; i++) {
int idx = attribList.oldList[i];
glDisableVertexAttribArray(idx);
+ if (context.boundAttribs[idx].isInstanced()) {
+ ARBInstancedArrays.glVertexAttribDivisorARB(idx, 0);
+ }
context.boundAttribs[idx] = null;
}
context.attribIndexList.copyNewToOld();
@@ -2200,15 +2204,32 @@ public class LwjglRenderer implements Renderer {
attrib.setLocation(loc);
}
}
+
+ int slotsRequired = 1;
+ if (vb.isInstanced()) {
+ if (!GLContext.getCapabilities().GL_ARB_instanced_arrays
+ || !GLContext.getCapabilities().GL_ARB_draw_instanced) {
+ throw new RendererException("Instancing is required, "
+ + "but not supported by the "
+ + "graphics hardware");
+ }
+ if (vb.getNumComponents() > 4 && vb.getNumComponents() % 4 != 0) {
+ throw new RendererException("Number of components in multi-slot "
+ + "buffers must be divisible by 4");
+ }
+ slotsRequired = vb.getNumComponents() / 4;
+ }
if (vb.isUpdateNeeded() && idb == null) {
updateBufferData(vb);
}
VertexBuffer[] attribs = context.boundAttribs;
- if (!context.attribIndexList.moveToNew(loc)) {
- glEnableVertexAttribArray(loc);
- //System.out.println("Enabled ATTRIB IDX: "+loc);
+ for (int i = 0; i < slotsRequired; i++) {
+ if (!context.attribIndexList.moveToNew(loc + i)) {
+ glEnableVertexAttribArray(loc + i);
+ //System.out.println("Enabled ATTRIB IDX: "+loc);
+ }
}
if (attribs[loc] != vb) {
// NOTE: Use id from interleaved buffer if specified
@@ -2222,14 +2243,43 @@ public class LwjglRenderer implements Renderer {
//statistics.onVertexBufferUse(vb, false);
}
- glVertexAttribPointer(loc,
- vb.getNumComponents(),
- convertFormat(vb.getFormat()),
- vb.isNormalized(),
- vb.getStride(),
- vb.getOffset());
+ if (slotsRequired == 1) {
+ glVertexAttribPointer(loc,
+ vb.getNumComponents(),
+ convertFormat(vb.getFormat()),
+ vb.isNormalized(),
+ vb.getStride(),
+ vb.getOffset());
+ } else {
+ for (int i = 0; i < slotsRequired; i++) {
+ // The pointer maps the next 4 floats in the slot.
+ // E.g.
+ // P1: XXXX____________XXXX____________
+ // P2: ____XXXX____________XXXX________
+ // P3: ________XXXX____________XXXX____
+ // P4: ____________XXXX____________XXXX
+ // stride = 4 bytes in float * 4 floats in slot * num slots
+ // offset = 4 bytes in float * 4 floats in slot * slot index
+ glVertexAttribPointer(loc + i,
+ 4,
+ convertFormat(vb.getFormat()),
+ vb.isNormalized(),
+ 4 * 4 * slotsRequired,
+ 4 * 4 * i);
+ }
+ }
- attribs[loc] = vb;
+ for (int i = 0; i < slotsRequired; i++) {
+ int slot = loc + i;
+ if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
+ // non-instanced -> instanced
+ ARBInstancedArrays.glVertexAttribDivisorARB(slot, 1);
+ } else if (!vb.isInstanced() && attribs[slot] != null && attribs[slot].isInstanced()) {
+ // instanced -> non-instanced
+ ARBInstancedArrays.glVertexAttribDivisorARB(slot, 0);
+ }
+ attribs[slot] = vb;
+ }
}
} else {
throw new IllegalStateException("Cannot render mesh without shader bound");
@@ -2241,7 +2291,8 @@ public class LwjglRenderer implements Renderer {
}
public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
- if (count > 1) {
+ boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
+ if (useInstancing) {
ARBDrawInstanced.glDrawArraysInstancedARB(convertElementMode(mode), 0,
vertCount, count);
} else {
@@ -2350,7 +2401,7 @@ public class LwjglRenderer implements Renderer {
}
}
- public void updateVertexArray(Mesh mesh) {
+ public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) {
int id = mesh.getId();
if (id == -1) {
IntBuffer temp = intBuf1;
@@ -2368,6 +2419,10 @@ public class LwjglRenderer implements Renderer {
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);
}
+
+ if (instanceData != null) {
+ setVertexAttrib(instanceData, null);
+ }
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
if (vb.getBufferType() == Type.InterleavedData
@@ -2386,9 +2441,9 @@ public class LwjglRenderer implements Renderer {
}
}
- private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
+ private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) {
if (mesh.getId() == -1) {
- updateVertexArray(mesh);
+ updateVertexArray(mesh, instanceData);
} else {
// TODO: Check if it was updated
}
@@ -2414,25 +2469,25 @@ public class LwjglRenderer implements Renderer {
clearTextureUnits();
}
- private void renderMeshDefault(Mesh mesh, int lod, int count) {
- VertexBuffer indices;
-
+ private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);
}
-// IntMap buffers = mesh.getBuffers();
-// SafeArrayList buffersList = mesh.getBufferList();
-
+ VertexBuffer indices;
if (mesh.getNumLodLevels() > 0) {
indices = mesh.getLodLevel(lod);
} else {
indices = mesh.getBuffer(Type.Index);
}
-// for (Entry entry : buffers) {
-// VertexBuffer vb = entry.getValue();
+ if (instanceData != null) {
+ for (VertexBuffer vb : instanceData) {
+ setVertexAttrib(vb, null);
+ }
+ }
+
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
@@ -2458,7 +2513,7 @@ public class LwjglRenderer implements Renderer {
clearTextureUnits();
}
- public void renderMesh(Mesh mesh, int lod, int count) {
+ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}
@@ -2489,7 +2544,7 @@ public class LwjglRenderer implements Renderer {
// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){
// renderMeshVertexArray(mesh, lod, count);
// }else{
- renderMeshDefault(mesh, lod, count);
+ renderMeshDefault(mesh, lod, count, instanceData);
// }
}