0. Díl WPF - Rychlost vykreslování

C# .NET WPF 0. Díl WPF - Rychlost vykreslování

Ahoj, rozhodl jsem se začít s tutoriály pro WPF, protože mi přijde, že je pokročilejší než Windows Forms, alespoň pokud se jedná o vykreslování. Je tomu proto, že ve WPF se již nepoužívá GDI a jeho systém zpracování grafiky v procesoru, nýbrž se využívá DirectX3D, což je bezesporu velká výhoda. Již nedochází k problikávání při aktualizaci okna. Pokud jste nikdy nedělali ve Windows Forms, tak tyto tutoriály nejspíš moc nepochopíte , protože zde budu hodně porovnávat WF a WPF a popisovat rozdíly a jak přejít z WF na WPF.

WPF sice používá jazyk C# stejně jako WF, ale liší se tím, že na nastylování okna používá jazyk Xaml, který je podobný HTML kódu.

Jeho největší přednost vám ukáži na jednoduchém prográmku, který jsem si nazval pro názornost HalfHeight který si vytvořím zároveň ve WF i ve WPF.

V tomto "programu" se nám bude pohybovat obdélník, který zabírá celý form na šířku a půlku na výšku. Bude se pohybovat nahoru a dolů, tím můžeme sledovat rychlost vykreslování.

Zdrojový kód hlavního formu HalfHeight ve WF:

{
    public partial class Form1 : Form
    {
        // základní graphics formu
        Graphics g;

        // vedlejší graphics který neuvidíme
        Graphics tempGraphics;

        // bitmapa pro vytvoření vedlejších graphics
        Bitmap tempBitmap;

        // čas poslední snímku
        DateTime lastFrame;

        // obdelník pro zkoušku vykreslování
        Rectangle rectangle;

        // štětec s barvou
        SolidBrush brush = new SolidBrush(Color.Red);

        // proměnná zjištující pohyb obdelníku směrem dolů
        bool IsRectangleMovingDown = true;

        public Form1()
        {
            InitializeComponent();

            // vytvoří graphics hlavního formu
            g = this.CreateGraphics();

            // nastaví proměnou DateTime na teď
            lastFrame = DateTime.Now;

            // vytvoří nový timer
            Timer t = new Timer();

            // nastaví timer aby se spouštěl každou milisekundu (bude se spouštět méně často, ale menší hodnotu nastavit nelze)
            t.Interval = 33;

            // přidá event k timeru
            t.Tick += new EventHandler(t_Tick);

            // spustí timer
            t.Start();
        }

        protected override void OnLoad(EventArgs e)
        {
            // vytvoří novou bitmapu s velikostí okna
            tempBitmap = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);

            // vytvoří vedlejší graphics podle bitmapy
            tempGraphics = Graphics.FromImage(tempBitmap);

            // deklaruje nový obdelník
            rectangle = new Rectangle(0, 0, this.ClientSize.Width, this.ClientSize.Height / 2);
        }

        void t_Tick(object sender, EventArgs e)
        {
            // vyčistí vedlejší graphics
            tempGraphics.Clear(Color.White);

            // vykreslí obdelník do vedlejších graphics
            tempGraphics.FillRectangle(brush, rectangle);

            // vykreslí vedlejší graphics do formu
            g.DrawImage(tempBitmap, Point.Empty);

            // pohyb obdelníku nahoru a dolů
            if (IsRectangleMovingDown)
            {
                if (rectangle.Y < this.ClientSize.Height / 2)
                {
                    rectangle = new Rectangle(rectangle.X, rectangle.Y + 25, rectangle.Width, rectangle.Height);
                }
                else
                {
                    IsRectangleMovingDown = false;
                }
            }
            else
            {
                if (rectangle.Y > 0)
                {
                    rectangle = new Rectangle(rectangle.X, rectangle.Y - 25, rectangle.Width, rectangle.Height);
                }
                else
                {
                    IsRectangleMovingDown = true;
                }
            }

            // přepočítání času
            TimeSpan elapsed = DateTime.Now - lastFrame;

            // zobrazí počet uplynulých ms od posledního updatu
            label1.Text = elapsed.TotalMilliseconds.ToString();

            // zapsání nového času
            lastFrame = DateTime.Now;
        }
    }
}

Zdrojový kód hlavního formu HalfHeight ve WPF:

XAML část:

