Karol Szmaj

Karol Szmaj


Thoughts, stories and ideas about programming.

Karol Szmaj
Author

CTO at Whalla Labs, .Net, iOS Developer.

Share


Tags


Twitter


Wydajny render kafelków w Background Task'u.

Karol SzmajKarol Szmaj

Wraz z pojawieniem się pierwszej wersji systemu Windows Phone, Microsoft wprowadził nowy sposób interakcji z aplikacjami poprzez wykorzystanie kafelków. Każda aplikacja może posiadać jeden główny kafelek lub wiele dodatkowych. Wykorzystanie tego mechanizmu umożliwia użytkownikom przypinanie najważniejszych informacji do głównego ekranu na swoim telefonie. Pomysł okazał się na tyle ciekawy, że korporacja zdecydowała się go wprowadzić również do desktopowej wersji Windowsa. I tak już od kilku lat kafelki zagościły na dobre.

Z punktu widzenia użytkownika jest to bardzo wygodne rozwiązanie, z kolei programiści nie mają wcale tak łatwo - dlaczego? Otóż generacja i przypinanie kafelków w ramach procesu aplikacji nie sprawia większego problemu. System operacyjny daje nam do dyspozycji znacznie większą ilość pamięci niż w przypadku BackgroundTaska. Problem pojawia się, gdy nasza aplikacja ma zaktualizować kafelki w tle.

Aktualizację tile'a można przeprowadzić w ramach zadań realizowanych w tle (Background Task). Wspomniane procesy mają dużo większe restrykcje niż uruchomiona aplikacja. W najgorszej sytuacji programista do wyrenderowania kafla ma zaledwie 16MB pamięci operacyjnej. Taki przypadek zachodzi dla systemu Windows Phone, który działa na telefonie o bardzo słabej konfiguracji sprzętowej (512MB RAM).

Dwa podejścia do generacji kafli

Pierwszy mechanizm jest bardzo prosty, ponieważ SDK dla obu wersji systemów zapewnia wbudowane mechanizmy umożliwiające rysowanie kafelków w oparciu o przygotowane szablony XML. W tym przypadku programista dostaje bardzo wydajny mechanizm i nie musi się aż tak bardzo przejmować dostępną pamięcią. To system operacyjny jest odpowiedzialny za wyrenderowanie naszego szablonu.

Drugim sposobem jest własna generacja kafelka w oparciu o przygotowaną wcześniej kontrolkę. Ten mechanizm umożliwia wygenerowanie bitmapy, która ostatecznie zostanie wykorzystana jako główny element naszego Tile'a.

W tym poście skupię się na drugim podejściu, ponieważ często pisząc aplikacje na tę platformę spotykałem się z wymaganiami biznesowymi, które nie mogły być zrealizowane poprzez użycie pierwszego scenariusza.

Dostępne mechanizmy dla aplikacji Windows Runtime

W celu wyrenderowania kafla mamy dwa predefiniowane mechanizmy: RenderTargetBitmap oraz XamlRenderingBackgroundTask. Trzecią, niestandardową opcją jest Win2D.

RenderTargetBitmap

Przeznaczenie tej klasy jest bardzo proste, ponieważ odpowiada za wyrenderowanie wskazanego UIElement'u.

RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(); 
await renderTargetBitmap.RenderAsync(RenderedGrid, width, height); 
RenderedImage.Source = renderTargetBitmap;

Rozwiązanie jest proste i przyjemne w użyciu, lecz ma pewne poważne wady.

Pierwszą z nich są duże skoki użycia pamięci podczas generacji bitmapy. Wahania są na tyle duże, że łatwo przekroczyć wspomniane 16MB, nawet jeżeli rysowana kontrolka nie jest skomplikowana. W tym przypadku mamy bardzo duży narzut związany z samym drzewem elementów.

Drugim problemem, jest sytuacja związana z wyciekami pamięci. Nawet przy próbie wymuszenia dealokacji zasobów, pamięć nie jest zwalniana. Wielu programistów zgłaszało ten problem, a wątki na ten temat można znaleźć tutaj.

Kiedy nasza aplikacja musi zaktualizować kilka kafli, to szczerze nie polecam używać tej klasy ;)

