How can I get rid of jerkiness in WinForms scrolling animation? - c#

I'm writing a simple control in C# that works like a picture box, except the image is constantly scrolling upwards (and re-appearing from the bottom). The animation effect is driven by a timer (System.Threading.Timer) which copies from the cached image (in two parts) to a hidden buffer, which is then drawn to the control's surface in its Paint event.
The problem is that this scrolling animation effect is slightly jerky when run at a high frame rate of 20+ frames per second (at lower frame rates the effect is too small to be perceived). I suspect that this jerkiness is because the animation is not synchronized in any way with my monitor's refresh rate, which means that each frame stays on the screen for a variable length of time instead of for exactly 25 milliseconds.
Is there any way I can get this animation to scroll smoothly?
You can download a sample application here (run it and click "start"), and the source code is here. It doesn't look horribly jerky, but if you look at it closely you can see the hiccups.
WARNING: this animation produces a pretty weird optical illusion effect which might make you a little sick. If you watch it for awhile and then turn it off, it will look as if your screen is stretching itself vertically.
UPDATE: as an experiment, I tried creating an AVI file with my scrolling bitmaps. The result was less jerky than my WinForms animation, but still unacceptable (and it still made me sick to my stomach to watch it for too long). I think I'm running into a fundamental problem of not being synced with the refresh rate, so I may have to stick to making people sick with my looks and personality.

You would need to wait for a VSYNC before you draw the buffered image.
There is a CodeProject article that suggests using a multimedia timer and DirectX' method IDirectDraw::GetScanLine().
I'm quite sure you can use that method via Managed DirectX from C#.
EDIT:
After some more research and googling I come to the conclusion that drawing via GDI doesn't happen in realtime and even if you're drawing in the exact right moment it might actually happen too late and you will have tearing.
So, with GDI this seems not to be possible.

(http://www.vcskicks.com/animated-windows-form.html)
This link has an animation and they explain the way they accomplish it. There is also a sample project that you can download to see it in action.

Use double buffering. Here are two articles: 1 2.
Another factor to ponder is that using a timer doesn't guarantee you to be called at exactly the right time. The correct way to do this is to look at the time passed since the last draw and calculate the correct distance to move smoothly.

I had a similar problem a couple of months ago, and solved them by switching to WPF. The animated control ran a lot smoother than with a standard timer-based solution and I didn't have to take care of synchronization any more.
You might want to give it a try.

You need to stop relying on the timer event firing exactly when you ask it to, and work out the time difference instead, and then work out the distance to move. This is what games, and WPF do, which is why they can achieve smooth scrolling.
Let's say you know you need to move 100 pixels in 1 second (to sync with the music), then you calculate the time since your last timer event was fired (let's say it was 20ms) and work out the distance to move as a fraction of the total (20 ms / 1000 ms * 100 pixels = 2 pixels).
Rough sample code (not tested):
Image image = Image.LoadFromFile(...);
DateTime lastEvent = DateTime.Now;
float x = 0, y = 0;
float dy = -100f; // distance to move per second
void Update(TimeSpan elapsed) {
y += (elapsed.TotalMilliseconds * dy / 1000f);
if (y <= -image.Height) y += image.Height;
}
void OnTimer(object sender, EventArgs e) {
TimeSpan elapsed = DateTime.Now.Subtract(lastEvent);
lastEvent = DateTime.Now;
Update(elapsed);
this.Refresh();
}
void OnPaint(object sender, PaintEventArgs e) {
e.Graphics.DrawImage(image, x, y);
e.Graphics.DrawImage(image, x, y + image.Height);
}

