Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

So I'm doing some DirectX development, using SharpDX under .NET to be exact (but DirectX/C++ API solutions are applicable). I'm looking for the fastest way to render lines in an orthogonal projection (e.g. simulating 2D line drawing for scientific apps) using DirectX.

A screenshot of the sorts of plots I'm trying to render follows: enter image description here

It's not uncommon for these sorts of plots to have lines with millions of segments, of variable thickness, with or without antialiasing per-line (or full screen AA on/off). I need to update the vertices for the lines very frequently (e.g. 20 times/second) and offload as much to the GPU as possible.

So far I have tried:

  1. Software rendering, e.g. GDI+ actually not bad performance but obviously is heavy on the CPU
  2. Direct2D API - slower than GDI, especially with Antialiasing on
  3. Direct3D10 using this method to emulate AA using vertex colours and tessellation on the CPU side. Also slow (I profiled it and 80% of time is spent computing vertex positions)

For the 3rd method I'm using Vertex Buffers to send a triangle strip to the GPU and updating every 200ms with new vertices. I'm getting a refresh rate of around 5FPS for 100,000 line segments. I need millions ideally!

Now I'm thinking that the fastest way would be to do the tessellation on the GPU, e.g. in a Geometry Shader. I could send the vertices as a line-list or pack in a texture and unpack in a Geometry Shader to create the quads. Or, just send raw points to a pixel shader and implement Bresenham Line drawing entirely in a pixel shader. My HLSL is rusty, shader model 2 from 2006 so I don't know about the crazy stuff modern GPUs can do.

So the question is: - has anyone done this before, and do you have any suggestions to try? - Do you have any suggestions to improve performance with rapidly updating geometry (e.g. new vertex list every 20ms)?

UPDATE 21st Jan

I have since implemented method (3) above using Geometry shaders using LineStrip and Dynamic Vertex Buffers. Now I'm getting 100FPS at 100k points and 10FPS at 1,000,000 points. This is a huge improvement but now I'm fill-rate and compute limited, so I got thinking about other techniques/ideas.

  • What about Hardware Instancing of a Line Segment geometry?
  • What about Sprite Batch?
  • What about other (Pixel shader) oriented methods?
  • Can I efficiently cull on the GPU or CPU?

Your comments & suggestions much appreciated!

share|improve this question
1  
What about native AA in Direct3D? –  API-Beast Jan 1 '13 at 13:07
4  
Can you show a few example curves that you wish to plot? You mention a million vertices but your screen probably doesn’t have much more than a million pixels, is that quantity really needed? It seems to me that you won't need that full data density everywhere. Have you considered LODs? –  Sam Hocevar Jan 1 '13 at 14:17
1  
The second approach you linked seems good, are you using a dynamic vertex buffer that you update, or do you create a new one each time? –  melak47 Jan 1 '13 at 16:20
1  
You should probably go with a dynamic vertex buffer (not much work, just tell your vertex buffer to be dynamic, map the buffer, copy your data over, unmap) but if your bottleneck is the vertex generation in the first place, that probably won't help much right now. Don't think there's anything mad about using a GS to lighten the load on the CPU –  melak47 Jan 1 '13 at 16:46
1  
If the number of vertices end up being too big for the GPU, you could still try downsampling your inputs - you don't really need hundreds of millions of line segments for a single curve when your screen is going to be a few thousand pixels wide at most. –  melak47 Jan 1 '13 at 17:13
show 6 more comments

3 Answers 3

up vote 14 down vote accepted

If you are going to render Y = f(X) graphs only, then I suggest trying the following method.

The curve data is passed as texture data, making it persistent, and allowing for partial updates through glTexSubImage2D for instance. If you need scrolling you could even implement a circular buffer and only update a few values per frame. Each curve is rendered as a fullscreen quad and all the work is done by the pixel shader.

The one-component texture contents could look like this:

+----+----+----+----+
| 12 | 10 |  5 | .. | values for curve #1
+----+----+----+----+
| 55 | 83 | 87 | .. | values for curve #2
+----+----+----+----+

The work of the pixel shader is as follows:

  • find the X coordinate of the current fragment in the dataset space
  • take eg. the 4 closest data points that have data; for instance if the X value is 41.3 it would choose 40, 41, 42 and 43.
  • query the texture for the 4 Y values (make sure the sampler does no interpolation of any kind)
  • convert the X,Y pairs to screen space
  • compute the distance from current fragment to each of the three segments and four points
  • use the distance as an alpha value for the current fragment

