# Rendering multiple billboard with one draw call

## Recommended Posts

I have created billboard class but it's drawing one single billboard per draw call, now I want to it draw ALL the billboards with one draw call.

I'm creating the vertex buffer as the following:

// Class Constructor
float scale = 20.0f;

device->CreateVertexBuffer(4 * sizeof(BILLBOARD_VERTEX),
D3DUSAGE_WRITEONLY,
CustomBillboardFVF,
D3DPOOL_DEFAULT,
&vbuffer,
NULL);

vbuffer->Lock(0, 0, (void**)&vertices, 0);
pVertices[0] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*scale, 1.0f*scale, 0.0f), 0.0f, 0.0f);
pVertices[1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*scale, 1.0f*scale, 0.0f), 1.0f, 0.0f);
pVertices[2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*scale, -1.0f*scale, 0.0f), 0.0f, 1.0f);
pVertices[3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*scale, -1.0f*scale, 0.0f), 1.0f, 1.0f);
vbuffer->Unlock();


For rendering:

void render()
{
device->SetFVF(CustomBillboardFVF);
device->SetTexture(0, billboardTexture);
device->SetStreamSource(0, vbuffer, 0, sizeof(BILLBOARD_VERTEX));
device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
}

I'm storing the billboards in vector<billboard>.

How do I change my code to render all the billboards with one single draw call?

##### Share on other sites

This is from my "old" code base that i've used couple of years ago (when i was stupid, note that i am not much smarter nowadays  ) , based on Irrlicht engine particle system, note that (although this works) some things should be done better, and i am to lazy to clean up my code but you should get some ideas how to use it:

bool ParticleSceneNode::Init(.......)
{
D3D9Device = d3d9device;

VDecl = new VertexDeclaration;
if(!VDecl->Init(D3D9Device, EVT_3D_PNCT1))
{
return false;
}

// create vertex buffer arbitrarily large to hold some particle quads
VBuffer = new VertexBuffer;
if(!VBuffer->Init(D3D9Device, 512, sizeof(Vertex3D_PNCT1), true))
{
return false;
}

UINT numIndices = (VBuffer->GetNumVertices() * 2) - (VBuffer->GetNumVertices() / 2);
IBuffer = new IndexBuffer;
if(!IBuffer->Init(D3D9Device, numIndices, sizeof(USHORT)))
{
return false;
}

USHORT* pIndices = 0;
IBuffer->Lock((LPVOID*)&pIndices);
USHORT j = 0;
for(USHORT i = 0; i < numIndices; i += 6)
{
pIndices[0+i] = 0+j;
pIndices[1+i] = 2+j;
pIndices[2+i] = 1+j;
pIndices[3+i] = 0+j;
pIndices[4+i] = 3+j;
pIndices[5+i] = 2+j;
j += 4;
}
IBuffer->Unlock();

...
// HERE ADD SOME PARTICLE EMMITERS & AFFECTORS...
...

ParticleSize  = 8;
LastEmittTime = 0;

return true;
}

