changeset 2069:8e51c0c3f2e3 segacd

Initial attempt at implementing the Sega CD graphics hardware
author Michael Pavone <pavone@retrodev.com>
date Sun, 30 Jan 2022 19:55:33 -0800
parents f573f2c31bc9
children afc54649ebed
files Makefile cd_graphics.c cd_graphics.h segacd.c segacd.h
diffstat 5 files changed, 383 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sun Jan 30 11:58:34 2022 -0800
+++ b/Makefile	Sun Jan 30 19:55:33 2022 -0800
@@ -214,11 +214,13 @@
 
 MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o $(RENDEROBJS) io.o romdb.o hash.o menu.o xband.o \
 	realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
-	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o gen_player.o segacd.o lc8951.o cue.o cdd_mcu.o
+	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o gen_player.o \
+	segacd.o lc8951.o cue.o cdd_mcu.o cd_graphics.o
 
 LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.o xband.o realtec.o \
 	i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
-	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o rom.db.o gen_player.o segacd.o lc8951.o cue.o cdd.o cdd_mcu.o $(LIBZOBJS)
+	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o rom.db.o gen_player.o $(LIBZOBJS) \
+	segacd.o lc8951.o cue.o cdd_mcu.o cd_graphics.o
 
 ifdef NONUKLEAR
 CFLAGS+= -DDISABLE_NUKLEAR
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cd_graphics.c	Sun Jan 30 19:55:33 2022 -0800
@@ -0,0 +1,280 @@
+#include "cd_graphics.h"
+#include "backend.h"
+
+void cd_graphics_init(segacd_context *cd)
+{
+	cd->graphics_int_cycle = CYCLE_NEVER;
+}
+
+#define BIT_HFLIP 0x8000
+
+static uint8_t get_src_pixel(segacd_context *cd)
+{
+	uint16_t x = cd->graphics_x >> 11;
+	uint16_t y = cd->graphics_y >> 11;
+	cd->graphics_x += cd->graphics_dx;
+	cd->graphics_x &= 0xFFFFFF;
+	cd->graphics_y += cd->graphics_dy;
+	cd->graphics_y &= 0xFFFFFF;
+	uint16_t stamp_shift, pixel_mask;
+	uint16_t stamp_num_mask;
+	if (cd->gate_array[GA_STAMP_SIZE] & BIT_STS) {
+		//32x32 stamps
+		stamp_shift = 5;
+		pixel_mask = 0x1F;
+		stamp_num_mask = 0x3FC;
+	} else {
+		//16x16 stamps
+		stamp_shift = 4;
+		pixel_mask = 0xF;
+		stamp_num_mask = 0x3FF;
+	}
+	uint16_t stamp_x = x >> stamp_shift;
+	uint16_t stamp_y = y >> stamp_shift;
+	uint16_t max, base_mask;
+	uint32_t row_shift;
+	if (cd->gate_array[GA_STAMP_SIZE] & BIT_SMS) {
+		max = 4096 >> stamp_shift;
+		base_mask = 0xE000 << ((5 - stamp_shift) << 1);
+		//128 stamps in 32x32 mode, 256 stamps in 16x16 mode
+		row_shift = 12 - stamp_shift;
+	} else {
+		max = 256 >> stamp_shift;
+		base_mask = 0xFFE0 << ((5 - stamp_shift) << 1);
+		//8 stamps in 32x32 mode, 16 stamps in 16x16 mode
+		row_shift = 8 - stamp_shift;
+	}
+	if (stamp_x > max || stamp_y > max) {
+		if (cd->gate_array[GA_STAMP_SIZE] & BIT_RPT) {
+			stamp_x &= max - 1;
+			stamp_y &= max - 1;
+		} else {
+			return 0;
+		}
+	}
+	uint32_t address = (cd->gate_array[GA_STAMP_MAP_BASE] & base_mask) << 1;
+	address += (stamp_y << row_shift) + stamp_x;
+	uint16_t stamp_def = cd->word_ram[address];
+	uint16_t stamp_num = stamp_def & stamp_num_mask;
+	uint16_t pixel_x = x & pixel_mask;
+	uint16_t pixel_y = y & pixel_mask;
+	if (stamp_def & BIT_HFLIP) {
+		pixel_x = pixel_mask - pixel_x;
+	}
+	uint16_t tmp;
+	switch (stamp_def >> 13 & 3)
+	{
+	case 0:
+		break;
+	case 1:
+		tmp = pixel_y;
+		pixel_y = pixel_x;
+		pixel_x = pixel_mask - tmp;
+		break;
+	case 2:
+		tmp = pixel_y;
+		pixel_y = pixel_mask - pixel_x;
+		pixel_x = pixel_mask - tmp;
+		break;
+	case 3:
+		tmp = pixel_y;
+		pixel_y = pixel_mask - pixel_x;
+		pixel_x = tmp;
+		break;
+	}
+	uint16_t cell_x = pixel_x >> 4;
+	uint32_t pixel_address = stamp_num << 6;
+	pixel_address += (pixel_y << 1) + (cell_x << (stamp_shift + 2)) + (pixel_x >> 2 & 1);
+	uint16_t word = cd->word_ram[pixel_address];
+	switch (pixel_x & 3)
+	{
+	default:
+	case 0:
+		return word >> 12;
+	case 1:
+		return word >> 8 & 0xF;
+	case 2:
+		return word >> 4 & 0xF;
+	case 3:
+		return word & 0xF;
+	}
+
+}
+
+enum {
+	FETCH_X,
+	FETCH_Y,
+	FETCH_DX,
+	FETCH_DY,
+	PIXEL0,
+	PIXEL1,
+	PIXEL2,
+	PIXEL3,
+	DRAW
+};
+
+void draw_pixels(segacd_context *cd)
+{
+	uint16_t to_draw = 4 - (cd->graphics_dst_x & 3);
+	uint16_t x_end = cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3);
+	if (cd->graphics_dst_x + to_draw > x_end) {
+		to_draw = cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3) - cd->graphics_dst_x;
+	}
+	for(uint16_t i = 0; i < to_draw; i++)
+	{
+		uint32_t dst_address = cd->gate_array[GA_IMAGE_BUFFER_START] << 1;
+		dst_address += cd->graphics_dst_y << 4;
+		dst_address += cd->graphics_dst_x >> 2 & 1;
+		dst_address += ((cd->graphics_dst_x >> 3) * cd->gate_array[GA_IMAGE_BUFFER_VCELLS]) << 4;
+		uint16_t pixel_shift = 12 - 4 * (cd->graphics_dst_x & 3);
+		uint16_t pixel = cd->graphics_pixels[i] << pixel_shift;
+		uint16_t src_mask_check = 0xFF << pixel_shift;
+		uint16_t src_mask_keep = ~src_mask_check;
+		switch (cd->gate_array[1] >> 3 & 3)
+		{
+		case 0:
+			//priority mode off
+			cd->word_ram[dst_address] &= src_mask_keep;
+			cd->word_ram[dst_address] |= pixel;
+			break;
+		case 1:
+			//underwrite
+			if (pixel && ! (cd->word_ram[dst_address] & src_mask_check)) {
+				cd->word_ram[dst_address] &= src_mask_keep;
+				cd->word_ram[dst_address] |= pixel;
+			}
+			break;
+		case 3:
+			//overwrite
+			if (pixel) {
+				cd->word_ram[dst_address] &= src_mask_keep;
+				cd->word_ram[dst_address] |= pixel;
+			}
+			break;
+		}
+	}
+	cd->graphics_dst_x += to_draw;
+	if (cd->graphics_dst_x == x_end) {
+		cd->graphics_dst_y++;
+		--cd->gate_array[GA_IMAGE_BUFFER_LINES];
+		cd->graphics_step = FETCH_X;
+	} else {
+		cd->graphics_step = PIXEL0;
+	}
+}
+
+#define CHECK_CYCLES cd->graphics_step++; if(cd->graphics_cycle >= cycle) break
+#define CHECK_ONLY if(cd->graphics_cycle >= cycle) break
+
+static void do_graphics(segacd_context *cd, uint32_t cycle)
+{
+	if (!cd->gate_array[GA_IMAGE_BUFFER_LINES]) {
+		return;
+	}
+	while (cd->graphics_cycle < cycle)
+	{
+		switch (cd->graphics_step)
+		{
+		case FETCH_X:
+			cd->graphics_x = cd->word_ram[cd->gate_array[GA_TRACE_VECTOR_BASE] << 1] << 8;
+			cd->graphics_cycle += 3*4;
+			cd->graphics_dst_x = cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3;
+			CHECK_CYCLES;
+		case FETCH_Y:
+			cd->graphics_y = cd->word_ram[cd->gate_array[GA_TRACE_VECTOR_BASE] << 1 + 1] << 8;
+			cd->graphics_cycle += 2*4;
+			CHECK_CYCLES;
+		case FETCH_DX:
+			cd->graphics_dx = cd->word_ram[cd->gate_array[GA_TRACE_VECTOR_BASE] << 1 + 2];
+			if (cd->graphics_dx & 0x8000) {
+				cd->graphics_dx |= 0xFF0000;
+			}
+			cd->graphics_cycle += 2*4;
+			CHECK_CYCLES;
+		case FETCH_DY:
+			cd->graphics_dy = cd->word_ram[cd->gate_array[GA_TRACE_VECTOR_BASE] << 1 + 3];
+			if (cd->graphics_dy & 0x8000) {
+				cd->graphics_dy |= 0xFF0000;
+			}
+			cd->graphics_cycle += 2*4;
+			CHECK_CYCLES;
+		case PIXEL0:
+			cd->graphics_pixels[0] = get_src_pixel(cd);
+			cd->graphics_cycle += 2*4;
+			if ((cd->graphics_dst_x & 3) == 3 || (cd->graphics_dst_x + 1 == cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3))) {
+				cd->graphics_step = DRAW;
+				CHECK_ONLY;
+			} else {
+				CHECK_CYCLES;
+			}
+		case PIXEL1:
+			cd->graphics_pixels[1] = get_src_pixel(cd);
+			cd->graphics_cycle += 2*4;
+			if ((cd->graphics_dst_x & 3) == 2 || (cd->graphics_dst_x + 2 == cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3))) {
+				cd->graphics_step = DRAW;
+				CHECK_ONLY;
+			} else {
+				CHECK_CYCLES;
+			}
+		case PIXEL2:
+			cd->graphics_pixels[2] = get_src_pixel(cd);
+			cd->graphics_cycle += 2*4;
+			if ((cd->graphics_dst_x & 3) == 1 || (cd->graphics_dst_x + 3 == cd->gate_array[GA_IMAGE_BUFFER_HDOTS] + (cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3))) {
+				cd->graphics_step = DRAW;
+				CHECK_ONLY;
+			} else {
+				CHECK_CYCLES;
+			}
+		case PIXEL3:
+			cd->graphics_pixels[3] = get_src_pixel(cd);
+			cd->graphics_cycle += 2*4;
+			CHECK_CYCLES;
+		case DRAW:
+			draw_pixels(cd);
+			cd->graphics_cycle += 1*4;
+			if (!cd->gate_array[GA_IMAGE_BUFFER_LINES]) {
+				break;
+			}
+			CHECK_ONLY;
+		}
+	}
+}
+
+void cd_graphics_run(segacd_context *cd, uint32_t cycle)
+{
+	while (cd->graphics_cycle < cycle)
+	{
+		if (cd->gate_array[GA_STAMP_SIZE] & BIT_GRON) {
+			do_graphics(cd, cycle);
+			//end calculation and actual emulated execution time probably don't 100% line up yet
+			//deal with that here for now
+			for(; cd->graphics_cycle < cycle; cd->graphics_cycle += 4)
+			{
+			}
+			if (cd->graphics_cycle >= cd->graphics_int_cycle) {
+				cd->gate_array[GA_STAMP_SIZE] &= ~BIT_GRON;
+				break;
+			}
+		} else {
+			cd->graphics_cycle = cycle;
+		}
+	}
+}
+void cd_graphics_start(segacd_context *cd)
+{
+	if (!(cd->gate_array[GA_STAMP_SIZE] & BIT_GRON)) {
+		printf("grahpics start @ %u\n", cd->graphics_cycle);
+		cd->gate_array[GA_STAMP_SIZE] |= BIT_GRON;
+		//Manual scan is bad, but formula appears to be
+		// vsize * (13 + 2 * hoffset + 9 * (hdots + hoffset - 1))
+		//with an additional 13? cycle setup cost per line
+		uint32_t lines = cd->gate_array[GA_IMAGE_BUFFER_LINES];
+		uint32_t hdots = cd->gate_array[GA_IMAGE_BUFFER_HDOTS];
+		uint32_t hoffset = cd->gate_array[GA_IMAGE_BUFFER_OFFSET] & 3;
+		cd->graphics_int_cycle = cd->graphics_cycle + 4 * lines * (13 + 2 * hoffset + 9 * (hdots + hoffset - 1));
+		cd->graphics_dst_y = cd->gate_array[GA_IMAGE_BUFFER_OFFSET] >> 3;
+	} else {
+		printf("graphics start ignored @ %u\n", cd->graphics_cycle);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cd_graphics.h	Sun Jan 30 19:55:33 2022 -0800
@@ -0,0 +1,27 @@
+#ifndef CD_GRAPHICS_H_
+#define CD_GRAPHICS_H_
+
+#include "segacd.h"
+
+enum {
+	GA_STAMP_SIZE = 0x58/2,
+	GA_STAMP_MAP_BASE,
+	GA_IMAGE_BUFFER_VCELLS,
+	GA_IMAGE_BUFFER_START,
+	GA_IMAGE_BUFFER_OFFSET,
+	GA_IMAGE_BUFFER_HDOTS,
+	GA_IMAGE_BUFFER_LINES,
+	GA_TRACE_VECTOR_BASE
+};
+
+//GA_STAMP_SIZE
+#define BIT_GRON 0x8000
+#define BIT_SMS  0x0004
+#define BIT_STS  0x0002
+#define BIT_RPT  0x0001
+
+void cd_graphics_init(segacd_context *cd);
+void cd_graphics_run(segacd_context *cd, uint32_t cycle);
+void cd_graphics_start(segacd_context *cd);
+
+#endif //CD_GRAPHICS_H_
--- a/segacd.c	Sun Jan 30 11:58:34 2022 -0800
+++ b/segacd.c	Sun Jan 30 19:55:33 2022 -0800
@@ -1,6 +1,6 @@
 #include <stdlib.h>
 #include <string.h>
-#include "segacd.h"
+#include "cd_graphics.h"
 #include "genesis.h"
 #include "util.h"
 
@@ -367,6 +367,12 @@
 						context->int_cycle = cd->int2_cycle;
 						context->int_num = 2;
 					}
+					if (mask < 1) {
+						if (cd->graphics_int_cycle < context->int_cycle && (cd->gate_array[GA_INT_MASK] & BIT_MASK_IEN1)) {
+							context->int_cycle = cd->graphics_int_cycle;
+							context->int_num = 1;
+						}
+					}
 				}
 			}
 		}
