/* * graphics.c * * Graphics function emulation * This pretty much attempts to emulate the QuickBasic-style text screen * in 25x40 or 25x80 mode with no scrolling... */ #include #include #include #include #include "SDL.h" #include "grv.h" #include "graphics.h" #define min(x,y) ((x)<(y)?(x):(y)) #define max(x,y) ((x)>(y)?(x):(y)) #define TS_WIDTH 80 #define TS_HEIGHT 25 #define FONT_XSIZE 8 #define FONT_YSIZE 14 #define PX_WIDTH (TS_WIDTH*FONT_XSIZE) #define PX_HEIGHT (TS_HEIGHT*FONT_YSIZE) typedef uint8_t font_t; extern font_t grv_font[256][FONT_YSIZE]; #define NCOLORS 16 static uint32_t colors[NCOLORS]; static struct rgba { uint8_t a, r, g, b; } rgbcolors[NCOLORS] = { {0x00,0x00,0x00,0x00}, /* black */ {0x00,0x00,0x00,0xaa}, /* dark blue */ {0x00,0x00,0xaa,0x00}, /* dark green */ {0x00,0x00,0xaa,0xaa}, /* dark cyan */ {0x00,0xaa,0x00,0x00}, /* dark red */ {0x00,0xaa,0x00,0xaa}, /* dark purple */ {0x00,0xaa,0x55,0x00}, /* brown */ {0x00,0xaa,0xaa,0xaa}, /* light grey */ {0x00,0x55,0x55,0x55}, /* dark grey */ {0x00,0x55,0x55,0xff}, /* light blue */ {0x00,0x55,0xff,0x55}, /* light green */ {0x00,0x55,0xff,0xff}, /* light cyan */ {0x00,0xff,0x55,0x55}, /* light red */ {0x00,0xff,0x55,0xff}, /* light purple */ {0x00,0xff,0xff,0x55}, /* yellow */ {0x00,0xff,0xff,0xff}, /* white */ }; struct screen_char { uint8_t sym; /* Symbol code */ uint8_t attr; /* Attribute code */ }; static struct screen_char screendata[TS_HEIGHT][TS_WIDTH]; static uint8_t screendirty[TS_HEIGHT][TS_WIDTH]; static struct current_screen { int x; int y; uint8_t attr; uint8_t dwidth; /* Double width characters, must be 0 or 1 */ } current = {0, 0, 7, 1}; /* Current screen parameters */ static SDL_Surface *rscreen; static volatile uint8_t blink_mask = 0; /* * Set the attribute: fg, bg * For compatiblity's sake the flashing attribute is in fg, not bg */ void color(int fg, int bg) { current.attr = (fg & 15) | ((bg & 7) << 4) | ((fg & 16) << 3); } /* * Set the cursor position (1-based for compatiblity) */ void locate(int r, int c) { current.y = r-1; current.x = (c-1) << current.dwidth; } /* * Get the symbol at a specific screen coordinate */ int screen(int r, int c) { return screendata[r-1][(c-1) << current.dwidth].sym; } /* * Get the attribute at a specific screen coordinate */ int screen1(int r, int c) { return screendata[r-1][(c-1) << current.dwidth].attr; } /* * Prepare screen for modification */ static int screen_lock_ctr = 0; static void lock_screen(void) { if (!screen_lock_ctr++) SDL_LockSurface(rscreen); } static void unlock_screen(void) { if (!--screen_lock_ctr) SDL_UnlockSurface(rscreen); } /* * Update all locations on the screen that has been modified. * This must be called with the screen unlocked. */ void update_screen(void) { /* * Maximum number of rectangles possible; if we have more than this * number, then rectangles will be mergable horizontally at a minimum. */ struct SDL_Rect rects[TS_WIDTH*TS_HEIGHT/2]; SDL_Rect *r = rects; int x, x0, y, y0; int n = 0; for (y = 0; y < TS_HEIGHT; y++) { for (x = 0; x < TS_WIDTH; x++) { if (screendirty[y][x]) { x0 = x; y0 = y; /* Extend the rectangle horizontally */ while (x < TS_WIDTH && screendirty[y][x]) { screendirty[y][x] = 0; x++; } /* See if we can expand this rectangle vertically */ while (++y < TS_HEIGHT) { int x1; for (x1 = x0; x1 < x; x1++) if (!screendirty[y][x1]) goto done; /* It worked... */ for (x1 = x0; x1 < x; x1++) screendirty[y][x1] = 0; } done: r->x = x0 * FONT_XSIZE; r->y = y0 * FONT_YSIZE; r->w = (x - x0) * FONT_XSIZE; r->h = (y - y0) * FONT_YSIZE; r++; n++; y = y0; x--; } } } if ( n ) SDL_UpdateRects(rscreen, n, rects); } /* * Update the on-screen structure to match the screendata[] * for character (tx,ty), but don't refresh the rectangle just * yet. This should be called with the screen locked. * * tx,ty is zero-based and must be adjusted for doublewidth mode */ static void put_screen(int tx, int ty) { font_t *fontp; font_t v; uint32_t *pixelp, *pixelr, fgp, bgp; int x, y, z; int attr; int pxwid = current.dwidth + 1; int bmask = blink_mask; attr = screendata[ty][tx].attr; fontp = grv_font[screendata[ty][tx].sym]; bgp = colors[(attr >> 4) & 7]; fgp = (bmask & attr) ? bgp : colors[attr & 15]; /* * This assumes that either unaligned references are okay, or * pitch will be a multiple of 4. */ pixelr = ((uint32_t *) ((char *)rscreen->pixels + ty*FONT_YSIZE*rscreen->pitch)) + tx*FONT_XSIZE; for ( y = 0 ; y < FONT_YSIZE ; y++ ) { pixelp = pixelr; v = fontp[y]; for ( x = 0 ; x < FONT_XSIZE ; x++ ) { for ( z = 0 ; z < pxwid ; z++ ) *pixelp++ = v & ((font_t)1 << (FONT_XSIZE-1)) ? fgp : bgp; v <<= 1; } pixelr = (uint32_t *)((char *)pixelr + rscreen->pitch); } screendirty[ty][tx] = 1; screendirty[ty][tx+pxwid-1] = 1; } /* * This routine switches the blink status, then goes around the screen * and updates all characters which has the blink attribute set. */ void update_blink(void) { int x, y, xs; xs = current.dwidth+1; blink_mask ^= 0x80; lock_screen(); for ( y = 0 ; y < TS_HEIGHT ; y++ ) for ( x = 0 ; x < TS_WIDTH ; x += xs ) if ( screendata[y][x].attr & 0x80 ) put_screen(x,y); unlock_screen(); update_screen(); } /* * Display a string of characters and advance cursor */ void print(const char *str) { const unsigned char *p = (const unsigned char *)str; unsigned char ch; int x = current.x; int y = current.y; uint8_t attr = current.attr; int x0 = x; int y0 = y; int x1 = x; int y1 = y; int xwid; if ( !*str ) return; xwid = current.dwidth+1; lock_screen(); while ( (ch = *p++) ) { screendata[y][x].sym = ch; screendata[y][x].attr = attr; put_screen(x,y); x0 = min(x0,x); y0 = min(y0,y); x1 = max(x1,x+xwid-1); y1 = max(y1,y); x += xwid; if ( x >= TS_WIDTH ) { x = 0; y++; if ( y >= TS_HEIGHT ) { y = 0; } } } unlock_screen(); current.x = x; current.y = y; } /* * Same except with printf-style formatting */ void gprintf(const char *fmt, ...) { char buffer[4096]; va_list ap; va_start(ap, fmt); vsprintf(buffer, fmt, ap); va_end(ap); print(buffer); } /* * locate() and print() as a combined function */ void lprint(int r, int c, const char *str) { locate(r,c); print(str); } /* * locate() and gprintf() as a combined function */ void lprintf(int r, int c, const char *fmt, ...) { char buffer[4096]; va_list ap; locate(r,c); va_start(ap, fmt); vsprintf(buffer, fmt, ap); va_end(ap); print(buffer); } /* * Set 40/80 character mode. Unlike hardware this doesn't change the * text currently on the screen, but funny things can happen; cls() is * highly recommended. */ void width(int wid) { switch(wid) { case 40: current.dwidth = 1; current.x &= ~1; break; case 80: current.dwidth = 0; break; default: abort(); } } /* * Clear the screen, set all characters to ' ' and set all pixels to the * current background, then refresh the screen content. */ void cls(void) { int i; struct screen_char *scp = &screendata[0][0]; struct screen_char empty; uint32_t pixelval; SDL_Rect rect; lock_screen(); empty.sym = ' '; empty.attr = current.attr; pixelval = colors[(current.attr >> 4) & 7]; for ( i = TS_WIDTH*TS_HEIGHT ; i ; i-- ) *scp++ = empty; rect.x = rect.y = 0; rect.w = PX_WIDTH; rect.h = PX_HEIGHT; SDL_FillRect(rscreen, &rect, pixelval); unlock_screen(); SDL_UpdateRects(rscreen, 1, &rect); memset(screendirty, 0, sizeof screendirty); current.x = current.y = 0; } /* * Save the screen as well as "current" in a malloc'd buffer */ struct saved_screen { struct current_screen current; struct screen_char screendata[TS_HEIGHT][TS_WIDTH]; }; struct saved_screen *save_screen(void) { struct saved_screen *save = malloc(sizeof(struct saved_screen)); if ( !save ) return NULL; memcpy(&save->current, ¤t, sizeof(current)); memcpy(&save->screendata, &screendata, sizeof(screendata)); return save; } void restore_screen(struct saved_screen *save) { int x, y, xs; memcpy(¤t, &save->current, sizeof(current)); memcpy(&screendata, &save->screendata, sizeof(screendata)); xs = current.dwidth+1; lock_screen(); for ( y = 0 ; y < TS_HEIGHT ; y++ ) { for ( x = 0 ; x < TS_WIDTH ; x += xs ) { put_screen(x,y); } } unlock_screen(); update_screen(); } /* * Initialize SDL and the data structures */ int screen_init(int window) { int i; if ( SDL_Init(SDL_INIT_TIMER|SDL_INIT_VIDEO | (opt.debug ? SDL_INIT_NOPARACHUTE : 0)) ) return -1; atexit(SDL_Quit); if ( !(rscreen = SDL_SetVideoMode(PX_WIDTH, PX_HEIGHT, 32, SDL_SWSURFACE | (window ? 0 : SDL_FULLSCREEN))) ) { return -1; } /* No mouse cursor, please */ if ( !window ) SDL_ShowCursor(SDL_DISABLE); /* Convert colors to preferred machine representation */ for ( i = 0 ; i < NCOLORS ; i++ ) { colors[i] = SDL_MapRGB(rscreen->format, rgbcolors[i].r, rgbcolors[i].g, rgbcolors[i].b); } cls(); SDL_AddTimer(400, post_periodic, (void *)event_blink); return 0; }