Jump to content
  • Advertisement
Sign in to follow this  
AlanMcCormick

Fastest way to draw connected lines?

This topic is 2634 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am working on an application in C# .NET. In the application we need to be able to graph data being received as fast as possible. I've been looking into different technologies to determine the most efficient way to draw the graphs which are simply a connected series of lines. I was experimenting with Direct2d and using SharpDX (or SlimDX) to integrate the DirectX into my application. However, it does not seem as if the line drawing code that I am experimenting with is being hardware accelerated. I am only getting around 25 frames per second, drawing a few hundred lines on a panel in the .NET application using Direct2d. Also, the GDI code I wrote renders the lines about 4 times faster than the direct2d calls.

Here's what I've tried to render the lines.. in the most basic way. Is this being hardware accelerated? (I have 2 cards, one is much better than the other, both render same amount of times and hit the CPU hard) I feel like I should get more than 25 frames per second on a panel that is less than 1000 pixels wide (in this example less than 1000 lines)


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SharpDX.Direct2D1;
using SharpDX.DXGI;
using System.Diagnostics;
using SharpDX.Direct3D10;
using Device = SharpDX.Direct3D10.Device;
using Device1 = SharpDX.Direct3D10.Device1;
using DriverType = SharpDX.Direct3D10.DriverType;
using FeatureLevel = SharpDX.Direct3D10.FeatureLevel;
using Factory = SharpDX.Direct2D1.Factory;
using SharpDX;

namespace GraphDrawingTests
{
public partial class GraphDrawingDialog : Form
{
PointF[][] pts;
Factory factory;
WindowRenderTarget renderTarget;
Device1 device;
SwapChain swapChain;
Texture2D backBuffer;
RenderTargetView backBufferView;

public GraphDrawingDialog()
{
InitializeComponent();

Random r = new Random();
pts = new PointF[10][];

for (int i = 0; i < pts.Length; i++)
{
pts = new PointF[panel1.Width];
for (int k = 0; k < pts.Length; k++)
{
pts[k] = new PointF(k, r.Next(0, panel1.Height));
}
}

/*
* Confused on why I woul dneed to do this?
*
* SwapChainDescription desc = new SwapChainDescription()
{
BufferCount = 1,
ModeDescription =
new ModeDescription(panel1.Width, panel1.Height,
new Rational(60, 1), Format.R8G8B8A8_UNorm),
IsWindowed = true,
OutputHandle = panel1.Handle,
SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard,
Usage = Usage.RenderTargetOutput
};
SharpDX.Direct3D10.Device1.CreateWithSwapChain(DriverType.Hardware,
DeviceCreationFlags.Debug | DeviceCreationFlags.BgraSupport,
desc,
SharpDX.Direct3D10.FeatureLevel.Level_10_0,
out device,
out swapChain);
factory = swapChain.GetParent<Factory>();
factory.MakeWindowAssociation(panel1.Handle, WindowAssociationFlags.IgnoreAll);
backBuffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0);
backBufferView = new RenderTargetView(device, backBuffer);
*/

factory = new SharpDX.Direct2D1.Factory(FactoryType.SingleThreaded);
renderTarget = new WindowRenderTarget(
factory,
new RenderTargetProperties(),
new HwndRenderTargetProperties()
{
Hwnd = panel1.Handle,
PixelSize = new System.Drawing.Size(panel1.Width, panel1.Height)
});
}

protected override void OnClosed(EventArgs e)
{
renderTarget.Dispose();
factory.Dispose();

base.OnClosed(e);
}

private void button1_Click(object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();

int frames = 0;
while (sw.ElapsedMilliseconds < 2000)
{
PathGeometry geometry = new PathGeometry(factory);
GeometrySink sink = geometry.Open();

sink.BeginFigure(new PointF(0, 0), new FigureBegin());
sink.AddLines(pts[frames % 10]);
sink.EndFigure(new FigureEnd());
sink.Close();

renderTarget.BeginDraw();
renderTarget.Clear(new SharpDX.Color4(255, 0, 0, 0));
SolidColorBrush brush = new SolidColorBrush(renderTarget, new SharpDX.Color4(255, 255, 255, 255));
renderTarget.DrawGeometry(geometry, brush);
brush.Dispose();
renderTarget.EndDraw();
geometry.Dispose();
frames++;
}

MessageBox.Show("D2D Frames: " + frames);
}

private void button2_Click(object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();

int frames = 0;
while (sw.ElapsedMilliseconds < 2000)
{
Graphics gfx = panel1.CreateGraphics();
gfx.Clear(Color.Black);
Pen p = new Pen(Color.White);
gfx.DrawLines(p, pts[frames % 10]);
p.Dispose();
gfx.Dispose();
frames++;
}

MessageBox.Show("GDI Frames: " + frames);
}
}
}

