ハイレゾ画面への対応

1. はじめに

 Palm OS 5ではマルチメディアへの対応が進み、これまではCLIEの独断場だったハイレゾ画面、PCM音声再生などの機能がOS標準機能として用意されました。(注意:Palm OS 5標準のPCM音声再生機能はCLIEには搭載されていません)

 ハイレゾ画面に関しては、CLIEのハイレゾ ライブラリとは異なり、専用の機能が用意されているわけではありませんが、画面描画を行うWindow Managerに画面のDensity(密度)という概念が追加されました。これにより、標準解像度(160x160ドット)から始まり、1.5倍(240x240ドット)、2倍(320x320ドット)という異なった画面解像度がOSレベルでサポートされるようになりました。これらの機能はソニーのHigh Resolutionに対してHigh Density対応機能と呼ばれており、技術的には3倍(480x480)、4倍(640x640)といった値も実現可能なようです。High Densityは画面の解像度だけを変更可能としたものであり、画面の大きさ自体はこれまでと変わりません。画面の大きさを変更する機能はPalm OS 5.3で実現されるバーチャルGraffitiはのぞいてOS 6に持ち越されたようです。

 ハイレゾ画面がOSの標準機能としてサポートされたのはうれしいことですが、これによりOS 4までは正常に動作していたゲームの多くが動作しなくなったのはご存じかと思います。これまでは動作速度を向上させるため、画面メモリーに直接読み書きをしていたのですが、High Densityになることで画面メモリーの配置が変わってしまったため、正常に動作しなくなったのです。
 OS 4までのCLIEでは画面がハイレゾ・ローレゾのモードを備えていたため、画面をローレゾにすることでそれらのアプリケーションも正常に動作していました。しかしOS 5からは画面は常にハイレゾモードとなったため、完全に動作しなくなってしまいました。

 High Density機能への対応については、高解像度での描画、高解像度に対応したBitmapの利用いろいろな方法がありますが、今回は、画面メモリーに直接アクセスしているために、OS 5では正常に動作しなくなってしまったアプリケーションを動作させる、最低限必要な方法について解説いたします。

2. サンプル

 今回のサンプルは、図1 にあるように画面内で色が異なるドットが点滅しながら移動していくものです。シューティングゲームにおける星をイメージしたものです。ON/OFFボタンを押すと画面描画のON/OFFが切り替わります。

図1

 また、画面メモリーに直接アクセスする効果を測定するために、図2 に示すようにFASTとSAFEという2つの描画方法とBenchmark機能を用意しました。FASTでは画面のメモリーに直接アクセスし、SAFEではWindow Managerが持つ機能を利用して描画を行います。そしてBenchmarkボタンを押すと一定の回数画面の書き換えを行い、それにかかった秒数を表示します(図3)

図2  図3

3. Palm OS 4対応アプリケーション

 では、OS 5対応前のリストを見ていきましょう。(ダウンロードはこちらから:Sample4-1.zip)
まずは各種定義からです。

#define ANIMATE_INTERVAL 2 // アニメーションの時間間隔をTicksで表現。2Ticks=20msec
#define NUM_STARS 1000; // 画面内の星の数

// 星の状態を記録する構造体
typedef struct {
  Int16 x; // X座標 0..159
  Int16 y; // Y座標*VERTICAL_RESOLUTION値 0..1599
  Int16 y_speed; // Y方向の速度をVERTICAL_RESOLUTION倍したもの2..8
  Int16 flash_interval; // 点滅間隔
  Int16 time_remain_to_flash; // 次に点滅する前での描画回数
  Int16 current_state_of_flash; // 現在の点滅状態(0..消えている、1..表示中)
  IndexedColorType color; // 星の色(0..215)
} StarType;

#define METHOD_SAFE 0 // SAFEな描画方法
#define METHOD_FAST 1 // FASTな描画方法
#define VERTICAL_RESOLUTION 10 // Y方向の解像度。1ドット=10とする。