void ParticleSceneNode::OnRender()
{
DWORD time = ::GetTickCount();
DoParticleSystem(time);

const Matrix4& viewMat = Smgr->GetActiveCamera()->GetView();
float f = 0.5f * (float)ParticleSize;
Vector3D horizontal(viewMat.M00 * f, viewMat.M10 * f, viewMat.M20 * f);

f = -0.5f * (float)ParticleSize;
Vector3D vertical(viewMat.M01 * f, viewMat.M11 * f, viewMat.M21 * f);

Vector3D view(-viewMat.M02, -viewMat.M12 , -viewMat.M22);

D3D9Device->SetVertexDeclaration(VDecl->GetDeclaration());
D3D9Device->SetIndices(IBuffer->GetBuffer());
D3D9Device->SetStreamSource(0, VBuffer->GetBuffer(), 0, sizeof(Vertex3D_PNCT1));

UINT numVert    = 0;
UINT numTri     = 0;
Vertex3D_PNCT1* pVertices = 0;
for(DWORD i = 0; i < (DWORD)AParticles.Size(); i++)
{
const Particle& particle                   = AParticles[i];

Vector3D shorizontal = horizontal * particle.Scale;
Vector3D svertical   = vertical * particle.Scale;

pVertices[0 + numVert].Position         = particle.Position + shorizontal + svertical;
pVertices[0 + numVert].Normal           = view;
pVertices[0 + numVert].TexCoord0        = Vector2D(0.0f, 0.0f);
pVertices[0 + numVert].Color            = particle.Color;

pVertices[1 + numVert].Position         = particle.Position + shorizontal - svertical;
pVertices[1 + numVert].Normal           = view;
pVertices[1 + numVert].TexCoord0        = Vector2D(0.0f, 1.0f);
pVertices[1 + numVert].Color            = particle.Color;

pVertices[2 + numVert].Position         = particle.Position - shorizontal - svertical;
pVertices[2 + numVert].Normal           = view;
pVertices[2 + numVert].TexCoord0        = Vector2D(1.0f, 1.0f);
pVertices[2 + numVert].Color            = particle.Color;

pVertices[3 + numVert].Position         = particle.Position - shorizontal + svertical;
pVertices[3 + numVert].Normal           = view;
pVertices[3 + numVert].TexCoord0        = Vector2D(1.0f, 0.0f);
pVertices[3 + numVert].Color            = particle.Color;

if(particle.Rotation)
{
Vector2D centerCoord(0.5f, 0.5f);
pVertices[0 + numVert].TexCoord0.RotateBy(particle.Rotation, centerCoord);
pVertices[1 + numVert].TexCoord0.RotateBy(particle.Rotation, centerCoord);
pVertices[2 + numVert].TexCoord0.RotateBy(particle.Rotation, centerCoord);
pVertices[3 + numVert].TexCoord0.RotateBy(particle.Rotation, centerCoord);
}

numVert    += 4;
numTri     += 2;

// if exceded buffer size unlock -> draw -> lock
if(numVert > (VBuffer->GetNumVertices() - 4))
{
VBuffer->Unlock();
D3D9Device->DrawIndexedPrimitive(
D3DPT_TRIANGLELIST, 0, 0, numVert, 0, numTri);
pVertices = 0;
numVert   = 0;
numTri    = 0;
}
}
VBuffer->Unlock();

// if more quads left draw it
if(numVert > 0)
{
D3D9Device->DrawIndexedPrimitive(
D3DPT_TRIANGLELIST, 0, 0, numVert, 0, numTri);
}
}

Edited by belfegor

##### Share on other sites

Why not just make your vertex buffer bigger and have a different position value for each billboard - there is a limit to the size of the vertex buffer, but I do a brute force approach and pack a big vertex buffer full of all the billboards I'm going to need, and then draw them all in one call.

You can find the logic and code samples for rotating the billboard in the vertex shader here.

##### Share on other sites

I changed my code, now I can't see the billboards anymore:

#define BillboardFVF (D3DFVF_XYZ | D3DFVF_TEX1)

Billboard class constructor:

// Class Constructor
BILLBOARD_VERTEX *vertices = NULL;

device->CreateVertexBuffer(10000 * sizeof(BILLBOARD_VERTEX),
D3DUSAGE_WRITEONLY,
CustomBillboardFVF,
D3DPOOL_DEFAULT,
&vbuffer,
NULL);

device->CreateIndexBuffer(10000 * sizeof(BILLBOARD_VERTEX),
D3DUSAGE_WRITEONLY, D3DFMT_INDEX32,
D3DPOOL_MANAGED,
&indexBuffer,
NULL);

UINT numIndices = (10000 * 2) - (10000 / 2);
USHORT* pIndices = 0;
indexBuffer->Lock(0, 0, (LPVOID*)&pIndices, 0);
USHORT j = 0;
for(USHORT i = 0; i < numIndices; i += 6)
{
pIndices[0+i] = 0+j;
pIndices[1+i] = 2+j;
pIndices[2+i] = 1+j;
pIndices[3+i] = 0+j;
pIndices[4+i] = 3+j;
pIndices[5+i] = 2+j;
j += 4;
}
indexBuffer->Unlock();


Rendering code:

