ダブルバッファリングによるビットマップ描画で

[戻る]
WORLD 2010/04/06(火) 20:37:45
MFC VC++ 2005です。

ダブルバッファリングによって、ビットマップ(DIB)を定期的に描画するプログラムを
作っています。

メモリデバイスコンテキストに描画して、そのメモリデバイスコンテキストから実際の画
面に転送(BitBlt)しています。
この場合は画面は一気に塗り変わるはずですよね?
時々、画面の左から右に向かってゆっくり塗り変わっていくことがあるのですが、このた
び初めてダブルバッファリングを使ったので原因がわかりません。
CPU使用率が高いのって、原因でしょうか?

bun 2010/04/07(水) 07:33:03
> この場合は画面は一気に塗り変わるはずですよね?
そうです。

CPU使用率が高ければゆっくり描画しているように見えることはあり
ます。しかし、この手の話は実際に動かしてみないとなんとも。

> び初めてダブルバッファリングを使ったので原因がわかりません。
描画途中が見えないという話?
であれば、いったん表画面への直接描画に切り替えてみるといいよ。

ryo 2010/04/07(水) 12:12:25
>この場合は画面は一気に塗り変わるはずですよね?
>時々、画面の左から右に向かってゆっくり塗り変わっていくことがあるのですが
ここの一行目をみると「変わるはずなのに変わってない」とも受け取れるが
2行目では「時々ゆっくり」とある。・・・・よくわからない
「一応変わるが、時々ゆっくりになる」のか?
「ほとんど変わることはなく、時々だけゆっくり画面が変わる」
のどっちかな?

他、

1:毎秒何回ぐらい更新している?
2:時々発生の「時々」とはどのくらい?(頻度、継続性)
3:現象発生時、PCそのものは快適?それとも、重い?
4:CPU負荷は普段から高いのか?それとも高いときだけ発生するのか?
5:左から右とは?(□:変更前、■変更後)
A:全体が左から右へ
■□□
■□□
■□□
B:幅をもって左から右へ行き、それが上から下へと
■■■
■□□
□□□
(Aだとしたら・・・怖いな)
6:ゆっくりかわるってどのくらい?


バックバッファへの書込部分や、表画面への転送時のソースもあるとわかるかもしれない
それとWORLDさんの現象にあてはまるかどうかは保証できないが
「垂直同期」って単語でネット検索してみて

PATIO 2010/04/07(水) 15:56:45
一般論だけで書いてしまうとWindowsでは画面描画処理は
他の部分が忙しければ後回しにされてしまう程度の優先度です。
何が原因でCPUの使用率が高いのかわかりませんけれど、
一般的にCPUが忙しいと画面描画は後回しにされる事が多いと思います。
CPUの使用率が異常に高い状態だとエクセルやワードでも
画面更新の様子が見えたりする事があるのでCPUの使用率を高くしている
原因がわかっていて対策が可能なら改善の余地はあるかもしれません。

実際にどの位の使用率になっているのかも提示した方が良いと
思いますよ。単に高い、低いでは主観の話になってしまうので。

WORLD 2010/04/07(水) 21:54:02
>ここの一行目をみると「変わるはずなのに変わってない」とも受け取れるが
>2行目では「時々ゆっくり」とある。・・・・よくわからない
>「一応変わるが、時々ゆっくりになる」のか?
>「ほとんど変わることはなく、時々だけゆっくり画面が変わる」
>のどっちかな?

「一応変わるが、時々ゆっくりになる」ですね。

>1:毎秒何回ぐらい更新している?
300ms間隔でInvalidate()しています。

>2:時々発生の「時々」とはどのくらい?(頻度、継続性)
重いときはほぼ毎回です。

>3:現象発生時、PCそのものは快適?それとも、重い?
重いです。

>4:CPU負荷は普段から高いのか?それとも高いときだけ発生するのか?
高いときだけです。

>5:左から右とは?(□:変更前、■変更後)
>A:全体が左から右へ
>B:幅をもって左から右へ行き、それが上から下へ
よく見てから回答します。

