1
0
forked from 0ad/0ad

Experimental GPU skinning.

Share inverse bind pose matrices between models.

This was SVN commit r11490.
This commit is contained in:
Ykkrosh 2012-04-12 15:43:59 +00:00
parent ce215cace3
commit 227f9e403f
15 changed files with 247 additions and 96 deletions

View File

@ -61,6 +61,9 @@ preferglsl = false
; Replace alpha-blending with alpha-testing, for performance experiments ; Replace alpha-blending with alpha-testing, for performance experiments
forcealphatest = false forcealphatest = false
; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk
gpuskinning = false
; Opt-in online user reporting system ; Opt-in online user reporting system
userreport.url = "http://feedback.wildfiregames.com/report/upload/v1/" userreport.url = "http://feedback.wildfiregames.com/report/upload/v1/"

View File

@ -1,4 +1,9 @@
#if USE_GPU_SKINNING
// Skinning requires GLSL 1.30 for ivec4 vertex attributes
#version 130
#else
#version 120 #version 120
#endif
uniform mat4 transform; uniform mat4 transform;
uniform vec3 cameraPos; uniform vec3 cameraPos;
@ -22,8 +27,32 @@ attribute vec3 a_vertex;
attribute vec3 a_normal; attribute vec3 a_normal;
attribute vec2 a_uv0; attribute vec2 a_uv0;
#if USE_GPU_SKINNING
const int MAX_INFLUENCES = 4;
const int MAX_BONES = 64;
uniform mat4 skinBlendMatrices[MAX_BONES];
attribute ivec4 a_skinJoints;
attribute vec4 a_skinWeights;
#endif
varying vec4 debugx;
void main() void main()
{ {
#if USE_GPU_SKINNING
vec3 p = vec3(0.0);
vec3 n = vec3(0.0);
for (int i = 0; i < MAX_INFLUENCES; ++i) {
int joint = a_skinJoints[i];
if (joint != 0xff) {
mat4 m = skinBlendMatrices[joint];
p += vec3(m * vec4(a_vertex, 1.0)) * a_skinWeights[i];
n += vec3(m * vec4(a_normal, 0.0)) * a_skinWeights[i];
}
}
vec4 position = instancingTransform * vec4(p, 1.0);
vec3 normal = mat3(instancingTransform) * normalize(n);
#else
#if USE_INSTANCING #if USE_INSTANCING
vec4 position = instancingTransform * vec4(a_vertex, 1.0); vec4 position = instancingTransform * vec4(a_vertex, 1.0);
vec3 normal = mat3(instancingTransform) * a_normal; vec3 normal = mat3(instancingTransform) * a_normal;
@ -31,6 +60,7 @@ void main()
vec4 position = vec4(a_vertex, 1.0); vec4 position = vec4(a_vertex, 1.0);
vec3 normal = a_normal; vec3 normal = a_normal;
#endif #endif
#endif
gl_Position = transform * position; gl_Position = transform * position;

View File

@ -8,6 +8,8 @@
<attrib name="a_vertex" semantics="gl_Vertex"/> <attrib name="a_vertex" semantics="gl_Vertex"/>
<attrib name="a_normal" semantics="gl_Normal"/> <attrib name="a_normal" semantics="gl_Normal"/>
<attrib name="a_uv0" semantics="gl_MultiTexCoord0"/> <attrib name="a_uv0" semantics="gl_MultiTexCoord0"/>
<attrib name="a_skinJoints" semantics="CustomAttribute0" if="USE_GPU_SKINNING"/>
<attrib name="a_skinWeights" semantics="CustomAttribute1" if="USE_GPU_SKINNING"/>
</vertex> </vertex>
<fragment file="glsl/model_common.fs"/> <fragment file="glsl/model_common.fs"/>

View File

@ -4,6 +4,8 @@
<vertex file="glsl/model_common.vs"> <vertex file="glsl/model_common.vs">
<stream name="pos"/> <stream name="pos"/>
<attrib name="a_vertex" semantics="gl_Vertex"/> <attrib name="a_vertex" semantics="gl_Vertex"/>
<attrib name="a_skinJoints" semantics="CustomAttribute0" if="USE_GPU_SKINNING"/>
<attrib name="a_skinWeights" semantics="CustomAttribute1" if="USE_GPU_SKINNING"/>
</vertex> </vertex>
<fragment file="glsl/solid.fs"/> <fragment file="glsl/solid.fs"/>

View File

@ -4,6 +4,8 @@
<vertex file="glsl/model_common.vs"> <vertex file="glsl/model_common.vs">
<stream name="pos"/> <stream name="pos"/>
<attrib name="a_vertex" semantics="gl_Vertex"/> <attrib name="a_vertex" semantics="gl_Vertex"/>
<attrib name="a_skinJoints" semantics="CustomAttribute0" if="USE_GPU_SKINNING"/>
<attrib name="a_skinWeights" semantics="CustomAttribute1" if="USE_GPU_SKINNING"/>
</vertex> </vertex>
<fragment file="glsl/solid_player.fs"/> <fragment file="glsl/solid_player.fs"/>

View File

@ -6,6 +6,8 @@
<stream name="uv0"/> <stream name="uv0"/>
<attrib name="a_vertex" semantics="gl_Vertex"/> <attrib name="a_vertex" semantics="gl_Vertex"/>
<attrib name="a_uv0" semantics="gl_MultiTexCoord0"/> <attrib name="a_uv0" semantics="gl_MultiTexCoord0"/>
<attrib name="a_skinJoints" semantics="CustomAttribute0" if="USE_GPU_SKINNING"/>
<attrib name="a_skinWeights" semantics="CustomAttribute1" if="USE_GPU_SKINNING"/>
</vertex> </vertex>
<fragment file="glsl/solid_tex.fs"/> <fragment file="glsl/solid_tex.fs"/>

View File

@ -35,12 +35,13 @@
#include "lib/sysdep/rtl.h" #include "lib/sysdep/rtl.h"
#include "ps/Profile.h" #include "ps/Profile.h"
#include "ps/CLogger.h" #include "ps/CLogger.h"
#include "renderer/Renderer.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
CModel::CModel(CSkeletonAnimManager& skeletonAnimManager) CModel::CModel(CSkeletonAnimManager& skeletonAnimManager)
: m_Flags(0), m_Anim(NULL), m_AnimTime(0), : m_Flags(0), m_Anim(NULL), m_AnimTime(0),
m_BoneMatrices(NULL), m_InverseBindBoneMatrices(NULL), m_BoneMatrices(NULL),
m_AmmoPropPoint(NULL), m_AmmoLoadedProp(0), m_AmmoPropPoint(NULL), m_AmmoLoadedProp(0),
m_SkeletonAnimManager(skeletonAnimManager) m_SkeletonAnimManager(skeletonAnimManager)
{ {
@ -58,7 +59,6 @@ CModel::~CModel()
void CModel::ReleaseData() void CModel::ReleaseData()
{ {
rtl_FreeAligned(m_BoneMatrices); rtl_FreeAligned(m_BoneMatrices);
delete[] m_InverseBindBoneMatrices;
for (size_t i = 0; i < m_Props.size(); ++i) for (size_t i = 0; i < m_Props.size(); ++i)
delete m_Props[i].m_Model; delete m_Props[i].m_Model;
@ -84,21 +84,10 @@ bool CModel::InitModel(const CModelDefPtr& modeldef)
// allocate matrices for bone transformations // allocate matrices for bone transformations
// (one extra matrix is used for the special case of bind-shape relative weighting) // (one extra matrix is used for the special case of bind-shape relative weighting)
m_BoneMatrices = (CMatrix3D*)rtl_AllocateAligned(sizeof(CMatrix3D) * (numBones + 1 + numBlends), 16); m_BoneMatrices = (CMatrix3D*)rtl_AllocateAligned(sizeof(CMatrix3D) * (numBones + 1 + numBlends), 16);
for (size_t i = 0; i < numBones + numBlends; ++i) for (size_t i = 0; i < numBones + 1 + numBlends; ++i)
{ {
m_BoneMatrices[i].SetIdentity(); m_BoneMatrices[i].SetIdentity();
} }
m_InverseBindBoneMatrices = new CMatrix3D[numBones];
// store default pose until animation assigned
CBoneState* defpose = modeldef->GetBones();
for (size_t i = 0; i < numBones; ++i)
{
m_InverseBindBoneMatrices[i].SetIdentity();
m_InverseBindBoneMatrices[i].Translate(-defpose[i].m_Translation);
m_InverseBindBoneMatrices[i].Rotate(defpose[i].m_Rotation.GetInverse());
}
} }
m_PositionValid = true; m_PositionValid = true;
@ -344,27 +333,39 @@ void CModel::ValidatePosition()
ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys()); ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys());
m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION)); m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION));
// add world-space transformation to m_BoneMatrices
const CMatrix3D& transform = GetTransform();
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
m_BoneMatrices[i].Concatenate(transform);
} }
else if (m_BoneMatrices) else if (m_BoneMatrices)
{ {
// Bones but no animation - probably a buggy actor forgot to set up the animation, // Bones but no animation - probably a buggy actor forgot to set up the animation,
// so just render it in its bind pose // so just render it in its bind pose
const CMatrix3D& transform = GetTransform();
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
{ {
m_BoneMatrices[i].SetIdentity(); m_BoneMatrices[i].SetIdentity();
m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation); m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation);
m_BoneMatrices[i].Translate(m_pModelDef->GetBones()[i].m_Translation); m_BoneMatrices[i].Translate(m_pModelDef->GetBones()[i].m_Translation);
m_BoneMatrices[i].Concatenate(transform);
} }
} }
// For CPU skinning, we precompute as much as possible so that the only
// per-vertex work is a single matrix*vec multiplication.
// For GPU skinning, we try to minimise CPU work by doing most computation
// in the vertex shader instead.
// Using g_Renderer.m_Options to detect CPU vs GPU is a bit hacky,
// and this doesn't allow the setting to change at runtime, but there isn't
// an obvious cleaner way to determine what data needs to be computed,
// and GPU skinning is a rarely-used experimental feature anyway.
bool worldSpaceBoneMatrices = !g_Renderer.m_Options.m_GPUSkinning;
bool computeBlendMatrices = !g_Renderer.m_Options.m_GPUSkinning;
if (m_BoneMatrices && worldSpaceBoneMatrices)
{
// add world-space transformation to m_BoneMatrices
const CMatrix3D transform = GetTransform();
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
m_BoneMatrices[i].Concatenate(transform);
}
// our own position is now valid; now we can safely update our props' positions without fearing // our own position is now valid; now we can safely update our props' positions without fearing
// that doing so will cause a revalidation of this model (see recursion above). // that doing so will cause a revalidation of this model (see recursion above).
m_PositionValid = true; m_PositionValid = true;
@ -377,8 +378,10 @@ void CModel::ValidatePosition()
CMatrix3D proptransform = prop.m_Point->m_Transform;; CMatrix3D proptransform = prop.m_Point->m_Transform;;
if (prop.m_Point->m_BoneIndex != 0xff) if (prop.m_Point->m_BoneIndex != 0xff)
{ {
// m_BoneMatrices[i] already have world transform pre-applied (see above) CMatrix3D boneMatrix = m_BoneMatrices[prop.m_Point->m_BoneIndex];
proptransform.Concatenate(m_BoneMatrices[prop.m_Point->m_BoneIndex]); if (!worldSpaceBoneMatrices)
boneMatrix.Concatenate(GetTransform());
proptransform.Concatenate(boneMatrix);
} }
else else
{ {
@ -394,7 +397,7 @@ void CModel::ValidatePosition()
{ {
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
{ {
m_BoneMatrices[i] = m_BoneMatrices[i] * m_InverseBindBoneMatrices[i]; m_BoneMatrices[i] = m_BoneMatrices[i] * m_pModelDef->GetInverseBindBoneMatrices()[i];
} }
// Note: there is a special case of joint influence, in which the vertex // Note: there is a special case of joint influence, in which the vertex
@ -405,7 +408,8 @@ void CModel::ValidatePosition()
// (see http://trac.wildfiregames.com/ticket/1012) // (see http://trac.wildfiregames.com/ticket/1012)
m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform; m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform;
m_pModelDef->BlendBoneMatrices(m_BoneMatrices); if (computeBlendMatrices)
m_pModelDef->BlendBoneMatrices(m_BoneMatrices);
} }
} }