// グローバル変数
UInt32 last_animated_tick; // 最後に描画を行った時刻(Ticks値)
Int16 num_stars; // 画面内の星の数
StarType *list_stars; // 星を管理する構造体へのポインタ

Int16 gMethod; // 現在の描画方法(METHOD_SAFE or METHOD_FAST)
Boolean gAnimating; // 画面描画がON(true)かOFF(false)か

説明はコメントを読んでいただくとして、y方向の動作をスムーズにするためにVERTICAL_RESOLUTIONという値が利用されていることに気をつけて下さい。画面上のドットは160x160で管理されていますが、Y方向の座標及び速度は10倍されて管理されています。これにより微妙に速度が異なる星を表現しています。

 まずはアプリケーション起動時に動作するAppStartです。

static Err AppStart(void)
{
  UInt32 width;
  UInt32 height;
  UInt32 depth;
  Boolean enableColor;
  Coord x,y;

// This program runs only in 160x160x8bit screen mode
WinScreenMode (winScreenModeGet , &width , &height, &depth, &enableColor);

  if ( ( width != 160) || ( height != 160 ) || ( depth != 8)){
    FrmAlert( NonsupportedScreenAlert);
    return -1;
  }

  return errNone;
}

 ここでは画面サイズ及びカラーのチェックを行っています。WinScreenModeで画面縦、横のサイズ及び画面の深度を取得し、それが想定状態(160x160,8ビットカラー)と異なっていたらメッセージを表示してアプリケーションを終了します。

 次にMainFormのハンドラーであるMainFormHandleEventです。

static Boolean MainFormHandleEvent(EventType * eventP)
{
  Boolean handled = false;
  FormType * frmP;

  switch (eventP->eType)
  {
    case nilEvent: // nilEventを受けたらアニメーション処理を呼び出す
      MainFormDoAnimate();
    break;

    case menuEvent:
      return MainFormDoCommand(eventP->data.menu.itemID);

    case frmOpenEvent:
      frmP = FrmGetActiveForm();
      FrmDrawForm(frmP);
      MainFormInit(frmP);
      handled = true;
      break;

    case frmUpdateEvent:
    /*
     * To do any custom drawing here, first call
     * FrmDrawForm(), then do your drawing, and
     * then set handled to true.
    */
    break;

    case ctlSelectEvent:
      if ( eventP->data.ctlSelect.controlID == MainStartButton){
      // アニメーションのON/OFFを反転
        gAnimating = !gAnimating;
        handled = true;
      } else if ( eventP->data.ctlSelect.controlID == MainBenchmarkButton){
      // ベンチマークを実行
        DoBenchmark( gMethod);
      }
      break;

    case popSelectEvent:
      if ( eventP->data.popSelect.controlID == MainMethodPopTrigger ){
      // 描画方法を変更
        if ( eventP->data.popSelect.selection == 0 ){
          gMethod = METHOD_FAST;
        } else {
          gMethod = METHOD_SAFE;
        }
      }
      break;
    }

    return handled;
  }

ここもコメントどおりです。ON/OFFボタンが押されたらgAnimatingを変更し、描画方法が選択されたらgMethodを変更しています。Benchmarkボタンが押されたらベンチマークを行うDoBenchmark()を呼び出しています。
 また、nilEventを受けたときはアニメーション処理を行うためにMainFormDoAnimateを呼び出していることも注意してください。

 次にMainFormの初期化を行うMainFormInitです。

static void MainFormInit(FormType *frmP)
{
  Int16 i;
  StarType *star;
  num_stars = NUM_STARS;

  // 星の記憶領域を確保
  list_stars = MemPtrNew( sizeof (StarType ) * num_stars);

  // 星の初期化
    for ( i = 0 ; i < num_stars; i++){
      star = list_stars+i;

      // 座標を乱数で決定
      star->x = SysRandom(0) % 160;
      star->y = ( SysRandom(0) % 125 + 20) * VERTICAL_RESOLUTION;
      // Y方向の速度を乱数で決定
      star->y_speed = SysRandom(0) % 6 + 2;
      // 点滅間隔を乱数で決定
      star->flash_interval = SysRandom(0) % 20 + 10 ;
      // 点滅時刻の初期値を乱数で決定
      star->time_remain_to_flash = SysRandom(0) % 10;
      // 表示状態を乱数で決定
      star->current_state_of_flash =SysRandom(0) % 2;
      // 色を乱数で決定
      star->color = SysRandom(0) % 216;
    }

    // 方法の初期値は高速(画面直接書き換え)
    gMethod = METHOD_FAST;

    // はじめはアニメーションはオフ
    gAnimating = false;
}

 ここでは星の状態を乱数で決定し、描画方法と描画状態を初期化しています。