@@ -451,6 +457,14 @@
 			value |= pixel << (i * 4);
 		}
 		return value;
+	case GA_STAMP_SIZE:
+	case GA_IMAGE_BUFFER_LINES:
+		//these two have bits that change based on graphics operations
+		cd_graphics_run(cd, m68k->current_cycle);
+		return cd->gate_array[reg];
+	case GA_TRACE_VECTOR_BASE:
+		//write only
+		return 0xFFFF;
 	}
 	default:
 		return cd->gate_array[reg];
@@ -613,6 +627,40 @@
 	case GA_FONT_BITS:
 		cd->gate_array[reg] = value;
 		break;
+	case GA_STAMP_SIZE:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] &= BIT_GRON;
+		cd->gate_array[reg] |= value & (BIT_SMS|BIT_STS|BIT_RPT);
+		break;
+	case GA_STAMP_MAP_BASE:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0xFFE0;
+		break;
+	case GA_IMAGE_BUFFER_VCELLS:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0x1F;
+		break;
+	case GA_IMAGE_BUFFER_START:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0xFFF8;
+		break;
+	case GA_IMAGE_BUFFER_OFFSET:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0x3F;
+		break;
+	case GA_IMAGE_BUFFER_HDOTS:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0x1FF;
+		break;
+	case GA_IMAGE_BUFFER_LINES:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0xFF;
+		break;
+	case GA_TRACE_VECTOR_BASE:
+		cd_graphics_run(cd, m68k->current_cycle);
+		cd->gate_array[reg] = value & 0xFFFE;
+		cd_graphics_start(cd);
+		break;
 	default:
 		printf("Unhandled gate array write %X:%X\n", address, value);
 	}