>6:ゆっくりかわるってどのくらい?
どのくらいとは・・・?


>バックバッファへの書込部分や、表画面への転送時のソースもあるとわかるかもしれない
しばらくお待ちください。

WORLD 2010/04/07(水) 21:55:39
>実際にどの位の使用率になっているのかも提示した方が良いと
>思いますよ。

ほぼ100%に近いです。
タスクマネージャ(タスクトレイ)では、ゲージが満杯です。

WORLD 2010/04/07(水) 22:06:26
書き忘れました。

>>1:毎秒何回ぐらい更新している?
>300ms間隔でInvalidate()しています。

複数台のカメラの画像を1画面に表示させています。
カメラ1台ごとにスレッドを割り当てて、それらのスレッドでカメラから画像を取得して
います。
スレッドで画像を取得したら、画像を描画用のバッファにコピーしてInvalidate()します。
これらの処理の間隔が300msです。

描画処理では、OnDraw()で描画用のバッファにある画像を描画します。

WORLD 2010/04/07(水) 22:14:08
>描画途中が見えないという話?

そうではなくて、ダブルバッファリングという手法を使ったのが初めて、という意味です
^^;

ryo 2010/04/08(木) 01:02:22
>>6:ゆっくりかわるってどのくらい?
>どのくらいとは・・・?
「左から右」へという「変化の開始から終了までの時間」。

で、

1:300msは、どのスレッド(メインか、カメラごとか)が測っている?

2:バッファにコピーしたり、Invalidateを呼んでいるのは個々のスレッドごと?
それとも、全部のカメラ分更新されたことを確認してから一回?

3:もしOnDraw内の1回の処理に時間がかかってしまった場合に、
  その間スレッドのほうはどんどん進むようになっていますか?
  それとも、バッファの更新は待機しますか?

それと、二つほど実験を

1:カメラを一個だけにしてもおきるのか?
他のカメラ(スレッド)を完全にとめて一個だけ動かしたり、
カメラは動かすが、バッファの更新とIvalidateをするのは一個のカメラ分だけにする
など条件を変えてみる 

2:OnDrawの最初と最後に、TRACEでも入れて「OnDraw」が呼ばれる頻度を調べる。
ゆっくり」中に、OnDrawが何度も呼ばれているのか?
もしくは、OnDrawの一回がすごーく長いのか?などがわかると思います。


♯それはそれとして、一番最初にやらないといけないのは
♯PC全体が重くなるほどの重さだとは思いますが…
♯リークしまくって、重さとメモリ破壊おこしてるとかもありえそう

たいちう 2010/04/08(木) 09:09:41
> そうではなくて、ダブルバッファリングという手法を使ったのが初めて、という意味です
> ^^;

可能性としてはダブルバッファリングの方法が不適切というのもあります。
スクリーンセーバーの「ラインアート」のように、計算でPCへの負荷が小さいプログラ
ムを作り、
ダブルバッファリングの手法に問題がないことを確認してみてはいかがでしょうか。

テスト用なので「ラインアート」よりもずっと単純で結構です。
普通に描画するとちらつき、ダブルバッファリングだとちらつかないことを確認できれば
成功です。

bun 2010/04/08(木) 09:25:53
CPU使用率からして、いくらなんでも重すぎる気がする。
描画処理が後回しにされてもしょうがない。
ゆっくり描画されても仕方ない状況かな。

で、どうするか?
重すぎる物は仕方がないのでとれる手段は、
 1) 頑張って(ソフトウェア的に)高速化する
 2) パソコンのハード性能を上げる
 3) 更新周期を300msよりのばす

プログラマなら1)にチャレンジしたいのが本音でしょうから(笑)お勧めは
DIBの最適化です。ご存じかどうか分かりませんが、実はDIBはものすごく
遅いのです。
  http://lamoo.s53.xrea.com/develop/gdiplus/gdiplus_blt.html
で、高速化にチャレンジしてみた人のお話。
  http://miyano.s53.xrea.com/cgi-bin/sb/log/eid77.html

