Drawing Graphs with MonoGame in WPF is flickering

Hi there,

I have to draw some graphs in my WPF application. With MonoGame I finally found a solution that is performant enough to achieve this. To use MonoGame with WPF I use MonoGame.WpfCore.

But when I started testing, I found out, that it is flickering on some computers/screens. I have not yet been able to find out exactly when this is the case (external GPU, monitor, connection to the monitor).

It should look like this:

But when flickering it looks something like this:

It looks like the drawing just stops at some point. I checked the Draw() method in my view model, but it is always executed to the end. It sounds like it is a similar problem to the one here: https://github.com/craftworkgames/MonoGame.WpfCore/issues/2.

My question now is if I am making a mistake, or maybe there is a bug here. I tested two ways of drawing. But both show the same flickering.

GraphicsDevice.DrawUserPrimitives()

One way to draw my graphs, i found, was to use GraphicsDevice.DrawUserPrimitives():

   public class MainWindowViewModel : MonoGameViewModel
   {
      public override void LoadContent()
      {
         Random rand = new Random();
         for (int g = 0; g < _graphsCount; g++)
            _colors[g] = new Color((float)rand.NextDouble(), (float)rand.NextDouble(), (float)rand.NextDouble());
         for (int i = 0; i < _values.Length; i++)
            _values[i] = rand.Next(0, _valueRange);

         _graphVertices = new VertexPositionColor[_graphsCount][];
         CreateGraphVertices();

         _cursorVertices = new VertexPositionColor[2];

         _basicEffect = new BasicEffect(GraphicsDevice) { VertexColorEnabled = true };
         _basicEffect.World = Matrix.CreateOrthographicOffCenter(
             0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, 0, 0, 1);
      }

      private const int _graphsCount = 50;
      private Color[] _colors = new Color[_graphsCount];

      private const int _valueRange = 100;
      private int[] _values = new int[2000];

      private VertexPositionColor[] _cursorVertices;
      private VertexPositionColor[][] _graphVertices;
      private BasicEffect _basicEffect;

      public override void SizeChanged(object sender, SizeChangedEventArgs args)
      {
         _width = (int)args.NewSize.Width;
         _height = (int)args.NewSize.Height;

         if (_basicEffect != null)
         _basicEffect.World = Matrix.CreateOrthographicOffCenter(
             0, (int)_width, (int)_height, 0, 0, 1);

         if (_graphVertices != null)
            CreateGraphVertices();
      }

      private double _width = 100;
      private double _height = 100;

      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      private void CreateGraphVertices()
      {
         for (int g = 0; g < _graphsCount; g++)
         {
            _graphVertices[g] = new VertexPositionColor[_values.Length * 2 - 1];
            for (int i = 0; i < _graphVertices[g].Length; i += 2)
            {
               int valueIndex = i / 2;
               int x1 = GetX(valueIndex);
               int y1 = GetY(valueIndex, g);
               _graphVertices[g][i] = new VertexPositionColor(new Vector3(x1, y1, 0), _colors[g]);

               if (i + 1 < _graphVertices[g].Length)
               {
                  int x2 = GetX(valueIndex + 1);
                  _graphVertices[g][i + 1] = new VertexPositionColor(new Vector3(x2, y1, 0), _colors[g]);
               }
            }
         }
      }

      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      public override void Update(GameTime gameTime)
      {
         // Update cursor.
         _cursorPos++;
         if (_cursorPos > _width)
            _cursorPos = 1;
         _cursorVertices = new[]
         {
             new VertexPositionColor(new Vector3(_cursorPos, 0, 0), Color.White),
             new VertexPositionColor(new Vector3(_cursorPos, GraphicsDevice.Viewport.Height, 0), Color.White)
         };
      }

      private int _cursorPos = 1;

      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      public override void Draw(GameTime gameTime)
      {
         GraphicsDevice.Clear(Color.Black);

         EffectTechnique effectTechnique = _basicEffect.Techniques[0];
         EffectPassCollection effectPassCollection = effectTechnique.Passes;
         foreach (EffectPass pass in effectPassCollection)
         {
            pass.Apply();
            for (int g = 0; g < _graphsCount; ++g)
               GraphicsDevice.DrawUserPrimitives(PrimitiveType.LineStrip, _graphVertices[g], 0, _graphVertices[g].Length - 1);
            GraphicsDevice.DrawUserPrimitives(PrimitiveType.LineStrip, _cursorVertices, 0, 1);
         }
      }

      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      private int GetX(int valueIndex)
         => (int)(_width / _values.Length * valueIndex);


      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      private int GetY(int valueIndex, int graphIndex)
      {
         return (int)(_height / _graphsCount * graphIndex
            + _height / _graphsCount / _valueRange * _values[valueIndex]);
      }
   }

