How can I implement a renderer that can draw many kinds of primitives?

SirYakalot
  • How can I implement a renderer that can draw many kinds of primitives? SirYakalot

    This is somewhat linked to a question I previously asked concerning the drawing of indexed primitives.

    My problem was that I was only drawing one cube when I wanted to draw many. I was told that the problem was that I was overwriting the vertex and index buffers with each new instantiation of Cube and that instead I should create one at the origin and then draw many, passing through a transformation matrix to the shader that makes it appear in different places. This worked beautifully.

    I now have a new problem though: how would I draw many different types of primitives?

    Here is my code from the previous question:

    Cube::Cube(D3DXCOLOR colour, D3DXVECTOR3 min, D3DXVECTOR3 max)
    {
    // create eight vertices to represent the corners of the cube
    VERTEX OurVertices[] =
    {
        {D3DXVECTOR3(min.x, max.y, max.z), colour},
        {D3DXVECTOR3(min.x, max.y, min.z), colour},
        {D3DXVECTOR3(min.x, min.y, max.z), colour},
        {min, colour},
        {max, colour},
        {D3DXVECTOR3(max.x, max.y, min.z), colour},
        {D3DXVECTOR3(max.x, min.y, max.z), colour},
        {D3DXVECTOR3(max.x, min.y, min.z), colour},
    };
    
    // create the vertex buffer
    D3D10_BUFFER_DESC bd;
    bd.Usage = D3D10_USAGE_DYNAMIC;
    bd.ByteWidth = sizeof(VERTEX) * 8;
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    bd.MiscFlags = 0;
    
    device->CreateBuffer(&bd, NULL, &pBuffer);
    
    void* pVoid;    // the void pointer
    
    pBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the vertex buffer
    memcpy(pVoid, OurVertices, sizeof(OurVertices));    // copy the vertices to the buffer
    pBuffer->Unmap();
    
    // create the index buffer out of DWORDs
    DWORD OurIndices[] = 
    {
        0, 1, 2,    // side 1
        2, 1, 3,
        4, 0, 6,    // side 2
        6, 0, 2,
        7, 5, 6,    // side 3
        6, 5, 4,
        3, 1, 7,    // side 4
        7, 1, 5,
        4, 5, 0,    // side 5
        0, 5, 1,
        3, 7, 2,    // side 6
        2, 7, 6,
    };
    
    // create the index buffer
    // D3D10_BUFFER_DESC bd;    // redefinition
    bd.Usage = D3D10_USAGE_DYNAMIC;
    bd.ByteWidth = sizeof(DWORD) * 36;
    bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
    bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    bd.MiscFlags = 0;
    
    device->CreateBuffer(&bd, NULL, &iBuffer);
    
    iBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the index buffer
    memcpy(pVoid, OurIndices, sizeof(OurIndices));    // copy the indices to the buffer
    iBuffer->Unmap();
    
    //this is simply a single call to the update method that sets up the scale, rotation
    //and translation matrices, in case the cubes are static and you don't want to have to 
    //call update every frame
    Update(D3DXVECTOR3(1, 1, 1), D3DXVECTOR3(0, 0, 0), D3DXVECTOR3(0, 0, 0));
    }
    

    Clearly if I duplicated and modified the code to be a different object or shape, the last shape to be initialized would overwrite the vertex buffer, wouldn't it?

    Do I use multiple vertex buffers? Do I append the new vertex buffer to the old one and use the appropriate indices to draw them? Can I do either? Both?

  • It's probably a bad idea to create new classes for each type of geometry you're going to support. It's not very scalable or maintainable. Additionally, the class design you appear to want now seems to conflate the tasks of managing the geometry itself and the instance data for that geometry.

    Here's an approach you can take:

    Create two classes, Mesh and MeshInstance. A Mesh contains all the properties of the shared geometry -- basically a vertex buffer and an index buffer. If you want, you can create helper functions that create meshes containing cube vertex data (or sphere vertex data, or whatever else you like). You should tailor the public interface of the Mesh class to allow such helper functions to be implemented as non-member, non-friend functions.

    MeshInstance, on the other hand, must be constructed with a reference to a Mesh. The MeshInstance contains the properties of an individual object -- it's world transformation, and shader overrides used to render it, and so on.

    This way, when you want to create a new cube, you first obtain the Mesh object representing a cube from some shared library of primitive mesh objects you created at startup. Then you create a new MeshInstance, assigning it that cube Mesh.

    When you render, you build up a list of all the MeshInstances you want to draw, and submit them. If you group them by Mesh or texture, you can optimize for state change overhead (i.e., you draw all the mesh instances corresponding to the cube mesh at once, and then all the mesh instances corresponding to the sphere mesh, so you have fewer SetVertexBuffer calls on the D3D device). You can also group by other state, like texture and shader.

    In this fashion, you avoid wasting memory duplicating vertex data, and you ensure that your rendering system can scale to any arbitrary set of primitives simply by implementing new (a) functions to create meshes programmatically or (b) functions to load meshes from files of a particular format.

    Once your render pipeline works in terms of generalized Mesh objects, it's much easier to adapt it in a global fashion to new techniques or optimizations.

    Specific comments:

    Clearly if I duplicated and modified the code to be a different object or shape, the last shape to be initialised would overwrite the vertex buffer. wouldn't it?

    No. In the code you posted, the only way pBuffer and the like would be overwritten was if it was a static member variable. If you copied the class wholesale to create (for example) a Sphere class, that would be a new static variable. This is still a bad idea.

    Do I use multiple vertex buffers? Do I append the new vertex buffer to the old one and use the appropriate indices to draw them? Can I do either? Both?

    The naive implementation of the technique I describe above involves multiple buffers (one for each set of shared geometry). If that geometry is static it is possible to store it all into one (or multiple, as there is a practical optimal limit to buffer size) buffer to minimize buffer state changes even further. That should be considered an optimization and is left as an exercise for the reader; first get it working, then worry about making it fast.

