/*	
 *	clippp4.c
 *
 *	P/ECE PPU (RICOH RP2C02) Emulator (1/4解像度版)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Wed Feb 09 04:34:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

/****************************************************************************
 *
 ****************************************************************************/

#ifdef PPU_GRAYSCALE
/* 値の範囲は White:0..15:Black です。 */
const unsigned char ppu4_grayscale_table[64] = {
#define PPU_RGB(r,g,b) (15 - (int)((r*0.30 + g*0.59 + b*0.11) / 0xff * 15 + 0.5))
PPU_RGB(0x80,0x80,0x80),PPU_RGB(0x00,0x3d,0xa6),PPU_RGB(0x00,0x12,0xb0),PPU_RGB(0x44,0x00,0x96), /* 00-03 */
PPU_RGB(0xa1,0x00,0x5e),PPU_RGB(0xc7,0x00,0x28),PPU_RGB(0xba,0x06,0x00),PPU_RGB(0x8c,0x17,0x00), /* 04-07 */
PPU_RGB(0x5c,0x2f,0x00),PPU_RGB(0x10,0x45,0x00),PPU_RGB(0x05,0x4a,0x00),PPU_RGB(0x00,0x47,0x2e), /* 08-0B */
PPU_RGB(0x00,0x41,0x66),PPU_RGB(0x00,0x00,0x00),PPU_RGB(0x05,0x05,0x05),PPU_RGB(0x05,0x05,0x05), /* 0C-0F */
PPU_RGB(0xc7,0xc7,0xc7),PPU_RGB(0x00,0x77,0xff),PPU_RGB(0x21,0x55,0xff),PPU_RGB(0x82,0x37,0xfa), /* 10-13 */
PPU_RGB(0xeb,0x2f,0xb5),PPU_RGB(0xff,0x29,0x50),PPU_RGB(0xff,0x22,0x00),PPU_RGB(0xd6,0x32,0x00), /* 14-17 */
PPU_RGB(0xc4,0x62,0x00),PPU_RGB(0x35,0x80,0x00),PPU_RGB(0x05,0x8f,0x00),PPU_RGB(0x00,0x8a,0x55), /* 18-1B */
PPU_RGB(0x00,0x99,0xcc),PPU_RGB(0x21,0x21,0x21),PPU_RGB(0x09,0x09,0x09),PPU_RGB(0x09,0x09,0x09), /* 1C-1F */
PPU_RGB(0xff,0xff,0xff),PPU_RGB(0x0f,0xd7,0xff),PPU_RGB(0x69,0xa2,0xff),PPU_RGB(0xd4,0x80,0xff), /* 20-23 */
PPU_RGB(0xff,0x45,0xf3),PPU_RGB(0xff,0x61,0x8b),PPU_RGB(0xff,0x88,0x33),PPU_RGB(0xff,0x9c,0x12), /* 24-27 */
PPU_RGB(0xfa,0xbc,0x20),PPU_RGB(0x9f,0xe3,0x0e),PPU_RGB(0x2b,0xf0,0x35),PPU_RGB(0x0c,0xf0,0xa4), /* 28-2B */
PPU_RGB(0x05,0xfb,0xff),PPU_RGB(0x5e,0x5e,0x5e),PPU_RGB(0x0d,0x0d,0x0d),PPU_RGB(0x0d,0x0d,0x0d), /* 2C-2F */
PPU_RGB(0xff,0xff,0xff),PPU_RGB(0xa6,0xfc,0xff),PPU_RGB(0xb3,0xec,0xff),PPU_RGB(0xda,0xab,0xeb), /* 30-33 */
PPU_RGB(0xff,0xa8,0xf9),PPU_RGB(0xff,0xab,0xb3),PPU_RGB(0xff,0xd2,0xb0),PPU_RGB(0xff,0xef,0xa6), /* 34-37 */
PPU_RGB(0xff,0xf7,0x9c),PPU_RGB(0xd7,0xe8,0x95),PPU_RGB(0xa6,0xed,0xaf),PPU_RGB(0xa2,0xf2,0xda), /* 38-3B */
PPU_RGB(0x99,0xff,0xfc),PPU_RGB(0xdd,0xdd,0xdd),PPU_RGB(0x11,0x11,0x11),PPU_RGB(0x11,0x11,0x11), /* 3C-3F */
#undef PPU_RGB
};
#endif /*PPU_GRAYSCALE*/

/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/

