Draw loading cover image during rendering

Joined
Jan 22, 2020
Messages
10
Programming Experience
10+
Hi,
I have simple single borderless fullscreen Form (now with enabled double buffering) application because of Forms applet that I want. The problem is simple, a mentioned applet reqiure full lazy redrawing, so it causes screen flickering. That I make fullscreen cover image for hide loading state of that applet. Funny is, that my cover image flickering too! :) Problem is combination Forms nad hard image size. Can it be easy to the fast draw cover image for avoiding background flickering? (Some screen drawing probably with some Windows lib or Nuget package.) I want result with no external requirements, just small lib if must it be. Also remember, that I want transitions in/out this cover image for really smooth loading...
 
That I know and this is why I am here. I want access lower into Screen buffer...
"here" happens to be the WinForms forum. WinForms doesn't have that kind of lower level access. Note the word "forms" in "WinForms".

In your first post you said:
(Some screen drawing probably with some Windows lib or Nuget package.) I want result with no external requirements, just small lib if must it be.
If you don't want external requirements, then using DirectX or DirectDraw is going to be eliminated because those are external dependencies. Even though it maybe installed on Windows, you'll still have to jump outside of WinForms to get to them. As it is, your current P/Invoke call to GetDC() is also technically an external dependency/requirement since again, you are jumping outside of WinForms. Granted that WinForms is built on WinUser and WinGDI, consider what happens when you run your code on .NET Core 3.0 on Linux where you may not have a real Windows DC to play with.
 
Since you are so reticent to provide some code that tries to demonstrate the problem, here is some code that tries to reproduce it.

To emulate your form that hosts your SKIA based "applet", I have a fullscreen MainForm which has a SKGLControl. Inside the control, a bouncing ball and the current time is rendered at a target frame rate.

Unfortunately, I can't reproduce the flickering while drawing on SKIA control that is hardware accelerated. The closest I could get was for some horizontal tearing to happen on my old desktop computer with an 8 year ATI/AMD graphics card. (I couldn't get the same tearing to happen on my laptop that has a relatively modern dedicated ATI/AMD graphics chip.) I got the tearing to show by using higher values for PixelsPerSecond in the Ball class (line 142).

Anyway with the tearing appearing, I then show the covering DesktopForm on top of the MainForm by pressing the Escape key (see lines 91-101). All this form does is render an upside down screenshot of the desktop. I deliberately render the screen upside down because I was confusing myself as to when I had my real desktop vs. a static screenshot of it. If you don't want it upside down, comment out the transform lines. Any which way, I'm just using normal painting. I really don't understand why you would need to P/Invoke GetDC().

Anyway, I'm not seeing any flicker or tearing on this new form that is covering the hosting form. I can't reproduce your problem where you said that the covering form flickers as it covers the hosting form.

If you are not convinced that the hosting form is actually continuing to render, try uncommenting line 26 which sets the opacity of the cover form to 50%. You will be able to see the ball continuing to move around under the covering form.

Dismissing the covering form by clicking on it or pressing a key, the hosting form gets shown without any issues. I'm not seeing any blinking or flickering as the original form draws when the cover goes away.

SkiaWinForms:
using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SkiaWinForms
{
    [DesignerCategory("Code")]
    class DesktopForm : Form
    {
        readonly Bitmap _bitmap;

        public DesktopForm(Bitmap bitmap)
        {
            _bitmap = bitmap;

            Bounds = Screen.PrimaryScreen.Bounds;
            FormBorderStyle = FormBorderStyle.None;
            DoubleBuffered = true;
            // Opacity = 0.50;

            Click += (o, e) => Close();
            KeyPress += (o, e) => Close();
            Paint += (o, e) =>
            {
                e.Graphics.TranslateTransform(_bitmap.Width, bitmap.Height);
                e.Graphics.ScaleTransform(-1, -1);
                e.Graphics.DrawImage(_bitmap, Point.Empty);
            };
        }
    }

    [DesignerCategory("Code")]
    class MainForm : Form
    {
        readonly Bitmap _screenshot;
        readonly Ball _ball;

        MainForm(Bitmap screenshot, Ball ball, int framesPerSecond)
        {
            _screenshot = screenshot;
            _ball = ball;

            Bounds = Screen.PrimaryScreen.Bounds;
            FormBorderStyle = FormBorderStyle.None;
            DoubleBuffered = true;

            var skcontrol = new SKGLControl() { Dock = DockStyle.Fill };
            skcontrol.PaintSurface += Skcontrol_PaintSurface;
            skcontrol.Click += (o, e) => Close();
            skcontrol.KeyPress += (o, e) => Close();

            var timer = new Timer() { Interval = (int)Math.Round(1000.0 / framesPerSecond) };
            timer.Tick += (o, e) => { _ball.Update(); skcontrol.Invalidate(); };
            timer.Start();

            Controls.Add(skcontrol);
        }

        private void Skcontrol_PaintSurface(object sender, SKPaintGLSurfaceEventArgs e)
        {
            var surface = e.Surface;
            var canvas = surface.Canvas;

            canvas.Clear(SKColors.White);

            canvas.DrawCircle(_ball.Position, _ball.Radius, new SKPaint()
            {
                IsAntialias = true,
                Color = SKColors.Green,
                Style = SKPaintStyle.Fill
            });

            canvas.DrawText($"{DateTime.Now}", 100, 100, new SKPaint()
            {
                TextSize = 64.0f,
                IsAntialias = true,
                Color = SKColors.Red,
                Style = SKPaintStyle.Fill
            });

            canvas.Flush();
        }

        protected override bool ProcessDialogKey(Keys keyData)
        {
            if (keyData.HasFlag(Keys.Escape))
            {
                using (var desktop = new DesktopForm(_screenshot))
                    desktop.ShowDialog();
                return true;
            }

            return base.ProcessDialogKey(keyData);
        }

        static Bitmap CreateScreenshot()
        {
            var rect = Screen.PrimaryScreen.Bounds;
            var bitmap = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
            using (var g = Graphics.FromImage(bitmap))
            {
                g.CopyFromScreen(rect.Location, Point.Empty, rect.Size);
            }
            return bitmap;
        }

        [STAThread]
        static void Main()
        {
            const int FramesPerSecond = 24;

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm(CreateScreenshot(),
                            new Ball(Screen.PrimaryScreen.Bounds, FramesPerSecond),
                            FramesPerSecond));
        }
    }

    class Ball
    {
        readonly Rectangle _bounds;
        SKPoint _vector;
        SKPoint _position;

        public SKPoint Position => _position;
        public float Radius => 200.0f;

        public Ball(Rectangle bounds, int framesPerSecond)
        {
            _bounds = bounds;
            _position.X = _bounds.Width / 2;
            _position.Y = _bounds.Height / 2;

            const int PixelsPerSecond = 150;
            var pixelsPerFrame = (float) PixelsPerSecond / framesPerSecond;
            _vector = new SKPoint(pixelsPerFrame, pixelsPerFrame);
        }

        public void Update()
        {
            (_position.X, _vector.X) = CheckBounds(_position.X, _vector.X, _bounds.Left, _bounds.Right);
            (_position.Y, _vector.Y) = CheckBounds(_position.Y, _vector.Y, _bounds.Top, _bounds.Bottom);

            (float newValue, float newVector) CheckBounds(float value, float vector, float min, float max)
            {
                value += vector;
                if (value + Radius > max)
                {
                    value = max - Radius;
                    vector *= -1;
                }
                else if (value - Radius < min)
                {
                    value = min + Radius;
                    vector *= -1;
                }
                return (value, vector);
            }
        }
    }
}

