Visual Layer : Redonnez le pouvoir à votre GPU


Back to the roots

Introduit par Windows Vista sous le nom Desktop Windows Manager (DWM), le moteur de composition (Windows Composition) n’est pas totalement récent en soit. Il a notamment été mis en place pour proposer l’expérience “Windows Aero”, assurant les fameux effets de perspective 3D et de semi transparence sur les fenêtres de Windows Vista.

vista_flip3d

Un peu de contexte technico-historique

Sous Windows XP, chaque application écrivait dans un buffer d’affichage commun pour effectuer le rendu à l’écran. Avec l’arrivée de DWM, une révolution silencieuse fait son apparition : chaque application dispose désormais d’un buffer séparé (le grand luxe) ! Ces snapshots sont ensuite récupérés puis (éventuellement) retravaillés par le moteur de composition pour effectuer le rendu à l’écran.

On passe donc d’une collocation sympa mais un peu désorganisé à des appartements séparés avec un propriétaire qui veille au grain

Ce moteur connaît l’état de chaque buffer et il maintient un arbre visuel (visual tree) des différentes sources ;  assurer les effets de transparence ou de perspectives entre les différents visuels (en résumé les fenêtres) est désormais presque un jeu d’enfant. Naturellement, il s’appuie sur DirectX, les opérations de composition sont effectuées par le GPU – libérant au passage le CPU de toute surcharge de travail non méritée.

L’histoire aurait pu en rester la – mais les équipes (depuis Windows 8) ont eu la bonne idée de doter ce moteur d’une API : DirectComposition (qui, comme son nom l’indique est liée intimement à DirectX).

Pourquoi ne pas prolonger l’arbre visuel avec des éléments graphiques qui appartiennent directement à mon application ? Comme DWM se charge déjà d’afficher le rendu de mon application et qu’il accède au GPU, autant en profiter pour lui déléguer des tâches (animations, effets) qui vampirise les ressources à mon thread UI. Et bien, c’est tout l’intérêt de ce jeu d’API !

What’s new pour la plateforme universelle ?

La nouveauté, c’est que l’API DirectComposition est désormais nativement proposée par WinRT (depuis la build 1511 du SDK) sous la terminologie Visual Layer (disponible dans le namespace Windows.UI.Composition).

En résumé, le Visual Layer va nous aider à créer des interfaces attrayantes, fluides et ainsi améliorer l’expérience de nos utilisateurs.

Le but n’est pas de remplacer les autres frameworks, mais de vous offrir un nouvel accès, plus proche du GPU, sans avoir besoin de mettre les mains dans DirectX (enfin presque). Xaml et le Visual Layer sont capables de coopérer main dans la main, rendez-vous en fin d’article sur ce point !

layers

Comment débuter ?

Dans un premier temps, je vous propose de nous passer totalement de code Xaml (Rassurez-vous, ce n’est que temporaire).

  1. Créez une Application vide (Windows universel) puis supprimer les fichiers App.xaml, App.xaml.cs, MainPage.xaml, MainPage.xaml.cs. On fait du vide !
  2. Ajoutez une classe DefaultView.cs à votre projet et copiez le code suivant :

Compilez, et lancez l’application. Si tout se passe bien, vous avez une fenêtre vide devant vos yeux émerveillés.

Le seul point à regarder en détail c’est la méthode SetWindow. Le reste du code permet d’instancier la fenêtre et de gérer correctement la boucle des messages.

Terminologie pour comprendre le reste du code :

  • Compositor : Classe principale, qui va permettre la communication entre le moteur de composition et l’application.
  • CompositionTarget : Représente la surface d’affichage de notre application.

Composition : Les basiques

Le Visual Layer travaille sur un arbre de composition formé de Visual. Les nœuds sont constitués des objets suivants :

  • CompositionObject : Classe de base.
  • Visual : Hérite de CompositionObject et implémente toutes les opérations principales.
  • ContainerVisual : Identique à la classe Visual mais dispose d’une propriété Children additionnelle.
  • SpriteVisual : Identique à la classe ContainerVisual mais permet d’associer un objet Brush au Visual.

Copiez le code suivant pour alimenter notre arbre de composition avec deux objets SpriteVisual nommée foreground/backround, respectivement en cyan/indigo.

spritevisual

Animations

Le moteur de composition met à disposition deux types d’animations.

KeyFrame Animation : Animation traitée sur une durée de temps. On définie des positions clés (au minimum une position de départ et une position de fin) – on laisse ensuite le moteur interpoler toutes les positions intermédiaires pour chaque frame affichée à l’écran.

Le code suivant permet de faire tourner linéairement l’objet foreground ; un tour complet (360°) est effectuée toutes les 3 secondes.

