Rendering multiple billboard with one draw call

Started by
15 comments, last by VladR 11 years ago

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?

Advertisement

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 biggrin.png ) , 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;
    VBuffer->Lock((LPVOID*)&pVertices, D3DLOCK_DISCARD);
    for(DWORD i = 0; i < (DWORD)AParticles.Size(); i++)
    {
        const Particle& particle                   = AParticles;

        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->Lock((LPVOID*)&pVertices, D3DLOCK_DISCARD);
        }
    }
    VBuffer->Unlock();
 
    // if more quads left draw it
    if(numVert > 0)
    {
        D3D9Device->DrawIndexedPrimitive(
                D3DPT_TRIANGLELIST, 0, 0, numVert, 0, numTri);
    }
}

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.

http://www.dhpoware.com/demos/xnaBillboards3.html and here https://developer.nvidia.com/content/gpu-gems-2-chapter-1-toward-photorealism-virtual-botany-1

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.size, 1.0f*billboard.size, 0.0f), 0.0f, 0.0f);
     vertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 1.0f, 0.0f);
     vertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 0.0f, 1.0f);
     vertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, -1.0f*billboard.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?

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.size, 1.0f*billboard.size, 0.0f), 0.0f, 0.0f);
     vertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 1.0f, 0.0f);
     vertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 0.0f, 1.0f);
     vertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, -1.0f*billboard.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.

@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.

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;
            VBuffer->Lock((LPVOID*)&pVertices, D3DLOCK_DISCARD);
        }

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.

@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.size, 1.0f*billboard.size, 0.0f), 0.0f, 0.0f);
pVertices[i*4+1] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 1.0f, 0.0f);
pVertices[i*4+2] = BILLBOARD_VERTEX(D3DXVECTOR3(-1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 0.0f, 1.0f);
pVertices[i*4+3] = BILLBOARD_VERTEX(D3DXVECTOR3(1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 1.0f, 1.0f);

Try to think about it.

You got your billboard struct:

struct Billboard
{
    D3DXVECTOR3 pos; // position
    float size; // billboard size
    float lifeTime; // time alive
};
 
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.pos + D3DXVECTOR3(-1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 0.0f, 0.0f);
pVertices[i*4+1] = BILLBOARD_VERTEX(billboard.pos + D3DXVECTOR3(1.0f*billboard.size, 1.0f*billboard.size, 0.0f), 1.0f, 0.0f);
pVertices[i*4+2] = BILLBOARD_VERTEX(billboard.pos + D3DXVECTOR3(-1.0f*billboard.size, -1.0f*billboard.size, 0.0f), 0.0f, 1.0f);
pVertices[i*4+3] = BILLBOARD_VERTEX(billboard.pos + D3DXVECTOR3(1.0f*billboard.size, -1.0f*billboard.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.

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.

This topic is closed to new replies.

Advertisement