Some ideas (not all good!):
When using a threading timer, check that your rendering time is considerably less than one frame interval (from the sound of your program, you should be fine). If rendering takes longer than 1 frame, you will get re-entrant calls and will start rendering a new frame before you've finished the last. One solution to this is to register for only a single callback at startup. Then in your callback, set up a new callback (rather than just asking to be called repeatedly every n milliseconds). That way you can guarantee that you only schedule a new frame when you've finished rendering the current one.
Instead of using a thread timer, which will call you back after an indeterminate amount of time (the only guarantee is that it is greater than or equal to the interval you specified), run your animation on a separate thread and simply wait (busy wait loop or spinwait) until it is time for the next frame. You can use Thread.Sleep to sleep for shorter periods to avoid using 100% CPU or Thread.Sleep(0) simply to yield and get another timeslice as soon as possible. This will help you to get much more consistent frame intervals.
As mentioned above, use the time between frames to calculate the distance to scroll, so that the scroll speed is independent of the frame rate. But note that you will get temporal sampling/aliasing effects if you try to scroll by a non-pixel rate (e.g. if you need to scroll by 1.4 pixels in a frame, the best you can do is 1 pixel, which will give a 40% speed error). A workaround for this would be to use a larger offscreen bitmap for scrolling and then scale it down when blitting to screen, so you can effectively scroll by sub-pixel amounts.
Use a higher thread priority. (really nasty, but may help!)
Use something a bit more controllable (DirectX) rather than GDI for rendering. This can be set up to swap the offscreen buffer on a vsync. (I'm not sure if Forms' double buffering bothers with syncing)

I had the same problem before and found it to be a video card issue.
Are you sure your video card can handle it?

I modified your example to use a multimedia timer that has a precision down to 1 ms, and most of the jerkiness went away. However, there is still some little tearing left, depending on where exactly you drag the window vertically. If you want a complete and perfect solution, GDI/GDI+ is probably not your way, because (AFAIK) it gives you no control over vertical sync.

Well, if you wanted to run the timer at a lower speed, you can always change the ammount the image is scrolled in the view. This gives better preformance, but makes the effect look kinda jerky.
Just change the _T += 1; line to add the new step...
Actually, you could even add a property to the control to adjust the step ammount.

Related

How do I GET the screen refresh Hz in C# UWP?

Does anyone know how to get the monitor Screen Refresh Rate (Hz) in a C# UWP app?
...that will be valid to publish to the Microsoft Store (ie, no HWNDs?).
The reason for the question is that when writing a game, it's good to base movement on elapsed time between updates. To get smooth frame timing it's useful to know how often we're drawing to the screen and so time our frame updates to be in sync with that.
For example, if you know the Refresh Rate is 120Hz then it's optimal to set a fixed game time:
TargetElapsedTime = TimeSpan.FromTicks((long)(TimeSpan.TicksPerSecond / 120L));
Which results in consistent frame timing and smooth motion.
Unbound the process timing using IsFixedTimeStep = false; in Game1.
The default of graphics.SynchronizeWithVerticalRetrace = true; should cap the FPS to match the refresh rate.
It should work according to the source code comments:
The first argument instructs DXGI to block until VSync, putting the application to sleep until the next VSync. This ensures we don't waste any cycles rendering frames that will never be displayed to the screen.
If you still need the actual refresh rate in Hz, calculate the average of (1000/gameTime.ElapsedTime.TotalMilliseconds) in update.

Unity UI - Interacting with worldspace UI when cursor is locked

