How to achieve smooth UI updates every 16 ms? - wpf

I am trying to create sort of a radar. Radar is VisualCollection that consists of 360 DrawingVisual's (which represent radar beams). Radar is placed on Viewbox.
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 precalculated geometry for DrawingContext.DrawGeometry(brush, pen, geometry). Pen is null and brush is a LinearGradientBrush with about 500 GradientStops. The brush gets updated every few milliseconds, lets say 16 ms for this example. And that is what gets laggy. Here goes the overall logic.
In MainWindow() constructor I create the 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 Run() method there is an infinite loop, where random brush is generated, Dispatcher.Invoke() is called and a delay for 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;
}
}
Every Invoke() call it performs one simple thing: dc.DrawGeometry(), which redraws the beam under current beamIndex. However, sometimes it seems, like before UI updates, radar.Draw() is called few times and instead of drawing 1 beam per 16 ms, it draws 2-4 beams per 32-64 ms. And it is disturbing. I really want to achieve smooth movement. I need one beam to get drawn per exact period of time. Not this random stuff. This is the list of what I have tried so far (nothing helped):
placing radar in Canvas;
using Task, BackgroundWorker, Timer, custom Microtimer.dll and setting different Thread Priorities;
using different ways of implementing delay: Environment.TickCount, DateTime.Now.Ticks, Stopwatch.ElapsedMilliseconds;
changing LinearGradientBrush to 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);
working with 360 Polygon objects instead of 360 DrawingVisuals. This way I could avoid using Invoke() method. Polygon.FillProperty of each polygon was bound to ObservableCollection, and INotifyPropertyChanged was implemented. So simple line of code {brushCollection[beamIndex] = (new created and frozen brush)} led to polygon FillProperty update and UI was getting redrawn. But still no smooth movement;
probably there were few more little workarounds I could forget about.
What I did not try:
use tools to draw 3D (Viewport) to draw 2D radar;
...
So, this is it. I am begging for help.
EDIT: These lags are not about PC resources - without delay radar can do about 5 full circles per second (moving pretty fast). Most likely it is something about multithread/UI/Dispatcher or something else that I am yet to understand.
EDIT2: Attaching an .exe file so you could see what is actually going on: https://dl.dropboxusercontent.com/u/8761356/Radar.exe
EDIT3: DispatcherTimer(DispatcherPriority.Render) did not help aswell.

For smooth WPF animations you should make use of the
CompositionTarget.Rendering event.
No need for a thread or messing with the dispatcher. The event will automatically be fired before each new frame, similar to HTML's requestAnimationFrame().
In the event update your WPF scene and you're done!
There is a complete example available on MSDN.

You can check some graphics bottleneck using the WPF Performance Suite:
http://msdn.microsoft.com/es-es/library/aa969767(v=vs.110).aspx
Perforator is the tool that will show you performance issues. Maybe you are using a low performance VGA card?

while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
The sequence above blocks the thread. Use instead "await Task.Delay(...)" which doesn't block the thread like its counterpart Thread.Sleep(...).

Related

Making smooth effect in WPF manually in C# with DispatcherTimer