You may wish to substitute 4 with larger values depending on the potential zoom level.

I have written a very quick and dirty GLSL shader implementing this feature. I may add the HLSL version later, but you should be able to convert it without too much effort. The result can be seen below, with different line sizes and data densities:

curves

One clear advantage is that the amount of data transferred is very low, and the number of drawcalls is only one.

share|improve this answer
    
That's a pretty cool technique - I've done GPGPU before so packing textures with data is something I know about, but not something I considered for this. Whats the largest size (e.g. largest dataset you can upload?). Im gonna download your code if you don't mind and play around with it - I'm keen to see the performance. –  Dr. ABT Jan 1 '13 at 18:22
    
@Dr.ABT The dataset size is only limited by the maximum texture size. I don’t see why millions of points wouldn’t work given some proper data layout. Note that my code is certainly not production quality but feel free to contact me privately if there are any issues. –  Sam Hocevar Jan 1 '13 at 18:29
    
Cheers @SamHocevar - I'll give it a go. It's been a while since I compiled an OpenGL example mind! :-) –  Dr. ABT Jan 1 '13 at 19:05
    
Marking as answer, as although I'm not using texture load, you presented a real OpenGL example which could draw lines on a pixel shader and put us in the right direction!! :) –  Dr. ABT Feb 20 '13 at 21:02
add comment

There was a GPU Gems chapter on rendering antialiased lines: Fast Prefiltered Lines. The basic idea is to render each line segment as a quad and calculate, at each pixel, a Gaussian function of the pixel center's distance from the line segment.

This does mean drawing each line segment in the graph as a separate quad, but in D3D11 you could certainly use a geometry shader and/or instancing to generate the quads, reducing the amount of data to be transferred to the GPU to just the data points themselves. I'd probably set up the data points as a StructuredBuffer to be read by the vertex / geometry shader, then do a draw call specifying the number of segments to be drawn. There wouldn't be any actual vertex buffers; the vertex shader would just use SV_VertexID or SV_InstanceID to determine which data points to look at.

share|improve this answer
    
Hey thanks - yes I'm aware of that article, no source code with it unfortunately :-( The premise of GeoShader+FragShader seems to be a winner for this type of work. Getting the data to the GPU efficiently is going to be the tricky part! –  Dr. ABT Jan 3 '13 at 21:23
    
Hey @NathanReed going back to this - if I used Geometry Shader or instancing to draw quads, then Point1/Point2 are opposite vertices of a quad, how in the Pixel Shader can I calculate distance to the line between Pt1 & Pt2? Does the pixel shader have knowledge of input geometry? –  Dr. ABT Jan 21 '13 at 19:51
    
@Dr.ABT The parameters of the mathematical line have to be sent down to the pixel shader via interpolators. The pixel shader has no direct access to the geometry. –  Nathan Reed Jan 21 '13 at 19:59
    
I see, can this be done by sending outputs from the GeometryShader or instancing? For extra credit (if you're interested), I added a Q here: gamedev.stackexchange.com/questions/47831/… –  Dr. ABT Jan 21 '13 at 20:20
    
@Dr.ABT It's exactly the same way that texture coordinates, normal vectors, and all that kind of stuff are normally sent to the pixel shader - by writing outputs from the vertex/geometry shader that are interpolated and input to the pixel shader. –  Nathan Reed Jan 21 '13 at 20:27
add comment

@Dr.ABT - Apologies for this is a question, not an answer. I did not find a way to ask it within the answer by Sam Hocevar above.

Could you share more details on how you finally implemented the lines for your chart? I have the same need for a WPF app. Thanks in advance for any details or code you can share on this.

share|improve this answer
    
as you know its not an answer please edit and put it as a comment –  MephistonX Mar 17 '13 at 16:42
    
I was trying to do that but there is no "add comment" button on the original post. –  ErcGeek Mar 18 '13 at 18:37
    
Hi ErcGeek, Sorry I'm unable to share the final solution as this information is proprietary to my company. Although the examples and suggestions above put us on the right track. All the best! –  Dr. ABT Sep 19 '13 at 9:14
    
You can't post comments before you reach a certain amount of reputation. And all these downvotes sure won't provide you with that opportunity. –  Mathias Lykkegaard Lorenzen Dec 28 '13 at 18:45
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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