mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-27 14:38:05 +11:00
d1718a90c7
Because that mode behaves exactly like in a regular TMS9918, a new driver for TMS9918 has been added in blkfs and SMS' VDP now uses it. Also, fix broken 5x7 font.
376 lines
9.5 KiB
C
376 lines
9.5 KiB
C
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
|
|
#include <xcb/xcb.h>
|
|
#define XK_MISCELLANY
|
|
#include <X11/keysymdef.h>
|
|
|
|
#include "emul.h"
|
|
#include "sms_vdp.h"
|
|
#include "sms_ports.h"
|
|
#include "sms_pad.h"
|
|
#include "sms_spi.h"
|
|
#include "ps2_kbd.h"
|
|
#include "sdc.h"
|
|
|
|
#define RAMSTART 0xc000
|
|
#define VDP_CMD_PORT 0xbf
|
|
#define VDP_DATA_PORT 0xbe
|
|
#define PORTS_CTL_PORT 0x3f
|
|
#define PORTS_IO1_PORT 0xdc
|
|
#define PORTS_IO2_PORT 0xdd
|
|
#define SDC_CTL 0x05
|
|
#define SDC_SPI 0x04
|
|
#define MAX_ROMSIZE 0x8000
|
|
|
|
static xcb_connection_t *conn;
|
|
static xcb_screen_t *screen;
|
|
|
|
/* graphics contexts */
|
|
static xcb_gcontext_t fg;
|
|
/* win */
|
|
static xcb_drawable_t win;
|
|
|
|
// pixels to draw. We draw them in one shot.
|
|
static xcb_rectangle_t rectangles[(32*8)*(24*8)];
|
|
|
|
static Machine *m;
|
|
static VDP vdp;
|
|
static bool vdp_changed;
|
|
static Ports ports;
|
|
static Pad pad;
|
|
static Kbd kbd;
|
|
static bool use_kbd = false;
|
|
static SDC sdc;
|
|
static SPI spi;
|
|
|
|
static uint8_t iord_vdp_cmd()
|
|
{
|
|
return tms_cmd_rd(&vdp.tms);
|
|
}
|
|
|
|
static uint8_t iord_vdp_data()
|
|
{
|
|
return vdp_data_rd(&vdp);
|
|
}
|
|
|
|
static uint8_t iord_ports_io1()
|
|
{
|
|
return ports_A_rd(&ports);
|
|
}
|
|
|
|
static uint8_t iord_ports_io2()
|
|
{
|
|
return ports_B_rd(&ports);
|
|
}
|
|
|
|
static uint8_t iord_pad()
|
|
{
|
|
return pad_rd(&pad);
|
|
}
|
|
|
|
static uint8_t iord_kbd()
|
|
{
|
|
return kbd_rd(&kbd);
|
|
}
|
|
|
|
static void iowr_vdp_cmd(uint8_t val)
|
|
{
|
|
vdp_cmd_wr(&vdp, val);
|
|
}
|
|
|
|
static void iowr_vdp_data(uint8_t val)
|
|
{
|
|
vdp_changed = true;
|
|
vdp_data_wr(&vdp, val);
|
|
}
|
|
|
|
static void iowr_ports_ctl(uint8_t val)
|
|
{
|
|
ports_ctl_wr(&ports, val);
|
|
}
|
|
|
|
static byte iord_spi()
|
|
{
|
|
return spi_rd(&spi);
|
|
}
|
|
|
|
static byte spix_sdc(byte val) { return sdc_spix(&sdc, val); }
|
|
|
|
void create_window()
|
|
{
|
|
uint32_t mask;
|
|
uint32_t values[2];
|
|
|
|
/* Create the window */
|
|
win = xcb_generate_id(conn);
|
|
mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
|
|
values[0] = screen->white_pixel;
|
|
values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS |
|
|
XCB_EVENT_MASK_KEY_RELEASE;
|
|
xcb_create_window(
|
|
conn,
|
|
screen->root_depth,
|
|
win,
|
|
screen->root,
|
|
0, 0,
|
|
500, 500,
|
|
10,
|
|
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
screen->root_visual,
|
|
mask, values);
|
|
|
|
fg = xcb_generate_id(conn);
|
|
mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
|
|
values[0] = screen->black_pixel;
|
|
values[1] = 0;
|
|
xcb_create_gc(conn, fg, screen->root, mask, values);
|
|
|
|
/* Map the window on the screen */
|
|
xcb_map_window(conn, win);
|
|
}
|
|
|
|
// To make things simple with X11, we only support monochrome display, which is
|
|
// inverted: As soon as the color of the pixel is non-black, we show a black
|
|
// pixel. If the pixel is white, we show black.
|
|
void draw_pixels()
|
|
{
|
|
xcb_get_geometry_reply_t *geom;
|
|
|
|
geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
|
|
|
|
xcb_clear_area(
|
|
conn, 0, win, 0, 0, geom->width, geom->height);
|
|
// Figure out inner size to maximize our screen's aspect ratio
|
|
int psize = geom->height / vdp.tms.height;
|
|
if (geom->width / vdp.tms.width < psize) {
|
|
// width is the constraint
|
|
psize = geom->width / vdp.tms.width;
|
|
}
|
|
int innerw = psize * vdp.tms.width;
|
|
int innerh = psize * vdp.tms.height;
|
|
int innerx = (geom->width - innerw) / 2;
|
|
int innery = (geom->height - innerh) / 2;
|
|
free(geom);
|
|
int drawcnt = 0;
|
|
for (int i=0; i<vdp.tms.width; i++) {
|
|
for (int j=0; j<vdp.tms.height; j++) {
|
|
if (vdp_pixel(&vdp, i, j)) {
|
|
int x = innerx + (i*psize);
|
|
int y = innery + (j*psize);
|
|
rectangles[drawcnt].x = x;
|
|
rectangles[drawcnt].y = y;
|
|
rectangles[drawcnt].height = psize;
|
|
rectangles[drawcnt].width = psize;
|
|
drawcnt++;
|
|
}
|
|
}
|
|
}
|
|
if (drawcnt) {
|
|
xcb_poly_fill_rectangle(
|
|
conn, win, fg, drawcnt, rectangles);
|
|
}
|
|
vdp_changed = false;
|
|
xcb_flush(conn);
|
|
}
|
|
|
|
// Returns true to exist event loop
|
|
static bool _handle_keypress(xcb_generic_event_t *e)
|
|
{
|
|
xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
|
|
if (ev->detail == 0x09) { // ESC
|
|
return true;
|
|
}
|
|
bool ispressed = e->response_type == XCB_KEY_PRESS;
|
|
// change keycode into symbol
|
|
xcb_get_keyboard_mapping_reply_t* km = xcb_get_keyboard_mapping_reply(
|
|
conn, xcb_get_keyboard_mapping(conn, ev->detail, 1), NULL);
|
|
if (km->length) {
|
|
xcb_keysym_t* keysyms = (xcb_keysym_t*)(km + 1);
|
|
if (use_kbd) {
|
|
if ((keysyms[0] == XK_Shift_L) || (keysyms[0] == XK_Shift_R)) {
|
|
kbd_pressshift(&kbd, ispressed);
|
|
} else if (ispressed) {
|
|
fprintf(stderr, "pressing %x\n", keysyms[0]);
|
|
kbd_presskey(&kbd, keysyms[0]);
|
|
}
|
|
} else { // pad
|
|
switch (keysyms[0]) {
|
|
case 'w':
|
|
pad_setbtn(&pad, PAD_BTN_UP, ispressed);
|
|
break;
|
|
case 'a':
|
|
pad_setbtn(&pad, PAD_BTN_LEFT, ispressed);
|
|
break;
|
|
case 's':
|
|
pad_setbtn(&pad, PAD_BTN_DOWN, ispressed);
|
|
break;
|
|
case 'd':
|
|
pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed);
|
|
break;
|
|
case 'h':
|
|
pad_setbtn(&pad, PAD_BTN_A, ispressed);
|
|
break;
|
|
case 'j':
|
|
pad_setbtn(&pad, PAD_BTN_B, ispressed);
|
|
break;
|
|
case 'k':
|
|
pad_setbtn(&pad, PAD_BTN_C, ispressed);
|
|
break;
|
|
case 'l':
|
|
pad_setbtn(&pad, PAD_BTN_START, ispressed);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
free(km);
|
|
return false;
|
|
}
|
|
|
|
void event_loop()
|
|
{
|
|
while (1) {
|
|
for (int i=0; i<100; i++) {
|
|
if (!emul_step()) {
|
|
fprintf(stderr, "CPU halted, quitting\n");
|
|
usleep(1000 * 1000);
|
|
break;
|
|
}
|
|
spi_pulse(&spi);
|
|
}
|
|
if (vdp_changed) {
|
|
// To avoid overdrawing, we'll let the CPU run a bit to finish its
|
|
// drawing operation.
|
|
for (int i=0; i<10000; i++) {
|
|
if (!emul_step()) {
|
|
fprintf(stderr, "CPU halted, quitting\n");
|
|
usleep(1000 * 1000);
|
|
break;
|
|
}
|
|
spi_pulse(&spi);
|
|
}
|
|
draw_pixels();
|
|
}
|
|
// A low tech way of checking when the window was closed. The proper way
|
|
// involving WM_DELETE is too complicated.
|
|
xcb_get_geometry_reply_t *geom;
|
|
geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
|
|
if (geom == NULL) {
|
|
return; // window has been closed.
|
|
} else {
|
|
free(geom);
|
|
}
|
|
xcb_generic_event_t *e = xcb_poll_for_event(conn);
|
|
if (!e) {
|
|
continue;
|
|
}
|
|
switch (e->response_type & ~0x80) {
|
|
/* ESC to exit */
|
|
case XCB_KEY_RELEASE:
|
|
case XCB_KEY_PRESS:
|
|
if (_handle_keypress(e)) return;
|
|
break;
|
|
case XCB_EXPOSE: {
|
|
draw_pixels();
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
free(e);
|
|
}
|
|
}
|
|
|
|
static void usage()
|
|
{
|
|
fprintf(stderr, "Usage: ./sms [-k] [-c sdcard.img] /path/to/rom\n");
|
|
}
|
|
|
|
static byte spi_dbg(byte val)
|
|
{
|
|
fprintf(stderr, "SPI XCH: %x\n", val);
|
|
return val+1;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
if (argc < 2) {
|
|
usage();
|
|
return 1;
|
|
}
|
|
vdp_init(&vdp);
|
|
vdp_changed = false;
|
|
ports_init(&ports);
|
|
pad_init(&pad, &ports.THA);
|
|
kbd_init(&kbd, &ports.THA);
|
|
sdc_init(&sdc);
|
|
spi_init(&spi, &ports.THB, &ports.TRB, spix_sdc);
|
|
|
|
int ch;
|
|
while ((ch = getopt(argc, argv, "kc:")) != -1) {
|
|
switch (ch) {
|
|
case 'k':
|
|
use_kbd = true;
|
|
break;
|
|
case 'c':
|
|
fprintf(stderr, "Setting up SD card image with %s\n", optarg);
|
|
sdc.fp = fopen(optarg, "r+");
|
|
if (sdc.fp == NULL) {
|
|
fprintf(stderr, "Can't open file\n");
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (optind != argc-1) {
|
|
usage();
|
|
return 1;
|
|
}
|
|
FILE *fp = fopen(argv[optind], "r");
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "Can't open %s\n", argv[1]);
|
|
return 1;
|
|
}
|
|
m = emul_init();
|
|
m->ramstart = RAMSTART;
|
|
int i = 0;
|
|
int c;
|
|
while ((c = fgetc(fp)) != EOF && i < MAX_ROMSIZE) {
|
|
m->mem[i++] = c & 0xff;
|
|
}
|
|
pclose(fp);
|
|
if (c != EOF) {
|
|
fprintf(stderr, "ROM image too large.\n");
|
|
return 1;
|
|
}
|
|
if (use_kbd) {
|
|
ports.portA_rd = iord_kbd;
|
|
} else {
|
|
ports.portA_rd = iord_pad;
|
|
}
|
|
ports.portB_rd = iord_spi;
|
|
|
|
m->iord[VDP_CMD_PORT] = iord_vdp_cmd;
|
|
m->iord[VDP_DATA_PORT] = iord_vdp_data;
|
|
m->iord[PORTS_IO1_PORT] = iord_ports_io1;
|
|
m->iord[PORTS_IO2_PORT] = iord_ports_io2;
|
|
m->iord[PORTS_CTL_PORT] = iord_noop;
|
|
m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd;
|
|
m->iowr[VDP_DATA_PORT] = iowr_vdp_data;
|
|
m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl;
|
|
|
|
conn = xcb_connect(NULL, NULL);
|
|
screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
|
create_window();
|
|
draw_pixels();
|
|
event_loop();
|
|
emul_printdebug();
|
|
if (sdc.fp) {
|
|
fclose(sdc.fp);
|
|
}
|
|
return 0;
|
|
}
|