Code also available at: azureskydiver/SKIAWinForms
 
It was so stupidly easy. I want to avoid double form visible for user comfort, but it was only the problem. I just leave idea of one Form active and it resolve it. Just I open second Form, that it let previous Form rendering and after I can just Hide my cover Form when main Form rendered. Both Form DoubleBuffered and it make smooth loading at all. It was my question from begin - how to draw image to let Form render in background. And answer is, that I need second app just for managing cover. Now I just try to bind both Forms together in case, that user somehow change Window size, so my cover Form must have same position and size as covering Form... (I make some test before display cover, to make it well.) Also I am interested in, how can I hide dual Form icons of my app - to not making mess in taskbar etc... But really thank you for try to help...
 
Also I am interested in, how can I hide dual Form icons of my app - to not making mess in taskbar etc...
If you don't want a form to show in the taskbar, just set ShowInTaskbar to false. But if your main form is full screen as you said in post #1, then it'll be hiding the taskbar anyway, so I don't see the point. (More on fullscreen below.)

And answer is, that I need second app just for managing cover.
You don't need a second app. See how I managed with just a single application in post #18. The trick is to keep the message pump running. In post #18, I did that by using ShowDialog() to create a dialog. A modeless dialog will also work. (For details on modeless dialogs, see Modeless Dialogs in WinForms .)

What you essentially need is effectively a splash screen while your main form prepares to run. To do that, you simply show the cover form as a modeless dialog that is on top of your current form, and then when you are ready, you close the cover form.

Now I just try to bind both Forms together in case, that user somehow change Window size, so my cover Form must have same position and size as covering Form...
Assuming you are using a single app, whenever your main form gets a resize event, tell your cover form to also resize. If you had to 2 apps, then you'd create a custom WM_USER+n message and post that

I'm not seeing how your main form would resize in normal situations, though. In your post #1 you said:
I have simple single borderless fullscreen Form
That means that there won't be any resize handles if it is borderless.

The only time you'd get resizing is if you are running on a tablet and the user changes orientation, or if you on a PC or laptop and the user replaces the monitor. The probability of that happening is really low considering that you said that the flickering happens only when the main form is first shown. How long is that flickering period? Less than 1 second? Less than 3 seconds?
 
Windows 10 have multiple Desktops, so still can be shown in Taskbar. I have other gaming programmer in my office, that I know well, there is a lot of gaming modding software, that can make something unexpectional. So, I want to be able expect a lot as it is possible... :) So I will try it with single app as you suggest me. Thank you.
 
Yes, if you have the feature to show apps in other desktops to show up on the current desktop. But why would you do that? From my understanding, most (non-millenials) setup multiple desktops to declutter and focus on an particular tasks. Perhaps I don't understand the millenial attitude of wanting to be plugged in to multiple inputs at the same time. If you are going to be plugged in anyway, why setup multiple desktops since you want to see that content anyway.

I was going to put together some sample code to do the splash screen, but never mind. Anyway, I don't really understand what you are trying to do. Good luck.
 
You can see all applications in all Desktops. If not in TaskBar (optional), in Process Manager for sure. Just I not want to make mess with a lot records of same application... I not want to show my app in more Desktops, but user can make it. Also I am affraid, that my solution cause some problems in other Windows Systems, or etc. But if it will working in single form, it should be the best solution. Just I want to make it fast, clean and pro... Just I finish transition animation between forms, so for now I have everything for smooth swapping rendering.
 
Back
Top Bottom