void render()
{
// Set FVF
d3ddev->SetFVF(CustomBillboardFVF);

for(DWORD i = 0; i < billboard.size(); i++)
{
vbuffer->Lock(0, 0, (void**)&pVertices, 0);
vertices[i*4+0] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 0.0f, 0.0f);
vertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 1.0f, 0.0f);
vertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 0.0f, 1.0f);
vertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 1.0f, 1.0f);
vbuffer->Unlock();
}

D3DXMATRIX matWorld;
D3DXMatrixIdentity(&matWorld);
device->SetTransform(D3DTS_WORLD, &matWorld);

// 2-Sided
device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

// Texture
device->SetTexture(0, texture);

device->SetStreamSource(0, vbuffer, 0, sizeof(CUSTOM_VERTEX));
device->SetIndices(indexBuffer);
device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 10000, 0, 5000);
}


What's wrong?

##### Share on other sites

There are many problems in your code.

1. Check all functions that returns HRESULT for failure.

2.

device->CreateIndexBuffer(10000 * sizeof(BILLBOARD_VERTEX), ....


should be:

// if D3DFMT_INDEX32
device->CreateIndexBuffer(numIndices * sizeof(DWORD), ...

//if D3DFMT_INDEX16
device->CreateIndexBuffer(numIndices * sizeof(WORD), ...


There are 4 vertices for each particle/billboard. There are 2 triangles in each of those and there is a 3 indices per triangle:

maxNumParticlesInBuffer = 100;
numVertices                     = maxNumParticlesInBuffer * 4;
numTriangles                   = (maxNumParticlesInBuffer * 2);
numIndices                       = numTriangles * 3;


3.

This:

for(DWORD i = 0; i < billboard.size(); i++)
{
vbuffer->Lock(0, 0, (void**)&pVertices, 0);
vertices[i*4+0] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 0.0f, 0.0f);
vertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 1.0f, 0.0f);
vertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 0.0f, 1.0f);
vertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 1.0f, 1.0f);
vbuffer->Unlock();
}


You are placing each particle on the same spot, and locking/unlocking buffer for each one.

You should create something like this:

struct Particle
{
Vec3 pos;
Color col;
...// add more components that you need
};
...
std::vector<Particle> vParticles;
...// fill it with emitter and move, change color (affect) with affectors.


and then use this to update your buffer vertices.

4.

This says CUSTOM_VERTEX

device->SetStreamSource(0, vbuffer, 0, sizeof(CUSTOM_VERTEX));


but above for vertices you used BILLBOARD_VERTEX.

Edited by belfegor

##### Share on other sites

@belfegor: I notice that you make one draw call per billboard in the code you posted, while I want to draw all the billboards with one single draw call.

##### Share on other sites

I notice that you make one draw call per billboard in the code you posted,

That is not true!

// if exceded buffer size unlock -> draw -> lock
if(numVert > (VBuffer->GetNumVertices() - 4))
{
VBuffer->Unlock();
D3D9Device->DrawIndexedPrimitive(
D3DPT_TRIANGLELIST, 0, 0, numVert, 0, numTri);
pVertices = 0;
numVert   = 0;
numTri    = 0;
}


i only call draw if current number of particles exceed buffer size! If you make your buffer large enough this should not happen and you get only one draw call instead possible 2 - 3 witch isn't bad at all in that case either, and i can bet this is the common way it is done by other people.

Can you estimate how many billboards you will have? If so, you can set buffer at exact size so you will 100% get only one draw call.

Edited by belfegor

##### Share on other sites

@belfegor: How do I change the following code to set the billboard position (with respect to size)?

pVertices[i*4+0] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 0.0f, 0.0f);
pVertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 1.0f, 0.0f);
pVertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 0.0f, 1.0f);
pVertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 1.0f, 1.0f);

##### Share on other sites

struct Billboard
{
D3DXVECTOR3 pos; // position
float size; // billboard size
};

vector< Billboard > billboard;


Over time you fill this vector with new billboards, remove old ones when their time expire and update its position/color the way you like.

Then you use that position to update its four vertices:

