Smooth WPF animations on Remote Desktop to VM - c#

So I'm attempting to make a docking library very similar to the ones used in VSCode and Atom, however I've run into some issues. Basically, for the start, I need to make a box starting from width 0 to increase to the end of the screen. I'm able to achieve this:
Rectangle rectangle = new Rectangle();
myCanvas.Children.Add(rectangle);
rectangle.Width = 0;
rectangle.Height = ActualHeight;
rectangle.Fill = new SolidColorBrush(Color.FromArgb(80, 120, 120, 120));
Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation(rectangle.Width, ActualWidth, new Duration(TimeSpan.FromMilliseconds(1000)));
Storyboard.SetTargetProperty(da, new PropertyPath("(Rectangle.Width)"));
sb.Children.Add(da);
rectangle.BeginStoryboard(sb);
The animation is quite smooth on my dev-machine, but when remote-desktoping into a VM, the animation is VERY choppy. However, VSCodes and Atoms animations are still pretty decent. Is there any way to make this work better?
Thanks.
EDIT:
Is there a way to make a smooth animation for Winforms aswell?
EDIT 2:
So I have this Winforms GDI code sample here:
private void FastTimer_Tick(object sender, EventArgs e)
{
var x = 1;
if (solidBrush == IntPtr.Zero)
{
solidBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.FromArgb(120, 120, 120)));
hDC = CreateGraphics().GetHdc();
}
index += x;
int w = x;
int h = Height;
//create memory device context
var memdc = CreateCompatibleDC(hDC);
//create bitmap
var hbitmap = CreateCompatibleBitmap(hDC, index, h);
////select bitmap in to memory device context
var holdbmp = SelectObject(memdc, hbitmap);
RECT rect = new RECT(new Rectangle(0, 0, w, h));
FillRect(memdc, ref rect, solidBrush);
AlphaBlend(hDC, index - x, 0, w, h, memdc, 0, 0, w, h, new BLENDFUNCTION(0, 0, 128, 0));
SelectObject(memdc, holdbmp);
DeleteObject(hbitmap);
DeleteDC(memdc);
}
With the fast timer core being this (Interval is 1000 in testing):
double frequency = Stopwatch.Frequency;
long prevTicks = 0;
while (true)
{
if (!_enabled)
{
return;
}
double interval = frequency / Interval;
long ticks = Stopwatch.GetTimestamp();
if (ticks >= prevTicks + interval)
{
prevTicks = ticks;
Tick?.Invoke(this, EventArgs.Empty);
}
}
However, the animations are still choppy on Remote Desktop. (I know RD cannot use hardware acceleration, but I thought GDI was hardware-accelerated on compatible machines, but I do not see any GPU usage on my dev-machine)
With regards to thatguys answer, how do I make this faster?

These issues are common with WPF and are caused by bitmap remoting, see this Microsoft blog.
Starting with the release of NET 3.5 SP1 (including NET 4), WPF renders the application content using a software rasterizer on the server and then remotes the content as bitmaps in all cases.
This is crucial for animations, due to lots of redrawing. Animations often affect a large area at once, imagine a color gradient. Therefore, large parts of the application need to be invalidated and redrawn. Furthermore, there is a the lack of support for occlusion in WPF that might aggravate the issue, too.
Bitmaps are highly compressed by the underlying RDC stack and only regions that changed are being updated. Also note that WPF does not currently have efficient occlusion support, so for instance animations that are completely hidden behind other opaque WPF elements will still force invalidation and RDP update.
The reason that you might not encouter this issue for eaxmple in native Win32 or MFC applications is that they use GDI or GDI+ for drawing their user interface controls.
When apps use GDI (such as many Win32 and Winforms apps do), only the GDI primitives are remoted. In many cases this can be more efficient than remoting WPF apps since WPF apps remotes bitmaps which typically result in more content being sent over the wire than a similar GDI-based app. The additional data may result in slower performance depending on network bandwidth and the size and frequency of updates.
Atom and VS Code are built on Electron which is based on chromium, not WPF, that is why there are large differences on remote desktop.
In the linked blog you can find hints on how to improve the remote performance in general. Apart from considering to disable animations in remote desktop scenarios because there are no guarantees on factors like bandwidth or latency for your clients, the essential parts for improving animations are:
Adapt the DesiredFrameRate to reduce the animation frame rate, which in turn reduces traffic
Turn animations off in occluded areas of the application, since they cause redrawing, too
Reduce traffic by removing animations from default WPF control templates for ProgressBar, Button, ComboBox and others, as well as all of your own non-essential animations
Improve RDP compression efficiency by simplifying other areas of the application, like replacing color gradients with solid colors
Avoid anything that is slow to render in software like bitmap effects or 3D views
Use a WPF performance profiling tool like Perforator to identify the critical areas that are invalidated frequently to optimize them

