1
\$\begingroup\$

I don't understand exactly how batching works.

What I have understood: Batching is useful to regroup similar elements together and draw them in one draw call (performance++), currently drawing an element is 90% of the time in my application.

How I do that: I need to regroup elements with the same Material (Shader, Texture, …) and with his type (static or dynamic geometry).

Here is my rendering structure:

// Get Visibles spacials.
SpacialSet* spacials        = this->scene;
SpacialSet* visibleSpacials = this->culler->execute(spacials, this->scene->camera);

// Render the scene (this method call "draw", for each spacial)
this->renderer->render(visibleSpacials, this->scene->camera);

How I draw an element:

void Renderer::draw( Spacial* spacial )
{
    this->setGeometry(spacial->geometry);  // Bind buffers
    this->setMaterial(spacial->material);  // Send data to shader/bind textures.
    this->drawPrimitive( material->drawingMode, geometry->indicesCount);
}

How I think I will do it:

I should create a smart "Batch" class, his goal would be to create a new mesh (large VBO, pointer to textures, material) who will be a merge of all Spacials with the same apparence.

  • Look current mesh and search a good batch or create a new if not found
  • Merge it with previous elements
  • Hide current mesh/ignore drawing
  • Draw the batch/big mesh.

My questions:

  • Does my approach look good?

  • How do I manage positions per mesh?

  • Do I need to update my batch each frame?

  • How does it work with culling? If I have a static batch with all my sprites and 70% of sprites in this batch are out of screen, should I draw this batch ?

  • Do I need to do batching before or after culling?

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

I agree with petterson, but if you want to reduce amount of draw calls as well you need to use instancing.

Does my approach look good?

If you create it once and will not move a lot of stuff around each frame, there is nothing bad in it. It all depends on you usage.

How do I manage positions per mesh?

For this you need to identity what mesh is currently being processed. If you need to draw many same meshes (say a lot of stones, or grass) you should look into Instancing (e.g.Efficiently Drawing Multiple Instances of Geometry) there you basically have one stream which has mesh data similar to all objects(vertices, tex coords etc.) and another which has per instance data (like object positions in the world).

Both OpenGL and DX have support for instancing.

OpenGL (since 4.3) also has very cool function glMultiDrawIndirect (or chapter 1.3 here) which allows to do what, I think, you want - draw several different objects using only one draw call. This is "more advanced" instancing, you pass array of structures describing how many instances to draw and where to get data for them in your buffers. See the description it is quite clear.

However, you need to use same shader for all of the objects because you cannot pass "shaderid" in your per instance buffer. But you may write a generic shader and based on gl_DrawID (passed to your shader by OpenGL, has unique value for each instance) do stuff that you want. E.g., you may construct uniform array and index it based on gl_DrawID. You can do the same if you need different textures per instance. In this case you can create texture array and index it (note that in this case all textures have to have same size and format). You also may try to use bindless textures (OpenGL 4.4 :P).

Do I need to update my batch each frame?

Depend on what have changed.

How does it work with culling? If I have a static batch with all my sprites and 70% of sprites in this batch are out of screen, should I draw this batch ?

In case of simple instancing (many same objects) you will need to update per instance data and amount of instances to be drawn according to results of your occlusion test.

In case of glMultiDrawIndirect, however, you may update "count" parameter (set it to = 0 if object is not visible), but driver will, most probably, still issue an empty draw call. But you may as well update the whole array of structures containing the draw parameters.

You might be interested in GPU based occlusion, like one described here starting at slide 35.

Do I need to do batching before or after culling?

Probably after, you need this information to construct per instance data buffers. But there is no need to modify mesh data.

I hope this was clear.

\$\endgroup\$
2
\$\begingroup\$

first I wouldn't copy VBO's etc at runtime. Merging meshes to larger buffers sounds like something for a offline preprocess. But you can always group your drawable meshes to minimize shader and renderstate changes. So when preparing for drawing, collect your visible meshes (i.e. after culling) and sort them by shader / material and renderstate. In your example: after culler->execute, sort your visibleSpacials and call the render method. If you only have a handful of differnent materials, you could create "buckets", one for each material and sort your spatials. Then the renderer only has to set material/shader once for each bucket.

hope that helps a bit to get you started

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .