WPFでFreeze

今日、飲み会をしました。
メンバー的には関西MS系勉強会でおなじみのディープテクノロジーでおなじみのよねさんとデザイン&グラフィックスでお馴染みの若社長であるみつばたんと女性エンジニアの4人飲み会。

そこで、WPFとDirect2Dの連携をオフスクリーンレンダリングとImageコントロールで解決するってパターンでもそこそこ速い。(要出典)
って話を耳にした。実はちょっとレンダリングコストの話は後回しにしようかと思っててんけど、そう言われちゃうと「そうなん?」ってなるやんか。だって人間だもの。

で、もっぺんWPFボトルネックを確認しようと思って、10万本の線を引いたらどれくらいかかるか図りなおしたわけ。
ざっくり書くとこういう感じ。

static Pen pen = new Pen(Brushes.Black, 0.3);
--snip--
protected override void OnRender(DrawingContext drawingContext)
{
    var sw = new Stopwatch();
    sw.Start();
    for (var i = 0; i < 100000; i++)
    {
        var x1 = rnd.Next() % 10000;
        var y1 = rnd.Next() % 10000;
        var x2 = rnd.Next() % 10000;
        var y2 = rnd.Next() % 10000;

        var p1 = new Point(x1, y1);
        var p2 = new Point(x2, y2);
        drawingContext.DrawLine(pen, new Point(x1, y1), new Point(x2, y2));
    }
    sw.Stop();
    Debug.WriteLine(sw.ElapsedMilliseconds);
}

実はメンバに持ってるPenをFreezeせんかったら1FPS出ないくらい糞重い。(なんで?)
それをFreezeさせたら10万本の半透明の線が40msで描ける。20FPSぐらいか。
普通に考えたら10万本を無造作に描くことないし、文字描画は恐らく100倍くらい遅いやろけど30FPS出るなら文字だけ遅延レンダリングとかする手でなんとでもなりそう。

いろいろ作戦考えてたけど、ひとまずDirect2D使わんとWPFで実装進める方向で行く気になった。(Direct2Dのオフスクリーンレンダリングは切り札)

10万本引くとこんな感じ。5000エンティティやったとしても、こんなに描く必要ないわな。
WPFでもサクサク動く確率が高いような気がしてきた。(飲み会の後なので、べろんべろんの思考力!)

ボチボチ進めていこう。レンダリング以外のとこの方がやること多いんやしね。

ホットウーロン茶でべろんべろんに酔っぱらったのは私だけ。

シンタックスとセマンティックス(補足)

TMの文脈における狭義のシンタックスとセマンティックスについて俺っちなりの解釈を補足しときます。

  • シンタックス(構文論)とは、TMのルールにのっとってTMDを「どう描くか」
  • セマンティクス(意味論)とは、TMDが「どういう意味を持つか」

ってことになります。
例えば、プログラムの世界で代入式は=で、行末に;が必要とするプログラミング言語があるとする。

a = b; (これはセマンティクス上正しい)
a b = (これはシンタックスエラー)

TMで言えば2組みのリソースから複数の対照表が出るのはシンタックスエラーです。
イベントの個体識別子がリソースに(R)として入ってもシンタックスエラーです。

構文として同じ形式を持つ2つ式があるとして。

a = 32;
a = "ABC";

どちらもシンタックスとしては正しい。意味は違います。

その程度の話。

TMについてひとりごち その2

俺っちがTM(当時のT字形ER手法)に出会ったのは1999年なので17年近く前。
SRCのセミナーを聴いてもさっぱりわからんかったね。
そもそもRDBってのを触ったことないし、業務システムを当時はまだ作ったことも触ったこともなかった。

実は当時からTMDを作図するためのアプリケーションが2つあった。マサミさん非公認のやったと思うけど。
自分でアプリケーションを設計する上で一番優先したのは、その2つの先行者に似せないこと。
要するにパクったって言われるのも、パクったって思われるのも嫌やったって話。パクられるのは別に良い、真似たなるほどよくできてるってことやから。