次に画面のアニメーションを行うMainFormDoAnimateです。

static void MainFormDoAnimate()
{
  UInt8 *bits;
  Int16 i;
  StarType *star;
  BitmapType *bitmap;
  WindowType *win;

  // アニメーションがONになっていなければ終了
  if ( gAnimating == false)
    return;

  // 前回アニメーションをした時から、必要な時間がたっているか?
  // たっていなければ終了
  if ( TimGetTicks() < last_animated_tick + ANIMATE_INTERVAL )
    return;

  // 最後にアニメーションをした時刻を記憶
  last_animated_tick = TimGetTicks();

  // アニメーションを実行
  MoveStars( gMethod );
}

 ここではタイミングの調整を主に行っています。MainFormがnilEventを受け取るタイミングは正確ではありませんので、前回にアニメーションを行った時刻を覚えておき、そのときから必要な時間が経過しているかどうかを判断しています。まだ必要な時間が経過していない場合は何もせずに終了しています。アニメーションのタイミングを調整する方法については後ほど説明します。

 次は実際にアニメーションを行うMoveStarsです。

static void MoveStars(Int16 method)
{
  UInt8 *bits;
  Int16 i;
  StarType *star;
  BitmapType *bitmap;
  WindowType *win;

  // 直接書き換え用のアドレスの取得
  win = WinGetDisplayWindow(); // 画面のWindowを取得
  bitmap = WinGetBitmap( win ); // 画面のBitmapを取得
  bits = BmpGetBits( bitmap ); // 画面のピクセルが格納されているアドレスを取得

  // すべての星について繰り返す
  for ( i = 0 ; i < num_stars; i++){
  star = list_stars + i;

  // 現在画面に表示されていたら、それを消去する。
  if ( star->current_state_of_flash){
  // 画面の直接書き換え
  if ( method == METHOD_FAST){
    *(bits + ToOffset(star->x, star->y) ) = 0; // Clear
  // Win系PalmOS関数を使う方法
  } else if ( method == METHOD_SAFE){
    WinSetForeColor(0);
    WinPaintPixel( star->x, star->y / VERTICAL_RESOLUTION );
    }
  }

  // 下へ移動する。
  star->y += star->y_speed;

  // 画面の下端に達していたら、画面上部へ
  if ( star->y > 145 * VERTICAL_RESOLUTION){
    star->y = 20 * VERTICAL_RESOLUTION;
  }

  // 点滅間隔を減らす。
  star->time_remain_to_flash --;

    // 点滅をするタイミングになった?
    if ( star->time_remain_to_flash <= 0){
    // 表示状態を反転
      star->current_state_of_flash = ( star->current_state_of_flash)?0:1;
      // 点滅間隔を初期化
      star->time_remain_to_flash = star->flash_interval;
    }

    // 表示状態であれば、画面に描画
    if ( star->current_state_of_flash){
    // 画面の直接書き換え
    if ( method == METHOD_FAST){
      *(bits + ToOffset(star->x, star->y) ) = star->color;
    // Win系PalmOS関数を使う方法
    } else if ( method == METHOD_SAFE){
      WinSetForeColor(star->color);
      WinPaintPixel( star->x, star->y / VERTICAL_RESOLUTION);
      }
    }
  }
}

 やっていること自体は比較的単純ですので、コメントを参照して下さい。ここで注意していただきたいのは2点です。
 1点目は画面への直接アクセスを行うために、画面メモリーのアドレスを取得する方法です。リストにあるように、まずWinGetDisplayWindowによって画面全体を管理するWindow構造体へのポインタを取得し、次にWinGetBitmapによりWindow構造体が管理するBitmap構造体を取得します。最後にBmpGetBitsにより、Bitmapが管理するピクセルデータの先頭アドレスを取得します。

 もう一点は現在の描画方法に従って、画面へのアクセス方法を変えていることです。FASTの場合は画面のアドレスに直接書き込みを行っていますが、SAFEの場合はWin系の関数を利用して描画を行っています。そして、画面に直接書き込む場合はToOffsetという関数により、星のX,Y座標から画面上のアドレス(先頭アドレスからのオフセット)を計算しています。ToOffsetは以下のようになっています。