I'm trying to make pretty effect with not using Storyboard or another ready/already done stuff in WPF.
I want to make smooth effect, where on some event (like click) the UI element resizes for 2-3 seconds and bluring with changing color. All these items I want to make in smooth pretty way.
I have prepared such class to render each frame of my effect:
public static class ApplicationHelper
{
[SecurityPermissionAttribute(SecurityAction.Demand,
Flags=SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents(DispatcherPriority priority)
{
DispatcherFrame frame = new DispatcherFrame();
DispatcherOperation oper = Dispatcher.CurrentDispatcher.
BeginInvoke(priority,
new DispatcherOperationCallback(ExitFrameOperation),
frame);
Dispatcher.PushFrame(frame);
if (oper.Status != DispatcherOperationStatus.Completed)
{
oper.Abort();
}
}
private static object ExitFrameOperation(object obj)
{
((DispatcherFrame)obj).Continue = false;
return null;
}
[SecurityPermissionAttribute(SecurityAction.Demand,
Flags=SecurityPermissionFlag.UnmanagedCode)]
public static void DoEvents()
{
DoEvents(DispatcherPriority.Background);
}
}
Here I'm trying to make it work with DispatcherTimer:
void vb1_click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
DispatcherTimer dt = new DispatcherTimer();
dt.Interval = new TimeSpan(0, 0, 0, 0, 500);
dt.Tick += new System.EventHandler(dt_Tick);
dt.Start();
}
void dt_Tick(object sender, System.EventArgs e)
{
for(int i = 0; i < 20; i++)
{
this.vb2_blur_eff.Radius = (double)i;
ApplicationHelper.DoEvents();
}
}
The main problem is, that when I'm launcing it, I'm only waiting and at the final time ( when must last frame be rendered ) , I'm getting in a very quick speed all frames, but perviously there was nothing.
How to solve it and make perfect smooth effect in pure C# way with not using some ready/done stuff?
Thank you!
The ApplicationHelper.DoEvents() in dt_Tick probably does nothing, since there are no events to process. At least not the ones you're probably expecting.
If I'm not mistaken, your code will just quickly set the Radius to 0, then 1, 2, and so on in quick succession, and finally to 19. All of that will happen every 500 milliseconds (on every Tick, that is).
I think you might believe that each Tick will only set Radius to one value and then wait for the next Tick, but it does not. Every Tick will set the Radius to all the values, ending at 19. That is one possible explanation for what you're experiencing.
I would also like to comment on the DoEvents approach. It's most likely a bad idea. Whenever I see a DoEvents I get chills up my spine. (It reminds me of some seriously bad Visual Basic 5/6 code I stumbled across 10-15 years ago.) As I see it, an event handler should return control of the GUI thread as quickly as possible. If the operation takes a not insignificant amount of time, then you should delegate that work to a worker thread. And nowadays, you have plenty of options for writing asynchronous code.

WP7 Silverlight/XNA split

I want to make an app that has Silverlight menus but the game part of the app is XNA. I am trying to get the Silverlight/XNA split working using the code from this example game and the method of doing XNA rendering in Silverlight here. Combining these 2 tutorials I have source code that looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Phone.Controls;
using System.Windows.Navigation;
namespace FYP
{
public partial class GamePage : PhoneApplicationPage
{
GameTimer timer;
SpriteBatch spriteBatch;
Texture2D ballTexture;
IList<Ball> balls = new List<Ball>();
bool touching = false;
public GamePage(ContentManager contentManager)
{
InitializeComponent();
//base.Initialize();
// Create a timer for this page
timer = new GameTimer();
timer.UpdateInterval = TimeSpan.FromTicks(333333);
//timer.Update += OnUpdate;
//timer.Draw += OnDraw;
// TODO: use this.Content to load your game content here
ballTexture = contentManager.Load<Texture2D>("Ball");
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Set the sharing mode of the graphics device to turn on XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
timer.Start();
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Set the sharing mode of the graphics device to turn off XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
// Stop the timer
timer.Stop();
}
private void OnUpdate(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
//this.Exit();
// TODO: Add your update logic here
//base.Update(gameTime);
HandleTouches();
UpdateBalls();
}
private void OnDraw(GameTime gameTime)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.White);
// TODO: Add your drawing code here
foreach (Ball ball in balls)
{
ball.Draw(spriteBatch);
}
//base.Draw(gameTime);
}
private void HandleTouches()
{
TouchCollection touches = TouchPanel.GetState();
if (!touching && touches.Count > 0)
{
touching = true;
Random random = new Random(DateTime.Now.Millisecond);
Color ballColor = new Color(random.Next(255), random.Next(255), random.Next(255));
Vector2 velocity = new Vector2((random.NextDouble() > .5 ? -1 : 1) * random.Next(9), (random.NextDouble() > .5 ? -1 : 1) * random.Next(9)) + Vector2.UnitX + Vector2.UnitY;
//Vector2 center = new Vector2((float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width / 2, (float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Height / 2);
Vector2 center = new Vector2((float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width / 2, 200);
float radius = 25f * (float)random.NextDouble() + 5f;
balls.Add(new Ball(this, ballColor, ballTexture, center, velocity, radius));
}
else if (touches.Count == 0)
{
touching = false;
}
}
private void UpdateBalls()
{
foreach (Ball ball in balls)
{
ball.Update();
}
}
}
}
I do not understand how base works, I've had to comment out base.initialize, update and draw however base.OnNavigatedFrom works.
Also, should I be able to get my code to work in theory? I find it very complicated and although reading about XNA/Silverlight to be possible I can't find any source code where people have successfully combined XNA and Silverlight into the same app.
these videos will help you out to understand XNA and SilverLight/XNA combined platform
http://channel9.msdn.com/Series/Mango-Jump-Start/Mango-Jump-Start-11a-XNA-for-Windows-Phone--Part-1
http://channel9.msdn.com/Series/Mango-Jump-Start/Mango-Jump-Start-11b-XNA-for-Windows-Phone--Part-2
Theoretically XNA and SilverLight XNA Combined platform are pretty much the same, just a difference of bits and pieces, You can even ask XNA to render some SilverLight Control, which will make it easier to handle some button event in your game.
Hope this helps

