8ビットPC時代、グラフィックスハードウェアにはテクスチャマッパーやブリッターのような現代的な機能がなく、プログラマーは単一のフレームを描画するために、画面バッファのすべてのバイトを手動で移動させる必要がありました。6809プロセッサで横スクロールゲームをプレイ可能にするために、ある開発者は、157サイクルを要する単純な行コピーループから、同じ操作をわずか56サイクルで実行する特殊なスタックベースのハックへと移行しました。

ハードウェアの制約

1980年代、PCのグラフィックスハードウェアはデータの移動においてほとんど支援を提供しませんでした。横スクロールゲームを表示するには、プログラマーは28x28ピクセルのタイルを、320x240の画面バッファにバイトごとにコピーする必要がありました。画面バッファはタイルよりも幅広かったため、各行のコピーには、移動先のポインタを進めるための特定のメモリオフセット計算が必要でした。

初期のグラフィックスハードウェアのロジックを示すアセンブリコードのリスト
グラフィックスハードウェアの制約を管理するために使用された初期のアセンブリコードの例。blog.moertel.com

このスクリーンショットは、8ビット時代に必要であった手動のメモリ管理を説明しています。

「私はマシン上の利用可能なすべてのレジスタを使い切ることに成功した」

— A Great Old-Timey Game-Programming Hack - Tom Moertel’s Blog

反復的なアセンブリ最適化

最初の最適化では、行のワードをコピーするために使用される7カウントのループのオーバーヘッドを排除するため、内側ループを展開しました。これにより、サイクル数は157から120に減少し、30%の高速化を実現しました。

行コピーのサイクル数: 素朴なループ 157 cycles; 最適化されたループ 98 cycles; スタックハック 56 cycles
行コピーのサイクル数blog.moertel.com

HN Digestによる図。blog.moertel.comの報告数値に基づく。数値はソースの表記通り。

次のステップでは、3サイクルのポストインクリメント操作を1サイクルの正確なオフセットに置き換えました。ソースおよびデスティネーションレジスタに対する各ワードの正確な位置を計算することで、開発者は行コピーのコストを98サイクルに削減し、元のコードから60%の改善を達成しました。

パフォーマンス最適化のためのアセンブリコードのリスト
サイクル数を削減するために使用された反復的な最適化を示すアセンブリコード。blog.moertel.com

コードのリストは、単純なループから最適化されたアセンブリ命令への移行を示しています。

スタックとダイレクトページハック

最も極端な最適化は、6809のユニークなアーキテクチャを活用したものでした。このプロセッサには、非常に少ないサイクル数ですべてのレジスタをハードウェアスタックにプッシュまたはプルできるPSHSおよびPULS命令を備えていました。スタックレジスタ(SおよびU)を背景タイルと画面バッファに向けさせることで、開発者はデータを大きな塊で移動させることができました。

28ピクセルの行(14バイト)が6で割り切れないという事実に対処するため、開発者は6809の「ダイレクトページ」レジスタを利用しました。特定のメモリページを「ダイレクトページ」として指定することで、DPレジスタを追加の1バイトの仲介役として機能させることができ、コードによってプル・プッシュのペアごとに7バイトをコピーできるようになりました。この最終的な手法により、行コピー操作はわずか56サイクルにまで削減されました。

スタックおよびダイレクトページハックのためのアセンブリコードのリスト
6809のユニークなスタックおよびダイレクトページレジスタを利用した高度なアセンブリハック。blog.moertel.com

このリストは、56サイクルの行コピーを達成した「クレイジー」なハックを実証しています。

代替的なパフォーマンス戦略

6809のハックはコピー操作を高速化することに焦点を当てていましたが、他の開発者はコピーが必要な内容自体を変更することで、同じパフォーマンスのボトルネックに対処しました。「ダーティレクタングル」や適応型タイル更新といった技術では、ゲームの状態の変化を追跡し、毎フレーム画面全体を更新するのではなく、移動した特定のピクセルやタイルのみを再描画します。