@@ -747,6 +795,7 @@
 {
 	timers_run(cd, cycle);
 	cdd_run(cd, cycle);
+	cd_graphics_run(cd, cycle);
 }
 
 static m68k_context *sync_components(m68k_context * context, uint32_t address)
@@ -755,6 +804,9 @@
 	scd_peripherals_run(cd, context->current_cycle);
 	switch (context->int_ack)
 	{
+	case 1:
+		cd->graphics_int_cycle = CYCLE_NEVER;
+		break;
 	case 2:
 		cd->int2_cycle = CYCLE_NEVER;
 		break;
@@ -816,6 +868,14 @@
 	}
 	cdd_mcu_adjust_cycle(&cd->cdd, deduction);
 	lc8951_adjust_cycles(&cd->cdc, deduction);
+	cd->graphics_cycle -= deduction;
+	if (cd->graphics_int_cycle != CYCLE_NEVER) {
+		if (cd->graphics_int_cycle > deduction) {
+			cd->graphics_int_cycle -= deduction;
+		} else {
+			cd->graphics_int_cycle = 0;
+		}
+	}
 }
 
 static uint16_t main_gate_read16(uint32_t address, void *vcontext)
@@ -1106,6 +1166,7 @@
 		media = media->chain;
 	}
 	cdd_mcu_init(&cd->cdd, media);
