定周期BITMAP更新のちらつき防止

[戻る]
ebimayo 2006/06/27(火) 10:26:11
いつもお世話になります。ebimayoです。

Bitmap表示の際のちらつき防止について教えてください。

タイトルに書いた定周期BITMAP更新処理の流れは以下の通りです。

定周期で画像Bitmapファイルを読み込み(毎回読み込むBitmapは違うもの)
↓
読み込んだBitmapの情報をCreateCompatibleBitmapで用意したメモリ上のDCにBitBlt
↓
InvalidateRect(Bitmapの領域,FALSE); で再描画
↓
OnPaintでBitBltを実行して描画
BitBlt( 実際に表示するDC, ・・・, 
        CreateCompatibleBitmapで用意したDC, 0, 0, SRCCOPY);


現在試しているのはこのような処理で、定周期は300ms、Bitmapは256色の1024×1024
です。表示する領域とBitmapのサイズは同じようにして実行しています。

Bitmapの表示処理自体はうまくいってますが、これを実行すると画面がちらついてしま
います。
(Bitmapの表示処理がうまくいっている確認はAPIなどでエラーが出ていないこと、
 実際に表示したいものが画面に表示されていることで確認)

同様の処理で定周期ではなく(1つのBitmapを一度描画するだけ)、上に異なる
ダイアログ等が重なった場合の再描画などによるちらつきはまったくありません。

いろいろ探してみたのですが、どこでも画面のちらつき防止に裏画面
を使うと書いていますが、周期的に異なるBitmapを表示している所は見つからず
手詰まり状態です。


ちらつきを抑える方法を何か教えていただけたらと思い質問しました。

開発環境
Microsoft WindowsXP(SP2)
Microsoft Visual C++ 2005
  ダイアログベースアプリケーション(MFC)

PATIO 2006/06/27(火) 13:05:22
後考えられるとしたら、OnEraseBkgndをオバーライドしてFALSEで返せば、
背景の塗潰しが抑制されてちらつきが減るかもしれません。
やっていないのであれば、試してみてください。

PATIO 2006/06/27(火) 13:07:32
ちなみにWM_ERASEBKGNDでの背景の塗潰しを抑制するのはちらつき対策で
良くやります。但し、これをやる場合はクライアント領域の背景部分の
描画を全て自前で行う必要があります。
行っていないと再描画がかかっていない部分は前の画像が残ってしまいます。

ebimayo 2006/06/27(火) 14:02:47
[[解決]]
回答ありがとうございます。


書き漏れていて重要なことなのかもしれないのですが、この処理は
CDialogの派生クラスのOnPaint つまりダイアログの背景を直接描画する
処理として書いています。

CDialogの派生クラスでGOnEraseBkgndはオーバーライドすることも
試していたのですが、同様にちらついてしまいました。

ダイアログの背景に直接描画処理を書くとちらつく現象が
今回の件とは別に以前も起きたことを思い出したので、
試しにCStaticの派生クラスをCDialogの子ウィンドウとして作り
そのOnPaint処理で同様の処理を書いたところ描画によるちらつきがなくなりました。

原因がまったくわからずに解決にするのもいけないこととは思いつつ
時間に限りがあるため取りあえず解決とさせていただきます。
時間があればまた調べてみたいとは思っていますが、この現象で
何か思い当たることがありましたら教えてもらえないでしょうか。

宜しくお願いします。

ebimayo 2006/06/27(火) 14:03:50
誤記訂正

誤)GOnEraseBkgnd
正)OnEraseBkgnd

PATIO 2006/06/27(火) 14:22:57
例え自前で背景描画を行っていたとしてもOnEraseBkgndと同じ実装をしていたら
結局、ちらつくと思いますよ。
ちらつく原因は、ビットマップを貼るスペースは本来ならビットマップが載るわけですから
その部分の背景描画は必要ないわけです。にもかかわらず背景を全てベタ塗りしてしまうと
その分だけ処理時間が掛かってしまい、背景が塗潰された後、ビットマップを貼り付ける
様子が
目に見えてしまうのが原因でしょう。
コントロールを貼り付けるとコントロールの後ろの部分の描画がスキップされるから
ちらつきが抑えられるのではないでしょうか。
自前で描画するのであれば、ビットマップをまず貼り付けてしまって
ビットマップが無い部分だけを背景描画するようにするとちらつきが少なくなると思います。
ちなみに実際のちらつきを全く無くすというのは多分無理です。
描画が早いと結果的にちらつきが目立たなくなるだけで書き換えが起こっている部分は
大なり小なりちらつきます。
あとは、PC側の描画性能によると思います。

