Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

簡易グラフィックスとPLOTルーチン(ベーシックマスター開発 その29)

2024/09/05BASICMASTER, 昔のパソコン

BASICMASTERには、64x48dotの簡易グラフィックスを表示する機能がある。64×48は、今ではフォント1文字のサイズである。

それでも、この制限された狭いグラフィック表示の中で、頑張って見栄えの良い画面を作ろうとしたものだ。


簡易グラフィックスは文字でできている

初期の日本のパソコンは専用のグラフィック機能を持たないものが多い。TK-80BSもMZ-80KもPC-8001も そうだ。

では、どのようにして簡易グラフィックを表示しているかというと、1文字を何等分かしたドットの組み合わせで表示を行っていた。

ベーシックマスターでは、1文字を4分割していて、32行x24文字にそれを表示することで、擬似的に64x48dotを表示していた。画面にPOKEすると、セミグラフィック文字が表示される。
(画面を見ると、1文字が4ドット分の大きさになっていることもわかる)

MZ-80Kも同様に4分割で80×50のセミグラフィックを表示できたし、PC-8001は1文字を8分割していて 実に160×100ドットの表示が行えた。羨ましい。

ベーシックマスターのセミグラフィックは4分割の並びが素直じゃ無い

さて、ベーシックマスターのグラフィック文字は、文字コード$00-$0Fの16個を使っていて、下記のような並びになっている(図はマニュアルから引用)。

一見すると並びが不自然である。

L2 BASICのPLOT ルーチンを読むと、dot位置に下記のように重みをつけた値を3倍し、下4bitの位置を文字コードとして使っているようだ。なぜ3倍?

b1b3
b0b2


他機種はわかりやすい

MZ-80K、PC-8001のセミグラフィック文字はb0-b3(あるいはb0-b7)が2列に並んでいて、bitと文字コードの対応が素直に計算できる。

なぜかベーシックマスターでは(0,0)が左下になっていて、さらに値が3倍されているために、テーブル参照が必要になっている。なんでこんな順番にしたのだろうか?

NTB用にプログラム書いた

NTBを移植するときに、グラフィック命令も欲しかったので、PLOTルーチンをでっちあげた。
x,yの座標を2で割ってVRAMアドレスに変換し、2の剰余(AND #1)をx=1,y=2の重みを付けて足してキャラクタ内のbit位置(0-3)を計算した。

b0b1
b2b3

bitと文字の対応は双方向のテーブルを使っている。

まずキャラクタコード(0-15)からbit表現に変換し、その後bit AND/OR/XOR処理を行い、結果を再びキャラクタコードに戻す。

L2 BASIC PLOTの重み付けを解析する前だったので dot→CHRがテーブルになっている。


*
*   ベーシックマスターのグラフィックキャラクタは素直な2進表現ではないので、変換テーブルが必要
*               00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
PLOTD0  FCB     $0,$7,$9,$4,$A,$D,$1,$E,$2,$5,$B,$6,$8,$F,$3,$C ; CHR→dot
PLOTD1  FCB     $0,$6,$8,$E,$3,$9,$B,$1,$C,$2,$4,$A,$F,$5,$7,$D ; dot←CHR

MC6800はこの手のテーブル引きは得意ではないので、手間暇がかかっている。6809なら LDA B,X で1命令なのに。6502でも1命令。

6800だと自己書き換えコードを使って2命令にするか、CPUをMB8861に差し替えて、ADX命令を使うか。

私は実際にベーシックマスターをMB6881に差し替えて使っていた。日立アセンブラをMB8861命令が使えるように改造した記事をI/Oに採用してもらったり。
(大多数の人はHD6800のままなので、投稿するソフトはMC6800のままだったけどね…)

L2 BASICの処理

L2 BASICでは、以下のテーブルが$CF6Aにあって、これ1つで処理を行っている。CHR→dotの表引きを1回だけADDIXBで行っていて、dot→CHRは3倍で戻している。


PLOT_data FCB   $00,$0B,$06,$01,$0C,$07,$02,$0D,$08,$03,$0E,$09,$04,$0F,$0A,$05        

前に述べたように、これは左上から b1/b3、次の行がb0/b2の順序である。左下がb0なのは、前述したように L2 BASICのグラフィック座標系が左下が原点(0,0)になっているせい。b1が左上なのは、b3-b0を3倍すると実際のキャラクタが得られるから。

b1b3
b0b2

dot→CHRは簡単なのだが(TAB/ABA/ABAで3倍)、逆はCHR→dotが3での除算になるので簡単にはできない。それで表引きが発生している。

BMUG TL/1では

BMUG会報1982.2 (No.2) には、TL/1の移植とプロットルーチンの追加が載っている。
こちらでは点灯/消去に各32byteのテーブルを使っている。

キャラクタコードが不自然なおかげで無意味に苦労していると思う。なぜ、このような並びにしたのだろうか?

思いついたのでメモ

11を掛けて下位4bitを取れば良さそう。11は(32+1)/3。perlで簡単なプログラムを書いて試したら、L2 BASICのテーブルの値が得られた。


#!/usr/bin/perl

for($i=0;$i<16;$i++){
	printf("%02x,",($i*0b1011)%16);
}
printf("\n");

$ perl a.pl
00,0b,06,01,0c,07,02,0d,08,03,0e,09,04,0f,0a,05,

アセンブラだとAccAに値があるとして、こうかな? 8倍+3倍。


	TAB
	ASLA
	ASLA
	ASLA
	ABA
	ABA
	ABA
	ANDA	#$0F

全部2cycleの命令なので16cycle/9bytes。ADDIXBだけでも39cycle/19bytesなので、格段に速い。

テーブルが不要になるのでメモリも喰わない。NTBのPLOTを書き換えようかな…

追記: 2024/11/24

12倍して1引いた方が小さくて速い。


	TAB
	ASLA
	ABA
	ASLA
	ASLA
	SBA
	ANDA	#$0F


追記

copilotに聞いたら、同じく11倍して16で剰余を取った例を示された。すごい。どうやってるんだ?


この順序には特定のパターンがあるようです。以下のように変換する関数を作成します:

def map_values(input_value):
    if input_value < 0 or input_value > 15:
        raise ValueError("入力値が範囲外です")
    
    # 変換のための計算
    return (input_value * 11) % 16

# テスト
for i in range(16):
    print(f"{i} -> {map_values(i)}")


L2 BASIC PLOTルーチンの謎

L2 BASICを見ていると、一見するとPLOTルーチンは画面サイズの変更に対応できそうな作りになっている。

マニュアルには書かれていないが、$0031 BYTOUTの次の$0034に再度1文字出力(CHROUT)があり、その次の$0037,$0038が画面サイズとして設定されている。

($0034(ASCOUT2)は、OPENやLIST#で呼ばれていて、出力先の変更に使われている。プリンタ出力などで使う)

L2 BASICのPLOTルーチンは、ゼロページの($37,$38)に格納されている画面サイズ($1F,$17)を見て処理を行っている。
画面サイズ($37,$38)はPLOTルーチンだけで使われていて、他の箇所では使われていない。モニタでも使っていない。

(0ページに格納されている画面サイズは、($F021,$F022)に元々あって、ASCIN〜BYTOUTを設定するときに一緒に書き込まれている)

しかし、PLOTドット位置の計算に使われている CURPOSルーチン($FAA3)は画面サイズが横32列、縦24行で固定になっていて、$0037,$0038を見ていない。

なので、$37,$38を書き換えても期待するような動作は行われない。

結局、ゼロページを無意味に食っているだけで意味のないデータになっている。謎である。