Consulta sobre emulador de nintendo

El espacio para revivir clásicos (y no tanto) mediante emuladores. Un lugar dedicado 100% a la emulación.
gzamoranelson
Usuario nuevo
Usuario nuevo
Mensajes: 1
Registrado: 02/03/07 22:09

Consulta sobre emulador de nintendo

Mensaje por gzamoranelson » 03/03/07 15:53

Primero que todo decirles que soy nuevo en este foro :D

Bueno aca va mi pregunta sobre la NES.

Tengo implementada una emulacion del procesador 6502 escrita en lenguaje C, cada instruccion realiza bien las operaciones, aumenta adecuadamente el PC dependiendo del operando y del modo de direccionamiento del opcode, tambien tengo una variable CICLOS, que va disminuyendo despues de cada instruccion ejecutada y cual variable puede ser seteada desde todo el emulador, para asi sincronizar el cpu con el ppu, papu, etc. ademas con las siguientes funciones para su utilizacion.

Código: Seleccionar todo

IniciarCPU(); // funcion que resetea registros A, X e Y, entre otras cosas, setea PC desde vector reset, etc
EjecutarCPU(); // funcion que va ejecutando las instrucciones indicadas por el PC
tambien tengo unas funciones para las interrupciones, nmi y irq, que apilan el PC y el registro de estado P
Tambien tengo implementado un pequeño render de video el cual luego de haber sido seteados los registros del ppu (0x2000 y 0x2001) copia desde la chr_ram del rom a la VRAM, ya sea a 0x0 o a 0x1000 con las siguientes funciones:

Código: Seleccionar todo

IniciarPPU(); // inicia el modo en SDL, (esa biblioteca estoy usando), y copia lo anterior explicado
EjecutarPPU(); // Tengo una variable global llamada actual_scanline, la cual dentro de esta funcion si varia desde 0 a 261, va dibujando los scanlines desde los tiles y los sprites,
Bueno eso fue una introduccion al asunto.

El problema esta en como sincronizo estos dos amiguitos :D , resulta que si cada vbalnk se ejecuta unas 60 veces por segundo, segun mis calculos y a la velocidad a la que corre el 6502:

velocidad en hz / 60 = ciclos entre cada vblank

El asunto es que no entiendo bien como funciona este proceso entre vblank e interrupciones nmi, tambpoco entiendo muy bien lo que va ocurriendo entre cada scanline, que flags se van seteando, cuales no, alguien seria tan amable de explicarme el proceso desde que el rayito de has de luz va a posicionarse a dibujar otra frama, que flags de P se van seteando y desactivando, si antes hay que hacer una llamada a la interrupcion nmi o no, o si hay que llamarla entre cada scanline, por que la info que encontre no la entendi muy bien :? , se agradece de antemano :D

Avatar de Usuario
Faker
Check Point!
Check Point!
Mensajes: 10265
Registrado: 20/09/06 22:11
Frase: AGUANTE MARIO10
Ubicación: ONMF!

Mensaje por Faker » 03/03/07 15:58

No tengo la mas minima idea de lo que estas hablando.

Pero te doy toda la buena onda y bienvenida que te mereces por interezarte en este foro para publicar tu problema.
Esperamos verte postear seguido y si sos un gamer, vas a estar.

Viva la NES!!!