SpriteBatch.Draw()

I also tried to draw my graphs with a sprite batch:

   public class MainWindowViewModel : MonoGameViewModel
   {
      private SpriteBatch _spriteBatch;

      public override void LoadContent()
      {
         Random rand = new Random();
         for (int g = 0; g < _graphsCount; ++g)
            _colors[g] = new Color((float)rand.NextDouble(), (float)rand.NextDouble(), (float)rand.NextDouble());
         for (int i = 0; i < _values.Length; i++)
            _values[i] = rand.Next(0, _valueRange);

         _spriteBatch = new SpriteBatch(GraphicsDevice);
      }

      private const int _graphsCount = 50;
      private Color[] _colors = new Color[_graphsCount];

      private const int _valueRange = 100;
      private int[] _values = new int[1000];

      public override void SizeChanged(object sender, SizeChangedEventArgs args)
      {
         _width = (int)args.NewSize.Width;
         _height = (int)args.NewSize.Height;
      }

      private double _width = 100;
      private double _height = 100;

      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      public override void Update(GameTime gameTime)
      {
         // Update cursor.
         _cursorPos++;
         if (_cursorPos > _width)
            _cursorPos = 1;
      }

      private int _cursorPos = 1;

      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      public override void Draw(GameTime gameTime)
      {
         GraphicsDevice.Clear(Color.Black);

         _spriteBatch.Begin();

         // Draw graphs.
         for (int g = 0; g < _graphsCount; ++g)
         {
            for (int i = 0; i < _values.Length - 1; i++)
            {
               int x1 = GetX(i);
               int y1 = GetY(i, g);

               // Draw lines to next value.
               if (i + 1 < _values.Length)
               {
                  int x2 = GetX(i + 1);
                  int y2 = GetY(i + 1, g);
                  _spriteBatch.DrawHorizontalLine(x1, y1, x2 - x1 + 1, _colors[g], 1);
                  _spriteBatch.DrawVerticalLine(x2, y2, y1 - y2, _colors[g], 1);
               }
            }
         }

         // Draw cursor.
         _spriteBatch.DrawVerticalLine(_cursorPos, 0, (int)_height, Color.White, 1);

         _spriteBatch.End();
      }

      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      private int GetX(int valueIndex)
         => (int)(_width / _values.Length * valueIndex);


      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      private int GetY(int valueIndex, int graphIndex)
      {
         return (int)(_height / _graphsCount * graphIndex
            + _height / _graphsCount / _valueRange * _values[valueIndex]);
      }
   }

   public static class Lines
   {
      private static Texture2D _pixelTexture;

      private static void CreateTexture(SpriteBatch spriteBatch)
      {
         _pixelTexture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
         _pixelTexture.SetData(new[] { Color.White });
      }

      public static void DrawHorizontalLine(this SpriteBatch spriteBatch, int x, int y, int length, Color color, int thickness)
      {
         if (_pixelTexture == null)
            CreateTexture(spriteBatch);

         spriteBatch.Draw(_pixelTexture, new Vector2(x, y), null, color, 0, Vector2.Zero, new Vector2(length, thickness), SpriteEffects.None, 0);
      }

      public static void DrawVerticalLine(this SpriteBatch spriteBatch, int x, int y, int height, Color color, float thickness)
      {
         if (_pixelTexture == null)
            CreateTexture(spriteBatch);

         if (height > 0)
         {
            spriteBatch.Draw(_pixelTexture, new Vector2(x, y), null, color, 0, Vector2.Zero, new Vector2(thickness, height), SpriteEffects.None, 0);
         }
         else
         {
            spriteBatch.Draw(_pixelTexture, new Vector2(x, y + height), null, color, 0, Vector2.Zero, new Vector2(thickness, height * (-1)), SpriteEffects.None, 0);
         }
      }
   }

