Both VT11 and VS60 properly autoconfigure on the PDP11. PDP11 now runs Lunar Lander on all SDL supported platforms. Reworked refresh logic to not require internal delays in the display library
1068 lines
32 KiB
C
1068 lines
32 KiB
C
/*
|
||
* $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;
|
||
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; 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);
|
||
}
|