int
ppu4_update(unsigned char vbuff[/*128*120*/])
{
	PPU* p_ppu = &ppu;
	unsigned char* vram = ppu_vram;
	unsigned char* sprram = ppu_sprram;
	//
	int i;
	int v;
	int scan_start;
	int scan_end;
	int sp0_ypos;
	const unsigned char* src;
	      unsigned char* dst;

	/* * 描画処理は、走査区間に対するCPU処理の実行後に行う必要があります。
	 *   実行前に描画してしまうと、この走査区間に対する設定変更(スクロールなど)が描画に反映されません。
	 *   従って、処理手順は、
	 *	1. 前回の走査区間の描画 +-+- ppu4_update()
	 *	2. 今回の走査区間の算出 +-+
	 *	3. CPU実行 ----------------- アプリケーション
	 *   となります。
	 */

	/**************************
	 *  前回の走査区間の描画  *
	 **************************/

	/* 前回の走査区間を取得します。 */
	scan_start = p_ppu->scan_start;	/* 0..240 */
	scan_end   = p_ppu->scan_end;	/* 0..240 */
	ASSERT(0 <= scan_start && scan_start <= 240);
	ASSERT(0 <= scan_end   && scan_end   <= 240);

	/* 実描画が要求されていたら(vbuff<>NULL)、描画を行います。
	 * ただし、前回の走査区間がVBlank区間(scan_start=240,scan_end=0)ならば、描画しません。
	 */
	if(vbuff && scan_end) {

		/* 現在の描画ターゲットを格納します。 */
		p_ppu->vbuff = vbuff;

		/* パレットを展開します。 */
		src = &vram[0x3f00];
		dst = p_ppu->clut;
		i = 32;
		do {
			v = *src++ & 63;
#ifdef PPU_GRAYSCALE
			v = ppu4_grayscale_table[v];
#endif /*PPU_GRAYSCALE*/
			*dst++ = v;
		} while(--i);

		/* 背景を塗りつぶします。 */
		{
			int s = scan_start / 2;
			int e = scan_end   / 2;
			memset(&vbuff[s * 128], p_ppu->clut[0], (e - s) * 128);
		}

		/* 背面スプライトを描きます。 */
		if(p_ppu->ppu_control2 & PPU_CONTROL2_SPRITE_VISIBILITY) {
			ppu4_draw_sprite(1);
		}

		/* BGを描きます。 */
		if(p_ppu->ppu_control2 & PPU_CONTROL2_BACKGROUND_VISIBILITY) {
			ppu4_draw_bg();
		}

		/* 前面スプライトを描きます。 */
		if(p_ppu->ppu_control2 & PPU_CONTROL2_SPRITE_VISIBILITY) {
			ppu4_draw_sprite(0);
		}
	}

	/**************************
	 *  今回の走査区間の算出  *
	 **************************/

	/* 前回の走査終了ラインを、今回の走査開始ラインとします。 */
	p_ppu->scan_start = scan_start = scan_end;

	/* 走査開始ラインが画面内(0..239)の場合: */
	if(scan_start < 240) {

		/* フレーム開始時に、VBlankとSprite#0 Hitフラグをクリアします。 */
		if(scan_start == 0/*画面内の最初のライン=フレーム開始*/) {
			p_ppu->ppu_status &= ~(PPU_STATUS_VBLANK_OCCURRENCE | PPU_STATUS_SPRITE0_OCCURRENCE);
		}

		/* まだSprite#0 Hitを検出していなければ... */
		if(!(p_ppu->ppu_status & PPU_STATUS_SPRITE0_OCCURRENCE)) {

			/* Sprite#0のY座標を求めます。(-16..239) */
			sp0_ypos = (unsigned char)(sprram[0] + 1/*仕様*/);
			if(sp0_ypos >= 240) {
				sp0_ypos = (char)sp0_ypos; /* 240..255 -> -16..-1 */
			}

			/* 走査開始ラインがまだSprite#0のY座標に達していない場合: */
			if(scan_start < sp0_ypos) {

				/* Sprite#0のY座標の直前ラインまで進めます。 */
				scan_end = sp0_ypos; /* 1..239 */

			/* 走査開始ラインがSprite#0のY座標と同じか超えている場合: */
			} else {

				/* Sprite#0 Hitフラグをセットします。 */
				p_ppu->ppu_status |= PPU_STATUS_SPRITE0_OCCURRENCE;

				/* 画面外の最初のラインまで進めます。 */
				scan_end = 240;
			}

		/* 既にSprite#0 Hitを検出していたら... */
		} else {

			/* 画面外の最初のラインまで進めます。 */
			scan_end = 240;
		}

		/* 次回の走査開始ラインを格納します。 */
		p_ppu->scan_end = scan_end;

	/* 走査開始ラインが画面外の最初のライン(240)の場合 */
	} else {

		/* VBlankを開始し、必要に応じて割り込みラインをアサートします。 */
		p_ppu->ppu_status |= PPU_STATUS_VBLANK_OCCURRENCE;
		if(p_ppu->ppu_control1 & PPU_CONTROL1_NMI_ON_VBLANK) {
			p_ppu->flags |= PPU_FLAGS_INTERRUPT;
		}

		/* 画面外の最後のラインまで進めます。 */
		scan_end = 262;

		/* 次回の走査開始ラインを格納します。 */
		p_ppu->scan_end = 0;
	}

	/* NTSCカラーサブキャリアクロック単位で、経過サイクル数を返します。 */
	return (scan_end - scan_start) * 228/*1ライン当りのサイクル数*/;
}

