しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

【Win10 Pre】 UWP アプリで表示項目の描画優先度をつける

Windows 10 では UI の描画性能を向上するいくつかの新機能が追加されています

x:Bind および x:Pahse もその1つで、この記述を XAML で利用することで UWP アプリの表示項目に描画の優先度をつけらます

ちょっと効果がわかりづらかったので、動作を確かめてみることにしました

Windows 10 Insider Preview Build 10240 時点での情報のため、正式リリース後仕様等が変更になっている可能性があります

Fickr から取得したたくさんの写真を GridView で表示する XAML を下記のように記述しました

<Page
    x:Class="PhasingSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PhasingSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    RequestedTheme="Dark"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <GridView x:Name="GridView"
                  ItemsSource="{x:Bind ViewModel.Photos, Mode=OneWay}">

            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid ItemWidth="100" ItemHeight="100" Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>

            <GridView.ItemTemplate>
                <DataTemplate x:DataType="local:PhotoItem">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="50"/>
                        </Grid.RowDefinitions>

                        <Rectangle Fill="#222222"/>

                        <!-- Photo -->
                        <Image Grid.RowSpan="2" 
                               Source="{x:Bind Uri, Mode=OneWay}" 
                               Stretch="UniformToFill"/>
                        
                        <Border Grid.Row="1" Background="#66000000" Padding="10,5">
                            
                            <StackPanel Orientation="Vertical">
                                
                                <!-- Title -->
                                <TextBlock Text="{x:Bind Title, Mode=OneWay}" 
                                           FontSize="9"
                                           Margin="0,0,0,1" 
                                           TextWrapping="NoWrap" 
                                           TextTrimming="CharacterEllipsis"/>
                                
                                <StackPanel Orientation="Horizontal">
                                    
                                    <TextBlock Margin="0,0,0,5" 
                                               Text="by "
                                               FontSize="9"
                                               TextWrapping="NoWrap" 
                                               TextTrimming="CharacterEllipsis"/>

                                    <!-- Owner name -->
                                    <TextBlock Margin="0,0,0,5" 
                                               Text="{x:Bind OwnerName, Mode=OneWay}"
                                               FontSize="9"
                                               TextWrapping="NoWrap" 
                                               TextTrimming="CharacterEllipsis"/>
                                </StackPanel>
                            </StackPanel>
                        </Border>
                        
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
            
        </GridView>

    </Grid>
</Page>

まずは比較用に x:Phase の記述をしていないバージョンです

コードビハインドは次のように記述します

/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
    /// <summary>
    /// MainPage ViewModel
    /// </summary>
    public MainPageViewModel ViewModel = new MainPageViewModel();

    /// <summary>
    /// Constructor
    /// </summary>
    public MainPage()
    {
        this.InitializeComponent();

        this.Loaded += this.OnLoaded;
    }

    /// <summary>
    /// Page loaded event handler
    /// </summary>
    /// <param name="sender">event sender</param>
    /// <param name="e">event aruments</param>
    private async void OnLoaded(object sender, RoutedEventArgs e)
    {
        this.Loaded -= this.OnLoaded;
        this.GridView.ContainerContentChanging += this.OnGridViewContainerContentChanging;

        await this.ViewModel.InilizeAsync();
    }

    /// <summary>
    /// GridView ContainerContentChanging event handler
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    private void OnGridViewContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
    {
        // One-third of items render after 1 ms 
        if (args.ItemIndex % 3 == 0)
        {
            new AutoResetEvent(false).WaitOne(1);
        }

        // Regist call back by occur next phase
        args.RegisterUpdateCallback(this.OnGridViewContainerContentChanging);
    }
}

画面の読み込み完了後にイベントハンドラ OnLoaded 内で InitializeAsync メソッドで写真の情報を ViewModel に 1 枚ずつ詰め込み GridView.ContainerContentChanging イベントの購読を開始しています

InitializeAsync メソッド内の処理については省略しますが、Flickr からの写真情報を 1 枚ずつ取り込んでいると思ってください

GridView.ContainerContentChanging は GridView 内のアイテムの中身が更新されると呼び出され、RegisterUpdateCallback で次の描画フェーズにもさらに呼び出されるように予約しています

普通に試すと描画が速すぎて効果がわかりにくいので、ContainerContentChanging イベントハンドラ内で 3 つのうち 1 つの表示項目の描画時に 1 ms の待ち時間が入るようにしてみました

この状態で実行した様子が次の通り

一気にタイル内の要素を表示しているため、一斉に画像が描画されることが体感的に遅く感じさせそうですね

スクロールさせると、仮想化の外のアイテムが描画されるため、3つずつ一気にタイル内の表示要素を描画しているのがわかります

次に先ほどの XAML から x:Phase の記述だけを追加しました

<Page
    x:Class="PhasingSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PhasingSample"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    RequestedTheme="Dark"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <GridView x:Name="GridView"
                  ItemsSource="{x:Bind ViewModel.Photos, Mode=OneWay}">

            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsWrapGrid ItemWidth="100" ItemHeight="100" Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>

            <GridView.ItemTemplate>
                <DataTemplate x:DataType="local:PhotoItem">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*"/>
                            <RowDefinition Height="50"/>
                        </Grid.RowDefinitions>

                        <Rectangle Fill="#222222"/>

                        <!-- Photo -->
                        <Image Grid.RowSpan="2" 
                               Source="{x:Bind Uri, Mode=OneWay}" 
                               Stretch="UniformToFill"
                               x:Phase="3"/>
                        
                        <Border Grid.Row="1" Background="#66000000" Padding="10,5">
                            
                            <StackPanel Orientation="Vertical">
                                
                                <!-- Title -->
                                <TextBlock Text="{x:Bind Title, Mode=OneWay}" 
                                           FontSize="9"
                                           Margin="0,0,0,1" 
                                           TextWrapping="NoWrap" 
                                           TextTrimming="CharacterEllipsis"
                                           x:Phase="1"/>
                                
                                <StackPanel Orientation="Horizontal">
                                    
                                    <TextBlock Margin="0,0,0,5" 
                                               Text="by "
                                               FontSize="9"
                                               TextWrapping="NoWrap" 
                                               TextTrimming="CharacterEllipsis"/>

                                    <!-- Owner name -->
                                    <TextBlock Margin="0,0,0,5" 
                                               Text="{x:Bind OwnerName, Mode=OneWay}"
                                               FontSize="9"
                                               TextWrapping="NoWrap" 
                                               TextTrimming="CharacterEllipsis"
                                               x:Phase="2"/>
                                </StackPanel>
                            </StackPanel>
                        </Border>
                        
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
            
        </GridView>

    </Grid>
</Page>

タイトル、所有者、写真画像の順にフェーズの数字を設定しています

x:Phase に指定された数字が小さいほど優先して描画されます

x:Phase に指定する数字はバインディングできず固定値で 1 からしか指定できないのと、x:Bind の事前コンパイルバインディングの利用が条件になるのでご注意ください

この状態で実行してみた様子は次の通りとなりました

タイトル、所有者、写真画像の順に描画されていますね

スクロールさせると、きちんと x:Phase に指定した順に優先的に描画しているのがわかります

フェーズ分けしている分だけ待ち時間は多くなっているのですが、体感的な遅さは改善したかと思います

今回のような単純な表示要素では、待ち時間をなくした実際の場合あまり差異はでないですが、もっと複雑な描画させたり表示要素がたくさんある場合は x:Bind と x:Phase を組み合わせることも効果的かと思います