view src/vdp.c @ 43:6e7bfe83d2b0

Changed the design to vastly simplify the video hardware and support a 23-bit address space on the CPU
author Michael Pavone <pavone@retrodev.com>
date Sat, 27 Aug 2016 22:38:31 -0700
parents 083347ccd508
children
line wrap: on
line source

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

#define MAX_ACTIVE_LINES 240
#define TOTAL_LINES 262
#define ACTIVE_WIDTH 320
#define TOTAL_WIDTH 416

#define VDP_STATUS_FB_SELECT    1
#define VDP_STATUS_PENDING_VINT 2
#define VDP_STATUS_VBLANK       4
#define VDP_STATUS_CRAM_PENDING 8
#define VDP_STATUS_VINT_ENABLED 0x2000
#define VDP_STATUS_DEPTH        0x4000
#define VDP_STATUS_ENABLED      0x8000

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;
}

void vdp_run(vdp *context, uint32_t target)
{
	uint8_t *current_fb = context->status & VDP_STATUS_FB_SELECT ? context->vram + 64*1024 : context->vram;
	
	while (context->cycles < target)
	{
		context->hcounter+=2;
		if (context->hcounter == TOTAL_WIDTH) {
			context->hcounter = 0;
			context->vcounter++;
			if (context->vcounter == TOTAL_LINES) {
				context->vcounter = 0;
			}
		}
		//Draw to framebuffer
		if (context->vcounter < MAX_ACTIVE_LINES && context->hcounter < ACTIVE_WIDTH) {
			if (!context->framebuffer) {
				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);
			}
			uint16_t *dest = context->framebuffer + context->vcounter * context->pitch + context->hcounter;
			if (
				context->status & VDP_STATUS_ENABLED 
				&& context->vcounter >= context->top_skip 
				&& context->vcounter < MAX_ACTIVE_LINES - context->bottom_skip
			) {
				if (context->status & VDP_STATUS_DEPTH) {
					uint16_t offset = context->start_offset + (context->vcounter - context->top_skip) * ACTIVE_WIDTH + context->hcounter;
					//8bpp
					*(dest++) = context->cram[current_fb[offset++]];
					*dest = context->cram[current_fb[offset]];
				} else {
					//4bpp
					uint8_t pixels = current_fb[context->start_offset + (context->vcounter - context->top_skip) * ACTIVE_WIDTH + context->hcounter >> 1];
					*(dest++) = context->cram[context->pal_select | pixels >> 4];
					*dest = context->cram[context->pal_select | (pixels & 0xF)];
				}
			} else {
				*(dest++) = context->cram[0];
				*dest = context->cram[0];
			}
		} else if (context->framebuffer && context->hcounter < ACTIVE_WIDTH) {
			system_framebuffer_updated();
			context->framebuffer = NULL;
		}
		if (!context->hcounter) {
			if (context->vcounter == (context->vcounter - context->bottom_skip)) {
				context->status |= VDP_STATUS_PENDING_VINT | VDP_STATUS_VBLANK;
			} else if (context->vcounter == context->top_skip) {
				//clear pending interrupt flag since VBlank is over
				context->status &= ~(VDP_STATUS_PENDING_VINT | VDP_STATUS_VBLANK);
			}
		}
		context->cycles += context->clock_inc;
	}
}
void vdp_write_mode(vdp *context, uint16_t value)
{
	uint16_t status_bits = VDP_STATUS_ENABLED | VDP_STATUS_DEPTH | VDP_STATUS_VINT_ENABLED | VDP_STATUS_FB_SELECT;
	context->status &= ~status_bits;
	context->status |= value & status_bits;
	context->pal_select = value >> 7 & 0x30;
	context->top_skip = value >> 6 & 0x1F;
	context->bottom_skip = value >> 1 & 0x1F;
}

void vdp_write_cram(vdp *context, uint16_t value)
{
	if (context->status & VDP_STATUS_CRAM_PENDING) {
		context->cram[context->pal_write_index++] = value;
		if (!(--context->pal_write_count)) {
			context->status &= ~VDP_STATUS_CRAM_PENDING;
		}
	} else {
		context->pal_write_count = value;
		context->pal_write_index = value >> 8;
		context->status |= VDP_STATUS_CRAM_PENDING;
	}
}

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 + ((TOTAL_WIDTH - context->hcounter) >> 1) * context->clock_inc;
		uint32_t vint_line = (MAX_ACTIVE_LINES - context->bottom_skip);
		if (context->vcounter < vint_line) {
			return next_line_cyc + (vint_line - next_line) * (TOTAL_WIDTH >> 1) * context->clock_inc;
		} else {
			return next_line_cyc + (vint_line + TOTAL_LINES - next_line) * (TOTAL_WIDTH >> 1) * context->clock_inc;
		}
	} 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;
}

uint8_t *vdp_get_back_buffer(vdp *context)
{
	return context->status & VDP_STATUS_FB_SELECT ? context->vram : context->vram + 64*1024;
}