Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

背景塗りつぶしにPatBltを使う Part1 #765

Merged

Conversation

berryzplus
Copy link
Contributor

目的

背景塗りつぶしに使うAPIを変更します。

まずは、試しに1箇所だけ変更して、どのような違いになるのかを示します。
根拠説明のところで、変更すべき状況を示すので、横展開は別PRとするつもりです。

経緯

描画速度は FillRect > ExtTextOut > PatBlt と言われています。
サクラエディタでは元々 ExtTextOut を改変した独自関数を使っています。

各関数の概要は以下のとおりです。

関数名 本来の用途 必要なパラメータ 速度
FillRect 領域の塗りつぶし ブラシハンドル
ExtTextOut 文字列の描画 なし(事前に背景色を設定する)
PatBlt ブラシパターンの転送 ブラシハンドル

ExtTextOut関数は文字列描画が目的の関数ですが、渡した矩形領域を塗りつぶす機能があります。
サクラエディタの塗りつぶし処理のほとんどは、空文字列をExtTextOut関数に渡すことで行われています。

根拠説明

プログラムを変更するには一定の根拠が必要だと思います。

FillRectよりPatBltのが速い?そんなの常識じゃん? で突き進むのはなんか違うと思います。
形式的にでも相応の根拠が必要だと思ったので、探してみました。

とりあえずググってみるとこんなページがヒットします。
テテのつぶやき - ベンチマークテスト(長方形の単色塗りつぶし)

ベンチマークテストの実施条件と結果が載っています。
(GetDCでデスクトップのDCを取得し、5000回ずつ塗りつぶしさせた結果を比較)

せっかく実施条件が書いてあるので 検証資材 を作って似た検証ができるようにしてみました。
サクラエディタの事情で検証内容を少し変えていますが、以下が実行結果です。

速度考慮 FillRect ExtTextOut PatBlt(PATCOPY)
なし 1,454,860,380 7,179,924 6,216,724
あり 1,435,629,087 5,075,016 5,368,788

「速度考慮」の有無は、該当関数を使うための準備(オブジェクト生成等)を
ループの外で行うか中で行うかの違いです。

速度考慮なしでPatBltが最速、
速度考慮ありでExtTextOutが最速
という結果になりました。

サクラエディタの塗りつぶしは、基本ExtTextOutになっています。
過去の開発者も似たような感じで「ExtTextOut最速じゃん!」ってなったんだと思います。

ただ、ExtTextOutが最速なのは「下準備を行わない場合」です。
背景塗りつぶしのためにExtTextOut関数を使うにあたっての下準備はSetBkColorの呼出です。
他の理由でSetBkColorを呼出済みの状態から塗り潰す場合はExtTextOutでいいんですが、
単純に背景色を指定して塗りつぶしたい場合はPatBltのほうが速いです。

つまり、
SetBkColorとセットで呼び出しているExtTextOut
変更した方が「(速度効果的に)良い」になります。

このPRの確認方法

バージョン情報ダイアログのURL部分を変更しています。
URL部分のウインドウにカーソルを当てた時に背景色を変更する処理です。
デバッグ版ビルドで確認すると以下のような違いを確認できます。

変更前)URL部分にカーソルを当てると背景色が黄色になり、左上に黒点が打たれる
変更後)URL部分にカーソルを当てると背景色が黄色になり、黒点は打たれない

変更前が黒点を打つ挙動はvista向けのデバッグ仕様だそうです。

従来ExtTextOutを使っていた塗りつぶしにPatBltを使うように変更します。
描画速度は FillRect > ExtTextOut > PatBlt と言われています。
@berryzplus berryzplus changed the title 背景塗りつぶしにPatBltを使う 背景塗りつぶしにPatBltを使う Part1 Jan 13, 2019
@ds14050
Copy link
Contributor

ds14050 commented Jan 13, 2019

速度を目的とした変更なら、アプリケーションに適用した場合のベンチマークが知りたいです。フィボナッチ数を数えるようなベンチマークではなくて。

@berryzplus
Copy link
Contributor Author

速度を目的とした変更なら、アプリケーションに適用した場合のベンチマークが知りたいです。フィボナッチ数を数えるようなベンチマークではなくて。

実動作に合わせたベンチマークは用意してません。
速度改善効果の確認がしたいわけじゃないからです。