Tags
c++ directx graphics-programming directx10
Related questions and answers
  • ; bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; bd.MiscFlags = 0; device->CreateBuffer(&bd, NULL, &iBuffer); iBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid); // map the index buffer memcpy...}, }; // create the vertex buffer D3D10_BUFFER_DESC bd; bd.Usage = D3D10_USAGE_DYNAMIC; bd.ByteWidth = sizeof(VERTEX) * 8; bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; bd.MiscFlags = 0; device->CreateBuffer(&bd, NULL, &pBuffer); void* pVoid; // the void pointer pBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid); // map the vertex buffer

  • ; bd.ByteWidth = sizeof(DWORD) * 36; bd.BindFlags = D3D10_BIND_INDEX_BUFFER; bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; bd.MiscFlags = 0; device->CreateBuffer(&bd, NULL, &iBuffer); // void...) * 8; bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; bd.MiscFlags = 0; device->CreateBuffer(&bd, NULL, &pBuffer); void* pVoid; // the void pointer...; bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; bd.MiscFlags = 0; device->CreateBuffer(&bd, NULL, &iBuffer); // void* pVoid; // redefinition iBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid

  • , but why? what's the solution? D3D10_BUFFER_DESC bd; bd.Usage = D3D10_USAGE_DYNAMIC; bd.ByteWidth = sizeof(VERTEX) * temporary2DVerts.size();// THIS LINE bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; bd.MiscFlags = 0; device->CreateBuffer(&bd, NULL, &pBuffer); void* pVoid; pBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid); //HERE memcpy(pVoid...(D3D10_PRIMITIVE_TOPOLOGY_LINELIST); UINT stride = sizeof(VERTEX); UINT offset = 0; device->IASetVertexBuffers(0, 1, &pBuffer, &stride, &offset); screenPass->Apply(0

  • : temp2DVerts.size(); bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE; bd.MiscFlags = 0; device->CreateBuffer(&bd, NULL, &pBuffer); void* pVoid; // the void pointer pBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid); // map the vertex buffer memcpy(pVoid, &temporary2DVerts[0], sizeof(temporary2DVerts[0... it all separately? Basically how would I extend this code to include more vectors of vertices? if (temporary2DVerts.size() > 0) { // create the vertex buffer and store the pointer into pBuffer

  • = D3D10_USAGE_DYNAMIC; desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE; Step 2 of my plan is to use CopyResource or CopyResourceSubregion to copy data from the disk...; spTextureResource-&gt;QueryInterface <ID3D10Texture2D&gt; ( &amp;m_spTexture ); if( ! m_spTexture.p ) return false; The texture loads fine, I can blit this onscreen. Later, I want to use that texture as an atlas. To do so, the first thing I want to do is to call ID3D10Texture2D::Map() in order to get at the texels and parse them (to determine where my tiles are). The following call fails with E

  • UINT stride = sizeof(LINE); UINT offset = 0; device-&gt;IASetVertexBuffers(0, 1, &amp;pBuffer, &amp;stride, &amp;offset); device-&gt;IASetIndexBuffer(iBuffer, DXGI_FORMAT_R32_UINT, 0); allLines... UINT stride = sizeof(VERTEX); UINT offset = 0; device-&gt;IASetVertexBuffers(0, 1, mesh.PBuffer(), &amp;stride, &amp;offset); device-&gt;IASetIndexBuffer(mesh.IBuffer(), DXGI_FORMAT_R32_UINT, 0...); pRotation-&gt;SetMatrix(&amp;temporaryLines[i].rotation._11); // set the rotation matrix in the effect pPass-&gt;Apply(0); device-&gt;DrawIndexed(2, 0, 0); } temporaryLines.clear

  • onscreen, in the shape of the terrain rectangle, but there are no regular lines etc. Here's the code I use for rendering: void ShaderProgram::Draw() { using namespace AntiMatter; if( ! m...]; // nx, ny, nz; float colour[4]; // r, g, b, a float padding[20]; // padded for performance }; I've already written a working VertexBufferObject class that creates a vertex buffer... into a shader program, for use with glDrawArrays. My vertex data already exists in a VertexBufferObject that uses the following data structure to create a vertex buffer: class CustomVertex { public

  • ;at(index)-&gt;Kill(); } } } void DrawBullets(BITMAP* buffer, BITMAP* sprite) { for(unsigned int index = 0; index < bullets-&gt;size(); index++) { if (bullets-&gt;at(index)-&gt;Alive == true) { bullets-&gt;at(index)-&gt;Draw(buffer, sprite); } } } //Entry point of the application int main(void) { Ship* s = new Ship(); int x... it is the pad that is causing the problem. Maybe the bullet is being drawn out of the screen already. But I am not sure since I cannot debug maybe somebody can see the flaw in my code. main.cpp class

  • theScreen("ascreen"); string splashScreen[24] = { // HERE"S THE SPLASH ! // 1 2 3 4 5 6 7 8... */ "|______________________________________________________________________________|",}; string _lines_[56] = { // 1 2 3 4 5 6 7 8 // 123456789 123456789 123456789... /////////////////////////// void Refresh(int command= ALL, int transition= NONE) { //old_map = new_map; // update the old map for(int r= 0; r< nrows; r++){ move(r,0); addstr

Data information