Expression Animation : Animation qui n’est pas dépendante du temps ; on calcule explicitement la position pour chaque frame, basée sur un calcul mathématique (une expression). 

Le code suivant permet d’effectuer une translation de l’objet background ; l’expression utilise une condition basée sur l’animation précédente  : l’angle de rotation de l’objet foreground.

La méthode SetReferenceParameter permet à l’expression de faire référence aux propriétés des objets Visual.

Pour aller plus loin : compositions animations

Effets

Jusqu’à maintenant, on a utilisé un CompositionColorBrush pour ‘remplir’ le Visual avec une couleur fixe. Avant d’appliquer un effet, je vous propose de charger une image dans un Visual, on bascule désormais sur un CompositionSurfaceBrush. L’étape pour charger une image dans ce type d’objet n’est pas directement pris en charge dans Windows.UI.Composition, vous devez faire appel à Microsoft.UI.Composition.Toolkit (ou utiliser le package nugget CompositionImageLoader) pour vous aider dans cette démarche. Le code suivant fait le reste (il faut adapter un peu le code si vous utilisez le package nugget):

visualimage

Pour aller plus loin sur les objets Brush proposés par le Visual Layer : composition brushes 

Saturation

La définition des effets s’appuie sur le même namespace que Win2D : Microsoft.Graphics.Canvas.Effects. Les effets non supportés actuellement par le le moteur de composition (il faut bien l’avouer c’est la majorité actuellement) sont listés avec l’attribut [NoComposition]. Même (si de ma compréhension) Win2D n’est pas utilisé directement pour faire le rendu de l’effet, il est nécessaire d’installer le nugget Win2D pour accéder aux déclarations des effets.

Pour la suite, voici comment effectuer un effet de (dé)saturation sur l’image :

saturation

Pour aller plus loin : compositions effects

Et le code Xaml dans tout ca ?

La classe ElementCompositionPreview nous vient en aide, elle permet de récupérer un Visual depuis un élément du Framework Xaml. Vous pouvez dès lors utiliser les connaissances précédentes pour appliquer une animation sur cet élément, comme dans le code ci-dessous :

Conclusion

Prometteur pour assurer de la fluidité à nos interfaces de demain, il manque cependant encore quelques briques pour que le Visual Layer prenne toutes ses lettres de noblesse (notamment des effets comme le blur et le drop shadow). Cette version laisse toutefois entrevoir de nouvelles possibilités et sans doutes quelques surprises dans les prochaines versions.

What’s next ? Je vous invite à regarder les exemples proposés par https://github.com/Microsoft/composition et la documentation msdn. N’hésitez pas à suivre @wincomposition pour vous tenir informé des prochaines nouveautés.

Comments (2)

  1. Thierry says:

    Salut Jean-sébastien, super article qui résume de façon clair la composition
    As-tu réussi a charger du xaml depuis un template ?
    J’ai fais plusieurs test et pense que ce code devrait marcher mais sans résultat pour l’instant :
    var template = MesResources[“mapItemImageTemplate”] as DataTemplate;
    var uiElement = template.LoadContent() as UIElement;
    var visual = ElementCompositionPreview.GetElementVisual(uiElement);

    1. Tu peux utiliser le VisualTreeHelper pour rechercher l’élément dans l’arborescence.

      MainPage.xaml :


      <Page.Resources>
      <DataTemplate x:Key="MyTemplate">
      <Image x:Name="MyImage" Source="Assets/windowscube.jpg" Stretch="Uniform" />
      </DataTemplate>
      </Page.Resources>
      <Grid x:Name="LayoutRoot" Background="{ThemeResource ApplicationPageBackgroundThemeBrush>
      <ContentPresenter ContentTemplate="{StaticResource MyTemplate}"/>
      </Grid>

      MainPage.xaml.cs :

      1/ Ajouter le code suivant dans le projet


      public static class FrameworkElementExtensions
      {
      public static FrameworkElement FindDescendantByName(this FrameworkElement element, string name)
      {
      if (element == null || string.IsNullOrWhiteSpace(name)) { return null; }

      if (name.Equals(element.Name, StringComparison.OrdinalIgnoreCase))
      {
      return element;
      }
      var childCount = VisualTreeHelper.GetChildrenCount(element);
      for (int i = 0; i < childCount; i++)
      {
      var result = (VisualTreeHelper.GetChild(element, i) as FrameworkElement).FindDescendantByName(name);
      if (result != null) { return result; }
      }
      return null;
      }
      }

      2/ Récupérer l'élément du DataTemplate simplement.


      var element = LayoutRoot.FindDescendantByName("MyImage") as UIElement;
      Visual visual = ElementCompositionPreview.GetElementVisual(element);

Skip to main content