このPRで行いたかったことは、常識としての「PatBltのが速いらしい」のウラを取ることとPatBltの方式に転換するために障害があるのかを知ることです。

いまPRを出しているのは、ウラは取れた、と思っていて、転換するための障害はほとんどないと判断しているからです。

@ds14050
Copy link
Contributor

ds14050 commented Jan 13, 2019

(自分は)脱落しました。

@berryzplus
Copy link
Contributor Author

(自分は)脱落しました。

了解っす。なんか方向性おかしなことになった場合のツッコミはお願いします。

Copy link
Contributor

@beru beru left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

問題無いと思います。
マウスカーソルをURLに合わせた際にちゃんと背景が黄色に変化する事を確認しました。

@beru
Copy link
Contributor

beru commented Jan 14, 2019

背景塗りつぶしに使うAPIを変更する目的が速度、つまり描画を速くして描画が完了するまでの時間と消費電力を削る事だとしたら気になるのは以下の3点です。

  1. アプリケーションのどこの処理の背景塗りつぶしでどの程度の処理負荷があるのか
  2. PatBlt に変える事でどの程度改善するのか
  3. FillRect, ExtTextOut, PatBlt 以外でもっと高速な方法は無いか

色々と検証しないとはっきりしないと思うので用意してくれた機材をこちらでも確認しようと思います。

@berryzplus
Copy link
Contributor Author

レビューありがとうございます。

@berryzplus berryzplus merged commit 0f6fa57 into sakura-editor:master Jan 15, 2019
@ds14050
Copy link
Contributor

ds14050 commented Jan 15, 2019

目的も根拠も不明確なまま、つまりは「変更のための変更」がマージされたのが残念です。

#765 (comment) で否定されたことにより速度改善が目的ではないため、この PR 本文の「根拠説明」は意味がありません。「目的」に書かれた要旨は「APIを変更します」であり、変更の目的を何ら説明していません。

残念です。

@berryzplus
Copy link
Contributor Author

@ds14050 さん

目的も根拠も不明確なまま、つまりは「変更のための変更」がマージされたのが残念です。

#765 (comment) で否定されたことにより速度改善が目的ではないため、この PR 本文の「根拠説明」は意味がありません。「目的」に書かれた要旨は「APIを変更します」であり、変更の目的を何ら説明していません。

残念です。

お気付きと思いますが、言いたいことが理解できていません。
「残念です。」なので反対意見ですよね?

目的や根拠が不明瞭なままマージが行われるのはいつものことです。
何故今回は反対するんですか?

@ds14050
Copy link
Contributor

ds14050 commented Jan 16, 2019

自分も、なぜこれで話が通じていないのかが理解できません。これまでのやりとりからそうなることが見えていたから、早々に「脱落しました」。

これは @berryzplus さんの言葉です。

ぼくが見るときの基準は、まず目的が妥当であるか否か。 何度か書いていますけど、ほぼそれが全てです。 このPRに反応してないのは目的が一部よく分らなかったからです。

目的が明かされなければ妥当もクソもありません。

言いたいことが理解できていません。

目的と、根拠が、この PR の本文からは読み取れないので体裁を整えて欲しいということです。マージが行われる前に、PR を出すときに、それを済ませて欲しかったです。

以下は berryzplus さんの書き込みです。

このPRで行いたかったことは、常識としての「PatBltのが速いらしい」のウラを取ることとPatBltの方式に転換するために障害があるのかを知ることです。

いまPRを出しているのは、ウラは取れた、と思っていて、転換するための障害はほとんどないと判断しているからです。

ここに書かれていることは、サクラエディタとは無関係の理由と内向きの理由であって、他者への働きかけがありません。どこにも「リクエスト」がありません。だから自分は離れました。

@beru
Copy link
Contributor

beru commented Jan 17, 2019

#765 (comment)

にリンクが張られていた検証資材をビルドして動かしてみました。
MAX_COUNT は 5000 だと時間が掛かり過ぎるので 50 に変更しました。
Release x64 ビルドで確認しました。

case1 elapsed=8853393
case2 elapsed=93359
case3 elapsed=128753
case4 elapsed=8551426
case5 elapsed=91108
case6 elapsed=117786