// X,Y座標から画面上のアドレスに変換する。
  UInt32 ToOffset(Int16 x, Int16 y)
  {
    return (UInt32)y / VERTICAL_RESOLUTION * 160 + x;
  }

 次がBenchmarkを行うDoBenchmarkです。

  // 速度計測
  static void DoBenchmark( Int16 method)
  {
    UInt32 start, stop;
    UInt16 total_sec;
    UInt16 loop;
    char msg[32];

  FrmCustomAlert( CustomAlert," Start benchmarking.", "","");

  // 開始時刻を取得
  start = TimGetTicks();

  // 200回繰り返す
  for ( loop = 0; loop < 200; loop++){
    MoveStars( gMethod);
    }

  // 終了時刻を取得
  stop = TimGetTicks();

  // Ticksを秒に変換
  total_sec = (stop - start) / SysTicksPerSecond();

  // 結果を表示
  StrPrintF(msg, "Total %d secs", total_sec);
  FrmCustomAlert( CustomAlert, msg,"","");
  }

MoveStarsを200回呼び出し、その前後で時刻を取得することでかかった秒数を計算しています。

 最後がAppEventLoopです。

  static void AppEventLoop(void)
  {
    UInt16 error;
    EventType event;

    do
    {
    // アニメーションを行うためにタイムアウト値を1Ticks(=0.01秒)に設定
    EvtGetEvent(&event, 1);

    if (! SysHandleEvent(&event)){
      if (! MenuHandleEvent(0, &event, &error)){
        if (! AppHandleEvent(&event)){
          FrmDispatchEvent(&event);
          }
        }
      }
    } while (event.eType != appStopEvent);
  }

 さて、ここの説明をするためにはPalm OSにおけるアニメーションの実現方法について解説する必要があります。
 Palm OSで長時間にわたるアニメーションを行う場合は、以下のような流れにするのが適切です。

  AppEventLoopでタイムアウト値を設定し、nilEventを発生させる
  ->FormのイベントハンドラーでnilEventを検出し、それを契機としてアニメーションに必要な画面描画を行う。

 ひとつの関数の中でループをつくり、アニメーション処理と時間調整を行うことも可能ですが、イベント処理が一切されなくなってしまいますので、キャンセルができない、アプリケーションの切り替えができないなどの問題が生じ、長時間のアニメーションには不適切です。
 イベント処理に組み込むことで、イベントを処理しながら画面の描画を定期的に行うことが可能になります。

 また、アニメーションは一定の時間間隔で行う必要があります。これには2つの方法があります。

(方法1)次のアニメーションまでの間隔を計算して、タイムアウト値に設定する。

 たとえば10msec毎にアニメーションを行いたい場合、以下のようなコードを書きたくなると思います。

EvtGetEvent( &event , 10); //タイムアウト値を10ticks=100msecとする。

 この方法の問題点は、アニメーションとアニメーションの間隔を100msecに設定しているため、アニメーションに時間がかかるとタイミングが不正確になってしまうことです。たとえば1回のアニメーション処理に30msecかかったとすると、実際のアニメーションの間隔は130msecとなってしまいます。

 これを防ぐためには、アニメーションにかかった処理も考慮に入れてタイムアウト値を設定することが必要です。