Related

Take screenshot of external OpenGL game C# with BitBlt (CopyFromScreen)

I am trying to create some kind of antycheat for counter strike (hl) game. Of course funcionality of making a screenshot in-game is built-in, but exploited (detected) by antyss applications, so every time screenshot is taken from the game, antyss is disabling the cheats (so that no cheats are visible on the screenshots)
For the last few days, I've read dozens of threads regarding this topic. Most of them are outdated and are using libraries, that are obsolete right now.
I've read about the approach with mirage driver (which is not working on windows 10), about injecting to the application (of course application/game is not part of my code) and using/incjeting some code with OPEN GL/D3D library (to read backbuffer). Probably this could be in the end the only solution.
But right now I have almost a working solution. I write "almost" because it is working but giving me only some kind of "cached" data. It is giving me a correct screenshot, but if I take another screenshot - still the same screenshot is taken as last time. If while being in-game I minimize the application (full-screen mode) and then get back to the game, the new screenshot taken will have up to date screenshot, but then again, the next screenshot would be exactly the same.
I don't know if it is "by design" or is it "some sort of bug" Nevertheless my question is: Can I force somehow this "reloading" without having to programmatically call some kind of "alt+tab" and then focusing on the application once again?
In this topic:
How to take screenshots of a game with OpenGL
#Andon M. Coleman wrote:
Are you on Windows? In fullscreen mode starting with Windows Vista, there is trouble with anything that tries to capture the front-buffer (including the built-in Alt + PrintScreen). The easiest solution is to change your buffer swap behavior to Copy Swap (PFD_SWAP_COPY, slower but guaranteed to work). Often if you Alt+TAB out and back in after making the fullscreen mode switch that will fix it too; though I have never been able to explain that ;) If you did not write the game in the question, then the second solution may be your only choice if you want to use that code.
This is exactly the problem I am facing. As he wrote: "Alt+Tab" is fixing the problem (although he did not know whether it is a feature or a bug) He proposed to change the buffer swap behavior to Copy Swap(PFD_SWAP_COPY) Any tips on how to change my code with that will also be most welcome (I can try this one) But if I understood correctly, this approach is the viable solution only if you can change this in the game (and this is not my case)
Here is my working code (which in topics about such scenarios was claiming that in this approach the screenshot is BLACK. But it is working for me)
private const int SW_RESTORE = 9;
public void TakeScreenShot()
{
var guid = Guid.NewGuid();
string procName = "hl";
Process proc;
try
{
proc = Process.GetProcessesByName(procName)[0];
}
catch (IndexOutOfRangeException e)
{
return;
}
// Focus on the application
SetForegroundWindow(proc.MainWindowHandle);
ShowWindow(proc.MainWindowHandle, SW_RESTORE);
Thread.Sleep(1000);
Rect rect = new Rect();
IntPtr error = GetWindowRect(proc.MainWindowHandle, ref rect);
while (error == (IntPtr)0)
{
error = GetWindowRect(proc.MainWindowHandle, ref rect);
}
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
using (Bitmap printscreen = new Bitmap(width, height, PixelFormat.Format32bppArgb))
{
using (var graphics = Graphics.FromImage(printscreen))
{
graphics.CopyFromScreen(rect.left,
rect.top,
0,
0,
new Size(width, height),
CopyPixelOperation.SourceCopy);
printscreen.Save($#"{Path.GetTempPath()}\{guid.ToString()}.jpg", ImageFormat.Jpeg);
}
}
}
I want this application to work on Windows7, Windows8, Windows 10. The best would be to cover full screen and windowed mode (but fullscreen is probably more important)
Any advice how to proceed (or why I am getting the "cached" data) would be nice :)
Of course if someone will say (with full authority), that what i want to achieve is impossible with CopyFromScreen (and there is no hack to fix that, apart from minimizing and maximazing the screen) i will consider option with injecting the code. But normally i would want to stay away from this one, as this could be treated as cheat and can lead to VAC ban.
====== UPDATE ======
You can try reproduce the process of taking screenshot by downloading the game (is small one, 260 MB):
https://cssetti.pl/Api/GameDownload.php?GameDownloadId=v43
Then you can copy-paste my code to Linqpad (or any other editor) and run the code. The application after launching will launch the HL process which is then use to try to grab the screenshot.
====== UPDATE 2 ======
In windows mode everything works correctly (the printscreens are ok)

