GL: Render meshes out of a VBO

While we're still not doing skinning on the GPU, copying the vertex data
to a VBO immediately after updating the animation allows us to re-use
that data for unanimated meshes. It also allows us to store unanimated
data on the GPU, instead of transferring it over the bus for each frame.
stable-6.1
Nicolas Hake 2015-02-24 17:36:58 +00:00
parent 5b0759952e
commit c66833e2db
3 changed files with 88 additions and 13 deletions

View File

@ -2,7 +2,7 @@
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2013, The OpenClonk Team and contributors
* Copyright (c) 2013-2015, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
@ -456,7 +456,10 @@ namespace
const StdMeshMaterial& material = instance.GetMaterial();
assert(material.BestTechniqueIndex != -1);
const StdMeshMaterialTechnique& technique = material.Techniques[material.BestTechniqueIndex];
const StdMeshVertex* vertices = instance.GetVertices().empty() ? &mesh_instance.GetSharedVertices()[0] : &instance.GetVertices()[0];
bool using_shared_vertices = instance.GetVertices().empty();
GLuint vbo = mesh_instance.GetVBO();
size_t buffer_offset = using_shared_vertices ? 0 : instance.GetOffsetInBuffer();
// Render each pass
for (unsigned int i = 0; i < technique.Passes.size(); ++i)
@ -525,11 +528,12 @@ namespace
glBlendFunc(OgreBlendTypeToGL(pass.SceneBlendFactors[0]), GL_ONE);
}
// TODO: Use vbo if available.
glTexCoordPointer(2, GL_FLOAT, sizeof(StdMeshVertex), &vertices->u);
glVertexPointer(3, GL_FLOAT, sizeof(StdMeshVertex), &vertices->x);
glNormalPointer(GL_FLOAT, sizeof(StdMeshVertex), &vertices->nx);
#define VERTEX_OFFSET(field) reinterpret_cast<const uint8_t *>(offsetof(StdMeshVertex, field))
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glTexCoordPointer(2, GL_FLOAT, sizeof(StdMeshVertex), buffer_offset + VERTEX_OFFSET(u));
glVertexPointer(3, GL_FLOAT, sizeof(StdMeshVertex), buffer_offset + VERTEX_OFFSET(x));
glNormalPointer(GL_FLOAT, sizeof(StdMeshVertex), buffer_offset + VERTEX_OFFSET(nx));
#undef VERTEX_OFFSET
glMatrixMode(GL_TEXTURE);
@ -649,6 +653,7 @@ namespace
glMatrixMode(GL_MODELVIEW);
glDrawElements(GL_TRIANGLES, instance.GetNumFaces()*3, GL_UNSIGNED_INT, instance.GetFaces());
call.Finish();
glBindBuffer(GL_ARRAY_BUFFER, 0);
if(!pass.DepthCheck)
glEnable(GL_DEPTH_TEST);

View File