timeOut = lastAnimatedTicks + 10 - TimGetTicks(); // 次にアニメーションを行う時刻から現在の時刻を引くことでタイムアウト値を計算
EvtGetEvent( &event , timeOut);
if ( event.eType == nilEvent)
lastAnimatedTicks= TimGetTicks();

 これで時間がかかるアニメーションを行う場合も間隔が正確になります。

 ただし、この方法には問題があります。それは「nilEventはタイムアウト時にのみ発生する」という前提です。実際にはさまざまな理由でnilEventが発生するため、それがタイムアウトで発生したのかどうかは判定できず、この方法だけで正確なアニメーションは実行できません。

(方法2)頻繁にnilEventを発生させ、アプリケーションでアニメーションの間隔を制御する。

 これが今回利用している方法です。AppEventLoopではタイムアウト値を10msecという小さな値に設定し、常にnilEventを発生させています。そしてMainFormDoAnimateの中でアニメーションを行う時刻がきたかどうかを監視しています。今回のサンプルでは、各関数が以下のように役割を分担しています。

AppEventLoop:短い間隔で定期的にnilEventを発生
MainFormHandleEvent:nilEventが発生したらMainFormDoAnimateを呼び出す
MainFormDoAnimate:前回のアニメーションタイミングから、アニメーションを行うタイミングになったかを判断
MoveStars:実際のアニメーション処理を実行

 この方法ではイベントが頻繁に発生します。それはCPUがより忙しく動作するということを意味し、バッテリー消費は大きくなります。

 実際には、方法1と方法2を組み合わせると、最も正確でバッテリー消費も少なくアニメーションを実行できます。つまり、タイムアウト値を正確に計算することで不要なイベントの発生を防ぎ、さらにnilEventを受け取った時もアプリケーションでタイミングを判断するという方法です。今回のサンプルでは簡単にするために方法2のみを採用しています。


4. サンプルの実行

 では、サンプルを実行させてみましょう。まずはPalm m505等の160x160の画面を持ったカラーマシンで実行させてみてください。ハイレゾ画面を持ったCLIEの場合はハイレゾアシストを切った状態で実行させてください。
 ON/OFFボタンを押すとアニメーションがON/OFFされることを確認してください。また、SAFEとFASTを切り替えるとアニメーションの速度が変わることもお分かりになりますか?
 treo90(Palm OS 4.1 DragonBall VZ 33MHz)でBenchmarkを行ってみましたが、FASTで9秒、SAFEで54秒という結果になりました。描画の内容、頻度によって効果は変わりますが、今回のサンプルで言えば画面に直接アクセスすると、描画速度への効果が非常に大きいということができそうです。

 次にハイレゾ画面を持ったPalm OS 5マシン、もしくはハイレゾ画面を持ったPalm OS 3.5/4.0搭載のCLIEでハイレゾアシスト(もしくはSwitchDash等の同等の機能を持ったアプリケーション)をONにして実行してみてください。SAFEの場合は正常に動作し、FASTにすると図4のように画面の上部4分の一に描画されてしまいます。
 AppStartで画面のサイズをチェックしていますが、それが機能していないことも注意が必要です。WinScreenModeは画面のピクセル数ではなく、物理的なサイズを返却するためにハイレゾ画面においても縦横160という値を返してしまうからです。

5.Palm OS 5への対応方針

 上記のサンプルをCLIE TG50で実行した場合のベンチマーク結果はSAFE4秒、FAST4秒(画面は崩れています)となっています。つまり、画面直接書き換えをしても動作速度は向上していないことになります。これはメモリー直接書き換えが68K Codeで記述されており、エミュレーションが行われるのに対して、Window Manager関数は純粋なARM Codeで記述されており非常に動作速度が早いためです。
 このサンプルをPalm OS 5上で動作させる場合に限っていえば、Window Managerの関数のみを使う方法が、性能的にも互換性の面でもより優れているということになります。

 ただし、ソフトウェアによっては画面の直接書き換えが有効な場合もあるでしょうし、コード全体がメモリー直接書き換えをベースとして作られている場合は、Window Manager関数を使う方法にすると改造量が大きくなってしまいます。また、そのように改造するとPalm OS 4搭載機での実行速度が著しく落ちてしまうため、コードがOS 4用とOS 5用に別れてしまうという問題もあります。

 今回は、コードの大部分はメモリー直接書き換えのまま残し、Palm OS 5でも正常に動作させる方法を説明していきましょう。Palm OS 4上で実行する場合は、高速な動作速度を実現するために、これまで通り画面の直接書き換えを行います。