View File

@ -189,10 +189,6 @@ public:
return m_BoneMatrices; return m_BoneMatrices;
} }
const CMatrix3D* GetInverseBindBoneMatrices() {
return m_InverseBindBoneMatrices;
}
/** /**
* Load raw animation frame animation from given file, and build an * Load raw animation frame animation from given file, and build an
* animation specific to this model. * animation specific to this model.
@ -278,8 +274,6 @@ private:
* @see SPropPoint * @see SPropPoint
*/ */
CMatrix3D* m_BoneMatrices; CMatrix3D* m_BoneMatrices;
// inverse matrices for the bind pose's bones; null if not skeletal
CMatrix3D* m_InverseBindBoneMatrices;
// list of current props on model // list of current props on model
std::vector<Prop> m_Props; std::vector<Prop> m_Props;

View File

@ -140,7 +140,7 @@ void CModelDef::SkinPointsAndNormals_SSE(
ASSERT((intptr_t)newPoseMatrices % 16 == 0); ASSERT((intptr_t)newPoseMatrices % 16 == 0);
ASSERT((intptr_t)PositionData % 16 == 0); ASSERT((intptr_t)PositionData % 16 == 0);
ASSERT((intptr_t)PositionStride % 16 == 0); ASSERT((intptr_t)PositionStride % 16 == 0);
ASSERT((intptr_t)NormalData % 16 == 0); ASSERT((intptr_t)NormalData % 16 == 0);
ASSERT((intptr_t)NormalStride % 16 == 0); ASSERT((intptr_t)NormalStride % 16 == 0);
__m128 col0, col1, col2, col3, vec0, vec1, vec2; __m128 col0, col1, col2, col3, vec0, vec1, vec2;
@ -219,17 +219,15 @@ void CModelDef::BlendBoneMatrices(
// (see http://trac.wildfiregames.com/ticket/1012) // (see http://trac.wildfiregames.com/ticket/1012)
boneMatrix.Blend(boneMatrices[blend.m_Bone[0]], blend.m_Weight[0]); boneMatrix.Blend(boneMatrices[blend.m_Bone[0]], blend.m_Weight[0]);
boneMatrix.AddBlend(boneMatrices[blend.m_Bone[1]], blend.m_Weight[1]); for (size_t j = 1; j < SVertexBlend::SIZE && blend.m_Bone[j] != 0xFF; ++j)
for (size_t j = 2; j < SVertexBlend::SIZE && blend.m_Bone[j] != 0xFF; ++j)
{
boneMatrix.AddBlend(boneMatrices[blend.m_Bone[j]], blend.m_Weight[j]); boneMatrix.AddBlend(boneMatrices[blend.m_Bone[j]], blend.m_Weight[j]);
}
} }
} }
// CModelDef Constructor // CModelDef Constructor
CModelDef::CModelDef() : CModelDef::CModelDef() :
m_NumVertices(0), m_pVertices(0), m_NumFaces(0), m_pFaces(0), m_NumBones(0), m_Bones(0), m_NumVertices(0), m_pVertices(0), m_NumFaces(0), m_pFaces(0),
m_NumBones(0), m_Bones(0), m_InverseBindBoneMatrices(NULL),
m_NumBlends(0), m_pBlends(0), m_pBlendIndices(0), m_NumBlends(0), m_pBlends(0), m_pBlendIndices(0),
m_Name(L"[not loaded]") m_Name(L"[not loaded]")
{ {
@ -243,6 +241,7 @@ CModelDef::~CModelDef()
delete[] m_pVertices; delete[] m_pVertices;
delete[] m_pFaces; delete[] m_pFaces;
delete[] m_Bones; delete[] m_Bones;
delete[] m_InverseBindBoneMatrices;
delete[] m_pBlends; delete[] m_pBlends;
delete[] m_pBlendIndices; delete[] m_pBlendIndices;
} }
@ -381,6 +380,16 @@ CModelDef* CModelDef::Load(const VfsPath& filename, const VfsPath& name)
} }
} }
// Compute the inverse bind-pose matrices, needed by the skinning code
mdef->m_InverseBindBoneMatrices = new CMatrix3D[mdef->m_NumBones];
CBoneState* defpose = mdef->GetBones();
for (size_t i = 0; i < mdef->m_NumBones; ++i)
{
mdef->m_InverseBindBoneMatrices[i].SetIdentity();
mdef->m_InverseBindBoneMatrices[i].Translate(-defpose[i].m_Translation);
mdef->m_InverseBindBoneMatrices[i].Rotate(defpose[i].m_Rotation.GetInverse());
}
return mdef.release(); return mdef.release();
} }