How to render a series of computed bitmaps as fast as possible in Silverlight?

I'm interested in displaying a series of computed bitmaps to the screen in Silverlight as fast as possible for the purpose of animation. Right now this is the strategy I am using which results in mid 50ies FPS on my laptop for a 1200x700 pixel image.
Can you recommend a better way?
public partial class MainPage : UserControl
{
private int _height;
private int _width;
private WriteableBitmap _bitmap;
private DateTime _start;
private int _count = 0;
public MainPage()
{
InitializeComponent();
_width = (int)this.MainImage.Width;
_height = (int)this.MainImage.Height;
_bitmap = new WriteableBitmap(_width, _height);
this.MainImage.Source = _bitmap;
_start = DateTime.Now;
RenderFrame();
}
private void RenderFrame()
{
Dispatcher.BeginInvoke(RenderFrameHelp);
}
private void RenderFrameHelp()
{
int solid = -16777216;
for (int i = 0; i < _width * _height; i++)
{
_bitmap.Pixels[i] = _count % 2 == 0 ? 255 : 100 | solid;
}
_bitmap.Invalidate();
this.FPS.Text = (_count++ / (DateTime.Now - _start).TotalSeconds).ToString();
RenderFrame();
}
}
QuakeLight uses roughly the following solution:
Instead of using a WriteableBitmap, you can make a very basic PNG encoder (raw bitmap, no compression, take it from QuakeLight if you must). Fill a normal array with pixel data, encode it as PNG in memory, then wrap it in a MemoryStream and attach it to an Image. Making an uncompressed PNG basically means slapping a fixed size header in front of your array.
You could even use a producer-consumer queue so that you can build your PNG in a separate thread, letting you utilize multi-core systems for better performance.
Hope this helps. Share your experience if you try this method.
The fastest approach will probably be to pre-render the images in your animation to a list of WriteableBitmaps and then selectively set each of them as the source of an Image control.

WPF Image Generation using an N x N grid of images