Share this post


Link to post
Share on other sites
Advertisement
While it certainly sounds as if perf is non-optimal, if this graph is to be perceived and acted upon by humans, we've all got about 2/10ths of a second of inbuilt reaction time anyway, and 5 frames have gone by already in that time, even in your "slow" graph code. You might want to evaluate whether this is a real problem that affects results, or a perceived one which is only irking you because you know/feel performance should be better.

Also, unless you've specifically profiled the graph-drawing code and found the lines to be the culprit, then its probably more likely that something else is the problem, such as clearing the surface that the lines appear on.

Share this post


Link to post
Share on other sites
A Hhwnd target probably won't be hardware accelerated, you need a DXGI render target for that. Get the back-buffer D3D10.1 surface and use that to create a DXGI D2D render target. Not sure if that's your problem though, as 25 FPS with a few hundred lines sounds low even for software.. but could be.

Share this post


Link to post
Share on other sites

A Hhwnd target probably won't be hardware accelerated, you need a DXGI render target for that. Get the back-buffer D3D10.1 surface and use that to create a DXGI D2D render target. Not sure if that's your problem though, as 25 FPS with a few hundred lines sounds low even for software.. but could be.


I'll give that a try. One thing about the lines was they were from random data.. If you had a smoother curve or something of lines.. it would be less up and down and probably faster.

It isn't a perceived problem. I may be getting this data from multiple devices in real time and graphing it to compare the relative values. If I can only draw 1 line 25 times a second, then if I add a bunch of lines to the same graph, with other information and grid lines etc. etc. it could become sluggish especially while panning the graph. I would also expect it to run faster than GDI.

Share this post


Link to post
Share on other sites

A Hhwnd target probably won't be hardware accelerated, you need a DXGI render target for that. Get the back-buffer D3D10.1 surface and use that to create a DXGI D2D render target. Not sure if that's your problem though, as 25 FPS with a few hundred lines sounds low even for software.. but could be.


I'm having trouble figuring out how to do this... can you help me? I tried creating a RenderTarget and passing in a surface.. but I'm not sure if I created it correctly.. do I need two factories? one for DXGI and one for D2D? Also, my code didn't crash, but also didn't display anywhere.

Share this post


Link to post
Share on other sites
First you need to create a D3D10.1 device as usual (on any feature level). Then get the back-buffer DXGI surface and pass it to D2D when creating a DXGI D2D render target. Then D2D will use the D3D device to draw with hardware acceleration behind the scenes, and you show the image by calling Present on the D3D device.

Share this post


Link to post
Share on other sites
Thanks Erik. Here's my code. I got it to work and display... I get 46 frames in 2 seconds (23 FPS on a GTX 560 with an i7) My panel is 818x400 and therefore I'm drawing 818 lines one per each X-value with a random y-value between 0-400.

I still am not getting a frame rate that I would consider good. I watched my CPU usage on the process and it was using 13% (on a 4 core (8 with hyperthreading) processor) 100 / 8 = 12.5.... I wonder is it still rendering this on the CPU? Is it possible that some sort of transfer from-to video memory and back is responsible? Is my code wrong?

The code seems pretty straight forward, but I've never tried this before...

