/*	
 *	clipppu.c
 *
 *	P/ECE PPU (RICOH RP2C02) Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Sun Jan 30 21:25:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

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

PPU ppu;				/* PPU core */
unsigned char ppu_vram[0x4000];		/* VRAM (16KB) */
unsigned char ppu_sprram[0x100];	/* SPR-RAM (256B) */

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

#ifdef PPU_GRAYSCALE
/* 値の範囲は White:0..42:Black です。42の理由について、以下に説明します。 */
/* 256x240->128x88圧縮のために、2x3=6pixelの平均を取り、1pixelとします。   */
/* 6pixelの合計を取った後、6で割るのは遅いので、16で割ることにします。     */
/* そこで元のpixel値pを、(p*6)/16 の最大値が15になるよう設定しておきます。 */
/* すなわち、p=42です。((42*6)/16=15.75)                                   */
const unsigned char ppu_grayscale_table[64] = {
#define PPU_RGB(r,g,b) (42 - (int)((r*0.30 + g*0.59 + b*0.11) / 0xff * 42 + 0.5))
#else /*PPU_GRAYSCALE*/
const unsigned char ppu_color_table[64][3/*R,G,B*/] = {
#define PPU_RGB(r,g,b) {r,g,b}
#endif /*PPU_GRAYSCALE*/
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
};

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

void
ppu_reset(int mirroring)
{
	PPU* p_ppu = &ppu;

	/* まず、クリアします。 */
	memset(p_ppu, 0, sizeof(PPU));

	/* ミラーリングを格納します。 */
	p_ppu->flags = mirroring & PPU_FLAGS_MIRRORING;
}

int
ppu_read(int addr)
{
	PPU* p_ppu = &ppu;
	//
	int data;

	switch(addr & 7) {
	//case 0: /* $2000 PPU Control Register #1 (W) */
	//case 1: /* $2001 PPU Control Register #2 (W) */
	case 2: /* $2002 PPU Status Register (R) */
		data = p_ppu->ppu_status;
		break;
	//case 3: /* $2003 SPR-RAM Address Register (W) */
	//case 4: /* $2004 SPR-RAM I/O Register (W) */
	//case 5: /* $2005 VRAM Address Register #1 (W2) */
	//case 6: /* $2006 VRAM Address Register #2 (W2) */
	case 7: /* $2007 VRAM I/O Register (RW) */
		data = p_ppu->vram_buffer; /* VRAM読み出しは1回分づつ遅延します!!(仕様) */
		p_ppu->vram_buffer = ppu_vram[p_ppu->vram_address2 & 0x3fff];
		p_ppu->vram_address2 += p_ppu->ppu_control1 & PPU_CONTROL1_PPU_ADDRESS_INCREMENT ? 32 : 1;
		break;
	default:
		data = 0;
		break;
	}

	return data;
}

void
ppu_write(int addr, int data)
{
	PPU* p_ppu = &ppu;

	switch(addr & 7) {
	case 0: /* $2000 PPU Control Register #1 (W) */
		p_ppu->ppu_control1 = data;
		break;
	case 1: /* $2001 PPU Control Register #2 (W) */
		p_ppu->ppu_control2 = data;
		break;
	//case 2: /* $2002 PPU Status Register (R) */
	case 3: /* $2003 SPR-RAM Address Register (W) */
		p_ppu->sprram_address = data;
		break;
	case 4: /* $2004 SPR-RAM I/O Register (W) */
		ppu_sprram[p_ppu->sprram_address++ & 0xff] = data;
		break;
	case 5: /* $2005 VRAM Address Register #1 (W2) */
		p_ppu->vram_address1 = p_ppu->vram_address1 << 8 | data;
		break;
	case 6: /* $2006 VRAM Address Register #2 (W2) */
		p_ppu->vram_address2 = p_ppu->vram_address2 << 8 | data;
		break;
	case 7: /* $2007 VRAM I/O Register (RW) */
		ppu_vram_write(p_ppu->vram_address2 & 0x3fff, data);
		p_ppu->vram_address2 += p_ppu->ppu_control1 & PPU_CONTROL1_PPU_ADDRESS_INCREMENT ? 32 : 1;
		break;
	}
}

int
ppu_update(unsigned char vbuff[/*256*240*/])
{
	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. 前回の走査区間の描画 +-+- ppu_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 = ppu_grayscale_table[v];
#endif /*PPU_GRAYSCALE*/
			*dst++ = v;
		} while(--i);

		/* 背景を塗りつぶします。 */
		memset(&vbuff[scan_start * 256], p_ppu->clut[0], (scan_end - scan_start) * 256);

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

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

		/* 前面スプライトを描きます。 */
		if(p_ppu->ppu_control2 & PPU_CONTROL2_SPRITE_VISIBILITY) {
			ppu_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
ppu_vram_write(int addr, int data)
{
	PPU* p_ppu = &ppu;
	unsigned char* vram = ppu_vram;

	if(addr < 0x2000) { /* Pattern Table */
		/* Mapper 0や3などでは、書き換え不可のCHR-ROMです。
		 * Mapper 2や7などでは、書き換え可能なCHR-RAMです。
		 * CHR-ROMへ書き込もうとするプログラムは無いと仮定し、
		 * 常に書き換え可能なCHR-RAMとして扱うことにしました。
		 */
		vram[addr] = data;

	} else if(addr < 0x3000) { /* Name Table, Attribute Table */
		if(p_ppu->flags & PPU_FLAGS_MIRRORING) {
			/* Vertical mirroring */
			vram[addr & ~0x800] = data; /* $2000-$23ff or $2400-$27ff */
			vram[addr |  0x800] = data; /* $2800-$2bff or $2c00-$2fff */
		} else {
			/* Horizontal mirroring */
			vram[addr & ~0x400] = data; /* $2000-$23ff or $2800-$2bff */
			vram[addr |  0x400] = data; /* $2400-$27ff or $2c00-$2fff */
		}

	} else if(addr < 0x3f00) { /* Mirror of $2000-2EFF */
		/* 未対応 */

	} else if(addr < 0x3f10) { /* Image Palette */
		vram[addr] = data;
		if(!(addr & 3)) {  /* Mirror $3F00->$3F10,$3F04->$3F14,$3F08->$3F18,$3F0C->$3F1C */
			vram[addr + 0x10] = data;
		}

	} else if(addr < 0x3f20) { /* Sprite Palette */
		vram[addr] = data;
		if(!(addr & 3)) {  /* Mirror $3F10->$3F00,$3F14->$3F04,$3F18->$3F08,$3F1C->$3F0C */
			vram[addr - 0x10] = data;
		}

	} else { /* Mirror of $3F00-3F1F */
		/* 未対応 */
	}
}

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

void
ppu_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;
				ppu_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
ppu_draw_sprite(int priority)
{
	PPU* p_ppu = &ppu;

	if(p_ppu->ppu_control1 & PPU_CONTROL1_SPRITE_SIZE) {
		ppu_draw_sprite_8x16(priority);
	} else {
		ppu_draw_sprite_8x8(priority);
	}
}

void
ppu_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: ppu_draw_chr      (xpos, ypos, code, palno); break;
			case 1:  ppu_draw_chr_revx (xpos, ypos, code, palno); break;
			case 2:  ppu_draw_chr_revy (xpos, ypos, code, palno); break;
			case 3:  ppu_draw_chr_revxy(xpos, ypos, code, palno); break;
			}
		}
	} while(--i);
}