ebimayo 2006/06/27(火) 16:32:46
回答ありがとうございます。

PATIOさんの話から考えると、
ダイアログで描画するとOnEraseBkgndオーバーライドしていても
OnPaint処理でその他の描画処理(実際にはIvalidateRectで描画対象にはならないと思
っていますけれど)
も書いてあるため、その分ちらつきが目立つのかもしれないと想像。

取りあえず基本クラスは何でもいいのですがCStaticの派生として
Bitmap更新時の描画をそのCStaticの派生クラス内でのみ描画すると
余計な処理が走らずに結果的にちらつきがなくなるのかな、全然まとはずれかもしれま
せんが・・・

PATIO 2006/06/28(水) 11:08:51
OnPaint内で背景を全て矩形か何かで塗潰しているのであれば、
InvalidateRectをしたところで一度塗潰してからビットマップを貼り付けると言う
動きには変わりありませんよね。ビットマップが大きければ塗潰す面積は大きくなります。
PCの性能が低ければ、この書き換えがちらつきに見えてしまうくらい遅いという可能性は
ありえるのではないでしょうか。
ダイアログの場合、上にコントロールが載っているとその部分はクリッピングされるので
描画対象から外れるようになっていたと思います。
これは少しでも描画のパフォーマンスをあげる為に上書きされるはずの部分の描画を
しないで済ませると言う事です。
結果としてちらつきが少なくなるというのは私が書いた理屈に合っていると思います。

GDIで描画を行う場合、上に重なる事がわかっている部分は書かないというのは、
描画スピードを上げる為の基本思想です。
あと、バックバッファを使うのであれば、クライアント領域をそっくりカバーするような
物を作成しておいてその上で背景を塗潰してビットマップを貼り付け、バックバッファの
内容を実際のクライアント領域に貼り付けるようにしないとちらつきは抑えられません。
ちらつきを抑えるというのは、描画過程をユーザーに見せないようにする事で実現します。
今のebimayoさんの実装では背景の塗潰しと言う描画をクライアント画面に直接行って
いる為にその部分の描画過程をユーザーに見せてしまっているのでちらつくのです。

PATIO 2006/06/28(水) 11:12:08
InvalidateRectを生かしたいのであれば、背景全体を矩形等で一気に塗潰すのでは
なくてビットマップの領域外の背景のみを塗り潰すようなコードを書けば生きてくると
思いますよ。
その場合はビットマップ外の部分の描画は表に出てきませんから。

Ban 2006/06/28(水) 12:37:30
コントロールにしたことで描画範囲が確実に狭くなった→ちらつき抑制
であろうことは現象を聞く限り同意ですし、範囲が広くて描画が重いのが根本原因だとは思います
が、
書き込みを見る限りでは、クライアント画面に直接描写しているわけではなさそうに見えます。

> 読み込んだBitmapの情報をCreateCompatibleBitmapで用意したメモリ上のDCにBitBlt
> ↓
> InvalidateRect(Bitmapの領域,FALSE); で再描画
> ↓
> OnPaintでBitBltを実行して描画
> BitBlt( 実際に表示するDC, ・・・, 
>        CreateCompatibleBitmapで用意したDC, 0, 0, SRCCOPY);

Ban 2006/06/28(水) 12:38:33
# あ、実際はどうなんでしょう?>スレ主様、ってだけのことですが。

subaru 2006/06/28(水) 13:10:33
PATIOさん
>ダイアログの場合、上にコントロールが載っているとその部分はクリッピングされるの>描画対象から外れるようになっていたと思います。
ちょっと補足します。
このような動作をするのはダイアログのスタイルにWS_CLIPCHILDRENが含まれている場合
だけです。
WS_CLIPCHILDRENがない場合にダイアログ上にコントロールを配置してしまうと
逆にちらついてしまうと思われますのでご注意を。
#元記事からはコントロールがあるかどうかはわかりませんが・・・

