How to achieve smooth user interface updates every 16 ms?

I'm trying to create some kind of radar. Radar is a VisualCollection that consists of 360 DrawingVisual (which are radar rays). The radar is placed in the viewport.

class Radar : FrameworkElement { private VisualCollection visuals; private Beam[] beams = new Beam[BEAM_POSITIONS_AMOUNT]; // all geometry calculation goes here public Radar() { visuals = new VisualCollection(this); for (int beamIndex = 0; beamIndex < BEAM_POSITIONS_AMOUNT; beamIndex++) { DrawingVisual dv = new DrawingVisual(); visuals.Add(dv); using (DrawingContext dc = dv.RenderOpen()) { dc.DrawGeometry(Brushes.Black, null, beams[beamIndex].Geometry); } } DrawingVisual line = new DrawingVisual(); visuals.Add(line); // DISCRETES_AMOUNT is about 500 this.Width = DISCRETES_AMOUNT * 2; this.Height = DISCRETES_AMOUNT * 2; } public void Draw(int beamIndex, Brush brush) { using (DrawingContext dc = ((DrawingVisual)visuals[beamIndex]).RenderOpen()) { dc.DrawGeometry(brush, null, beams[beamIndex].Geometry); } } protected override Visual GetVisualChild(int index) { return visuals[index]; } protected override int VisualChildrenCount { get { return visuals.Count; } } } 

Each DrawingVisual has pre-calculated geometry for DrawingContext.DrawGeometry (brush, pen, geometry). Pen is null and the brush is a LinearGradientBrush with approximately 500 gradients. The brush is updated every few milliseconds, say, 16 ms for this example. And this is what lags receive. Here comes the general logic.

In the constructor of MainWindow (), I create a radar and start a background thread:

  private Radar radar; public MainWindow() { InitializeComponent(); radar = new Radar(); viewbox.Child = radar; Thread t = new Thread(new ThreadStart(Run)); t.Start(); } 

In the Run () method, there is an infinite loop in which a random brush is generated, Dispatcher.Invoke () is called, and a delay of 16 ms is set:

  private int beamIndex = 0; private Random r = new Random(); private const int turnsPerMinute = 20; private static long delay = 60 / turnsPerMinute * 1000 / (360 / 2); private long deltaDelay = delay; public void Run() { int beginTime = Environment.TickCount; while (true) { GradientStopCollection gsc = new GradientStopCollection(DISCRETES_AMOUNT); for (int i = 1; i < Settings.DISCRETES_AMOUNT + 1; i++) { byte color = (byte)r.Next(255); gsc.Add(new GradientStop(Color.FromArgb(255, 0, color, 0), (double)i / (double)DISCRETES_AMOUNT)); } LinearGradientBrush lgb = new LinearGradientBrush(gsc); lgb.StartPoint = Beam.GradientStarts[beamIndex]; lgb.EndPoint = Beam.GradientStops[beamIndex]; lgb.Freeze(); viewbox.Dispatcher.Invoke(new Action( () => { radar.Draw(beamIndex, lgb); })); beamIndex++; if (beamIndex >= BEAM_POSITIONS_AMOUNT) { beamIndex = 0; } while (Environment.TickCount - beginTime < delay) { } delay += deltaDelay; } } 

Each invoke () call does one simple thing: dc.DrawGeometry (), which redraws the beam under the current beamIndex. However, sometimes it seems that, as before updating the user interface, radar.Draw () is called several times and instead of drawing 1 beam in 16 ms, it draws 2-4 rays in 32-64 ms. And that is troubling. I really want to achieve a smooth movement. I need one ray to draw for the exact period of time. Not this random stuff. This is a list of what I have tried so far (nothing helped):

  • radar placement in Canvas;
  • using Task, BackgroundWorker, Timer, custom Microtimer.dll and setting various thread priorities;
  • using various methods for implementing the delay: Environment.TickCount, DateTime.Now.Ticks, Stopwatch.ElapsedMilliseconds;
  • Modifying LinearGradientBrush to a predefined SolidColorBrush;
  • using BeginInvoke () instead of Invoke () and changing dispatcher priorities;
  • using InvalidateVisuals () and ugly DoEvents ();
  • using BitmapCache, WriteableBitmap and RenderTargetBitmap (using DrawingContext.DrawImage (bitmap);
  • works with 360 Polygon objects instead of 360 DrawingVisuals. That way, I could avoid using the Invoke () method. Each Polygon's Polygon.FillProperty was bound to an ObservableCollection, and INotifyPropertyChanged was implemented. So a simple line of code {brushCollection [beamIndex] = (new created and frozen brush)} led to an update of the FillProperty polygon, and the user interface was redrawn. But still there is no smooth movement;
  • Perhaps there were a few small workarounds that I could forget about.

What I have not tried:

  • use 3D drawing tools (Viewport) to draw a 2D radar;
  • ...

So there it is. I ask for help.

EDIT: These delays are not related to PC resources - without delay, the radar can perform about 5 full circles per second (it moves quite quickly). Most likely, this is something like multi-threaded / UI / Dispatcher or something else that I still do not understand.

EDIT2: Attaching the .exe file so you can see what is actually happening: https://dl.dropboxusercontent.com/u/8761356/Radar.exe

EDIT3: DispatcherTimer (DispatcherPriority.Render) didn't help either.

+7
wpf
source share
3 answers

For smooth WPF animations you should use the CompositionTarget.Rendering event.

No need to stream or mess with the dispatcher. The event will be automatically fired before each new frame, like HTML requestAnimationFrame() .

If you update your WPF scene, you're done!

MSDN has a complete example .

+3
source share

You can check out some graphics bottlenecks using the WPF Performance Suite:

http://msdn.microsoft.com/es-es/library/aa969767 (v = vs .110) .aspx

A rotary hammer is a tool that will show you performance problems. Perhaps you are using a low-performance VGA card?

0
source share
 while (Environment.TickCount - beginTime < delay) { } delay += deltaDelay; 

The sequence above blocks the flow. Instead, use "wait Task.Delay (...)", which does not block the thread, like its Thread.Sleep (...) counterpart.

0
source share

All Articles