忍者ブログ
ADMINNEW ENTRY
C A L E N D A R
02 2024/03 04
S M T W T F S
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31

HP→http://sora-blue.net/~kuar/index.html
No.428 No.427 No.426 No.425 No.424 No.423 No.422 No.421
2024-03-19(Tue)
[PR]
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。



2011-01-04(Tue)
無題
Cocoaを参考に自作しているFrameWorkの描画についての高速化の覚え書き。
まず前提として、Windowに対してViewが1枚貼られている。規定となるViewである。
CocoaでいうNSWindowのcontentLayerに該当する。
ちなみにこのViewは常にWindowsサイズと同じサイズになるようにWindowのサイズ変更が行われると、このViewのサイズも変更される。

Viewについてはレイヤー構造となっており、Viewが配列を有しており、addSubViewするとpush_backされる。
ViewはsuperViewへのポインタを格納している。無論subViewsも。因みにcontentLayerのsuperViewはNULL。
上の階層へ行くには連結リストを利用し、下の階層へは配列でアクセスすることになる。

描画はViewがもつdrawRect(Rect rect)で行う。Viewが持つdrawRect関数に関してはsubViewに対してdrawRect関数を呼ぶだけなので画像やテキストを描画する際にはViewを継承したImageViewやTextViewを利用する。

引数のrectが描画する四角形である。再描画処理は重いので必要最低限の範囲で行う。
例えばWindowサイズが800*600に対して再描画するViewが100*50だったなら、Viewの100*50だけ再描画する。

半透明などが絡むと混色処理が必要になるので再描画はcontentLayerからdrawRect関数を呼び出す。
また、contentLayerを持っているのはWindowなので実際には再描画はWindowに対して再描画するように命令を出す。

実際に関数はこんな感じ

struct Rect{
 Rect()x(0),y(0),width(0),height(0){}
 Rect(int _x,int _y ,int _width,int _height)x(_x),y(_y),width(_width),height(_height){}
 int x,y,width,height;
};
class Window{
public:
 virtual void drawRect(Rect rect);
private:
 View* m_contentLayer;
};
class View{
public:
 View(Rect rect);
 virtual void drawRect(Rect rect);
 virtual void repaint();
private:
 Rect m_rect;
 std::vector m_subViews;
};

void Window::drawRect(Rect rect){
 contentLayer->drawRect(rect);
}



void View::drawRect(Rect rect){
 // ここに何かしら描画処理
 std::vector::iterator it = m_subViews.begin();
 std::vector::iterator end = m_subViews.end();
 for(; it != end; ++it){
   (*it)->drawRect(rect);
 }
}

再描画関数に関しては自身のx,yからwidth,heightだけ再描画すればいいので

void View::repaint(){
 m_window->drawRect(m_frame);
}

となる。因みにcontentLayerはWindowへのポインタを持っており、addSubViewを呼び出すことにより、子にもwindowへのポインタが渡されるため、addSubViewされている全てのviewはwindowにアクセスできる。
画像に関してはもしも再描画したいViewが半透明ではない場合、contentLayerから描画するのは無意味である。
なぜなら混色が行われない以上、そのViewから下の階層のレイヤーしか見えないのだから。

void Window::drawRect(Rect rect,View* view){
 if(view == NULL){
  contentLayer->drawRect(rect);
 }else{
  view->drawRect(rect);
 }
}

この実装により、200枚ImageViewを貼ったとしても見える部分しか描画しないので処理は画像を2〜3枚貼る程度に収めることができる。
ただ、if文或いは三項条件演算子を通るコストはかかるが……。
実際にはsuperViewが該当のviewの背面に写るであろうviewとは限らない。
というと構造はsuperViewのsubViewsの中にviewそのものが格納されている。
もしviewがsuperViewから見てsubViewsの[1]以上であれば、superViewとviewの間に他のviewが存在する。
しかし、そのviewは再描画するべきviewと完全に重なっている保証はないため、完全に重なっているsuperViewに対して処理を行なっている。
最も、superViewのsubViewにviewが100枚とかあるとsuperViewとviewの間のviewへ問い合わせる処理にしないと最適化にならないが、
ここでは少量のviewしか扱わないことを前提としてこの実装にしている。

void View::repaint(){
 m_window->repaint(m_frame,this);
}

因みに不可視 or alphaが0なら描画の必要はないため

void View::drawRect(Rect rect){
 if(m_alpha != 0){
  //ここに何かしら描画処理
  std::vector::iterator it = m_subViews.begin();
  std::vector::iterator end = m_subViews.end();

  for(; it != end; ++it){
    (*it)->drawRect(rect);
  }
 }
}

自身が見えないのであれば当然subViewも見えないわけである。
見えるようにしたければ子の描画処理をifの外へ出してやるといい。ただ、不便なのは間違いない。

では、半透明だった場合を考える。半透明であれば混色が必要となる。
しかし半透明だから諦めてcontentLayerから描画するのは重すぎる。
なので半透明か否かをsuperViewへ問い合せて不透明ならそこから描画、半透明ならさらにsuperViewへ問い合わせ…ということにする。
連結リストなのでアクセスは遅いが描画をするよりかはマシだろう。
View* view::getSuperOpaqueView(){
 return (m_superView->getAlpha == 255) ? m_superView : m_superView->getSuperOpaqueView();
}
void View::repaint(){
 if(m_alpha == 255){
   m_window->drawRect(m_rect,this);
 }else{
   m_window->drawRect(m_rect,getSuperOpaqueView());
 }
}

ここでは不透明の値が255と定義されているのもとする。モノによっては1.0とかだったりするので状況に合わせて値を変えてほしい。

因みにこれは人によりけりであるが、addSubViewやsetAlpha、setVisible、setFrameなどViewを弄るような関数を利用する場合
自動的にrepaintするか否かである。基本的には自動でrepaintしたほうが便利だがこれでは高速化は望めない。
速さを気にする人なら明示的にrepaintを行うようにするといい。どっちもという人はwindowにisAutorepaintという関数をひとつ作って、局所的にするといい。

void addSubView(View* view){
 m_subViews.push_back(view);
 if(m_window->isAutoRepaint()){
  repaint();
 }
}

実際に使うときは
m_window->setAutoRepaint(false);
for(int i=0;i<100;++i){
 addSubView(new View(Rect(0,0,100,100)));
}
m_window->setAutoRepaint(true);
repaint();

とするとわりと高速に動作するようになる。
複数のWindowを扱う時を想定して、Windowのメンバ関数にしてある。
全Windowおかまいなしにしたいなら広域変数でいいだろう。

ここでは特に何も書いていないがdrawRect関数には本来ならば他にも複数の引数が絡むことになり、Windows32APIであれば、HDCもしくはHWNDが、DirectXであればLPDIRECT3D9、LPDIRECT3DDEVICE9などが入ることになるであろう。故にdrawRect関数そのものは直接呼び出すことはできないであろう。最も実装によっては可能と言えば可能だが難しいと私は思う。
PR



・C O M M E N T
  • この記事へのコメント投稿フォームです。


  • Name:
    Title:
    Mail:
    Url:
    Color:
    Decoration:
    Vodafone絵文字 i-mode絵文字 Ezweb絵文字
    Message:
    Pass:
    ※編集時に必要です。


    TRACKBACK
    • この記事のURLとトラックバックURLです。
    • 必要に応じてご使用くださいませ。
    この記事のURL▽
    この記事のトラックバックURL▽
    No.428 No.427 No.426 No.425 No.424 No.423 No.422 No.421

    Powered by NinjaBlog. Template by うさねこ.
    忍者ブログ[PR]