PD: Aca hay mucha gente que sabe de lo que estas hablando , me apena que yo no pueda darte una mano. :(

Saludos!

Faker

Avatar de Usuario
Shinnok
Admin
Admin
Mensajes: 18761
Registrado: 20/09/06 20:54
Frase: Nothing, Nothing Can Prepare You !
Ubicación: Outworld
Contactar:

Mensaje por Shinnok » 03/03/07 17:02

Hola gzamoranelson !! Aprovecho para darte la bienvenida al foro y si querés podés presentarte en sociedad acá viewforum.php?f=13
Sobre tu duda del emulador yo ni idea, pero seguramente Switchstance, Hernán o algún otro puede ayudarte, saludos y suerte ! :D
Imagen

Avatar de Usuario
Switchstance
Guardián del Aguila
Guardián del Aguila
Mensajes: 4549
Registrado: 02/10/06 2:05
Frase: Donde está Kurt1977???
Ubicación: Rio Gallegos
Contactar:

Mensaje por Switchstance » 04/03/07 14:08

Bueno, dejá que me ponga mis lentes NERDS :

Primero que nada, probá chequeando el Wiki de Brad Taylor con info del PPU :

http://nesdevwiki.ath.cx/wiki/index.php/Main_Page

Cada tanto está siendo actualizada, y sobre el problema del Sync esto es bastante común y se encuentra la solución por ahi.
La segunda respuesta que te sugiero es que chequees el código fuente del NESTOPIA justo en la parte que estás trabado, ahi verias como solucionó el problema el autor de ese emulador.

Código: Seleccionar todo

PPU::PPU(CPU& c)
:
CiRam       (n4k),
GarbageLine (new IO::GFX::PIXEL[MARGIN + IO::GFX::WIDTH]),
cpu         (c),
pal         (FALSE)
{}

////////////////////////////////////////////////////////////////////////////////////////
// destructor
////////////////////////////////////////////////////////////////////////////////////////

PPU::~PPU()
{
	delete [] GarbageLine;
}

////////////////////////////////////////////////////////////////////////////////////////
// reset ppu
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::Reset(const BOOL hard)
{
	LogOutput("PPU: reset");

	for (UINT i=0; i < 4; ++i)
		logged[i] = false;

	if (hard)
	{
		CiRam.Clear();

		PDXMemZero( cRam,   n8k );
		PDXMemZero( SpRam,  256 );
		PDXMemZero( PalRam,	 32 );
	}

	cycles.SetMode(pal);
	cycles.WarmUp = hard ? CYCLES::WARM_UP : CYCLES::READY;

	ctrl0             = 0;
	ctrl1             = 0;
	status            = 0;	
	enabled           = FALSE;
	vRamAddress       = 0x2000;
	vRamLatch         = 0x2000;
	OamAddress        = 0x0000;
	OamLatch          = 0x00;
	DmaLatch          = 0x20;
	latch             = 0;
	ReadLatch         = 0;
	xFine             = 0;
	FlipFlop          = 0;
	SpIndex           = 0;
	MaxSprites        = 8;
	EvenFrame         = 1^1;
	ScanLine          = SCANLINE_VBLANK;
	SpTmpBufferSize   = 0;
	SpBufferSize      = 0;
	screen            = NULL;
	process           = processes;
	output.clipping   = 0xFF;
	output.monochrome = 0xFF;
	output.pixels     = NULL;
	output.ClipOffset = NULL;

	UpdateTmpVariables();

	// Default to horizontal mirroring

	CiRam.SwapBanks<n1k,0x0000>(0);
	CiRam.SwapBanks<n1k,0x0400>(0);
	CiRam.SwapBanks<n1k,0x0800>(1);
	CiRam.SwapBanks<n1k,0x0C00>(1);

	vRam.SetPort( 0x0000, 0x1FFF, this, Peek_vRam_00xx, Poke_vRam_00xx );
	vRam.SetPort( 0x2000, 0x23FF, this, Peek_vRam_20xx, Poke_vRam_20xx );
	vRam.SetPort( 0x2400, 0x27FF, this, Peek_vRam_24xx, Poke_vRam_24xx );
	vRam.SetPort( 0x2800, 0x2BFF, this, Peek_vRam_28xx, Poke_vRam_28xx );
	vRam.SetPort( 0x2C00, 0x2FFF, this, Peek_vRam_2Cxx, Poke_vRam_2Cxx );
	vRam.SetPort( 0x3000, 0x33FF, this, Peek_vRam_20xx, Poke_vRam_20xx );
	vRam.SetPort( 0x3400, 0x37FF, this, Peek_vRam_24xx, Poke_vRam_24xx );
	vRam.SetPort( 0x3800, 0x3BFF, this, Peek_vRam_28xx, Poke_vRam_28xx );
	vRam.SetPort( 0x3C00, 0x3FFF, this, Peek_vRam_2Cxx, Poke_vRam_2Cxx );

	for (UINT i=0x2000; i < 0x4000; i += 0x8)
	{
		cpu.SetPort( i + 0x0, this, Peek_2xxx, Poke_2000 );
		cpu.SetPort( i + 0x1, this, Peek_2xxx, Poke_2001 );
		cpu.SetPort( i + 0x2, this, Peek_2002, Poke_2xxx );
		cpu.SetPort( i + 0x3, this, Peek_2xxx, Poke_2003 );
		cpu.SetPort( i + 0x4, this, Peek_2004, Poke_2004 );
		cpu.SetPort( i + 0x5, this, Peek_2xxx, Poke_2005 );
		cpu.SetPort( i + 0x6, this, Peek_2xxx, Poke_2006 );
		cpu.SetPort( i + 0x7, this, Peek_2007, Poke_2007 );
	}

	cpu.SetPort( 0x4014, this, Peek_4014, Poke_4014 );

	PDXMemZero( SpBuffer,    64 );
	PDXMemZero( SpTmpBuffer, 64 );
	PDXMemZero( BgBuffer        );
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::UpdateTmpVariables()
{
	enabled          = ctrl1 & (CTRL1_BG_ENABLED | CTRL1_SP_ENABLED);
	AddressIncrease  = (ctrl0 & CTRL0_INC32) ? 32 : 1;
	SpHeight         = (ctrl0 & CTRL0_SP8X16) ? 16 : 8;
	BgPatternAddress = (ctrl0 & CTRL0_BG_OFFSET) ? 0x1000 : 0x0000;
	SpPatternAddress = (ctrl0 & CTRL0_SP_OFFSET) ? 0x1000 : 0x0000;
	emphasis         = ((ctrl1 & CTRL1_BG_COLOR) >> CTRL1_BG_COLOR_SHIFT) * 64;
	monochrome       = (ctrl1 & CTRL1_MONOCHROME) ? 0xF0 : 0xFF;
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::CYCLES::Reset(const BOOL pal,const BOOL EvenFrame)
{
	SetMode( pal, EvenFrame );

	if (WarmUp == READY)
	{
		count = 0;
	}
	else
	{
		++WarmUp;
		count = frame;
	}
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::CYCLES::SetMode(const BOOL pal,const BOOL EvenFrame)
{
	if (pal)
	{
		fetch = NES_PPU_MCC_FETCH_PAL;
		pixel = NES_PPU_MCC_PIXEL_PAL;
		frame = EvenFrame ? NES_PPU_MCC_FRAME_0_PAL : NES_PPU_MCC_FRAME_1_PAL;
		vint  = NES_PPU_MCC_VINT_PAL;
	}
	else
	{
		fetch = NES_PPU_MCC_FETCH_NTSC;
		pixel = NES_PPU_MCC_PIXEL_NTSC;
		frame = EvenFrame ? NES_PPU_MCC_FRAME_0_NTSC : NES_PPU_MCC_FRAME_1_NTSC;
		vint  = NES_PPU_MCC_VINT_NTSC;
	}
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,vRam_00xx) { cRam[address & 0x1FFF] = data;   }
NES_POKE(PPU,vRam_20xx) { CiRam(0,address & 0x3FF) = data; }
NES_POKE(PPU,vRam_24xx) { CiRam(1,address & 0x3FF) = data; }
NES_POKE(PPU,vRam_28xx) { CiRam(2,address & 0x3FF) = data; }
NES_POKE(PPU,vRam_2Cxx) { CiRam(3,address & 0x3FF) = data; }

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

NES_PEEK(PPU,vRam_00xx) { return cRam[address & 0x1FFF];  }
NES_PEEK(PPU,vRam_20xx) { return CiRam(0,address & 0x3FF); } 
NES_PEEK(PPU,vRam_24xx) { return CiRam(1,address & 0x3FF); } 
NES_PEEK(PPU,vRam_28xx) { return CiRam(2,address & 0x3FF); } 
NES_PEEK(PPU,vRam_2Cxx) { return CiRam(3,address & 0x3FF); } 

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::SetMirroring(const MIRRORING type)
{
	PDX_ASSERT(UINT(type) < 7);

	PDX_COMPILE_ASSERT
	(
	    MIRROR_HORIZONTAL == 0 &&
		MIRROR_VERTICAL   == 1 &&
		MIRROR_FOURSCREEN == 2 &&
		MIRROR_ZERO       == 3 &&
		MIRROR_ONE        == 4 &&
		MIRROR_TWO        == 5 &&
		MIRROR_THREE      == 6
	);

	Update();

	static const UCHAR select[7][4] =
	{
		{0,0,1,1}, // horizontal
		{0,1,0,1}, // vertical
		{0,1,2,3}, // four-screen
		{0,0,0,0}, // banks #1
		{1,1,1,1}, // banks #2
		{2,2,2,2}, // banks	#3
		{3,3,3,3}  // banks	#4
	};

	CiRam.SwapBanks<n1k,0x0000>( select[type][0] );
	CiRam.SwapBanks<n1k,0x0400>( select[type][1] );
	CiRam.SwapBanks<n1k,0x0800>( select[type][2] );
	CiRam.SwapBanks<n1k,0x0C00>( select[type][3] );
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::SetMode(const MODE mode)
{
	pal = (mode == MODE_PAL);
	cycles.SetMode(pal,EvenFrame);
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::SetContext(const IO::GFX::CONTEXT& context)
{
	MaxSprites = context.InfiniteSprites ? 64 : 8;
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::GetContext(IO::GFX::CONTEXT& context) const
{
	context.InfiniteSprites = (MaxSprites == 64);
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::Update()
{
	const ULONG target = PDX_MIN
	(
    	cycles.frame,
		cpu.GetCycles<CPU::CYCLE_MASTER>()
	);

	while (cycles.count < target)
		(this->**process++)();
}

////////////////////////////////////////////////////////////////////////////////////////
// write to control register 0
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,2000)
{
	Update();

	ctrl0 = latch = data;

	AddressIncrease  = (data & CTRL0_INC32) ? 32 : 1;
	SpHeight         = (data & CTRL0_SP8X16) ? 16 : 8;
	BgPatternAddress = (data & CTRL0_BG_OFFSET) ? 0x1000 : 0x0000;
	SpPatternAddress = (data & CTRL0_SP_OFFSET) ? 0x1000 : 0x0000;
	
	vRamLatch &= ~VRAM_NAME;
	vRamLatch |= (data & CTRL0_NAME_OFFSET) << 10;

	const BOOL retrigger = !cpu.IsLine(CPU::NMI) && (data & CTRL0_NMI) && (status & STATUS_VBLANK);

	cpu.SetLine(CPU::NMI,data & CTRL0_NMI);

	if (retrigger)
		cpu.DoNMI();
}

////////////////////////////////////////////////////////////////////////////////////////
// write to control register 1
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,2001)
{
	Update();

	enabled    = data & (CTRL1_BG_ENABLED | CTRL1_SP_ENABLED);
	emphasis   = ((data & CTRL1_BG_COLOR) >> CTRL1_BG_COLOR_SHIFT) * IO::GFX::PALETTE_CHUNK;
	monochrome = (data & CTRL1_MONOCHROME) ? 0xF0 : 0xFF;

	if ((data & CTRL1_BG_ENABLED) < (ctrl1 & CTRL1_BG_ENABLED))
	{
		BgBuffer.address    = 0x0000;
		BgBuffer.name       = 0x00;
		BgBuffer.attribute  = 0x00;
		BgBuffer.pattern[0] = 0x00;
		BgBuffer.pattern[1] = 0x00;
		
		PDXMemZero( BgBuffer.pixels, 16 );
	}

	ctrl1 = latch = data;
}

////////////////////////////////////////////////////////////////////////////////////////
// read the status register
////////////////////////////////////////////////////////////////////////////////////////

NES_PEEK(PPU,2002)
{
	Update();

	FlipFlop = 0;

	latch = (latch & STATUS_LATCH) | status;
	status &= ~STATUS_VBLANK;

	return latch;
}

////////////////////////////////////////////////////////////////////////////////////////
// write to the sprite RAM address counter
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,2003)
{
	Update();

	OamAddress = latch = data;
	OamLatch = latch & 0x7;
}

////////////////////////////////////////////////////////////////////////////////////////
// write a byte to the current sprite RAM address
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,2004)
{
	Update();

	latch = data;

	if (OamLatch >= 0x8)
	{
		if (OamAddress >= 0x8)
			SpRam[OamAddress] = data;
	}
	else
	{
		SpRam[OamLatch] = data;
	}

	OamLatch = (OamLatch + 1) & 0xFF;
	OamAddress = (OamAddress + 1) & 0xFF;
}

////////////////////////////////////////////////////////////////////////////////////////
// read from sprite RAM
////////////////////////////////////////////////////////////////////////////////////////

NES_PEEK(PPU,2004)
{
	Update();

	latch = SpRam[OamAddress];
	OamAddress = (OamAddress + 1) & 0xFF;

	return latch;
}

////////////////////////////////////////////////////////////////////////////////////////
// write a compressed byte to the scroll register
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,2005)
{
	Update();

	latch = data;

	if (FlipFlop ^= 1)
	{
       	// First write. Store the x tile and x fine. 
		
		vRamLatch &= ~VRAM_X_TILE;
		vRamLatch |= latch >> 3;
		xFine = latch & b00000111;
	}
	else
	{
		// Second write. Store the y tile and y fine.

		vRamLatch &= ~(VRAM_Y_TILE|VRAM_Y_FINE);
		vRamLatch |= (latch & b11111000) << 2;
		vRamLatch |= (latch & b00000111) << 12;
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// write to the register controling the VRAM address
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,2006)
{
	Update();

	latch = data;

	if (FlipFlop ^= 1)
	{
		// First write. Store the high byte 6 bits 
		// and clear out the last two.

		vRamLatch &= ~VRAM_HIGH;
		vRamLatch |= (latch & b00111111) << 8;
	}
	else
	{
		// Second write. Store the low byte 8 bits.
		
		vRamLatch &= ~VRAM_LOW;
		vRamLatch |= latch;
		vRamAddress = vRamLatch;
		vRam[vRamAddress & 0x3FFF];
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// write a byte to VRAM
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,2007)
{
	Update();

	latch = data;

	UINT offset = vRamAddress;
	vRamAddress += AddressIncrease;

	if (offset >= 0x3F00)
	{
		if (offset < 0x4000)
		{			  
			WritePalRam( offset );
			return;
		}

		offset &= 0x3FFF;
	}

	vRam.Poke( offset, data );
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::WritePalRam(const UINT address)
{
	if (!(address & 0xF))
	{
		PalRam[0x00] = 
		PalRam[0x04] = 
		PalRam[0x08] = 
		PalRam[0x0C] =			
		PalRam[0x10] = 
		PalRam[0x14] = 
		PalRam[0x18] = 
		PalRam[0x1C] = latch & b111111;
	}
	else if (address & 0x3)
	{
		PalRam[address & 0x1F] = latch & b111111;
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// read a byte from VRAM
////////////////////////////////////////////////////////////////////////////////////////

NES_PEEK(PPU,2007)
{
	Update();

	UINT offset = vRamAddress;
	vRamAddress += AddressIncrease;

	// first read is always a throw-away read (except palette RAM)

	if (offset >= 0x3F00)
	{
		if (offset < 0x4000)
			return latch = PalRam[offset & 0x1F];

		offset &= 0x3FFF;
	}

	latch = ReadLatch;
	ReadLatch = vRam[offset];

	return latch;
}

////////////////////////////////////////////////////////////////////////////////////////
// put any crap that gets written on the bus
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,2xxx)
{
	Update();
	Log("PPU: useless write to 0x2xxx",0);
	latch = data;
}

////////////////////////////////////////////////////////////////////////////////////////
// read what's left on the bus
////////////////////////////////////////////////////////////////////////////////////////

NES_PEEK(PPU,2xxx)
{
	Update();
	Log("PPU: useless read from 0x2xxx",1);
	return latch;
}

////////////////////////////////////////////////////////////////////////////////////////
// sprite DMA
////////////////////////////////////////////////////////////////////////////////////////

NES_POKE(PPU,4014)
{
	Update();

	DmaLatch = data;

	if (ScanLine == SCANLINE_VBLANK || enabled)
	{
		const UINT offset = data << 8;
		const UINT length = offset + 256;

		if (offset < 0x2000)
		{
			const U8* const PDX_RESTRICT ram = cpu.Ram();			
			UINT i = offset;

			for (; i < length; ++i, OamLatch = (OamLatch+1) & 0xFF, OamAddress = (OamAddress+1) & 0xFF)
			{
				if (OamLatch >= 0x8)
				{
					if (OamAddress >= 0x8)
						SpRam[OamAddress] = ram[i & 0x7FF];
				}
				else
				{
					SpRam[OamLatch] = ram[i & 0x7FF];
				}
			}

			latch = ram[(i-1) & 0x7FF];
		}
		else
		{
			for (UINT i=offset; i < length; ++i, OamLatch = (OamLatch+1) & 0xFF, OamAddress = (OamAddress+1) & 0xFF)
			{
				latch = cpu.Peek(i);

				if (OamLatch >= 0x8)
				{
					if (OamAddress >= 0x8)
						SpRam[OamAddress] = latch;
				}
				else
				{
					SpRam[OamLatch] = latch;
				}
			}
		}

		cpu.AdvanceCycles( pal ? NES_PPU_MCC_SPRITE_DMA_PAL : NES_PPU_MCC_SPRITE_DMA_NTSC );
	}
	else
	{
		latch = data;
		Log("PPU: sprite DMA transfer denied",2);
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// return the current sprite DMA address
////////////////////////////////////////////////////////////////////////////////////////

NES_PEEK(PPU,4014)
{
	Update();
	Log("PPU: read from 0x4014",3);
	return DmaLatch;
}

////////////////////////////////////////////////////////////////////////////////////////
// start vertical blanking
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::BeginVBlank()
{
	OamLatch = 0x00;
	status |= STATUS_VBLANK;
	ScanLine = SCANLINE_VBLANK;
	cycles.count = cycles.vint;
}

////////////////////////////////////////////////////////////////////////////////////////
// end vertical blanking
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::EndVBlank()
{
	status &= ~(STATUS_VBLANK|STATUS_SP_ZERO_HIT|STATUS_SP_OVERFLOW);

	SpTmpBufferSize = 0;
	SpBufferSize = 0;
}

////////////////////////////////////////////////////////////////////////////////////////
// dummy name table byte fetch
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchName()
{
	cycles.count += cycles.fetch;

	if (enabled)
		vRam[0x2000 + (vRamAddress & 0x0FFF)];
}

////////////////////////////////////////////////////////////////////////////////////////
// dummy attribute table fetch
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchAttribute()
{
	cycles.count += cycles.fetch;

	if (enabled)
	{
		const UINT address = 
		(
	    	0x23C0 + 
			((vRamAddress & 0x0C00) >> 0) + 
			((vRamAddress & 0x0380) >> 4) + 
			((vRamAddress & 0x001F) >> 2)
		);

		vRam[address];
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// dummy pattern table byte fetch #1
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchPattern0()
{
	cycles.count += cycles.fetch;

	if (enabled)
		vRam[BgPatternAddress];
}

////////////////////////////////////////////////////////////////////////////////////////
// dummy pattern table byte fetch #2
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchPattern1()
{
	cycles.count += cycles.fetch;

	if (enabled)
		vRam[BgPatternAddress+8];
}

////////////////////////////////////////////////////////////////////////////////////////
// name table byte fetch
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::PreFetchBgName()
{
	cycles.count += cycles.fetch;

	if (enabled)
		BgBuffer.name = vRam[0x2000 + (vRamAddress & 0x0FFF)];
}

////////////////////////////////////////////////////////////////////////////////////////
// name table byte fetch
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchBgName()
{
	if (enabled)
	{
		if ((vRamAddress & VRAM_X_TILE) == VRAM_X_TILE)
		{
			vRamAddress ^= VRAM_NAME_LOW;
			vRamAddress &= ~VRAM_X_TILE;
		}
		else
		{
			++vRamAddress;
		}

		BgBuffer.name = vRam[0x2000 + (vRamAddress & 0x0FFF)];
	}

	BgBuffer.index &= 0xF;

	if (ctrl1 & CTRL1_BG_ENABLED)
	{
		U8* const PDX_RESTRICT pixels = BgBuffer.pixels + (BgBuffer.index ^ 8);

		const UINT pattern =
		(
			((BgBuffer.pattern[0] & b01010101) << 0) |
			((BgBuffer.pattern[0] & b10101010) << 7) |
			((BgBuffer.pattern[1] & b01010101) << 1) |
			((BgBuffer.pattern[1] & b10101010) << 8) 
		);
				
		pixels[0] = ( (pattern >> 0xE) & 0x3 ) + BgBuffer.attribute;
		pixels[1] = ( (pattern >> 0x6) & 0x3 ) + BgBuffer.attribute;
		pixels[2] = ( (pattern >> 0xC) & 0x3 ) + BgBuffer.attribute;
		pixels[3] = ( (pattern >> 0x4) & 0x3 ) + BgBuffer.attribute;
		pixels[4] = ( (pattern >> 0xA) & 0x3 ) + BgBuffer.attribute;
		pixels[5] = ( (pattern >> 0x2) & 0x3 ) + BgBuffer.attribute;
		pixels[6] = ( (pattern >> 0x8) & 0x3 ) + BgBuffer.attribute;
		pixels[7] = ( (pattern >> 0x0) & 0x3 ) + BgBuffer.attribute;

		if (output.pixels >= output.ClipOffset || (ctrl1 & CTRL1_BG_NO_CLIPPING))
		{
			output.clipping = 0xFF;
			output.monochrome = monochrome;
		}
		else
		{
			output.clipping = 0x00;
			output.monochrome = 0xFF;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// attribute table fetch with cycle adjustment
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::PreFetchBgAttribute()
{
	cycles.count += cycles.fetch;
	FetchBgAttribute();
}

////////////////////////////////////////////////////////////////////////////////////////
// attribute table fetch
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchBgAttribute()
{
	if (enabled)
	{
		const UINT xTile = (vRamAddress & VRAM_X_TILE) >> 0;
		const UINT yTile = (vRamAddress & VRAM_Y_TILE) >> 5;

		const UINT address = 0x23C0 + (vRamAddress & 0x0C00) + ((yTile & 0x1C) << 1) + (xTile >> 2);
		const UINT shifter = (xTile & 0x2) + ((yTile & 0x2) << 1);

		BgBuffer.attribute = ((vRam[address] >> shifter) & 0x3) << 2;
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// pattern table byte fetch #1 with cycle adjustment
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::PreFetchBgPattern0()
{
	cycles.count += cycles.fetch;
	FetchBgPattern0();
}

////////////////////////////////////////////////////////////////////////////////////////
// pattern table byte fetch #1
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchBgPattern0()
{
	if (enabled)
	{
		BgBuffer.address = 0x1FFF &
		(
	    	BgPatternAddress + 
			(BgBuffer.name << 4) + 
			((vRamAddress & VRAM_Y_FINE) >> 12)
		);
		
		BgBuffer.pattern[0] = vRam[BgBuffer.address];
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// pattern table byte fetch #2 with cycle adjustment
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::PreFetchBgPattern1()
{
	cycles.count += cycles.fetch;
	FetchBgPattern1();
}

////////////////////////////////////////////////////////////////////////////////////////
// pattern table byte fetch #2
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchBgPattern1()
{
	if (enabled)
		BgBuffer.pattern[1] = vRam[BgBuffer.address + 8];
}

////////////////////////////////////////////////////////////////////////////////////////
// render two dummy pixels
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::RenderDummyPixels()
{
	cycles.count += cycles.pixel + cycles.pixel;
	BgBuffer.index += 2;
}

////////////////////////////////////////////////////////////////////////////////////////
// render one pixel
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::RenderPixel()
{
	cycles.count += cycles.pixel;

	const UINT BgPixel = BgBuffer.pixels[(BgBuffer.index++ + xFine) & 0xF] & output.clipping;
	UINT pixel = BgPalRam[BgPixel];

	const SP* const PDX_RESTRICT end = SpBuffer + SpBufferSize;

	for (SP* PDX_RESTRICT sp = SpBuffer; sp != end; ++sp)
	{
		if (--sp->x <= 0 && sp->x >= -7)
		{
			const UINT SpPixel = sp->pixels[-sp->x];

			if (SpPixel)
			{
				// first two bits of sp->zero and sp->behind are masked if true
				// (for avoiding additional branches with lazy evaluation)

				if (sp->zero & BgPixel) 
					status |= STATUS_SP_ZERO_HIT;

				if (!(sp->behind & BgPixel))
					pixel = sp->palette[SpPixel];

				while (++sp != end)
					--sp->x;

				break;
			}
		}
	}

	*output.pixels++ = emphasis + (output.monochrome & pixel);
}

////////////////////////////////////////////////////////////////////////////////////////
// sprite evaluation for the next line
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::EvaluateSp()
{
	if (ctrl1 & CTRL1_SP_ENABLED)
	{
		const U8* const PDX_RESTRICT sprite = SpRam + SpIndex;

		LONG y = ScanLine - sprite[SP_Y];

		if (y >= 0 && y < SpHeight && sprite[SP_Y] < 240)
		{
			if (SpTmpBufferSize == MaxSprites)
			{
				status |= STATUS_SP_OVERFLOW;
			}
			else
			{
				SPTMP& SpTmp = SpTmpBuffer[SpTmpBufferSize++];

				if (SpHeight == 8)
				{
					SpTmp.address = 
					(
						(sprite[SP_TILE] << 4) + 
						((sprite[SP_ATTRIBUTE] & SP_Y_FLIP) ? y^7 : y)
					);
				}
				else
				{
					if (y > 7)
						y += 8;

					SpTmp.address = 
					(
						((sprite[SP_TILE] & 0x01) << 0xC) + 
						((sprite[SP_TILE] & 0xFE) << 0x4) + 
						((sprite[SP_ATTRIBUTE] & SP_Y_FLIP) ? y^23 : y)
					);
				}

				SpTmp.x = sprite[SP_X] + 1;
				SpTmp.attribute = sprite[SP_ATTRIBUTE];
				SpTmp.index = SpIndex;
			}
		}
	}

	SpIndex += 4;
}

////////////////////////////////////////////////////////////////////////////////////////
// sprite tile pattern table byte fetch #1
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchSpPattern0()
{
	cycles.count += cycles.fetch;

	if (enabled)
	{
		if (SpBufferSize < SpTmpBufferSize)
		{
			UINT index = SpBufferSize;

			do
			{
				SPTMP& SpTmp = SpTmpBuffer[index];

				if (SpHeight == 8)
					SpTmp.address += SpPatternAddress;

				SpTmp.pattern = vRam[SpTmp.address];
			}
			while (MaxSprites > 8 && ++index >= 8 && index < SpTmpBufferSize);
		}
		else
		{
			vRam[SpPatternAddress];
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// sprite tile pattern table byte fetch #2
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::FetchSpPattern1()
{
	cycles.count += cycles.fetch;

	if (enabled)
	{
		if (SpBufferSize < SpTmpBufferSize)
		{
			do
			{
				const SPTMP& SpTmp = SpTmpBuffer[SpBufferSize];

				SP& sp = SpBuffer[SpBufferSize++];

				sp.x       = SpTmp.x;
				sp.palette = SpPalRam + ((SpTmp.attribute & SP_COLOR) << 2);
				sp.behind  = ((SpTmp.attribute & SP_BEHIND) ? b11 : b00);
				sp.zero    = (SpTmp.index ? b00 : b11);

				const UINT pattern1 = vRam[SpTmp.address + 8];

				const UINT pattern = 
				(
					((SpTmp.pattern & b01010101) << 0) |
					((SpTmp.pattern & b10101010) << 7) |
					((pattern1      & b01010101) << 1) |
					((pattern1      & b10101010) << 8) 
				);

				static const UCHAR shifter[2][8] =
				{
					{0xE,0x6,0xC,0x4,0xA,0x2,0x8,0x0},
					{0x0,0x8,0x2,0xA,0x4,0xC,0x6,0xE}
				};

				const UCHAR* const PDX_RESTRICT order = shifter[(SpTmp.attribute & SP_X_FLIP) ? 1 : 0];

				for (UINT i=0; i < 8; ++i)
					sp.pixels[i] = (pattern >> order[i]) & 0x3;

				if (sp.x < (8+1) && !(ctrl1 & CTRL1_SP_NO_CLIPPING))
					memset( sp.pixels, 0x00, sizeof(U8) * ((8+1) - sp.x) );
			}
			while (MaxSprites > 8 && SpBufferSize >= 8 && SpBufferSize < SpTmpBufferSize);
		}
		else
		{
			vRam[SpPatternAddress];
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// start the pixel rendering
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::BeginHActive()
{
	PDX_ASSERT(SpIndex == 0);

	SpTmpBufferSize = 0;

	++ScanLine;

	if (screen)
	{
		output.pixels = screen->pixels + (ScanLine * IO::GFX::WIDTH);
		output.ClipOffset = output.pixels + 8;
	}
	else
	{
		output.pixels = GarbageLine;
		output.ClipOffset = GarbageLine + 8;

		if (status & STATUS_SP_ZERO_HIT)
		{
			// Now it's safe to skip one line
			cycles.count += (pal ? NES_PPU_MCC_HACTIVE_PAL : NES_PPU_MCC_HACTIVE_NTSC);
			process = processes + PROCESS_HBLANK;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// end the pixel rendering
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::EndHActive()
{
	status &= ~STATUS_SP_OVERFLOW;	
	
	SpBufferSize = 0;
	SpIndex = 0;

	if (!screen)
	{
		if (status & STATUS_SP_ZERO_HIT)
		{
			// Simulate sprite and background pattern fetches for
			// any IRQ generating hardware depending on them

			if (enabled == (CTRL1_BG_ENABLED|CTRL1_SP_ENABLED))
			{
				vRam[BgPatternAddress];
				vRam[SpPatternAddress];
			}

			cycles.count += (pal ? NES_PPU_MCC_HBLANK_PAL : NES_PPU_MCC_HBLANK_NTSC);
			process = processes + (ScanLine < 239 ? PROCESS_HACTIVE : PROCESS_END);
		}
	}

	if (enabled)
	{
		if (ScanLine == SCANLINE_VBLANK)
		{
			vRamAddress = vRamLatch;
		}
		else
		{
			vRamAddress &= ~(VRAM_X_TILE|VRAM_NAME_LOW);
			vRamAddress |= vRamLatch & (VRAM_X_TILE|VRAM_NAME_LOW);

			const UINT yFine = vRamAddress & VRAM_Y_FINE;

			if (yFine == (7U << 12))
			{
				const UINT yTile = vRamAddress & VRAM_Y_TILE;

				switch (yTile)
				{
	     			case (29U << 5):	

	    				vRamAddress ^= VRAM_NAME_HIGH;

	      			case (31U << 5): 

	     				vRamAddress &= ~(VRAM_Y_FINE|VRAM_Y_TILE);
	    				return;

	       			default:

	       				vRamAddress &= ~VRAM_Y_FINE;
	         			vRamAddress += (1U << 5);
		     			return;
				}
			}
			else
			{
				vRamAddress += (1U << 12);
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// begin a new frame
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::BeginFrame(IO::GFX* const gfx)
{ 
	screen = gfx;
	process = processes;

	cycles.Reset( pal, EvenFrame ^= 1 );

	cpu.SetFrameCycles( cycles.frame );
	cpu.DoNMI();
}

////////////////////////////////////////////////////////////////////////////////////////
// end the frame
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::EndFrame()
{ 
	const ULONG target = cycles.frame;

	while (cycles.count < target)
		(this->**process++)();
}

/////////////////////////////////////////////////////////////////////////////////////////
// end the horizontal blanking
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::EndHBlank()
{
	cycles.count += cycles.rest;

	if (ScanLine < 239)
		process = processes + PROCESS_HACTIVE;
}

////////////////////////////////////////////////////////////////////////////////////////
// end the horizontal dummy scanline blanking
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::EndHDummy()
{
	if (EvenFrame)
		cycles.count += cycles.rest;

	if (screen && PDX_FAILED(screen->Lock()))	
		screen = NULL;
}

////////////////////////////////////////////////////////////////////////////////////////
// end the frame pixel rendering process
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::EndVActive()
{
	cycles.count = cycles.frame;
	ScanLine = SCANLINE_VBLANK;

	if (screen)
	{
		screen->Unlock();
		screen = NULL;
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// read from any memory location
////////////////////////////////////////////////////////////////////////////////////////

UINT PPU::Peek(UINT address)
{
	Update();
	
	address &= 0x3FFF;
	
	return (address < 0x2000) ? cRam[address] : CiRam[address & 0x1FFF];
}

////////////////////////////////////////////////////////////////////////////////////////
// write into any memory location
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::Poke(UINT address,const UINT data)
{
	Update();
	
	address &= 0x3FFF;
	
	if (address < 0x2000) 
		cRam[address] = data;
	else
		CiRam[address & 0x1FFF] = data;
}

////////////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////////////

VOID PPU::Log(const CHAR* const msg,const UINT which)
{
	if (!logged[which])
	{
		logged[which] = true;
		LogOutput( msg );
	}
}

////////////////////////////////////////////////////////////////////////////////////////
// load state file
////////////////////////////////////////////////////////////////////////////////////////

PDXRESULT PPU::LoadState(PDXFILE& file)
{
	{
		HEADER header;

		if (!file.Read(header))
			return PDX_FAILURE;

		ctrl0        = header.ctrl0;
		ctrl1        = header.ctrl1;
		status       = header.status;
		vRamAddress  = header.vRamAddress;
		vRamLatch    = header.vRamLatch;
		OamAddress   = header.OamAddress;
		DmaLatch     = header.DmaLatch;
		latch        = header.latch;
		ReadLatch    = header.ReadLatch;
		xFine        = header.xFine;
		FlipFlop     = header.FlipFlop;
		EvenFrame    = header.EvenFrame;
	}

	UpdateTmpVariables();

	if (!file.Readable(sizeof(U8) * (n8k+256+32)))
		return PDX_FAILURE;

	file.Read( cRam, cRam + n8k );
	file.Read( SpRam, SpRam + 256 );
	file.Read( PalRam, PalRam + 32 );

	return CiRam.LoadState(file);
}

////////////////////////////////////////////////////////////////////////////////////////
// save state file
////////////////////////////////////////////////////////////////////////////////////////

PDXRESULT PPU::SaveState(PDXFILE& file) const
{
	{
		HEADER header;

		header.ctrl0        = ctrl0;
		header.ctrl1        = ctrl1;
		header.status       = status;
		header.vRamAddress  = vRamAddress;
		header.vRamLatch    = vRamLatch;
		header.OamAddress   = OamAddress;
		header.DmaLatch     = DmaLatch;
		header.latch        = latch;
		header.ReadLatch    = ReadLatch;
		header.xFine        = xFine;
		header.FlipFlop     = FlipFlop ? 1 : 0;
		header.EvenFrame    = EvenFrame ? 1 : 0;

		file.Write(header);
	}

	file.Write( cRam, cRam + n8k );
	file.Write( SpRam, SpRam + 256 );
	file.Write( PalRam, PalRam + 32 );

	return CiRam.SaveState(file);
}

////////////////////////////////////////////////////////////////////////////////////////
// garbage fetches for the dummy scanline
////////////////////////////////////////////////////////////////////////////////////////

#define NES_DUMMYTILE \
			    	  \
 EvaluateSp,	      \
 FetchName,		      \
 FetchAttribute,      \
 EvaluateSp,	      \
 FetchPattern0,       \
 FetchPattern1

////////////////////////////////////////////////////////////////////////////////////////
// fetches for the first two tiles at the end of the scanline
////////////////////////////////////////////////////////////////////////////////////////

#define NES_PREFETCH  \
				      \
 PreFetchBgName,	  \
 PreFetchBgAttribute, \
 PreFetchBgPattern0,  \
 PreFetchBgPattern1,  \
 FetchBgName,     	  \
 RenderDummyPixels,	  \
 FetchBgAttribute,    \
 RenderDummyPixels,	  \
 FetchBgPattern0,     \
 RenderDummyPixels,   \
 FetchBgPattern1,     \
 RenderDummyPixels

////////////////////////////////////////////////////////////////////////////////////////
// dummy scanline
////////////////////////////////////////////////////////////////////////////////////////

#define NES_DUMMY_LINE												\
																	\
 NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE,		\
 NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE,		\
 NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE,		\
 NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE,		\
 NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE,		\
 NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE,		\
 NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE,		\
 NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE, NES_DUMMYTILE,		\
																	\
 EndHActive,														\
																	\
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,			\
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,			\
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,			\
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,			\
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,			\
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,			\
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,			\
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,			\
																	\
 NES_PREFETCH,														\
																	\
 FetchName, FetchName, EndHDummy
																					   
////////////////////////////////////////////////////////////////////////////////////////
// pixel rendering and fetches for tiles 2..32 
////////////////////////////////////////////////////////////////////////////////////////

#define NES_TILE	 \
					 \
 EvaluateSp,		 \
 FetchBgName,		 \
 RenderPixel,		 \
 RenderPixel,		 \
 FetchBgAttribute,   \
 RenderPixel,		 \
 RenderPixel,		 \
 EvaluateSp,		 \
 FetchBgPattern0,	 \
 RenderPixel,		 \
 RenderPixel,		 \
 FetchBgPattern1,	 \
 RenderPixel,		 \
 RenderPixel

////////////////////////////////////////////////////////////////////////////////////////
// active scanline
////////////////////////////////////////////////////////////////////////////////////////

#define NES_LINE												  \
																  \
 BeginHActive,													  \
																  \
 NES_TILE, NES_TILE, NES_TILE, NES_TILE,						  \
 NES_TILE, NES_TILE, NES_TILE, NES_TILE,						  \
 NES_TILE, NES_TILE, NES_TILE, NES_TILE,						  \
 NES_TILE, NES_TILE, NES_TILE, NES_TILE,						  \
 NES_TILE, NES_TILE, NES_TILE, NES_TILE,						  \
 NES_TILE, NES_TILE, NES_TILE, NES_TILE,						  \
 NES_TILE, NES_TILE, NES_TILE, NES_TILE,						  \
 NES_TILE, NES_TILE, NES_TILE, NES_TILE,						  \
																  \
 EndHActive,													  \
																  \
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,		  \
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,		  \
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,		  \
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,		  \
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,		  \
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,		  \
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,		  \
 FetchName, FetchName, FetchSpPattern0, FetchSpPattern1,		  \
																  \
 NES_PREFETCH,													  \
 FetchName, FetchName, EndHBlank

////////////////////////////////////////////////////////////////////////////////////////
// all the routines gathered in one big fat LUT
////////////////////////////////////////////////////////////////////////////////////////

const PPU::PROCESS PPU::processes[] =
{
	BeginVBlank, 
	EndVBlank, 
	NES_DUMMY_LINE,
	NES_LINE,
	EndVActive
};

NES_NAMESPACE_END
Espero sea de tu ayuda.

anewuser
Posteador
Posteador
Mensajes: 43
Registrado: 29/10/06 16:50
Ubicación: IRC

Mensaje por anewuser » 04/03/07 16:54

Mi buen samaritano Switchstance usa por favor http://pastebin.ca/ para mensajes tan largos ;)

Avatar de Usuario
Switchstance
Guardián del Aguila
Guardián del Aguila
Mensajes: 4549
Registrado: 02/10/06 2:05
Frase: Donde está Kurt1977???
Ubicación: Rio Gallegos
Contactar:

Mensaje por Switchstance » 04/03/07 17:03

No creo que nadie que no esté interesado lo iba a leer, no creo que este TOPIC sea para toda la comunidad. Procedo a cerrarlo y al amigo que está interesando en el developing de emuladores, la seguimos por MSN.

No es de mala onda, pero no quiero off-topic en un topic copado como este y como somos pocos, estas cosas mejor discutirlas por msn.

Switchstance : keyboard_ninja@hotmail.com

Avatar de Usuario
marty_00
Aprendiz de Pirata
Aprendiz de Pirata
Mensajes: 2186
Registrado: 23/02/06 21:27

Mensaje por marty_00 » 04/03/07 17:51

Por si no logran encontrar la solución hoy le planteé esto a un amigo que me dijo que iba a ayudar en lo que podía. A partir de ahora cualquier mensaje de offtopic dentro de este thread será borrado. Espero que logren solucionarlo, y Switch la debe tener clara :)
Imagen

Avatar de Usuario
Faker
Check Point!
Check Point!
Mensajes: 10265
Registrado: 20/09/06 22:11
Frase: AGUANTE MARIO10
Ubicación: ONMF!

Mensaje por Faker » 05/03/07 22:15

Muy bien ahi Swicthstance!!!

Talos
Usuario nuevo
Usuario nuevo
Mensajes: 2
Registrado: 24/02/06 20:11
Ubicación: Carapachay
Contactar:

Mensaje por Talos » 06/03/07 0:51

Quería hacer mi aporte también con algo de código de otra fuente. El emulador en cuestión es el FCE Ultra (http://fceultra.sourceforge.net/). La implementación de la emulación del PPU está en el módulo ppu.c. De paso podés ver cómo está hecho el sync.

Con respecto al sync, y si no entendí mal (cualquier cosa confío en que Switch me corrija :D), propongo una solución del tipo:

Código: Seleccionar todo


while(!done) {
   ...
   actualizar_cpu();
   n_ciclos++;

   if((n_ciclos % CICLOS_CPU_VBLANK) == 0) {
      ppu_comenzar_vblank();
   }
   ppu_actualizar();
   ...
}
donde tenés un bucle central que maneja tus eventos y actualiza los estados del CPU y del PPU.

Donde decís que el número de ciclos de CPU por vblank es vel_micro_Hz / 60, usás 60Hz por la frecuencia de NTSC? Agrego el dato (no sé si hará falta, y supongo que ya lo sabrás): la frecuencia de refresco de PAL es 50Hz (y el micro corre a 1.66MHz) en comparación con los 60Hz y 1.79MHz para NTSC.

anewuser
Posteador
Posteador
Mensajes: 43
Registrado: 29/10/06 16:50
Ubicación: IRC

Mensaje por anewuser » 07/03/07 23:35

Switchstance escribió:No creo que nadie que no esté interesado lo iba a leer, no creo que este TOPIC sea para toda la comunidad. Procedo a cerrarlo y al amigo que está interesando en el developing de emuladores, la seguimos por MSN.

No es de mala onda, pero no quiero off-topic en un topic copado como este y como somos pocos, estas cosas mejor discutirlas por msn.
Saludos.

De hecho estoy de acuerdo contigo :(
Mis disculpas públicas a todos.
Viendo porno o escuchando chiptunes...bueno...es lo mismo ^^

Responder

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 1 invitado