XamlRenderingBackgroundTask

Jest nieco innym bytem niż poprzednia klasa. Koncepcyjnie jest to specjalny typ zadania w tle, który zoptymalizowany jest dla generowania drzewa XAML z naszej kontrolki. W teorii bardzo ciekawe rozwiązanie, lecz jeżeli nasza aplikacja napisana jest w C#, a zespół nie czuje się zbyt silny w języku C++, to opisywane rozwiązanie może nie przynieść oczekiwanych efektów. Dokumentacja Microsoft'u dosadnie przedstawia problem.

To keep the memory footprint of the background task as low as possible, this task should be implemented in a C++ Windows Runtime Component for Windows Phone. The memory footprint will be higher if written in C# and will cause out of memory exceptions on low-memory devices which will terminate the background task. For more information on memory constraints, see Support your app with background tasks.

Programiści korzystający z tego mechanizmu (w C#) niestety przekraczają limity dla słabych urządzeń, dlatego rozwiązanie nie daje 100% gwarancji na powodzenie aktualizacji kafelków.

Win2D

Win2d jest projektem rozwijanym przez Microsoft od dobrego roku. Całkiem niedawno pojawiły się wersje produkcyjne, które można było realnie wdrażać do projektów. Czym zatem jest wspomniany framework? Chyba najlepiej tłumaczy to skrócony opis na githubie.

Win2D is an easy-to-use Windows Runtime API for immediate mode 2D graphics rendering with GPU acceleration. It is available to C# and C++ developers writing Windows apps for Windows 8.1, Windows Phone 8.1 and Windows 10. It utilizes the power of Direct2D, and integrates seamlessly with XAML and CoreWindow.

Pisząc ten wpis spodziewałem się, że wyniki będą bardzo obiecujące i warto rozwinąć ten temat.

Założenia oraz testy

Proces wykonywany w tle ma ograniczony czas na zrealizowanie zdania, co dodatkowo utrudnia operację. Moje założenia wyglądały następująco:

Dodatkowo wszystkie kafelki aplikacji będą renderowane dla największego rozmiaru, czyli 691×336 pikseli.

Postanowiłem, że w tym poście przedstawię Wam wyniki mojej analizy, a cały kod będzie dostępny pod tym adresem.

Przygotowana aplikacja umożliwia wygenerowanie kafli zarówno podczas działania w tyle, jak i w foregroundzie. Każdy z wygenerowanych kafelków był ustawiony jako Wide. Render z poziomu aplikacji wygląda następująco.

Proces aplikacji po uruchomieniu zajmował około 14MB. Największy zarejestrowany skok pamięci oscylował w okolicy 16MB. Efekt całkiem dobry, lecz najważniejsze są wyniki zużycia pamięci w ramach procesu w tle.

W przypadku Background Taska i aplikacji skompilowanej w trybie Release, wykorzystywana pamięć wahała się od 7-8MB w momencie generacji bitmapy.
Poniżej zdjęcie ukazujące wyrenderowane kafle z Background Taska.

Ostatecznie proces wykonywany w tle zakończony był ze średnim użyciem pamięci wynoszącym 1,3 MB.

Podsumowanie

Win2d jest technologią, której warto poświęcić kilka minut. Framework świetnie dostosowany jest do aplikacji napisanych w języku XAML, ponieważ udostępnia nam specjalny typ Canvasa, którego rysowanie wspomagane jest sprzętowo.

Moim zdaniem projekt może bardzo mocno wspomóc programistów, którzy posiadają i rozwijają różnego rodzaju aplikacje związane z obróbką zdjęć i nie tylko.

Projekt mimo słabej kondycji mobilnej platformy Microsoftu nadal jest rozwijany. Pozostaje mi tylko trzymać kciuki i bacznie obserwować dalsze kroki zespołu projektowego.

Kod źródłowy testowanego rozwiązania dostępny jest pod tym adresem: https://github.com/karolszmaj/Win2d-BackgroundTask-LiveTiles

Karol Szmaj
Author

Karol Szmaj

CTO at Whalla Labs, .Net, iOS Developer.

Comments