さらに言えば、俺っちは当時からUI/UXに相当なこだわりがあったので、その点は絶対に負けないようには考えてた。
とはいえ、.NETはまだない、CPUなんてPentiumⅢが出始めた頃でWindows95上で動かさなアカン・・・書いててわらけるな。
VC++6.0での開発やった。もちろん、先行の2つの製品も同じ環境やったわけやけど。

ぶっちゃけTM自体がマイナーやったはずなので牧歌的な時代やったのかも知らんけどね。
ま、本音を言えばそれよりつらかったのはTMのシンタックスに準拠するって制限がある中で、TM自体がセマンティック上の理由からシンタックスを少しづつ変化させてってことにある。
それは今でも同じなのかもしんないけどねw

マサミさんの中ではその変化は一貫性があったのかも知らんけど、TMのシンタックスを守るだけでもシステム開発におけるデータモデリングのメリットが十分に発揮されてたので、従来のTMファンの人たちの間では葛藤あったんじゃないのかなあ。
マサミさん的に、「L-真とってF-真で験正するやり方」ってのは俺っちの言葉言うと「Tの字のルールを使って、その結果が実際の業務に合致してるかチェックすることでモデルの正しさを担保する」ってことになる。(ほんとかどうかは知らん)
余談
 ネット上にTMについて書かれてる資料が本家以外のがあまりにも少ないのは、マサミさん自身が反主流志向が強いってだけじゃ無くて、TMについて俺はこう思う、俺はこう理解してるって言いにくい難解さがあるからなんじゃね?こんなこと書いたらマサミさんにこのMMがーとかアホがって思われんっちゃうかってドキドキしちゃうw

もとい、シンタックスを守るだけでメリットが十分に発揮されるってのはなんでかと言うと、「L-真のみ保障したTMD」も「F-真をきんと験正したTMD」も「物理実装しちゃえば同じ形」になるからなのな。
表記がMOやろうとER(エンティティロール)やろうと、(R)がRefereceKeyやろうとRe-usedやろうと物理実装後には何の関係もない。だから、そこに重心を置いてるTM利用者にとってはぶっちゃけTMを変更されることの方がつらい。

ここまでをまとめると

  • TMのシンタックスとして正しいモデルとそのモデルの意味が正しいかどうかは一致しない。
  • TMのシンタックスとして正しいモデルとセマンティックス上正しいモデルも物理実装形は同じ。
  • 物理実装形を重視するならTMのシンタックスを守るだけで十分。(効き目がでる)

具体的な例でいえば
2つのテーブル(A、B)に 1 : n の関係があるとする。
この時、モデル上 1: n のカーディナリティがあったとして、実際にA/B テーブルのデータがそうなるかどうかはプログラム依存になる。言い換えればカーディナリティが n : n だったとしても、プログラム上のアルゴリズムで 1 : n に制限することができる。ただし、モデルとしては n : n にとしか読み取れない。

これが是か非かって話なのな。
ビジネスプロセスにおけるデータ構造は大まかにTMのシンタックスを守るだけで担保できる、でもそこまでやとモデルでは表現できない制約をプログラムでのみつけることになる。
マサミさんがどう思ってるかはともかくとして、これを是だと考えるのもありやと思う。すべてのTM利用者が原理主義になる必要はないと思うし、メリット/デメリットをコストも踏まえて選択すれば良い。

平易に自分の言葉で書こうとしてもやっぱ難しくなっちゃうんだよなあ。俺っち文学青年やないんやけどなあ。

つづくかも。

ダレトク実況3