I'm trying to interact with world space UI using a first person controller when Cursor.lockState is set to CursorLockMode.Locked.
world space UI and a character
But when cursor is locked, cursor position is set to (-1, -1),
which is told from the Inspector.
cursor position of (-1, -1)
I performed a graphic raycast with EventSystem.RaycastAll, Sreen.width/2 and PointerEventData. EventSystem.current.RaycastAll collects all UI objects in the middle of screen, but no events is sent to them.
I also tried ExecuteEvents.Execute<IEventSystemHandler> to manully send event to UI targets. This works for button when I send 'submit' event to it. Obviously this is not an elegant solution. I have no idea how to send message to slider either.
// manully send a 'submit' event to UI elements
List<RaycastResult> results = new List<RaycastResult>();
void Update() {
if (Input.GetButtonUp("Fire1")) {
PointerEventData data = new PointerEventData(EventSystem.current);
data.position = new Vector2(Screen.width / 2, Screen.height / 2);
EventSystem.current.RaycastAll(data, results);
foreach (var result in results) {
ExecuteEvents.ExecuteHierarchy<ISubmitHandler>(
result.gameObject, data,
ExecuteEvents.submitHandler
);
}
}
}
This crazy attempt works when played full-screen on Windows. 2333
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int SetCursorPos ( int x , int y );
void SetCursorPositionToCenter()
{
  SetCursorPos(Screen.width/2, Screen.height/2);
}
Relavant Resources
I had a lot of problems working with Graphics Raycaster and Global UI elements as well. I tried a lot of things as well but the event system didn't seem to work. But I was simulating a mouse with a touchpad and maybe I didn't completely simulate how a mouse works with UI elements.
I ended up abandoning graphics raycaster and instead add a box collider to all UI elements. That way a regular physics raycaster would be able to detect it and run some code. For the slider, it is just increasing the slider's value as your x or y of your pointer increase.
Hope this helps a little.
What you're doing with the submit handler is similar to the many different ways this can be done. I've not dealt with sliders specifically, because it's not very easy to control sliders in this method (player controller or, in the case of VR, the user's head, does not do straight lines very well). So for slider input we usually go with +/- buttons. Another idea could be that when the user is pointing at a UI element that you release the cursorLock when they click. When click is released you can resume cursor lock.
UI elements are also often in need of states like hover/onEnter, onExit, sometimes pointer up, and usually pointer down. So we've often used Selectable class for this. I think all the interactable UI elements inherit from this so it's a good spot to access the UI element from. This will usually trigger the proper events but it can be a little tricky. Examine some of the many readily available gaze input systems as they're doing the same thing. I don't think I've ever seen one handle sliders though (they're usually meant for VR and gaze-to-slider control would be bad UX).
Cheers and good luck!
If you're trying to access UI elements without affecting gameplay you can simply set Time.TimeScale = 0; which basically pauses time but UI elements will still be active. Then just unlock your cursor and use it freely.

Set position in mediaElement (ScrubbingEnabled alternative in WP8.1)

I am trying to set position of video while video is paused.
In WPF it is simple with ScrubbingEnabled
property.
But there is nothing like this for windows phone.
So I use:
_mediaElement.Play();
_mediaElement.Position += new TimeSpan(0,0,0,0,50);//move video 50 milisecond forward
_mediaElement.Pause();
It is "working", but when I am trying to go (about)5 miliseconds back, it is moving forward and it is not clear solution...
How can I replace ScrubbingEnabled property on windows phone?
Edit
Code for 5ms back:
_mediaElement.Play();
_mediaElement.Position -= new TimeSpan(0,0,0,0,5);
_mediaElement.Pause();
Even assuming that your video is running at 60 frames per second, there's only one frame every 16.67 ms. So to move 5 ms should probably do nothing in many cases.
That would also explain why when you ask it to move 50 ms, it doesn't move by that exact amount, but rather goes to the time of the closest frame.
(if you move position by only a few frames, due to how digital video compression works it might not work as you expect either)
Ok. This is not exactly the answer, but today WP8.1 is obsolete and setting position with mediaElement on Windows 10 work pretty fine without Play-Pause hack.
So:
_mediaElement.Position -= new TimeSpan(0,0,0,0,33);
is moving video 1 frame back (30 FPS video), and
_mediaElement.Position -= new TimeSpan(0,0,0,0,5);
is not moving anywhere.
One interesting thing:
To move 1 frame you need to change position at least one half of frame time.
_mediaElement.Position -= new TimeSpan(0,0,0,0,16);//not moving
_mediaElement.Position -= new TimeSpan(0,0,0,0,17);//moving every 2nd "click"
Frame time for 30 FPS video is 1000/30 = 33.333 ms

C# XNA 2D trail effect optimization