View File

@ -134,6 +134,8 @@ public:
// information of a model // information of a model
class CModelDef class CModelDef
{ {
NONCOPYABLE(CModelDef);
public: public:
// current file version given to saved animations // current file version given to saved animations
enum { FILE_VERSION = 3 }; enum { FILE_VERSION = 3 };
@ -170,6 +172,7 @@ public:
// accessor: get bone data // accessor: get bone data
size_t GetNumBones() const { return m_NumBones; } size_t GetNumBones() const { return m_NumBones; }
CBoneState* GetBones() const { return m_Bones; } CBoneState* GetBones() const { return m_Bones; }
CMatrix3D* GetInverseBindBoneMatrices() { return m_InverseBindBoneMatrices; }
// accessor: get blend data // accessor: get blend data
size_t GetNumBlends() const { return m_NumBlends; } size_t GetNumBlends() const { return m_NumBlends; }
@ -259,6 +262,7 @@ public:
// bone data - default model pose // bone data - default model pose
size_t m_NumBones; size_t m_NumBones;
CBoneState* m_Bones; CBoneState* m_Bones;
CMatrix3D* m_InverseBindBoneMatrices;
// blend data // blend data
size_t m_NumBlends; size_t m_NumBlends;
SVertexBlend *m_pBlends; SVertexBlend *m_pBlends;

View File

@ -50,15 +50,17 @@ struct IModelDef : public CModelDefRPrivate
VertexArray::Attribute m_Position; VertexArray::Attribute m_Position;
VertexArray::Attribute m_Normal; VertexArray::Attribute m_Normal;
VertexArray::Attribute m_UV; VertexArray::Attribute m_UV;
VertexArray::Attribute m_BlendJoints; // valid iff gpuSkinning == true
VertexArray::Attribute m_BlendWeights; // valid iff gpuSkinning == true
/// Indices are the same for all models, so share them /// Indices are the same for all models, so share them
VertexIndexArray m_IndexArray; VertexIndexArray m_IndexArray;
IModelDef(const CModelDefPtr& mdef); IModelDef(const CModelDefPtr& mdef, bool gpuSkinning);
}; };
IModelDef::IModelDef(const CModelDefPtr& mdef) IModelDef::IModelDef(const CModelDefPtr& mdef, bool gpuSkinning)
: m_IndexArray(GL_STATIC_DRAW), m_Array(GL_STATIC_DRAW) : m_IndexArray(GL_STATIC_DRAW), m_Array(GL_STATIC_DRAW)
{ {
size_t numVertices = mdef->GetNumVertices(); size_t numVertices = mdef->GetNumVertices();
@ -75,6 +77,17 @@ IModelDef::IModelDef(const CModelDefPtr& mdef)
m_UV.elems = 2; m_UV.elems = 2;
m_Array.AddAttribute(&m_UV); m_Array.AddAttribute(&m_UV);
if (gpuSkinning)
{
m_BlendJoints.type = GL_UNSIGNED_BYTE;
m_BlendJoints.elems = 4;
m_Array.AddAttribute(&m_BlendJoints);
m_BlendWeights.type = GL_UNSIGNED_BYTE;
m_BlendWeights.elems = 4;
m_Array.AddAttribute(&m_BlendWeights);
}
m_Array.SetNumVertices(numVertices); m_Array.SetNumVertices(numVertices);
m_Array.Layout(); m_Array.Layout();
@ -85,6 +98,21 @@ IModelDef::IModelDef(const CModelDefPtr& mdef)
ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal); ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal);
ModelRenderer::BuildUV(mdef, UVit); ModelRenderer::BuildUV(mdef, UVit);
if (gpuSkinning)
{
VertexArrayIterator<u8[4]> BlendJoints = m_BlendJoints.GetIterator<u8[4]>();
VertexArrayIterator<u8[4]> BlendWeights = m_BlendWeights.GetIterator<u8[4]>();
for (size_t i = 0; i < numVertices; ++i)
{
const SModelVertex& vtx = mdef->GetVertices()[i];
for (size_t j = 0; j < 4; ++j)
{
BlendJoints[i][j] = vtx.m_Blend.m_Bone[j];
BlendWeights[i][j] = (u8)(255.f * vtx.m_Blend.m_Weight[j]);
}
}
}
m_Array.Upload(); m_Array.Upload();
m_Array.FreeBackingStore(); m_Array.FreeBackingStore();
@ -98,6 +126,8 @@ IModelDef::IModelDef(const CModelDefPtr& mdef)
struct InstancingModelRendererInternals struct InstancingModelRendererInternals
{ {
bool gpuSkinning;
/// Previously prepared modeldef /// Previously prepared modeldef
IModelDef* imodeldef; IModelDef* imodeldef;
@ -107,9 +137,10 @@ struct InstancingModelRendererInternals
// Construction and Destruction // Construction and Destruction
InstancingModelRenderer::InstancingModelRenderer() InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning)
{ {
m = new InstancingModelRendererInternals; m = new InstancingModelRendererInternals;
m->gpuSkinning = gpuSkinning;
m->imodeldef = 0; m->imodeldef = 0;
} }
@ -125,11 +156,14 @@ CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* m
CModelDefPtr mdef = model->GetModelDef(); CModelDefPtr mdef = model->GetModelDef();
IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m); IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m);
ENSURE(!model->IsSkinned()); if (m->gpuSkinning)
ENSURE(model->IsSkinned());
else
ENSURE(!model->IsSkinned());
if (!imodeldef) if (!imodeldef)
{ {
imodeldef = new IModelDef(mdef); imodeldef = new IModelDef(mdef, m->gpuSkinning);
mdef->SetRenderData(m, imodeldef); mdef->SetRenderData(m, imodeldef);
} }
@ -177,15 +211,33 @@ void InstancingModelRenderer::PrepareModelDef(const CShaderProgramPtr& shader, i
if (streamflags & STREAM_UV0) if (streamflags & STREAM_UV0)
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, stride, base + m->imodeldef->m_UV.offset); shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, stride, base + m->imodeldef->m_UV.offset);
// GPU skinning requires extra attributes to compute positions/normals
if (m->gpuSkinning)
{
shader->VertexAttribIPointer("a_skinJoints", 4, GL_UNSIGNED_BYTE, stride, base + m->imodeldef->m_BlendJoints.offset);
shader->VertexAttribPointer("a_skinWeights", 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, base + m->imodeldef->m_BlendWeights.offset);
}
shader->AssertPointersBound(); shader->AssertPointersBound();
} }
// Render one model // Render one model
void InstancingModelRenderer::RenderModel(const CShaderProgramPtr& UNUSED(shader), int UNUSED(streamflags), CModel* model, CModelRData* UNUSED(data)) void InstancingModelRenderer::RenderModel(const CShaderProgramPtr& shader, int UNUSED(streamflags), CModel* model, CModelRData* UNUSED(data))
{ {
CModelDefPtr mdldef = model->GetModelDef(); CModelDefPtr mdldef = model->GetModelDef();
if (m->gpuSkinning)
{
// Bind matrices for current animation state.
// Add 1 to NumBones because of the special 'root' bone.
// HACK: NVIDIA drivers return uniform name with "[0]", Intel Windows drivers without;
// try uploading both names since one of them should work, and this is easier than
// canonicalising the uniform names in CShaderProgramGLSL
shader->Uniform("skinBlendMatrices[0]", mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices());
shader->Uniform("skinBlendMatrices", mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices());
}
// render the lot // render the lot
size_t numFaces = mdldef->GetNumFaces(); size_t numFaces = mdldef->GetNumFaces();

View File

@ -35,7 +35,7 @@ struct InstancingModelRendererInternals;
class InstancingModelRenderer : public ModelVertexRenderer class InstancingModelRenderer : public ModelVertexRenderer
{ {
public: public:
InstancingModelRenderer(); InstancingModelRenderer(bool gpuSkinning);
~InstancingModelRenderer(); ~InstancingModelRenderer();
// Implementations // Implementations

View File

@ -288,17 +288,18 @@ public:
// Submitted models are split on two axes: // Submitted models are split on two axes:
// - Normal vs Transp[arent] - alpha-blended models are stored in a separate // - Normal vs Transp[arent] - alpha-blended models are stored in a separate
// list so we can draw them above/below the alpha-blended water plane correctly // list so we can draw them above/below the alpha-blended water plane correctly
// - Instancing vs [not instancing] - with hardware lighting we don't need to // - Skinned vs Unskinned - with hardware lighting we don't need to
// duplicate mesh data per model instance (except for skinned models), // duplicate mesh data per model instance (except for skinned models),
// so non-skinned models get different ModelVertexRenderers // so non-skinned models get different ModelVertexRenderers
ModelRendererPtr Normal; ModelRendererPtr NormalSkinned;
ModelRendererPtr NormalInstancing; ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported
ModelRendererPtr Transp; ModelRendererPtr TranspSkinned;
ModelRendererPtr TranspInstancing; ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported
ModelVertexRendererPtr VertexRendererShader; ModelVertexRendererPtr VertexRendererShader;
ModelVertexRendererPtr VertexInstancingShader; ModelVertexRendererPtr VertexInstancingShader;
ModelVertexRendererPtr VertexGPUSkinningShader;
LitRenderModifierPtr ModShader; LitRenderModifierPtr ModShader;
} Model; } Model;
@ -339,12 +340,20 @@ public:
*/ */
void CallModelRenderers(const CShaderDefines& context, int flags) void CallModelRenderers(const CShaderDefines& context, int flags)
{ {
CShaderDefines contextInstancing = context; CShaderDefines contextSkinned = context;
contextInstancing.Add("USE_INSTANCING", "1"); if (g_Renderer.m_Options.m_GPUSkinning)
{
contextSkinned.Add("USE_INSTANCING", "1");
contextSkinned.Add("USE_GPU_SKINNING", "1");
}
Model.NormalSkinned->Render(Model.ModShader, contextSkinned, flags);
Model.Normal->Render(Model.ModShader, context, flags); if (Model.NormalUnskinned != Model.NormalSkinned)
if (Model.NormalInstancing) {
Model.NormalInstancing->Render(Model.ModShader, contextInstancing, flags); CShaderDefines contextUnskinned = context;
contextUnskinned.Add("USE_INSTANCING", "1");
Model.NormalUnskinned->Render(Model.ModShader, contextUnskinned, flags);
}
} }
/** /**
@ -352,12 +361,20 @@ public:
*/ */
void CallTranspModelRenderers(const CShaderDefines& context, int flags) void CallTranspModelRenderers(const CShaderDefines& context, int flags)
{ {
CShaderDefines contextInstancing = context; CShaderDefines contextSkinned = context;
contextInstancing.Add("USE_INSTANCING", "1"); if (g_Renderer.m_Options.m_GPUSkinning)
{
contextSkinned.Add("USE_INSTANCING", "1");
contextSkinned.Add("USE_GPU_SKINNING", "1");
}
Model.TranspSkinned->Render(Model.ModShader, contextSkinned, flags);
Model.Transp->Render(Model.ModShader, context, flags); if (Model.TranspUnskinned != Model.TranspSkinned)
if (Model.TranspInstancing) {
Model.TranspInstancing->Render(Model.ModShader, contextInstancing, flags); CShaderDefines contextUnskinned = context;
contextUnskinned.Add("USE_INSTANCING", "1");
Model.TranspUnskinned->Render(Model.ModShader, contextUnskinned, flags);
}
} }
/** /**
@ -365,9 +382,9 @@ public:
*/ */
void FilterModels(CModelFilter& filter, int passed, int flags = 0) void FilterModels(CModelFilter& filter, int passed, int flags = 0)
{ {
Model.Normal->Filter(filter, passed, flags); Model.NormalSkinned->Filter(filter, passed, flags);
if (Model.NormalInstancing) if (Model.NormalUnskinned != Model.NormalSkinned)
Model.NormalInstancing->Filter(filter, passed, flags); Model.NormalUnskinned->Filter(filter, passed, flags);
} }
/** /**
@ -375,9 +392,9 @@ public:
*/ */
void FilterTranspModels(CModelFilter& filter, int passed, int flags = 0) void FilterTranspModels(CModelFilter& filter, int passed, int flags = 0)
{ {
Model.Transp->Filter(filter, passed, flags); Model.TranspSkinned->Filter(filter, passed, flags);
if (Model.TranspInstancing) if (Model.TranspUnskinned != Model.TranspSkinned)
Model.TranspInstancing->Filter(filter, passed, flags); Model.TranspUnskinned->Filter(filter, passed, flags);
} }
}; };
@ -410,10 +427,12 @@ CRenderer::CRenderer()
m_Options.m_ShadowPCF = false; m_Options.m_ShadowPCF = false;
m_Options.m_PreferGLSL = false; m_Options.m_PreferGLSL = false;
m_Options.m_ForceAlphaTest = false; m_Options.m_ForceAlphaTest = false;
m_Options.m_GPUSkinning = false;
// TODO: be more consistent in use of the config system // TODO: be more consistent in use of the config system
CFG_GET_USER_VAL("preferglsl", Bool, m_Options.m_PreferGLSL); CFG_GET_USER_VAL("preferglsl", Bool, m_Options.m_PreferGLSL);
CFG_GET_USER_VAL("forcealphatest", Bool, m_Options.m_ForceAlphaTest); CFG_GET_USER_VAL("forcealphatest", Bool, m_Options.m_ForceAlphaTest);
CFG_GET_USER_VAL("gpuskinning", Bool, m_Options.m_GPUSkinning);
#if CONFIG2_GLES #if CONFIG2_GLES
// Override config option since GLES only supports GLSL // Override config option since GLES only supports GLSL
@ -535,21 +554,31 @@ void CRenderer::ReloadShaders()
bool cpuLighting = (GetRenderPath() == RP_FIXED); bool cpuLighting = (GetRenderPath() == RP_FIXED);
m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer(cpuLighting)); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer(cpuLighting));
m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer); m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false));
m->Model.Normal = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); if (GetRenderPath() == RP_SHADER && m_Options.m_GPUSkinning) // TODO: should check caps and GLSL etc too
m->Model.Transp = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); {
m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true));
m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
}
else
{
m->Model.VertexGPUSkinningShader.reset();
m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
}
// Use instancing renderers in shader mode // Use instancing renderers in shader mode
if (GetRenderPath() == RP_SHADER) if (GetRenderPath() == RP_SHADER)
{ {
m->Model.NormalInstancing = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
m->Model.TranspInstancing = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
} }
else else
{ {
m->Model.NormalInstancing.reset(); m->Model.NormalUnskinned = m->Model.NormalSkinned;
m->Model.TranspInstancing.reset(); m->Model.TranspUnskinned = m->Model.TranspSkinned;
} }
m->ShadersDirty = false; m->ShadersDirty = false;
@ -1310,12 +1339,12 @@ void CRenderer::RenderSubmissions()
// Prepare model renderers // Prepare model renderers
{ {
PROFILE3("prepare models"); PROFILE3("prepare models");
m->Model.Normal->PrepareModels(); m->Model.NormalSkinned->PrepareModels();
m->Model.Transp->PrepareModels(); m->Model.TranspSkinned->PrepareModels();
if (m->Model.NormalInstancing) if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalInstancing->PrepareModels(); m->Model.NormalUnskinned->PrepareModels();
if (m->Model.TranspInstancing) if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspInstancing->PrepareModels(); m->Model.TranspUnskinned->PrepareModels();
} }
m->terrainRenderer.PrepareForRendering(); m->terrainRenderer.PrepareForRendering();
@ -1441,12 +1470,12 @@ void CRenderer::EndFrame()
m->particleRenderer.EndFrame(); m->particleRenderer.EndFrame();
// Finish model renderers // Finish model renderers
m->Model.Normal->EndFrame(); m->Model.NormalSkinned->EndFrame();
m->Model.Transp->EndFrame(); m->Model.TranspSkinned->EndFrame();
if (m->Model.NormalInstancing) if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalInstancing->EndFrame(); m->Model.NormalUnskinned->EndFrame();
if (m->Model.TranspInstancing) if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspInstancing->EndFrame(); m->Model.TranspUnskinned->EndFrame();
ogl_tex_bind(0, 0); ogl_tex_bind(0, 0);
@ -1560,24 +1589,21 @@ void CRenderer::SubmitNonRecursive(CModel* model)
// Tricky: The call to GetWorldBounds() above can invalidate the position // Tricky: The call to GetWorldBounds() above can invalidate the position
model->ValidatePosition(); model->ValidatePosition();
bool canUseInstancing = false; bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0);
if (model->GetModelDef()->GetNumBones() == 0)
canUseInstancing = true;
if (model->GetMaterial().UsesAlphaBlending()) if (model->GetMaterial().UsesAlphaBlending())
{ {
if (canUseInstancing && m->Model.TranspInstancing) if (requiresSkinning)
m->Model.TranspInstancing->Submit(model); m->Model.TranspSkinned->Submit(model);
else else
m->Model.Transp->Submit(model); m->Model.TranspUnskinned->Submit(model);
} }
else else
{ {
if (canUseInstancing && m->Model.NormalInstancing) if (requiresSkinning)
m->Model.NormalInstancing->Submit(model); m->Model.NormalSkinned->Submit(model);
else else
m->Model.Normal->Submit(model); m->Model.NormalUnskinned->Submit(model);
} }
} }