How to workaround .NET Out of memory on GDI+ DrawArc() bug and find reference material

I know GDI+ is a bit antiquated, but I'm currently being forced to use it in in a WinForms app to draw complex CAD type models.
I encountered what I believe is the same issue as:
Why is my c# paint method running out of memory? as described by the currently accepted answer (https://stackoverflow.com/a/7501899/9300908) as well as some comments. I've ensured my brushes, pens, and fonts are disposed of as well as bitmaps and other gdi objects. I'm drawing 10s to 100s of thousands of arcs with the DrawArc() function and the same arc (1.13 degrees) each time is giving me trouble. I suspect I have encountered the small arc bug.
I'm planning to simply draw a line instead of an arc at whenever the conditions exceed a certain threshold, but I'm struggling to define the threshold. I'm trying to find a good source for what size is too small (I see references to < 1 degree, < 2 pixels, < 3.5 degrees) so that I don't need to rely on the exception handler especially when dealing with similarly small arcs.
The referenced answer as well as comments refer to a number of Microsoft Connect articles that are unavailable now that Microsoft Connect is now retired. I don't seem to have the magic sauce to find it in the visual studio developer community where I thought it might be moved to. Anyone have any reference material on the DrawArc bug or maybe an alternative workaround?
Connect references:
http://connect.microsoft.com/VisualStudio/feedback/details/121532/drawarc-out-of-memory-exception-on-small-arcs
http://connect.microsoft.com/VisualStudio/feedback/details/253886/graphics-drawarc-throws-outofmemoryexception-for-no-good-reason
Mentioned on msdn regarding DrawPath:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/4c0aa2b2-6555-4c6f-85fc-8467e86bdf20/systemdrawinggraphicsdrawpath-throws-outofmemoryexception-when-drawing-a-very-small-cubic-bezier?forum=netfxbcl
https://connect.microsoft.com/VisualStudio/feedback/details/182774
Other forums I've found point back to the referenced question.
Update
Environment is x64, .NET 4.5.2
1.13 is the sweepAngle (see below for the effective values). It and the startAngle are both native floats.
I can recreate the exception with this sample code from a new WinForms app.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// g is a graphics object created from a 1920W x 1080H Bitmap:
using (Bitmap mBitmapModel = new Bitmap(1920, 1080))
{
using (Graphics g = Graphics.FromImage(mBitmapModel))
{
float lStartAngle = -0.00816029962F;
float lSweepAngle = 1.13203466F;
RectangleF lRect = new RectangleF(742.741333F, 157.927505F, 3.28945208F, 3.28945208F);
using (Pen lPen = new Pen(Color.Blue, 2F))
{
g.DrawArc(lPen, lRect, lStartAngle, lSweepAngle);
}
}
}
}
}

Reduce time to take a screenshot to 40ms

I'm taking a screenshot, shrinking it down to 50% of resolution,to take less space and then I put bytes in the buffer, however unfortunately this process takes too much time.
Currently it takes from 60 to 100ms for 1920x1080 source resolution, this causes that the output video at 30fps is speeded up as I'm not producing screenshots fast enough. I need to achieve about 40ms to make video that I make out of it fluent.
Question: How can I apply a faster interpolation algorithm or limit the required operation to speed it up?
public void Screenshot(byte[] buffer)
{
using (var bmp = new Bitmap(Params.SourceWidth, Params.SourceHeight))
{
using (var g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(Point.Empty, Point.Empty, new Size(Params.SourceWidth, Params.SourceHeight), CopyPixelOperation.SourceCopy);
g.Flush();
using (Bitmap resized = new Bitmap(bmp, new Size(Params.TargetWidth, Params.TargetHeight)))
{
var bits = resized.LockBits(new Rectangle(0, 0, Params.TargetWidth, Params.TargetHeight), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
Marshal.Copy(bits.Scan0, buffer, 0, buffer.Length);
resized.UnlockBits(bits);
}
}
}
}
This approach is slow because you're using GDI, which is no longer the main graphics API in Windows (and hasn't been since Windows Vista). One of the reasons it's slow is because it copies image data from your GPU's memory (VRAM) into your computer's main memory (RAM) for it to be manipulated with through GDI.
If you want fast operations on your computer's framebuffer, use the DWM APIs and only copy memory between buffers in your GPU. You can probably also do the image resizing and other operations really fast by doing them as shaders or CUDA programs.

