|
@@ -63,7 +63,7 @@ import java.util.logging.Logger;
|
|
public class SkeletonControl extends AbstractControl implements Cloneable {
|
|
public class SkeletonControl extends AbstractControl implements Cloneable {
|
|
|
|
|
|
/**
|
|
/**
|
|
- * The skeleton of the model
|
|
|
|
|
|
+ * The skeleton of the model.
|
|
*/
|
|
*/
|
|
private Skeleton skeleton;
|
|
private Skeleton skeleton;
|
|
/**
|
|
/**
|
|
@@ -75,16 +75,29 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
|
|
* are visible in at least one camera.
|
|
* are visible in at least one camera.
|
|
*/
|
|
*/
|
|
private boolean wasMeshUpdated = false;
|
|
private boolean wasMeshUpdated = false;
|
|
|
|
+
|
|
/**
|
|
/**
|
|
- * Flag to enable hardware/gpu skinning if available, disable for
|
|
|
|
- * software/cpu skinning, enabled by default
|
|
|
|
|
|
+ * User wishes to use hardware skinning if available.
|
|
*/
|
|
*/
|
|
- private boolean useHwSkinning = false;
|
|
|
|
|
|
+ private transient boolean hwSkinningDesired = false;
|
|
|
|
+
|
|
/**
|
|
/**
|
|
- * Flag to check if we have to check the shader if it would work and on fail
|
|
|
|
- * switch to sw skinning
|
|
|
|
|
|
+ * Hardware skinning is currently being used.
|
|
*/
|
|
*/
|
|
- private boolean triedHwSkinning = false;
|
|
|
|
|
|
+ private transient boolean hwSkinningEnabled = false;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Hardware skinning was tested on this GPU, results
|
|
|
|
+ * are stored in {@link #hwSkinningSupported} variable.
|
|
|
|
+ */
|
|
|
|
+ private transient boolean hwSkinningTested = false;
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * If hardware skinning was {@link #hwSkinningTested tested}, then
|
|
|
|
+ * this variable will be set to true if supported, and false if otherwise.
|
|
|
|
+ */
|
|
|
|
+ private transient boolean hwSkinningSupported = false;
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Bone offset matrices, recreated each frame
|
|
* Bone offset matrices, recreated each frame
|
|
*/
|
|
*/
|
|
@@ -100,46 +113,85 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
|
|
public SkeletonControl() {
|
|
public SkeletonControl() {
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Hint to use hardware/software skinning mode. If gpu skinning fails or is
|
|
|
|
- * disabledIf in hardware mode all or some models display the same animation
|
|
|
|
- * cycle make sure your materials are not identical but clones
|
|
|
|
- *
|
|
|
|
- * @param useHwSkinning the useHwSkinning to set
|
|
|
|
- *
|
|
|
|
- */
|
|
|
|
- public void setUseHwSkinning(boolean useHwSkinning) {
|
|
|
|
- this.useHwSkinning = useHwSkinning;
|
|
|
|
- this.triedHwSkinning = false;
|
|
|
|
- //next full 10 bones (e.g. 30 on 24 bones )
|
|
|
|
- int bones = ((skeleton.getBoneCount() / 10) + 1) * 10;
|
|
|
|
|
|
+ private void switchToHardware() {
|
|
|
|
+ // Next full 10 bones (e.g. 30 on 24 bones)
|
|
|
|
+ int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10;
|
|
for (Material m : materials) {
|
|
for (Material m : materials) {
|
|
- if (useHwSkinning) {
|
|
|
|
- try {
|
|
|
|
- m.setInt("NumberOfBones", bones);
|
|
|
|
- } catch (java.lang.IllegalArgumentException e) {
|
|
|
|
- Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "{0} material doesn't support Hardware Skinning reverting to software", new String[]{m.getName()});
|
|
|
|
- setUseHwSkinning(false);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- if (m.getParam("NumberOfBones") != null) {
|
|
|
|
- m.clearParam("NumberOfBones");
|
|
|
|
- }
|
|
|
|
|
|
+ m.setInt("NumberOfBones", numBones);
|
|
|
|
+ }
|
|
|
|
+ for (Mesh mesh : targets) {
|
|
|
|
+ if (isMeshAnimated(mesh)) {
|
|
|
|
+ mesh.prepareForAnim(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ private void switchToSoftware() {
|
|
|
|
+ for (Material m : materials) {
|
|
|
|
+ if (m.getParam("NumberOfBones") != null) {
|
|
|
|
+ m.clearParam("NumberOfBones");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
for (Mesh mesh : targets) {
|
|
for (Mesh mesh : targets) {
|
|
if (isMeshAnimated(mesh)) {
|
|
if (isMeshAnimated(mesh)) {
|
|
- mesh.prepareForAnim(!useHwSkinning); // prepare for software animation
|
|
|
|
|
|
+ mesh.prepareForAnim(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- public boolean isUseHwSkinning() {
|
|
|
|
- return useHwSkinning;
|
|
|
|
|
|
+ private boolean testHardwareSupported(RenderManager rm) {
|
|
|
|
+ for (Material m : materials) {
|
|
|
|
+ // Some of the animated mesh(es) do not support hardware skinning,
|
|
|
|
+ // so it is not supported by the model.
|
|
|
|
+ if (m.getMaterialDef().getMaterialParam("NumberOfBones") == null) {
|
|
|
|
+ Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING,
|
|
|
|
+ "Not using hardware skinning for {0}, " +
|
|
|
|
+ "because material {1} doesn''t support it.",
|
|
|
|
+ new Object[]{spatial, m.getMaterialDef().getName()});
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switchToHardware();
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ rm.preloadScene(spatial);
|
|
|
|
+ return true;
|
|
|
|
+ } catch (RendererException e) {
|
|
|
|
+ Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING, "Could not enable HW skinning due to shader compile error:", e);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Specifies if hardware skinning is preferred. If it is preferred and
|
|
|
|
+ * supported by GPU, it shall be enabled, if its not preferred, or not
|
|
|
|
+ * supported by GPU, then it shall be disabled.
|
|
|
|
+ *
|
|
|
|
+ * @see #isHardwareSkinningUsed()
|
|
|
|
+ */
|
|
|
|
+ public void setHardwareSkinningPreferred(boolean preferred) {
|
|
|
|
+ hwSkinningDesired = preferred;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @return True if hardware skinning is preferable to software skinning.
|
|
|
|
+ * Set to false by default.
|
|
|
|
+ *
|
|
|
|
+ * @see #setHardwareSkinningPreferred(boolean)
|
|
|
|
+ */
|
|
|
|
+ public boolean isHardwareSkinningPreferred() {
|
|
|
|
+ return hwSkinningDesired;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @return True is hardware skinning is activated and is currently used, false otherwise.
|
|
|
|
+ */
|
|
|
|
+ public boolean isHardwareSkinningUsed() {
|
|
|
|
+ return hwSkinningEnabled;
|
|
|
|
+ }
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Creates a skeleton control. The list of targets will be acquired
|
|
* Creates a skeleton control. The list of targets will be acquired
|
|
* automatically when the control is attached to a node.
|
|
* automatically when the control is attached to a node.
|
|
@@ -169,7 +221,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
|
|
private void findTargets(Node node, ArrayList<Mesh> targets, HashSet<Material> materials) {
|
|
private void findTargets(Node node, ArrayList<Mesh> targets, HashSet<Material> materials) {
|
|
Mesh sharedMesh = null;
|
|
Mesh sharedMesh = null;
|
|
|
|
|
|
-
|
|
|
|
for (Spatial child : node.getChildren()) {
|
|
for (Spatial child : node.getChildren()) {
|
|
if (child instanceof Geometry) {
|
|
if (child instanceof Geometry) {
|
|
Geometry geom = (Geometry) child;
|
|
Geometry geom = (Geometry) child;
|
|
@@ -217,46 +268,65 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
|
|
findTargets(node, meshes, mats);
|
|
findTargets(node, meshes, mats);
|
|
targets = meshes.toArray(new Mesh[meshes.size()]);
|
|
targets = meshes.toArray(new Mesh[meshes.size()]);
|
|
materials = mats.toArray(new Material[mats.size()]);
|
|
materials = mats.toArray(new Material[mats.size()]);
|
|
- //try hw skinning, will be reset to sw skinning if render call fails
|
|
|
|
- setUseHwSkinning(true);
|
|
|
|
} else {
|
|
} else {
|
|
targets = null;
|
|
targets = null;
|
|
materials = null;
|
|
materials = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private void controlRenderSoftware() {
|
|
|
|
+ resetToBind(); // reset morph meshes to bind pose
|
|
|
|
+
|
|
|
|
+ offsetMatrices = skeleton.computeSkinningMatrices();
|
|
|
|
+
|
|
|
|
+ for (int i = 0; i < targets.length; i++) {
|
|
|
|
+ // NOTE: This assumes that code higher up
|
|
|
|
+ // Already ensured those targets are animated
|
|
|
|
+ // otherwise a crash will happen in skin update
|
|
|
|
+ //if (isMeshAnimated(targets)) {
|
|
|
|
+ softwareSkinUpdate(targets[i], offsetMatrices);
|
|
|
|
+ //}
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void controlRenderHardware() {
|
|
|
|
+ offsetMatrices = skeleton.computeSkinningMatrices();
|
|
|
|
+ for (Material m : materials) {
|
|
|
|
+ m.setParam("BoneMatrices", VarType.Matrix4Array, offsetMatrices);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void controlRender(RenderManager rm, ViewPort vp) {
|
|
protected void controlRender(RenderManager rm, ViewPort vp) {
|
|
if (!wasMeshUpdated) {
|
|
if (!wasMeshUpdated) {
|
|
- if (useHwSkinning) {
|
|
|
|
- //preload scene to check if shader won’t blow with too many bones
|
|
|
|
- if (!triedHwSkinning) {
|
|
|
|
- triedHwSkinning = true;
|
|
|
|
- try {
|
|
|
|
- rm.preloadScene(this.spatial);
|
|
|
|
- } catch (RendererException e) {
|
|
|
|
- //revert back to sw skinning for this model
|
|
|
|
- setUseHwSkinning(false);
|
|
|
|
- }
|
|
|
|
|
|
+ // Prevent illegal cases. These should never happen.
|
|
|
|
+ assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled);
|
|
|
|
+ assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported);
|
|
|
|
+
|
|
|
|
+ if (hwSkinningDesired && !hwSkinningTested) {
|
|
|
|
+ hwSkinningTested = true;
|
|
|
|
+ hwSkinningSupported = testHardwareSupported(rm);
|
|
|
|
+
|
|
|
|
+ if (hwSkinningSupported) {
|
|
|
|
+ hwSkinningEnabled = true;
|
|
|
|
+
|
|
|
|
+ Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for " + spatial);
|
|
|
|
+ } else {
|
|
|
|
+ switchToSoftware();
|
|
}
|
|
}
|
|
- offsetMatrices = skeleton.computeSkinningMatrices();
|
|
|
|
|
|
+ } else if (hwSkinningDesired && hwSkinningSupported && !hwSkinningEnabled) {
|
|
|
|
+ switchToHardware();
|
|
|
|
+ hwSkinningEnabled = true;
|
|
|
|
+ } else if (!hwSkinningDesired && hwSkinningEnabled) {
|
|
|
|
+ switchToSoftware();
|
|
|
|
+ hwSkinningEnabled = false;
|
|
|
|
+ }
|
|
|
|
|
|
- hardwareSkinUpdate();
|
|
|
|
|
|
+ if (hwSkinningEnabled) {
|
|
|
|
+ controlRenderHardware();
|
|
} else {
|
|
} else {
|
|
- resetToBind(); // reset morph meshes to bind pose
|
|
|
|
-
|
|
|
|
- offsetMatrices = skeleton.computeSkinningMatrices();
|
|
|
|
-
|
|
|
|
- // if hardware skinning is supported, the matrices and weight buffer
|
|
|
|
- // will be sent by the SkinningShaderLogic object assigned to the shader
|
|
|
|
- for (int i = 0; i < targets.length; i++) {
|
|
|
|
- // NOTE: This assumes that code higher up
|
|
|
|
- // Already ensured those targets are animated
|
|
|
|
- // otherwise a crash will happen in skin update
|
|
|
|
- //if (isMeshAnimated(targets)) {
|
|
|
|
- softwareSkinUpdate(targets[i], offsetMatrices);
|
|
|
|
- //}
|
|
|
|
- }
|
|
|
|
|
|
+ controlRenderSoftware();
|
|
}
|
|
}
|
|
|
|
|
|
wasMeshUpdated = true;
|
|
wasMeshUpdated = true;
|
|
@@ -275,7 +345,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
|
|
Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData();
|
|
Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData();
|
|
Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData();
|
|
Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData();
|
|
if (!biBuff.hasArray() || !bwBuff.hasArray()) {
|
|
if (!biBuff.hasArray() || !bwBuff.hasArray()) {
|
|
- mesh.prepareForAnim(!useHwSkinning); // prepare for software animation
|
|
|
|
|
|
+ mesh.prepareForAnim(true); // prepare for software animation
|
|
}
|
|
}
|
|
VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
|
|
VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
|
|
VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
|
|
VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
|
|
@@ -409,18 +479,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- /**
|
|
|
|
- * Update the mesh according to the given transformation matrices
|
|
|
|
- *
|
|
|
|
- * @param mesh then mesh
|
|
|
|
- * @param offsetMatrices the transformation matrices to apply
|
|
|
|
- */
|
|
|
|
- private void hardwareSkinUpdate() {
|
|
|
|
- for (Material m : materials) {
|
|
|
|
- m.setParam("BoneMatrices", VarType.Matrix4Array, offsetMatrices);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
/**
|
|
/**
|
|
* Method to apply skinning transforms to a mesh's buffers
|
|
* Method to apply skinning transforms to a mesh's buffers
|
|
*
|
|
*
|