Currently as a trail effect in my game I have for every 5 frames a translucent texture copy of a sprite is added to a List<> of trails.
The alpha values of these trails is decremented every frame and a draw function iterates through the list and draws each texture. Once they hit 0 alpha they are removed from the List<>.
The result is a nice little trail effect behind moving entities. The problem is for about 100+ entities, the frame rate begins to drop drastically.
All trail textures come from the same sprite sheet so i dont think it's batching issue. I profiled the code and the CPU intensity is lower during the FPS drop spikes then it is at normal FPS so I assume that means its a GPU limitation?
Is there any way to achieve this effect more efficiently?
Heres the general code im using:
// fade alpha
m_alpha -= (int)(gameTime.ElapsedGameTime.TotalMilliseconds / 10.0f);
// draw
if (m_alpha > 0) {
// p is used to alter RGB of the trails color (m_tint) depending on alpha value
float p = (float)m_alpha/255.0f;
Color blend = new Color((int)(m_tint.R*p), (int)(m_tint.G*p), (int)(m_tint.B*p), m_alpha);
// draw texture to sprite batch
Globals.spriteBatch.Draw(m_texture, getOrigin(), m_rectangle, blend, getAngle(), new Vector2(m_rectangle.Width/2, m_rectangle.Height/2), m_scale, SpriteEffects.None, 0.0f);
} else {
// flag to remove from List<>
m_isDone = true;
}
I guess i should note, the m_texture given to the trail class is a reference to a global texture shared by all trails. Im note creating a hard copy for each trail.
EDIT: If I simply comment out the SpriteBatch.Draw call, even when im allocating a new trail every single frame for hundreds of objects there is no drop in frames... there has got to be a better way to do this.
Usually for trails, instead of clearing the screen on every frame, you simply draw a transparent screen-sized rectangle before drawing the current frame. Thus the previous frame is "dimmed" or "color blurred" while the newer frame is fully "clear" and "bright". As this is repeated, a trail is generated from all the previous frames, which are never cleared but rather "dimmed".
This technique is VERY efficient and it is used in the famous Flurry screensaver (www.youtube.com/watch?v=pKPEivA8x4g).
In order to make the trails longer, you simply increase the transparency of the rectangle that you use to clear the screen. Otherwise, you make it more opaque to make the trail shorter. Note, however, that if you make the trails too long by making the rectangle too transparent, you risk leaving some light traces of the trail that due to alpha blending, might not completely erase even after a long time. The Flurry screensaver suffers from this kind of artifact, but there are ways to compensate for it.
Depending on your situation, you might have to adapt the technique. For instance, you might want to have several drawing layers that allow certain objects to leave a trail while others don't generate trails.
This technique is more efficient for long trails than trying to redraw a sprite thousands of times as your current approach.
On the other hand, I think the bottleneck in your code is the following line:
Globals.spriteBatch.Draw(m_texture, getOrigin(), m_rectangle, blend, getAngle(), new Vector2(m_rectangle.Width/2, m_rectangle.Height/2), m_scale, SpriteEffects.None, 0.0f);
It is inefficient to have thousands of GPU calls like Draw(). It would be more efficient if you had a list of polygons in a buffer, where each polygon is located in the correct position and it has transparency information stored with it. Then, with a SINGLE call to Draw(), you can then render all polygons with the correct texture and transparency. Sorry I cannot provide you with code for this, but if you want to continue with your approach, this might be the direction you are headed. In short, your GPU can certainly draw millions of polygons at a time, but it can't call Draw() that many times...

What does the DragCompletedGestureEventArgs.Velocity parameter mean exactly?

The DragCompleted event of a wp7 control has a parameter of type DragCompletedGestureEventArgs that contains the variables HorizontalVelocity and VerticalVelocity.
How are these velocity variables interpreted? I get a value of 0 for a slow drag and a value of >4000 for a fast drag, but I'm not sure how I can relate these numbers into a number of pixels dragged per time interval value.
Background of my question: In my program the user can grab an object and then drag it. I want the object to continue moving (up to a standstill) when the user lets go of the object.
You can't relate them into pixels. But you can create a relative scale according to the velocity, and then how much it should slow down / speed up, based on the velocity.
If you think of the Pictures Hub image viewer, then you can see that you can pan around images, but if you use a high enough velocity, it'll flick to the next/previous image.

Categories