blob: 65e6bf116c4c8fdaf1a0817371c54f7fca0af8d6 [file] [log] [blame] [raw]
/*
* $Id: display.c,v 1.56 2004/02/03 21:44:34 phil Exp - revised by DAG $
* Simulator and host O/S independent XY display simulator
* Phil Budne <phil@ultimate.com>
* September 2003
*
* with changes by Douglas A. Gwyn, 05 Feb. 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-2018 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;
const char *name;
struct color *color0, *color1;
short xpoints, ypoints;
};
/*
* original phosphor constants from Raphael Nabet's XMame 0.72.1 PDP-1 sim.
*
* http://bitsavers.trailing-edge.com/components/rca/hb-3/1963_HB-3_CRT_Storage_Tube_and_Monoscope_Section.pdf
* pdf p374 says 16ADP7 used P7 phosphor.
* pdf pp28-32 describe P7 phosphor (spectra, buildup, persistence)
*
* https://www.youtube.com/watch?v=hZumwXS4fJo
* "3RP7A CRT - P7 Phosphor Persistence" shows colors/persistence
*/
static struct phosphor p7[] = {
{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_p7 = { p7, ELEMENTS(p7), 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)
* P7 Phosphor??? Two phosphor layers:
* fast blue (.05s half life), and slow green (.2s half life)
*/
{ DIS_TX0, "MIT TX-0", &color_p7, 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)
* P7 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_p7, 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-1/4/6/7/9/10
*
* Raytheon 16ADP7A CRT, same as Type 30
* 1024x1024
* 9 3/8" raster (.01" dot pitch)
* 0,0 at lower left
* 8 intensity levels
*/
{ DIS_TYPE340, "Type 340", &color_p7, 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;
static int refresh_elapsed = 0; /* in units of DELAY_UNIT bounded by refresh_interval */
int changed;
if (!initialized && !display_init(DISPLAY_TYPE, PIX_SCALE, NULL))
return 0;
if (slowdown)
display_delay(t, slowdown);
changed = 0;
elapsed += t;
if (elapsed < DELAY_UNIT)
return 0;
t = elapsed / DELAY_UNIT;
elapsed %= DELAY_UNIT;
++refresh_elapsed;
if (refresh_elapsed >= refresh_interval) {
display_sync ();
refresh_elapsed = 0;
}
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, NULL))
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, void *dptr)
{
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, dptr))
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_poll (NULL, 0);
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: bit toggled on key up, all cleared on space
* enough for PDP-1/4/7/9/15 (for munching squares!):
* 123 456 789 qwe rty uio
*
* second set of 18 for PDP-6/10, IBM7xxx (shifted versions of above):
* !@# $%^ &*( QWE RTY UIO
*
*/
unsigned long spacewar_switches = 0;
/* here from window system */
void
display_keydown(int k)
{
switch (k) {
/* handle spacewar switches: see display.h for copious commentary */
#define SWSW(LC,UC,BIT,POS36,FUNC36) \
case LC: case UC: spacewar_switches |= BIT; return;
SPACEWAR_SWITCHES
#undef SWSW
default: return;
}
}
/* here from window system */
void
display_keyup(int k)
{
unsigned long test_switches, test_switches2;
cpu_get_switches(&test_switches, &test_switches2);
switch (k) {
/* handle spacewar switches: see display.h for copious commentary */
#define SWSW(LC,UC,BIT,POS36,NAME36) \
case LC: case UC: spacewar_switches &= ~BIT; return;
SPACEWAR_SWITCHES
#undef SWSW
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': test_switches ^= 1<<8; break;
case 'w': test_switches ^= 1<<7; break;
case 'e': test_switches ^= 1<<6; break;
case 'r': test_switches ^= 1<<5; break;
case 't': test_switches ^= 1<<4; break;
case 'y': test_switches ^= 1<<3; break;
case 'u': test_switches ^= 1<<2; break;
case 'i': test_switches ^= 1<<1; break;
case 'o': test_switches ^= 1; break;
/* second set of 18 switches */
case '!': test_switches2 ^= 1<<17; break;
case '@': test_switches2 ^= 1<<16; break;
case '#': test_switches2 ^= 1<<15; break;
case '$': test_switches2 ^= 1<<14; break;
case '%': test_switches2 ^= 1<<13; break;
case '^': test_switches2 ^= 1<<12; break;
case '&': test_switches2 ^= 1<<11; break;
case '*': test_switches2 ^= 1<<10; break;
case '(': test_switches2 ^= 1<<9; break;
case 'Q': test_switches2 ^= 1<<8; break;
case 'W': test_switches2 ^= 1<<7; break;
case 'E': test_switches2 ^= 1<<6; break;
case 'R': test_switches2 ^= 1<<5; break;
case 'T': test_switches2 ^= 1<<4; break;
case 'Y': test_switches2 ^= 1<<3; break;
case 'U': test_switches2 ^= 1<<2; break;
case 'I': test_switches2 ^= 1<<1; break;
case 'O': test_switches2 ^= 1; break;
case ' ': test_switches = test_switches2 = 0; break;
default: return;
}
cpu_set_switches(test_switches, test_switches2);
}