Now I hope someone can help me. Am I doing something wrong? Or is there a better way to draw graphs? Or is this a bug after all? I am grateful for any advice.

Perhaps some hints to reproduce:

  • Most of the cases when the flickering appeared, I used laptops with intel hd graphics. One laptop with intel hd graphics plus a dedicated GPU showed no flickering, as long as the laptop screen was used. But as soon as it was connected to a screen via USB type C it started flickering.
  • All tested devices had Windows 10 installed.
  • Tested with MonoGame.Framework.WindowsDX 3.7.1.189 and 3.8.1.303 as well.

Thank you very much for your help.

I imagine it’s an issue with synchronization.

Compare this to this

If you building from source, probably you can ‘fix’ it by turning off multisampling.

Try this. Draw the odd lines first, then the even ones.
I am suspecting that the Image control is copying the
internal _texture midway while it’s been resolved.

You will still get the half top of the image,
because the ms-texture is already drawn ( Flash() ),
and then the WPF control is invalidated ( AddDirtyRect(…) )
before the rendertarget is resolved completely ( SetRenderTarget(null) ).

1 Like

Thank you for your help and good hints. I found out an interesting thing through this.

First I changed the preferredMultiSampleCount, to zero

var renderTarget = new RenderTarget2D(GraphicsDevice, actualWidth, actualHeight,
    false, SurfaceFormat.Bgra32, DepthFormat.Depth24Stencil8, 0,
    RenderTargetUsage.PlatformContents, true);

and changed the order according to your code.

GraphicsDevice.Flush();
GraphicsDevice.SetRenderTarget(null);
_direct3DImage.Lock();
_direct3DImage.AddDirtyRect(new Int32Rect(0, 0, (int)ActualWidth, (int)ActualHeight));
_direct3DImage.Unlock();

Did I understand you correctly here?

Unfortunately, the flickering continued to occur afterwards. I modified both of my test applications. The one with GraphicsDevice.DrawUserPrimitives() and the one with SpriteBatch.Draw().

But when I implemented your suggestion to draw the odd lines first, the following behavior showed up:

Or sometimes:

That’s interesting. I also did a test where the lines are drawn from the bottom up. There, the lines were then missing at the top during flickering.

Interesting. The incomplete line also looks like the the drawing stopped.
or rather that the texture was copied and composed into the WPF window while it as drawing. Did you had incomplete lines before?

Right now I dont remember how exacly I was using my control. I have to look it up.
Maybe you have to dispatch the drawing or the copying to the main thread.
If you are not getting any exceptions then it
Is some sort of syncronization issue.

Thank you for your help.

Yes, the incomplete lines have been occurring since I started testing MonoGame. But I never got an exception.

I also tested D2dControl (a WPF Control for Direct2D with SharpDX) and there were no problems with flickering, but the performance was significantly worse.

Okay, I now tried to optimize the performance of my D2dControl test and ended up, having the exact same behavior like in my MonoGame test. It is now also flickering in the exact same way.

The only thing I changed, was to draw a geometry for each graph instead of all line parts separately. This led to a noticeable performance improvement, but also resulted in flickering.

So I think you can rule out MonoGame as the cause of the problem.

I just wanted to give another hint. I don’t know if it is helpful.

When I disabled hardware rendering, as suggested at WPF render thread failures - .NET Framework | Microsoft Learn, the flickering was gone.