/****************************************************************************
 *	内部関数
 ****************************************************************************/

void
ppu4_draw_bg()
{
	PPU* p_ppu = &ppu;
	unsigned char* vram = ppu_vram;
	//
	int scan_start = p_ppu->scan_start;
	int scan_end = p_ppu->scan_end;
	//
	int i;
	int x;
	int y;
	int col;
	int row;
	int code;
	int attr;
	int palno;
	int code_base;
	int name_table;

	code_base = (p_ppu->ppu_control1 & PPU_CONTROL1_BACKGROUND_PATTERN_TABLE_ADDRESS) << 4; /* $00->$000, $10->$100 */
	name_table = 0x2000 | (p_ppu->ppu_control1 & PPU_CONTROL1_NAME_TABLE_ADDRESS) << 10; /* $2000,$2400,$2800,$2c00 */

	x = p_ppu->vram_address1;
	y = (unsigned char)x;
	x >>= 8;
	if(y >= 240) { /* ※要確認 */
		name_table ^= 0x800;
		y -= 16;
	}
	col = x >> 3; /* x = 0..255 -> col = 0..31 */
	row = y >> 3; /* y = 0..239 -> row = 0..29 */
	x = -(x & 7); /* x = -7..0 */
	y = -(y & 7); /* y = -7..0 */

	do {
		/* 描画領域の上方向に完全に外れるRowはスキップします。 */
		if(y > scan_start - 8) {
			i = 32 + 1;
			do {
				/*        ++++------------ name_table
				 * addr = 10nnrr rrrccccc
				 *            || |||+++++- col
				 *            ++-+++------ row
				 */
				code = vram[name_table | row << 5 | col] | code_base;
				/*        ++++------------ name_table
				 *        ||||++-++------- $3c0
				 * addr = 10nn11 11rrrccc
				 *                 |||+++- col[4:2]
				 *                 +++---- row[4:2]
				 */
				attr = vram[name_table | 0x3c0 | (row & ~3) << 1 | col >> 2];
				/* attr = 33221100
				 *        ||||||++- palno for (col&3)=0or1, (row&3)=0or1
				 *        ||||++--- palno for (col&3)=2or3, (row&3)=0or1
				 *        ||++----- palno for (col&3)=0or1, (row&3)=2or3
				 *        ++------- palno for (col&3)=2or3, (row&3)=2or3
				 */
				palno = (attr >> ((col & 2) | (row & 2) << 1)) & 3;
				ppu4_draw_chr(x, y, code, palno);
				//
				x += 8;
				col++;
				if(col >= 32) {
					col -= 32;
					name_table ^= 0x400;
				}
			} while(--i);
			x -= 8 * (32 + 1);
			col += 32 - 1;
			if(col >= 32) {
				col -= 32;
				name_table ^= 0x400;
			}
		}
		y += 8;
		row++;
		if(row >= 30) {
			row -= 30;
			name_table ^= 0x800;
		}
	} while(y < scan_end); /* 描画領域を下方向へ完全に外れたら、抜けます。 */
}

void
ppu4_draw_sprite(int priority)
{
	PPU* p_ppu = &ppu;

	if(p_ppu->ppu_control1 & PPU_CONTROL1_SPRITE_SIZE) {
		ppu4_draw_sprite_8x16(priority);
	} else {
		ppu4_draw_sprite_8x8(priority);
	}
}

