|  | /* | 
|  | * $Id: display.c,v 1.57 2004/02/04 16:59:01 phil Exp $ | 
|  | * Simulator and host O/S independent XY display simulator | 
|  | * Phil Budne <phil@ultimate.com> | 
|  | * September 2003 | 
|  | * | 
|  | * with changes by Douglas A. Gwyn, 21 Jan. 2004 | 
|  | * | 
|  | * started from PDP-8/E simulator vc8e.c; | 
|  | *	This PDP8 Emulator was written by Douglas W. Jones at the | 
|  | *	University of Iowa.  It is distributed as freeware, of | 
|  | *	uncertain function and uncertain utility. | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2003-2004, Philip L. Budne | 
|  | * | 
|  | * Permission is hereby granted, free of charge, to any person obtaining a | 
|  | * copy of this software and associated documentation files (the "Software"), | 
|  | * to deal in the Software without restriction, including without limitation | 
|  | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | 
|  | * and/or sell copies of the Software, and to permit persons to whom the | 
|  | * Software is furnished to do so, subject to the following conditions: | 
|  | * | 
|  | * The above copyright notice and this permission notice shall be included in | 
|  | * all copies or substantial portions of the Software. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL | 
|  | * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | 
|  | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | 
|  | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | 
|  | * | 
|  | * Except as contained in this notice, the names of the authors shall | 
|  | * not be used in advertising or otherwise to promote the sale, use or | 
|  | * other dealings in this Software without prior written authorization | 
|  | * from the authors. | 
|  | */ | 
|  |  | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <math.h> | 
|  | #include <limits.h>			/* for USHRT_MAX */ | 
|  | #include "ws.h" | 
|  | #include "display.h" | 
|  |  | 
|  | /* | 
|  | * The user may select (at compile time) how big a window is used to | 
|  | * emulate the display.  Using smaller windows saves memory and screen space. | 
|  | * | 
|  | * Type 30 has 1024x1024 addressing, but only 512x512 visible points. | 
|  | * VR14 has only 1024x768 visible points; VR17 has 1024x1024 visible points. | 
|  | * VT11 supports 4096x4096 addressing, clipping to the lowest 1024x1024 region. | 
|  | * VR48 has 1024x1024 visible points in the main display area and 128x1024 | 
|  | * visible points in a menu area on the right-hand side (1152x1024 total). | 
|  | * VT48 supports 8192x8192 (signed) main-area addressing, clipping to a | 
|  | * 1024x1024 window which can be located anywhere within that region. | 
|  | * (XXX -- That is what the VT11/VT48 manuals say; however, evidence suggests | 
|  | * that the VT11 may actually support 8192x8192 (signed) addressing too.) | 
|  | */ | 
|  |  | 
|  | /* Define the default display type (if display_init() not called) */ | 
|  | #ifndef DISPLAY_TYPE | 
|  | #define DISPLAY_TYPE DIS_TYPE30 | 
|  | #endif /* DISPLAY_TYPE not defined */ | 
|  |  | 
|  | /* select a default resolution if display_init() not called */ | 
|  | /* XXX keep in struct display? */ | 
|  | #ifndef PIX_SCALE | 
|  | #define PIX_SCALE RES_HALF | 
|  | #endif /* PIX_SCALE not defined */ | 
|  |  | 
|  | /* select a default light-pen hit radius if display_init() not called */ | 
|  | #ifndef PEN_RADIUS | 
|  | #define PEN_RADIUS 4 | 
|  | #endif /* PEN_RADIUS not defined */ | 
|  |  | 
|  | /* | 
|  | * note: displays can have up to two different colors (eg VR20) | 
|  | * each color can be made up of any number of phosphors | 
|  | * with different colors and decay characteristics (eg Type 30) | 
|  | */ | 
|  |  | 
|  | #define ELEMENTS(X) (sizeof(X)/sizeof(X[0])) | 
|  |  | 
|  | struct phosphor { | 
|  | double red, green, blue; | 
|  | double level;			/* decay level (0.5 for half life) */ | 
|  | double t_level;			/* seconds to decay to level */ | 
|  | }; | 
|  |  | 
|  | struct color { | 
|  | struct phosphor *phosphors; | 
|  | int nphosphors; | 
|  | int half_life;			/* for refresh calc */ | 
|  | }; | 
|  |  | 
|  | struct display { | 
|  | enum display_type type; | 
|  | char *name; | 
|  | struct color *color0, *color1; | 
|  | short xpoints, ypoints; | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * original phosphor constants from Raphael Nabet's XMame 0.72.1 PDP-1 sim. | 
|  | * not even sure Type30 really used P17 (guess by Daniel P. B. Smith) | 
|  | */ | 
|  | static struct phosphor p17[] = { | 
|  | {0.11, 0.11, 1.0,  0.5, 0.05},	/* fast blue */ | 
|  | {1.0,  1.0,  0.11, 0.5, 0.20}	/* slow yellow/green */ | 
|  | }; | 
|  | static struct color color_p17 = { p17, ELEMENTS(p17), 125000 }; | 
|  |  | 
|  | /* green phosphor for VR14, VR17, VR20 */ | 
|  | static struct phosphor p29[] = {{0.0260, 1.0, 0.00121, 0.5, 0.025}}; | 
|  | struct color color_p29 = { p29, ELEMENTS(p29), 25000 }; | 
|  |  | 
|  | static struct phosphor p40[] = { | 
|  | /* P40 blue-white spot with yellow-green decay (.045s to 10%?) */ | 
|  | {0.4, 0.2, 0.924, 0.5, 0.0135}, | 
|  | {0.5, 0.7, 0.076, 0.5, 0.065} | 
|  | }; | 
|  | static struct color color_p40 = { p40, ELEMENTS(p40), 20000 }; | 
|  |  | 
|  | /* "red" -- until real VR20 phosphor type/number/constants known */ | 
|  | static struct phosphor pred[] = { {1.0, 0.37, 0.37, 0.5, 0.10} }; | 
|  | static struct color color_red = { pred, ELEMENTS(pred), 100000 }; | 
|  |  | 
|  | static struct display displays[] = { | 
|  | /* | 
|  | * TX-0 | 
|  | * | 
|  | * | 
|  | * Unknown manufacturer | 
|  | * | 
|  | * 12" tube, | 
|  | * maximum dot size ??? | 
|  | * 50us point plot time (20,000 points/sec) | 
|  | * P17 Phosphor??? Two phosphor layers: | 
|  | * fast blue (.05s half life), and slow green (.2s half life) | 
|  | * | 
|  | * | 
|  | */ | 
|  | { DIS_TX0, "MIT TX-0", &color_p17, NULL, 512, 512 }, | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Type 30 | 
|  | * PDP-1/4/5/8/9/10 "Precision CRT" display system | 
|  | * | 
|  | * Raytheon 16ADP7A CRT? | 
|  | * web searches for 16ADP7 finds useful information!! | 
|  | * 16" tube, 14 3/8" square raster | 
|  | * maximum dot size .015" | 
|  | * 50us point plot time (20,000 points/sec) | 
|  | * P17 Phosphor??? Two phosphor layers: | 
|  | * fast blue (.05s half life), and slow green (.2s half life) | 
|  | * 360 lb | 
|  | * 7A at 115+-10V 60Hz | 
|  | */ | 
|  | { DIS_TYPE30, "Type 30", &color_p17, NULL, 1024, 1024 }, | 
|  |  | 
|  | /* | 
|  | * VR14 | 
|  | * used w/ GT40/44, AX08, VC8E | 
|  | * | 
|  | * Viewable area 6.75" x 9" | 
|  | * 12" diagonal | 
|  | * brightness >= 30 fL | 
|  | * dot size .02" (20 mils) | 
|  | * settle time: | 
|  | *	full screen 18us to +/-1 spot diameter | 
|  | *	.1" change 1us to +/-.5 spot diameter | 
|  | * weight 75lb | 
|  | */ | 
|  | { DIS_VR14, "VR14", &color_p29, NULL, 1024, 768 }, | 
|  |  | 
|  | /* | 
|  | * VR17 | 
|  | * used w/ GT40/44, AX08, VC8E | 
|  | * | 
|  | * Viewable area 9.25" x 9.25" | 
|  | * 17" diagonal | 
|  | * dot size .02" (20 mils) | 
|  | * brightness >= 25 fL | 
|  | * phosphor: P39 doped for IR light pen use | 
|  | * light pen: Type 375 | 
|  | * weight 85lb | 
|  | */ | 
|  | { DIS_VR17, "VR17", &color_p29, NULL, 1024, 1024 }, | 
|  |  | 
|  | /* | 
|  | * VR20 | 
|  | * on VC8E | 
|  | * Two colors!! | 
|  | */ | 
|  | { DIS_VR20, "VR20", &color_p29, &color_red, 1024, 1024 }, | 
|  |  | 
|  | /* | 
|  | * VR48 | 
|  | * (on VT48 in VS60) | 
|  | * from Douglas A. Gwyn 23 Nov. 2003 | 
|  | * | 
|  | * Viewable area 12" x 12", plus 1.5" x 12" menu area on right-hand side | 
|  | * 21" diagonal | 
|  | * dot size <= .01" (10 mils) | 
|  | * brightness >= 31 fL | 
|  | * phosphor: P40 (blue-white fluorescence with yellow-green phosphorescence) | 
|  | * light pen: Type 377A (with tip switch) | 
|  | * driving circuitry separate | 
|  | * (normally under table on which CRT is mounted) | 
|  | */ | 
|  | { DIS_VR48, "VR48", &color_p40, NULL, 1024+VR48_GUTTER+128, 1024 }, | 
|  |  | 
|  | /* | 
|  | * Type 340 Display system | 
|  | * on PDP-4/6/7/9/10 | 
|  | * | 
|  | * 1024x1024 | 
|  | * 9 3/8" raster (.01" dot pitch) | 
|  | * 0,0 at lower left | 
|  | * 8 intensity levels | 
|  | */ | 
|  | { DIS_TYPE340, "Type 340", &color_p17, NULL, 1024, 1024 } | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * Unit time (in microseconds) used to store display point time to | 
|  | * live at current aging level.  If this is too small, delay values | 
|  | * cannot fit in an unsigned short.  If it is too large all pixels | 
|  | * will age at once.  Perhaps a suitable value should be calculated at | 
|  | * run time?  When display_init() calculates refresh_interval it | 
|  | * sanity checks for both cases. | 
|  | */ | 
|  | #define DELAY_UNIT 250 | 
|  |  | 
|  | /* levels to display in first half-life; determines refresh rate */ | 
|  | #ifndef LEVELS_PER_HALFLIFE | 
|  | #define LEVELS_PER_HALFLIFE 4 | 
|  | #endif | 
|  |  | 
|  | /* after 5 half lives (.5**5) remaining intensity is 3% of original */ | 
|  | #ifndef HALF_LIVES_TO_DISPLAY | 
|  | #define HALF_LIVES_TO_DISPLAY 5 | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * refresh_rate is number of times per (simulated) second a pixel is | 
|  | * aged to next lowest intensity level. | 
|  | * | 
|  | * refresh_rate = ((1e6*LEVELS_PER_HALFLIFE)/PHOSPHOR_HALF_LIFE) | 
|  | * refresh_interval = 1e6/DELAY_UNIT/refresh_rate | 
|  | *		    = PHOSPHOR_HALF_LIFE/LEVELS_PER_HALF_LIFE | 
|  | * intensities = (HALF_LIVES_TO_DISPLAY*PHOSPHOR_HALF_LIFE)/refresh_interval | 
|  | *	       = HALF_LIVES_TO_DISPLAY*LEVELS_PER_HALFLIFE | 
|  | * | 
|  | * See also comments on display_age() | 
|  | * | 
|  | * Try to keep LEVELS_PER_HALFLIFE*HALF_LIVES_TO_DISPLAY*NLEVELS <= 192 | 
|  | * to run on 8-bit (256 color) displays! | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * number of aging periods to display a point for | 
|  | */ | 
|  | #define NTTL (HALF_LIVES_TO_DISPLAY*LEVELS_PER_HALFLIFE) | 
|  |  | 
|  | /* | 
|  | * maximum (initial) TTL for a point. | 
|  | * TTL's are stored 1-based | 
|  | * (a stored TTL of zero means the point is off) | 
|  | */ | 
|  | #define MAXTTL NTTL | 
|  |  | 
|  | /* | 
|  | * number of drawing intensity levels | 
|  | */ | 
|  | #define NLEVELS (DISPLAY_INT_MAX-DISPLAY_INT_MIN+1) | 
|  |  | 
|  | #define MAXLEVEL (NLEVELS-1) | 
|  |  | 
|  | /* | 
|  | * Display Device Implementation | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Each point on the display is represented by a "struct point".  When | 
|  | * a point isn't dark (intensity > 0), it is linked into a circular, | 
|  | * doubly linked delta queue (a priority queue where "delay" | 
|  | * represents the time difference from the previous entry (if any) in | 
|  | * the queue. | 
|  | * | 
|  | * All points are aged refresh_rate times/second, each time moved to the | 
|  | * next (logarithmically) lower intensity level.  When display_age() is | 
|  | * called, only the entries which have expired are processed.  Calling | 
|  | * display_age() often allows spreading out the workload. | 
|  | * | 
|  | * An alternative would be to have intensity levels represent linear | 
|  | * decreases in intensity, and have the decay time at each level change. | 
|  | * Inverting the decay function for a multi-component phosphor may be | 
|  | * tricky, and the two different colors would need different time tables. | 
|  | * Furthermore, it would require finding the correct location in the | 
|  | * queue when adding a point (currently only need to add points at end) | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * 12 bytes/entry on 32-bit system when REFRESH_RATE > 15 | 
|  | * (requires 3MB for 512x512 display). | 
|  | */ | 
|  |  | 
|  | typedef unsigned short delay_t; | 
|  | #define DELAY_T_MAX USHRT_MAX | 
|  |  | 
|  | struct point { | 
|  | struct point *next;			/* next entry in queue */ | 
|  | struct point *prev;			/* prev entry in queue */ | 
|  | delay_t delay;			/* delta T in DELAY_UNITs */ | 
|  | unsigned char ttl;			/* zero means off, not linked in */ | 
|  | unsigned char level : 7;		/* intensity level */ | 
|  | unsigned char color : 1;		/* for VR20 (two colors) */ | 
|  | }; | 
|  |  | 
|  | static struct point *points;		/* allocated array of points */ | 
|  | static struct point _head; | 
|  | #define head (&_head) | 
|  |  | 
|  | /* | 
|  | * time span of all entries in queue | 
|  | * should never exceed refresh_interval | 
|  | * (should be possible to make this a delay_t) | 
|  | */ | 
|  | static long queue_interval; | 
|  |  | 
|  | /* convert X,Y to a "struct point *" */ | 
|  | #define P(X,Y) (points + (X) + ((Y)*(size_t)xpixels)) | 
|  |  | 
|  | /* convert "struct point *" to X and Y */ | 
|  | #define X(P) (((P) - points) % xpixels) | 
|  | #define Y(P) (((P) - points) / xpixels) | 
|  |  | 
|  | static int initialized = 0; | 
|  |  | 
|  | /* | 
|  | * global set by O/S display level to indicate "light pen tip switch activated" | 
|  | * (This is used only by the VS60 emulation, also by vttest to change patterns) | 
|  | */ | 
|  | unsigned char display_lp_sw = 0; | 
|  |  | 
|  | /* | 
|  | * global set by DR11-C simulation when DR device enabled; deactivates | 
|  | * light pen and instead reports mouse coordinates as Talos digitizer | 
|  | * data via DR11-C | 
|  | */ | 
|  | unsigned char display_tablet = 0; | 
|  |  | 
|  | /* | 
|  | * can be changed with display_lp_radius() | 
|  | */ | 
|  | static long scaled_pen_radius_squared; | 
|  |  | 
|  | /* run-time -- set by display_init() */ | 
|  | static int xpoints, ypoints; | 
|  | static int xpixels, ypixels; | 
|  | static int refresh_rate; | 
|  | static int refresh_interval; | 
|  | static int ncolors; | 
|  | static enum display_type display_type; | 
|  | static int scale; | 
|  |  | 
|  | /* | 
|  | * relative brightness for each display level | 
|  | * (all but last must be less than 1.0) | 
|  | */ | 
|  | static float level_scale[NLEVELS]; | 
|  |  | 
|  | /* | 
|  | * table of pointer to window system "colors" | 
|  | * for painting each age level, intensity level and beam color | 
|  | */ | 
|  | void *colors[2][NLEVELS][NTTL]; | 
|  |  | 
|  | void | 
|  | display_lp_radius(int r) | 
|  | { | 
|  | r /= scale; | 
|  | scaled_pen_radius_squared = r * r; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * from display_age and display_point | 
|  | * since all points age at the same rate, | 
|  | * only adds points at end of list. | 
|  | */ | 
|  | static void | 
|  | queue_point(struct point *p) | 
|  | { | 
|  | int d; | 
|  |  | 
|  | d = refresh_interval - queue_interval; | 
|  | queue_interval += d; | 
|  | /* queue_interval should now be == refresh_interval */ | 
|  |  | 
|  | #ifdef PARANOIA | 
|  | if (p->ttl == 0 || p->ttl > MAXTTL) | 
|  | printf("queuing %d,%d level %d!\n", X(p), Y(p), p->level); | 
|  | if (d > DELAY_T_MAX) | 
|  | printf("queuing %d,%d delay %d!\n", X(p), Y(p), d); | 
|  | if (queue_interval > DELAY_T_MAX) | 
|  | printf("queue_interval (%d) > DELAY_T_MAX (%d)\n", | 
|  | (int)queue_interval, DELAY_T_MAX); | 
|  | #endif /* PARANOIA defined */ | 
|  |  | 
|  | p->next = head; | 
|  | p->prev = head->prev; | 
|  |  | 
|  | head->prev->next = p; | 
|  | head->prev = p; | 
|  |  | 
|  | p->delay = d; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * here to to dynamically adjust interval for examination | 
|  | * of elapsed vs. simulated time, and fritter away | 
|  | * any extra wall-clock time without eating CPU | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * more parameters! | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * upper bound for elapsed time between elapsed time checks. | 
|  | * if more than MAXELAPSED microseconds elapsed while simulating | 
|  | * delay_check simulated microseconds, decrease delay_check. | 
|  | */ | 
|  | #define MAXELAPSED 100000		/* 10Hz */ | 
|  |  | 
|  | /* | 
|  | * lower bound for elapsed time between elapsed time checks. | 
|  | * if fewer than MINELAPSED microseconds elapsed while simulating | 
|  | * delay_check simulated microseconds, increase delay_check. | 
|  | */ | 
|  | #define MINELAPSED 50000		/* 20Hz */ | 
|  |  | 
|  | /* | 
|  | * upper bound for delay (sleep/poll). | 
|  | * If difference between elapsed time and simulated time is | 
|  | * larger than MAXDELAY microseconds, decrease delay_check. | 
|  | * | 
|  | * since delay is elapsed time - simulated time, MAXDELAY | 
|  | * should be <= MAXELAPSED | 
|  | */ | 
|  | #ifndef MAXDELAY | 
|  | #define MAXDELAY 100000			/* 100ms */ | 
|  | #endif /* MAXDELAY not defined */ | 
|  |  | 
|  | /* | 
|  | * lower bound for delay (sleep/poll). | 
|  | * If difference between elapsed time and simulated time is | 
|  | * smaller than MINDELAY microseconds, increase delay_check. | 
|  | * | 
|  | * since delay is elapsed time - simulated time, MINDELAY | 
|  | * should be <= MINELAPSED | 
|  | */ | 
|  | #ifndef MINDELAY | 
|  | #define MINDELAY 50000			/* 50ms */ | 
|  | #endif /* MINDELAY not defined */ | 
|  |  | 
|  | /* | 
|  | * Initial amount of simulated time to elapse before polling. | 
|  | * Value is very low to ensure polling occurs on slow systems. | 
|  | * Fast systems should ramp up quickly. | 
|  | */ | 
|  | #ifndef INITIAL_DELAY_CHECK | 
|  | #define INITIAL_DELAY_CHECK 1000	/* 1ms */ | 
|  | #endif /* INITIAL_DELAY_CHECK */ | 
|  |  | 
|  | /* | 
|  | * gain factor (2**-GAINSHIFT) for adjustment of adjustment | 
|  | * of delay_check | 
|  | */ | 
|  | #ifndef GAINSHIFT | 
|  | #define GAINSHIFT 3			/* gain=0.125 (12.5%) */ | 
|  | #endif /* GAINSHIFT not defined */ | 
|  |  | 
|  | static void | 
|  | display_delay(int t, int slowdown) | 
|  | { | 
|  | /* how often (in simulated us) to poll/check for delay */ | 
|  | static unsigned long delay_check = INITIAL_DELAY_CHECK; | 
|  |  | 
|  | /* accumulated simulated time */ | 
|  | static unsigned long sim_time = 0; | 
|  | unsigned long elapsed; | 
|  | long delay; | 
|  |  | 
|  | sim_time += t; | 
|  | if (sim_time < delay_check) | 
|  | return; | 
|  |  | 
|  | elapsed = os_elapsed();		/* read and reset elapsed timer */ | 
|  | if (elapsed == ~0L) {		/* first time thru? */ | 
|  | slowdown = 0;			/* no adjustments */ | 
|  | elapsed = sim_time; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * get delta between elapsed (real) time, and simulated time. | 
|  | * if simulated time running faster, we need to slow things down (delay) | 
|  | */ | 
|  | if (slowdown) | 
|  | delay = sim_time - elapsed; | 
|  | else | 
|  | delay = 0;			/* just poll */ | 
|  |  | 
|  | #ifdef DEBUG_DELAY2 | 
|  | printf("sim %d elapsed %d delay %d\r\n", sim_time, elapsed, delay); | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * Try to keep the elapsed (real world) time between checks for | 
|  | * delay (and poll for window system events) bounded between | 
|  | * MAXELAPSED and MINELAPSED.  Also tries to keep | 
|  | * delay/poll time bounded between MAXDELAY and MINDELAY -- large | 
|  | * delays make the simulation spastic, while very small ones are | 
|  | * inefficient (too many system calls) and tend to be inaccurate | 
|  | * (operating systems have a certain granularity for time | 
|  | * measurement, and when you try to sleep/poll for very short | 
|  | * amounts of time, the noise will dominate). | 
|  | * | 
|  | * delay_check period may be adjusted often, and oscillate.  There | 
|  | * is no single "right value", the important things are to keep | 
|  | * the delay time and max poll intervals bounded, and responsive | 
|  | * to system load. | 
|  | */ | 
|  | if (elapsed > MAXELAPSED || delay > MAXDELAY) { | 
|  | /* too much elapsed time passed, or delay too large; shrink interval */ | 
|  | if (delay_check > 1) { | 
|  | delay_check -= delay_check>>GAINSHIFT; | 
|  | #ifdef DEBUG_DELAY | 
|  | printf("reduced period to %d\r\n", delay_check); | 
|  | #endif /* DEBUG_DELAY defined */ | 
|  | } | 
|  | } | 
|  | else if ((elapsed < MINELAPSED) || (slowdown && (delay < MINDELAY))) { | 
|  | /* too little elapsed time passed, or delta very small */ | 
|  | int gain = delay_check>>GAINSHIFT; | 
|  | if (gain == 0) | 
|  | gain = 1;			/* make sure some change made! */ | 
|  | delay_check += gain; | 
|  | #ifdef DEBUG_DELAY | 
|  | printf("increased period to %d\r\n", delay_check); | 
|  | #endif /* DEBUG_DELAY defined */ | 
|  | } | 
|  | if (delay < 0) | 
|  | delay = 0; | 
|  | /* else if delay < MINDELAY, clamp at MINDELAY??? */ | 
|  |  | 
|  | /* poll for window system events and/or delay */ | 
|  | ws_poll(NULL, delay); | 
|  |  | 
|  | sim_time = 0;			/* reset simulated time clock */ | 
|  |  | 
|  | /* | 
|  | * delay (poll/sleep) time included in next "elapsed" period | 
|  | * (clock not reset after a delay) | 
|  | */ | 
|  | } /* display_delay */ | 
|  |  | 
|  | /* | 
|  | * here periodically from simulator to age pixels. | 
|  | * | 
|  | * calling often with small values will age a few pixels at a time, | 
|  | * and assist with graceful aging of display, and pixel aging. | 
|  | * | 
|  | * values should be smaller than refresh_interval! | 
|  | * | 
|  | * returns true if anything on screen changed. | 
|  | */ | 
|  |  | 
|  | int | 
|  | display_age(int t,			/* simulated us since last call */ | 
|  | int slowdown)		/* slowdown to simulated speed */ | 
|  | { | 
|  | struct point *p; | 
|  | static int elapsed = 0; | 
|  | int changed; | 
|  |  | 
|  | if (!initialized && !display_init(DISPLAY_TYPE, PIX_SCALE)) | 
|  | return 0; | 
|  |  | 
|  | display_delay(t, slowdown); | 
|  |  | 
|  | changed = 0; | 
|  |  | 
|  | elapsed += t; | 
|  | if (elapsed < DELAY_UNIT) | 
|  | return 0; | 
|  |  | 
|  | t = elapsed / DELAY_UNIT; | 
|  | elapsed %= DELAY_UNIT; | 
|  |  | 
|  | while ((p = head->next) != head) { | 
|  | int x, y; | 
|  |  | 
|  | /* look at oldest entry */ | 
|  | if (p->delay > t) {		/* further than our reach? */ | 
|  | p->delay -= t;		/* update head */ | 
|  | queue_interval -= t;	/* update span */ | 
|  | break;			/* quit */ | 
|  | } | 
|  |  | 
|  | x = X(p); | 
|  | y = Y(p); | 
|  | #ifdef PARANOIA | 
|  | if (p->ttl == 0) | 
|  | printf("BUG: age %d,%d ttl zero\n", x, y); | 
|  | #endif /* PARANOIA defined */ | 
|  |  | 
|  | /* dequeue point */ | 
|  | p->prev->next = p->next; | 
|  | p->next->prev = p->prev; | 
|  |  | 
|  | t -= p->delay;			/* lessen our reach */ | 
|  | queue_interval -= p->delay;	/* update queue span */ | 
|  |  | 
|  | ws_display_point(x, y, colors[p->color][p->level][--p->ttl]); | 
|  | changed = 1; | 
|  |  | 
|  | /* queue it back up, unless we just turned it off! */ | 
|  | if (p->ttl > 0) | 
|  | queue_point(p); | 
|  | } | 
|  | return changed; | 
|  | } /* display_age */ | 
|  |  | 
|  | /* here from window system */ | 
|  | void | 
|  | display_repaint(void) { | 
|  | struct point *p; | 
|  | int x, y; | 
|  | /* | 
|  | * bottom to top, left to right. | 
|  | */ | 
|  | for (p = points, y = 0; y < ypixels; y++) | 
|  | for (x = 0; x < xpixels; p++, x++) | 
|  | if (p->ttl) | 
|  | ws_display_point(x, y, colors[p->color][p->level][p->ttl-1]); | 
|  | ws_sync(); | 
|  | } | 
|  |  | 
|  | /* (0,0) is lower left */ | 
|  | static int | 
|  | intensify(int x,			/* 0..xpixels */ | 
|  | int y,			/* 0..ypixels */ | 
|  | int level,			/* 0..MAXLEVEL */ | 
|  | int color)			/* for VR20! 0 or 1 */ | 
|  | { | 
|  | struct point *p; | 
|  | int bleed; | 
|  |  | 
|  | if (x < 0 || x >= xpixels || y < 0 || y >= ypixels) | 
|  | return 0;			/* limit to display */ | 
|  |  | 
|  | p = P(x,y); | 
|  | if (p->ttl) {			/* currently lit? */ | 
|  | #ifdef LOUD | 
|  | printf("%d,%d old level %d ttl %d new %d\r\n", | 
|  | x, y, p->level, p->ttl, level); | 
|  | #endif /* LOUD defined */ | 
|  |  | 
|  | /* unlink from delta queue */ | 
|  | p->prev->next = p->next; | 
|  |  | 
|  | if (p->next == head) | 
|  | queue_interval -= p->delay; | 
|  | else | 
|  | p->next->delay += p->delay; | 
|  | p->next->prev = p->prev; | 
|  | } | 
|  |  | 
|  | bleed = 0;				/* no bleeding for now */ | 
|  |  | 
|  | /* EXP: doesn't work... yet */ | 
|  | /* if "recently" drawn, same or brighter, same color, make even brighter */ | 
|  | if (p->ttl >= MAXTTL*2/3 && level >= p->level && p->color == color && | 
|  | level < MAXLEVEL) | 
|  | level++; | 
|  |  | 
|  | /* | 
|  | * this allows a dim beam to suck light out of | 
|  | * a recently drawn bright spot!! | 
|  | */ | 
|  | if (p->ttl != MAXTTL || p->level != level || p->color != color) { | 
|  | p->ttl = MAXTTL; | 
|  | p->level = level; | 
|  | p->color = color;		/* save color even if monochrome */ | 
|  | ws_display_point(x, y, colors[p->color][p->level][p->ttl-1]); | 
|  | } | 
|  |  | 
|  | queue_point(p);			/* put at end of list */ | 
|  | return bleed; | 
|  | } | 
|  |  | 
|  | int | 
|  | display_point(int x,			/* 0..xpixels (unscaled) */ | 
|  | int y,			/* 0..ypixels (unscaled) */ | 
|  | int level,		/* DISPLAY_INT_xxx */ | 
|  | int color)		/* for VR20! 0 or 1 */ | 
|  | { | 
|  | long lx, ly; | 
|  |  | 
|  | if (!initialized && !display_init(DISPLAY_TYPE, PIX_SCALE)) | 
|  | return 0; | 
|  |  | 
|  | /* scale x and y to the displayed number of pixels */ | 
|  | /* handle common cases quickly */ | 
|  | if (scale > 1) { | 
|  | if (scale == 2) { | 
|  | x >>= 1; | 
|  | y >>= 1; | 
|  | } | 
|  | else { | 
|  | x /= scale; | 
|  | y /= scale; | 
|  | } | 
|  | } | 
|  |  | 
|  | #if DISPLAY_INT_MIN > 0 | 
|  | level -= DISPLAY_INT_MIN;		/* make zero based */ | 
|  | #endif | 
|  | intensify(x, y, level, color); | 
|  | /* no bleeding for now (used to recurse for neighbor points) */ | 
|  |  | 
|  | if (ws_lp_x == -1 || ws_lp_y == -1) | 
|  | return 0; | 
|  |  | 
|  | lx = x - ws_lp_x; | 
|  | ly = y - ws_lp_y; | 
|  | return lx*lx + ly*ly <= scaled_pen_radius_squared; | 
|  | } /* display_point */ | 
|  |  | 
|  | /* | 
|  | * calculate decay color table for a phosphor mixture | 
|  | * must be called AFTER refresh_rate initialized! | 
|  | */ | 
|  | static void | 
|  | phosphor_init(struct phosphor *phosphors, int nphosphors, int color) | 
|  | { | 
|  | int ttl; | 
|  |  | 
|  | /* for each display ttl level; newest to oldest */ | 
|  | for (ttl = NTTL-1; ttl > 0; ttl--) { | 
|  | struct phosphor *pp; | 
|  | double rr, rg, rb;		/* real values */ | 
|  |  | 
|  | /* fractional seconds */ | 
|  | double t = ((double)(NTTL-1-ttl))/refresh_rate; | 
|  |  | 
|  | int ilevel;			/* intensity levels */ | 
|  | int p; | 
|  |  | 
|  | /* sum over all phosphors in mixture */ | 
|  | rr = rg = rb = 0.0; | 
|  | for (pp = phosphors, p = 0; p < nphosphors; pp++, p++) { | 
|  | double decay = pow(pp->level, t/pp->t_level); | 
|  | rr += decay * pp->red; | 
|  | rg += decay * pp->green; | 
|  | rb += decay * pp->blue; | 
|  | } | 
|  |  | 
|  | /* scale for brightness for each intensity level */ | 
|  | for (ilevel = MAXLEVEL; ilevel >= 0; ilevel--) { | 
|  | int r, g, b; | 
|  | void *cp; | 
|  |  | 
|  | /* | 
|  | * convert to 16-bit integer; clamp at 16 bits. | 
|  | * this allows the sum of brightness factors across phosphors | 
|  | * for each of R G and B to be greater than 1.0 | 
|  | */ | 
|  |  | 
|  | r = (int)(rr * level_scale[ilevel] * 0xffff); | 
|  | if (r > 0xffff) r = 0xffff; | 
|  |  | 
|  | g = (int)(rg * level_scale[ilevel] * 0xffff); | 
|  | if (g > 0xffff) g = 0xffff; | 
|  |  | 
|  | b = (int)(rb * level_scale[ilevel] * 0xffff); | 
|  | if (b > 0xffff) b = 0xffff; | 
|  |  | 
|  | cp = ws_color_rgb(r, g, b); | 
|  | if (!cp) {				/* allocation failed? */ | 
|  | if (ttl == MAXTTL-1) {		/* brand new */ | 
|  | if (ilevel == MAXLEVEL)	/* highest intensity? */ | 
|  | cp = ws_color_white();	/* use white */ | 
|  | else | 
|  | cp = colors[color][ilevel+1][ttl]; /* use next lvl */ | 
|  | } /* brand new */ | 
|  | else if (r + g + b >= 0xffff*3/3) /* light-ish? */ | 
|  | cp = colors[color][ilevel][ttl+1]; /* use previous TTL */ | 
|  | else | 
|  | cp = ws_color_black(); | 
|  | } | 
|  | colors[color][ilevel][ttl] = cp; | 
|  | } /* for each intensity level */ | 
|  | } /* for each TTL */ | 
|  | } /* phosphor_init */ | 
|  |  | 
|  | static struct display * | 
|  | find_type(enum display_type type) | 
|  | { | 
|  | int i; | 
|  | struct display *dp; | 
|  | for (i = 0, dp = displays; i < ELEMENTS(displays); i++, dp++) | 
|  | if (dp->type == type) | 
|  | return dp; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | int | 
|  | display_init(enum display_type type, int sf) | 
|  | { | 
|  | static int init_failed = 0; | 
|  | struct display *dp; | 
|  | int half_life; | 
|  | int i; | 
|  |  | 
|  | if (initialized) { | 
|  | /* cannot change type once started */ | 
|  | /* XXX say something???? */ | 
|  | return type == display_type; | 
|  | } | 
|  |  | 
|  | if (init_failed) | 
|  | return 0;			/* avoid thrashing */ | 
|  |  | 
|  | init_failed = 1;			/* assume the worst */ | 
|  | dp = find_type(type); | 
|  | if (!dp) { | 
|  | fprintf(stderr, "Unknown display type %d\r\n", (int)type); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | /* Initialize display list */ | 
|  | head->next = head->prev = head; | 
|  |  | 
|  | display_type = type; | 
|  | scale = sf; | 
|  |  | 
|  | xpoints = dp->xpoints; | 
|  | ypoints = dp->ypoints; | 
|  |  | 
|  | /* increase scale factor if won't fit on desktop? */ | 
|  | xpixels = xpoints / scale; | 
|  | ypixels = ypoints / scale; | 
|  |  | 
|  | /* set default pen radius now that scale is set */ | 
|  | display_lp_radius(PEN_RADIUS); | 
|  |  | 
|  | ncolors = 1; | 
|  | /* | 
|  | * use function to calculate from looking at avg (max?) | 
|  | * of phosphor half lives??? | 
|  | */ | 
|  | #define COLOR_HALF_LIFE(C) ((C)->half_life) | 
|  |  | 
|  | half_life = COLOR_HALF_LIFE(dp->color0); | 
|  | if (dp->color1) { | 
|  | if (dp->color1->half_life > half_life) | 
|  | half_life = COLOR_HALF_LIFE(dp->color1); | 
|  | ncolors++; | 
|  | } | 
|  |  | 
|  | /* before phosphor_init; */ | 
|  | refresh_rate = (1000000*LEVELS_PER_HALFLIFE)/half_life; | 
|  | refresh_interval = 1000000/DELAY_UNIT/refresh_rate; | 
|  |  | 
|  | /* | 
|  | * sanity check refresh_interval | 
|  | * calculating/selecting DELAY_UNIT at runtime might avoid this! | 
|  | */ | 
|  |  | 
|  | /* must be non-zero; interval of 1 means all pixels will age at once! */ | 
|  | if (refresh_interval < 1) { | 
|  | /* decrease DELAY_UNIT? */ | 
|  | fprintf(stderr, "NOTE! refresh_interval too small: %d\r\n", | 
|  | refresh_interval); | 
|  |  | 
|  | /* dunno if this is a good idea, but might be better than dying */ | 
|  | refresh_interval = 1; | 
|  | } | 
|  |  | 
|  | /* point lifetime in DELAY_UNITs will not fit in p->delay field! */ | 
|  | if (refresh_interval > DELAY_T_MAX) { | 
|  | /* increase DELAY_UNIT? */ | 
|  | fprintf(stderr, "bad refresh_interval %d > DELAY_T_MAX %d\r\n", | 
|  | refresh_interval, DELAY_T_MAX); | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * before phosphor_init; | 
|  | * set up relative brightness of display intensity levels | 
|  | * (could differ for different hardware) | 
|  | * | 
|  | * linear for now.  boost factor insures low intensities are visible | 
|  | */ | 
|  | #define BOOST 5 | 
|  | for (i = 0; i < NLEVELS; i++) | 
|  | level_scale[i] = ((float)i+1+BOOST)/(NLEVELS+BOOST); | 
|  |  | 
|  | points = (struct point *)calloc((size_t)xpixels, | 
|  | ypixels * sizeof(struct point)); | 
|  | if (!points) | 
|  | goto failed; | 
|  |  | 
|  | if (!ws_init(dp->name, xpixels, ypixels, ncolors)) | 
|  | goto failed; | 
|  |  | 
|  | phosphor_init(dp->color0->phosphors, dp->color0->nphosphors, 0); | 
|  |  | 
|  | if (dp->color1) | 
|  | phosphor_init(dp->color1->phosphors, dp->color1->nphosphors, 1); | 
|  |  | 
|  | initialized = 1; | 
|  | init_failed = 0;			/* hey, we made it! */ | 
|  | return 1; | 
|  |  | 
|  | failed: | 
|  | fprintf(stderr, "Display initialization failed\r\n"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void | 
|  | display_reset(void) | 
|  | { | 
|  | /* XXX tear down window? just clear it? */ | 
|  | } | 
|  |  | 
|  | void | 
|  | display_sync(void) | 
|  | { | 
|  | ws_sync(); | 
|  | } | 
|  |  | 
|  | void | 
|  | display_beep(void) | 
|  | { | 
|  | ws_beep(); | 
|  | } | 
|  |  | 
|  | int | 
|  | display_xpoints(void) | 
|  | { | 
|  | return xpoints; | 
|  | } | 
|  |  | 
|  | int | 
|  | display_ypoints(void) | 
|  | { | 
|  | return ypoints; | 
|  | } | 
|  |  | 
|  | int | 
|  | display_scale(void) | 
|  | { | 
|  | return scale; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * handle keyboard events | 
|  | * | 
|  | * data switches; 18 -- enough for PDP-1/4/7/9/15 (for munching squares!) | 
|  | * 123 456 789 qwe rty uio | 
|  | * bit toggled on key up | 
|  | * all cleared on space | 
|  | * | 
|  | * spacewar switches; bit high as long as key down | 
|  | * asdf kl;' | 
|  | * just where PDP-1 spacewar expects them! | 
|  | * key mappings same as MIT Media Lab Java PDP-1 simulator | 
|  | * | 
|  | */ | 
|  | unsigned long spacewar_switches = 0; | 
|  |  | 
|  | /* here from window system */ | 
|  | void | 
|  | display_keydown(int k) | 
|  | { | 
|  | switch (k) { | 
|  | case 'f': case 'F': spacewar_switches |= 01; break; /* torpedos */ | 
|  | case 'd': case 'D': spacewar_switches |= 02; break; /* engines */ | 
|  | case 'a': case 'A': spacewar_switches |= 04; break; /* rotate R */ | 
|  | case 's': case 'S': spacewar_switches |= 010; break; /* rotate L */ | 
|  | case '\'': case '"': spacewar_switches |= 040000; break; /* torpedos */ | 
|  | case ';': case ':':	spacewar_switches |= 0100000; break; /* engines */ | 
|  | case 'k': case 'K': spacewar_switches |= 0200000; break; /* rotate R */ | 
|  | case 'l': case 'L': spacewar_switches |= 0400000; break; /* rotate L */ | 
|  | default: return; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* here from window system */ | 
|  | void | 
|  | display_keyup(int k) | 
|  | { | 
|  | unsigned long test_switches = cpu_get_switches(); | 
|  |  | 
|  | /* fetch console switches from simulator? */ | 
|  | switch (k) { | 
|  | case 'f': case 'F': spacewar_switches &= ~01; return; | 
|  | case 'd': case 'D': spacewar_switches &= ~02; return; | 
|  | case 'a': case 'A': spacewar_switches &= ~04; return; | 
|  | case 's': case 'S': spacewar_switches &= ~010; return; | 
|  |  | 
|  | case '\'': case '"': spacewar_switches &= ~040000; return; | 
|  | case ';': case ':': spacewar_switches &= ~0100000; return; | 
|  | case 'k': case 'K': spacewar_switches &= ~0200000; return; | 
|  | case 'l': case 'L': spacewar_switches &= ~0400000; return; | 
|  |  | 
|  | case '1': test_switches ^= 1<<17; break; | 
|  | case '2': test_switches ^= 1<<16; break; | 
|  | case '3': test_switches ^= 1<<15; break; | 
|  |  | 
|  | case '4': test_switches ^= 1<<14; break; | 
|  | case '5': test_switches ^= 1<<13; break; | 
|  | case '6': test_switches ^= 1<<12; break; | 
|  |  | 
|  | case '7': test_switches ^= 1<<11; break; | 
|  | case '8': test_switches ^= 1<<10; break; | 
|  | case '9': test_switches ^= 1<<9; break; | 
|  |  | 
|  | case 'q': case 'Q': test_switches ^= 1<<8; break; | 
|  | case 'w': case 'W': test_switches ^= 1<<7; break; | 
|  | case 'e': case 'E': test_switches ^= 1<<6; break; | 
|  |  | 
|  | case 'r': case 'R': test_switches ^= 1<<5; break; | 
|  | case 't': case 'T': test_switches ^= 1<<4; break; | 
|  | case 'y': case 'Y': test_switches ^= 1<<3; break; | 
|  |  | 
|  | case 'u': case 'U': test_switches ^= 1<<2; break; | 
|  | case 'i': case 'I': test_switches ^= 1<<1; break; | 
|  | case 'o': case 'O': test_switches ^= 1; break; | 
|  |  | 
|  | case ' ': test_switches = 0; break; | 
|  | default: return; | 
|  | } | 
|  | cpu_set_switches(test_switches); | 
|  | } |