<Window x:Class="HalfHeight_WPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" WindowState="Maximized" Loaded="Window_Loaded">
    <Grid Name="grid">
        <Rectangle HorizontalAlignment="Left" Name="rectangle1" Stroke="Black" VerticalAlignment="Top" Fill="Red" Width="100" Height="100" />
        <TextBlock HorizontalAlignment="Left" Name="textBlock1" Text="TextBlock" VerticalAlignment="Top" Background="Gray" Margin="12,12,0,0" />
    </Grid>
</Window>

C# část:

{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        bool IsRectangleMovingDown = true;

        DateTime lastFrame;

        public MainWindow()
        {
            InitializeComponent();

            lastFrame = DateTime.Now;

            DispatcherTimer t = new DispatcherTimer();
            t.Interval = TimeSpan.FromMilliseconds(1);
            t.Tick += new EventHandler(t_Tick);
            t.Start();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            rectangle1.Width = grid.ActualWidth;
            rectangle1.Height = grid.ActualHeight / 2;
        }

        void t_Tick(object sender, EventArgs e)
        {
            if (IsRectangleMovingDown)
            {
                if (rectangle1.Margin.Top < grid.ActualHeight / 2d)
                {
                    rectangle1.Margin = new Thickness(rectangle1.Margin.Left, rectangle1.Margin.Top + 25, 0, 0);
                }
                else
                {
                    IsRectangleMovingDown = false;
                }
            }
            else
            {
                if (rectangle1.Margin.Top > 0)
                {
                    rectangle1.Margin = new Thickness(rectangle1.Margin.Left, rectangle1.Margin.Top - 25, 0, 0);
                }
                else
                {
                    IsRectangleMovingDown = true;
                }
            }

            TimeSpan elapsed = DateTime.Now - lastFrame;
            textBlock1.Text = elapsed.TotalMilliseconds.ToString();

            lastFrame = DateTime.Now;
        }
    }
}

Porovnání

Na mém počítači se mi ukazoval ve WF čas od posledního snímku 90 - 110 ms (cca 10 FPS) a ve WPF 10 - 20 ms (cca 50 - 100 FPS).

Ve WF tam byl vidět jasně trhaný pohyb, zatímco ve WPF nebyl vidět jediný zásek.

Doufám, že to postačí jako důkaz jak rychle je WPF schopno vykreslovat narozdíl od WF, zdrojové kódy HalfHeight ve WF i WPF budou přiloženy.

V příštím díle si ukážeme základní kontrolky WPF.


 

Stáhnout

Staženo 409x (65.21 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

  Aktivity (1)

Článek pro vás napsal Theodor Johnson
Avatar
Autor má většinou na svědomí projekty v jazyce C#.

Jak se ti líbí článek?
Celkem (5 hlasů) :
3.63.63.63.6 3.6


 



 

 

Komentáře
Zobrazit starší komentáře (4)

Avatar
Theodor Johnson
Redaktor
Avatar
Theodor Johnson:

jo, je to zpíš úvodní díl, proto nultý

Odpovědět  +1 25.8.2013 14:14
Přecházím na "Cross-Platform Development"
Avatar
lastp
Redaktor
Avatar
lastp:

Metoda DateTime.Now se na měření času nehodí, protože má malou přesnost (zhruba 10ms). Na benchmarky je lepší použít třídu Stopwatch.
U Windows Forms aplikace jsem naměřil 20 FPS. U WPF aplikace jsem naměřil 60 FPS, ale procesor byl vytížen na méně než 10%.

 
Odpovědět 11.12.2013 13:46
Avatar
Jan Vargovský
Redaktor
Avatar
Odpovídá na lastp
Jan Vargovský:

To protože vykreslování neřeší CPU, ale GPU.

 
Odpovědět 11.12.2013 13:57
Avatar
Odpovídá na lastp
Luboš Běhounek (Satik):

60fps?
Tak to nejspíš ještě brzdí vertikální synchronizace :)

Odpovědět 11.12.2013 14:07
:)
Avatar
Theodor Johnson
Redaktor
Avatar
Odpovídá na lastp
Theodor Johnson:

Vím o tom, ale pro názornost to postačí, je to stejná třída, takže bude mít stejnou nepřesnost jak ve WF, tak i ve WPF

Odpovědět 11.12.2013 20:35
Přecházím na "Cross-Platform Development"
Avatar
Neaktivní uživatel:

mrkněte na obrázek

Odpovědět 31.12.2013 4:54
Neaktivní uživatelský účet
Avatar
Ondrca
Redaktor
Avatar
Ondrca:

V programování jde všechno od nuly ;)

