view src/vdp.c @ 26:083347ccd508

Implemented vblank interrupts and fixed a bug in exception vector address calculation
author Michael Pavone <pavone@retrodev.com>
date Fri, 01 Apr 2016 21:34:38 -0700
parents a085f17b79e9
children 6e7bfe83d2b0
line wrap: on
line source

#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "vdp.h"
#include "system.h"

void vdp_init(vdp *context, uint32_t clock_div)
{
	memset(context, 0, sizeof(vdp));
	//clock div specifies the pixel clock divider
	//but our emulation step is half that fast
	context->clock_inc = clock_div*2;
	context->drawbuffer = context->linebuffers;
	context->readbuffer = context->linebuffers+320;
}

void vdp_run(vdp *context, uint32_t target)
{
	while (context->cycles < target)
	{
		context->hcounter+=2;
		if (context->hcounter == 416) {
			context->hcounter = 0;
			context->vcounter++;
			if (context->vcounter == 262) {
				context->vcounter = 0;
			}
		}
		context->status &= ~(VDP_STATUS_VRAM|VDP_STATUS_SRAM);
		//Render to linebuffer
		if ((context->status & VDP_STATUS_ENABLED) && context->vcounter > 15 && context->vcounter < 240 && context->hcounter < 406) {
			if (context->hcounter < 246) {
				context->status |= VDP_STATUS_VRAM;
				if (!context->hcounter) {
					//flip linebuffers
					if (context->drawbuffer == context->linebuffers) {
						context->drawbuffer = context->linebuffers + 328;
						context->readbuffer = context->linebuffers;
					} else {
						context->drawbuffer = context->linebuffers;
						context->readbuffer = context->linebuffers + 328;
					}
					context->draw_dest = 0;
					//enable sprite scanning
					context->status |= VDP_STATUS_SPRITE_SCAN;
					context->current_draw = 0;
				}
				if (context->draw_counter) {
					context->draw_counter--;
					uint16_t pixels = context->vram[context->draw_source++];
					for (int i = context->hflip ? 0 : 12; i >= 0 && i < 16; i+= context->hflip ? 4 : -4)
					{
						uint8_t pixel = ((pixels >> i) & 0xF) | context->palpriority;
						context->drawbuffer[context->draw_dest ^ (context->hflip << 2)] = pixel;
						context->draw_dest++;
					}
				} else {
					//00VV VVVV VVHH HHHH
					uint16_t vpos = (context->vscroll & 0x7FF) + context->vcounter - 16;
					uint16_t vmask = (context->vscroll >> 2) & 0x3E00;
					uint16_t vcoarse = (vpos << 3) & 0x3FC0;
					uint16_t vfine = vpos & 7;
					uint16_t hcoarse = ((context->hscroll >> 3) + context->hcounter/6) & 0x3F;
					uint16_t tableaddress = hcoarse | (vcoarse & ~vmask) | ((context->vscroll << 3) & vmask);
					//printf("VCounter: %X, VScroll: %X, HCounter: %X, Table: %X\n", context->vcounter, context->vscroll, context->hcounter, tableaddress);
					uint16_t entry = context->vram[tableaddress];
					context->draw_source = (entry & 0x3FF) * 16;
					if (entry & 0x1000) {
						context->draw_source += 14 - vfine * 2;
					} else {
						context->draw_source += vfine * 2;
					}
					context->palpriority = entry >> 9 & 0x70;
					context->draw_counter = 2;
					context->hflip = (entry & 0x800) != 0;
				}
				if (context->status & VDP_STATUS_SPRITE_SCAN) {
					context->status |= VDP_STATUS_SRAM;
					uint16_t pos = context->sram[context->hcounter];
					uint16_t y = pos & 0xFF;
					uint16_t x = pos >> 8;
					uint16_t atts = context->sram[context->hcounter+1];
					x |= atts << 2 & 0x100;
					if (x | y) {
						uint16_t size = atts & 0x400 ? 16 : 8;
						if (context->vcounter >= y && context->vcounter < y + size) {
							uint16_t address = (atts & 0x3F) * 16;
							if (atts & 0x1000) {
								address += (size-1) * 2 - (context->vcounter - y) * 2;
							} else {
								address += (context->vcounter - y) * 2;
							}
							context->sprite_draws[context->current_draw].source = address;
							context->sprite_draws[context->current_draw].x = x;
							context->sprite_draws[context->current_draw].hflip = (atts & 0x800) != 0;
							context->sprite_draws[context->current_draw].palpriority = 0x80 | (atts >> 9 & 0x50);
							context->current_draw++;
							if (size == 16) {
								context->sprite_draws[context->current_draw].source = address + 32;
								context->sprite_draws[context->current_draw].x = x + 8;
								context->sprite_draws[context->current_draw].hflip = (atts & 0x800) != 0;
								context->sprite_draws[context->current_draw].palpriority = 0x80 | (atts >> 9 & 0x50);
								if (context->sprite_draws[context->current_draw].hflip) {
									context->sprite_draws[context->current_draw].x -= 8;
									context->sprite_draws[context->current_draw-1].x += 8;
								}
							}
							context->current_draw++;
							if (context->current_draw == 40) {
								//no more rendering capacity
								context->status &= ~VDP_STATUS_SPRITE_SCAN;
								context->current_draw = 0;
							}
						}
					} else {
						//hit sprite list terminator
						context->status &= ~VDP_STATUS_SPRITE_SCAN;
						context->current_draw = 0;
					}
				}
			} else {
				sprite_draw *draw = context->sprite_draws + (context->current_draw >> 1);
				if (draw->palpriority) {
					context->status |= VDP_STATUS_VRAM;
					uint16_t pixels = context->vram[draw->source + (context->current_draw & 1)];
					uint16_t x = draw->x - 16 + (context->hscroll & 7);
					for (int i = draw->hflip ? 0 : 12; i >= 0 && i < 16; i+= draw->hflip ? 4 : -4, x++)
					{
						uint8_t pixel = (pixels >> i) & 0xF;
						if (pixel && x < 328 && ((draw->palpriority & 0x40) || !(context->drawbuffer[x] & 0x40))) {
							context->drawbuffer[x ^ (draw->hflip << 2)] = pixel | draw->palpriority;
						}
					}
					if (context->current_draw & 1) {
						draw->palpriority = 0;
					} else {
						draw->x += 4;
					}
				}
				context->current_draw++;
			}
		}
		//Draw to framebuffer
		if (context->vcounter > 8 && context->vcounter < 249 && context->hcounter < 320) {
			if (!context->hcounter && context->vcounter == 9) {
				context->framebuffer = system_get_framebuffer(&context->pitch);
				//pitch is in terms of bytes, but we want it in terms of pixels
				context->pitch /= sizeof(uint16_t);
				//clear pending interrupt flag since VBlank is over
				context->status &= ~VDP_STATUS_PENDING_VINT;
			}
			uint16_t *dest = context->framebuffer + (context->vcounter - 9) * context->pitch + context->hcounter;
			if (context->status & VDP_STATUS_ENABLED && context->vcounter > 16 && context->vcounter < 241) {
				*dest = context->cram[0x3F & context->readbuffer[context->hcounter]];
				dest++;
				*dest = context->cram[0x3F & context->readbuffer[context->hcounter+1]];
			} else {
				//Display is disabled or we're in the border area, draw the background color
				*dest = *context->cram;
				dest++;
				*dest = *context->cram;
			}
		} else if(!context->hcounter && context->vcounter == 249) {
			if (context->status & VDP_STATUS_ENABLED) {
				context->status |= VDP_STATUS_PENDING_VINT;
			}
			system_framebuffer_updated();
			context->framebuffer = NULL;
		}
		//Handle the FIFO
		if (context->status & VDP_STATUS_FIFO) {
			switch (context->fifo_dest)
			{
			case FIFO_DEST_VRAM:
				if (!(context->status & VDP_STATUS_VRAM)) {
					context->vram[context->dest_offset++] = context->fifo;
					context->dest_offset &= sizeof(context->vram)/2-1;
					context->status &= ~VDP_STATUS_FIFO;
				}
				break;
			case FIFO_DEST_SRAM:
				if (!(context->status & VDP_STATUS_SRAM)) {
					context->sram[context->dest_offset++] = context->fifo;
					context->dest_offset &= sizeof(context->sram)/2-1;
					context->status &= ~VDP_STATUS_FIFO;
				}
				break;
			case FIFO_DEST_CRAM:
				context->cram[context->dest_offset++] = context->fifo;
				context->dest_offset &= sizeof(context->cram)/2-1;
				context->status &= ~VDP_STATUS_FIFO;
				break;
			}
		}
		context->cycles += context->clock_inc;
	}
}
void vdp_write_address(vdp *context, uint16_t value)
{
	context->status &= ~VDP_STATUS_FIFO;
	if (!(value & 0x8000)) {
		context->fifo_dest = FIFO_DEST_VRAM;
		context->dest_offset = (value & (sizeof(context->vram) -1))/2;
	} else if ((value & 0xFF00) == 0xFE00) {
		context->fifo_dest = FIFO_DEST_SRAM;
		context->dest_offset = (value & (sizeof(context->sram) -1))/2;
	} else if ((value & 0xFF00) == 0xFF00) {
		context->fifo_dest = FIFO_DEST_CRAM;
		context->dest_offset = (value & (sizeof(context->cram) -1))/2;
	} 
}