6.Palm OS 5への対応

 以下は、仮想的な画面を用意し、その上でメモリ直接書き換えを行う方法です。そしてWindow Manager関数を使って、仮想的な画面を実際の画面へコピーするというものです。

 仮想的な画面(オフスクリーンウィンドウとも呼ばれます)は装置の画面解像度によらず、一定のサイズ、カラーで生成することができます。そのため、安全に想定通りのメモリーが確保されることになります。

 またWindow ManagerのWinCopyRectangleでは、コピー元のウィンドウとコピー先のウィンドウの解像度、カラーなどが違っても安全な変換が行われます。これを使って仮想的な画面から実際の画面へコピーするのです。

 まとめると以下のようになります。

 ・標準解像度の仮想的な画面を確保する。
 ・仮想的な画面上で直接メモリー書き換えを行う。
 ・仮想的な画面上での描画が終わったらそれを実際の画面へコピーする。(この際に解像度、カラーの変換が行われる)

 では、以下Palm OS 5対応版のリストを見ていきましょう。(ダウンロードはこちらから:Sample4-2.zip)

 まず、定数定義部分です。

#define METHOD_SAFE 0 // SAFEな描画方法
#define METHOD_FAST 1 // FASTな描画方法
#define METHOD_MEDIUM 2 // SAFEかつFASTな描画方法

 MEDIUMという描画方法を用意しました。これが仮想的な画面を用いた、比較的高速で互換性が高い方法です。
 次に追加されるグローバル変数です。

// 追加分
Boolean supportHighDensity; // High Density利用可?
Boolean supportHRLibrary; // ハイレゾライブラリ利用可?
Boolean direct_access_to_screen; // 画面直接アクセスが可能か?

WindowType *fake_screen_window=0; // SAFE&FASTで利用する仮想画面のWindow構造体
BitmapType *fake_screen_bitmap=0; // SAFE&FASTで利用する仮想画面のBitmap構造体

 今回もPalm OS 4上では直接メモリーアクセスを行いますので、安全にメモリーアクセスが可能かどうかの判断を行う必要があります。そのためにHigh Density機能の有無、ハイレゾライブラリの有無をチェックし、格納する変数を用意します。

 fakeで始まる変数は、仮想的な画面を記憶するための変数です。後ほど解説するようにWindow構造体とBitmap構造体が必要になりますので2つの変数を用意します。

 次にアプリケーションの初期化を行うAppStartです。