出力値の単位は QueryPerformanceCounter で取得した値そのままですね。
事前に QueryPerformanceFrequency で取得した値で割れば単位が秒になりますが、その場合は double 型に変換してから割らないと駄目ですね。

自分の環境では、case2 と case 5 が速いようです。実装を見るとどちらも ExtTextOut を使ってました。MFCの FillSolidRect はこれ使ってるみたいですね。

なお使っているグラフィックスカードは、NVIDIA GeForce GTX 1070 を使ったものでドライバのバージョンは 24.21.14.1131 です。WDDM 1.1 で GDI のハードウェアアクセラレーションが規定されてますがそれ使ってるのかどうかは謎です。

デスクトップのDCを取得してそこに描画しているのはちょっと良くないかなと思います。実際にアプリで描画する際も表示用のDCではなくてメモリDCを対象に描画するようにしないと垂直同期の関係でティアリングが起きるので表示のちらつきが気になってしまいます。それにメモリDCの方が処理も高速に終わるのではないかと。

あと CMakeLists.txt が Visual Studio にも対応してるとビルドが楽でした。

@beru
Copy link
Contributor

beru commented Jan 17, 2019

自分が高速だと思う単色塗りつぶし方法は、32bit の DIB を描画用に確保して、そのDIBのビットマップのメモリを直接 std::fill で書きかえる方法です。

そこそこ最近のコンパイラであればおそらく x86/64 のストリング命令 rep stosd を生成してくれると思います。Intel の場合 Ivy Bridgeマイクロアーキテクチャ(第3世代Coreプロセッサー) 以降のCPUを使ってればストリング命令の処理速度が強化されていてかなり速く処理してくれます。有る程度対象のサイズが大きければ自分でループ書くよりも速いです。

https://stackoverflow.com/a/33485055/4699324

@ds14050
Copy link
Contributor

ds14050 commented Jan 18, 2019

自分も beru さんと同じように PR の「根拠説明」から速度改善が目的だと読み取ったのですが、「実動作に合わせたベンチマークは用意してません。 速度改善効果の確認がしたいわけじゃないからです。」と書かれてしまいました。

噂の裏を取って速度が障害にならないことを確認して、でも目的は速度改善ではないらしいのです。では何をする目的があって使用する API を変更しようとしていて、それによる速度への影響が障害にならないことを確認したのか。PR を出す時点で示されているべき説明がその後も出てこないことにいらだっています。

@berryzplus
Copy link
Contributor Author

berryzplus commented Jan 18, 2019

#765 (comment)

にリンクが張られていた検証資材をビルドして動かしてみました。
MAX_COUNT は 5000 だと時間が掛かり過ぎるので 50 に変更しました。
Release x64 ビルドで確認しました。

うちの結果は Debug x86 です。500で何回か試して、実行時間の比率がほとんどブレないことを見たあとで結果だけ5000のものを整形しました。