public partial class GraphDrawingDialog : Form
{
// general
PointF[][] pts;

// SharpDX
SharpDX.DXGI.Factory dxgiFactory;
SharpDX.DXGI.SwapChain swapChain_SharpDX;
SharpDX.Direct2D1.Factory d2dFactory;
SharpDX.Direct2D1.RenderTarget renderTarget;
SharpDX.Direct3D10.Device1 deviceSharpDX;
SharpDX.Direct3D10.Texture2D backBuffer;
SharpDX.Direct3D10.RenderTargetView backBufferView;

public GraphDrawingDialog()
{
InitializeComponent();

Random r = new Random();
pts = new PointF[10][];

for (int i = 0; i < pts.Length; i++)
{
pts = new PointF[panel1.Width];
for (int k = 0; k < pts.Length; k++)
{
pts[k] = new PointF(k, r.Next(0, panel1.Height));
}
}

SharpDXInit();
}

private void SharpDXInit()
{
SharpDX.DXGI.SwapChainDescription desc = new SharpDX.DXGI.SwapChainDescription()
{
BufferCount = 1,
ModeDescription = new SharpDX.DXGI.ModeDescription(
panel1.Width,
panel1.Height,
new SharpDX.DXGI.Rational(60, 1), SharpDX.DXGI.Format.R8G8B8A8_UNorm),
IsWindowed = true,
OutputHandle = panel1.Handle,
SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
SwapEffect = SharpDX.DXGI.SwapEffect.Discard,
Usage = SharpDX.DXGI.Usage.RenderTargetOutput
};
SharpDX.Direct3D10.Device1.CreateWithSwapChain(
SharpDX.Direct3D10.DriverType.Hardware,
SharpDX.Direct3D10.DeviceCreationFlags.Debug | SharpDX.Direct3D10.DeviceCreationFlags.BgraSupport,
desc,
SharpDX.Direct3D10.FeatureLevel.Level_10_0,
out deviceSharpDX,
out swapChain_SharpDX);
dxgiFactory = swapChain_SharpDX.GetParent<SharpDX.DXGI.Factory>();
//dxgiFactory.MakeWindowAssociation(panel1.Handle, WindowAssociationFlags.IgnoreAll);
backBuffer = SharpDX.Direct3D10.Texture2D.FromSwapChain<SharpDX.Direct3D10.Texture2D>(swapChain_SharpDX, 0);
backBufferView = new SharpDX.Direct3D10.RenderTargetView(deviceSharpDX, backBuffer);

SharpDX.DXGI.Surface surface = backBuffer.AsSurface();
d2dFactory = new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.SingleThreaded);

renderTarget = new SharpDX.Direct2D1.RenderTarget(
d2dFactory, surface, new SharpDX.Direct2D1.RenderTargetProperties()
{
PixelFormat = new SharpDX.Direct2D1.PixelFormat(
SharpDX.DXGI.Format.Unknown,
SharpDX.Direct2D1.AlphaMode.Premultiplied),
}
);


/*renderTarget = new WindowRenderTarget(
factory,
new RenderTargetProperties(),
new HwndRenderTargetProperties()
{
Hwnd = panel1.Handle,
PixelSize = new System.Drawing.Size(panel1.Width, panel1.Height)
});*/
}

protected override void OnClosed(EventArgs e)
{
renderTarget.Dispose();
d2dFactory.Dispose();
dxgiFactory.Dispose();
base.OnClosed(e);
}

private void button1_Click(object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();

int frames = 0;
while (sw.ElapsedMilliseconds < 10000)
{
SharpDX.Direct2D1.PathGeometry geometry = new SharpDX.Direct2D1.PathGeometry(d2dFactory);
SharpDX.Direct2D1.GeometrySink sink = geometry.Open();

sink.BeginFigure(new PointF(0, 0), new SharpDX.Direct2D1.FigureBegin());
sink.AddLines(pts[frames % 10]);
sink.EndFigure(new SharpDX.Direct2D1.FigureEnd());
sink.Close();

renderTarget.BeginDraw();
renderTarget.Clear(new SharpDX.Color4(255, 0, 0, 0));
SharpDX.Direct2D1.SolidColorBrush brush = new SharpDX.Direct2D1.SolidColorBrush(
renderTarget,
new SharpDX.Color4(255, 255, 255, 255));
renderTarget.DrawGeometry(geometry, brush);
brush.Dispose();
renderTarget.EndDraw();
geometry.Dispose();
frames++;
swapChain_SharpDX.Present(1, SharpDX.DXGI.PresentFlags.None);
}

MessageBox.Show("D2D Frames: " + frames);
}

private void button2_Click(object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();

int frames = 0;
while (sw.ElapsedMilliseconds < 2000)
{
Graphics gfx = panel1.CreateGraphics();
gfx.Clear(Color.Black);
Pen p = new Pen(Color.White);
gfx.DrawLines(p, pts[frames % 10]);
p.Dispose();
gfx.Dispose();
frames++;
}

MessageBox.Show("GDI Frames: " + frames);
}
}

Share this post


Link to post
Share on other sites
I see you create and dispose brushes and geometry each frame, which is not a good idea. Try creating all resources outside the loop, and re-use them with different data. This is especially important for expensive resources (which D2D resources created by a DLL or in a driver or similar can often be).

EDIT: I ran a small test (in C++, but should not make a difference), and creating color brushes seems essentially free. However, still try putting the path creation outside the loop and run a test drawing the same geometry over and over again. This should help you pinpoint where the problem is. If the rendering is slow then you shouldn't get much better performance, but if the geometry creation is slow then you will get a lot higher framerate.
I tried a simple DrawLine to draw 1000 random lines each frame and got about 250 FPS. My system is similar to yours.

I've never tried SharpDX, but your render target creation seems to use the same method that I use.