void vdp_write_data(vdp *context, uint16_t value)
{
	context->fifo = value;
	context->status |= VDP_STATUS_FIFO;
}

void vdp_write_hscroll(vdp *context, uint16_t value)
{
	context->hscroll = value & 0x1FF;
	if (value & 0x8000) {
		context->status |= VDP_STATUS_ENABLED;
	} else {
		context->status &= ~VDP_STATUS_ENABLED;
	}
}

uint32_t vdp_next_interrupt(vdp *context)
{
	if (context->status & VDP_STATUS_PENDING_VINT) {
		return 0;
	} else if (context->status & VDP_STATUS_ENABLED) {
		uint32_t next_line = context->vcounter + 1;
		uint32_t next_line_cyc = context->cycles + ((416 - context->hcounter) >> 1) * context->clock_inc;
		if (context->vcounter < 249) {
			return next_line_cyc + (249 - next_line) * 832;
		} else {
			return next_line_cyc + (249 + 262 - next_line) * 832;
		}
	} else {
		return 0xFFFFFFFF;
	}
}

void vdp_ack_interrupt(vdp *context)
{
	context->status &= ~VDP_STATUS_PENDING_VINT;
}

uint8_t vdp_interrupt_pending(vdp *context)
{
	return (context->status & VDP_STATUS_PENDING_VINT) != 0;
}