static Err AppStart(void)
{
  Coord width, height;
  UInt32 depth;
  Boolean enableColor;
  Coord x,y;
  Err err;
  BitmapType *bitmap;
  Coord bmp_width, bmp_height;
  UInt16 hrLibRefNum;

  // 初期化
  supportHighDensity = false;
  supportHRLibrary = false;

  // High Density機能の有無をチェック
  {
    UInt32 version;
    Err err;

    // Window Managerのバージョンが4以降ならHigh Density機能は有効
    // High Densityが有効=画面がハイレゾ ではないことに注意
    //(例:treo600はHigh Density対応でかつ画面は標準解像度)
    err = FtrGet(sysFtrCreator,sysFtrNumWinVersion, &version);
    if ( version >= 4){
      supportHighDensity = true;
      }
    }


    // SONYのハイレゾライブラリの有無をチェック
    {
    if ((err = SysLibFind(sonySysLibNameHR, &hrLibRefNum))) {
      if (err == sysErrLibNotFound) {
      /* couldn't find lib. Try to load it */
        err = SysLibLoad( 'libr', sonySysFileCHRLib, &hrLibRefNum );
      }
    }
    if (!err ) { // HRLibrary Exists
      supportHRLibrary = true;
    }
  }

  if ( supportHighDensity){
  // High Densityが有効な場合、画面のピクセル数の取得
    UInt32 temp;
    // 画面のサイズを取得
    WinScreenGetAttribute( winScreenWidth, &temp);
    width = temp;
    WinScreenGetAttribute( winScreenHeight, &temp);
    height = temp;
  // 画面のカラーを取得
    WinScreenMode (winScreenModeGet ,0 , 0, &depth, &enableColor);

  } else if (supportHRLibrary) { // OS 3.5 or 4.X Clie
  // ハイレゾライブラリが有効で、High Densityが無効な場合、画面のピクセル数の取得
  HROpen( hrLibRefNum );
  HRWinGetDisplayExtent( hrLibRefNum, &width , &height );
  // ハイレゾ画面になっていたら、ローレゾ画面に変更する。(メモリー直接書き換えが可能なように)
  if ( width == 320){
    UInt32 w, h;

    w = h = 160;
    HRWinScreenMode ( hrLibRefNum,winScreenModeSet, &w, &h, &depth, &enableColor );
    // 変更が成功したかどうかを判断するために再度取得
    HRWinGetDisplayExtent( hrLibRefNum, &width , &height );
  }
  // 色深度を取得
  WinScreenMode (winScreenModeGet , 0 , 0, &depth, &enableColor);
  HRClose( hrLibRefNum );
  } else {
  // ハイレゾライブラリもHigh Densityもない。
    WinGetDisplayExtent( &width , &height );
    WinScreenMode (winScreenModeGet ,0 , 0, &depth, &enableColor);
  }

  // 8bit Color以外のときは8bitに変更しようと試みる
  if ( ( enableColor == true ) && (depth != 8) ){
    depth = 8;
    WinScreenMode (winScreenModeSet ,0 , 0, &depth, &enableColor);
  // 変更が成功したかどうかを知るために再度取得
    WinScreenMode (winScreenModeGet ,0 , 0, &depth, &enableColor);
  }

  // ピクセル数が160x160で8Bit Colorの場合のみ画面直接書き換えが可能
  if ( ( width == 160) && ( height == 160 ) && ( depth == 8)){
    direct_access_to_screen = true;
    } else {
    direct_access_to_screen = false;
  }

  // Meduim方式用の仮想画面を用意する
  // 160x160 8Bit Colorのビットマップを用意する
  fake_screen_bitmap = BmpCreate( 160,160,8,0, &err);
  // 上記Bitmapへ描画するためのWindowを作成する。
  fake_screen_window = WinCreateBitmapWindow( fake_screen_bitmap, & err);

  return errNone;
}

 いかがでしょう。かなり長くなっています。ここでは主に以下の事を行っています。

 ・High Densityサポートの有無のチェック
 ・ハイレゾライブラリの有無のチェック
 ・画面のドット数、カラー(ビット深度)の取得
 ・ハイレゾモードの場合、ローレゾモードへ切り替え
 ・8Bitカラー以外の場合は8Bit Colorへ切り替え
 ・画面直接書き換えが可能かどうかの判断
 ・仮想画面の用意

 今回のサンプルでは、比較のため複数の描画方式を選択可能にしていますが、通常のアプリケーションでは、この時点で利用可能な方法のうち最も高速なものを選択することになるでしょう。

 後の部分では、MainFormHandleEventとMoveStarsに変更を加えています。MainFormHandleEventでは、増えた描画方法に対応しているだけなので省略します。
 以下はMoveStarsです。

