* Fix various issues with HW skinning state in SkeletonControl. setHardwareSkinningPreferred(boolean) can be used to request HW skinning, and isHardwareSkinningUsed() to see if its actually being used or not.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10550 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
sha..RD 12 years ago
parent b0f2e17db2
commit 281f9b6c20
  1. 212
      engine/src/core/com/jme3/animation/SkeletonControl.java

@ -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 * User wishes to use hardware skinning if available.
* software/cpu skinning, enabled by default
*/ */
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 * Hardware skinning is currently being used.
* switch to sw skinning
*/ */
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() {
} }
/** private void switchToHardware() {
* Hint to use hardware/software skinning mode. If gpu skinning fails or is // Next full 10 bones (e.g. 30 on 24 bones)
* disabledIf in hardware mode all or some models display the same animation int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10;
* 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;
for (Material m : materials) { for (Material m : materials) {
if (useHwSkinning) { m.setInt("NumberOfBones", numBones);
try { }
m.setInt("NumberOfBones", bones); for (Mesh mesh : targets) {
} catch (java.lang.IllegalArgumentException e) { if (isMeshAnimated(mesh)) {
Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "{0} material doesn't support Hardware Skinning reverting to software", new String[]{m.getName()}); mesh.prepareForAnim(false);
setUseHwSkinning(false);
return;
}
} else {
if (m.getParam("NumberOfBones") != null) {
m.clearParam("NumberOfBones");
}
} }
} }
}
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() { private boolean testHardwareSupported(RenderManager rm) {
return useHwSkinning; 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) { // Prevent illegal cases. These should never happen.
//preload scene to check if shader won’t blow with too many bones assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled);
if (!triedHwSkinning) { assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported);
triedHwSkinning = true;
try { if (hwSkinningDesired && !hwSkinningTested) {
rm.preloadScene(this.spatial); hwSkinningTested = true;
} catch (RendererException e) { hwSkinningSupported = testHardwareSupported(rm);
//revert back to sw skinning for this model
setUseHwSkinning(false); 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 controlRenderSoftware();
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);
//}
}
} }
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
* *

Loading…
Cancel
Save