プログラムの事とか

お約束ですが「掲載内容は私個人の見解です」

Windows 10 April 2018 Update (1803) に関するあれこれ

体験談でしかなく裏取りは相変わらずしていないのであしからず

入れたPC

今のところLaptop以外は問題なさそう

Laptopは

からのWindows 10 Sから入れ直し→1803でUEFI突入であきらめてサポートとチャット

サポートさんに

answers.microsoft.com

を教えてもらい1709に戻して使用中

という久々にトラブルシューティングゲームして楽しかった(楽しくなかった

他のPCはインテルSSDが高いからほかのにしてたおかげて助かったのか?

最近のアップデートは優秀でこんな感じの異常はPreview時に全部見つかっていると思い込んでいたのが敗因ですね(すっかりハードの異常だと思って交換する気満々でした)

IEの動作が変わった?

これはVisual Studio 2017側かもしれません

  • ソリューションに複数のWebプロジェクトを入れておく
  • デバッグ時に立ち上がるブラウザはIE
  • デバッグをマルチスタートアップにして両方起動する

って感じにしておいた場合、今まではIEがそれぞれ立ち上がっていたんですが、今はIE一つが立ち上がって一つのタブに両方表示しようとします(結果的に遅い方のページが表示される)

なんか昔こんな感じの動作への切り替えがはやったような気がしますがすでに過去の事なので(新しく立ち上げるのか既存に突っ込むのかっていう、あの・・・何とかって技術)

まぁWebサービス (IIS Expressで立ち上がるやつ)は両方立ち上がっているので問題ないですし、そもそも複数のWebプロジェクトを同時にデバッグとか私以外にいないんじゃないかという感じなので気にしない

まとめ

みんな早くアップデートするといいよ (とくにSurface勢)

WPFで速い描画方法が知りたい!

WPFで描画処理書いていてもっと速く描きたい!(ていうか自分の書いた処理遅い!)ということで実験を兼ねてプロジェクト作ってあわよくばすごい人に速い方法教えてもらおう!

プロジェクトはこちら

github.com

簡単な説明

class CustomDrawControl : FrameworkElement
{
    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        switch (DrawType)
        {
        case DrawType.NotFreeze:
        case DrawType.Freeze:
        case DrawType.Grouping:
        case DrawType.BackingStore:
        }

        _counter++;
        Task.Run(async () =>
        {
            await Task.Delay(1);
            await Dispatcher.BeginInvoke((Action)this.InvalidateVisual);
        });
    }
}

こんな感じでOnRender内でいろんな方法で描画してみてFPSを確認してみます

FPSの算出方法間違ってるとかそもそもOnRender使っちゃダメとかそういう突っ込みあったら教えてください)

描画はフレーム毎に色が変わるペン(パレットは8色)で点(短い線)をたくさん描きます

計測

最大FPS

何も描かないで上の処理のようにOnRenderTask.Delay(1) -> InvalidateVisual()ってやるだけで大体64fpsでした

f:id:puni-o:20180411131559j:plain

もっと速くなると思っていたのでこの再描画方法がそもそも間違っているんじゃないか疑惑が・・・

FreezableをFreezeした場合としなかった場合

遅い → Freeze()シロ っていう流れはググるとよく出てくるので実際に比較してみました

Freezeしない

f:id:puni-o:20180411131908j:plain

最大で 20 fps くらい

Freezableな奴を片っ端からFreezeする

f:id:puni-o:20180411131923j:plain

最大 64 fps

Freezeシロ

点の数は3000個で実験したのですがここまで差が出るとは

ちゃんとFreezeしようね


3パターンで計測

本番

ですがネット調べたけど私には3パターンしか見つけられませんでした

ここからは点の数を10000個で実験です


2018/4/12

デバッグビルドで計測していたり、一つは完全に間違った処理だったりなので以下はスクショ入れ替えてます


1点ずつ描画

private void FreezeRender(DrawingContext drawingContext)
{
    foreach (var p in _particles)
    {
        drawingContext.DrawLine(penList[p.Pallete], new Point(p.X1, p.Y1), new Point(p.X2, p.Y2));
    }
}

こんな感じで描きます(余計なの消しているので全部見たければGitHubからどうぞ)

f:id:puni-o:20180412103100j:plain

結果は50 fpsちょいでした

パレット毎にまとめて描画

パスはGeometryにまとめて描画した方が速いみたいな書き込みをみたので、パレット毎に一気に描画してみます