static void MoveStars(Int16 method)
{
  UInt8 *bits;
  Int16 i;
  StarType *star;
  BitmapType *bitmap;
  WindowType *win;

  if ( method == METHOD_FAST ){ // FASTの場合は画面のアドレスを取得
    win = WinGetDisplayWindow();
    bitmap = WinGetBitmap( win );
    bits = BmpGetBits( bitmap );
    } else if ( method == METHOD_MEDIUM ) {
    // Mediumの場合は仮想画面のアドレスを取得
      bits = BmpGetBits( fake_screen_bitmap);

    }

    // これ以降、最後の全画面コピーまではFASTとMediumは同じ処理を行う。

    for ( i = 0 ; i < num_stars; i++){
    star = list_stars + i;

    // 現在画面に表示されていたら、それを消去する。
    if ( star->current_state_of_flash){
    // 画面の直接書き換え
      if ( ( method == METHOD_FAST ) || ( method == METHOD_MEDIUM) ){
        *(bits + ToOffset(star->x, star->y) ) = 0; // Clear
      // Win系PalmOS関数を使う方法
      } else if ( method == METHOD_SAFE){
        WinSetForeColor(0);
        WinPaintPixel( star->x, star->y / VERTICAL_RESOLUTION );
      }
    }

  // 下へ移動する。
  star->y += star->y_speed;

  // 画面の下端に達していたら、画面上部へ
  if ( star->y > 145 * VERTICAL_RESOLUTION){
    star->y = 20 * VERTICAL_RESOLUTION;
  }

  // 点滅間隔を減らす。
  star->time_remain_to_flash --;

  // 点滅をするタイミングになった?
  if ( star->time_remain_to_flash <= 0){
    // 表示状態を反転
    star->current_state_of_flash = ( star->current_state_of_flash)?0:1;
    // 点滅間隔を初期化
    star->time_remain_to_flash = star->flash_interval;
  }

  // 表示状態であれば、画面に描画
  if ( star->current_state_of_flash){
    // 画面の直接書き換え
    if ( ( method == METHOD_FAST ) || ( method == METHOD_MEDIUM) ){
      *(bits + ToOffset(star->x, star->y) ) = star->color;
      // Win系PalmOS関数を使う方法
    } else if ( method == METHOD_SAFE){
      WinSetForeColor(star->color);
      WinPaintPixel( star->x, star->y / VERTICAL_RESOLUTION);
      }
    }
  }

  // 描画が完了した。Mediumの場合、仮想画面から実画面へのコピーを行う。
  // 画面解像度、色数などはここで自動的に変換される。
  if ( method == METHOD_MEDIUM ){
    RectangleType rect= { { 0, 20} , { 160,125}};

    WinCopyRectangle( fake_screen_window , WinGetDisplayWindow() , &rect , 0,20,winPaint);
    }

  }

 注意すべき点は2点です。
1点目はFASTでは画面のアドレスを取得しているのに対して、Mediumでは仮想画面内のアドレスを取得していることです。これ以降の描画はFASTとMediumで同じ処理を行っています。

さらにMediumでは、全ての描画処理が終わった後で仮想画面を実画面へコピーしていることです。ここで画面解像度、色数の変換がOSによって自動的に行われます。

 以上で改造は終わりです。今回のサンプルでは描画が少ないため、確認はできませんでしたが、描画処理が多くなるとFASTで書かれたコードをSAFEに変換するよりはMediumに変換するほうがはるかに容易であることがおわかりになるでしょうか。

7. Palm OS 5対応版の動作速度

 新しいバージョンを動作させて見ましょう。m505等のデバイスでは従来と同じように画面直接書き換えが行われますので、動作速度は変わりません。一方Mediumの場合FASTよりわずかに遅い11秒となりました。

 またTG-50の場合、FAST、SAFE、Medium全て4秒という結果になりました。

 これらの結果から以下のようなことが言えます。

 FAST:OS 4では非常に高速。OS 5では変わらない。互換性は低い
 SAFE:OS 4では非常に低速。互換性は高い。
 Medium:全体的に高速。互換性は高い。

 今回解説した方法は、動作速度も互換性もメリットがあります。OS 4のローレゾ画面でしか動作しないゲームをお持ちの場合は、ぜひ対応を検討してください。



copyright(C)2002 Hacker Dude-san all rights reserved.