| /* |
| * $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); |
| } |