void
ppu_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: ppu_draw_chr      (xpos, ypos    , code + 0, palno);
			         ppu_draw_chr      (xpos, ypos + 8, code + 1, palno); break;
			case 1:  ppu_draw_chr_revx (xpos, ypos    , code + 0, palno);
			         ppu_draw_chr_revx (xpos, ypos + 8, code + 1, palno); break;
			case 2:  ppu_draw_chr_revy (xpos, ypos    , code + 1, palno);
			         ppu_draw_chr_revy (xpos, ypos + 8, code + 0, palno); break;
			case 3:  ppu_draw_chr_revxy(xpos, ypos    , code + 1, palno);
			         ppu_draw_chr_revxy(xpos, ypos + 8, code + 0, palno); break;
			}
		}
	} while(--i);
}

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

/* * 描画速度は、垂直、水平反転無しのキャラ描画速度だけで九割方決まるはずです。
 *   スプライトよりもBGの描画面積がずっと大きく、BGには反転機能が無いからです。
 * * 実行速度と高速RAM容量節約との兼ね合いで、反転無しのキャラ描画だけを高速RAMに配置することにしました。
 *   反転有りのキャラ描画(三種類)は、SRAMに配置し、アセンブラ化もしていません。(2005/02/04現在)
 */

#define PPU_DRAW_CHR_IMPLEMENT
//
#ifndef PPU_ASM
#define PPU_REVX 0
#define PPU_REVY 0
void ppu_draw_chr(int xpos, int ypos, int code, int palno)
#include "clipppui.h"
#undef PPU_REVX
#undef PPU_REVY
#endif /*PPU_ASM*/
//
#define PPU_REVX 1
#define PPU_REVY 0
void ppu_draw_chr_revx(int xpos, int ypos, int code, int palno)
#include "clipppui.h"
#undef PPU_REVX
#undef PPU_REVY
//
#define PPU_REVX 0
#define PPU_REVY 1
void ppu_draw_chr_revy(int xpos, int ypos, int code, int palno)
#include "clipppui.h"
#undef PPU_REVX
#undef PPU_REVY
//
#define PPU_REVX 1
#define PPU_REVY 1
void ppu_draw_chr_revxy(int xpos, int ypos, int code, int palno)
#include "clipppui.h"
#undef PPU_REVX
#undef PPU_REVY
//
#undef PPU_DRAW_CHR_IMPLEMENT

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