void
ppu4_draw_sprite_8x8(int priority)
{
	PPU* p_ppu = &ppu;
	unsigned int* sprram = (unsigned int*)(ppu_sprram + 4 * 63); /* 前面:0..63:背面 */
	//
	int i;
	int xpos;
	int ypos;
	int code;
	int attr;
	int palno;
	int code_base;

	code_base = (p_ppu->ppu_control1 & PPU_CONTROL1_SPRITE_PATTERN_TABLE_ADDRESS) << 5; /* $00->$000, $08->$100 */
	priority <<= 5; /* 0->$00, 1->$20 */

	i = 64;
	do {
		xpos = *sprram--; /* xxxxxxxx vhp000cc cccccccc yyyyyyyy */
		ypos = (unsigned char)xpos;
		xpos >>= 8;       /* -------- xxxxxxxx vhp000cc cccccccc */
		code = (unsigned char)xpos;
		xpos >>= 8;       /* -------- -------- xxxxxxxx vhp000cc */
		attr = (unsigned char)xpos;
		xpos >>= 8;       /* -------- -------- -------- xxxxxxxx */
		xpos = (unsigned char)xpos;
		if(!((attr ^ priority) & 0x20)) {
			ypos = (unsigned char)(ypos + 1/*仕様*/);
			if(ypos >= 240) {
				ypos = (char)ypos; /* 240..255 -> -16..-1 */
			}
			code |= code_base;
			palno = (attr & 3) | 4;
			switch(attr >> 6) {
			default: ppu4_draw_chr      (xpos, ypos, code, palno); break;
			case 1:  ppu4_draw_chr_revx (xpos, ypos, code, palno); break;
			case 2:  ppu4_draw_chr_revy (xpos, ypos, code, palno); break;
			case 3:  ppu4_draw_chr_revxy(xpos, ypos, code, palno); break;
			}
		}
	} while(--i);
}

void
ppu4_draw_sprite_8x16(int priority) /* ドルアーガの塔が使ってます */
{
	//PPU* p_ppu = &ppu;
	unsigned int* sprram = (unsigned int*)(ppu_sprram + 4 * 63); /* 前面:0..63:背面 */
	//
	int i;
	int xpos;
	int ypos;
	int code;
	int attr;
	int palno;

	priority <<= 5; /* 0->$00, 1->$20 */

	i = 64;
	do {
		xpos = *sprram--; /* xxxxxxxx vhp000cc cccccccc yyyyyyyy */
		ypos = (unsigned char)xpos;
		xpos >>= 8;       /* -------- xxxxxxxx vhp000cc cccccccc */
		code = (unsigned char)xpos;
		xpos >>= 8;       /* -------- -------- xxxxxxxx vhp000cc */
		attr = (unsigned char)xpos;
		xpos >>= 8;       /* -------- -------- -------- xxxxxxxx */
		xpos = (unsigned char)xpos;
		if(!((attr ^ priority) & 0x20)) {
			ypos += 1/*仕様*/;
			code = (code & ~1) | (code & 1) << 8;
			palno = (attr & 3) | 4;
			switch(attr >> 6) {
			default: ppu4_draw_chr      (xpos, ypos    , code + 0, palno);
			         ppu4_draw_chr      (xpos, ypos + 8, code + 1, palno); break;
			case 1:  ppu4_draw_chr_revx (xpos, ypos    , code + 0, palno);
			         ppu4_draw_chr_revx (xpos, ypos + 8, code + 1, palno); break;
			case 2:  ppu4_draw_chr_revy (xpos, ypos    , code + 1, palno);
			         ppu4_draw_chr_revy (xpos, ypos + 8, code + 0, palno); break;
			case 3:  ppu4_draw_chr_revxy(xpos, ypos    , code + 1, palno);
			         ppu4_draw_chr_revxy(xpos, ypos + 8, code + 0, palno); break;
			}
		}
	} while(--i);
}

/****************************************************************************
 *
 ****************************************************************************/

#define PPU_DRAW_CHR_IMPLEMENT
//
#ifndef PPU_ASM
#define PPU_REVX 0
#define PPU_REVY 0
void ppu4_draw_chr(int xpos, int ypos, int code, int palno)
#include "clippp4i.h"
#undef PPU_REVX
#undef PPU_REVY
#endif /*PPU_ASM*/
//
#define PPU_REVX 1
#define PPU_REVY 0
void ppu4_draw_chr_revx(int xpos, int ypos, int code, int palno)
#include "clippp4i.h"
#undef PPU_REVX
#undef PPU_REVY
//
#define PPU_REVX 0
#define PPU_REVY 1
void ppu4_draw_chr_revy(int xpos, int ypos, int code, int palno)
#include "clippp4i.h"
#undef PPU_REVX
#undef PPU_REVY
//
#define PPU_REVX 1
#define PPU_REVY 1
void ppu4_draw_chr_revxy(int xpos, int ypos, int code, int palno)
#include "clippp4i.h"
#undef PPU_REVX
#undef PPU_REVY
//
#undef PPU_DRAW_CHR_IMPLEMENT

/****************************************************************************
 *
 ****************************************************************************/