View File

@ -123,6 +123,7 @@ public:
bool m_ShadowPCF; bool m_ShadowPCF;
bool m_PreferGLSL; bool m_PreferGLSL;
bool m_ForceAlphaTest; bool m_ForceAlphaTest;
bool m_GPUSkinning;
} m_Options; } m_Options;
struct Caps { struct Caps {

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games. /* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -146,6 +146,26 @@ VertexArrayIterator<u16> VertexArray::Attribute::GetIterator<u16>() const
return vertexArray->MakeIterator<u16>(this); return vertexArray->MakeIterator<u16>(this);
} }
template<>
VertexArrayIterator<u8> VertexArray::Attribute::GetIterator<u8>() const
{
ENSURE(vertexArray);
ENSURE(type == GL_UNSIGNED_BYTE);
ENSURE(elems >= 1);
return vertexArray->MakeIterator<u8>(this);
}
template<>
VertexArrayIterator<u8[4]> VertexArray::Attribute::GetIterator<u8[4]>() const
{
ENSURE(vertexArray);
ENSURE(type == GL_UNSIGNED_BYTE);
ENSURE(elems >= 4);
return vertexArray->MakeIterator<u8[4]>(this);
}
static size_t RoundStride(size_t stride) static size_t RoundStride(size_t stride)