1
\$\begingroup\$

I have a buffer of about 134 million particles and a very powerful computer to render them smoothly, but I am getting an error when trying to render them as primitive lines. It says that I cannot render more than around 1 million. Is there a way I can do this? Is there a better way to render this other than with lines? I'm comfortable with having 1 pixel points or anything as long as the vertices are shown all the time. I'm basically just plotting the points.

\$\endgroup\$
3
  • \$\begingroup\$ I can't imagine a situation where you'd need 134 million separate particles. I mean, that's WAY more pixels than the average monitor even has. Far fewer particles would likely be visually indistinguishable from what you're using. \$\endgroup\$
    – House
    Mar 28 '12 at 20:55
  • \$\begingroup\$ @Byte56 it's not a stretch of imagination to assume that he is doing a research project (with a dataset that size) and that he does have access to at least a 13.5 megapixel screen. Or he is trying to do 'poor man's voxels'. \$\endgroup\$ Apr 25 '12 at 8:43
  • \$\begingroup\$ @JonathanDickinson Well then I suppose I can imagine it now. \$\endgroup\$
    – House
    Apr 25 '12 at 14:12
3
\$\begingroup\$

David indicated that you can likely do this by batching these into multiple draw calls. I assume (from experience using 16-bit index buffers) that XNA/your GPU won't care if the actual buffer (array) is much larger than what you are asking it to draw.

Your wireframe will likely be a LineStrip - which according to the documentation is an interconnected set of points.

I made some extension methods that will deal with the batching for you, requiring minimal code changes (just replace DrawUserPrimitives with DrawMassUserPrimitives). This may need tweaking as I haven't actually tested it (especially the calculations - LineStrip is correct though). In addition, if you need the other Draw* calls that has been left as an exercise for the student.

public static class GraphicsDeviceExtensions
{
    private const int MaxPrimitivesHiDef = 1048575;
    private const int MaxPrimitivesReach = 65535;

    // Given the number of primitives, calculate the number of vertices
    // required to create that number of primitives.
    private static readonly Func<int, int>[] CalculateVertices = new Func<int, int>[]
    {
        // TriangleList
        x => x * 3, 
        // TriangleStrip
        x => x + 2, 
        // LineList
        x => x * 2,
        // LineStrip
        x => x + 1
    };

    // Inverse of the above.
    private static readonly Func<int, int>[] CalculatePrimitives = new Func<int, int>[]
    {
        // TriangleList
        x => x / 3,
        // TriangleStrip
        x => x - 2, 
        // LineList
        x => x / 2,
        // LineStrip
        x => x - 1
    };

    public static void DrawMassUserPrimitives<T>(this GraphicsDevice device, PrimitiveType primitiveType, T[] vertexData, int vertexOffset, int primitiveCount) 
        where T : struct, IVertexType
    {
        DrawMass(device, primitiveType, vertexOffset, primitiveCount, (batchVertexOffset, batchPrimitiveCount) =>
        {
            device.DrawUserPrimitives<T>(primitiveType, vertexData, batchVertexOffset, batchPrimitiveCount);
        });
    }

    public static void DrawMassUserPrimitives<T>(this GraphicsDevice device, PrimitiveType primitiveType, T[] vertexData, int vertexOffset, int primitiveCount, VertexDeclaration vertexDeclaration) 
        where T : struct
    {
        DrawMass(device, primitiveType, vertexOffset, primitiveCount, (batchVertexOffset, batchPrimitiveCount) =>
        {
            device.DrawUserPrimitives<T>(primitiveType, vertexData, batchVertexOffset, batchPrimitiveCount, vertexDeclaration);
        });
    }

    private static void DrawMass(GraphicsDevice device, PrimitiveType primitiveType, int vertexOffset, int primitiveCount, Action<int, int> draw)
    {
        // Grab the calculation functions for our primitive type.
        var calculateVertices = CalculateVertices[(int)primitiveType];
        var calculatePrimitives = CalculatePrimitives[(int)primitiveType];

        // Work out:

        // The maximum number of primitives our profile supports.
        var maxPrimitives = device.GraphicsProfile == GraphicsProfile.HiDef ? MaxPrimitivesHiDef : MaxPrimitivesReach;
        // How many vertices would be rendered for that number of primitives.
        var maxVertices = calculateVertices(maxPrimitives);
        // The number of vertices the user wants to draw.
        var vertexCount = calculateVertices(primitiveCount);
        // The index of the last vertex the user wants to draw.
        var endVertex = vertexOffset + vertexCount;

        // Go over the range in steps of 'maxVertices'.
        for (var i = vertexOffset; i < endVertex; i += maxVertices)
        {
            // Make sure we don't draw too many vertices.
            var batchEnd = i + maxVertices;
            if (batchEnd > endVertex)
                batchEnd = endVertex;
            var batchCount = batchEnd - i;

            // Work out the number of primitives again.
            var batchPrimitives = calculatePrimitives(batchCount);

            // Ask the calling method to draw what we have worked out.
            draw(i, batchPrimitives);
        }
    }
}
\$\endgroup\$
7
  • \$\begingroup\$ Cool stuff Jonathan! :) \$\endgroup\$ Apr 25 '12 at 17:10
  • \$\begingroup\$ @DavidGouveia haven't seen you around here in a while :). Sorry to snipe your answer; but I recently learnt that adding information to an answer is discouraged :(. \$\endgroup\$ Apr 25 '12 at 18:53
  • \$\begingroup\$ No problem :) And as usual, very elegant solution :P \$\endgroup\$ Apr 25 '12 at 21:31
  • \$\begingroup\$ I tried it, and it freezes, even with a smaller data. I don't quite understand what is wrong \$\endgroup\$
    – luis
    Apr 28 '12 at 23:01
  • \$\begingroup\$ I think the problem is that I am using DrawPrimitives instead of DrawUserPrimitives because the data does not change at all, and I would like to keep it in the GPU for faster usage and rendering, but your example uses DrawUserPrimitives which doesn't use a VertexBuffer, instead it copies all the data everytime it renders, which is the reason why it is so slow in this case. Do you have an idea for an approach using DrawPrimitives instead, I tried something but I am running into problems that VertexBuffer can only handle a limited number of items and 137 million is too much for it \$\endgroup\$
    – luis
    Apr 29 '12 at 7:29
2
\$\begingroup\$

The maximum number of primitives that you can draw with a single call in XNA while using the HiDef profile is 1048575 according to this article.

I've never tried it but since that limit is defined per draw call I think you should be able to draw more primitives by using separate draw calls. All of the DrawPrimitives type of methods take an offset and count into the buffer (e.g. startVertex and primitiveCount) so you can play with those values to draw different portions of the buffer in each call and keep incrementing the offset until everything is drawn.

You can also take a look into the PrimitiveBatch class available in this sample as it's pretty easy to use. Just increase the buffer size inside the class first. And while you're at it check this one too.

\$\endgroup\$
4
  • \$\begingroup\$ what about the wireframe sort of thing so that I can show only the vertices (joins) of the lines made by all the plotting points \$\endgroup\$
    – luis
    Mar 24 '12 at 2:43
  • \$\begingroup\$ How did we go from particles (usually represented as points) to vertices and wire-frames which are ways to represent meshes? \$\endgroup\$ Mar 25 '12 at 0:09
  • \$\begingroup\$ @ClassicThunder I think this might be some form of a research project. \$\endgroup\$ Apr 25 '12 at 8:05
  • \$\begingroup\$ it is a research project \$\endgroup\$
    – luis
    Apr 29 '12 at 8:51

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.