@ -2,7 +2,7 @@
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2013, The OpenClonk Team and contributors
* Copyright (c) 2009-2015, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
@ -552,8 +552,8 @@ void StdMesh::PostInit()
}
StdSubMeshInstance::StdSubMeshInstance(StdMeshInstance& instance, const StdSubMesh& submesh, float completion):
Vertices(submesh.GetNumVertices()),
Material(NULL), CurrentFaceOrdering(FO_Fixed)
Vertices(submesh.GetNumVertices()),
Material(NULL), CurrentFaceOrdering(FO_Fixed)
{
// Copy initial Vertices/Faces
for (unsigned int i = 0; i < submesh.GetNumVertices(); ++i)
@ -961,7 +961,7 @@ void StdMeshInstance::AttachedMesh::MapBonesOfChildToParent(const StdMeshSkeleto
}
StdMeshInstance::StdMeshInstance(const StdMesh& mesh, float completion):
Mesh(&mesh), SharedVertices(mesh.GetSharedVertices().size()), Completion(completion),
Mesh(&mesh), SharedVertices(mesh.GetSharedVertices().size()), vbo(0), Completion(completion),
BoneTransforms(Mesh->GetSkeleton().GetNumBones(), StdMeshMatrix::Identity()),
SubMeshInstances(Mesh->GetNumSubMeshes()), AttachParent(NULL),
BoneTransformsDirty(false)
@ -979,6 +979,9 @@ StdMeshInstance::StdMeshInstance(const StdMesh& mesh, float completion):
// copy, order is fine at the moment since only default materials are used.
SubMeshInstancesOrdered = SubMeshInstances;
// Initialize VBOs for non-animated mesh instances
UpdateVBO();
}
StdMeshInstance::~StdMeshInstance()
@ -997,7 +1000,12 @@ StdMeshInstance::~StdMeshInstance()
// Delete submeshes
for (unsigned int i = 0; i < SubMeshInstances.size(); ++i)
{
delete SubMeshInstances[i];
}
if (vbo)
glDeleteBuffers(1, &vbo);
}
void StdMeshInstance::SetFaceOrdering(FaceOrdering ordering)
@ -1350,6 +1358,9 @@ bool StdMeshInstance::UpdateBoneTransforms()
if(!submesh.GetVertices().empty())
ApplyBoneTransformToVertices(submesh.GetVertices(), SubMeshInstances[i]->Vertices);
}
// Vertexes have moved, update VBO
UpdateVBO();
}
// Update attachment's attach transformations. Note this is done recursively.
@ -1388,6 +1399,58 @@ bool StdMeshInstance::UpdateBoneTransforms()
return was_dirty;
}
void StdMeshInstance::UpdateVBO()
{
bool has_animation = Mesh->GetSkeleton().IsAnimated();
// Create VBO on demand
if (vbo == 0)
{
glGenBuffers(1, &vbo);
}
// Calculate total number of vertices
size_t total_vertices = GetNumSharedVertices();
for (auto &submesh : SubMeshInstances)
{
total_vertices += submesh->GetNumVertices();
}
glBindBuffer(GL_ARRAY_BUFFER, vbo);
// Unmapping the buffer may fail for certain reasons, in which case we need to try again.
do
{
// Allocate VBO backing memory. If this mesh's skeleton has no animations
// defined, we assume that the VBO will not change frequently.
glBufferData(GL_ARRAY_BUFFER, total_vertices * sizeof(StdMeshVertex), NULL, has_animation ? GL_STREAM_DRAW : GL_STATIC_DRAW);
void *map = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
uint8_t *buffer = static_cast<uint8_t*>(map);
uint8_t *cursor = buffer;
// Add shared vertices to buffer
if (!SharedVertices.empty())
{
size_t shared_vertices_size = GetNumSharedVertices() * sizeof(SharedVertices[0]);
std::memcpy(cursor, &SharedVertices[0], shared_vertices_size);
cursor += shared_vertices_size;
}
// Add all submeshes to buffer
for (auto &submesh : SubMeshInstances)
{
// Store the offset, so the render code can use it later
submesh->buffer_offset = cursor - buffer;
if (submesh->Vertices.empty()) continue;
size_t vertices_size = sizeof(submesh->Vertices[0]) * submesh->Vertices.size();
std::memcpy(cursor, &submesh->Vertices[0], vertices_size);
cursor += vertices_size;
}
} while (glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE);
// Unbind the buffer so following rendering calls do not use it
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void StdMeshInstance::ReorderFaces(StdMeshMatrix* global_trans)
{
for (unsigned int i = 0; i < SubMeshInstances.size(); ++i)

View File

@ -2,7 +2,7 @@
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2013, The OpenClonk Team and contributors
* Copyright (c) 2009-2015, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
@ -116,6 +116,7 @@ public:
const StdMeshBone* GetBoneByName(const StdStrBuf& name) const;
const StdMeshAnimation* GetAnimationByName(const StdStrBuf& name) const;
bool IsAnimated() const { return !Animations.empty(); }
// TODO: This code should maybe better be placed in StdMeshLoader...
void MirrorAnimation(const StdMeshAnimation& animation);
@ -233,6 +234,8 @@ public:
// go elsewhere if/when we want to calculate this on the hardware.
const std::vector<StdMeshVertex>& GetVertices() const { return Vertices; }
size_t GetNumVertices() const { return Vertices.size(); }
// Return the offset into the backing vertex buffer where this SubMesh's data starts
size_t GetOffsetInBuffer() const { return buffer_offset; }
// Get face of instance. The instance faces are the same as the mesh faces,
// with the exception that they are differently ordered, depending on the
@ -258,6 +261,7 @@ protected:
// b) compute them on the GPU
std::vector<StdMeshVertex> Vertices;
std::vector<StdMeshFace> Faces; // TODO: Indices could also be stored on GPU in a vbo (element index array). Should be done in a next step if at all.
size_t buffer_offset;
const StdMeshMaterial* Material;
@ -281,7 +285,6 @@ protected:
FaceOrdering CurrentFaceOrdering; // NoSave
// TODO: GLuint texenv_list; // NoSave, texture environment setup could be stored in a display list (w/ and w/o ClrMod). What about PlayerColor?
// TODO: GLuint vbo; // NoSave, replacing vertices list -- can be mapped into memory for writing. Should be moved to StdMesh once we apply skeletal transformation on the GPU.
private:
StdSubMeshInstance(const StdSubMeshInstance& other); // noncopyable
@ -592,6 +595,8 @@ public:
const StdMesh& GetMesh() const { assert(Mesh != NULL); return *Mesh; }
GLuint GetVBO() const { return vbo; }
protected:
typedef std::vector<AnimationNode*> AnimationNodeList;
@ -606,6 +611,8 @@ protected:
float Completion; // NoSave
std::vector<StdMeshVertex> SharedVertices;
GLuint vbo;
void UpdateVBO();
AnimationNodeList AnimationNodes; // for simple lookup of animation nodes by their unique number
AnimationNodeList AnimationStack; // contains top level nodes only, ordered by slot number