Share this post


Link to post
Share on other sites

I see you create and dispose brushes and geometry each frame, which is not a good idea. Try creating all resources outside the loop, and re-use them with different data. This is especially important for expensive resources (which D2D resources created by a DLL or in a driver or similar can often be).

EDIT: I ran a small test (in C++, but should not make a difference), and creating color brushes seems essentially free. However, still try putting the path creation outside the loop and run a test drawing the same geometry over and over again. This should help you pinpoint where the problem is. If the rendering is slow then you shouldn't get much better performance, but if the geometry creation is slow then you will get a lot higher framerate.
I tried a simple DrawLine to draw 1000 random lines each frame and got about 250 FPS. My system is similar to yours.

I've never tried SharpDX, but your render target creation seems to use the same method that I use.


I had read somewhere that creating the brushes is very minimal.. but wasn't sure about the path geometry. I modified it and still get 24 or 25 FPS.


private void button1_Click(object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();

SharpDX.Direct2D1.PathGeometry geometry = new SharpDX.Direct2D1.PathGeometry(d2dFactory_SharpDX);
SharpDX.Direct2D1.GeometrySink sink = geometry.Open();

sink.BeginFigure(new PointF(0, 0), new SharpDX.Direct2D1.FigureBegin());
sink.AddLines(pts[0]);
sink.EndFigure(new SharpDX.Direct2D1.FigureEnd());
sink.Close();

SharpDX.Direct2D1.SolidColorBrush brush = new SharpDX.Direct2D1.SolidColorBrush(
renderTarget_SharpDX,
new SharpDX.Color4(255, 255, 255, 255));

int frames = 0;
while (sw.ElapsedMilliseconds < 1000)
{
renderTarget_SharpDX.BeginDraw();
renderTarget_SharpDX.Clear(new SharpDX.Color4(255, 0, 0, 0));
renderTarget_SharpDX.DrawGeometry(geometry, brush);
renderTarget_SharpDX.EndDraw();

frames++;
swapChain_SharpDX.Present(0, SharpDX.DXGI.PresentFlags.None);
}

brush.Dispose();
geometry.Dispose();

MessageBox.Show("D2D Frames: " + frames);
}

Share this post


Link to post
Share on other sites

[quote name='Erik Rufelt' timestamp='1317227860' post='4866853']
I see you create and dispose brushes and geometry each frame, which is not a good idea. Try creating all resources outside the loop, and re-use them with different data. This is especially important for expensive resources (which D2D resources created by a DLL or in a driver or similar can often be).

EDIT: I ran a small test (in C++, but should not make a difference), and creating color brushes seems essentially free. However, still try putting the path creation outside the loop and run a test drawing the same geometry over and over again. This should help you pinpoint where the problem is. If the rendering is slow then you shouldn't get much better performance, but if the geometry creation is slow then you will get a lot higher framerate.
I tried a simple DrawLine to draw 1000 random lines each frame and got about 250 FPS. My system is similar to yours.

I've never tried SharpDX, but your render target creation seems to use the same method that I use.


I had read somewhere that creating the brushes is very minimal.. but wasn't sure about the path geometry. I modified it and still get 24 or 25 FPS.


private void button1_Click(object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();

SharpDX.Direct2D1.PathGeometry geometry = new SharpDX.Direct2D1.PathGeometry(d2dFactory_SharpDX);
SharpDX.Direct2D1.GeometrySink sink = geometry.Open();

sink.BeginFigure(new PointF(0, 0), new SharpDX.Direct2D1.FigureBegin());
sink.AddLines(pts[0]);
sink.EndFigure(new SharpDX.Direct2D1.FigureEnd());
sink.Close();

SharpDX.Direct2D1.SolidColorBrush brush = new SharpDX.Direct2D1.SolidColorBrush(
renderTarget_SharpDX,
new SharpDX.Color4(255, 255, 255, 255));

int frames = 0;
while (sw.ElapsedMilliseconds < 1000)
{
renderTarget_SharpDX.BeginDraw();
renderTarget_SharpDX.Clear(new SharpDX.Color4(255, 0, 0, 0));
renderTarget_SharpDX.DrawGeometry(geometry, brush);
renderTarget_SharpDX.EndDraw();

frames++;
swapChain_SharpDX.Present(0, SharpDX.DXGI.PresentFlags.None);
}

brush.Dispose();
geometry.Dispose();

MessageBox.Show("D2D Frames: " + frames);
}

[/quote]

I would recommend moving sw.Start() to just before the while loop since time will elapsed during your setup before the loop (though I can't gaurantee how much that will help)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!