WPFのGridで矩形を重ね合わせして、下(奥)のレイヤーのRenderTransformをずらして半透明の影をつけた。

    <Grid>
        <!--影の部分-->
        <Grid Background="#40000000" RenderTransformOrigin="0.5,0.5">
            <Grid.RenderTransform>
                <TransformGroup>
                    <ScaleTransform/>
                    <SkewTransform/>
                    <RotateTransform/>
                    <TranslateTransform X="5" Y="5"/>
                </TransformGroup>
            </Grid.RenderTransform>
        </Grid>
        <!--エンティティ本体-->
        <Grid Background="#FEFEFE">
            <Grid.RowDefinitions>
                <RowDefinition MinHeight="40"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Grid Grid.Row="0">
-- snip --

ま、1個だけ出しても半透明もくそもないね。

拡大縮小と移動の座標変換どうするっかなあ。
ワールド座標系とスクリーン座標系の変換だけ持って、左上オフセットさせた奴をWPFのRenderTransformに食わせるとかイケるかな。

    public class Transform2D
    {
        public float OffsetX { get; set; }
        public float OffsetY { get; set; }
        public float Scale { get; set; }

        PointF WorldToLocal(PointF world)
        {
            return new PointF(world.X * Scale, world.Y * Scale);
        }
        PointF LocalToWorld(PointF world)
        {
            return new PointF(world.X / Scale, world.Y / Scale);
        }
    }

クリッピングWPF任せやとするとクリッピング領域(≒画面上で描画されてる範囲)もいらんわなあ。
ここいらは本番はDirect2Dで描くつもりなんで、実はテキトー

実際問題この辺ってWPFに完全に乗っかるのが良いのか、Direct2Dなりで自前レンダリングすれば良いのかの答えは自分では持ってない。
けど、完全にWPFに乗っかって最後の最後でパフォーマンスでないとか、こういう描画はWPF苦手とかってなったときにつぶしがきかないんで自前レンダリングをイメージしとく方が無難。
こういうところがゲームとかCADとかの面倒くさいとこなのよねえ。でも、そこだけ。それ以外はWPFバンザイなのな。

複数個出してみた。位置合わせとかテキトーやけどね。

位置の考慮は無しで、ズームだけ実装。
等倍

1/10ぐらい?

3.5倍ぐらいかな。


位置を考慮すんのもそない大変や無いのよ、実は。
描画とスケーリング(拡大縮小)をWPF任せにするなら、エンティティの座標をWorldToLocal変換するだけ。
さらに、CanvasコントロールをScrollViewerでくるんじゃえばオフセットの考慮もWPF任せで行ける。

左上隅原点での縮小

左上隅原点での拡大



やっとWPFレンダリングパフォーマンスの計測が出来た。

くっそ重い。うちのオンボードグラフィックじゃこんな感じなのか。
5000エンティティどころか1000エンティティでも帰ってこない。上のスクショは200エンティティぐらい。

現実的に実用する場合にはエンティティ同士を重ねて配置することはないので半透明の影は不要。
どうしても影が欲しけりゃ不透明の灰色の影で十分。(俺っちはそれだと嫌やけど、WPFで半透明の影でも俺っちは嫌なので同じレベル)
不透明で灰色の影にしたら2000エンティティで2〜5FPS(Frames Per Sec)ぐらいでる。(俺っちの感覚)
けど、アプリのメモリ使用量が500MBとか超えてくる。これじゃあダメ。
ズームインした状態なら自前でクリッピングして見えてるオブジェクトに制限できるけど、結局ズームアウトしたら2000個出すのは同じなので意味がない。

もちろん、全部自前版はTextBoxがエンティティ数 x 4なので無茶苦茶やってるってことだけは明記しとく。

先人や関係各所に迷惑かけずに無邪気にアップ出来んのこの辺までかな?
なのでTDrawProto1はこの辺でおしまい。ここまでのは公開しています。当然、やっつけ仕事ですが。

https://github.com/fuku518/TDrawProto1/tree/develop

以上

ダレトク実況2

テキトーに書いてみた。

ダサ!!!でもなんか楽しなってきた。
調整してみた。