仲澤@失業者 2010/04/08(木) 09:35:07
やってみましたが、余裕で動きますね。
スレッドは使いませんでしたが、50[ms]毎にリソース
にある9枚のBMPを1枚にStretchBltしたものを、
表DCにBitBltしてみました。
Pentium4(2.66GHz)のしょぼいPCで、フルスクリーン
にしてもCPU稼働率20%程度ですね。

1.画像取得スレッドは十分早いか
2.裏DCはWM_SIZE時のみ作成しなおし、という方法になっているか
3.裏->表転送はBitBltとする。

などをチェックしてみたらどうでしょう。

PATIO 2010/04/08(木) 12:02:27
既に皆さんから意見が出ていますけれど、
CPUの使用率が100%近いという状況がそもそも問題ですね。
そこまでいっていると言う事は他の処理は動かなくて当たり前です。

CPUの使用率が100%になっている原因を特定する必要が有るでしょう。
原因が特定されない限り対策も何も有りません。

ロマ 2010/04/08(木) 12:43:30
これ、参考になりますか。

http://blogs.msdn.com/e7/archive/2009/04/25/engineering-windows-7-for-graphics-
performance.aspx

WORLD 2010/04/08(木) 22:00:40
みなさん、たくさんの回答ありがとうございます<(_ _)>

やはり、負荷が高いのが有力のようですね。
負荷が高いのは、私のコーディングもあるとは思いますが、カメラの数ではないかと思っ
ています。
カメラの数を減らしたところ、発生しなくなりました。
カメラの画像は、某メーカーから提供されているDLLのコールバック関数で取得しています。
その関数はDLLからコールされるのですが、コールされる頻度はこちらでコントロールで
きないのです。
カメラ1台でも数百ms間隔でコールされています。
それがカメラ数分、並列でコールされるために負荷が上がっているのだと思っています。
対策については、他の人と相談したいと思います。

別件で申し訳ないのですが、この画像描画プログラムにはもう1つ問題があります。
画面をリサイズすると、画像も画面サイズに合わせて拡大縮小しているのですが、
そのときに古い画像が残ってしまうことがあるのです。
必ずではなくて、これまた時々です。
この問題はカメラの数が1台でも発生します。

■■■■■□
■■■■■□
■■■■■□
■■■■■□

(□の部分が古い画像)

カメラの画像を取得したら、新しい画像を再描画しているので、動画っぽくなっているの
ですが、
□の部分だけは再描画されず、静止画のようになっています。
描画は裏画面のメモリデバイスコンテキストからOnDraw()の引数のpDCに対してBitBlt()
しているのに、
このようなこと(一部分だけ描画されないこと)が起こるのはなぜなのでしょうか?

WORLD 2010/04/08(木) 22:20:03
ソースコードです。覚えている範囲で^^;

メンバ変数
CDC m_backDC;

OnCreate()
{
  CClientDC dc(this);
  m_backDC.CreateCompatibleDC(&dc);
}

OnDraw(CDC *pDC)
{
  CDC memDC;
  HBITMAP hBMP;
  BITMAP bmpWK;
  CBitmap bmp;
  CBitmap *pbmp;
  CRect r;

  GetClientRect(r);

  bmp.CreateCompatibleBitmap(&m_backDC, r.Width(), r.Height());
  pbmp = m_backDC.SelectObject(&bmp);

  memDC.CreateCompatibleDC(pDC);
  hBMP = CreateDIBSection();

  memcpy();    // CreateDIBSection()で得られたビットマップ領域(?)にDIBをコピー

  pbmp = memDC.SelectObject(hBMP);
  GetObject(hBMP, sizeof(HBITMAP), &bmpWK);

  m_backDC.StretchBlt();  // memDCからm_backDCに対してStretchBlt()

  DelectObject(hBMP);
  memDC.DeleteDC();

  pDC->BitBlt(0, 0, r.Width(), r.Height(), &m_backDC, 0, 0, SRCCOPY); //m_backDC
からpDCに対してBitBlt()

  m_backDC.SelectObject(pbmp);
}