+	cd_graphics_init(cd);
 
 	return cd;
 }
--- a/segacd.h	Sun Jan 30 11:58:34 2022 -0800
+++ b/segacd.h	Sun Jan 30 19:55:33 2022 -0800
@@ -18,8 +18,16 @@
 	uint8_t         *bram;
 	uint32_t        stopwatch_cycle;
 	uint32_t        int2_cycle;
+	uint32_t        graphics_int_cycle;
 	uint32_t        periph_reset_cycle;
+	uint32_t        graphics_cycle;
 	uint32_t        base;
+	uint32_t        graphics_x;
+	uint32_t        graphics_y;
+	uint32_t        graphics_dx;
+	uint32_t        graphics_dy;
+	uint16_t        graphics_dst_x;
+	uint8_t         graphics_pixels[4];
 	uint8_t         timer_pending;
 	uint8_t         timer_value;
 	uint8_t         busreq;
@@ -31,6 +39,8 @@
 	cdd_mcu         cdd;
 	uint8_t         cdc_dst_low;
 	uint8_t         cdc_int_ack;
+	uint8_t         graphics_step;
+	uint8_t         graphics_dst_y;
 } segacd_context;
 
 segacd_context *alloc_configure_segacd(system_media *media, uint32_t opts, uint8_t force_region, rom_info *info);