I'm working on a personal project that creates an single image from a grid of images. It takes a while to generate the image and doesn't refresh everytime only once the code is done executing. How can the make the interface still functional (not locked up) when its generating the image.
So to start:
I have a N x N grid of identifiers, based on the identifier I draw a specific image at (x,y) with a given scaled height and width.
This image is regenerated each iteration and needs to be updated on the WPF. It is also bound to the ImageSource of the Image on the xaml side
My issue is 'How do I improve performance of generating this large image' and 'How do I refresh the image as many times as I need to (per generation).
for (int i = 0; i < numberOfIterations; i++)
{
// Do Some Work
UpdateImage();
}
...
BitmapImage imgFlower = new BitmapImage(new Uri(#"Images\Flower.bmp", UriKind.Relative));
BitmapImage imgPuppy = new BitmapImage(new Uri(#"Images\Puppy.bmp", UriKind.Relative));
ImageSource GeneratedImage{ get{ GenerateImage(); } set; }
...
void UpdateImage() { OnPropertyChanged("GeneratedImage"); }
...
ImageSource GenerateImage()
{
RenderTargetBitmap bmp = new RenderTargetBitmap(223, 223, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
double scaleRatio = CalculateScaleRatio();
DrawGridOfImages(drawingContext, scaleRatio);
}
bmp.Render(drawingVisual);
return bmp;
}
...
DrawGridOfImages(...)
{
double x,y;
for (int r = 0; r < NumberOfRows; r++)
{
x = r * scaleRatio;
for (int c = 0; c < NumberOfColumns; c++)
{
y = c * scaleRatio;
switch (imageOccupancy[r, c])
{
case Flower: drawingContext.DrawImage(imgFlower, new Rect(x,y,scaleRatio,scaleRation));
case Puppy: drawingContext.DrawImage(imgPuppy, new Rect(x,y,scaleRatio,scaleRatio));
}
}
}
}
There are two ways. To first and most beneficial would be to improve the perceived performance, do this by generating the image on a worker thread and use events to update the image on the UI thread at key points so your users can see the progress.
To improve actual performance, if you are targeting and using multicore systems you can try parallel functions if your iterations can actually be performed in parallel. This will require some work and a different mindset but will help if you put the effort in. I'd recommend studying PLINQ to get started.

Performing a Flip animation completely through code WPF

I am try to add a flip animation to a user control I built. The user control is simple it has a 87x87 image front and back and some properties. It is suppose to represent a tile in a game I am working on for fun. I am trying to animate a flipping affect of the user picking the tile from the deck. I feel I need to do this through code instead of xaml for two reasons: 1. There is another transform after the tile is flip to rotate the tile (currently working) 2. After the tile is flipped I want to unhook the event.
The issue that I am getting is only the last animation runs after the method has exited.
I think I need a Storyboard but all the examples I looked at confused me in two ways:
How do I change the image mid story board, and what do I set the targetProperty to be
I have been working off these two blogs.
http://www.codeguru.com/csharp/csharp/cs_misc/userinterface/article.php/c12221
http://blogs.msdn.com/tess/archive/2009/03/16/silverlight-wpf-flipimage-animation.aspx
public void FlipFront()
{
DoubleAnimation flipfront = new DoubleAnimation(0, 90, new Duration(new TimeSpan(0, 0, 1)));
SkewTransform skew = new SkewTransform();
this.RenderTransform = skew;
skew.BeginAnimation(SkewTransform.AngleYProperty, flipfront);
}
public void FlipBack()
{
ImageSourceConverter source = new ImageSourceConverter();
this.ImageFace.Source = new BitmapImage(new Uri("Back.jpg", UriKind.Relative));
DoubleAnimation flipfront = new DoubleAnimation(90, 0, new Duration(new TimeSpan(0, 0, 1)));
SkewTransform skew = new SkewTransform();
this.RenderTransform = skew;
skew.BeginAnimation(SkewTransform.AngleYProperty, flipfront);
}
public void Flip()
{
FlipFront();
FlipBack();
}
I broke flip into two separate methods because I though it would help fix the issue I am experiencing.
Wow, this hasn't been updated in a loong time...just in case anybody's tracking this one:
The problem is you're not waiting for the "flip front" animation to complete before immediately starting the "flip back" - now since you're basically force-jumping the Y angle animation immediately to 90 degrees, that's why it looks like it's not firing properly.
There are a LOT of ways you can work around this - the first thing that jumps to mind is that the DoubleAnimations have a method on them called CreateClock, which will return you back an AnimationClock object. That object has a Completed event on it, which will tell you when that animation is "done". Attach a handler (remember you'll want to detach it lest you leak memory), and call your "start flipping to back" method there. I've thrown something very inefficient together, but it'll show the principle:
public AnimationClock StartFlipFrontAnimation()
{
this.ImageFace.Source = _frontFace;
DoubleAnimation flipfront = new DoubleAnimation(0, 90, new Duration(new TimeSpan(0, 0, 3)));
SkewTransform skew = new SkewTransform();
this.RenderTransform = skew;
skew.BeginAnimation(SkewTransform.AngleYProperty, flipfront);
return flipfront.CreateClock();
}
public AnimationClock StartFlipBackAnimation()
{
this.ImageFace.Source = _backFace;
DoubleAnimation flipfront = new DoubleAnimation(90, 0, new Duration(new TimeSpan(0, 0, 3)));
SkewTransform skew = new SkewTransform();
this.RenderTransform = skew;
skew.BeginAnimation(SkewTransform.AngleYProperty, flipfront);
return flipfront.CreateClock();
}
public void BeginFlip()
{
var frontClk = StartFlipFrontAnimation();
frontClk.Completed += FrontFlipDone;
}
private void FrontFlipDone(object sender, EventArgs args)
{
var clk = sender as AnimationClock;
if(clk != null)
{
clk.Completed -= FrontFlipDone;
}
var backClk = StartFlipBackAnimation();
}

Resources