速いほう2つの結果が逆転したのは興味深いです。
グラボの性能傾向によって結果が変わってる気がします。
テスト機はいつもの win 8.1 で、 GTX Titan Black が載ってます。こいつは確かに「メモリ転送のが速い」って結果が出ても不思議じゃない・・・。(普通のグラボより転送レートが高い上に転送幅も広いです。GTX 1080 Tiほどじゃないですが。

出力値の単位は QueryPerformanceCounter で取得した値そのままですね。
事前に QueryPerformanceFrequency で取得した値で割れば単位が秒になりますが、その場合はdouble 型に変換してから割らないと駄目ですね。

考えるのがめんどくさくなってコメントアウトしてしまったのは、お察しの通りです・・・。

デスクトップのDCを取得してそこに描画しているのはちょっと良くないかなと思います。

FillRectが遅いのは「よくない作り」のツケを支払わされてるんじゃないかと思ってます。
確証はないんで話半分にしてほしいんですが、自プロセスのHWNDがカバーしない領域に描画しようとした場合、優先順的に「待たされる」ような構造になってるんじゃないかな?と思うんです。テストプログラムは自プロセスのウインドウを持たないので、常に「待たされる」はずです。絶望的に思えるほどのこの速度差も、何かの条件を満たしたら一気に詰まるものなのかも知れません。

あと CMakeLists.txt が Visual Studio にも対応してるとビルドが楽でした。

Visual Studio側から File ⇒ Open ⇒ CMake で CMakeLists.txt を選択できます。
CMake モードで CMakeLists.txt を開けたらソリューションビューからやりたい放題です。
CMakeLists.txt を右クリックで Change CMake Settings すると CMakeSettings.json ができます。
CMakeSettings.json を右クリックで Add Configuration できます。
うちの環境だと Add Configuration のダイアログで Mingw64-Debug が選べます。(発動条件がわからん。

@berryzplus berryzplus deleted the refactoring/using_patblt branch January 18, 2019 15:47
@beru
Copy link
Contributor

beru commented Jan 18, 2019

berryzplus さんが作ってくれた検証資材を改造しました。
https://gist.github.com/beru/d52f6335f42674e88f473e69297927ae

Desktop Window DC
case1 elapsed=3.051277 sec
case2 elapsed=0.045124 sec
case3 elapsed=0.041721 sec
case4 elapsed=3.012590 sec
case5 elapsed=0.039918 sec
case6 elapsed=0.040916 sec
Memory DC
case1 elapsed=0.002042 sec
case2 elapsed=0.001968 sec
case3 elapsed=0.002061 sec
case4 elapsed=0.001782 sec
case5 elapsed=0.001850 sec
case6 elapsed=0.001809 sec

デスクトップウィンドウに対しての FillRect は1秒間に20回とかなり低いパフォーマンスでした。
DWM(Desktop Window Manager) のせいだと思うので、WindowsXP だとこんなに遅くは無いかもしれません。Vistaから導入されたDWMのせいで3フレーム遅延が起きるという話がありますがおそらくそれに該当してそうです。

メモリDCの方が速度が大分速いので、描画はちゃんと裏画面を作ってやらないとパフォーマンス的にも良くないですね。なおメモリDCが選択するビットマップも描画の度に毎回作るのはパフォーマンスを落とす原因になると思うので事前に作成するのが良いと思います。

「こうすれば性能が上がるのにっ」って思ってはいてもサクラエディタの今の描画のコードを大幅に改造するのは大変なので指をたくさん動かさないと実現は出来ないですね。:sweat_smile:

@berryzplus
Copy link
Contributor Author

メモリDCの方が速度が大分速いので、描画はちゃんと裏画面を作ってやらないとパフォーマンス的にも良くないですね。

メモリDCに変えると速度差が誤差レベルになるんですね。
通常DC時のFillRectが一体何をしているのか、謎が深まったようなw
内部的にPolyPolygon のカーネル版を呼び出してるらしいですが、通常DCで時間がかかる理由がよく分らんです。

懸念としてあるのは、NVIDIA GTX(ゲーマー向けハイエンドグラボ)で検証した結果に基づいて、非ゲームアプリのコードに手を入れていいかどうか?ってことです。GTXは数GB単位のグラフィックスメモリを積んでますが、世の中にはグラボなし(≒CPU内臓 Intel HD Graphics のみ)のマシンもあってメインメモリと共用になってる場合があります。これについてはあとでノートPC(グラボなし)の結果を見とこうと思ってます。

サクラエディタも、オプションでメモリDCを使う描画モードを選べます。
このメモリDCは「複雑な描画処理をキャッシュして省略しよう」のニュアンスで使われていて、メモリDCにキャッシュされる部分と、キャッシュされない部分があります。選択範囲や検索語句の強調、対括弧の強調表示などの「操作で変わる部分」はキャッシュなしで描画されます。

@beru さんの検証結果によると、キャッシュなし部分もメモリDCを経由したほうが速い、ということになるんですけど、このあたりを踏まえた改善コードを一度で作るとなると内容の把握が大変めんどくさいことになりそうな予感がしています。

個人的に思っているサクラエディタの「描画が遅い理由」は、これまでも何度か書いているんですけど、「文字色などのスタイルを動的に判断しながら描画している」ということと「再描画を指示するときに InvalidateRect(ぬるぬる) してる(≒再描画範囲を指定してない)」ということを挙げられます。

構築済みのレイアウト情報に基づいて描画する場合、背景を描画して、青い文字を描画して、緑色の文字を描画して、黒い文字を描画して・・・のように色切替を最小限に抑えることができます。現状の処理スタイルだと、色が変わるたびに切替命令を発行するので遅いです。

再描画範囲の指定については、1回で済むはずの描画なのに3回描画しているケースがありそうだ、ということを言ってます。「1回分でも3回分でも速度は変わらない」と考えるのは不自然だと思ってます。もしかしたら「無視できるレベル」なのかも知れませんけど、そういうのが積み重なった結果で「遅い」になってると思っています。

@berryzplus
Copy link
Contributor Author

グラボなしマシンの測定結果を上げときます。

Desktop Window DC
case1 elapsed=2.026958 sec
case2 elapsed=0.014761 sec
case3 elapsed=0.022024 sec
case4 elapsed=1.995388 sec
case5 elapsed=0.028906 sec
case6 elapsed=0.030954 sec
Memory DC
case1 elapsed=0.001375 sec
case2 elapsed=0.001171 sec
case3 elapsed=0.002656 sec
case4 elapsed=0.001093 sec
case5 elapsed=0.000905 sec
case6 elapsed=0.001283 sec

Intel HD Graphics 520搭載機(グラボなし)です。RAM多めに積んでるので省メモリの参考にはならんです。
結果があんまり変わらんようなので、メモリDCが速いこととグラボ性能との関連は薄いのかな 😄

@takke
Copy link
Member

takke commented Jan 19, 2019

まだちゃんとコードを見てないんですが、メモリDCに(常に)使っているわけではないんですね。
どの部分がボトルネックになっているのか検証するのであればまずはプロファイリングツールを使うのが定石かと思いますが、どんなときに描画が遅く感じるのか(シナリオみたいなもの)もちゃんと用意して検証しないといけないし、なかなか系統立てて手を出せない感じなんでしょうかね、仕事でもないし。

@ds14050
Copy link
Contributor

ds14050 commented Jan 19, 2019

体全体への影響を見ないで、ポリフェノールが体にいい、炭水化物は避けたほうがいい、というような論法はナンセンスです。だから、アプリケーションに適用した結果が知りたいと書きました。

実アプリケーションでプロファイルを取って有意な差が出て初めて最適化は意味があります。他人の手間なのでかけた手間に比して大した効果がなくても構わないといえば構わないわけですが、思惑に反して悪影響が出ていないことの確認は必須です。それができないのならそもそも最適化に手を出してはいけない。

@ds14050
Copy link
Contributor

ds14050 commented Jan 19, 2019

(続き) やらなくてもいいことなんだから。

@ds14050
Copy link
Contributor

ds14050 commented Jan 19, 2019

速度考慮なしでPatBltが最速、
速度考慮ありでExtTextOutが最速

SetBkColorとセットで呼び出しているExtTextOutは
変更した方が「(速度効果的に)良い」

これだけ限定条件を並べながら実際の効果を調べずに「たぶん良くなるだろう」で話を進められることが信じられません。

@beru
Copy link
Contributor

beru commented Jan 19, 2019

自プロセスのウィンドウに対して GetWindowDC で取得したDCや、WM_PAINT メッセージのハンドラで BeginPaint を呼んで取得した DC を使った場合の速度の違いに興味が出ますね。これらは検証資材では確認していなかったので。

サクラエディタのアプリ本体に描画ベンチ機能が無いので、実アプリケーションのプロファイルは少し手間ですね。

一番お手軽なのは Visual Studio の Performance Profiler で確認ですが、多分今のソースコードそのままだと厳しい気もします。#766 でやったように間接層の MyFillRect 関数群経由にしておけば計りやすくなると思います。確認用に inline 指定を外してから VS の Performance Profiler で計ってみてそれでも分かりにくかったら、自前で中に差分時間を集計するプロファイル用のコードを仕込むとかでしょうか。。

@beru
Copy link
Contributor

beru commented Jan 19, 2019

結果があんまり変わらんようなので、メモリDCが速いこととグラボ性能との関連は薄いのかな 😄

メモリDCは裏画面に描いているようなもので描画途中の内容が画面に反映される事が無いのが速い理由かなと思います。メモリDCを使った方が速くてちらつかない事は昔から有名な話でした。
https://www.codeproject.com/Articles/33/%2FArticles%2F33%2FFlicker-Free-Drawing-In-MFC

ただグラボ性能との関連となると、なかなか見えないコードで処理されている事なので分からないですね。。結局手持ちの複数の環境で色々なパターンで試験して有意な差を確認出来なければ関連は薄いと判断せざるを得ないとは思います。

GDIのハードウェアアクセラレーションについては WDDM で決まっていて下記に書かれていますが、
https://docs.microsoft.com/en-us/windows-hardware/drivers/display/gdi-hardware-acceleration
塗りつぶし操作は WindowsAPI に頼らずに32bitDIBのビットマップメモリ領域を書きかえるのが近道じゃないかなと思います。ちゃんとDIBを使うようにサクラエディタのコードを書き直すのがとても大変ですが…。

@beru
Copy link
Contributor

beru commented Jan 19, 2019

メモリDCに変えると速度差が誤差レベルになるんですね。
通常DC時のFillRectが一体何をしているのか、謎が深まったようなw
内部的にPolyPolygon のカーネル版を呼び出してるらしいですが、通常DCで時間がかかる理由がよく分らんです。

WDMはフレームキューイングしてるようですが、FillRect の場合はもろにそれと絡んでるって事でしょうかね。具体的なメカニズムの解説はあんまり詳しくないので出来ません…。

懸念としてあるのは、NVIDIA GTX(ゲーマー向けハイエンドグラボ)で検証した結果に基づいて、非ゲームアプリのコードに手を入れていいかどうか?ってことです。GTXは数GB単位のグラフィックスメモリを積んでますが、世の中にはグラボなし(≒CPU内臓 Intel HD Graphics のみ)のマシンもあってメインメモリと共用になってる場合があります。これについてはあとでノートPC(グラボなし)の結果を見とこうと思ってます。

NVIDIA GTXシリーズはハイエンド、ローエンド、ミドルレンジ(+拡張された概念のミドルエンド)と色々あるし、Maxwellアーキテクチャ以降はAMDのより出来が良いGPUをコンスタントに出してきているのでそれを検証に使う事自体は問題無いと思います。おっしゃる通り内蔵グラフィックスを使った場合の検証も必要ですが。Intel HD Graphics 等の内蔵グラフィックスで済ませられるならアスク税を大幅に払う必要が無いのでそれが良いんですが、根本的にメモリ帯域が狭いので残念な性能になってますね。

テキストエディタをフルスクリーン表示で使う人はちらほらいるので、DWMの弊害を回避する為にもDirectXのフルスクリーン表示を使うのは有りだと思います。

サクラエディタも、オプションでメモリDCを使う描画モードを選べます。
このメモリDCは「複雑な描画処理をキャッシュして省略しよう」のニュアンスで使われていて、メモリDCにキャッシュされる部分と、キャッシュされない部分があります。選択範囲や検索語句の強調、対括弧の強調表示などの「操作で変わる部分」はキャッシュなしで描画されます。

メモリDCの活用は後付け的な感じですよね。

@beru さんの検証結果によると、キャッシュなし部分もメモリDCを経由したほうが速い、ということになるんですけど、このあたりを踏まえた改善コードを一度で作るとなると内容の把握が大変めんどくさいことになりそうな予感がしています。

面倒なんですが描画速度の改善の為には 32bitのDIB を作成して、GDIを使った描画はメモリDCでそれを選択して行う、という対応が必要だと考えてます。

個人的に思っているサクラエディタの「描画が遅い理由」は、これまでも何度か書いているんですけど、「文字色などのスタイルを動的に判断しながら描画している」ということと「再描画を指示するときに InvalidateRect(ぬるぬる) してる(≒再描画範囲を指定してない)」ということを挙げられます。

構築済みのレイアウト情報に基づいて描画する場合、背景を描画して、青い文字を描画して、緑色の文字を描画して、黒い文字を描画して・・・のように色切替を最小限に抑えることができます。現状の処理スタイルだと、色が変わるたびに切替命令を発行するので遅いです。

再描画範囲の指定については、1回で済むはずの描画なのに3回描画しているケースがありそうだ、ということを言ってます。「1回分でも3回分でも速度は変わらない」と考えるのは不自然だと思ってます。もしかしたら「無視できるレベル」なのかも知れませんけど、そういうのが積み重なった結果で「遅い」になってると思っています。

自分あんまりそこらへん追えてないので詳しくは分かりませんが、なんか色々な事情があるんですね。なるべく少ない手間で描画速度を大幅に改善できるに越したことは無いんですが、そうもいかない事は覚悟しないといけなそうですね。

@berryzplus
Copy link
Contributor Author

@ds14050 さん

これだけ限定条件を並べながら実際の効果を調べずに「たぶん良くなるだろう」で話を進められることが信じられません。

1回しか呼ばれない単発処理について実測して改善効果の有無を見極めるのは無理だと思います。
その理屈だと、ループしない部分の処理は速度改善目的で変更できないことになります。

必ずしもそうじゃない、と思っています。

@ds14050
Copy link
Contributor

ds14050 commented Jan 20, 2019

初めて教えてもらいましたが、この PR の目的は速度の改善だったのですね。詳細に言うなら、速度の改善のために PatBlt を使う計画があって、そのプロトタイプとして1か所だけ変更するのがこの PR だというわけですね。今からでも本文を修正してもらいたいです。

そしてそれを踏まえて意見を述べると、この PR は WIP を付けてマージを目的とはせず、最小の差分を提示するだけを目的とすべきだったと思います。

横展開したものは当然、効果を見極めることができるはずです。効果を見極める前にプロトタイプだけをマージしては、あとで引き返すのが面倒です。

@ds14050
Copy link
Contributor

ds14050 commented Jan 20, 2019

ループしない部分の処理は速度改善目的で変更できないことになります。

実際そうですよ。できなくはありませんが、意味がありません。コードが汚くなるなら有害ですらあります。

@beru
Copy link
Contributor

beru commented Jan 20, 2019

自分は以前から何度もしつこく「塗りつぶし操作は 32bit の DIBを作ってそのメモリ領域を直接書き換えてしまうのが一番速いはず」と主張をしていたのですが、言うだけじゃなくてちゃんとプログラム書いて確認した方が良いよな…と思ったので書いてみました。

https://gist.github.com/beru/b8e585d92d91f73dc64f74584116ffeb

結果としては小さい領域なら DIB のメモリを書き換えるやり方は速いですが、領域のサイズがどんどんと大きくなっていくと API を使った方が速い事が自分の環境では確認出来ました。

なおAPIを使う場合はDIBではなくDDBを使っていてそしてハードウェアアクセラレーションも掛かっていると考えられます。まぁ処理の仕組みを考えるとこの結果は納得出来るような気もします。

なおDIBのメモリを書き換える方法もマルチスレッド使えばもっと速く出来るので、ハードウェアアクセラレーションに頼らないやり方も(CPUが十分速い環境なら)捨てたもんじゃないかなとは思います、という負け惜しみを残しておきます。

@beru
Copy link
Contributor

beru commented Jan 20, 2019

環境によるかもしれないですけど、ExtTextOut より PatBlt の方が速いのかというとそんな事無いような気がしてしまいますね。検証プログラムも試験規模も小さくて、十分な分析が出来ているかというと出来ていないと思うので、あんまり言い切れませんが。

@beru
Copy link
Contributor

beru commented Jan 22, 2019

検証用のコードを更新しました。

DDBの場合はハードウェアアクセラレーションが効きますが、DIBの場合は当然効かない結果となり自前で塗りつぶした方が速い事が確認出来ました。マイクロソフトがGDIのソフトウェア実装を見直してくれると良いですね。。

さて、この検証プログラムはまだウィンドウを作って表示をしていないので不十分です。ぼちぼち追加してみようかと思いますが…。

単に全面塗りつぶし表示だけを行うなら表示用のDCを使って塗りつぶしをするAPIを呼ぶだけで済みますが、実際のアプリでは色々な表示を行うのでメモリDCを作成し各種イベント時に(ゲームだと一定間隔)色々描画して、WM_PAINT メッセージのタイミングで BitBlt APIでメモリDCから表示用のDCに転送するのが定石だと思います。

サクラエディタは上記の定石に沿った実装ではないので、余計な再描画等を行ってしまってるのではないかなと推測しています。

@berryzplus
Copy link
Contributor Author

定石といえば BeginPaint の前に GetUpdateRect を呼んでないことが気になっています。
InvalidateRect(ぬるぬる) と言ってた話と関係します。 ← 「ぬるぬる」じゃなくて「ぬるふぁるす」ですが 😢
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getupdaterect
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-beginpaint
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-invalidaterect
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-updatewindow

[定石のフロー]
InvalidateRect でウインドウの一部を invalid (更新対象)にする
 ↓
なんかの弾みで UpdateWindow が呼ばれる(or InvalidateRect の第3引数に TRUE を渡す)
 ↓
WM_PAINT が送出されて WndProc に制御が渡る
 ↓
GetUpdateRect を呼び出して更新矩形があるか確認する
※更新矩形がなければ即座に制御を戻すのが定石 ←これがない
 ↓
BeginPaint で PAINTSTRUC T ( DCを含む ) を取得して描画処理を開始する

ところどころ主語が違ってるんで注意してください。

要するに「WM_PAINTの冒頭にあるべき処理」がないんじゃないかと言いたい。

これは更新指示が InvalidateRect( hWnd, NULL, ... だと効果が薄いはずなので、
そこを含めた見直しが必要な めんどくさい系 の提案ではあるのですけど。

@beru
Copy link
Contributor

beru commented Jan 23, 2019

@berryzplus さん

定石といえば BeginPaint の前に GetUpdateRect を呼んでないことが気になっています。

BeginPaint の引数経由で取れる PAINTSTRUCT::rcPaint にも同じ内容が書き込まれるようですが、GetUpdateRect を使って確認した方が良いんでしょうか?

BeginPaint で PAINTSTRUC T ( DCを含む ) を取得して描画処理を開始する

描画処理というのは具体的に何の事でしょうか?自分の考えでは BeginPaint で取得した表示用のDCに対してメモリDCから BitBlt を使って転送するだけに留めるべきだと考えています。裏画面への実描画自体は他の操作やタイマー等のイベントで送られてくるメッセージで行うようにすれば、不必要な再描画が減らせるのではないかと。

ただし裏画面の再描画を減らすには、裏画面も1画面ではなくレイヤーで複数画面持って、本当に更新された箇所だけ再描画するようにするとか、色々な工夫は出来ると思います。コードの書き換えが大量に必要な面倒くさい話ですが。。

あと他に描画を速くするのに必要な事として、WM_ERASEBKGNDメッセージに対しては非ゼロを返す事と、RegisterClass で登録するウィンドウクラスの hbrBackground メンバーの内容を NULL にする事かと思います。

色々と書きましたが、背景描画より文字描画の負荷の方が割合としては大きいと思うので、そこも今後改良していきたいですね。

@berryzplus
Copy link
Contributor Author

まだ起きてたので一点だけ。

定石といえば BeginPaint の前に GetUpdateRect を呼んでないことが気になっています。

BeginPaint の引数経由で取れる PAINTSTRUCT::rcPaint にも同じ内容が書き込まれるようですが、GetUpdateRect を使って確認した方が良いんでしょうか?

BeginPaint の呼出は GetUpdateRect よりも「かなり」重いとむかし何かで見ました。
事実なのかどうか、また、現在もそうなのかどうか検証が必要な話題です。

@beru
Copy link
Contributor

beru commented Jan 23, 2019

BeginPaint の呼出は GetUpdateRect よりも「かなり」重いとむかし何かで見ました。
事実なのかどうか、また、現在もそうなのかどうか検証が必要な話題です。

むかしっていつ頃でしょうか? Windows 95 が登場した頃のCPUが 80386 SX 16MHz とかそれぐらいでしょうか?まんが日本昔ばなしがまだ放送してた頃の話でしょうか。。

@berryzplus
Copy link
Contributor Author

BeginPaint の呼出は GetUpdateRect よりも「かなり」重いとむかし何かで見ました。
事実なのかどうか、また、現在もそうなのかどうか検証が必要な話題です。

むかしっていつ頃でしょうか? Windows 95 が登場した頃のCPUが 80386 SX 16MHz とかそれぐらいでしょうか?まんが日本昔ばなしがまだ放送してた頃の話でしょうか。。

い・い・な い・い・な~・・・って歌っとる場合かっ!とセルフ突っ込みしつつ。

時期は1999年~2001年のどっかだと思います。
VC++のメーリングリストをROMってたのでそこで得た情報かも知れないし、そうじゃないかも知れない。
はっきりしたことは言えんですが、今現在どうなのかは確認しときたいなぁ、と。

@m-tmatma m-tmatma added this to the next release milestone Feb 3, 2019
HoppingTappy pushed a commit to HoppingTappy/sakura that referenced this pull request Jun 11, 2019
…g_patblt

背景塗りつぶしにPatBltを使う Part1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants