/*
 *	framrodg.c
 *
 *	ROaD GENerator
 *
 *	* Fri Jun 02 21:33:04 JST 2017 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sat Jun 03 21:50:28 JST 2017 Naoyuki Sawa
 *	- RodGen_DrawLine2()を追加しました。
 *	  既存のRodGen_DrawLine1()とRodGen_DrawLine2()の違いは、引数の指定方法の違いだけであり、どちらを使っても同じ描画結果が得られます。
 *	  アプリケーション次第で使い易い方の関数を使って下さい。
 *	  一般的には、RodGen_DrawLine1()の方が柔軟性が高いですが、RodGen_DrawLine2()の方が直感的で使い易いです。
 *	  RodGen_DrawLine1()の方が柔軟性が高い理由は、RodGen_DrawLine1()で(zoom_n=0,zoom_d<0)に相当する描画が、RodGen_DrawLine2()では指定出来ないからです。
 *	  とは言え、RodGen_DrawLine1()で(zoom_n=0,zoom_d<0)に相当する描画に依存するようなアプリケーションは稀だと思うので、あまり気にする必要は有りません。
 *	* Sun Jun 04 22:03:50 JST 2017 Naoyuki Sawa
 *	- RodGen_DrawSeg()を追加しました。
 */
#include "clip.h"
/****************************************************************************
 *	グローバル関数
 ****************************************************************************/
//画面上の指定されたラインに、指定された道のパターンを、拡大縮小して描画する。
//[in]
//	pRodGen			管理構造体。
//	iLine			画面上のライン番号。
//	iRoad			道のパターン番号。
//	offset			道のパターンの左端ピクセルを、画面上の左から何ピクセル目に描画するか。
//	zoom_n,zoom_d		道のパターンを(zoom_n/zoom_d)倍に拡大縮小して描画する。
//				描画範囲は常に画面上の左端から右端の全体であり、余白が生じた場合は道のパターンの左端,及び,右端ピクセルを画面内一杯まで引き伸ばして描画します。
//				zoom_nとzoom_dに異符号を指定した場合、逆方向に拡大縮小して描画します。
//				(zoom_n==0)&&(zoom_d>=0)を指定した場合、offsetよりも左には道のパターンの左端ピクセル、offsetよりも右は道のパターンの右端ピクセルを描画します。
//				(zoom_n==0)&&(zoom_d< 0)を指定した場合、offsetよりも左には道のパターンの右端ピクセル、offsetよりも右は道のパターンの左端ピクセルを描画します。
//[note]
//	- RodGen_DrawLine1()の引数zoom_n,zoom_dの変数名に関する注意点:
//	  zoom_n(=Numerator=分子)とzoom_d(=Denominator=分母)は、rgfxを何倍(zoom_n/zoom_d)に拡縮してvramに描画するかという観点です。
//	  当関数の実際の処理は、vram上の各ピクセルに対して参照先のrgfxのピクセルを求めるので、使い方が逆(zoom_d/zoom_n)になります。
//	  変数名と実際の処理での使い方が一致していない事に注意して下さい。
#ifndef PIECE
void RodGen_DrawLine1(ST_RodGen* pRodGen, int iLine, int iRoad, int offset, int zoom_n/*分子*/, int zoom_d/*分母*/) {
	uint8_t* vram;
	const uint8_t* rgfx;
	int i, u, n, e, f, v, x;
	//vramの指定されたラインの左端アドレスを求める。
	vram = pRodGen->vram + (pRodGen->vram_pitch * iLine);
	//rgfxの指定されたパターンの左端アドレスを求める。
	rgfx = pRodGen->rgfx + (pRodGen->rgfx_pitch * iRoad);
	//zoom_n(=拡大率の分子=処理上の分母)が0,又は,負ならば…
	if(zoom_n <= 0) {
		//zoom_nが0ならば、特例処理とする。
		if(!zoom_n) {
			//offsetよりも左ならば、rgfxの左端の色とする。
			//offsetよりも右ならば、rgfxの右端の色とする。
			//ただし、zoom_dが負の場合は、左右を逆転する。
		#if 0	//①素直な実装。
			for(x = 0; x < pRodGen->vram_width; x++) {
				const uint8_t* p = rgfx;
				if(zoom_d >= 0) {
					if(offset <  x) { p += (pRodGen->rgfx_width - 1); }	//(zoom_d>=0)&&((offset-x)<0)
				} else {
					if(offset >= x) { p += (pRodGen->rgfx_width - 1); }	//(zoom_d<0)&&((offset-x)>=0)
				}
				*vram++ = *p;
			}
		#elif 0	//②毎回offsetとxを比較する代わりに、offsetを直接変化させて少し最適化した実装。
			x = pRodGen->vram_width;
			do {
				const uint8_t* p = rgfx;
				if(zoom_d >= 0) {
					if(offset <  0) { p += (pRodGen->rgfx_width - 1); }	//(zoom_d>=0)&&((offset-x)<0)
				} else {
					if(offset >= 0) { p += (pRodGen->rgfx_width - 1); }	//(zoom_d<0)&&((offset-x)>=0)
				}
				*vram++ = *p;
				offset--;
			} while(--x);
		#else	//③比較処理を一箇所にまとめて最適化した実装。①～③どれでも同じ結果になります。③をベースにアセンブラ化する事にしました。
			x = pRodGen->vram_width;
			do {
				const uint8_t* p = rgfx;
				if((zoom_d ^ offset) < 0) { p += (pRodGen->rgfx_width - 1); }	//((zoom_d>=0)&&((offset-x)<0))||((zoom_d<0)&&((offset-x)>=0))
				*vram++ = *p;
				offset--;
			} while(--x);
		#endif
			return;	//ここまで
		}
		//zoom_nが正になるように、zoom_nとzoom_dの符号を反転する。
		zoom_n = -zoom_n;
		zoom_d = -zoom_d;
	}
	//DDAセットアップ。
	i = -(offset * zoom_d / zoom_n);	//初期値(+/-)	=vram[0]に対応するrgfxのピクセル位置
	e =   offset * zoom_d % zoom_n;		//誤差項の計算途中の値(-(zoom_n-1)～+(zoom_n-1))
	u =            zoom_d / zoom_n;		//固定変化量(+/-)
	n =            zoom_d % zoom_n;		//誤差項の減少量(+/-)
	v =          1;				//┐
	if(zoom_d < 0) {			//├誤差項が0以下になった時のピクセル位置の変化量(+/-)
		v = -1;				//┘
		n = -n;				//誤差項の減少量(+)
		e = -e;				//誤差項の計算途中の値(-(zoom_n-1)～+(zoom_n-1))
		e -= zoom_n;			//この下の「e+=zoom_n」を相殺する。
	}
	e += zoom_n;				//誤差項(+)	ただし(offset<0)&&(zoom_d<0)の場合(e<0)に成り得るが、(i<=0)の範囲でしか変化しないので描画結果は問題無い。
	f  = zoom_n;				//誤差項が0以下になった時の誤差項の増加量
	//vram上の各ピクセルについて…
	x = pRodGen->vram_width;
	do {
		//rgfxのピクセル位置を求める。
#if 0		//□素直な実装。
		int j = i;
		if(j < 0) { j = 0; }							//rgfxの左端を超えたら、rgfxの左端の色とする。(GL_CLAMP相当)
		if(j > (pRodGen->rgfx_width - 1)) { j = (pRodGen->rgfx_width - 1); }	//rgfxの右端を超えたら、rgfxの右端の色とする。(GL_CLAMP相当)
#else		//□アセンブラ化した時に比較回数が少なくて済むように最適化した実装。
		int j;
		if(  (unsigned)i <= (unsigned)(pRodGen->rgfx_width - 1)) {	//←┐
			j = i;							//　├アセンブラ版では比較は1回だけ行い、そのフラグを参照して分岐条件を変えるだけで済む。
		} else if((int)i <=      (int)(pRodGen->rgfx_width - 1)) {	//←┘
			j = 0;
		} else {
			j = (pRodGen->rgfx_width - 1);
		}
#endif
		//描画処理。
		*vram++ = rgfx[j];
		//DDAステップ実行。
		i += u;
		if((e -= n) <= 0) {
			e += f;
			i += v;
		}
	} while(--x);
}
#else //PIECE
void RodGen_DrawLine1(ST_RodGen* pRodGen, int iLine, int iRoad, int offset, int zoom_n/*分子*/, int zoom_d/*分母*/);
asm("
		.code
		.align		1
		.global		RodGen_DrawLine1
RodGen_DrawLine1:
		;//%r12    := pRodGen
		;//%r13    := iLine
		;//%r14    := iRoad
		;//%r15    := offset
		;//[%sp+0] := retp
		;//[%sp+4] := zoom_n
		;//[%sp+8] := zoom_d
		ld.w		%r4, [%r12]+			;//%r4  := vram                = pRodGen->vram
		ld.uh		%r5, [%r12]+			;//%r5  := vram_width          = pRodGen->vram_width
		ld.uh		%r9, [%r12]+			;//%r9  :=          vram_pitch = pRodGen->vram_pitch
		mltu.h		%r9, %r13			;//%alr :=          vram_pitch * iLine						(0<=iLine<=UINT16_MAX)と想定してmltu.hを使用した。
		ld.w		%r9, %alr			;//%r9  :=          vram_pitch * iLine
		add		%r4, %r9			;//%r4  := vram += (vram_pitch * iLine)
		ld.w		%r6, [%r12]+			;//%r6  := rgfx                = pRodGen->rgfx
		ld.uh		%r7, [%r12]+			;//%r7  := rgfx_width          = pRodGen->rgfx_width
		ld.uh		%r9, [%r12]			;//%r9  :=          rgfx_pitch = pRodGen->rgfx_pitch
		sub		%r7, 1				;//%r7  := rgfx_width - 1				*anti-interlock*
		mltu.h		%r9, %r14			;//%alr :=          rgfx_pitch * iRoad						(0<=iRoad<=UINT16_MAX)と想定してmltu.hを使用した。
		ld.w		%r9, %alr			;//%r9  :=          rgfx_pitch * iRoad
		add		%r6, %r9			;//%r6  := rgfx += (rgfx_pitch * iRoad)
		xld.w		%r13, [%sp+4]			;//%r13 := zoom_n
		xld.w		%r14, [%sp+8]			;//%r14 := zoom_d
		;//%r4     := vram
		;//%r5     := x = vram_width
		;//%r6     := rgfx
		;//%r7     := rgfx_width - 1
		;//%r13    := zoom_n
		;//%r14    := zoom_d
		;//%r15    := offset
		;//[%sp+0] := retp
		;//[%sp+4] := (zoom_n)
		;//[%sp+8] := (zoom_d)
		cmp		%r13, 0				;//if(zoom_n <= 0) {
		jrgt		RodGen_DrawLine1_L30		;//  
		jrne		RodGen_DrawLine1_L20		;//  if(!zoom_n) {								C=0,V=0,Z=1,N=0		────────────┐①	以下の特例処理へ突入する場合は、必ず(C=0,V=0,Z=1,N=0)と想定出来ます。
RodGen_DrawLine1_L10:						;//    do {															│
		ld.w		%r9, %r6			;//      %r9  := p = rgfx													│
		xor		%r14, %r15			;//      %r14 := zoom_d ^ offset						C=?(変化無し),V=0(変化無し),Z=?,N=?	←───┤	xorはC,Vを変化させません。初回ループでは①のC,Vを引き継ぎ、二回目以降のループでは②のC,Vを引き継ぎます。この下の条件分岐ではNとVだけを参照し、CとZは関係有りません。引き継いだVとxorが変化させたNを参照して、この下で条件分岐を行います。
		jrge.d		3				;//      if(N^V) {														│	原則的にV=0と想定出来るので、xorが変化させたNだけで条件分岐を行うのと同じです。Nは(zoom_d^offset)の最上位ビットを反映していますから、(zoom_d^offset)が同符号か異符号かによって条件分岐を行います。尚、万一V=1だったとしても描画内容が不正に左右反転してしまうだけでメモリ破壊等は起こりません。V=1になるのは引数offsetが異常値だった場合だけですから描画結果が不正になっても問題無いと思います。
		 xor		%r14, %r15			;//      %r14 := zoom_d					*delay*									│
		 add		%r9, %r7			;//        %r9  := p += (rgfx_width - 1)											│
		ld.b		%r9, [%r9]			;//      %r9  := c = *p														│
		sub		%r5, 1				;//      %r5  := x--					*anti-interlock*							│
		ld.b		[%r4]+, %r9			;//      *vram++ = c														│
		jrne.d		RodGen_DrawLine1_L10		;//    } while(x)														│
		sub		%r15, 1				;//      %r15 := offset--				*delay*			C=?,V=0(右記参照),Z=?,N=?	────────┘②	ここでV=1になるのはoffsetがINT_MINからINT_MAXへ変化する場合のみです。一般的に引数offsetにINT_MIN付近の値を指定する事は無いのでここではV=0になると想定出来ます。万一V=1になったとしても描画内容が不正になるだけでメモリ破壊等は起こりません。だいたい引数offsetにINT_MIN付近の値を指定する事自体が異常ですから描画内容が不正になっても問題無いと思います。
		ret						;//    return
RodGen_DrawLine1_L20:						;//  }
		not		%r13, %r13			;//  %r13 := zoom_n = -zoom_n
		add		%r13, 1				;//  
		not		%r14, %r14			;//  %r14 := zoom_d = -zoom_d
		add		%r14, 1				;//  
RodGen_DrawLine1_L30:						;//}
		mlt.w		%r15, %r14			;//%alr :=       offset * zoom_d						拡大率の分子と分母のスケールの解釈はアプリケーションの次第であり、UINT16_MAXを超える値も指定される(例えば16.16固定小数点と見なす場合など)。従ってここではmlt.hではなくmlt.wを使用しておくのが安全だろう。
		xcall.d		__divsi3			;//%r10 :=       offset * zoom_d / zoom_n					%ahr := offset * zoom_d % zoom_n
		ld.w		%r12, %alr			;//%r12 :=       offset * zoom_d			*delay*
		not		%r11, %r10			;//%r11 := i = -(offset * zoom_d / zoom_n)
		add		%r11, 1				;//
		ld.w		%r15, %ahr			;//%r15 := e =   offset * zoom_d % zoom_n
		xcall.d		__divsi3			;//%r10 := u =            zoom_d / zoom_n					%ahr := zoom_d % zoom_n
		ld.w		%r12, %r14			;//%r12 :=                zoom_d			*delay*
		ld.w		%r12, %ahr			;//%r12 := n =            zoom_d % zoom_n
		cmp		%r14, 0				;//if(zoom_d < 0) {
		jrge.d		8				;//  
		 ld.w		%r14, 1				;//%r14 := v = 1					*delay*
		 ld.w		%r14, -1			;//  %r14 := v = -1
		 not		%r12, %r12			;//  %r12 := n = -n
		 add		%r12, 1				;//  
		 not		%r15, %r15			;//  %r15 := e = -e
		 add		%r15, 1				;//  
		 sub		%r15, %r13			;//  %r15 := e -= zoom_n
		add		%r15, %r13			;//} %r15 := e += zoom_n
		;//%r4     := vram
		;//%r5     := x = vram_width
		;//%r6     := rgfx
		;//%r7     := rgfx_width - 1
		;//%r10    := u
		;//%r11    := i
		;//%r12    := n
		;//%r13    := f = zoom_n
		;//%r14    := v
		;//%r15    := e
		;//[%sp+0] := retp
		;//[%sp+4] := (zoom_n)
		;//[%sp+8] := (zoom_d)
		cmp		%r11, %r7			;//  %psr :=        i <=>          (rgfx_width - 1)	────────────┐
RodGen_DrawLine1_L40:						;//do {										│
		jrule.d		5				;//  if(  (unsigned)i <= (unsigned)(rgfx_width - 1)) {	←───────────┤
		 ld.w		%r9, %r11			;//           %r9  := j = i				*delay*			│
		 jrle.d		3				;//  } else if((int)i <=      (int)(rgfx_width - 1)) {				│
		  ld.w		%r9, 0				;//           %r9  := j = 0				*delay*			│
		  ld.w		%r9, %r7			;//  } else { %r9  := j =          (rgfx_width - 1)  }				│
		add		%r9, %r6			;//  %r9  :=      &rgfx[j]							│
		ld.b		%r9, [%r9]			;//  %r9  :=   c = rgfx[j]							│
		sub		%r15, %r12			;//  %r15 := e -= n					*anti-interlock*	│
		ld.b		[%r4]+, %r9			;//  *vram++ = c								│
		jrgt.d		4				;//  if(e <= 0) {								│
		 add		%r11, %r10			;//  %r11 := i += u					*delay*			│
		 add		%r15, %r13			;//    %r15 := e += f								│
		 add		%r11, %r14			;//    %r11 := i += v }								│
		sub		%r5, 1				;//  %r5  := x--								│
		jrne.d		RodGen_DrawLine1_L40		;//} while(x)									│
		cmp		%r11, %r7			;//  %psr :=        i <=>          (rgfx_width - 1)	*delay*	────────┘	ここがタイトループなので一命令増えてでも遅延スロットを有効活用して高速化する事を優先した。
		ret
");
#endif//PIECE
/****************************************************************************
 *	RodGen_DrawLine1(),RodGen_DrawLine2()の使用例
 ****************************************************************************/
#if 0
void test();
#ifdef  WIN32
//□テストルーチンのWindows依存部分
uint8_t vbuff[128 * 88];
void update() {
	int SiHandle, GrHandle, i, x, y;
	SiHandle = MakePAL8ColorSoftImage(128, 88);
	for(i = 0; i < 256; i++) {
		if(i < 4) { SetPaletteSoftImage(SiHandle, i, 255 - (i * 85), 255 - (i * 85), 255 - (i * 85), 0); }
		else      { SetPaletteSoftImage(SiHandle, i,              0,            255,              0, 0); }
	}
	for(y = 0; y < 88; y++) {
		for(x = 0; x < 128; x++) {
			DrawPixelPalCodeSoftImage(SiHandle, x, y, vbuff[(y * 128) + x]);
		}
	}
	GrHandle = CreateGraphFromSoftImage(SiHandle);
	DrawExtendGraph(4, 4, DISP_X - 4, DISP_Y - 4, GrHandle, FALSE);
	DeleteGraph(GrHandle);
	DeleteSoftImage(SiHandle);
	memset(vbuff, -1, sizeof vbuff);	//次回のために
}
int cpp_main(int argc, char* argv[]) {
	if(RRS_i_job(pRRS) != (RRS_n_job(pRRS) - 1)) { DIE(); }	//最後のジョブがUIジョブである事を確認する。
	if(!setjmp(env_exit)) { test(); }
	return EXIT_SUCCESS;
}
#endif//WIN32
#ifdef  PIECE
//□テストルーチンのP/ECE依存部分
int app_main(int argc, char* argv[]) {
	test();
	return EXIT_SUCCESS;
}
#endif//PIECE
//□テストルーチンの共通部分
ST_RodGen stRodGen;
const uint8_t rgfx[]={0,1,2,3};
void test() {
	RodGen_Init(&stRodGen, vbuff, 128, 128, rgfx, 4, 4);
	for(;;) {
		int i = UPDOWN_MOTION(now, 128);
		schedule();
		//RodGen_DrawLine1()
		RodGen_DrawLine1(&stRodGen, 0, 0, 0, 1, 2);		//左端を基準にして1/2倍表示
		RodGen_DrawLine1(&stRodGen, 1, 0, 0, 1, 1);		//左端を基準にして等倍表示
		RodGen_DrawLine1(&stRodGen, 2, 0, 0, 8, 1);		//左端を基準にして8倍表示
		RodGen_DrawLine1(&stRodGen, 3, 0, 0, i, 4);		//左端を基準にして拡縮表示
		RodGen_DrawLine1(&stRodGen, 4, 0, -16, 1, 2);		//左画面外を基準にして1/2倍表示
		RodGen_DrawLine1(&stRodGen, 5, 0, -16, 1, 1);		//左画面外を基準にして等倍表示
		RodGen_DrawLine1(&stRodGen, 6, 0, -16, 8, 1);		//左画面外を基準にして8倍表示
		RodGen_DrawLine1(&stRodGen, 7, 0, -16, i, 4);		//左画面外を基準にして拡縮表示
		RodGen_DrawLine1(&stRodGen, 8, 0, 127, 1, -2);		//右端を基準にして逆方向に1/2倍表示
		RodGen_DrawLine1(&stRodGen, 9, 0, 127, 1, -1);		//右端を基準にして逆方向に等倍表示
		RodGen_DrawLine1(&stRodGen, 10, 0, 127, 8, -1);		//右端を基準にして逆方向に8倍表示
		RodGen_DrawLine1(&stRodGen, 11, 0, 127, i, -4);		//右端を基準にして逆方向に拡縮表示		「～,-i,4)」よりも「～,i,-4)」が望ましい。(i=0)の時も逆方向の描画とするためです。
		RodGen_DrawLine1(&stRodGen, 12, 0, 127+16, 1, -2);	//右画面外を基準にして逆方向に1/2倍表示
		RodGen_DrawLine1(&stRodGen, 13, 0, 127+16, 1, -1);	//右画面外を基準にして逆方向に等倍表示
		RodGen_DrawLine1(&stRodGen, 14, 0, 127+16, 8, -1);	//右画面外を基準にして逆方向に8倍表示
		RodGen_DrawLine1(&stRodGen, 15, 0, 127+16, i, -4);	//右画面外を基準にして逆方向に拡縮表示
		RodGen_DrawLine1(&stRodGen, 16, 0, 64, 1, 2);		//中央を基準にして1/2倍表示
		RodGen_DrawLine1(&stRodGen, 17, 0, 64, 1, 1);		//中央を基準にして等倍表示
		RodGen_DrawLine1(&stRodGen, 18, 0, 64, 8, 1);		//中央を基準にして8倍表示
		RodGen_DrawLine1(&stRodGen, 19, 0, 64, i, 4);		//中央を基準にして拡縮表示
		RodGen_DrawLine1(&stRodGen, 20, 0, 63, 1, -2);		//中央を基準にして逆方向に1/2倍表示
		RodGen_DrawLine1(&stRodGen, 21, 0, 63, 1, -1);		//中央を基準にして逆方向に等倍表示
		RodGen_DrawLine1(&stRodGen, 22, 0, 63, 8, -1);		//中央を基準にして逆方向に8倍表示
		RodGen_DrawLine1(&stRodGen, 23, 0, 63, i, -4);		//中央を基準にして逆方向に拡縮表示
		RodGen_DrawLine1(&stRodGen, 24, 0, -16, i, -4);		//左画面外を基準にして逆方向へ拡縮表示		画面内には常にrgfx[0(白)]が描画されれば正常です。
		RodGen_DrawLine1(&stRodGen, 25, 0, 127+16, i, 4);	//右画面外を基準にして拡縮表示			画面内には常にrgfx[0(白)]が描画されれば正常です。
		RodGen_DrawLine1(&stRodGen, 26, 0, i, 0, 1);		//基準位置を変化しながら0倍表示
		RodGen_DrawLine1(&stRodGen, 27, 0, i, 0, -1);		//基準位置を変化しながら逆方向に0倍表示
		//RodGen_DrawLine2()
		RodGen_DrawLine2(&stRodGen, 28, 0, i, 127-i);		//左端と右端を交互に行き来する
#ifdef  WIN32
		update();
#endif//WIN32
	}
}
#endif