Odpovědět 13.4.2014 13:44
Zase jsem o něco chytřejší
Avatar
pracansky
Člen
Avatar
pracansky:

Nepochybuji o tom že WPF má své výhody ale myslím si že zrovna tento příklad je zavádějící. Porovnáváš neporovnatelné. Ve WPF posouváš nakreslený objekt zatímco ve WF rendruješ bitmapy přes procesor a to ještě 2x.

Upravil jsem trochu ten WF projekt. Přidal jsem tam červený panel (panel1) což je obdoba toho rectagle ve WPF a posouvám ho ho stejným způsobem jaky ty ve WPF.

Změnil jsem časování na 1ms aby to sedělo s tím WPF příkladem a dostal jsem stejný výsledek jako WPF. Zatížení procesoru <1% a naprosto plynulý pohyb. Všechno šlo přes GPU. Tetováno na win7.

Předpokládám že použití Graphics ve WPF by bylo stejně pomalé jako je tomu ve WF (ještě to zkusím), takže ten rozdíl není ve WPF ale v použití.

Kdo nevěří ať si to zkusí :)

public partial class Form1 : Form
{
    DateTime lastFrame;
    Rectangle rectangle;

    bool IsRectangleMovingDown = true;

    public Form1()
    {
        InitializeComponent();

        lastFrame = DateTime.Now;

        Timer t = new Timer();
        t.Interval = 1;
        t.Tick += new EventHandler(t_Tick);
        t.Start();
    }

    protected override void OnLoad(EventArgs e)
    {
        rectangle = new Rectangle(0, 0, this.ClientSize.Width, this.ClientSize.Height / 2);
        panel1.Location = rectangle.Location;
        panel1.Size = rectangle.Size;
    }

    void t_Tick(object sender, EventArgs e)
    {
        panel1.Location = rectangle.Location;

        // pohyb obdelníku nahoru a dolů
        if (IsRectangleMovingDown)
        {
            if (rectangle.Y < this.ClientSize.Height / 2)
            {
                rectangle = new Rectangle(rectangle.X, rectangle.Y + 3, rectangle.Width, rectangle.Height);
            }
            else
            {
                IsRectangleMovingDown = false;
            }
        }
        else
        {
            if (rectangle.Y > 0)
            {
                rectangle = new Rectangle(rectangle.X, rectangle.Y - 3, rectangle.Width, rectangle.Height);
            }
            else
            {
                IsRectangleMovingDown = true;
            }
        }

        TimeSpan elapsed = DateTime.Now - lastFrame;
        label1.Text = elapsed.TotalMilliseconds.ToString();

        lastFrame = DateTime.Now;
    }
}

Mimochodem ten DateTime.Now není vhodný na časy v milisekundách.

 
Odpovědět 2.5.2015 12:46
Avatar
coells
Redaktor
Avatar
coells:

Mám dotaz na autora článku - proč jako výhodu WPF proti WF ukazuješ jednu z nejvíce zpackaných věcí, která ve WPF je?

WPF je graficky akcelerované, má omezení na 60 FPS (ano, ano, odhad 50-100 FPS pro WPF je nesmysl) a posouvání jedné grafické komponenty přes druhou (přičemž obě jsou cacheované na GPU) zabírá 10% procesu. WTF?! Není náhodou s WPF něco špatně? O tomhle problému se ví už od bety a dodnes s tím nic neudělali.

Navíc, jak už kolega výše trefně podotknul, ve WPF používáš pro animaci layout, zatímco ve WF ručně renderuješ - ke všemu způsobem, který se odjakživa hrubě nedoporučoval. A už ani nebudu zmiňovat fakt, že k tomu pleteš vykreslování písma a zahnuješ to do jednoho měření.

Takovýhle článek je pouhá fabulace, takže si doporučuji nejdřív něco o WPF přečíst, pochopit, jak funguje CPU vs. GPU a pak psát low-level články.

 
Odpovědět  -1 2.5.2015 18:22
Avatar
Theodor Johnson
Redaktor
Avatar
Odpovídá na coells
Theodor Johnson:

K článku už se moc vyjadřovat nebudu, co jsem potřeboval napsat tak je v článku nebo komentářích. Spíše ti doporučuji se podívat na tuto sérii od Davida Čápky. Já už jsem po (tuším) pátém díle neměl moc na psaní čas tak jsem tuto sérii neupdatoval ani jsem v ní nepokračoval. :)

Odpovědět 2.5.2015 18:34
Přecházím na "Cross-Platform Development"
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 14. Zobrazit vše