BitBlt failing on window rendered by OpenGL

I'm making a program to take a screenshot of a game while I'm streaming, so it take a screenshot and save it automatically, but when I set the game to use OpenGL my function fail, it keep saving the same image over and over again, it just change the image after I restart the game.
It seems it works in the first run, but on the next ones it keep saving the first image.
Here is what I'm using:
public static Bitmap PrintWindow(IntPtr hwnd) {
try {
RECT rc;
GetClientRect(hwnd, out rc);
IntPtr hdcFrom = GetDC(hwnd);
IntPtr hdcTo = CreateCompatibleDC(hdcFrom);
//X and Y coordinates of window
int Width = rc.right;
int Height = rc.bottom;
Bitmap bmp = null;
IntPtr hBitmap = CreateCompatibleBitmap(hdcFrom, Width, Height);
if (hBitmap != IntPtr.Zero) {
// adjust and copy
IntPtr hLocalBitmap = SelectObject(hdcTo, hBitmap);
BitBlt(hdcTo, 0, 0, Width, Height, hdcFrom, 0, 0, CopyPixelOperation.SourceCopy);
SelectObject(hdcTo, hLocalBitmap);
//We delete the memory device context.
DeleteDC(hdcTo);
//We release the screen device context.
ReleaseDC(hwnd, hdcFrom);
//Image is created by Image bitmap handle and assigned to Bitmap variable.
bmp = System.Drawing.Image.FromHbitmap(hBitmap);
//Delete the compatible bitmap object.
DeleteObject(hBitmap);
bmp.Save("saving.png", System.Drawing.Imaging.ImageFormat.Png);
}
return bmp;
}
catch {
}
return new Bitmap(0, 0);
}
If I change the game graphic to use DirectX it works good, it's just happening while using OpenGL, so not sude if it must be different for openGL windows or if it's impossible to capture those kind of window.
Using double buffered OpenGL is mutually exclusive with using GDI operations¹. Use glReadPixels to take a screenshot.
¹: Well, technically if you know what you're doing and take the right precautions you can mix them. But it's more trouble than it's worth doing.
After a recent windows 10 update, DirectX rendered windows now suffer from the same problem. I found a workaround, but you're not going to like it...
If you set hwnd = 0, the BitBlt now refers to the whole screen, instead of just one window. Then you can change BitBlt's source offset values to grab only the target window.
Although this works, its much slower than the original way you had it. :(
On my laptop, grabbing a 1080p window used to take 3ms, but now, using this workaround, it takes 27ms, which really ruins the streaming performance. :(
Still, its better than nothing.

How do screenshot in c# (low level may be) when desktop blocked?

I try get screenshot (windows 8) with code and get black screen when desktop blocked:
public static Bitmap ImageFromScreen()
{
Graphics gr;
Bitmap bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height,PixelFormat.Format32bppRgb);
gr = Graphics.FromImage(bmp);
gr.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y,
0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
return bmp;
}
You don't.
Apart from window cache (which isn't accessible, and doesn't really have to be there), the data simply isn't there. You'd have to fake WM_PAINT messages and force the application to draw to your surface (most controls will take the HDC from wParam; that still excludes a ton of badly written (or not Windows-native) applications - and even then, this will not work most of the time, like when the windows are minimized or the desktop is locked.
What is it that you're actually trying to do?
EDIT:
Okay, it's obvious that you're explicitly talking about the "locked screen" case - there's no way to get a screenshot of the desktop in that case - it doesn't really exist. The lock screen exists in a different session, so you no longer have any connection to the "hidden" user session. This is similar to trying to take screenshots on a server application after you've disconnected Remote Desktop - there's nothing to take shots of. It may be possible to force some applications to draw to your context, but nothing that would simply work.

Categories

Resources