private void GroupingRender(DrawingContext drawingContext)
{
    foreach (var g in _particles.GroupBy(p => p.Pallete))
    {
        var geometry = new StreamGeometry();
        using (var context = geometry.Open())
        {
            foreach (var p in g)
            {
                context.BeginFigure(new Point(p.X1, p.Y1), false, false);
                context.LineTo(new Point(p.X2, p.Y2), true, false);
            }
        }
        drawingContext.DrawGeometry(null, penList[g.Key], geometry);
    }
}

余計なの消していますが(以下略

f:id:puni-o:20180412103124j:plain

60 fps以上出ているのでまだ余裕がありそうです(UIの操作が普通にできるので)

ダブルバッファリング?

DrawingGroup作ってそっちに描いてOnRender時はそれを描け的なことを見かけたので実験

昔懐かしのダブルバッファリングなのだろうか?

readonly DrawingGroup _backingStore = new DrawingGroup();
private void BackingStoreRender(DrawingContext drawingContext)
{
    var context = _backingStore.Open();
    foreach (var p in _particles)
    {
        context.DrawLine(penList[p.Pallete], new Point(p.X1, p.Y1), new Point(p.X2, p.Y2));
    }
    context.Close();
    drawingContext.DrawDrawing(_backingStore);
}

f:id:puni-o:20180412103226j:plain

10 fpsくらい

backingStore使って~っていうのは多分こういう使い方じゃないんだよね(OnRenderで事前に描画しておいたのを表示とかそういう使い方かな?)

まとめて描画できるならまとめよう

とりあえずまとめて描けるならまとめた方が速そう


2018/4/16 追記

  • FPSCompositionTarget.Renderingで測るみたいなのでそっちに移しました

  • DrawType.WriteableBitmapを追加しました

スクショとかないけど一番速かったです

雑な測定結果はGitHubの方にまとめてあるのでそちらを参照ということで・・・


まとめ

WPFの便利なもろもろを使いつつ速い描画を求めるならWriteableBitmapになるんですかね?

これより速そうなのだとDirectXくらいしか思いつかないんですが、それだと自由度が無くなりすぎだと思うんですよね(透過できないのが痛い)

WPFのDrawingContextでアニメーション

DrawingContextの謎メソッド(自分の中で)の実験

WPFでちょっと変わった表現をしたい時とか結局自分で描きますよね(WPFに限らずですが)

例えば

public class DrawTest : FrameworkElement
{
    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        var center = new Point(this.ActualWidth / 2, this.ActualHeight / 2);

        drawingContext.DrawEllipse(Brushes.Orange, null, center, 20, 20);
    }
}

こんなクラス作ってMainWindowに張り付けると

f:id:puni-o:20180409100051j:plain

こんな感じに中央にオレンジの円を描画します

上で使ったDrawingContext.DrawEllipseが円を描くメソッドなわけですがこいつには

public abstract void DrawEllipse(Brush brush, Pen pen, Point center, double radiusX, double radiusY);
public abstract void DrawEllipse(Brush brush, Pen pen, Point center, AnimationClock centerAnimations, double radiusX, AnimationClock radiusXAnimations, double radiusY, AnimationClock radiusYAnimations);

こんな感じにオーバーロードな奴がもう一つあります

AnimationClockを引数に持つんですが、AnimationClockってことはアニメーションしてくれんの?でもアニメーションってOnRenderで一コマずつ描くんじゃないの??どういうこと???といつも疑問に思って試していませんでした

ためしてみよう

簡単に試せるんだから試してみました

protected override void OnRender(DrawingContext drawingContext)
{
    base.OnRender(drawingContext);

    var center = new Point(this.ActualWidth / 2, this.ActualHeight / 2);

    var pointAnimation = new PointAnimation(new Point(this.ActualWidth, this.ActualHeight / 2), new Duration(TimeSpan.FromSeconds(3)));
    pointAnimation.AutoReverse = true;

    var doubleAnimation = new DoubleAnimation(20, 5, new Duration(TimeSpan.FromSeconds(5)));
    doubleAnimation.AutoReverse = true;

    drawingContext.DrawEllipse(Brushes.Orange, null, center, pointAnimation.CreateClock(), 20, doubleAnimation.CreateClock(), 20, doubleAnimation.CreateClock());
}

OnRenderを書き換え

centerAnimationsにはPointAnimationを、radiusAnimationsにはDoubleAnimationをつっこみます

これで実行すると

f:id:puni-o:20180409100748g:plain

うごいているよぉぉぉ~~~

OnRenderはちゃんと(?)1回しか呼ばれてません

予想通りだったんだけど予想通りでびっくりしました

また一つWPFに詳しくなったぞ