0
\$\begingroup\$

I have several draw functions in my renderer to draw primitives e.g.:

Drawing a Quad:

void Renderer::DrawQuad2D(float left, float bottom, float right, float top, const Rgba& color /*= Rgba::WHITE*/, const Vector4& texCoords /*= Vector4::ZW_AXIS*/) noexcept {
    Vector3 v_lb = Vector3(left, bottom, 0.0f);
    Vector3 v_rt = Vector3(right, top, 0.0f);
    Vector3 v_lt = Vector3(left, top, 0.0f);
    Vector3 v_rb = Vector3(right, bottom, 0.0f);
    Vector2 uv_lt = Vector2(texCoords.x, texCoords.y);
    Vector2 uv_lb = Vector2(texCoords.x, texCoords.w);
    Vector2 uv_rt = Vector2(texCoords.z, texCoords.y);
    Vector2 uv_rb = Vector2(texCoords.z, texCoords.w);
    std::vector<Vertex3D> vbo = {
        Vertex3D(v_lb, color, uv_lb)
        ,Vertex3D(v_lt, color, uv_lt)
        ,Vertex3D(v_rt, color, uv_rt)
        ,Vertex3D(v_rb, color, uv_rb)
    };
    std::vector<unsigned int> ibo = {
        0, 1, 2
        , 0, 2, 3
    };
    DrawIndexed(PrimitiveType::Triangles, vbo, ibo);

}

Sending the verts and indexes to the GPU

void Renderer::DrawIndexed(const PrimitiveType& topology, const std::vector<Vertex3D>& vbo, const std::vector<unsigned int>& ibo) noexcept {
    UpdateVbo(vbo);
    UpdateIbo(ibo);
    DrawIndexed(topology, _temp_vbo.get(), _temp_ibo.get(), ibo.size());
}

Setup and call DrawIndex on immediate context.

void Renderer::DrawIndexed(const PrimitiveType& topology, VertexBuffer* vbo, IndexBuffer* ibo, std::size_t index_count, std::size_t startVertex /*= 0*/, std::size_t baseVertexLocation /*= 0*/) noexcept {
    GUARANTEE_OR_DIE(_current_material, "Attempting to call Draw function without a material set!\n");
    D3D11_PRIMITIVE_TOPOLOGY d3d_prim = PrimitiveTypeToD3dTopology(topology);
    _rhi_context->GetDxContext()->IASetPrimitiveTopology(d3d_prim);
    unsigned int stride = sizeof(VertexBuffer::arraybuffer_t);
    unsigned int offsets = 0;
    ID3D11Buffer* dx_vbo_buffer = vbo->GetDxBuffer();
    ID3D11Buffer* dx_ibo_buffer = ibo->GetDxBuffer();
    _rhi_context->GetDxContext()->IASetVertexBuffers(0, 1, &dx_vbo_buffer, &stride, &offsets);
    _rhi_context->GetDxContext()->IASetIndexBuffer(dx_ibo_buffer, DXGI_FORMAT_R32_UINT, offsets);
    _rhi_context->DrawIndexed(index_count, startVertex, baseVertexLocation);
}

The most noticeable problem is each function issues a draw call. Doing this enough per frame (about 100) causes massive lag. How would I implement a batching such that each of these functions, instead of emitting a draw call immediately, collects all the verts/indexes and doesn't draw until the end of the frame?

\$\endgroup\$
0
\$\begingroup\$

A few things come to mind when I look at your code.

  1. Setting up your draw context should be done once at the start before you start executing draw calls.
  2. Consider using Instancing to reduce your call count down (does require more set up).
  3. Small vertex sets in a draw call will mean that the GPU Wavefront will not be filled completely. If you draw say 6 vertices and the GPU can handle on its wave front say 64 vertices per wave front then you leave a lot of waste. Having said that, it shouldn't be an issue here for say 100 draw call.
  4. If you have a slow CPU but it has multiple cores, look up deferred contexts and multi threading. This method in DX 11 allows the GPU graphics driver to order the draw calls more readily.
  5. If you have lots of different vertex buffers, pack them into 1 Vertex buffer, and call the drawindexed with the offsets of each vertex group. (say object 1 starts at Vertex 0, object 2 starts at 300, if you want to draw object 2, you make the draw call with the vertex offset at 300).

In general, try not switch the state of the GPU everytime, because this causes microstalls because it has to flush the entire pipeline on occassion to switch state. Now this isn't a problem on later GPUs, they can localise flushing of state in the pipeline. But you should try in general to reduce this.

If you need examples for say for packing multiple models into one vertex buffer, I will add them as a follow up. But in general, batching can be achieved through instancing (in regards to your original question) or through using a deferred context if you want to continue using discreet calls per object.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.