ebimayo 2006/06/28(水) 14:14:15
PATIOさん、Banさん、subaruさんありがとうございます。

PATIOさんへ
 何か私の書き方が下手で誤解を与えてしまっているかなと思ったので以下補足。

 現在描画を行っている領域は1024×1024固定で、InvalidateRectもこの領域を
 RECT構造体で渡して第二引数FALSEを設定して描画処理を走らせています。なので、
 今回対象の描画領域外の背景の描画処理は走っていないと推測しています。

 後、これが誤解させてしまって書き方が悪かったと思うのですが
 「ダイアログの背景を直接描画」と書いたのは、ダイアログに貼り付けた
 コントロール(CStaticなどの部品)に描画しているわけではないと言いたかった
 のです。すいません。
 実際には、最初の投稿で書いたように
 裏画面(メモリデバイスコンテキスト)に書いたものをダイアログのOnPaint処理時に
 BitBltしています。

Banさんへ
 おっしゃる通り、 裏画面(メモリデバイスコンテキスト)に書いたものを
 ダイアログの背景としてBitBltしています。

subaruさんへ
 WS_CLIPCHILDRENはスタイルに含めていません。しかし、実際は1280×1024の
 ダイアログ内のうち1024×1024の領域を同じサイズのビットマップでBitBlt
 していて、それに重なるコントロールなどはないです。で、今回はInvalidateRect
 でビットマップの領域の1024×1024をRECTで指定して、第二引数FALSEで
 渡しているためWS_CLIPCHILDRENは関係ないかなと思ってたりします。

 後、同じ親を持つコントロールのクリッピングは自前で用意しています。
 今までこんなこと無かったのですが、今回作り方が変?特殊?なのか
 なぜかウィンドウスタイルで指定してもクリッピングがうまく作用して
 いなかったので・・・

PATIO 2006/06/28(水) 15:41:53
はて、オフスクリーン上に全て描画していてOnPaintで背景の塗潰しをしていない
となるとなんでちらつくんでしょうね。オフスクリーンは実画面のコンパチブルの
ようだからBitBltの負荷もこれ以上は減らせないでしょうし。
InvalidateRectは多分、ビットマップのサイズと位置で実行していると思うので
ビットマップを貼り付ける実画面側のOnPaintにクライアント画面全体を背景色で
塗り潰すような処理があるのではないかと考えていたんですが、
そのような処理が一切ないとするとちょっと見当がつかないです。
私が言っているのはInvalidateRectした時にビットマップを貼る領域は当然再描画の
対象になるのでOnPaint内でクライアント領域全体の塗潰し処理をしていると
その領域の塗り潰しが走ってしまってちらついているのかなと考えたのです。

あと、InvalidateRectの第二引数をFALSEにした場合の背景描画の処理は
多分、WM_ERASEBKGNDの事を言っているのではないかと思うのでオバーライドして
つぶしている場合は関係ないと思いますし、OnPaint内で背景の塗潰しをしている場合は
このフラグの設定に関わらず、背景描画されると思います。

CStaticでコントロールを作ってそのコントロールに直接Invalidateをしてやると
ビットマップ以外の部分は全く関係ないレベルで再描画が起こると思うので
それでちらつきが少なくなるならやはりダイアログ側のOnPaintに何かあるのでは
ないかと思うんですが。

Ban 2006/06/28(水) 16:56:50
# 私もむかし、ダイアログはなんかチラついた記憶がありまして。
3 うろ覚えで、直接のお役に立てなかったのですが…

PATIO 2006/06/28(水) 17:36:32
ダイアログベースだったらOnPaintの中にアイコン化のときの処理とか
入っていると思うんですが、その中でCDialog::OnPaintを呼んでいるのが
原因とか言う事はないかなぁ。
デフォルトの実装で何かやっているとか。
ソースの調査までやっていないので単なる思い付きですけれど。

ryo 2006/07/02(日) 00:41:49
ちょいと日付たってるけど
OnPaint()内と、OnEraseBkgnd()内あたりのソースを提示してみてはいかがでしょう?

古いスレッドには追加発言できません。