pVertices[i*4+0] = BILLBOARD_VERTEX(billboard[i].pos + D3DXVECTOR3(-1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 0.0f, 0.0f);
pVertices[i*4+1] = BILLBOARD_VERTEX(billboard[i].pos + D3DXVECTOR3(1.0f*billboard[i].size, 1.0f*billboard[i].size, 0.0f), 1.0f, 0.0f);
pVertices[i*4+2] = BILLBOARD_VERTEX(billboard[i].pos + D3DXVECTOR3(-1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 0.0f, 1.0f);
pVertices[i*4+3] = BILLBOARD_VERTEX(billboard[i].pos + D3DXVECTOR3(1.0f*billboard[i].size, -1.0f*billboard[i].size, 0.0f), 1.0f, 1.0f);


But this code doesn't take orientation into account. I gave you good sample in second post of this thread.

##### Share on other sites

I don't know how many billboard I will be having.

Should I create use device->CreateVertexBuffer() while rendering so I can make the buffer size as needed?

Will using device->CreateVertexBuffer() while rendering affect the performance?

My code will be like:

void render()
{
device->CreateVertexBuffer(billboards.size() * 4 * sizeof(BILLBOARD_VERTEX),
D3DUSAGE_WRITEONLY,
CustomBillboardFVF,
D3DPOOL_DEFAULT,
&vbuffer,
NULL);

device->CreateIndexBuffer(...);

// Others codes for rendering here...
}


I'm thinking the above code could affect the performance since I should not be creating Vertex and Index buffer while rendering.

##### Share on other sites

No. Create VB only at init time. Number of vertices depends on particular effect you want to achieve.

For example, for smoke steam going out of pipe i used around 60x4 (240 vertices), which for me looks satisfactory.

Your container of particles should change dynamically over time so you cant use it to determine
VB size, unless it is constant and you never remove or add new ones. You must use some kind of emitter, and then update your particle/billboards vector:

void onUpdate( float deltaTime )
{
emitter->emitt(deltaTime, vParticles);

// affectors affect particle
// adds to their direction/position/scale/rotation whatever
for(std::size_t i = 0; i < vAffectors.size(); ++i)
{
if(!vAffectors[i]->isEnabled())
continue;
vAffectors[i]->affect(deltaTime, vParticles);
}

for(std::size_t i = 0; i < vParticles.size(); ++i)
{
vParticles[i].pos      += vParticles[i].dir * deltaTime; // particle.dir is their emitted direction
vParticles[i].currTime += deltaTime; // increment their life time

if(vParticles[i].currTime > vParticles[i].endTime) // if time to die, remove particle
{
std::swap(vParticles[i--], vParticles.back());
vParticles.pop_back();
}
}
}

Edited by belfegor

##### Share on other sites

I will be having a single class that will handle all the billboards, so I don't know how many billboards I will be using, I could use 100 or I could use 5000 or maybe even 20000 billboard.

So it depends on the game, the billboard class will be related to the engine, so the game developer will not be editing the class.

The class should be able to handle unlimited number of billboards.

##### Share on other sites

What about you get this working first (at least) and then think about "advanced class that will manage all billboards with unlimited count"?

My example above can already handle "unlimited number of billboard" but the draw calls depends on initial size of VB. You will always be faced with some "limitations", which isn't that bad at all.

##### Share on other sites

Okay, since I don't know how many billboard I will be drawing in the class constructor, is it a good idea to set the initial size of the VB to a large number? for example: 40000*sizeof(DWORD) or 80000*sizeof(DWORD).

##### Share on other sites

I give up. Good luck with your project.

##### Share on other sites

Mdeo3337 - did you read the links I posted ? They have a very thorough explanation of the techniques and give you free code for constructing billboards.

Good luck.

##### Share on other sites

This looks like a great time to just thrash whatever you have done with billboards and start from scratch - otherwise you will just kill inordinate amounts of time trying to fix something completely broken.

Seriously, just delete the code and start again using the knowledge about billboards. If you really understand how a billboard is constructed, you should be up&running within 2 hrs anyway.

Start small - one quad, then 2 quads, then 200 quads and save the general case for the last...

## Create an account

Register a new account

• ### Forum Statistics

• Total Topics
627735
• Total Posts
2978854

• 10
• 10
• 21
• 14
• 12