1 /* xscreensaver, Copyright (c) 1998-2010 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * Apple ][ CRT simulator, by Trevor Blackwell <tlb@tlb.org>
12 * with additional work by Jamie Zawinski <jwz@jwz.org>
16 #include "screenhackI.h"
18 #include "ximage-loader.h"
20 #ifdef HAVE_XSHM_EXTENSION
25 * Implementation notes
27 * The A2 had 3 display modes: text, lores, and hires. Text was 40x24, and it
28 * disabled color in the TV. Lores gave you 40x48 graphics blocks, using the
29 * same memory as the text screen. Each could be one of 16 colors. Hires gave
30 * you 280x192 pixels. Odd pixels were blue or purple, and even pixels were
31 * orange or green depending on the setting of the high bit in each byte.
33 * The graphics modes could also have 4 lines of text at the bottom. This was
34 * fairly unreadable if you had a color monitor.
36 * Each mode had 2 different screens using different memory space. In hires
37 * mode this was sometimes used for double buffering, but more often the lower
38 * screen was full of code/data and the upper screen was used for display, so
39 * you got random garbage on the screen.
41 * The text font is based on X's standard 6x10 font, with a few tweaks like
42 * putting a slash across the zero.
44 * To use this, you'll call apple2(display, window, duration,
45 * controller) where the function controller defines what will happen.
46 * See bsod.c and apple2-main.c for example controllers. The
47 * controller function gets called whenever the machine ready to start
48 * something new. By setting sim->printing or sim->typing, it'll be
49 * busy for some time spitting characters out one at a time. By
50 * setting *next_actiontime+=X.X, it'll pause and just update the screen
51 * for that long before calling the controller function again.
53 * By setting stepno to A2CONTROLLER_DONE, the loop will end. It will also end
54 * after the time specified by the delay parameter. In either case, it calls
55 * the controller with stepno==A2CONTROLLER_FREE to allow it to release any
58 * The void* apple2_sim_t::controller_data is for the use of the controller.
59 * It will be initialize to NULL, and the controller can store its own state
65 a2_scroll(apple2_state_t *st)
68 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off cursor */
70 for (i=0; i<23; i++) {
71 memcpy(st->textlines[i],st->textlines[i+1],40);
73 memset(st->textlines[23],0xe0,40);
77 a2_printc_1(apple2_state_t *st, char c, int scroll_p)
79 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
81 if (c == '\n') /* ^J == NL */
94 else if (c == 014) /* ^L == CLS, Home */
99 else if (c == '\t') /* ^I == tab */
101 a2_goto(st, st->cursy, (st->cursx+8)&~7);
103 else if (c == 010) /* ^H == backspace */
105 st->textlines[st->cursy][st->cursx]=0xe0;
106 a2_goto(st, st->cursy, st->cursx-1);
108 else if (c == '\r') /* ^M == CR */
114 st->textlines[st->cursy][st->cursx]=c ^ 0xc0;
127 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
131 a2_printc(apple2_state_t *st, char c)
133 a2_printc_1(st, c, 1);
137 a2_printc_noscroll(apple2_state_t *st, char c)
139 a2_printc_1(st, c, 0);
144 a2_prints(apple2_state_t *st, char *s)
146 while (*s) a2_printc(st, *s++);
150 a2_goto(apple2_state_t *st, int r, int c)
154 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
157 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
161 a2_cls(apple2_state_t *st)
164 for (i=0; i<24; i++) {
165 memset(st->textlines[i],0xe0,40);
170 a2_clear_gr(apple2_state_t *st)
173 for (i=0; i<24; i++) {
174 memset(st->textlines[i],0x00,40);
179 a2_clear_hgr(apple2_state_t *st)
182 for (i=0; i<192; i++) {
183 memset(st->hireslines[i],0,40);
188 a2_invalidate(apple2_state_t *st)
193 a2_poke(apple2_state_t *st, int addr, int val)
196 if (addr>=0x400 && addr<0x800) {
198 int row=((addr&0x380)/0x80) + ((addr&0x7f)/0x28)*8;
199 int col=(addr&0x7f)%0x28;
200 if (row<24 && col<40) {
201 st->textlines[row][col]=val;
202 if (!(st->gr_mode&(A2_GR_HIRES)) ||
203 (!(st->gr_mode&(A2_GR_FULL)) && row>=20)) {
207 else if (addr>=0x2000 && addr<0x4000) {
208 int row=(((addr&0x1c00) / 0x400) * 1 +
209 ((addr&0x0380) / 0x80) * 8 +
210 ((addr&0x0078) / 0x28) * 64);
211 int col=((addr&0x07f)%0x28);
212 if (row<192 && col<40) {
213 st->hireslines[row][col]=val;
214 if (st->gr_mode&A2_GR_HIRES) {
221 a2_hplot(apple2_state_t *st, int hcolor, int x, int y)
225 highbit=((hcolor<<5)&0x80) ^ 0x80; /* capture bit 2 into bit 7 */
227 if (y<0 || y>=192 || x<0 || x>=280) return;
229 for (run=0; run<2 && x<280; run++) {
230 unsigned char *vidbyte = &st->hireslines[y][x/7];
231 unsigned char whichbit=1<<(x%7);
234 *vidbyte = (*vidbyte & 0x7f) | highbit;
236 /* use either bit 0 or 1 of hcolor for odd or even pixels */
237 masked_bit = (hcolor>>(1-(x&1)))&1;
239 /* Set whichbit to 1 or 0 depending on color */
240 *vidbyte = (*vidbyte & ~whichbit) | (masked_bit ? whichbit : 0);
247 a2_hline(apple2_state_t *st, int hcolor, int x1, int y1, int x2, int y2)
249 int dx,dy,incx,incy,x,y,balance;
251 /* Bresenham's line drawing algorithm */
275 a2_hplot(st, hcolor, x, y);
283 a2_hplot(st, hcolor, x, y);
289 a2_hplot(st, hcolor, x, y);
297 a2_hplot(st, hcolor, x, y);
302 a2_plot(apple2_state_t *st, int color, int x, int y)
307 if (x<0 || x>=40 || y<0 || y>=48) return;
309 byte=st->textlines[textrow][x];
311 byte = (byte&0xf0) | (color&0x0f);
313 byte = (byte&0x0f) | ((color&0x0f)<<4);
315 st->textlines[textrow][x]=byte;
319 a2_display_image_loading(apple2_state_t *st, unsigned char *image,
323 When loading images,it would normally just load the big binary
324 dump into screen memory while you watched. Because of the way
325 screen memory was laid out, it wouldn't load from the top down,
326 but in a funny interleaved way. You should call this with lineno
327 increasing from 0 thru 191 over a period of a few seconds.
330 int row=(((lineno / 24) % 8) * 1 +
331 ((lineno / 3 ) % 8) * 8 +
332 ((lineno / 1 ) % 3) * 64);
334 memcpy (st->hireslines[row], &image[row * 40], 40);
338 Simulate plausible initial memory contents for running a program.
341 a2_init_memory_active(apple2_sim_t *sim)
345 apple2_state_t *st=sim->st;
347 while (addr<0x4000) {
350 switch (random()%4) {
354 for (i=0; i<n && addr<0x4000; i++) {
355 unsigned char rb=((random()%6==0 ? 0 : random()%16) |
356 ((random()%5==0 ? 0 : random()%16)<<4));
357 a2_poke(st, addr++, rb);
362 /* Simulate shapes stored in memory. We use the font since we have it.
363 Unreadable, since rows of each character are stored in consecutive
364 bytes. It was typical to store each of the 7 possible shifts of
365 bitmaps, for fastest blitting to the screen. */
366 x=random()%(sim->text_im->width);
367 for (i=0; i<100; i++) {
368 for (y=0; y<8; y++) {
370 for (j=0; j<8; j++) {
371 c |= XGetPixel(sim->text_im, (x+j)%sim->text_im->width, y)<<j;
373 a2_poke(st, addr++, c);
375 x=(x+1)%(sim->text_im->width);
382 for (i=0; i<n && addr<0x4000; i++) {
383 a2_poke(st, addr++, 0);
393 #if 1 /* jwz: since MacOS doesn't have "6x10", I dumped this font to a PNG...
396 #include "images/gen/apple2font_png.h"
399 a2_make_font(apple2_sim_t *sim)
402 XWindowAttributes xgwa;
404 Pixmap p = image_data_to_pixmap (sim->dpy, sim->window,
405 apple2font_png, sizeof(apple2font_png),
407 XImage *im = XGetImage (sim->dpy, p, 0, 0, pix_w, pix_h, ~0L, ZPixmap);
408 XImage *mm = XGetImage (sim->dpy, m, 0, 0, pix_w, pix_h, 1, XYPixmap);
409 unsigned long black =
410 BlackPixelOfScreen (DefaultScreenOfDisplay (sim->dpy));
413 XFreePixmap (sim->dpy, p);
414 XFreePixmap (sim->dpy, m);
415 if (pix_w != 64*7) abort();
416 if (pix_h != 8) abort();
418 XGetWindowAttributes (sim->dpy, sim->window, &xgwa);
419 sim->text_im = XCreateImage (sim->dpy, xgwa.visual, 1, XYBitmap, 0, 0,
421 sim->text_im->data = malloc (sim->text_im->bytes_per_line *
422 sim->text_im->height);
424 /* Convert deep image to 1 bit */
425 for (y = 0; y < pix_h; y++)
426 for (x = 0; x < pix_w; x++)
427 XPutPixel (sim->text_im, x, y,
428 (XGetPixel (mm, x, y)
429 ? XGetPixel (im, x, y) == black
438 /* This table lists fixes for characters that differ from the standard 6x10
439 font. Each encodes a pixel, as (charindex*7 + x) + (y<<10) + (value<<15)
440 where value is 0 for white and 1 for black. */
441 static const unsigned short a2_fixfont[] = {
442 /* Fix $ */ 0x8421, 0x941d,
443 /* Fix % */ 0x8024, 0x0028, 0x8425, 0x0426, 0x0825, 0x1027, 0x1426, 0x9427,
445 /* Fix * */ 0x8049, 0x8449, 0x8849, 0x0c47, 0x0c48, 0x0c4a, 0x0c4b, 0x9049,
447 /* Fix , */ 0x9057, 0x1458, 0x9856, 0x1857, 0x1c56,
448 /* Fix . */ 0x1465, 0x1864, 0x1866, 0x1c65,
449 /* Fix / */ 0x006e, 0x186a,
450 /* Fix 0 */ 0x8874, 0x8c73, 0x9072,
451 /* Fix 1 */ 0x0878, 0x1878, 0x187c,
452 /* Fix 5 */ 0x8895, 0x0c94, 0x0c95,
453 /* Fix 6 */ 0x809f, 0x8c9c, 0x109c,
454 /* Fix 7 */ 0x8ca4, 0x0ca5, 0x90a3, 0x10a4,
455 /* Fix 9 */ 0x08b3, 0x8cb3, 0x98b0,
456 /* Fix : */ 0x04b9, 0x08b8, 0x08ba, 0x0cb9, 0x90b9, 0x14b9, 0x18b8, 0x18b9,
458 /* Fix ; */ 0x04c0, 0x08bf, 0x08c1, 0x0cc0, 0x90c0, 0x14c1, 0x98bf, 0x18c0,
460 /* Fix < */ 0x80c8, 0x00c9, 0x84c7, 0x04c8, 0x88c6, 0x08c7, 0x8cc5, 0x0cc6,
462 0x94c7, 0x14c8, 0x98c8, 0x18c9,
463 /* Fix > */ 0x80d3, 0x00d4, 0x84d4, 0x04d5, 0x88d5, 0x08d6, 0x8cd6, 0x0cd7,
465 0x94d4, 0x14d5, 0x98d3, 0x18d4,
466 /* Fix @ */ 0x88e3, 0x08e4, 0x8ce4, 0x98e5,
467 /* Fix B */ 0x84ef, 0x04f0, 0x88ef, 0x08f0, 0x8cef, 0x90ef, 0x10f0, 0x94ef,
469 /* Fix D */ 0x84fd, 0x04fe, 0x88fd, 0x08fe, 0x8cfd, 0x0cfe, 0x90fd, 0x10fe,
471 /* Fix G */ 0x8116, 0x0516, 0x9916,
472 /* Fix J */ 0x0129, 0x012a, 0x052a, 0x852b, 0x092a, 0x892b, 0x0d2a, 0x8d2b,
474 0x152a, 0x952b, 0x992a,
475 /* Fix M */ 0x853d, 0x853f, 0x093d, 0x893e, 0x093f,
476 /* Fix Q */ 0x915a, 0x155a, 0x955b, 0x155c, 0x195b, 0x995c, 0x1d5c,
477 /* Fix V */ 0x8d7b, 0x0d7c, 0x0d7e, 0x8d7f, 0x917b, 0x117c, 0x117e, 0x917f,
478 /* Fix [ */ 0x819e, 0x81a2, 0x859e, 0x899e, 0x8d9e, 0x919e, 0x959e, 0x999e,
480 /* Fix \ */ 0x01a5, 0x19a9,
481 /* Fix ] */ 0x81ac, 0x81b0, 0x85b0, 0x89b0, 0x8db0, 0x91b0, 0x95b0, 0x99ac,
483 /* Fix ^ */ 0x01b5, 0x05b4, 0x05b6, 0x09b3, 0x89b5, 0x09b7, 0x8db4, 0x8db6,
485 /* Fix _ */ 0x9db9, 0x9dbf,
490 a2_make_font(apple2_sim_t *sim)
493 Generate the font. It used a 5x7 font which looks a lot like the standard X
494 6x10 font, with a few differences. So we render up all the uppercase
495 letters of 6x10, and make a few tweaks (like putting a slash across the
496 zero) according to fixfont.
500 const char *def_font="6x10";
506 font = load_font_retry (sim->dpy, def_font);
508 fprintf(stderr, "%s: can't load font %s\n", progname, def_font);
512 text_pm=XCreatePixmap(sim->dpy, sim->window, 64*7, 8, 1);
514 memset(&gcv, 0, sizeof(gcv));
518 gc=XCreateGC(sim->dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
520 XSetForeground(sim->dpy, gc, 0);
521 XFillRectangle(sim->dpy, text_pm, gc, 0, 0, 64*7, 8);
522 XSetForeground(sim->dpy, gc, 1);
523 for (i=0; i<64; i++) {
529 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
531 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
536 for (i=0; a2_fixfont[i]; i++) {
537 XSetForeground (sim->dpy, gc, (a2_fixfont[i]>>15)&1);
538 XDrawPoint(sim->dpy, text_pm, gc, a2_fixfont[i]&0x3ff,
539 (a2_fixfont[i]>>10)&0xf);
541 XWriteBitmapFile(sim->dpy, "/tmp/a2font.xbm", text_pm, 64*7, 8, -1, -1);
544 sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0, 64*7, 8, ~0L, ZPixmap);
545 XFreeGC(sim->dpy, gc);
546 XFreePixmap(sim->dpy, text_pm);
548 for (i=0; a2_fixfont[i]; i++) {
549 XPutPixel(sim->text_im, a2_fixfont[i]&0x3ff,
550 (a2_fixfont[i]>>10)&0xf,
551 (a2_fixfont[i]>>15)&1);
558 apple2_start(Display *dpy, Window window, int delay,
559 void (*controller)(apple2_sim_t *sim,
561 double *next_actiontime))
565 sim=(apple2_sim_t *)calloc(1,sizeof(apple2_sim_t));
567 sim->window = window;
569 sim->controller = controller;
571 sim->st = (apple2_state_t *)calloc(1,sizeof(apple2_state_t));
572 sim->dec = analogtv_allocate(dpy, window);
573 sim->inp = analogtv_input_allocate();
575 sim->reception.input = sim->inp;
576 sim->reception.level = 1.0;
580 if (random()%4==0 && !sim->dec->use_cmap && sim->dec->use_color && sim->dec->visbits>=8) {
581 sim->dec->flutter_tint=1;
583 else if (random()%3==0) {
584 sim->dec->flutter_horiz_desync=1;
586 sim->typing_rate = 1.0;
588 analogtv_set_defaults(sim->dec, "");
589 sim->dec->squish_control=0.05;
590 analogtv_setup_sync(sim->inp, 1, 0);
596 a2_goto(sim->st,23,0);
598 if (random()%2==0) sim->basetime_tv.tv_sec -= 1; /* random blink phase */
599 sim->next_actiontime=0.0;
602 sim->next_actiontime=sim->curtime;
603 sim->controller (sim, &sim->stepno, &sim->next_actiontime);
605 # ifdef GETTIMEOFDAY_TWO_ARGS
606 gettimeofday(&sim->basetime_tv, NULL);
608 gettimeofday(&sim->basetime_tv);
615 apple2_one_frame (apple2_sim_t *sim)
621 if (sim->stepno==A2CONTROLLER_DONE)
622 goto DONE; /* when caller says we're done, be done, dammit! */
625 struct timeval curtime_tv;
626 # ifdef GETTIMEOFDAY_TWO_ARGS
628 gettimeofday(&curtime_tv, &tzp);
630 gettimeofday(&curtime_tv);
632 sim->curtime=(curtime_tv.tv_sec - sim->basetime_tv.tv_sec) +
633 0.000001*(curtime_tv.tv_usec - sim->basetime_tv.tv_usec);
634 if (sim->curtime > sim->dec->powerup)
635 sim->dec->powerup=sim->curtime;
638 blinkphase=sim->curtime/0.8;
640 /* The blinking rate was controlled by 555 timer with a resistor/capacitor
641 time constant. Because the capacitor was electrolytic, the flash rate
642 varied somewhat between machines. I'm guessing 1.6 seconds/cycle was
643 reasonable. (I soldered a resistor in mine to make it blink faster.) */
645 sim->st->blink=((int)blinkphase)&1;
646 if (sim->st->blink!=i && !(sim->st->gr_mode&A2_GR_FULL)) {
648 /* For every row with blinking text, set the changed flag. This basically
649 works great except with random screen garbage in text mode, when we
650 end up redrawing the whole screen every second */
652 for (row=(sim->st->gr_mode ? 20 : 0); row<24; row++) {
653 for (col=0; col<40; col++) {
654 int c=sim->st->textlines[row][col];
655 if ((c & 0xc0) == 0x40) {
666 if (sim->curtime >= sim->delay)
667 sim->stepno = A2CONTROLLER_DONE;
671 while (*sim->printing) {
672 if (*sim->printing=='\001') { /* pause */
676 else if (*sim->printing=='\n') {
677 a2_printc(sim->st,*sim->printing);
683 a2_printc(sim->st,*sim->printing);
687 if (!*sim->printing) sim->printing=NULL;
689 else if (sim->curtime >= sim->next_actiontime) {
693 /* If we're in the midst of typing a string, emit a character with
701 a2_printc(sim->st, c);
702 if (c=='\r' || c=='\n') {
703 sim->next_actiontime = sim->curtime;
706 sim->next_actiontime = sim->curtime + 0.1;
709 sim->next_actiontime = (sim->curtime +
710 (((random()%1000)*0.001 + 0.3) *
715 sim->next_actiontime = sim->curtime;
717 sim->controller (sim, &sim->stepno, &sim->next_actiontime);
719 if (sim->stepno==A2CONTROLLER_DONE) {
722 sim->stepno=A2CONTROLLER_FREE;
723 sim->controller (sim, &sim->stepno, &sim->next_actiontime);
724 /* if stepno is changed, return 1 */
725 if (sim->stepno != A2CONTROLLER_FREE)
728 XClearWindow(sim->dpy, sim->window);
731 /* This is from a2_make_font */
732 free(sim->text_im->data);
733 sim->text_im->data = 0;
734 XDestroyImage(sim->text_im);
737 analogtv_release(sim->dec);
749 analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0);
750 analogtv_setup_frame(sim->dec);
752 for (textrow=0; textrow<24; textrow++) {
754 for (row=textrow*8; row<textrow*8+8; row++) {
756 /* First we generate the pattern that the video circuitry shifts out
757 of memory. It has a 14.something MHz dot clock, equal to 4 times
758 the color burst frequency. So each group of 4 bits defines a color.
759 Each character position, or byte in hires, defines 14 dots, so odd
760 and even bytes have different color spaces. So, pattern[0..600]
761 gets the dots for one scan line. */
763 signed char *pp=&sim->inp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100];
765 if ((sim->st->gr_mode&A2_GR_HIRES) &&
766 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
769 /* Emulate the mysterious pink line, due to a bit getting
770 stuck in a shift register between the end of the last
771 row and the beginning of this one. */
772 if ((sim->st->hireslines[row][0] & 0x80) &&
773 (sim->st->hireslines[row][39]&0x40)) {
774 pp[-1]=ANALOGTV_WHITE_LEVEL;
777 for (col=0; col<40; col++) {
778 unsigned char b=sim->st->hireslines[row][col];
779 int shift=(b&0x80)?0:1;
781 /* Each of the low 7 bits in hires mode corresponded to 2 dot
782 clocks, shifted by one if the high bit was set. */
783 for (i=0; i<7; i++) {
784 pp[shift+1] = pp[shift] = (((b>>i)&1)
785 ?ANALOGTV_WHITE_LEVEL
786 :ANALOGTV_BLACK_LEVEL);
791 else if ((sim->st->gr_mode&A2_GR_LORES) &&
792 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
794 for (col=0; col<40; col++) {
795 unsigned char nib=((sim->st->textlines[textrow][col] >> (((row/4)&1)*4))
797 /* The low or high nybble was shifted out one bit at a time. */
798 for (i=0; i<14; i++) {
799 *pp = (((nib>>((col*14+i)&3))&1)
800 ?ANALOGTV_WHITE_LEVEL
801 :ANALOGTV_BLACK_LEVEL);
808 for (col=0; col<40; col++) {
810 int c=sim->st->textlines[textrow][col]&0xff;
811 /* hi bits control inverse/blink as follows:
816 rev=!(c&0x80) && (!(c&0x40) || sim->st->blink);
818 for (i=0; i<7; i++) {
819 unsigned long pix=XGetPixel(sim->text_im,
822 pp[1] = pp[2] = ((pix^rev)
823 ?ANALOGTV_WHITE_LEVEL
824 :ANALOGTV_BLACK_LEVEL);
831 analogtv_reception_update(&sim->reception);
833 const analogtv_reception *rec = &sim->reception;
834 analogtv_draw(sim->dec, 0.02, &rec, 1);
843 a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime)
849 a2_invalidate(sim->st);
851 For testing color rendering. The spec is:
859 6 med blue 20 207 253
860 7 lt blue 208 195 255
865 12 lt green 20 245 60
866 13 yellow 208 221 141
870 sim->st->gr_mode=A2_GR_LORES;
871 for (row=0; row<24; row++) {
872 for (col=0; col<40; col++) {
873 sim->st->textlines[row][col]=(row&15)*17;
876 *next_actiontime+=0.4;
881 if (sim->curtime > 10) *stepno=-1;