そもそもWPFやから論理単位1はいわゆる1pixelじゃなくて太いんやな。Direct2Dとは違う。
SnapsToDevicePixels="True"すればいわゆる1pixelになる。

はやいとこ線描くとこまで言ってパフォーマンスみたいんやけどな。
このへぼい数年前のマシン(Core i5-3330 @ 3.00GHzオンボードグラフィック)で、WPFお任せでエンティティ5千個くらいを30FPSぐらいでレンダリングできるかどうか確認したい。
Direct2D(DirectWrite)はちょっと試した感じでは字を書くのがすげー苦手っぽいんで、WPFでイケるんなら当面は手抜きしたいとこなのよねえ。

ダレトク実況1

コード書くのはすぐ出来るんやけど、周辺の環境作りとかブログにするのが時間がかかる。
ボチボチ行くけど。

Visual Studioでビルドが成功したら、バージョン番号付にファイル名を改名してdistフォルダにzip圧縮してコピーする。それをそのままプッシュしたらバージョンと実行ファイルが組みになる。
これだとcsprojファイルを汚染しちゃうんで、ファイルの変更監視して裏で勝手にやる仕組みのが良いのか。

後で考えよう。

まずは一発目の試作。

そっかWPFなんで論理単位1はそもそも太いのか。(Direct2Dとは違う)
SnapsToDevicePixels="True" すれば1pixelになるんやろな。




・・・いや、待て。色々言いたいことはわかる。俺を信じろ。
そもそもシンタックス的に受注でRってなんやねん。ってなるんで修正する。

Rんとこ選んだらカーソル入るんで、そのままEに書き換える。

これでEになった。

こんなペースで進めてたら日が暮れるんで、同様に左と右も入れる。
左は、受注番号、商品コード(R)ぐらいでいいか。
右は、受注数と受注日くらい?

それっぽくなった。何でも描ける全部自前方式。いい感じ。
個人でやるだけなら無料のVisual Studio 2015 Commnunity版でビルドも出来るのでどうぞ。

次は何かなあ。
・枠線の描画
・移動
とかかなあ。エンティティの中の線動かすとこまで行くと一気にコード増えちゃうんでなあ。
枠線行くか。

https://github.com/fuku518/TDrawProto1

TMについてのひとりごち その1

もうそろそろTMについて書いても良い頃なんじゃね?って個人的に思う。

4年弱経ったから古巣への義理は果たしたやろし(?)、たーまいんのUIも既に詳細は思い出せないし、Eclipseで書くらしい奴ももでびもあるし。

ほんというと個人的にはあんまりエンタープライズモデリングの世界もシステム開発も好きや無いし、好きや無いから専門でもないと思ってる。いわゆうSE的な業務知識も無いしね。
自分はプログラマーで専門分野はWindowsネイティブアプリ、ただしゲーム開発以外はアセンブラも組み込みもCADもデスクトップアプリもストアアプリもUWPもXamarinもウェブも全部やったけど。

話を戻して、転職しよっかって思ったあの日にTMは捨てたつもりやったし、過去の思い出くらいにしか思ってなかった。
そもそも細いながらもマサミさんと個人的なお付き合いを今になるまで続けるつもりも無かったからね。

今でも積極的に飯のタネにするつもりは無いし、TMD描く機会が無いんでがっつりってわけや無いんやけど。
先月マサミさんと半年ぶりに呑んで、お話するうちにいろいろと思うところがあるんで、趣味でなんかやってみようかと思ってる。(ド直球で書けばちょっとムカついてハートに火が付いたw   もちろんマサミさんに対してでは無い)

・・・なるほど、マサミさんはわざとグーグラビリティの低い語彙を選んでんかもな。T Mの会の人にここ見られることないんやろけど、ひっそり宣伝せずに趣味は趣味らしく見つからんようにほそぼそとやっていこう。

その中でC#スキルが上がるやろかんね。(さすがひとりごち。チラ裏感がすごいなwダレトク感がはんぱないw)