Sign in to follow this  

Good batching approach

This topic is 3309 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello everybody! First I've to say sorry for my poor english, but I'll do my best. :) Currently I would like to implement a relatively "generic" vertex and index buffer handling in my engine which supports DX9 and DX10. So I thought that I start to design a class which you can use like this:
ITwlMesh* pMesh = pSceneGraph->CreateMesh("myMesh1", USAGE_STATIC, VERTEXFORMAT_P3_TEX2);
TwlVertex_P3_COL verts[3] =
{
	{ Vec3(0.0f, 1.0f, 0.0f), 0xFFFF0000 },
	{ Vec3(1.0f, 0.0f, 0.0f), 0xFFFF0000 },
	{ Vec3(-1.0f,0.0f, 0.0f), 0xFFFF0000 }
};
pMesh->SetVertices(&verts, 3);
pPlayerNode->AttachEntity(pMesh);
But I don't want to give every mesh some vertex and index buffers, because I also would like to reduce "SetStreamSource()" calls. When rendering I traverse the scene graph and sort every mesh depending on it's vertex declaration, material, shader constants, etc. My first idea was to implement a buffer manager which initializes some fixed size vertex and index buffers for every vertex type and only give each mesh offsets for this buffers. But now I'm stuck, because how can I handle "streaming" of very big worlds with this approach? I've to load different chunks of geometry if the user entries a new page of my world/terrain and unload unused pages/meshes/obstacles and so on? For dynamic meshes it seems very easy find a solution. Here I also use a vertex and indexbuffer for each vertextype, fill these buffers each frame until a buffer reaches it's maximum, flushes it and refill it with vertex data. But how I should treat static/dynamically loaded geometry? Thanks very much for your suggestions! ;)

Share this post


Link to post
Share on other sites
There are two reasons why no one is answering:

1) Most of the people here is from Usa and thus they sleep while we europeans sit at our computers wondering about DirectX and typing :)

2) Your question is a deep architectural problem which raises lots of other questions and substantially is about building a whole engine! (and isn't really strictly related to DirectX in the end)

What I personally suggest you is to think to the single "bricks" first and then assemble then into walls. Don't think about organization, sorting, graphs and such. Code down a class wich encapsulates data loading and rendering. Then create a layer that does all the rest.

As for coding parallel paths for both DX9 and DX10... from my personal experience I suggest you to go DX10 and DX10 only. The two APIs are so architecturally different that using both at the same time is extremely difficult. DX11 is coming, coding for DX9 is just... passè :)

Share this post


Link to post
Share on other sites
Thank you resle for your reply.

Sorry for my impatience, but I thought that this still is a very established problem, where over the years developers found a very generic solution in case of performance.

I already finished the most important parts of my engine and now I want to optimize things. And so I started thinking of optimizing my vertex/index buffer management in case of storing all static data in only one vertex and index buffer and only addressing parts of it through my mesh class. In this way I thought it would be faster to output my vertices. (fewer SetStreamSource() calls, because they aren't cheap!)
But I also thought that in terms of "streaming"/"dynamically loading world data at runtime" this can be very difficult to handle.
And so I wanted to know if anybody can give me a hint on how I can handle this in a good way?

Thanks!

Share this post


Link to post
Share on other sites
Now your request is more clear. The obvious (at least to me) answer to your doubt is: keep buffers separated (for each object in your "world"), and make a strong us of Instancing. Keeping a lot of stuff into a single buffer and rendering it using offsets is not a flexible enough technique for a large world: the best usage scenario for that, as far as I can think, is rendering only the visibile portions of a BSPed huge mesh (i.e.: a Quake/Doom/etc. map)

Share this post


Link to post
Share on other sites
I disagree with your on this point

For small meshes I would definitely use a buffermanager, sharing the buffers among meshes is as simple as storing (bufferid+bufferoffset)

For large meshes, such as heightmap terrain where you know the buffer size in advance I would allocate dependent on the size some fixed buffers and replace the data glSubBuffer...
when streaming in

e.g.:
a large buffer for 9 chunks
0 1 2 a
3 4 5 b
6 7 8 c

if you move east you fill the chunks 0 3 6 with new data, using one single large buffer


Also instead of mixing your mesh code with rendering specific code I advice you to use an intermediate object to fill the gap between rendering and mesh representation

something like

Renderable:
{
mesh reference
bufferid
bufferofset
bbox
position
orientation
}

Share this post


Link to post
Share on other sites
Thank you guys for your advice! :)

I also agree with you, Basiror.
Too many small VBs with under 1000 vertices are a big performance loss.
Ok, so for now having an internally VB/IB buffer management to cache meshes to min 1000 vertices per VB is a good starting point I think.

But what did you mean with the "intermediate renderable"?
Why should I do that?
To split loading/saving mesh code and render code?
Would you please give me a bit more detail in that?

Thanks!

Share this post


Link to post
Share on other sites
Well it make your code easier to reuse, you don t want to write a renderer for each kind of mesh do you? all you need to know about an object is its vertex data, orientation and position,
so once you got this you can create another level in your abstraction hierarchy to handle VBOS, in which case the VertexPtr() returns of offset inside your buffer and a additional function returns the BufferId ...
see below

[source language="cpp"]
class Mesh
{
};
class Renderable
{
public:
virtual const float* VertexPtr() const = 0;
virtual const AABB& BoundingBox() const = 0
...
virtual ~Renderable(){};
};

class VBORenderable : public Renderable
{
unsigned int m_BufferId;
public:
unsigned int BufferId() const { return m_BufferId; };
};
class MeshRenderable : public Renderable
{
boost::shared_ptr< Mesh > m_Mesh;
const Matrix4& m_Orientation;
const Vector3& m_Position;
const AABB& m_BoundingBox;
public:
const float VertexPtr () const { return ....; };
const AABB& BoundingBox() const { return m_BoundingBox; };
};

class VBOMeshRenderable : public VBORenderable
{
...
};
boost::shared_ptr< Renderable > renderable(new MeshRenderable(somemeshptr, entity.m_OrientationMatrix, entity.m_Position, entity.m_BoundingBox));







The renderables just act as some kind of accessor interface to make it easier for you to write a renderer without haveing to handle all kinds of different mesh representations seperately.

Looks like I am using the adapter pattern
http://en.wikipedia.org/wiki/Adapter_pattern


In my engine I split each renderable into Renderable (the entity) and RenderObject( material groups of a mesh )

So at first I cull the renderables,
then I retrieve all visible material groups and sort them in a list.

Each material group's vertices are stored in a VBO and I pass additional information to the buffer manager to store material groups with equal materials in the same buffers.

BufferManager.Allocate(size,userdata_material, MaterialComparator);

The MaterialComparator just offers you an opportunity to change the behaviour of allocation outside the manager class, you might want to use materialid and static positions for static geometries, or just materialids for dynamic geometry, thats up to you.

My manager is pretty simple, I allocate at least one buffer for each material and fill it up to a maximum size.

Share this post


Link to post
Share on other sites

This topic is 3309 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this