From 1b87ca8aa3a98600e69f14a83823e67eb90aabf7 Mon Sep 17 00:00:00 2001 From: Jeremy Penner Date: Sat, 5 Jan 2019 16:16:08 -0500 Subject: [PATCH] Initial commit --- .gitignore | 3 + testbed.c | 511 ++++++++++++++++++++++++++++++++++++++++++++++++++++ testbed.exe | Bin 0 -> 18862 bytes tiles.tif | Bin 0 -> 4078 bytes 4 files changed, 514 insertions(+) create mode 100644 .gitignore create mode 100755 testbed.c create mode 100755 testbed.exe create mode 100755 tiles.tif diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15e50eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.obj +*.bak + diff --git a/testbed.c b/testbed.c new file mode 100755 index 0000000..1c1e90a --- /dev/null +++ b/testbed.c @@ -0,0 +1,511 @@ +#include +#include +#include + +/*** V I D E O ***/ +#define setMode(hexval) asm { mov ax, hexval; int 10h } + +#define setVGAMode() setMode(0013h) +#define setEGAMode() setMode(000Dh) +#define setTextMode() setMode(0003h) + +#define REG_TS 0x03c4 +#define REG_GDC 0x03ce +#define REG_CRTC 0x03d4 + +#define PLANE_B 0x00 +#define PLANE_G 0x01 +#define PLANE_R 0x02 +#define PLANE_I 0x03 +#define setPlane(p) outport(REG_TS, 2 | (0x100 << p)) +#define setAllPlanes() outport(REG_TS, 0x0f02) + +#define VID ((volatile char far *)MK_FP(0xa000, 0)) + +void vid_cleanup() { + setTextMode(); +} + +typedef struct { + unsigned int b[16]; + unsigned int g[16]; + unsigned int r[16]; + unsigned int i[16]; +} Tile_t; + +unsigned int PAGE[] = { 0x00, 0x20, 0x40, 0x60 }; +// todo: HIGH BITS?? +#define setWriteMode(m) outport(REG_GDC, 0x05 | m << 8) + +void prepareEgaMemCopy() { + setAllPlanes(); + setWriteMode(1); +} +#define PAGE_STRIDE 40 +void blitTile(unsigned int offsetFrom, unsigned int offsetTo) { + int y; + for (y = 0; y < 16; y ++) { + VID[offsetTo] = VID[offsetFrom ++]; + VID[offsetTo + 1] = VID[offsetFrom ++]; + offsetTo += PAGE_STRIDE; + } +} + +#if 0 +void blitTile(Tile_t *tile, int plane, unsigned int page, unsigned int x, unsigned int y) { + unsigned int *data = tile->b + (plane << 4); + volatile unsigned int far *out = (volatile unsigned int far *) + &VID[(y * 640) + (x << 1) + (page << 8)]; + int i; + + for (i = 0; i < 320; i += 20) { + out[i] = *(data++); + } +} + +#endif + +#define flipPage(p) outport(REG_CRTC, 0x0c | (p << 8)) + +/*** K E Y B O A R D ***/ +#define KBD_INT 0x09 +void interrupt (*oldKbdISR)() = NULL; + +void kbd_cleanup() { + if (oldKbdISR != NULL) { + setvect(KBD_INT, oldKbdISR); + oldKbdISR = NULL; + } +} + +volatile char keybuf[128]; +volatile char kbd_triggered = 0; + +void interrupt kbd_isr() { + unsigned char raw; + char ctl; + + asm sti; + raw = inp(0x60); + ctl = inp(0x61) | 0x82; + outp(0x61, ctl); + outp(0x61, ctl & 0x7f); + outp(0x20, 0x20); + + if (raw & 0x80) { + keybuf[raw & 0x7f] = 0; + } else { + keybuf[raw] = 1; + } + kbd_triggered = raw; +} + +#define K_ESC 1 +#define K_1 2 +#define K_2 3 +#define K_3 4 +#define K_4 5 +#define K_5 6 +#define K_6 7 +#define K_7 8 +#define K_8 9 +#define K_9 10 +#define K_0 11 +#define K_MINUS 12 +#define K_EQUAL 13 +#define K_BKSP 14 +#define K_TAB 15 +#define K_Q 16 +#define K_W 17 +#define K_E 18 +#define K_R 19 +#define K_T 20 +#define K_Y 21 +#define K_U 22 +#define K_I 23 +#define K_O 24 +#define K_P 25 +#define K_LBRK 26 +#define K_RBRK 27 +#define K_ENTER 28 +#define K_CTRL 29 +#define K_A 30 +#define K_S 31 +#define K_D 32 +#define K_F 33 +#define K_G 34 +#define K_H 35 +#define K_J 36 +#define K_K 37 +#define K_L 38 +#define K_SEMI 39 +#define K_APOS 40 +#define K_TILDE 41 +#define K_LSHFT 42 +#define K_BSLSH 43 +#define K_Z 44 +#define K_X 45 +#define K_C 46 +#define K_V 47 +#define K_B 48 +#define K_N 49 +#define K_M 50 +#define K_COMMA 51 +#define K_DOT 52 +#define K_SLASH 53 +#define K_RSHFT 54 +#define K_PSCRN 55 +#define K_ALT 56 +#define K_SPACE 57 +#define K_CAPS 58 +#define K_F1 59 +#define K_F2 60 +#define K_F3 61 +#define K_F4 62 +#define K_F5 63 +#define K_F6 64 +#define K_F7 65 +#define K_F8 66 +#define K_F9 67 +#define K_F10 68 +#define K_NUMLK 69 +#define K_SCRL 70 +#define K_HOME 71 +#define K_UP 72 +#define K_PGUP 73 +#define K_NDASH 74 +#define K_LEFT 75 +#define K_CENT 76 +#define K_RIGHT 77 +#define K_NPLUS 78 +#define K_END 79 +#define K_DOWN 80 +#define K_PGDN 81 +#define K_INS 82 +#define K_DEL 83 +#define K_F11 87 +#define K_F12 88 +#define keyPressed(k) keybuf[k] + +unsigned char kbd_wait() { + kbd_triggered = 0; + while (!kbd_triggered) {} + return kbd_triggered; +} + +void kbd_init() { + if (oldKbdISR == NULL) { + memset(keybuf, 0, 128); + oldKbdISR = getvect(KBD_INT); + setvect(KBD_INT, kbd_isr); + atexit(kbd_cleanup); + } +} + +/*** M O U S E ***/ +struct { + unsigned int x; + unsigned int y; + unsigned int buttons; +} MOUSE; + +void far mouse_callback() { + asm { + mov ax, DGROUP + mov ds, ax + shr cx, 1 + mov MOUSE.x, cx + mov MOUSE.y, dx + mov MOUSE.buttons, bx + } +} + +void mouse_cleanup() { + //uninstall handler + asm { + mov ax, 0ch + mov dx, 0 + mov es, dx + mov cx, 0 + int 33h + + xor ax, ax + int 33h + } +} + +void mouse_init() { + unsigned seg_mouse_callback = FP_SEG(mouse_callback); + unsigned off_mouse_callback = FP_OFF(mouse_callback); + unsigned int result; + asm { + xor ax, ax + int 33h + mov result, ax + } + if (result == 0) { + printf("Mouse driver not installed\n"); + exit(1); + } + atexit(mouse_cleanup); + + asm { + mov ax, seg_mouse_callback + mov es, ax + mov dx, off_mouse_callback + mov ax, 0ch + mov cx, 1fh + int 33h + } +} + +#define mouse_hide() asm { mov ax, 02h; int 33h } +#define mouse_show() asm { mov ax, 01h; int 33h } + +/*** T I F F ***/ +typedef struct { + unsigned int endian; + unsigned int version; + unsigned long ifdOffset; +} TifHeader_t; + +#define TIF_WIDTH 256 +#define TIF_HEIGHT 257 +#define TIF_BITSPERSAMPLE 258 +#define TIF_COMPRESSION 259 +#define TIF_STRIPOFFSETS 273 +#define TIF_ROWSPERSTRIP 278 + +typedef struct { + unsigned int id; + unsigned int dataType; + unsigned long dataCount; + unsigned long dataOffset; +} TifTag_t; + +typedef struct { + unsigned int width; + unsigned int height; + unsigned long rowsPerStrip; + unsigned long stripCount; + unsigned long stripOffsets; +} TifImageMeta_t; + +TifImageMeta_t tifLoadMeta(FILE *f) { + TifImageMeta_t meta = {0, 0, 0, 0, 0}; + TifHeader_t header; + TifTag_t tag; + unsigned int i, tagCount; + + fseek(f, 0, SEEK_SET); + fread(&header, 8, 1, f); + + if (header.endian != 0x4949 || header.version != 0x2a) { + goto fail; + } + fseek(f, header.ifdOffset, SEEK_SET); + fread(&tagCount, 2, 1, f); + for (i = 0; i < tagCount; i ++) { + fread(&tag, 12, 1, f); + if (tag.id == TIF_WIDTH) { + meta.width = tag.dataOffset; + } else if (tag.id == TIF_HEIGHT) { + meta.height = tag.dataOffset; + } else if (tag.id == TIF_BITSPERSAMPLE) { + if (tag.dataOffset != 4) goto fail; + } else if (tag.id == TIF_COMPRESSION) { + if (tag.dataOffset != 1) goto fail; + } else if (tag.id == TIF_STRIPOFFSETS) { + meta.stripCount = tag.dataCount; + meta.stripOffsets = tag.dataOffset; + } else if (tag.id == TIF_ROWSPERSTRIP) { + meta.rowsPerStrip = tag.dataOffset; + } + } + return meta; +fail: + meta.stripCount = 0; + return meta; +} + +#define MAX_WIDTH 320 + +int tifLoadEGA(FILE *f, TifImageMeta_t meta, unsigned int vidOffset, int maxY) { + int istrip; + int irow; + int ipixelpair; + int y = 0; + unsigned long offset; + unsigned char rowData[MAX_WIDTH >> 1]; + volatile unsigned char far *out = &VID[vidOffset]; + unsigned char b, g, r, i; + + if (meta.width > MAX_WIDTH || (meta.width % 16) != 0) { + return 0; + } + setWriteMode(0); + + for (istrip = 0; istrip < meta.stripCount; istrip ++) { + fseek(f, meta.stripOffsets + (istrip << 2), SEEK_SET); + fread(&offset, 4, 1, f); + fseek(f, offset, SEEK_SET); + + for (irow = 0; irow < meta.rowsPerStrip; irow ++) { + int ipixelpairLim = meta.width >> 1; + fread(rowData, 1, ipixelpairLim, f); + b = g = r = i = 0; + for (ipixelpair = 0; ipixelpair < ipixelpairLim; ipixelpair ++) { + unsigned char pixelpair = rowData[ipixelpair]; + int bpair = (pixelpair & 0x01) | (pixelpair & 0x10) >> 3; + int gpair = (pixelpair & 0x02) >> 1 | (pixelpair & 0x20) >> 4; + int rpair = (pixelpair & 0x04) >> 2 | (pixelpair & 0x40) >> 5; + int ipair = (pixelpair & 0x08) >> 3 | (pixelpair & 0x80) >> 6; + int shift = (ipixelpair % 4) << 1; + + b |= bpair << shift; + g |= gpair << shift; + r |= rpair << shift; + i |= ipair << shift; + + if (shift == 6 || ipixelpair == ipixelpairLim - 1) { + // todo: use write mode 2, this is slooww + setPlane(PLANE_B); *out = b; + setPlane(PLANE_R); *out = r; + setPlane(PLANE_G); *out = g; + setPlane(PLANE_I); *out = i; + out ++; + b = g = r = i = 0; + } + } + y++; + if (y == maxY) { + return y; + } + } + } + return y; +} + +int tifLoad(FILE *f, TifImageMeta_t meta, unsigned int *planeBuf, int maxY, int yRepeat) { + int istrip; + int irow; + int ipixelpair; + int y = 0; + unsigned long offset; + unsigned char rowData[MAX_WIDTH >> 1]; + unsigned int planeStride = (meta.width >> 4) * yRepeat; + unsigned int *b = planeBuf - 1; + unsigned int *g = b + planeStride; + unsigned int *r = g + planeStride; + unsigned int *i = r + planeStride; + + if (meta.width > MAX_WIDTH || (meta.width % 16) != 0) { + return 0; + } + + for (istrip = 0; istrip < meta.stripCount; istrip ++) { + fseek(f, meta.stripOffsets + (istrip << 2), SEEK_SET); + fread(&offset, 4, 1, f); + fseek(f, offset, SEEK_SET); + + for (irow = 0; irow < meta.rowsPerStrip; irow ++) { + fread(rowData, 1, meta.width >> 1, f); + for (ipixelpair = 0; ipixelpair < meta.width >> 1; ipixelpair ++) { + unsigned char pixelpair = rowData[ipixelpair]; + int bpair = (pixelpair & 0x01) | (pixelpair & 0x10) >> 3; + int gpair = (pixelpair & 0x02) >> 1 | (pixelpair & 0x20) >> 4; + int rpair = (pixelpair & 0x04) >> 2 | (pixelpair & 0x40) >> 5; + int ipair = (pixelpair & 0x08) >> 3 | (pixelpair & 0x80) >> 6; + int shift = (ipixelpair % 8) << 1; + if (shift == 0) { + *++b = bpair; + *++g = gpair; + *++r = rpair; + *++i = ipair; + } else { + *b |= bpair << shift; + *g |= gpair << shift; + *r |= rpair << shift; + *i |= ipair << shift; + } + } + y++; + if (y == maxY) { + return y; + } + if (y % yRepeat == 0) { + b += planeStride * 3; + g += planeStride * 3; + r += planeStride * 3; + i += planeStride * 3; + } + } + } + return y; +} + +/*** S C R A T C H ***/ +void paintPattern(int r, int g, int b, int t) { + int i; + + setPlane(PLANE_R); + for (i = 0; i < 8000; i ++) { + VID[i] = r; + } + setPlane(PLANE_G); + for (i = 0; i < 8000; i ++) { + VID[i] = g; + } + setPlane(PLANE_B); + for (i = 0; i < 8000; i ++) { + VID[i] = b; + } + setPlane(PLANE_I); + for (i = 0; i < 8000; i ++) { + VID[i] = t; + } +} + +int main() { + Tile_t tiles[16]; + FILE *f; + TifImageMeta_t meta; + int plane; + int x; + int y; + int z = 0; + unsigned int drawOffset; + unsigned int page = 0; + + #define OFF_TILES 0x4200 + setEGAMode(); + + f = fopen("TILES.TIF", "rb"); + meta = tifLoadMeta(f); + tifLoadEGA(f, meta, OFF_TILES, 256); + fclose(f); + + mouse_init(); + kbd_init(); + atexit(vid_cleanup); + + while (!keyPressed(K_ESC)) { + page ^= 0x20; + + prepareEgaMemCopy(); + drawOffset = page << 8; + for (y = 0; y < 13; y ++) { + for (x = 0; x < 20; x ++) { + blitTile(OFF_TILES + ((((x + y + z) >> 2) % 3) << 5), drawOffset); + drawOffset += 2; + } + drawOffset += 600; // 40 bytes per line * 15 more lines + } + flipPage(page); + + kbd_wait(); + z++; + } + + return 0; +} diff --git a/testbed.exe b/testbed.exe new file mode 100755 index 0000000000000000000000000000000000000000..8910cb96927aff77a28421154a46f4ca02ef7ddc GIT binary patch literal 18862 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 literal 0 HcmV?d00001 diff --git a/tiles.tif b/tiles.tif new file mode 100755 index 0000000000000000000000000000000000000000..2e2f5df7c6ed478a2164b0054a30c2ac96668d44 GIT binary patch literal 4078 zcmeHDF>V4u4D{t91QJEkq@uba1r^dh;L_6bhSk0xqNFZ)GVWE0Ly^2J8jZIRC}q$@w$pt#j@zoVVbw;Pih`Z$3YQZ)W=+ z;5*DdQ4f5HorBo9i5)J`Ar9zt=}evOyy(vA0`rU?l=0Wz*CHLT<<)f+MQWWlQF-YG zuBnrmoI1CFisDkW1y^X(mH6e9{0$VG)|uF%h@K?PorbB7+GIzx{T zSTa|APKv)C=n+>(?{Jy=#6e0|e2BTviJG|R5#}e?Dd}@vdiXkm(r>i=zP|?|fk+?{ dhy)^mNFWl31R{Y*AQFfK{w0BLk<)28${#8QDl`B9 literal 0 HcmV?d00001