Index: ps/trunk/source/graphics/ModelDef.h =================================================================== --- ps/trunk/source/graphics/ModelDef.h (revision 11489) +++ ps/trunk/source/graphics/ModelDef.h (revision 11490) @@ -1,279 +1,283 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Defines a raw 3d model. */ #ifndef INCLUDED_MODELDEF #define INCLUDED_MODELDEF #include "ps/CStr.h" #include "maths/Vector3D.h" #include "maths/Quaternion.h" #include "lib/file/vfs/vfs_path.h" #include "renderer/VertexArray.h" #include #include class CBoneState; /** * Describes the position of a prop point within its parent model. A prop point is the location within a parent model * where the prop's origin will be attached. * * A prop point is specified by its transformation matrix (or separately by its position and rotation), which * can be relative to either the parent model's origin, or one of the parent's bones. If the parent model is boned, * then the @ref m_BoneIndex field may specify a bone to which the transformation matrix is relative (see * @ref CModel::m_BoneMatrices). Otherwise, the transformation matrix is assumed to be relative to the parent model's * origin. * * @see CModel::m_BoneMatrices */ struct SPropPoint { /// Name of the prop point CStr m_Name; /** * Position of the point within the parent model, relative to either the parent model's origin or one of the parent * model's bones if applicable. Also specified as part of @ref m_Transform. * @see m_Transform */ CVector3D m_Position; /** * Rotation of the prop model that will be attached at this point. Also specified as part of @ref m_Transform. * @see m_Transform */ CQuaternion m_Rotation; /** * Object to parent space transformation. Combines both @ref m_Position and @ref m_Rotation in a single * transformation matrix. This transformation is relative to either the parent model's origin, or one of its * bones, depending on whether it is skeletal. If relative to a bone, then the bone in the parent model to * which this transformation is relative may be found by m_BoneIndex. * @see m_Position, m_Rotation */ CMatrix3D m_Transform; /** * Index of parent bone to which this prop point is relative, if any. The value 0xFF specifies that either the parent * model is unboned, or that this prop point is relative to the parent model's origin rather than one if its bones. */ u8 m_BoneIndex; }; /////////////////////////////////////////////////////////////////////////////// // SVertexBlend: structure containing the necessary data for blending vertices // with multiple bones struct SVertexBlend { enum { SIZE = 4 }; // index of the influencing bone, or 0xff if none u8 m_Bone[SIZE]; // weight of the influence; all weights sum to 1 float m_Weight[SIZE]; bool operator==(const SVertexBlend& o) const { return !memcmp(m_Bone, o.m_Bone, sizeof(m_Bone)) && !memcmp(m_Weight, o.m_Weight, sizeof(m_Weight)); } }; /////////////////////////////////////////////////////////////////////////////// // SModelVertex: structure containing per-vertex data struct SModelVertex { // vertex position CVector3D m_Coords; // vertex normal CVector3D m_Norm; // vertex UVs float m_U, m_V; // vertex blend data SVertexBlend m_Blend; }; /////////////////////////////////////////////////////////////////////////////// // SModelFace: structure containing per-face data struct SModelFace { // indices of the 3 vertices on this face u16 m_Verts[3]; }; //////////////////////////////////////////////////////////////////////////////////////// // CModelDefRPrivate class CModelDefRPrivate { public: CModelDefRPrivate() { } virtual ~CModelDefRPrivate() { } }; //////////////////////////////////////////////////////////////////////////////////////// // CModelDef: a raw 3D model; describes the vertices, faces, skinning and skeletal // information of a model class CModelDef { + NONCOPYABLE(CModelDef); + public: // current file version given to saved animations enum { FILE_VERSION = 3 }; // supported file read version - files with a version less than this will be rejected enum { FILE_READ_VERSION = 1 }; public: CModelDef(); ~CModelDef(); // model I/O functions static void Save(const VfsPath& filename,const CModelDef* mdef); /** * Loads a PMD file. * @param filename VFS path of .pmd file to load * @param name arbitrary name to give the model for debugging purposes (usually pathname) * @return the model - always non-NULL * @throw PSERROR_File if it can't load the model */ static CModelDef* Load(const VfsPath& filename, const VfsPath& name); public: // accessor: get vertex data size_t GetNumVertices() const { return m_NumVertices; } SModelVertex* GetVertices() const { return m_pVertices; } // accessor: get face data size_t GetNumFaces() const { return m_NumFaces; } SModelFace* GetFaces() const { return m_pFaces; } // accessor: get bone data size_t GetNumBones() const { return m_NumBones; } CBoneState* GetBones() const { return m_Bones; } + CMatrix3D* GetInverseBindBoneMatrices() { return m_InverseBindBoneMatrices; } // accessor: get blend data size_t GetNumBlends() const { return m_NumBlends; } SVertexBlend* GetBlends() const { return m_pBlends; } size_t* GetBlendIndices() const { return m_pBlendIndices; } // find and return pointer to prop point matching given name; return // null if no match (case insensitive search) const SPropPoint* FindPropPoint(const char* name) const; /** * Transform the given vertex's position from the bind pose into the new pose. * * @return new world-space vertex coordinates */ static CVector3D SkinPoint(const SModelVertex& vtx, const CMatrix3D newPoseMatrices[]); /** * Transform the given vertex's normal from the bind pose into the new pose. * * @return new world-space vertex normal */ static CVector3D SkinNormal(const SModelVertex& vtx, const CMatrix3D newPoseMatrices[]); /** * Transform vertices' positions and normals. * (This is equivalent to looping over SkinPoint and SkinNormal, * but slightly more efficient.) */ static void SkinPointsAndNormals( size_t numVertices, const VertexArrayIterator& Position, const VertexArrayIterator& Normal, const SModelVertex* vertices, const size_t* blendIndices, const CMatrix3D newPoseMatrices[]); #if ARCH_X86_X64 /** * SSE-optimised version of SkinPointsAndNormals. */ static void SkinPointsAndNormals_SSE( size_t numVertices, const VertexArrayIterator& Position, const VertexArrayIterator& Normal, const SModelVertex* vertices, const size_t* blendIndices, const CMatrix3D newPoseMatrices[]); #endif /** * Blend bone matrices together to fill bone palette. */ void BlendBoneMatrices(CMatrix3D boneMatrices[]); /** * Register renderer private data. Use the key to * distinguish between private data used by different render paths. * The private data will be managed by this CModelDef object: * It will be deleted when CModelDef is destructed or when private * data is registered using the same key. * * @param key The opaque key that is used to identify the caller. * The given private data can be retrieved by passing key to GetRenderData. * @param data The private data. * * postconditions : data is bound to the lifetime of this CModelDef * object. */ void SetRenderData(const void* key, CModelDefRPrivate* data); // accessor: render data CModelDefRPrivate* GetRenderData(const void* key) const; // accessor: get model name (for debugging) const VfsPath& GetName() const { return m_Name; } public: // vertex data size_t m_NumVertices; SModelVertex* m_pVertices; // face data size_t m_NumFaces; SModelFace* m_pFaces; // bone data - default model pose size_t m_NumBones; CBoneState* m_Bones; + CMatrix3D* m_InverseBindBoneMatrices; // blend data size_t m_NumBlends; SVertexBlend *m_pBlends; size_t* m_pBlendIndices; // prop point data std::vector m_PropPoints; private: VfsPath m_Name; // filename // renderdata shared by models of the same modeldef, // by render path typedef std::map RenderDataMap; RenderDataMap m_RenderData; }; #endif Index: ps/trunk/source/graphics/Model.cpp =================================================================== --- ps/trunk/source/graphics/Model.cpp (revision 11489) +++ ps/trunk/source/graphics/Model.cpp (revision 11490) @@ -1,620 +1,624 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Mesh object with texture and skinning information */ #include "precompiled.h" #include "Model.h" #include "ModelDef.h" #include "maths/Quaternion.h" #include "maths/BoundingBoxAligned.h" #include "SkeletonAnim.h" #include "SkeletonAnimDef.h" #include "SkeletonAnimManager.h" #include "MeshManager.h" #include "ObjectEntry.h" #include "lib/res/graphics/ogl_tex.h" #include "lib/res/h_mgr.h" #include "lib/sysdep/rtl.h" #include "ps/Profile.h" #include "ps/CLogger.h" +#include "renderer/Renderer.h" ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // Constructor CModel::CModel(CSkeletonAnimManager& skeletonAnimManager) : 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_SkeletonAnimManager(skeletonAnimManager) { } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // Destructor CModel::~CModel() { ReleaseData(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // ReleaseData: delete anything allocated by the model void CModel::ReleaseData() { rtl_FreeAligned(m_BoneMatrices); - delete[] m_InverseBindBoneMatrices; for (size_t i = 0; i < m_Props.size(); ++i) delete m_Props[i].m_Model; m_Props.clear(); m_pModelDef = CModelDefPtr(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // InitModel: setup model from given geometry bool CModel::InitModel(const CModelDefPtr& modeldef) { // clean up any existing data first ReleaseData(); m_pModelDef = modeldef; size_t numBones = modeldef->GetNumBones(); if (numBones != 0) { size_t numBlends = modeldef->GetNumBlends(); // allocate matrices for bone transformations // (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); - for (size_t i = 0; i < numBones + numBlends; ++i) + for (size_t i = 0; i < numBones + 1 + numBlends; ++i) { 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; return true; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // CalcBound: calculate the world space bounds of this model void CModel::CalcBounds() { // Need to calculate the object bounds first, if that hasn't already been done if (! (m_Anim && m_Anim->m_AnimDef)) { if (m_ObjectBounds.IsEmpty()) CalcStaticObjectBounds(); } else { if (m_Anim->m_ObjectBounds.IsEmpty()) CalcAnimatedObjectBounds(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds); ENSURE(! m_Anim->m_ObjectBounds.IsEmpty()); // (if this happens, it'll be recalculating the bounds every time) m_ObjectBounds = m_Anim->m_ObjectBounds; } // Ensure the transform is set correctly before we use it ValidatePosition(); // Now transform the object-space bounds to world-space bounds m_ObjectBounds.Transform(GetTransform(), m_WorldBounds); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // CalcObjectBounds: calculate object space bounds of this model, based solely on vertex positions void CModel::CalcStaticObjectBounds() { m_ObjectBounds.SetEmpty(); size_t numverts=m_pModelDef->GetNumVertices(); SModelVertex* verts=m_pModelDef->GetVertices(); for (size_t i=0;im_AnimDef) { CSkeletonAnim dummyanim; dummyanim.m_AnimDef=anim; if (!SetAnimation(&dummyanim)) return; } size_t numverts=m_pModelDef->GetNumVertices(); SModelVertex* verts=m_pModelDef->GetVertices(); // Remove any transformations, so that we calculate the bounding box // at the origin. The box is later re-transformed onto the object, without // having to recalculate the size of the box. CMatrix3D transform, oldtransform = GetTransform(); CModelAbstract* oldparent = m_Parent; m_Parent = 0; transform.SetIdentity(); CRenderableObject::SetTransform(transform); // Following seems to stomp over the current animation time - which, unsurprisingly, // introduces artefacts in the currently playing animation. Save it here and restore it // at the end. float AnimTime = m_AnimTime; // iterate through every frame of the animation for (size_t j=0;jGetNumFrames();j++) { m_PositionValid = false; ValidatePosition(); // extend bounds by vertex positions at the frame for (size_t i=0;iGetFrameTime(); } m_PositionValid = false; m_Parent = oldparent; SetTransform(oldtransform); m_AnimTime = AnimTime; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// const CBoundingBoxAligned CModel::GetWorldBoundsRec() { CBoundingBoxAligned bounds = GetWorldBounds(); for (size_t i = 0; i < m_Props.size(); ++i) bounds += m_Props[i].m_Model->GetWorldBoundsRec(); return bounds; } const CBoundingBoxAligned CModel::GetObjectSelectionBoundsRec() { CBoundingBoxAligned objBounds = GetObjectBounds(); // updates the (children-not-included) object-space bounds if necessary // now extend these bounds to include the props' selection bounds (if any) for (size_t i = 0; i < m_Props.size(); ++i) { const Prop& prop = m_Props[i]; if (prop.m_Hidden) continue; // prop is hidden from rendering, so it also shouldn't be used for selection CBoundingBoxAligned propSelectionBounds = prop.m_Model->GetObjectSelectionBoundsRec(); if (propSelectionBounds.IsEmpty()) continue; // submodel does not wish to participate in selection box, exclude it // We have the prop's bounds in its own object-space; now we need to transform them so they can be properly added // to the bounds in our object-space. For that, we need the transform of the prop attachment point. // // We have the prop point information; however, it's not trivial to compute its exact location in our object-space // since it may or may not be attached to a bone (see SPropPoint), which in turn may or may not be in the middle of // an animation. The bone matrices might be of interest, but they're really only meant to be used for the animation // system and are quite opaque to use from the outside (see @ref ValidatePosition). // // However, a nice side effect of ValidatePosition is that it also computes the absolute world-space transform of // our props and sets it on their respective models. In particular, @ref ValidatePosition will compute the prop's // world-space transform as either // // T' = T x B x O // or // T' = T x O // // where T' is the prop's world-space transform, T is our world-space transform, O is the prop's local // offset/rotation matrix, and B is an optional transformation matrix of the bone the prop is attached to // (taking into account animation and everything). // // From this, it is clear that either O or B x O is the object-space transformation matrix of the prop. So, // all we need to do is apply our own inverse world-transform T^(-1) to T' to get our desired result. Luckily, // this is precomputed upon setting the transform matrix (see @ref SetTransform), so it is free to fetch. CMatrix3D propObjectTransform = prop.m_Model->GetTransform(); // T' propObjectTransform.Concatenate(GetInvTransform()); // T^(-1) x T' // Transform the prop's bounds into our object coordinate space CBoundingBoxAligned transformedPropSelectionBounds; propSelectionBounds.Transform(propObjectTransform, transformedPropSelectionBounds); objBounds += transformedPropSelectionBounds; } return objBounds; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // BuildAnimation: load raw animation frame animation from given file, and build a // animation specific to this model CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, float speed, float actionpos, float actionpos2) { CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname); if (!def) return NULL; CSkeletonAnim* anim = new CSkeletonAnim(); anim->m_Name = name; anim->m_AnimDef = def; anim->m_Speed = speed; if (actionpos == -1.f) anim->m_ActionPos = -1.f; else anim->m_ActionPos = actionpos * anim->m_AnimDef->GetDuration(); if (actionpos2 == -1.f) anim->m_ActionPos2 = -1.f; else anim->m_ActionPos2 = actionpos2 * anim->m_AnimDef->GetDuration(); anim->m_ObjectBounds.SetEmpty(); InvalidateBounds(); return anim; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // Update: update this model to the given time, in msec void CModel::UpdateTo(float time) { // update animation time, but don't calculate bone matrices - do that (lazily) when // something requests them; that saves some calculation work for offscreen models, // and also assures the world space, inverted bone matrices (required for normal // skinning) are up to date with respect to m_Transform m_AnimTime = time; // mark vertices as dirty SetDirty(RENDERDATA_UPDATE_VERTICES); // mark matrices as dirty InvalidatePosition(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // InvalidatePosition void CModel::InvalidatePosition() { m_PositionValid = false; for (size_t i = 0; i < m_Props.size(); ++i) m_Props[i].m_Model->InvalidatePosition(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // ValidatePosition: ensure that current transform and bone matrices are both uptodate void CModel::ValidatePosition() { if (m_PositionValid) { ENSURE(!m_Parent || m_Parent->m_PositionValid); return; } if (m_Parent && !m_Parent->m_PositionValid) { // Make sure we don't base our calculations on // a parent animation state that is out of date. m_Parent->ValidatePosition(); // Parent will recursively call our validation. ENSURE(m_PositionValid); return; } if (m_Anim && m_BoneMatrices) { // PROFILE( "generating bone matrices" ); ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys()); 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) { // Bones but no animation - probably a buggy actor forgot to set up the animation, // so just render it in its bind pose - const CMatrix3D& transform = GetTransform(); for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) { m_BoneMatrices[i].SetIdentity(); m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation); 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 // that doing so will cause a revalidation of this model (see recursion above). m_PositionValid = true; // re-position and validate all props for (size_t j = 0; j < m_Props.size(); ++j) { const Prop& prop=m_Props[j]; CMatrix3D proptransform = prop.m_Point->m_Transform;; if (prop.m_Point->m_BoneIndex != 0xff) { - // m_BoneMatrices[i] already have world transform pre-applied (see above) - proptransform.Concatenate(m_BoneMatrices[prop.m_Point->m_BoneIndex]); + CMatrix3D boneMatrix = m_BoneMatrices[prop.m_Point->m_BoneIndex]; + if (!worldSpaceBoneMatrices) + boneMatrix.Concatenate(GetTransform()); + proptransform.Concatenate(boneMatrix); } else { // not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin) proptransform.Concatenate(m_Transform); } prop.m_Model->SetTransform(proptransform); prop.m_Model->ValidatePosition(); } if (m_BoneMatrices) { 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 // is influenced by the bind-shape transform instead of a particular bone, // which we indicate with the blending bone ID set to the total number // of bones. But since we're skinning in world space, we use the model's // world space transform and store that matrix in this special index. // (see http://trac.wildfiregames.com/ticket/1012) m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform; - m_pModelDef->BlendBoneMatrices(m_BoneMatrices); + if (computeBlendMatrices) + m_pModelDef->BlendBoneMatrices(m_BoneMatrices); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // SetAnimation: set the given animation as the current animation on this model; // return false on error, else true bool CModel::SetAnimation(CSkeletonAnim* anim, bool once) { m_Anim = NULL; // in case something fails if (anim) { m_Flags &= ~MODELFLAG_NOLOOPANIMATION; if (once) m_Flags |= MODELFLAG_NOLOOPANIMATION; if (!m_BoneMatrices && anim->m_AnimDef) { // not boned, can't animate return false; } if (m_BoneMatrices && !anim->m_AnimDef) { // boned, but animation isn't valid // (e.g. the default (static) idle animation on an animated unit) return false; } if (anim->m_AnimDef && anim->m_AnimDef->GetNumKeys() != m_pModelDef->GetNumBones()) { // mismatch between model's skeleton and animation's skeleton LOGERROR(L"Mismatch between model's skeleton and animation's skeleton (%lu model bones != %lu animation keys)", (unsigned long)m_pModelDef->GetNumBones(), (unsigned long)anim->m_AnimDef->GetNumKeys()); return false; } // reset the cached bounds when the animation is changed m_ObjectBounds.SetEmpty(); InvalidateBounds(); // start anim from beginning m_AnimTime = 0; } m_Anim = anim; return true; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // CopyAnimation void CModel::CopyAnimationFrom(CModel* source) { m_Anim = source->m_Anim; m_AnimTime = source->m_AnimTime; m_Flags &= ~MODELFLAG_CASTSHADOWS; if (source->m_Flags & MODELFLAG_CASTSHADOWS) m_Flags |= MODELFLAG_CASTSHADOWS; m_ObjectBounds.SetEmpty(); InvalidateBounds(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // AddProp: add a prop to the model on the given point void CModel::AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry) { // position model according to prop point position // this next call will invalidate the bounds of "model", which will in turn also invalidate the selection box model->SetTransform(point->m_Transform); model->m_Parent = this; Prop prop; prop.m_Point = point; prop.m_Model = model; prop.m_ObjectEntry = objectentry; m_Props.push_back(prop); } void CModel::AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry) { AddProp(point, model, objectentry); m_AmmoPropPoint = point; m_AmmoLoadedProp = m_Props.size() - 1; m_Props[m_AmmoLoadedProp].m_Hidden = true; // we only need to invalidate the selection box here if it is based on props and their visibilities if (!m_CustomSelectionShape) m_SelectionBoxValid = false; } void CModel::ShowAmmoProp() { if (m_AmmoPropPoint == NULL) return; // Show the ammo prop, hide all others on the same prop point for (size_t i = 0; i < m_Props.size(); ++i) if (m_Props[i].m_Point == m_AmmoPropPoint) m_Props[i].m_Hidden = (i != m_AmmoLoadedProp); // we only need to invalidate the selection box here if it is based on props and their visibilities if (!m_CustomSelectionShape) m_SelectionBoxValid = false; } void CModel::HideAmmoProp() { if (m_AmmoPropPoint == NULL) return; // Hide the ammo prop, show all others on the same prop point for (size_t i = 0; i < m_Props.size(); ++i) if (m_Props[i].m_Point == m_AmmoPropPoint) m_Props[i].m_Hidden = (i == m_AmmoLoadedProp); // we only need to invalidate here if the selection box is based on props and their visibilities if (!m_CustomSelectionShape) m_SelectionBoxValid = false; } CModelAbstract* CModel::FindFirstAmmoProp() { if (m_AmmoPropPoint) return m_Props[m_AmmoLoadedProp].m_Model; for (size_t i = 0; i < m_Props.size(); ++i) { CModel* propModel = m_Props[i].m_Model->ToCModel(); if (propModel) { CModelAbstract* model = propModel->FindFirstAmmoProp(); if (model) return model; } } return NULL; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // Clone: return a clone of this model CModelAbstract* CModel::Clone() const { CModel* clone = new CModel(m_SkeletonAnimManager); clone->m_ObjectBounds = m_ObjectBounds; clone->InitModel(m_pModelDef); clone->SetMaterial(m_Material); clone->SetAnimation(m_Anim); clone->SetFlags(m_Flags); for (size_t i = 0; i < m_Props.size(); i++) { // eek! TODO, RC - need to investigate shallow clone here if (m_AmmoPropPoint && i == m_AmmoLoadedProp) clone->AddAmmoProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry); else clone->AddProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry); } return clone; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // SetTransform: set the transform on this object, and reorientate props accordingly void CModel::SetTransform(const CMatrix3D& transform) { // call base class to set transform on this object CRenderableObject::SetTransform(transform); InvalidatePosition(); } ////////////////////////////////////////////////////////////////////////// void CModel::AddFlagsRec(int flags) { m_Flags |= flags; if (flags & MODELFLAG_IGNORE_LOS) m_Material.AddShaderDefine("IGNORE_LOS", "1"); for (size_t i = 0; i < m_Props.size(); ++i) if (m_Props[i].m_Model->ToCModel()) m_Props[i].m_Model->ToCModel()->AddFlagsRec(flags); } void CModel::SetMaterial(const CMaterial &material) { m_Material = material; } void CModel::SetPlayerID(player_id_t id) { CModelAbstract::SetPlayerID(id); for (std::vector::iterator it = m_Props.begin(); it != m_Props.end(); ++it) it->m_Model->SetPlayerID(id); } void CModel::SetShadingColor(const CColor& colour) { CModelAbstract::SetShadingColor(colour); for (std::vector::iterator it = m_Props.begin(); it != m_Props.end(); ++it) it->m_Model->SetShadingColor(colour); } Index: ps/trunk/source/graphics/ModelDef.cpp =================================================================== --- ps/trunk/source/graphics/ModelDef.cpp (revision 11489) +++ ps/trunk/source/graphics/ModelDef.cpp (revision 11490) @@ -1,438 +1,447 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Defines a raw 3d model. */ #include "precompiled.h" #include "ModelDef.h" #include "graphics/SkeletonAnimDef.h" #include "ps/FileIo.h" #include "maths/Vector4D.h" #if ARCH_X86_X64 # include #endif CVector3D CModelDef::SkinPoint(const SModelVertex& vtx, const CMatrix3D newPoseMatrices[]) { CVector3D result (0, 0, 0); for (int i = 0; i < SVertexBlend::SIZE && vtx.m_Blend.m_Bone[i] != 0xff; ++i) { result += newPoseMatrices[vtx.m_Blend.m_Bone[i]].Transform(vtx.m_Coords) * vtx.m_Blend.m_Weight[i]; } return result; } CVector3D CModelDef::SkinNormal(const SModelVertex& vtx, const CMatrix3D newPoseMatrices[]) { // To be correct, the normal vectors apparently need to be multiplied by the // inverse of the transpose. Unfortunately inverses are slow. // If a matrix is orthogonal, M * M^T = I and so the inverse of the transpose // is the original matrix. But that's not entirely relevant here, because // the bone matrices include translation components and so they're not // orthogonal. // But that's okay because we have // M = T * R // and want to find // n' = (M^T^-1) * n // = (T * R)^T^-1 * n // = (R^T * T^T)^-1 * n // = (T^T^-1 * R^T^-1) * n // R is indeed orthogonal so R^T^-1 = R. T isn't orthogonal at all. // But n is only a 3-vector, and from the forms of T and R (which have // lots of zeroes) I can convince myself that replacing T with T^T^-1 has no // effect on anything but the fourth component of M^T^-1 - and the fourth // component is discarded since it has no effect on n', and so we can happily // use n' = M*n. // // (This isn't very good as a proof, but it's better than assuming M is // orthogonal when it's clearly not.) CVector3D result (0, 0, 0); for (int i = 0; i < SVertexBlend::SIZE && vtx.m_Blend.m_Bone[i] != 0xff; ++i) { result += newPoseMatrices[vtx.m_Blend.m_Bone[i]].Rotate(vtx.m_Norm) * vtx.m_Blend.m_Weight[i]; } // If there was more than one influence, the result is probably not going // to be of unit length (since it's a weighted sum of several independent // unit vectors), so we need to normalise it. // (It's fairly common to only have one influence, so it seems sensible to // optimise that case a bit.) if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence result.Normalize(); return result; } void CModelDef::SkinPointsAndNormals( size_t numVertices, const VertexArrayIterator& Position, const VertexArrayIterator& Normal, const SModelVertex* vertices, const size_t* blendIndices, const CMatrix3D newPoseMatrices[]) { // To avoid some performance overhead, get the raw vertex array pointers char* PositionData = Position.GetData(); size_t PositionStride = Position.GetStride(); char* NormalData = Normal.GetData(); size_t NormalStride = Normal.GetStride(); for (size_t j = 0; j < numVertices; ++j) { const SModelVertex& vtx = vertices[j]; CVector3D pos = newPoseMatrices[blendIndices[j]].Transform(vtx.m_Coords); CVector3D norm = newPoseMatrices[blendIndices[j]].Rotate(vtx.m_Norm); // If there was more than one influence, the result is probably not going // to be of unit length (since it's a weighted sum of several independent // unit vectors), so we need to normalise it. // (It's fairly common to only have one influence, so it seems sensible to // optimise that case a bit.) if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence norm.Normalize(); memcpy(PositionData + PositionStride*j, &pos.X, 3*sizeof(float)); memcpy(NormalData + NormalStride*j, &norm.X, 3*sizeof(float)); } } #if ARCH_X86_X64 void CModelDef::SkinPointsAndNormals_SSE( size_t numVertices, const VertexArrayIterator& Position, const VertexArrayIterator& Normal, const SModelVertex* vertices, const size_t* blendIndices, const CMatrix3D newPoseMatrices[]) { // To avoid some performance overhead, get the raw vertex array pointers char* PositionData = Position.GetData(); size_t PositionStride = Position.GetStride(); char* NormalData = Normal.GetData(); size_t NormalStride = Normal.GetStride(); // Must be aligned correctly for SSE ASSERT((intptr_t)newPoseMatrices % 16 == 0); ASSERT((intptr_t)PositionData % 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); __m128 col0, col1, col2, col3, vec0, vec1, vec2; for (size_t j = 0; j < numVertices; ++j) { const SModelVertex& vtx = vertices[j]; const CMatrix3D& mtx = newPoseMatrices[blendIndices[j]]; // Loads matrix to xmm registers. col0 = _mm_load_ps(mtx._data); col1 = _mm_load_ps(mtx._data + 4); col2 = _mm_load_ps(mtx._data + 8); col3 = _mm_load_ps(mtx._data + 12); // Loads and computes vertex coordinates. vec0 = _mm_load1_ps(&vtx.m_Coords.X); // v0 = [x, x, x, x] vec0 = _mm_mul_ps(col0, vec0); // v0 = [_11*x, _21*x, _31*x, _41*x] vec1 = _mm_load1_ps(&vtx.m_Coords.Y); // v1 = [y, y, y, y] vec1 = _mm_mul_ps(col1, vec1); // v1 = [_12*y, _22*y, _32*y, _42*y] vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y, ...] vec1 = _mm_load1_ps(&vtx.m_Coords.Z); // v1 = [z, z, z, z] vec1 = _mm_mul_ps(col2, vec1); // v1 = [_13*z, _23*z, _33*z, _43*z] vec1 = _mm_add_ps(vec1, col3); // v1 = [_13*z + _14, ...] vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y + _13*z + _14, ...] _mm_store_ps((float*)(PositionData + PositionStride*j), vec0); // Loads and computes normal vectors. vec0 = _mm_load1_ps(&vtx.m_Norm.X); // v0 = [x, x, x, x] vec0 = _mm_mul_ps(col0, vec0); // v0 = [_11*x, _21*x, _31*x, _41*x] vec1 = _mm_load1_ps(&vtx.m_Norm.Y); // v1 = [y, y, y, y] vec1 = _mm_mul_ps(col1, vec1); // v1 = [_12*y, _22*y, _32*y, _42*y] vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y, ...] vec1 = _mm_load1_ps(&vtx.m_Norm.Z); // v1 = [z, z, z, z] vec1 = _mm_mul_ps(col2, vec1); // v1 = [_13*z, _23*z, _33*z, _43*z] vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y + _13*z, ...] // If there was more than one influence, the result is probably not going // to be of unit length (since it's a weighted sum of several independent // unit vectors), so we need to normalise it. // (It's fairly common to only have one influence, so it seems sensible to // optimise that case a bit.) if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence { // Normalization. // vec1 = [x*x, y*y, z*z, ?*?] vec1 = _mm_mul_ps(vec0, vec0); // vec2 = [y*y, z*z, x*x, ?*?] vec2 = _mm_shuffle_ps(vec1, vec1, _MM_SHUFFLE(3, 0, 2, 1)); vec1 = _mm_add_ps(vec1, vec2); // vec2 = [z*z, x*x, y*y, ?*?] vec2 = _mm_shuffle_ps(vec2, vec2, _MM_SHUFFLE(3, 0, 2, 1)); vec1 = _mm_add_ps(vec1, vec2); // rsqrt(a) = 1 / sqrt(a) vec1 = _mm_rsqrt_ps(vec1); vec0 = _mm_mul_ps(vec0, vec1); } _mm_store_ps((float*)(NormalData + NormalStride*j), vec0); } } #endif void CModelDef::BlendBoneMatrices( CMatrix3D boneMatrices[]) { for (size_t i = 0; i < m_NumBlends; ++i) { const SVertexBlend& blend = m_pBlends[i]; CMatrix3D& boneMatrix = boneMatrices[m_NumBones + 1 + i]; // Note: there is a special case of joint influence, in which the vertex // is influenced by the bind-shape matrix instead of a particular bone, // which we indicate by setting the bone ID to the total number of bones. // It should be blended with the world space transform and we have already // set up this matrix in boneMatrices. // (see http://trac.wildfiregames.com/ticket/1012) 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 = 2; j < SVertexBlend::SIZE && blend.m_Bone[j] != 0xFF; ++j) - { + for (size_t j = 1; j < SVertexBlend::SIZE && blend.m_Bone[j] != 0xFF; ++j) boneMatrix.AddBlend(boneMatrices[blend.m_Bone[j]], blend.m_Weight[j]); - } } } // CModelDef Constructor 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_Name(L"[not loaded]") { } // CModelDef Destructor CModelDef::~CModelDef() { for(RenderDataMap::iterator it = m_RenderData.begin(); it != m_RenderData.end(); ++it) delete it->second; delete[] m_pVertices; delete[] m_pFaces; delete[] m_Bones; + delete[] m_InverseBindBoneMatrices; delete[] m_pBlends; delete[] m_pBlendIndices; } // FindPropPoint: find and return pointer to prop point matching given name; // return null if no match (case insensitive search) const SPropPoint* CModelDef::FindPropPoint(const char* name) const { for (size_t i = 0; i < m_PropPoints.size(); ++i) if (m_PropPoints[i].m_Name == name) return &m_PropPoints[i]; return 0; } // Load: read and return a new CModelDef initialised with data from given file CModelDef* CModelDef::Load(const VfsPath& filename, const VfsPath& name) { CFileUnpacker unpacker; // read everything in from file unpacker.Read(filename,"PSMD"); // check version if (unpacker.GetVersion() mdef (new CModelDef()); mdef->m_Name = name; // now unpack everything mdef->m_NumVertices = unpacker.UnpackSize(); mdef->m_pVertices=new SModelVertex[mdef->m_NumVertices]; unpacker.UnpackRaw(mdef->m_pVertices,sizeof(SModelVertex)*mdef->m_NumVertices); mdef->m_NumFaces = unpacker.UnpackSize(); mdef->m_pFaces=new SModelFace[mdef->m_NumFaces]; unpacker.UnpackRaw(mdef->m_pFaces,sizeof(SModelFace)*mdef->m_NumFaces); mdef->m_NumBones = unpacker.UnpackSize(); if (mdef->m_NumBones) { mdef->m_Bones=new CBoneState[mdef->m_NumBones]; unpacker.UnpackRaw(mdef->m_Bones,mdef->m_NumBones*sizeof(CBoneState)); mdef->m_pBlendIndices = new size_t[mdef->m_NumVertices]; std::vector blends; for (size_t i = 0; i < mdef->m_NumVertices; i++) { const SVertexBlend &blend = mdef->m_pVertices[i].m_Blend; if (blend.m_Bone[1] == 0xFF) { mdef->m_pBlendIndices[i] = blend.m_Bone[0]; } else { // If there's already a vertex using the same blend as this, then // reuse its entry from blends; otherwise add the new one to blends size_t j; for (j = 0; j < blends.size(); j++) { if (blend == blends[j]) break; } if (j >= blends.size()) blends.push_back(blend); // This index is offset by one to allow the special case of a // weighted influence relative to the bind-shape rather than // a particular bone. See comment in BlendBoneMatrices. mdef->m_pBlendIndices[i] = mdef->m_NumBones + 1 + j; } } mdef->m_NumBlends = blends.size(); mdef->m_pBlends = new SVertexBlend[mdef->m_NumBlends]; std::copy(blends.begin(), blends.end(), mdef->m_pBlends); } if (unpacker.GetVersion() >= 2) { // versions >=2 also have prop point data size_t numPropPoints = unpacker.UnpackSize(); mdef->m_PropPoints.resize(numPropPoints); if (numPropPoints) { for (size_t i = 0; i < numPropPoints; i++) { unpacker.UnpackString(mdef->m_PropPoints[i].m_Name); unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_Position.X, sizeof(mdef->m_PropPoints[i].m_Position)); unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_Rotation.m_V.X, sizeof(mdef->m_PropPoints[i].m_Rotation)); unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_BoneIndex, sizeof(mdef->m_PropPoints[i].m_BoneIndex)); // build prop point transform mdef->m_PropPoints[i].m_Transform.SetIdentity(); mdef->m_PropPoints[i].m_Transform.Rotate(mdef->m_PropPoints[i].m_Rotation); mdef->m_PropPoints[i].m_Transform.Translate(mdef->m_PropPoints[i].m_Position); } } } if (unpacker.GetVersion() <= 2) { // Versions <=2 don't include the default 'root' prop point, so add it here SPropPoint prop; prop.m_Name = "root"; prop.m_Transform.SetIdentity(); prop.m_BoneIndex = 0xFF; mdef->m_PropPoints.push_back(prop); } if (unpacker.GetVersion() <= 2) { // Versions <=2 store the vertexes relative to the bind pose. That // isn't useful when you want to do correct skinning, so later versions // store them in world space. So, fix the old models by skinning each // vertex: if (mdef->m_NumBones) // only do skinned models { std::vector bindPose (mdef->m_NumBones); for (size_t i = 0; i < mdef->m_NumBones; ++i) { bindPose[i].SetIdentity(); bindPose[i].Rotate(mdef->m_Bones[i].m_Rotation); bindPose[i].Translate(mdef->m_Bones[i].m_Translation); } for (size_t i = 0; i < mdef->m_NumVertices; ++i) { mdef->m_pVertices[i].m_Coords = SkinPoint(mdef->m_pVertices[i], &bindPose[0]); mdef->m_pVertices[i].m_Norm = SkinNormal(mdef->m_pVertices[i], &bindPose[0]); } } } + // 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(); } // Save: write the given CModelDef to the given file void CModelDef::Save(const VfsPath& filename, const CModelDef* mdef) { CFilePacker packer(FILE_VERSION, "PSMD"); // pack everything up const size_t numVertices = mdef->GetNumVertices(); packer.PackSize(numVertices); packer.PackRaw(mdef->GetVertices(), sizeof(SModelVertex) * numVertices); const size_t numFaces = mdef->GetNumFaces(); packer.PackSize(numFaces); packer.PackRaw(mdef->GetFaces(), sizeof(SModelFace) * numFaces); const size_t numBones = mdef->m_NumBones; packer.PackSize(numBones); if (numBones) packer.PackRaw(mdef->m_Bones, sizeof(CBoneState) * numBones); const size_t numPropPoints = mdef->m_PropPoints.size(); packer.PackSize(numPropPoints); for (size_t i = 0; i < numPropPoints; i++) { packer.PackString(mdef->m_PropPoints[i].m_Name); packer.PackRaw(&mdef->m_PropPoints[i].m_Position.X, sizeof(mdef->m_PropPoints[i].m_Position)); packer.PackRaw(&mdef->m_PropPoints[i].m_Rotation.m_V.X, sizeof(mdef->m_PropPoints[i].m_Rotation)); packer.PackRaw(&mdef->m_PropPoints[i].m_BoneIndex, sizeof(mdef->m_PropPoints[i].m_BoneIndex)); } // flush everything out to file packer.Write(filename); } // SetRenderData: Set the render data object for the given key, void CModelDef::SetRenderData(const void* key, CModelDefRPrivate* data) { delete m_RenderData[key]; m_RenderData[key] = data; } // GetRenderData: Get the render data object for the given key, // or 0 if no such object exists. // Reference count of the render data object is automatically increased. CModelDefRPrivate* CModelDef::GetRenderData(const void* key) const { RenderDataMap::const_iterator it = m_RenderData.find(key); if (it != m_RenderData.end()) return it->second; return 0; } Index: ps/trunk/source/graphics/Model.h =================================================================== --- ps/trunk/source/graphics/Model.h (revision 11489) +++ ps/trunk/source/graphics/Model.h (revision 11490) @@ -1,300 +1,294 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Mesh object with texture and skinning information */ #ifndef INCLUDED_MODEL #define INCLUDED_MODEL #include #include "graphics/Texture.h" #include "graphics/Material.h" #include "graphics/MeshManager.h" #include "graphics/ModelAbstract.h" #include "ps/Overlay.h" struct SPropPoint; class CObjectEntry; class CSkeletonAnim; class CSkeletonAnimDef; class CSkeletonAnimManager; #define MODELFLAG_CASTSHADOWS (1<<0) #define MODELFLAG_NOLOOPANIMATION (1<<1) #define MODELFLAG_SILHOUETTE_DISPLAY (1<<2) #define MODELFLAG_SILHOUETTE_OCCLUDER (1<<3) #define MODELFLAG_IGNORE_LOS (1<<4) #define MODELFLAG_FILTERED (1<<5) // used internally by renderer /////////////////////////////////////////////////////////////////////////////// // CModel: basically, a mesh object - holds the texturing and skinning // information for a model in game class CModel : public CModelAbstract { NONCOPYABLE(CModel); public: struct Prop { Prop() : m_Point(0), m_Model(0), m_ObjectEntry(0), m_Hidden(false) {} /** * Location of the prop point within its parent model, relative to either a bone in the parent model or to the * parent model's origin. See the documentation for @ref SPropPoint for more details. * @see SPropPoint */ const SPropPoint* m_Point; /** * Pointer to the model associated with this prop. Note that the transform matrix held by this model is the full object-to-world * space transform, taking into account all parent model positioning (see @ref CModel::ValidatePosition for positioning logic). * @see CModel::ValidatePosition */ CModelAbstract* m_Model; CObjectEntry* m_ObjectEntry; bool m_Hidden; ///< Should this prop be temporarily removed from rendering? }; public: // constructor CModel(CSkeletonAnimManager& skeletonAnimManager); // destructor ~CModel(); /// Dynamic cast virtual CModel* ToCModel() { return this; } // setup model from given geometry bool InitModel(const CModelDefPtr& modeldef); // update this model's state; 'time' is the absolute time since the start of the animation, in MS void UpdateTo(float time); // get the model's geometry data const CModelDefPtr& GetModelDef() { return m_pModelDef; } // set the model's material void SetMaterial(const CMaterial &material); // set the model's player ID, recursively through props void SetPlayerID(player_id_t id); // set the models mod color virtual void SetShadingColor(const CColor& colour); // get the model's material CMaterial& GetMaterial() { return m_Material; } // set the given animation as the current animation on this model bool SetAnimation(CSkeletonAnim* anim, bool once = false); // get the currently playing animation, if any CSkeletonAnim* GetAnimation() const { return m_Anim; } // set the animation state to be the same as from another; both models should // be compatible types (same type of skeleton) void CopyAnimationFrom(CModel* source); // set object flags void SetFlags(int flags) { m_Flags=flags; } // get object flags int GetFlags() const { return m_Flags; } // add object flags, recursively through props void AddFlagsRec(int flags); // recurse down tree setting dirty bits virtual void SetDirtyRec(int dirtyflags) { SetDirty(dirtyflags); for (size_t i=0;iSetDirtyRec(dirtyflags); } } virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) { for (size_t i = 0; i < m_Props.size(); ++i) m_Props[i].m_Model->SetTerrainDirty(i0, j0, i1, j1); } virtual void SetEntityVariable(const std::string& name, float value) { for (size_t i = 0; i < m_Props.size(); ++i) m_Props[i].m_Model->SetEntityVariable(name, value); } // --- WORLD/OBJECT SPACE BOUNDS ----------------------------------------------------------------- /// Overridden to calculate both the world-space and object-space bounds of this model, and stores the result in /// m_Bounds and m_ObjectBounds, respectively. virtual void CalcBounds(); /// Returns the object-space bounds for this model, excluding its children. const CBoundingBoxAligned& GetObjectBounds() { RecalculateBoundsIfNecessary(); // recalculates both object-space and world-space bounds if necessary return m_ObjectBounds; } virtual const CBoundingBoxAligned GetWorldBoundsRec(); // reimplemented here /// Auxiliary method; calculates object space bounds of this model, based solely on vertex positions, and stores /// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcAnimatedObjectBounds) if it has been determined /// that the object-space bounds are static. void CalcStaticObjectBounds(); /// Auxiliary method; calculate object-space bounds encompassing all vertex positions for given animation, and stores /// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcStaticBounds) if it has been determined that the /// object-space bounds need to take animations into account. void CalcAnimatedObjectBounds(CSkeletonAnimDef* anim,CBoundingBoxAligned& result); // --- SELECTION BOX/BOUNDS ---------------------------------------------------------------------- /// Reimplemented here since proper models should participate in selection boxes. virtual const CBoundingBoxAligned GetObjectSelectionBoundsRec(); /** * Set transform of this object. * * @note In order to ensure that all child props are updated properly, * you must call ValidatePosition(). */ virtual void SetTransform(const CMatrix3D& transform); /** * Return whether this is a skinned/skeletal model. If it is, Get*BoneMatrices() * will return valid non-NULL arrays. */ bool IsSkinned() { return (m_BoneMatrices != NULL); } // return the models bone matrices; 16-byte aligned for SSE reads const CMatrix3D* GetAnimatedBoneMatrices() { ENSURE(m_PositionValid); return m_BoneMatrices; } - const CMatrix3D* GetInverseBindBoneMatrices() { - return m_InverseBindBoneMatrices; - } - /** * Load raw animation frame animation from given file, and build an * animation specific to this model. * @param pathname animation file to load * @param name animation name (e.g. "idle") * @param speed animation speed as a factor of the default animation speed * @param actionpos offset of 'action' event, in range [0, 1] * @param actionpos2 offset of 'action2' event, in range [0, 1] * @return new animation, or NULL on error */ CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, float speed, float actionpos, float actionpos2); /** * Add a prop to the model on the given point. */ void AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry); /** * Add a prop to the model on the given point, and treat it as the ammo prop. * The prop will be hidden by default. */ void AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry); /** * Show the ammo prop (if any), and hide any other props on that prop point. */ void ShowAmmoProp(); /** * Hide the ammo prop (if any), and show any other props on that prop point. */ void HideAmmoProp(); /** * Find the first prop used for ammo, by this model or its own props. */ CModelAbstract* FindFirstAmmoProp(); // return prop list std::vector& GetProps() { return m_Props; } const std::vector& GetProps() const { return m_Props; } // return a clone of this model virtual CModelAbstract* Clone() const; /** * Ensure that both the transformation and the bone * matrices are correct for this model and all its props. */ virtual void ValidatePosition(); /** * Mark this model's position and bone matrices, * and all props' positions as invalid. */ virtual void InvalidatePosition(); private: // delete anything allocated by the model void ReleaseData(); // object flags int m_Flags; // model's material CMaterial m_Material; // pointer to the model's raw 3d data CModelDefPtr m_pModelDef; // object space bounds of model - accounts for bounds of all possible animations // that can play on a model. Not always up-to-date - currently CalcBounds() // updates it when necessary. CBoundingBoxAligned m_ObjectBounds; // animation currently playing on this model, if any CSkeletonAnim* m_Anim; // time (in MS) into the current animation float m_AnimTime; /** * Current state of all bones on this model; null if associated modeldef isn't skeletal. * Props may attach to these bones by means of the SPropPoint::m_BoneIndex field; in this case their * transformation matrix held is relative to the bone transformation (see @ref SPropPoint and * @ref CModel::ValidatePosition). * * @see SPropPoint */ CMatrix3D* m_BoneMatrices; - // inverse matrices for the bind pose's bones; null if not skeletal - CMatrix3D* m_InverseBindBoneMatrices; // list of current props on model std::vector m_Props; /** * The prop point to which the ammo prop is attached, or NULL if none */ const SPropPoint* m_AmmoPropPoint; /** * If m_AmmoPropPoint is not NULL, then the index in m_Props of the ammo prop */ size_t m_AmmoLoadedProp; // manager object which can load animations for us CSkeletonAnimManager& m_SkeletonAnimManager; }; #endif Index: ps/trunk/source/renderer/InstancingModelRenderer.h =================================================================== --- ps/trunk/source/renderer/InstancingModelRenderer.h (revision 11489) +++ ps/trunk/source/renderer/InstancingModelRenderer.h (revision 11490) @@ -1,54 +1,54 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Special ModelVertexRender that only works for non-animated models, * but is very fast for instanced models. */ #ifndef INCLUDED_INSTANCINGMODELRENDERER #define INCLUDED_INSTANCINGMODELRENDERER #include "renderer/ModelVertexRenderer.h" struct InstancingModelRendererInternals; /** * Render non-animated (but potentially moving) models using a ShaderRenderModifier. * This computes and binds per-vertex data; the modifier is responsible * for setting any shader uniforms etc (including the instancing transform). */ class InstancingModelRenderer : public ModelVertexRenderer { public: - InstancingModelRenderer(); + InstancingModelRenderer(bool gpuSkinning); ~InstancingModelRenderer(); // Implementations CModelRData* CreateModelData(const void* key, CModel* model); void UpdateModelData(CModel* model, CModelRData* data, int updateflags); void BeginPass(int streamflags); void EndPass(int streamflags); void PrepareModelDef(const CShaderProgramPtr& shader, int streamflags, const CModelDef& def); void RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model, CModelRData* data); protected: InstancingModelRendererInternals* m; }; #endif // INCLUDED_INSTANCINGMODELRENDERER Index: ps/trunk/source/renderer/Renderer.h =================================================================== --- ps/trunk/source/renderer/Renderer.h (revision 11489) +++ ps/trunk/source/renderer/Renderer.h (revision 11490) @@ -1,454 +1,455 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * higher level interface on top of OpenGL to render basic objects: * terrain, models, sprites, particles etc. */ #ifndef INCLUDED_RENDERER #define INCLUDED_RENDERER #include "graphics/Camera.h" #include "graphics/SColor.h" #include "graphics/ShaderProgram.h" #include "lib/res/handle.h" #include "ps/Singleton.h" #include "scripting/ScriptableObject.h" #include "renderer/Scene.h" // necessary declarations class CPatch; class CMaterial; class CModel; class CLightEnv; class CShaderDefines; class RenderPathVertexShader; class WaterManager; class SkyManager; class CTextureManager; class CShaderManager; class CParticleManager; class CMaterialManager; // rendering modes enum ERenderMode { WIREFRAME, SOLID, EDGED_FACES }; // transparency modes enum ETransparentMode { TRANSPARENT, TRANSPARENT_OPAQUE, TRANSPARENT_BLEND }; // access to sole renderer object #define g_Renderer CRenderer::GetSingleton() struct SScreenRect { GLint x1, y1, x2, y2; }; /////////////////////////////////////////////////////////////////////////////////////////// // CRenderer: base renderer class - primary interface to the rendering engine struct CRendererInternals; class CRenderer : public Singleton, public CJSObject, private SceneCollector { public: // various enumerations and renderer related constants enum { NumAlphaMaps=14 }; enum Option { OPT_NOVBO, OPT_SHADOWS, OPT_FANCYWATER, OPT_SHADOWPCF }; enum RenderPath { // If no rendering path is configured explicitly, the renderer // will choose the path when Open() is called. RP_DEFAULT, // Classic fixed function. RP_FIXED, // Use new ARB/GLSL system RP_SHADER }; // stats class - per frame counts of number of draw calls, poly counts etc struct Stats { // set all stats to zero void Reset() { memset(this, 0, sizeof(*this)); } // number of draw calls per frame - total DrawElements + Begin/End immediate mode loops size_t m_DrawCalls; // number of terrain triangles drawn size_t m_TerrainTris; // number of water triangles drawn size_t m_WaterTris; // number of (non-transparent) model triangles drawn size_t m_ModelTris; // number of overlay triangles drawn size_t m_OverlayTris; // number of splat passes for alphamapping size_t m_BlendSplats; // number of particles size_t m_Particles; }; // renderer options struct Options { bool m_NoVBO; bool m_Shadows; bool m_FancyWater; RenderPath m_RenderPath; bool m_ShadowAlphaFix; bool m_ARBProgramShadow; bool m_ShadowPCF; bool m_PreferGLSL; bool m_ForceAlphaTest; + bool m_GPUSkinning; } m_Options; struct Caps { bool m_VBO; bool m_ARBProgram; bool m_ARBProgramShadow; bool m_VertexShader; bool m_FragmentShader; bool m_Shadows; }; public: // constructor, destructor CRenderer(); ~CRenderer(); // open up the renderer: performs any necessary initialisation bool Open(int width,int height); // resize renderer view void Resize(int width,int height); // set/get boolean renderer option void SetOptionBool(enum Option opt,bool value); bool GetOptionBool(enum Option opt) const; void SetRenderPath(RenderPath rp); RenderPath GetRenderPath() const { return m_Options.m_RenderPath; } static CStr GetRenderPathName(RenderPath rp); static RenderPath GetRenderPathByName(const CStr& name); // return view width int GetWidth() const { return m_Width; } // return view height int GetHeight() const { return m_Height; } // return view aspect ratio float GetAspect() const { return float(m_Width)/float(m_Height); } // signal frame start void BeginFrame(); // signal frame end void EndFrame(); // set color used to clear screen in BeginFrame() void SetClearColor(SColor4ub color); // trigger a reload of shaders (when parameters they depend on have changed) void MakeShadersDirty(); /** * Set up the camera used for rendering the next scene; this includes * setting OpenGL state like viewport, projection and modelview matrices. * * @param viewCamera this camera determines the eye position for rendering * @param cullCamera this camera determines the frustum for culling in the renderer and * for shadow calculations */ void SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera); // set the viewport void SetViewport(const SViewPort &); /** * Render the given scene immediately. * @param scene a Scene object describing what should be rendered. */ void RenderScene(Scene& scene); /** * Return the scene that is currently being rendered. * Only valid when the renderer is in a RenderScene call. */ Scene& GetScene(); /** * Render text overlays on top of the scene. * Assumes the caller has set up the GL environment for orthographic rendering * with texturing and blending. */ void RenderTextOverlays(); // set the current lighting environment; (note: the passed pointer is just copied to a variable within the renderer, // so the lightenv passed must be scoped such that it is not destructed until after the renderer is no longer rendering) void SetLightEnv(CLightEnv* lightenv) { m_LightEnv=lightenv; } // set the mode to render subsequent terrain patches void SetTerrainRenderMode(ERenderMode mode) { m_TerrainRenderMode=mode; } // get the mode to render subsequent terrain patches ERenderMode GetTerrainRenderMode() const { return m_TerrainRenderMode; } // set the mode to render subsequent models void SetModelRenderMode(ERenderMode mode) { m_ModelRenderMode=mode; } // get the mode to render subsequent models ERenderMode GetModelRenderMode() const { return m_ModelRenderMode; } // debugging void SetDisplayTerrainPriorities(bool enabled) { m_DisplayTerrainPriorities = enabled; } // bind a GL texture object to active unit void BindTexture(int unit, unsigned int tex); // load the default set of alphamaps. // return a negative error code if anything along the way fails. // called via delay-load mechanism. int LoadAlphaMaps(); void UnloadAlphaMaps(); // return stats accumulated for current frame Stats& GetStats() { return m_Stats; } // return the current light environment const CLightEnv &GetLightEnv() { return *m_LightEnv; } // return the current view camera const CCamera& GetViewCamera() const { return m_ViewCamera; } // replace the current view camera void SetViewCamera(const CCamera& camera) { m_ViewCamera = camera; } // return the current cull camera const CCamera& GetCullCamera() const { return m_CullCamera; } /** * GetWaterManager: Return the renderer's water manager. * * @return the WaterManager object used by the renderer */ WaterManager* GetWaterManager() { return m_WaterManager; } /** * GetSkyManager: Return the renderer's sky manager. * * @return the SkyManager object used by the renderer */ SkyManager* GetSkyManager() { return m_SkyManager; } CTextureManager& GetTextureManager(); CShaderManager& GetShaderManager(); CParticleManager& GetParticleManager(); CMaterialManager& GetMaterialManager(); /** * GetCapabilities: Return which OpenGL capabilities are available and enabled. * * @return capabilities structure */ const Caps& GetCapabilities() const { return m_Caps; } static void ScriptingInit(); protected: friend struct CRendererInternals; friend class CVertexBuffer; friend class CPatchRData; friend class CDecalRData; friend class FixedFunctionModelRenderer; friend class ModelRenderer; friend class PolygonSortModelRenderer; friend class SortModelRenderer; friend class RenderPathVertexShader; friend class HWLightingModelRenderer; friend class ShaderModelVertexRenderer; friend class InstancingModelRenderer; friend class ShaderInstancingModelRenderer; friend class TerrainRenderer; // scripting // TODO: Perhaps we could have a version of AddLocalProperty for function-driven // properties? Then we could hide these function in the private implementation class. jsval JSI_GetRenderPath(JSContext*); void JSI_SetRenderPath(JSContext* ctx, jsval newval); jsval JSI_GetDepthTextureBits(JSContext*); void JSI_SetDepthTextureBits(JSContext* ctx, jsval newval); jsval JSI_GetShadows(JSContext*); void JSI_SetShadows(JSContext* ctx, jsval newval); jsval JSI_GetShadowAlphaFix(JSContext*); void JSI_SetShadowAlphaFix(JSContext* ctx, jsval newval); jsval JSI_GetShadowPCF(JSContext*); void JSI_SetShadowPCF(JSContext* ctx, jsval newval); jsval JSI_GetPreferGLSL(JSContext*); void JSI_SetPreferGLSL(JSContext* ctx, jsval newval); jsval JSI_GetSky(JSContext*); void JSI_SetSky(JSContext* ctx, jsval newval); //BEGIN: Implementation of SceneCollector void Submit(CPatch* patch); void Submit(SOverlayLine* overlay); void Submit(SOverlayTexturedLine* overlay); void Submit(SOverlaySprite* overlay); void Submit(CModelDecal* decal); void Submit(CParticleEmitter* emitter); void SubmitNonRecursive(CModel* model); //END: Implementation of SceneCollector // render any batched objects void RenderSubmissions(); // patch rendering stuff void RenderPatches(const CShaderDefines& context, const CFrustum* frustum = 0); // model rendering stuff void RenderModels(const CShaderDefines& context, const CFrustum* frustum = 0); void RenderTransparentModels(const CShaderDefines& context, ETransparentMode transparentMode, const CFrustum* frustum = 0); void RenderSilhouettes(const CShaderDefines& context); void RenderParticles(); // shadow rendering stuff void RenderShadowMap(const CShaderDefines& context); // render water reflection and refraction textures SScreenRect RenderReflections(const CShaderDefines& context, const CBoundingBoxAligned& scissor); SScreenRect RenderRefractions(const CShaderDefines& context, const CBoundingBoxAligned& scissor); // debugging void DisplayFrustum(); // enable oblique frustum clipping with the given clip plane void SetObliqueFrustumClipping(const CVector4D& clipPlane); void ReloadShaders(); // hotloading static Status ReloadChangedFileCB(void* param, const VfsPath& path); // RENDERER DATA: /// Private data that is not needed by inline functions CRendererInternals* m; // view width int m_Width; // view height int m_Height; // current terrain rendering mode ERenderMode m_TerrainRenderMode; // current model rendering mode ERenderMode m_ModelRenderMode; /** * m_ViewCamera: determines the eye position for rendering * * @see CGameView::m_ViewCamera */ CCamera m_ViewCamera; /** * m_CullCamera: determines the frustum for culling and shadowmap calculations * * @see CGameView::m_ViewCamera */ CCamera m_CullCamera; // only valid inside a call to RenderScene Scene* m_CurrentScene; // color used to clear screen in BeginFrame float m_ClearColor[4]; // current lighting setup CLightEnv* m_LightEnv; // ogl_tex handle of composite alpha map (all the alpha maps packed into one texture) Handle m_hCompositeAlphaMap; // coordinates of each (untransformed) alpha map within the packed texture struct { float u0,u1,v0,v1; } m_AlphaMapCoords[NumAlphaMaps]; // card capabilities Caps m_Caps; // build card cap bits void EnumCaps(); // per-frame renderer stats Stats m_Stats; /** * m_WaterManager: the WaterManager object used for water textures and settings * (e.g. water color, water height) */ WaterManager* m_WaterManager; /** * m_SkyManager: the SkyManager object used for sky textures and settings */ SkyManager* m_SkyManager; /** * m_DisplayFrustum: Render the cull frustum and other data that may be interesting * to evaluate culling and shadow map calculations * * Can be controlled from JS via renderer.displayFrustum */ bool m_DisplayFrustum; /** * Enable rendering of terrain tile priority text overlay, for debugging. */ bool m_DisplayTerrainPriorities; public: /** * m_ShadowZBias: Z bias used when rendering shadows into a depth texture. * This can be used to control shadowing artifacts. * * Can be accessed via JS as renderer.shadowZBias * ShadowMap uses this for matrix calculation. */ float m_ShadowZBias; /** * m_ShadowMapSize: Size of shadow map, or 0 for default. Typically slow but useful * for high-quality rendering. Changes don't take effect until the shadow map * is regenerated. * * Can be accessed via JS as renderer.shadowMapSize */ int m_ShadowMapSize; /** * m_SkipSubmit: Disable the actual submission of rendering commands to OpenGL. * All state setup is still performed as usual. * * Can be accessed via JS as renderer.skipSubmit */ bool m_SkipSubmit; }; #endif Index: ps/trunk/source/renderer/InstancingModelRenderer.cpp =================================================================== --- ps/trunk/source/renderer/InstancingModelRenderer.cpp (revision 11489) +++ ps/trunk/source/renderer/InstancingModelRenderer.cpp (revision 11490) @@ -1,207 +1,259 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Implementation of InstancingModelRenderer */ #include "precompiled.h" #include "lib/ogl.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "graphics/Color.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/Renderer.h" #include "renderer/RenderModifiers.h" #include "renderer/VertexArray.h" /////////////////////////////////////////////////////////////////////////////////////////////// // InstancingModelRenderer implementation struct IModelDef : public CModelDefRPrivate { /// Static per-CModel vertex array VertexArray m_Array; /// Position, normals and UV are all static VertexArray::Attribute m_Position; VertexArray::Attribute m_Normal; 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 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) { size_t numVertices = mdef->GetNumVertices(); m_Position.type = GL_FLOAT; m_Position.elems = 3; m_Array.AddAttribute(&m_Position); m_Normal.type = GL_FLOAT; m_Normal.elems = 3; m_Array.AddAttribute(&m_Normal); m_UV.type = GL_FLOAT; m_UV.elems = 2; 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.Layout(); VertexArrayIterator Position = m_Position.GetIterator(); VertexArrayIterator Normal = m_Normal.GetIterator(); VertexArrayIterator UVit = m_UV.GetIterator(); ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal); ModelRenderer::BuildUV(mdef, UVit); + if (gpuSkinning) + { + VertexArrayIterator BlendJoints = m_BlendJoints.GetIterator(); + VertexArrayIterator BlendWeights = m_BlendWeights.GetIterator(); + 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.FreeBackingStore(); m_IndexArray.SetNumVertices(mdef->GetNumFaces()*3); m_IndexArray.Layout(); ModelRenderer::BuildIndices(mdef, m_IndexArray.GetIterator()); m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); } struct InstancingModelRendererInternals { + bool gpuSkinning; + /// Previously prepared modeldef IModelDef* imodeldef; /// Index base for imodeldef u8* imodeldefIndexBase; }; // Construction and Destruction -InstancingModelRenderer::InstancingModelRenderer() +InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning) { m = new InstancingModelRendererInternals; + m->gpuSkinning = gpuSkinning; m->imodeldef = 0; } InstancingModelRenderer::~InstancingModelRenderer() { delete m; } // Build modeldef data if necessary - we have no per-CModel data CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* model) { CModelDefPtr mdef = model->GetModelDef(); IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m); - ENSURE(!model->IsSkinned()); + if (m->gpuSkinning) + ENSURE(model->IsSkinned()); + else + ENSURE(!model->IsSkinned()); if (!imodeldef) { - imodeldef = new IModelDef(mdef); + imodeldef = new IModelDef(mdef, m->gpuSkinning); mdef->SetRenderData(m, imodeldef); } return new CModelRData(key); } void InstancingModelRenderer::UpdateModelData(CModel* UNUSED(model), CModelRData* UNUSED(data), int UNUSED(updateflags)) { // We have no per-CModel data } // Setup one rendering pass. void InstancingModelRenderer::BeginPass(int streamflags) { ENSURE(streamflags == (streamflags & (STREAM_POS|STREAM_NORMAL|STREAM_UV0))); } // Cleanup rendering pass. void InstancingModelRenderer::EndPass(int UNUSED(streamflags)) { CVertexBuffer::Unbind(); } // Prepare UV coordinates for this modeldef void InstancingModelRenderer::PrepareModelDef(const CShaderProgramPtr& shader, int streamflags, const CModelDef& def) { m->imodeldef = (IModelDef*)def.GetRenderData(m); ENSURE(m->imodeldef); u8* base = m->imodeldef->m_Array.Bind(); GLsizei stride = (GLsizei)m->imodeldef->m_Array.GetStride(); m->imodeldefIndexBase = m->imodeldef->m_IndexArray.Bind(); if (streamflags & STREAM_POS) shader->VertexPointer(3, GL_FLOAT, stride, base + m->imodeldef->m_Position.offset); if (streamflags & STREAM_NORMAL) shader->NormalPointer(GL_FLOAT, stride, base + m->imodeldef->m_Normal.offset); if (streamflags & STREAM_UV0) 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(); } // 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(); + 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 size_t numFaces = mdldef->GetNumFaces(); if (!g_Renderer.m_SkipSubmit) { // Draw with DrawRangeElements where available, since it might be more efficient #if CONFIG2_GLES glDrawElements(GL_TRIANGLES, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); #else pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)mdldef->GetNumVertices()-1, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); #endif } // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_ModelTris += numFaces; } Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 11489) +++ ps/trunk/source/renderer/Renderer.cpp (revision 11490) @@ -1,1911 +1,1937 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * higher level interface on top of OpenGL to render basic objects: * terrain, models, sprites, particles etc. */ #include "precompiled.h" #include #include #include #include #include "Renderer.h" #include "lib/bits.h" // is_pow2 #include "lib/res/graphics/ogl_tex.h" #include "lib/allocators/shared_ptr.h" #include "maths/Matrix3D.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/Filesystem.h" #include "ps/World.h" #include "ps/Loader.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/MaterialManager.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ParticleManager.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" #include "graphics/TextureManager.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/OverlayRenderer.h" #include "renderer/ParticleRenderer.h" #include "renderer/RenderModifiers.h" #include "renderer/ShadowMap.h" #include "renderer/SkyManager.h" #include "renderer/TerrainOverlay.h" #include "renderer/TerrainRenderer.h" #include "renderer/VertexBufferManager.h" #include "renderer/WaterManager.h" /////////////////////////////////////////////////////////////////////////////////// // CRendererStatsTable - Profile display of rendering stats /** * Class CRendererStatsTable: Implementation of AbstractProfileTable to * display the renderer stats in-game. * * Accesses CRenderer::m_Stats by keeping the reference passed to the * constructor. */ class CRendererStatsTable : public AbstractProfileTable { NONCOPYABLE(CRendererStatsTable); public: CRendererStatsTable(const CRenderer::Stats& st); // Implementation of AbstractProfileTable interface CStr GetName(); CStr GetTitle(); size_t GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(size_t row, size_t col); AbstractProfileTable* GetChild(size_t row); private: /// Reference to the renderer singleton's stats const CRenderer::Stats& Stats; /// Column descriptions std::vector columnDescriptions; enum { Row_DrawCalls = 0, Row_TerrainTris, Row_WaterTris, Row_ModelTris, Row_OverlayTris, Row_BlendSplats, Row_Particles, Row_VBReserved, Row_VBAllocated, Row_ShadersLoaded, // Must be last to count number of rows NumberRows }; }; // Construction CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st) : Stats(st) { columnDescriptions.push_back(ProfileColumn("Name", 230)); columnDescriptions.push_back(ProfileColumn("Value", 100)); } // Implementation of AbstractProfileTable interface CStr CRendererStatsTable::GetName() { return "renderer"; } CStr CRendererStatsTable::GetTitle() { return "Renderer statistics"; } size_t CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(size_t row, size_t col) { char buf[256]; switch(row) { case Row_DrawCalls: if (col == 0) return "# draw calls"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls); return buf; case Row_TerrainTris: if (col == 0) return "# terrain tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris); return buf; case Row_WaterTris: if (col == 0) return "# water tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris); return buf; case Row_ModelTris: if (col == 0) return "# model tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris); return buf; case Row_OverlayTris: if (col == 0) return "# overlay tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris); return buf; case Row_BlendSplats: if (col == 0) return "# blend splats"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats); return buf; case Row_Particles: if (col == 0) return "# particles"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles); return buf; case Row_VBReserved: if (col == 0) return "VB bytes reserved"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_VBMan.GetBytesReserved()); return buf; case Row_VBAllocated: if (col == 0) return "VB bytes allocated"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_VBMan.GetBytesAllocated()); return buf; case Row_ShadersLoaded: if (col == 0) return "shader effects loaded"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded()); return buf; default: return "???"; } } AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row)) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation /** * Struct CRendererInternals: Truly hide data that is supposed to be hidden * in this structure so it won't even appear in header files. */ struct CRendererInternals { NONCOPYABLE(CRendererInternals); public: /// true if CRenderer::Open has been called bool IsOpen; /// true if shaders need to be reloaded bool ShadersDirty; /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; /// Shader manager CShaderManager shaderManager; /// Water manager WaterManager waterManager; /// Sky manager SkyManager skyManager; /// Texture manager CTextureManager textureManager; /// Terrain renderer TerrainRenderer terrainRenderer; /// Overlay renderer OverlayRenderer overlayRenderer; /// Particle manager CParticleManager particleManager; /// Particle renderer ParticleRenderer particleRenderer; /// Material manager CMaterialManager materialManager; /// Shadow map ShadowMap shadow; /// Various model renderers struct Models { // NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer, // RenderModifier, etc) is mostly a relic of an older design that implemented // the different materials and rendering modes through extensive subclassing // and hooking objects together in various combinations. // The new design uses the CShaderManager API to abstract away the details // of rendering, and uses a data-driven approach to materials, so there are // now a small number of generic subclasses instead of many specialised subclasses, // but most of the old infrastructure hasn't been refactored out yet and leads to // some unwanted complexity. // Submitted models are split on two axes: // - 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 - // - 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), // so non-skinned models get different ModelVertexRenderers - ModelRendererPtr Normal; - ModelRendererPtr NormalInstancing; - ModelRendererPtr Transp; - ModelRendererPtr TranspInstancing; + ModelRendererPtr NormalSkinned; + ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported + ModelRendererPtr TranspSkinned; + ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported ModelVertexRendererPtr VertexRendererShader; ModelVertexRendererPtr VertexInstancingShader; + ModelVertexRendererPtr VertexGPUSkinningShader; LitRenderModifierPtr ModShader; } Model; CShaderDefines globalContext; CRendererInternals() : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) { } /** * Load the OpenGL projection and modelview matrices and the viewport according * to the given camera. */ void SetOpenGLCamera(const CCamera& camera) { CMatrix3D view; camera.m_Orientation.GetInverse(view); const CMatrix3D& proj = camera.GetProjection(); #if CONFIG2_GLES #warning TODO: fix CRenderer camera handling for GLES (do not use global matrixes) #else glMatrixMode(GL_PROJECTION); glLoadMatrixf(&proj._11); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&view._11); #endif const SViewPort &vp = camera.GetViewPort(); glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } /** * Renders all non-alpha-blended models with the given context. */ void CallModelRenderers(const CShaderDefines& context, int flags) { - CShaderDefines contextInstancing = context; - contextInstancing.Add("USE_INSTANCING", "1"); + CShaderDefines contextSkinned = context; + 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.NormalInstancing) - Model.NormalInstancing->Render(Model.ModShader, contextInstancing, flags); + if (Model.NormalUnskinned != Model.NormalSkinned) + { + CShaderDefines contextUnskinned = context; + contextUnskinned.Add("USE_INSTANCING", "1"); + Model.NormalUnskinned->Render(Model.ModShader, contextUnskinned, flags); + } } /** * Renders all alpha-blended models with the given context. */ void CallTranspModelRenderers(const CShaderDefines& context, int flags) { - CShaderDefines contextInstancing = context; - contextInstancing.Add("USE_INSTANCING", "1"); + CShaderDefines contextSkinned = context; + 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.TranspInstancing) - Model.TranspInstancing->Render(Model.ModShader, contextInstancing, flags); + if (Model.TranspUnskinned != Model.TranspSkinned) + { + CShaderDefines contextUnskinned = context; + contextUnskinned.Add("USE_INSTANCING", "1"); + Model.TranspUnskinned->Render(Model.ModShader, contextUnskinned, flags); + } } /** * Filters all non-alpha-blended models. */ void FilterModels(CModelFilter& filter, int passed, int flags = 0) { - Model.Normal->Filter(filter, passed, flags); - if (Model.NormalInstancing) - Model.NormalInstancing->Filter(filter, passed, flags); + Model.NormalSkinned->Filter(filter, passed, flags); + if (Model.NormalUnskinned != Model.NormalSkinned) + Model.NormalUnskinned->Filter(filter, passed, flags); } /** * Filters all alpha-blended models. */ void FilterTranspModels(CModelFilter& filter, int passed, int flags = 0) { - Model.Transp->Filter(filter, passed, flags); - if (Model.TranspInstancing) - Model.TranspInstancing->Filter(filter, passed, flags); + Model.TranspSkinned->Filter(filter, passed, flags); + if (Model.TranspUnskinned != Model.TranspSkinned) + Model.TranspUnskinned->Filter(filter, passed, flags); } }; /////////////////////////////////////////////////////////////////////////////////// // CRenderer constructor CRenderer::CRenderer() { m = new CRendererInternals; m_WaterManager = &m->waterManager; m_SkyManager = &m->skyManager; g_ProfileViewer.AddRootTable(&m->profileTable); m_Width=0; m_Height=0; m_TerrainRenderMode=SOLID; m_ModelRenderMode=SOLID; m_ClearColor[0]=m_ClearColor[1]=m_ClearColor[2]=m_ClearColor[3]=0; m_DisplayFrustum = false; m_DisplayTerrainPriorities = false; m_SkipSubmit = false; m_Options.m_NoVBO = false; m_Options.m_RenderPath = RP_DEFAULT; m_Options.m_FancyWater = false; m_Options.m_Shadows = false; m_Options.m_ShadowAlphaFix = true; m_Options.m_ARBProgramShadow = true; m_Options.m_ShadowPCF = false; m_Options.m_PreferGLSL = false; m_Options.m_ForceAlphaTest = false; + m_Options.m_GPUSkinning = false; // TODO: be more consistent in use of the config system CFG_GET_USER_VAL("preferglsl", Bool, m_Options.m_PreferGLSL); CFG_GET_USER_VAL("forcealphatest", Bool, m_Options.m_ForceAlphaTest); + CFG_GET_USER_VAL("gpuskinning", Bool, m_Options.m_GPUSkinning); #if CONFIG2_GLES // Override config option since GLES only supports GLSL m_Options.m_PreferGLSL = true; #endif m_ShadowZBias = 0.02f; m_ShadowMapSize = 0; m_LightEnv = NULL; m_CurrentScene = NULL; m_hCompositeAlphaMap = 0; m_Stats.Reset(); AddLocalProperty(L"fancyWater", &m_Options.m_FancyWater, false); AddLocalProperty(L"horizonHeight", &m->skyManager.m_HorizonHeight, false); AddLocalProperty(L"waterMurkiness", &m->waterManager.m_Murkiness, false); AddLocalProperty(L"waterReflTintStrength", &m->waterManager.m_ReflectionTintStrength, false); AddLocalProperty(L"waterRepeatPeriod", &m->waterManager.m_RepeatPeriod, false); AddLocalProperty(L"waterShininess", &m->waterManager.m_Shininess, false); AddLocalProperty(L"waterSpecularStrength", &m->waterManager.m_SpecularStrength, false); AddLocalProperty(L"waterWaviness", &m->waterManager.m_Waviness, false); RegisterFileReloadFunc(ReloadChangedFileCB, this); } /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::~CRenderer() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); // we no longer UnloadAlphaMaps / UnloadWaterTextures here - // that is the responsibility of the module that asked for // them to be loaded (i.e. CGameView). delete m; } /////////////////////////////////////////////////////////////////////////////////// // EnumCaps: build card cap bits void CRenderer::EnumCaps() { // assume support for nothing m_Caps.m_VBO = false; m_Caps.m_ARBProgram = false; m_Caps.m_ARBProgramShadow = false; m_Caps.m_VertexShader = false; m_Caps.m_FragmentShader = false; m_Caps.m_Shadows = false; // now start querying extensions if (!m_Options.m_NoVBO) { if (ogl_HaveExtension("GL_ARB_vertex_buffer_object")) { m_Caps.m_VBO=true; } } if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL)) { m_Caps.m_ARBProgram = true; if (ogl_HaveExtension("GL_ARB_fragment_program_shadow")) m_Caps.m_ARBProgramShadow = true; } if (0 == ogl_HaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", NULL)) { if (ogl_HaveExtension("GL_ARB_vertex_shader")) m_Caps.m_VertexShader = true; if (ogl_HaveExtension("GL_ARB_fragment_shader")) m_Caps.m_FragmentShader = true; } #if CONFIG2_GLES m_Caps.m_Shadows = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", "GL_EXT_framebuffer_object", NULL)) { if (ogl_max_tex_units >= 4) m_Caps.m_Shadows = true; } #endif } void CRenderer::ReloadShaders() { ENSURE(m->IsOpen); m->globalContext = CShaderDefines(); if (m_Caps.m_Shadows && m_Options.m_Shadows) { m->globalContext.Add("USE_SHADOW", "1"); if (m_Caps.m_ARBProgramShadow && m_Options.m_ARBProgramShadow) m->globalContext.Add("USE_FP_SHADOW", "1"); if (m_Options.m_ShadowPCF) m->globalContext.Add("USE_SHADOW_PCF", "1"); #if !CONFIG2_GLES m->globalContext.Add("USE_SHADOW_SAMPLER", "1"); #endif } if (m_LightEnv) m->globalContext.Add(("LIGHTING_MODEL_" + m_LightEnv->GetLightingModel()).c_str(), "1"); if (GetRenderPath() == RP_SHADER && m_Caps.m_ARBProgram) m->globalContext.Add("SYS_HAS_ARB", "1"); if (GetRenderPath() == RP_SHADER && m_Caps.m_VertexShader && m_Caps.m_FragmentShader) m->globalContext.Add("SYS_HAS_GLSL", "1"); if (m_Options.m_PreferGLSL) m->globalContext.Add("SYS_PREFER_GLSL", "1"); m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier()); bool cpuLighting = (GetRenderPath() == RP_FIXED); 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)); - m->Model.Transp = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); + if (GetRenderPath() == RP_SHADER && m_Options.m_GPUSkinning) // TODO: should check caps and GLSL etc too + { + 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 if (GetRenderPath() == RP_SHADER) { - m->Model.NormalInstancing = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); - m->Model.TranspInstancing = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); + m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); + m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); } else { - m->Model.NormalInstancing.reset(); - m->Model.TranspInstancing.reset(); + m->Model.NormalUnskinned = m->Model.NormalSkinned; + m->Model.TranspUnskinned = m->Model.TranspSkinned; } m->ShadersDirty = false; } bool CRenderer::Open(int width, int height) { m->IsOpen = true; // Must query card capabilities before creating renderers that depend // on card capabilities. EnumCaps(); // Dimensions m_Width = width; m_Height = height; // set packing parameters glPixelStorei(GL_PACK_ALIGNMENT,1); glPixelStorei(GL_UNPACK_ALIGNMENT,1); // setup default state glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); GLint bits; glGetIntegerv(GL_DEPTH_BITS,&bits); LOGMESSAGE(L"CRenderer::Open: depth bits %d",bits); glGetIntegerv(GL_STENCIL_BITS,&bits); LOGMESSAGE(L"CRenderer::Open: stencil bits %d",bits); glGetIntegerv(GL_ALPHA_BITS,&bits); LOGMESSAGE(L"CRenderer::Open: alpha bits %d",bits); // Validate the currently selected render path SetRenderPath(m_Options.m_RenderPath); return true; } // resize renderer view void CRenderer::Resize(int width,int height) { // need to recreate the shadow map object to resize the shadow texture m->shadow.RecreateTexture(); m_Width = width; m_Height = height; } ////////////////////////////////////////////////////////////////////////////////////////// // SetOptionBool: set boolean renderer option void CRenderer::SetOptionBool(enum Option opt,bool value) { switch (opt) { case OPT_NOVBO: m_Options.m_NoVBO=value; break; case OPT_SHADOWS: m_Options.m_Shadows=value; MakeShadersDirty(); break; case OPT_FANCYWATER: m_Options.m_FancyWater=value; break; case OPT_SHADOWPCF: m_Options.m_ShadowPCF=value; MakeShadersDirty(); break; default: debug_warn(L"CRenderer::SetOptionBool: unknown option"); break; } } ////////////////////////////////////////////////////////////////////////////////////////// // GetOptionBool: get boolean renderer option bool CRenderer::GetOptionBool(enum Option opt) const { switch (opt) { case OPT_NOVBO: return m_Options.m_NoVBO; case OPT_SHADOWS: return m_Options.m_Shadows; case OPT_FANCYWATER: return m_Options.m_FancyWater; case OPT_SHADOWPCF: return m_Options.m_ShadowPCF; default: debug_warn(L"CRenderer::GetOptionBool: unknown option"); break; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // SetRenderPath: Select the preferred render path. // This may only be called before Open(), because the layout of vertex arrays and other // data may depend on the chosen render path. void CRenderer::SetRenderPath(RenderPath rp) { if (!m->IsOpen) { // Delay until Open() is called. m_Options.m_RenderPath = rp; return; } // Renderer has been opened, so validate the selected renderpath if (rp == RP_DEFAULT) { if (m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL)) rp = RP_SHADER; else rp = RP_FIXED; } if (rp == RP_SHADER) { if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL))) { LOGWARNING(L"Falling back to fixed function\n"); rp = RP_FIXED; } } m_Options.m_RenderPath = rp; MakeShadersDirty(); // We might need to regenerate some render data after changing path if (g_Game) g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_COLOR); } CStr CRenderer::GetRenderPathName(RenderPath rp) { switch(rp) { case RP_DEFAULT: return "default"; case RP_FIXED: return "fixed"; case RP_SHADER: return "shader"; default: return "(invalid)"; } } CRenderer::RenderPath CRenderer::GetRenderPathByName(const CStr& name) { if (name == "fixed") return RP_FIXED; if (name == "shader") return RP_SHADER; if (name == "default") return RP_DEFAULT; LOGWARNING(L"Unknown render path name '%hs', assuming 'default'", name.c_str()); return RP_DEFAULT; } ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() { PROFILE("begin frame"); // zero out all the per-frame stats m_Stats.Reset(); // choose model renderers for this frame if (m->ShadersDirty) ReloadShaders(); m->Model.ModShader->SetShadowMap(&m->shadow); m->Model.ModShader->SetLightEnv(m_LightEnv); } ////////////////////////////////////////////////////////////////////////////////////////// // SetClearColor: set color used to clear screen in BeginFrame() void CRenderer::SetClearColor(SColor4ub color) { m_ClearColor[0] = float(color.R) / 255.0f; m_ClearColor[1] = float(color.G) / 255.0f; m_ClearColor[2] = float(color.B) / 255.0f; m_ClearColor[3] = float(color.A) / 255.0f; } void CRenderer::RenderShadowMap(const CShaderDefines& context) { PROFILE3_GPU("shadow map"); m->shadow.BeginRender(); { PROFILE("render patches"); m->terrainRenderer.RenderPatches(); } CShaderDefines contextCast = context; contextCast.Add("MODE_SHADOWCAST", "1"); { PROFILE("render models"); m->CallModelRenderers(contextCast, MODELFLAG_CASTSHADOWS); } { PROFILE("render transparent models"); // disable face-culling for two-sided models glDisable(GL_CULL_FACE); m->CallTranspModelRenderers(contextCast, MODELFLAG_CASTSHADOWS); glEnable(GL_CULL_FACE); } m->shadow.EndRender(); m->SetOpenGLCamera(m_ViewCamera); } void CRenderer::RenderPatches(const CShaderDefines& context, const CFrustum* frustum) { PROFILE3_GPU("patches"); bool filtered = false; if (frustum) { if (!m->terrainRenderer.CullPatches(frustum)) return; filtered = true; } #if CONFIG2_GLES #warning TODO: implement wireface/edged rendering mode GLES #else // switch on wireframe if we need it if (m_TerrainRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // render all the patches, including blend pass if (GetRenderPath() == RP_SHADER) m->terrainRenderer.RenderTerrainShader(context, (m_Caps.m_Shadows && m_Options.m_Shadows) ? &m->shadow : 0, filtered); else m->terrainRenderer.RenderTerrain(filtered); #if !CONFIG2_GLES if (m_TerrainRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_TerrainRenderMode == EDGED_FACES) { // edged faces: need to make a second pass over the data: // first switch on wireframe glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); // setup some renderstate .. pglActiveTextureARB(GL_TEXTURE0); glDisable(GL_TEXTURE_2D); glColor3f(0.5f, 0.5f, 1.0f); glLineWidth(2.0f); // render tiles edges m->terrainRenderer.RenderPatches(filtered); // set color for outline glColor3f(0, 0, 1); glLineWidth(4.0f); // render outline of each patch m->terrainRenderer.RenderOutlines(filtered); // .. and restore the renderstates glLineWidth(1.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } class CModelCuller : public CModelFilter { public: CModelCuller(const CFrustum& frustum) : m_Frustum(frustum) { } bool Filter(CModel *model) { return m_Frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetWorldBoundsRec()); } private: const CFrustum& m_Frustum; }; void CRenderer::RenderModels(const CShaderDefines& context, const CFrustum* frustum) { PROFILE3_GPU("models"); int flags = 0; if (frustum) { flags = MODELFLAG_FILTERED; CModelCuller culler(*frustum); m->FilterModels(culler, flags); } #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif m->CallModelRenderers(context, flags); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = context; contextWireframe.Add("MODE_WIREFRAME", "1"); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallModelRenderers(contextWireframe, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } void CRenderer::RenderTransparentModels(const CShaderDefines& context, ETransparentMode transparentMode, const CFrustum* frustum) { PROFILE3_GPU("transparent models"); int flags = 0; if (frustum) { flags = MODELFLAG_FILTERED; CModelCuller culler(*frustum); m->FilterTranspModels(culler, flags); } #if !CONFIG2_GLES // switch on wireframe if we need it if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // disable face culling for two-sided models in sub-renders if (flags) glDisable(GL_CULL_FACE); CShaderDefines contextOpaque = context; contextOpaque.Add("ALPHABLEND_PASS_OPAQUE", "1"); CShaderDefines contextBlend = context; contextBlend.Add("ALPHABLEND_PASS_BLEND", "1"); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE) m->CallTranspModelRenderers(contextOpaque, flags); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND) m->CallTranspModelRenderers(contextBlend, flags); if (flags) glEnable(GL_CULL_FACE); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = contextOpaque; contextWireframe.Add("MODE_WIREFRAME", "1"); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallTranspModelRenderers(contextWireframe, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) // Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html // - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test) void CRenderer::SetObliqueFrustumClipping(const CVector4D& worldPlane) { // First, we'll convert the given clip plane to camera space, then we'll // Get the view matrix and normal matrix (top 3x3 part of view matrix) CMatrix3D normalMatrix = m_ViewCamera.m_Orientation.GetTranspose(); CVector4D camPlane = normalMatrix.Transform(worldPlane); CMatrix3D matrix = m_ViewCamera.GetProjection(); // Calculate the clip-space corner point opposite the clipping plane // as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and // transform it into camera space by multiplying it // by the inverse of the projection matrix CVector4D q; q.X = (sgn(camPlane.X) - matrix[8]/matrix[11]) / matrix[0]; q.Y = (sgn(camPlane.Y) - matrix[9]/matrix[11]) / matrix[5]; q.Z = 1.0f/matrix[11]; q.W = (1.0f - matrix[10]/matrix[11]) / matrix[14]; // Calculate the scaled plane vector CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q)); // Replace the third row of the projection matrix matrix[2] = c.X; matrix[6] = c.Y; matrix[10] = c.Z - matrix[11]; matrix[14] = c.W; // Load it back into the camera m_ViewCamera.SetProjection(matrix); m->SetOpenGLCamera(m_ViewCamera); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderReflections: render the water reflections to the reflection texture SScreenRect CRenderer::RenderReflections(const CShaderDefines& context, const CBoundingBoxAligned& scissor) { PROFILE3_GPU("water reflections"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; // Temporarily change the camera to one that is reflected. // Also, for texturing purposes, make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy reflections of slightly off-screen objects. m_ViewCamera.m_Orientation.Scale(1, -1, 1); m_ViewCamera.m_Orientation.Translate(0, 2*wm.m_WaterHeight, 0); m_ViewCamera.UpdateFrustum(scissor); m_ViewCamera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight)); SViewPort vp; vp.m_Height = wm.m_ReflectionTextureSize; vp.m_Width = wm.m_ReflectionTextureSize; vp.m_X = 0; vp.m_Y = 0; m_ViewCamera.SetViewPort(vp); m_ViewCamera.SetProjection(normalCamera.GetNearPlane(), normalCamera.GetFarPlane(), normalCamera.GetFOV()*1.05f); // Slightly higher than view FOV CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; m->SetOpenGLCamera(m_ViewCamera); CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight); SetObliqueFrustumClipping(camPlane); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection(); SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vp.m_Width); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vp.m_Height); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vp.m_Width); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vp.m_Height); if (screenScissor.x1 < screenScissor.x2 && screenScissor.y1 < screenScissor.y2) { glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glFrontFace(GL_CW); // Render sky, terrain and models m->skyManager.RenderSky(); ogl_WarnIfError(); RenderPatches(context, &m_ViewCamera.GetFrustum()); ogl_WarnIfError(); RenderModels(context, &m_ViewCamera.GetFrustum()); ogl_WarnIfError(); RenderTransparentModels(context, TRANSPARENT_BLEND, &m_ViewCamera.GetFrustum()); ogl_WarnIfError(); glFrontFace(GL_CCW); glDisable(GL_SCISSOR_TEST); // Copy the image to a texture pglActiveTextureARB(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, wm.m_ReflectionTexture); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, screenScissor.x1, screenScissor.y1, screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); } // Reset old camera m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); return screenScissor; } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderRefractions: render the water refractions to the refraction texture SScreenRect CRenderer::RenderRefractions(const CShaderDefines& context, const CBoundingBoxAligned &scissor) { PROFILE3_GPU("water refractions"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; // Temporarily change the camera to make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy refractions of slightly off-screen objects. m_ViewCamera.UpdateFrustum(scissor); m_ViewCamera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight)); SViewPort vp; vp.m_Height = wm.m_RefractionTextureSize; vp.m_Width = wm.m_RefractionTextureSize; vp.m_X = 0; vp.m_Y = 0; m_ViewCamera.SetViewPort(vp); m_ViewCamera.SetProjection(normalCamera.GetNearPlane(), normalCamera.GetFarPlane(), normalCamera.GetFOV()*1.05f); // Slightly higher than view FOV CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; m->SetOpenGLCamera(m_ViewCamera); CVector4D camPlane(0, -1, 0, wm.m_WaterHeight); SetObliqueFrustumClipping(camPlane); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection(); SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vp.m_Width); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vp.m_Height); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vp.m_Width); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vp.m_Height); if (screenScissor.x1 < screenScissor.x2 && screenScissor.y1 < screenScissor.y2) { glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // a neutral gray to blend in with shores glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Render terrain and models RenderPatches(context, &m_ViewCamera.GetFrustum()); ogl_WarnIfError(); RenderModels(context, &m_ViewCamera.GetFrustum()); ogl_WarnIfError(); RenderTransparentModels(context, TRANSPARENT_BLEND, &m_ViewCamera.GetFrustum()); ogl_WarnIfError(); glDisable(GL_SCISSOR_TEST); // Copy the image to a texture pglActiveTextureARB(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, wm.m_RefractionTexture); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, screenScissor.x1, screenScissor.y1, screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); } // Reset old camera m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); return screenScissor; } void CRenderer::RenderSilhouettes(const CShaderDefines& context) { PROFILE3_GPU("silhouettes"); CShaderDefines contextOccluder = context; contextOccluder.Add("MODE_SILHOUETTEOCCLUDER", "1"); CShaderDefines contextDisplay = context; contextDisplay.Add("MODE_SILHOUETTEDISPLAY", "1"); // Render silhouettes of units hidden behind terrain or occluders. // To avoid breaking the standard rendering of alpha-blended objects, this // has to be done in a separate pass. // First we render all occluders into depth, then render all units with // inverted depth test so any behind an occluder will get drawn in a constant // colour. float silhouetteAlpha = 0.75f; // Silhouette blending requires an almost-universally-supported extension; // fall back to non-blended if unavailable if (!ogl_HaveExtension("GL_EXT_blend_color")) silhouetteAlpha = 1.f; glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glColorMask(0, 0, 0, 0); // Render occluders: { PROFILE("render patches"); // To prevent units displaying silhouettes when parts of their model // protrude into the ground, only occlude with the back faces of the // terrain (so silhouettes will still display when behind hills) glCullFace(GL_FRONT); m->terrainRenderer.RenderPatches(); glCullFace(GL_BACK); } { PROFILE("render model occluders"); m->CallModelRenderers(contextOccluder, MODELFLAG_SILHOUETTE_OCCLUDER); } { PROFILE("render transparent occluders"); m->CallTranspModelRenderers(contextOccluder, MODELFLAG_SILHOUETTE_OCCLUDER); } glDepthFunc(GL_GEQUAL); glColorMask(1, 1, 1, 1); // Render more efficiently if alpha == 1 if (silhouetteAlpha == 1.f) { // Ideally we'd render objects back-to-front so nearer silhouettes would // appear on top, but sorting has non-zero cost. So we'll keep the depth // write enabled, to do the opposite - far objects will consistently appear // on top. glDepthMask(0); } else { // Since we can't sort, we'll use the stencil buffer to ensure we only draw // a pixel once (using the colour of whatever model happens to be drawn first). glEnable(GL_BLEND); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); pglBlendColorEXT(0, 0, 0, silhouetteAlpha); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NOTEQUAL, 1, (GLuint)-1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); } // TODO: For performance, we probably ought to do a quick raycasting check // to see which units are likely blocked by occluders and not bother // rendering any of the others { PROFILE("render models"); m->CallModelRenderers(contextDisplay, MODELFLAG_SILHOUETTE_DISPLAY); // (This won't render transparent objects with SILHOUETTE_DISPLAY - will // we have any units that need that?) } // Restore state glDepthFunc(GL_LEQUAL); if (silhouetteAlpha == 1.f) { glDepthMask(1); } else { glDisable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); pglBlendColorEXT(0, 0, 0, 0); glDisable(GL_STENCIL_TEST); } } void CRenderer::RenderParticles() { // Only supported in shader modes if (GetRenderPath() != RP_SHADER) return; PROFILE3_GPU("particles"); m->particleRenderer.RenderParticles(); #if !CONFIG2_GLES if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); glColor3f(0.0f, 0.5f, 0.0f); m->particleRenderer.RenderParticles(true); CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect("gui_solid"); shaderTech->BeginPass(); CShaderProgramPtr shader = shaderTech->GetShader(); shader->Uniform("color", 0.0f, 1.0f, 0.0f, 1.0f); shader->Uniform("transform", m_ViewCamera.GetViewProjection()); m->particleRenderer.RenderBounds(shader); shaderTech->EndPass(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderSubmissions: force rendering of any batched objects void CRenderer::RenderSubmissions() { PROFILE3("render submissions"); CShaderDefines context = m->globalContext; ogl_WarnIfError(); // Set the camera m->SetOpenGLCamera(m_ViewCamera); // Prepare model renderers { PROFILE3("prepare models"); - m->Model.Normal->PrepareModels(); - m->Model.Transp->PrepareModels(); - if (m->Model.NormalInstancing) - m->Model.NormalInstancing->PrepareModels(); - if (m->Model.TranspInstancing) - m->Model.TranspInstancing->PrepareModels(); + m->Model.NormalSkinned->PrepareModels(); + m->Model.TranspSkinned->PrepareModels(); + if (m->Model.NormalUnskinned != m->Model.NormalSkinned) + m->Model.NormalUnskinned->PrepareModels(); + if (m->Model.TranspUnskinned != m->Model.TranspSkinned) + m->Model.TranspUnskinned->PrepareModels(); } m->terrainRenderer.PrepareForRendering(); m->overlayRenderer.PrepareForRendering(); m->particleRenderer.PrepareForRendering(context); if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) { RenderShadowMap(context); } { PROFILE3_GPU("clear buffers"); glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } ogl_WarnIfError(); CBoundingBoxAligned waterScissor; if (m_WaterManager->m_RenderWater) { waterScissor = m->terrainRenderer.ScissorWater(m_ViewCamera.GetViewProjection()); if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { SScreenRect reflectionScissor = RenderReflections(context, waterScissor); SScreenRect refractionScissor = RenderRefractions(context, waterScissor); PROFILE3_GPU("water scissor"); SScreenRect dirty; dirty.x1 = std::min(reflectionScissor.x1, refractionScissor.x1); dirty.y1 = std::min(reflectionScissor.y1, refractionScissor.y1); dirty.x2 = std::max(reflectionScissor.x2, refractionScissor.x2); dirty.y2 = std::max(reflectionScissor.y2, refractionScissor.y2); if (dirty.x1 < dirty.x2 && dirty.y1 < dirty.y2) { glEnable(GL_SCISSOR_TEST); glScissor(dirty.x1, dirty.y1, dirty.x2 - dirty.x1, dirty.y2 - dirty.y1); glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); } } } // render submitted patches and models RenderPatches(context); ogl_WarnIfError(); // render debug-related terrain overlays TerrainOverlay::RenderOverlays(); ogl_WarnIfError(); // render other debug-related overlays before water (so they can be seen when underwater) m->overlayRenderer.RenderOverlaysBeforeWater(); ogl_WarnIfError(); RenderModels(context); ogl_WarnIfError(); // render water if (m_WaterManager->m_RenderWater && g_Game && waterScissor.GetVolume() > 0) { // render transparent stuff, but only the solid parts that can occlude block water RenderTransparentModels(context, TRANSPARENT_OPAQUE); ogl_WarnIfError(); m->terrainRenderer.RenderWater(); ogl_WarnIfError(); // render transparent stuff again, but only the blended parts that overlap water RenderTransparentModels(context, TRANSPARENT_BLEND); ogl_WarnIfError(); } else { // render transparent stuff, so it can overlap models/terrain RenderTransparentModels(context, TRANSPARENT); ogl_WarnIfError(); } // render some other overlays after water (so they can be displayed on top of water) m->overlayRenderer.RenderOverlaysAfterWater(); ogl_WarnIfError(); // particles are transparent so render after water RenderParticles(); ogl_WarnIfError(); RenderSilhouettes(context); #if !CONFIG2_GLES // Clean up texture blend mode so particles and other things render OK // (really this should be cleaned up by whoever set it) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); #endif // render debug lines if (m_DisplayFrustum) { DisplayFrustum(); m->shadow.RenderDebugBounds(); m->shadow.RenderDebugTexture(); ogl_WarnIfError(); } // render overlays that should appear on top of all other objects m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end void CRenderer::EndFrame() { PROFILE3("end frame"); // empty lists m->terrainRenderer.EndFrame(); m->overlayRenderer.EndFrame(); m->particleRenderer.EndFrame(); // Finish model renderers - m->Model.Normal->EndFrame(); - m->Model.Transp->EndFrame(); - if (m->Model.NormalInstancing) - m->Model.NormalInstancing->EndFrame(); - if (m->Model.TranspInstancing) - m->Model.TranspInstancing->EndFrame(); + m->Model.NormalSkinned->EndFrame(); + m->Model.TranspSkinned->EndFrame(); + if (m->Model.NormalUnskinned != m->Model.NormalSkinned) + m->Model.NormalUnskinned->EndFrame(); + if (m->Model.TranspUnskinned != m->Model.TranspSkinned) + m->Model.TranspUnskinned->EndFrame(); ogl_tex_bind(0, 0); { PROFILE3("error check"); if (glGetError()) { ONCE(LOGERROR(L"CRenderer::EndFrame: GL errors occurred")); } } } /////////////////////////////////////////////////////////////////////////////////////////////////// // DisplayFrustum: debug displays // - white: cull camera frustum // - red: bounds of shadow casting objects void CRenderer::DisplayFrustum() { #if CONFIG2_GLES #warning TODO: implement CRenderer::DisplayFrustum for GLES #else glDepthMask(0); glDisable(GL_CULL_FACE); glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4ub(255,255,255,64); m_CullCamera.Render(2); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3ub(255,255,255); m_CullCamera.Render(2); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_CULL_FACE); glDepthMask(1); #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // Text overlay rendering void CRenderer::RenderTextOverlays() { PROFILE3_GPU("text overlays"); if (m_DisplayTerrainPriorities) m->terrainRenderer.RenderPriorities(); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetSceneCamera: setup projection and transform of camera and adjust viewport to current view // The camera always represents the actual camera used to render a scene, not any virtual camera // used for shadow rendering or reflections. void CRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera) { m_ViewCamera = viewCamera; m_CullCamera = cullCamera; if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) m->shadow.SetupFrame(m_CullCamera, m_LightEnv->GetSunDir()); } void CRenderer::SetViewport(const SViewPort &vp) { glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } void CRenderer::Submit(CPatch* patch) { m->terrainRenderer.Submit(patch); } void CRenderer::Submit(SOverlayLine* overlay) { m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayTexturedLine* overlay) { m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySprite* overlay) { m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(CModelDecal* decal) { m->terrainRenderer.Submit(decal); } void CRenderer::Submit(CParticleEmitter* emitter) { m->particleRenderer.Submit(emitter); } void CRenderer::SubmitNonRecursive(CModel* model) { if (model->GetFlags() & MODELFLAG_CASTSHADOWS) { m->shadow.AddShadowedBound(model->GetWorldBounds()); } // Tricky: The call to GetWorldBounds() above can invalidate the position model->ValidatePosition(); - bool canUseInstancing = false; - - if (model->GetModelDef()->GetNumBones() == 0) - canUseInstancing = true; + bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0); if (model->GetMaterial().UsesAlphaBlending()) { - if (canUseInstancing && m->Model.TranspInstancing) - m->Model.TranspInstancing->Submit(model); + if (requiresSkinning) + m->Model.TranspSkinned->Submit(model); else - m->Model.Transp->Submit(model); + m->Model.TranspUnskinned->Submit(model); } else { - if (canUseInstancing && m->Model.NormalInstancing) - m->Model.NormalInstancing->Submit(model); + if (requiresSkinning) + m->Model.NormalSkinned->Submit(model); else - m->Model.Normal->Submit(model); + m->Model.NormalUnskinned->Submit(model); } } /////////////////////////////////////////////////////////// // Render the given scene void CRenderer::RenderScene(Scene& scene) { m_CurrentScene = &scene; CFrustum frustum = m_CullCamera.GetFrustum(); scene.EnumerateObjects(frustum, this); m->particleManager.RenderSubmit(*this, frustum); ogl_WarnIfError(); RenderSubmissions(); m_CurrentScene = NULL; } Scene& CRenderer::GetScene() { ENSURE(m_CurrentScene); return *m_CurrentScene; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BindTexture: bind a GL texture object to current active unit void CRenderer::BindTexture(int unit, GLuint tex) { pglActiveTextureARB(GL_TEXTURE0+unit); glBindTexture(GL_TEXTURE_2D, tex); #if !CONFIG2_GLES if (tex) { glEnable(GL_TEXTURE_2D); } else { glDisable(GL_TEXTURE_2D); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture int CRenderer::LoadAlphaMaps() { const wchar_t* const key = L"(alpha map composite)"; Handle ht = ogl_tex_find(key); // alpha map texture had already been created and is still in memory: // reuse it, do not load again. if(ht > 0) { m_hCompositeAlphaMap = ht; return 0; } // // load all textures and store Handle in array // Handle textures[NumAlphaMaps] = {0}; VfsPath path(L"art/textures/terrain/alphamaps/standard"); const wchar_t* fnames[NumAlphaMaps] = { L"blendcircle.png", L"blendlshape.png", L"blendedge.png", L"blendedgecorner.png", L"blendedgetwocorners.png", L"blendfourcorners.png", L"blendtwooppositecorners.png", L"blendlshapecorner.png", L"blendtwocorners.png", L"blendcorner.png", L"blendtwoedges.png", L"blendthreecorners.png", L"blendushape.png", L"blendbad.png" }; size_t base = 0; // texture width/height (see below) // for convenience, we require all alpha maps to be of the same BPP // (avoids another ogl_tex_get_size call, and doesn't hurt) size_t bpp = 0; for(size_t i=0;i data; AllocateAligned(data, total_w*total_h, maxSectorSize); // for each tile on row for (size_t i = 0; i < NumAlphaMaps; i++) { // get src of copy u8* src = 0; (void)ogl_tex_get_data(textures[i], &src); size_t srcstep = bpp/8; // get destination of copy u8* dst = data.get() + (i*tile_w); // for each row of image for (size_t j = 0; j < base; j++) { // duplicate first pixel *dst++ = *src; *dst++ = *src; // copy a row for (size_t k = 0; k < base; k++) { *dst++ = *src; src += srcstep; } // duplicate last pixel *dst++ = *(src-srcstep); *dst++ = *(src-srcstep); // advance write pointer for next row dst += total_w-tile_w; } m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w); m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w); m_AlphaMapCoords[i].v0 = 0.0f; m_AlphaMapCoords[i].v1 = 1.0f; } for (size_t i = 0; i < NumAlphaMaps; i++) (void)ogl_tex_free(textures[i]); // upload the composite texture Tex t; (void)tex_wrap(total_w, total_h, 8, TEX_GREY, data, 0, &t); m_hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key); (void)ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR); (void)ogl_tex_set_wrap (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); int ret = ogl_tex_upload(m_hCompositeAlphaMap, GL_ALPHA, 0, 0); return ret; } /////////////////////////////////////////////////////////////////////////////////////////////////// // UnloadAlphaMaps: frees the resources allocates by LoadAlphaMaps void CRenderer::UnloadAlphaMaps() { ogl_tex_free(m_hCompositeAlphaMap); m_hCompositeAlphaMap = 0; } Status CRenderer::ReloadChangedFileCB(void* param, const VfsPath& path) { CRenderer* renderer = static_cast(param); // If an alpha map changed, and we already loaded them, then reload them if (boost::algorithm::starts_with(path.string(), L"art/textures/terrain/alphamaps/")) { if (renderer->m_hCompositeAlphaMap) { renderer->UnloadAlphaMaps(); renderer->LoadAlphaMaps(); } } return INFO::OK; } void CRenderer::MakeShadersDirty() { m->ShadersDirty = true; } /////////////////////////////////////////////////////////////////////////////////////////////////// // Scripting Interface jsval CRenderer::JSI_GetRenderPath(JSContext*) { return ToJSVal(GetRenderPathName(m_Options.m_RenderPath)); } void CRenderer::JSI_SetRenderPath(JSContext* ctx, jsval newval) { CStr name; if (!ToPrimitive(ctx, newval, name)) return; SetRenderPath(GetRenderPathByName(name)); } jsval CRenderer::JSI_GetDepthTextureBits(JSContext*) { return ToJSVal(m->shadow.GetDepthTextureBits()); } void CRenderer::JSI_SetDepthTextureBits(JSContext* ctx, jsval newval) { int depthTextureBits; if (!ToPrimitive(ctx, newval, depthTextureBits)) return; m->shadow.SetDepthTextureBits(depthTextureBits); } jsval CRenderer::JSI_GetShadows(JSContext*) { return ToJSVal(m_Options.m_Shadows); } void CRenderer::JSI_SetShadows(JSContext* ctx, jsval newval) { ToPrimitive(ctx, newval, m_Options.m_Shadows); ReloadShaders(); } jsval CRenderer::JSI_GetShadowAlphaFix(JSContext*) { return ToJSVal(m_Options.m_ShadowAlphaFix); } void CRenderer::JSI_SetShadowAlphaFix(JSContext* ctx, jsval newval) { if (!ToPrimitive(ctx, newval, m_Options.m_ShadowAlphaFix)) return; m->shadow.RecreateTexture(); } jsval CRenderer::JSI_GetShadowPCF(JSContext*) { return ToJSVal(m_Options.m_ShadowPCF); } void CRenderer::JSI_SetShadowPCF(JSContext* ctx, jsval newval) { ToPrimitive(ctx, newval, m_Options.m_ShadowPCF); ReloadShaders(); } jsval CRenderer::JSI_GetPreferGLSL(JSContext*) { return ToJSVal(m_Options.m_PreferGLSL); } void CRenderer::JSI_SetPreferGLSL(JSContext* ctx, jsval newval) { ToPrimitive(ctx, newval, m_Options.m_PreferGLSL); ReloadShaders(); } jsval CRenderer::JSI_GetSky(JSContext*) { return ToJSVal(m->skyManager.GetSkySet()); } void CRenderer::JSI_SetSky(JSContext* ctx, jsval newval) { CStrW skySet; if (!ToPrimitive(ctx, newval, skySet)) return; m->skyManager.SetSkySet(skySet); } void CRenderer::ScriptingInit() { AddProperty(L"renderpath", &CRenderer::JSI_GetRenderPath, &CRenderer::JSI_SetRenderPath); AddProperty(L"displayFrustum", &CRenderer::m_DisplayFrustum); AddProperty(L"shadowZBias", &CRenderer::m_ShadowZBias); AddProperty(L"shadowMapSize", &CRenderer::m_ShadowMapSize); AddProperty(L"shadows", &CRenderer::JSI_GetShadows, &CRenderer::JSI_SetShadows); AddProperty(L"depthTextureBits", &CRenderer::JSI_GetDepthTextureBits, &CRenderer::JSI_SetDepthTextureBits); AddProperty(L"shadowAlphaFix", &CRenderer::JSI_GetShadowAlphaFix, &CRenderer::JSI_SetShadowAlphaFix); AddProperty(L"shadowPCF", &CRenderer::JSI_GetShadowPCF, &CRenderer::JSI_SetShadowPCF); AddProperty(L"preferGLSL", &CRenderer::JSI_GetPreferGLSL, &CRenderer::JSI_SetPreferGLSL); AddProperty(L"skipSubmit", &CRenderer::m_SkipSubmit); AddProperty(L"skySet", &CRenderer::JSI_GetSky, &CRenderer::JSI_SetSky); CJSObject::ScriptingInit("Renderer"); } CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } CShaderManager& CRenderer::GetShaderManager() { return m->shaderManager; } CParticleManager& CRenderer::GetParticleManager() { return m->particleManager; } CMaterialManager& CRenderer::GetMaterialManager() { return m->materialManager; } Index: ps/trunk/source/renderer/VertexArray.cpp =================================================================== --- ps/trunk/source/renderer/VertexArray.cpp (revision 11489) +++ ps/trunk/source/renderer/VertexArray.cpp (revision 11490) @@ -1,270 +1,290 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "lib/alignment.h" #include "lib/ogl.h" #include "lib/sysdep/rtl.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "graphics/SColor.h" #include "renderer/VertexArray.h" #include "renderer/VertexBuffer.h" #include "renderer/VertexBufferManager.h" VertexArray::VertexArray(GLenum usage, GLenum target) { m_Usage = usage; m_Target = target; m_NumVertices = 0; m_VB = 0; m_BackingStore = 0; m_Stride = 0; } VertexArray::~VertexArray() { Free(); } // Free all resources on destruction or when a layout parameter changes void VertexArray::Free() { rtl_FreeAligned(m_BackingStore); m_BackingStore = 0; if (m_VB) { g_VBMan.Release(m_VB); m_VB = 0; } } // Set the number of vertices stored in the array void VertexArray::SetNumVertices(size_t num) { if (num == m_NumVertices) return; Free(); m_NumVertices = num; } // Add vertex attributes like Position, Normal, UV void VertexArray::AddAttribute(Attribute* attr) { ENSURE((attr->type == GL_FLOAT || attr->type == GL_UNSIGNED_SHORT || attr->type == GL_UNSIGNED_BYTE) && "Unsupported attribute type"); ENSURE(attr->elems >= 1 && attr->elems <= 4); attr->vertexArray = this; m_Attributes.push_back(attr); Free(); } // Template specialization for GetIterator(). // We can put this into the source file because only a fixed set of types // is supported for type safety. template<> VertexArrayIterator VertexArray::Attribute::GetIterator() const { ENSURE(vertexArray); ENSURE(type == GL_FLOAT); ENSURE(elems >= 3); return vertexArray->MakeIterator(this); } template<> VertexArrayIterator VertexArray::Attribute::GetIterator() const { ENSURE(vertexArray); ENSURE(type == GL_FLOAT); ENSURE(elems >= 4); return vertexArray->MakeIterator(this); } template<> VertexArrayIterator VertexArray::Attribute::GetIterator() const { ENSURE(vertexArray); ENSURE(type == GL_FLOAT); ENSURE(elems >= 2); return vertexArray->MakeIterator(this); } template<> VertexArrayIterator VertexArray::Attribute::GetIterator() const { ENSURE(vertexArray); ENSURE(type == GL_UNSIGNED_BYTE); ENSURE(elems >= 3); return vertexArray->MakeIterator(this); } template<> VertexArrayIterator VertexArray::Attribute::GetIterator() const { ENSURE(vertexArray); ENSURE(type == GL_UNSIGNED_BYTE); ENSURE(elems >= 4); return vertexArray->MakeIterator(this); } template<> VertexArrayIterator VertexArray::Attribute::GetIterator() const { ENSURE(vertexArray); ENSURE(type == GL_UNSIGNED_SHORT); ENSURE(elems >= 1); return vertexArray->MakeIterator(this); } +template<> +VertexArrayIterator VertexArray::Attribute::GetIterator() const +{ + ENSURE(vertexArray); + ENSURE(type == GL_UNSIGNED_BYTE); + ENSURE(elems >= 1); + + return vertexArray->MakeIterator(this); +} + +template<> +VertexArrayIterator VertexArray::Attribute::GetIterator() const +{ + ENSURE(vertexArray); + ENSURE(type == GL_UNSIGNED_BYTE); + ENSURE(elems >= 4); + + return vertexArray->MakeIterator(this); +} + static size_t RoundStride(size_t stride) { if (stride <= 0) return 0; if (stride <= 4) return 4; if (stride <= 8) return 8; if (stride <= 16) return 16; return Align<32>(stride); } // Re-layout by assigning offsets on a first-come first-serve basis, // then round up to a reasonable stride. // Backing store is also created here, VBOs are created on upload. void VertexArray::Layout() { Free(); m_Stride = 0; //debug_printf(L"Layouting VertexArray\n"); for (ssize_t idx = m_Attributes.size()-1; idx >= 0; --idx) { Attribute* attr = m_Attributes[idx]; if (!attr->type || !attr->elems) continue; size_t attrSize = 0; switch(attr->type) { case GL_UNSIGNED_BYTE: attrSize = sizeof(GLubyte); break; case GL_UNSIGNED_SHORT: attrSize = sizeof(GLushort); break; case GL_FLOAT: attrSize = sizeof(GLfloat); break; default: attrSize = 0; debug_warn(L"Bad Attribute::type"); break; } attrSize *= attr->elems; attr->offset = m_Stride; m_Stride += attrSize; if (m_Target == GL_ARRAY_BUFFER) m_Stride = Align<4>(m_Stride); //debug_printf(L"%i: offset: %u\n", idx, attr->offset); } if (m_Target == GL_ARRAY_BUFFER) m_Stride = RoundStride(m_Stride); //debug_printf(L"Stride: %u\n", m_Stride); if (m_Stride) m_BackingStore = (char*)rtl_AllocateAligned(m_Stride * m_NumVertices, 16); } // (Re-)Upload the attributes. // Create the VBO if necessary. void VertexArray::Upload() { ENSURE(m_BackingStore); if (!m_VB) m_VB = g_VBMan.Allocate(m_Stride, m_NumVertices, m_Usage, m_Target); if (!m_VB) // failed to allocate VBO return; m_VB->m_Owner->UpdateChunkVertices(m_VB, m_BackingStore); } // Bind this array, returns the base address for calls to glVertexPointer etc. u8* VertexArray::Bind() { if (!m_VB) return NULL; u8* base = m_VB->m_Owner->Bind(); base += m_VB->m_Index*m_Stride; return base; } // Free the backing store to save some memory void VertexArray::FreeBackingStore() { rtl_FreeAligned(m_BackingStore); m_BackingStore = 0; } VertexIndexArray::VertexIndexArray(GLenum usage) : VertexArray(usage, GL_ELEMENT_ARRAY_BUFFER) { m_Attr.type = GL_UNSIGNED_SHORT; m_Attr.elems = 1; AddAttribute(&m_Attr); } VertexArrayIterator VertexIndexArray::GetIterator() const { return m_Attr.GetIterator(); } Index: ps/trunk/binaries/data/mods/public/shaders/glsl/model_solid.xml =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/model_solid.xml (revision 11489) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/model_solid.xml (revision 11490) @@ -1,11 +1,13 @@ + + Index: ps/trunk/binaries/data/mods/public/shaders/glsl/model_solid_player.xml =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/model_solid_player.xml (revision 11489) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/model_solid_player.xml (revision 11490) @@ -1,11 +1,13 @@ + + Index: ps/trunk/binaries/data/mods/public/shaders/glsl/model_common.xml =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/model_common.xml (revision 11489) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/model_common.xml (revision 11490) @@ -1,15 +1,17 @@ + + Index: ps/trunk/binaries/data/mods/public/shaders/glsl/model_common.vs =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/model_common.vs (revision 11489) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/model_common.vs (revision 11490) @@ -1,48 +1,78 @@ +#if USE_GPU_SKINNING +// Skinning requires GLSL 1.30 for ivec4 vertex attributes +#version 130 +#else #version 120 +#endif uniform mat4 transform; uniform vec3 cameraPos; uniform vec3 sunDir; uniform vec3 sunColor; uniform vec2 losTransform; uniform mat4 shadowTransform; uniform mat4 instancingTransform; varying vec3 v_lighting; varying vec2 v_tex; varying vec4 v_shadow; varying vec2 v_los; #if USE_SPECULAR varying vec3 v_normal; varying vec3 v_half; #endif attribute vec3 a_vertex; attribute vec3 a_normal; 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() { + #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 vec4 position = instancingTransform * vec4(a_vertex, 1.0); vec3 normal = mat3(instancingTransform) * a_normal; #else vec4 position = vec4(a_vertex, 1.0); vec3 normal = a_normal; #endif + #endif gl_Position = transform * position; #if USE_SPECULAR vec3 eyeVec = normalize(cameraPos.xyz - position.xyz); vec3 sunVec = -sunDir; v_half = normalize(sunVec + eyeVec); v_normal = normal; #endif v_lighting = max(0.0, dot(normal, -sunDir)) * sunColor; v_tex = a_uv0; v_shadow = shadowTransform * position; v_los = position.xz * losTransform.x + losTransform.y; } Index: ps/trunk/binaries/data/mods/public/shaders/glsl/model_solid_tex.xml =================================================================== --- ps/trunk/binaries/data/mods/public/shaders/glsl/model_solid_tex.xml (revision 11489) +++ ps/trunk/binaries/data/mods/public/shaders/glsl/model_solid_tex.xml (revision 11490) @@ -1,13 +1,15 @@ + + Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 11489) +++ ps/trunk/binaries/data/config/default.cfg (revision 11490) @@ -1,233 +1,236 @@ ; Global Configuration Settings ; ; ************************************************************** ; * DO NOT EDIT THIS FILE if you want personal customisations: * ; * create a text file called "local.cfg" instead, and copy * ; * the lines from this file that you want to change. * ; * * ; * On Linux / OS X, create: * ; * ~/.config/0ad/config/local.cfg * ; * * ; * On Windows, create: * ; * %appdata%/0ad/config/local.cfg * ; * * ; ************************************************************** ; Enable/disable windowed mode by default. (Use Alt+Enter to toggle in the game.) windowed = false ; Pause the game on window focus loss (Only applicable to single player mode) pauseonfocusloss = true ; Force a particular resolution. (If these are 0, the default is ; to keep the current desktop resolution in fullscreen mode or to ; use 1024x768 in windowed mode.) xres = 0 yres = 0 ; Force a non-standard bit depth (if 0 then use the current desktop bit depth) bpp = 0 ; System settings: fancywater = true shadows = true shadowpcf = true vsync = false nos3tc = false noautomipmap = true novbo = false noframebufferobject = false ; Disable hardware cursors nohwcursor = false ; Linux only: Set the driconf force_s3tc_enable option at startup, ; for compressed texture support force_s3tc_enable = true ; Specify the render path. This can be one of: ; default Automatically select one of the below, depending on system capabilities ; fixed Only use OpenGL fixed function pipeline ; shader Use vertex/fragment shaders for transform and lighting where possible ; Using 'fixed' instead of 'default' may work around some graphics-related problems, ; but will reduce performance and features when a modern graphics card is available. renderpath = default ; Prefer GLSL shaders over ARB shaders (not recommended) preferglsl = false ; Replace alpha-blending with alpha-testing, for performance experiments forcealphatest = false +; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk +gpuskinning = false + ; Opt-in online user reporting system userreport.url = "http://feedback.wildfiregames.com/report/upload/v1/" ; Colour of the sky (in "r g b" format) skycolor = "0 0 0" ; GENERAL PREFERENCES: sound.mastergain = 0.5 ; Camera control settings view.scroll.speed = 120.0 view.rotate.x.speed = 1.2 view.rotate.x.min = 28.0 view.rotate.x.max = 60.0 view.rotate.x.default = 35.0 view.rotate.y.speed = 2.0 view.rotate.y.speed.wheel = 0.45 view.rotate.y.default = 0.0 view.drag.speed = 0.5 view.zoom.speed = 256.0 view.zoom.speed.wheel = 32.0 view.zoom.min = 50.0 view.zoom.max = 200.0 view.zoom.default = 100.0 view.pos.smoothness = 0.1 view.zoom.smoothness = 0.4 view.rotate.x.smoothness = 0.5 view.rotate.y.smoothness = 0.3 view.near = 2.0 ; Near plane distance view.far = 4096.0 ; Far plane distance view.fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide ; HOTKEY MAPPINGS: ; Each one of the specified keys will trigger the action on the left ; for multiple-key combinations, separate keys with '+' and enclose the entire thing ; in doublequotes. ; See keys.txt for the list of key names. ; > SYSTEM SETTINGS hotkey.exit = "Alt+F4", "Ctrl+Break" ; Exit to desktop hotkey.leave = Escape ; End current game or Exit hotkey.pause = Pause ; Pause/unpause game hotkey.screenshot = F2 ; Take PNG screenshot hotkey.bigscreenshot = "Shift+F2" ; Take large BMP screenshot hotkey.togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode hotkey.screenshot.watermark = "K" ; Toggle product/company watermark for official screenshots hotkey.wireframe = "Alt+W" ; Toggle wireframe mode ; > CAMERA SETTINGS hotkey.camera.reset = "H" ; Reset camera rotation to default. hotkey.camera.follow = "F" ; Follow the first unit in the selection hotkey.camera.zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control) hotkey.camera.zoom.out = Minus, NumMinus ; Zoom camera out (continuous control) hotkey.camera.zoom.wheel.in = WheelUp ; Zoom camera in (stepped control) hotkey.camera.zoom.wheel.out = WheelDown ; Zoom camera out (stepped control) hotkey.camera.rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards hotkey.camera.rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards hotkey.camera.rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain hotkey.camera.rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain hotkey.camera.rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control) hotkey.camera.rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control) hotkey.camera.pan = MouseMiddle, ForwardSlash ; Enable scrolling by moving mouse hotkey.camera.left = A, LeftArrow ; Scroll or rotate left hotkey.camera.right = D, RightArrow ; Scroll or rotate right hotkey.camera.up = W, UpArrow ; Scroll or rotate up/forwards hotkey.camera.down = S, DownArrow ; Scroll or rotate down/backwards ; > CONSOLE SETTINGS hotkey.console.toggle = BackQuote, F9 ; Open/close console ; > CLIPBOARD CONTROLS hotkey.copy = "Ctrl+C" ; Copy to clipboard hotkey.paste = "Ctrl+V" ; Paste from clipboard hotkey.cut = "Ctrl+X" ; Cut selected text and copy to the clipboard ; > ENTITY SELECTION hotkey.selection.add = Shift ; Add units to selection hotkey.selection.milonly = Alt ; Add only military units to selection hotkey.selection.remove = Ctrl ; Remove units from selection hotkey.selection.idleworker = Period ; Select next idle worker hotkey.selection.idlewarrior = Comma ; Select next idle warrior hotkey.selection.offscreen = Alt ; Include offscreen units in selection hotkey.selection.group.select.0 = 0 hotkey.selection.group.save.0 = "Ctrl+0" hotkey.selection.group.add.0 = "Shift+0" hotkey.selection.group.select.1 = 1 hotkey.selection.group.save.1 = "Ctrl+1" hotkey.selection.group.add.1 = "Shift+1" hotkey.selection.group.select.2 = 2 hotkey.selection.group.save.2 = "Ctrl+2" hotkey.selection.group.add.2 = "Shift+2" hotkey.selection.group.select.3 = 3 hotkey.selection.group.save.3 = "Ctrl+3" hotkey.selection.group.add.3 = "Shift+3" hotkey.selection.group.select.4 = 4 hotkey.selection.group.save.4 = "Ctrl+4" hotkey.selection.group.add.4 = "Shift+4" hotkey.selection.group.select.5 = 5 hotkey.selection.group.save.5 = "Ctrl+5" hotkey.selection.group.add.5 = "Shift+5" hotkey.selection.group.select.6 = 6 hotkey.selection.group.save.6 = "Ctrl+6" hotkey.selection.group.add.6 = "Shift+6" hotkey.selection.group.select.7 = 7 hotkey.selection.group.save.7 = "Ctrl+7" hotkey.selection.group.add.7 = "Shift+7" hotkey.selection.group.select.8 = 8 hotkey.selection.group.save.8 = "Ctrl+8" hotkey.selection.group.add.8 = "Shift+8" hotkey.selection.group.select.9 = 9 hotkey.selection.group.save.9 = "Ctrl+9" hotkey.selection.group.add.9 = "Shift+9" ; > SESSION CONTROLS hotkey.session.kill = Delete ; Destroy selected units hotkey.session.garrison = Ctrl ; Modifier to garrison when clicking on building hotkey.session.queue = Shift ; Modifier to queue unit orders instead of replacing hotkey.session.batchtrain = Shift ; Modifier to train units in batches hotkey.session.massbarter = Shift ; Modifier to barter bunch of resources hotkey.session.unloadtype = Shift ; Modifier to unload all units of type hotkey.session.deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting hotkey.session.rotate.cw = RightBracket ; Rotate building placement preview clockwise hotkey.session.rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise hotkey.timewarp.fastforward = Space ; If timewarp mode enabled, speed up the game hotkey.timewarp.rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game ; > OVERLAY KEYS hotkey.fps.toggle = "Alt+F" ; Toggle frame counter hotkey.session.devcommands.toggle = "Alt+D" ; Toggle developer commands panel hotkey.session.gui.toggle = "Alt+G" ; Toggle visibility of session GUI hotkey.menu.toggle = "F10" ; Toggle in-game menu hotkey.timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter ; > HOTKEYS ONLY hotkey.chat = Return ; Toggle chat window ; > GUI TEXTBOX HOTKEYS hotkey.text.delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor hotkey.text.delete.right = "Ctrl+Del" ; Delete word to the right of cursor hotkey.text.move.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor hotkey.text.move.right = "Ctrl+RightArrow" ; Move cursor to start of word to the right of cursor ; > PROFILER hotkey.profile.toggle = "F11" ; Enable/disable real-time profiler hotkey.profile.save = "Shift+F11" ; Save current profiler data to logs/profile.txt hotkey.profile2.enable = "F11" ; Enable HTTP/GPU modes for new profiler profiler2.http.autoenable = false ; Enable HTTP server output at startup (default off for security/performance) profiler2.gpu.autoenable = false ; Enable GPU timing at startup (default off for performance/compatibility) profiler2.gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available profiler2.gpu.ext.enable = true ; Allow GL_EXT_timer_query timing mode when available profiler2.gpu.intel.enable = true ; Allow GL_INTEL_performance_queries timing mode when available hotkey.attackmove = Super ; > QUICKSAVE hotkey.quicksave = "Shift+F5" hotkey.quickload = "Shift+F8" ; EXPERIMENTAL: joystick/gamepad settings joystick.enable = false joystick.deadzone = 8192 joystick.camera.pan.x = 0 joystick.camera.pan.y = 1 joystick.camera.rotate.x = 3 joystick.camera.rotate.y = 2 joystick.camera.zoom.in = 5 joystick.camera.zoom.out = 4