bun 2010/04/09(金) 10:48:33
> 負荷が高いのは、私のコーディングもあるとは思いますが、カメラの数ではないかと
思っ
> ています。
それもあるかと思いますが、その前にやれることがあると思うから、
ここで質問しておられるのでは?
そうでないなら、話合い自体が無意味です。

これまでの情報からすると、
ちょっとした注意(テクニック)だけでも高速化できそうです。

すでに他の方も言っておられますが、OnDraw()の中で毎回やらなくていいことは、
あらかじめ終わらせておいて、OnDraw()では最低限のことだけをするのです。

ウィンドウサイズや配色などが変わらない限り、
  CreateCompatibleDC()
  CreateDIBSection()
などは1回やれば十分です。
他にもいろいろ高速化手法はありそうです。

もし、それらをやってもなお重いなら、本物の限界です。
静止画を連続転送して動画っぽく見せようとしているようですが、それだと非常に
重いです。大抵の動画フォーマットは動いている部分だけを送信し、背景部分は送
信しないのです。

ハードウェアMPEGエンコーダを内蔵したカメラ等に切り替えて、MPEG画像を表示す
るようにするとか、根本的な対策が必要でしょう。

ryo 2010/04/09(金) 12:15:42
○ソースについて
記憶を頼りにってことなので、細かくは書きませんが
これが本当なら、ビットマップとDCのselectobjectの関係や、
解放の順番などがおかしいです

タスクマネージャのGDIオブジェクトの数を確認してみてください


カメラを減らそうが、OnDrawが呼ばれる回数は変わらないはずだから
あまり関係ないかもしれませんが・・・

PATIO 2010/04/09(金) 13:09:49
描画がおかしくなる件に関しては本来なら一旦閉じて
別のスレッドとしてあげても良い話だと思います。
話題が変わるならそのタイミングでスレッドを変えるようにしないと
同じスレッドが長くなりすぎますし。

実際に描画がおかしくなっている状態の時に転送先のRECTがどうなっているのか
確認するべきだと思います。
デバッグビルドでも動かせるなら普通にTRACEで出せると思います。
リリースビルドで無いと動かせないならOutputDebugStringを使って
デバックモニター系のソフト使うという手もあります。

実際の状況を捕まえない事には判断も出来ません。
実際の状況を捕まえる所からやって見てはどうでしょう。

PATIO 2010/04/09(金) 13:11:25
基、

>実際に描画がおかしくなっている状態の時に転送先のRECTがどうなっているのか

実際に描画がおかしくなっている状態の時に転送先のサイズがどうなっているのか

とした方が状況に合いますね。

ITO 2010/04/10(土) 10:17:16
> やはり、負荷が高いのが有力のようですね。
> 負荷が高いのは、私のコーディングもあるとは思いますが、カメラの数ではないか
> と思っています。
> カメラの数を減らしたところ、発生しなくなりました。
> カメラの画像は、某メーカーから提供されているDLLのコールバック関数で取得して
> います。
> その関数はDLLからコールされるのですが、コールされる頻度はこちらで
> コントロールできないのです。
> カメラ1台でも数百ms間隔でコールされています。
1.データを保存するのに専念して、表示を間引きしたらどうですか?
  データ表示の終了のフラグをつけて、表示が終了するまで画面を再描画しない。
  表示できなかったデータは保存のみにして表示しない。

2.カメラの台数は何台ですか?
  顧客とカメラの最大数を取り決める。(固定にする。)
  MDIにして、カメラの台数分のウインドウを開く。
  それぞれスレッドにして描画に専念させる。

かな?
あと、僕も

ryoさんの意見で
 > ○ソースについて
 > 記憶を頼りにってことなので、細かくは書きませんが
 > これが本当なら、ビットマップとDCのselectobjectの関係や、
 > 解放の順番などがおかしいです

 > タスクマネージャのGDIオブジェクトの数を確認してみてください
これが気になりますね。
無視すると、数時間後にOS自体がリソース不足で暴走・停止する可能性があります。


 

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