blob: 7dddadb040e9ed73f6f0c23644d9b1ad6ec55e3b [file] [log] [blame] [raw]
/*
* $Id: vt11.c,v 1.19 2004/02/07 06:38:12 phil Exp $
* Simulator Independent VT11/VS60 Graphic Display Processor Simulation
* Started by Phil Budne <phil@ultimate.com> September 13, 2003
* Substantially revised by Douglas A. Gwyn, 05 Feb. 2004
*
* from EK-VT11-TM-001, September 1974
* and EK-VT48-TM-001, November 1976
* with help from Al Kossow's "VT11 instruction set" posting of 21 Feb 93
* and VT48 Engineering Specification Rev B
* and VS60 diagnostic test listings, provided by Alan Frisbie
*
* The VT11 is a calligraphic display-file device used in the GT4x series
* of workstations (PDP-11/04,34,40 based).
*
* The VS60 is an improved, extended, upward-compatible version of the
* VT11, used in the GT62 workstation (PDP-11/34 based). It supported
* dual consoles (CRTs with lightpens), multiple phosphor colors, 3D
* depth cueing, and circle/arc generator as options. We do not know
* whether any of these options were ever implemented or delivered.
* XXX VS60 depth-cueing option not yet fully implemented
*
* The VSV11/VS11 is a color raster display-file device (with joystick
* instead of light pen) with instructions similar to the VT11's but
* different enough that a separate emulation should be created by
* editing a copy of this source file rather than trying to hack it into
* this one. Very likely, the display (phosphor decay) simulation will
* also require revision to handle multiple colors.
*
* A PDP-11 system has at most one display controller attached.
* In principle, a VT11 or VS60 can also be used on a VAX Unibus.
*/
/*
* Copyright (c) 2003-2004, Philip L. Budne and Douglas A. Gwyn
*
* 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.
*/
#ifdef DEBUG_VT11
#include <stdio.h>
#endif
#include <string.h> /* memset */
#ifndef NO_CONIC_OPT
#include <math.h> /* atan2, cos, sin, sqrt */
#endif
#include "display.h" /* XY plot interface */
#include "vt11.h"
#define BITMASK(n) (1<<(n)) /* PDP-11 bit numbering */
/* mask for a field */
#define FIELDMASK(START,END) ((1<<((START)-(END)+1))-1)
/* extract a field */
#define GETFIELD(W,START,END) (((W)>>(END)) & FIELDMASK(START,END))
/* extract a 1-bit field */
#define TESTBIT(W,B) (((W) & BITMASK(B)) != 0)
#ifdef DEBUG_VT11
#define DEBUGF(X) do { printf X; fflush(stdout); } while (0)
#else
#define DEBUGF(X)
#endif
/*
* Note about coordinate signedness and wrapping:
*
* There is a discrepancy between the documentation and the known or suspected
* behavior of these devices. The VT11 manual says it wraps 12-bit position
* coordinates independently of sign (4097 -> 1), but then it doesn't mention
* any sign for X,Y position registers, yet we think that graphics is properly
* tracked in the negative domain. The VS60 manual says it wraps from 4097 to
* -4095, but there is no evidence of this in the manual's diagrams, and the
* absolute-point graphic data format doesn't show signs for X,Y coordinates.
* Most likely, both devices have signed position coordinates (either sign-
* magnitude or twos-complement) and merely drop bits that overflow.
*
* At one point this simulation attempted to implement position wrapping, but
* because it adds overhead, has uncertain characteristics, and should never
* be exploited by any application anyway, now no wrapping is done, and the
* position is tracked using at least 32 bits including sign.
*
* Note about scaling and offsets:
*
* The VS60 supports character and vector scaling and position offsets. The
* X, Y, and Z position register values always include scaling and offsets.
* It is not clear from the manual whether or not there are two "guard bits",
* which would better track the virtual position when using a scale factor of
* 3/4, 1/2, or 1/4. Most likely, there are no guard bits. This simulation
* maintains position values and offsets both multiplied by PSCALEF, which
* should be 4 to obtain maximum drawing precision, or 1 to mimic non-guard-bit
* display hardware. These internal coordinates are "normalized" (converted to
* correct virtual CRT coordinates) before being reported via the position/
* offset registers. The normalized Z position register value's 2 lowest bits
* are always 0.
* Example of why this matters: Set vector scale 1/2; draw successive vectors
* with delta X = 1, 1, and -2. With guard bits, the final and original X
* positions are the same; without guard bits, the final X position is one
* unit to the left of the original position. This effect accumulates over a
* long sequence of vectors, leading to quite visible distortion of the image.
*
* Light-pen and edge-interrupt positions always have "on-screen" values.
*/
#ifndef PSCALEF
#if 1 /* XXX temporary during development, to catch any oversights */
#define PSCALEF 4 /* position scale factor 4 for maximum precision */
#else
#define PSCALEF 1 /* position scale factor 1 for accurate simulation */
#endif
#endif
#define PSCALE(x) ((x) * PSCALEF)
#define PNORM(x) ((x) / PSCALEF)
/* virtual_CRT_coordinate = PNORM(scaled_value) */
/* VS60 scales points/vectors and characters separately */
#define VSCALE(x) (PSCALE(vector_scale * (int32)(x)) / 4)
#define CSCALE(x) (PSCALE(char_scale * (int32)(x)) / 4)
#define ABS(x) ((x) >= 0 ? (x) : -(x))
enum display_type vt11_display = DISPLAY_TYPE; /* DIS_VR{14,17,48} */
int vt11_scale = PIX_SCALE; /* RES_{FULL,HALF,QUARTER,EIGHTH} */
unsigned char vt11_init = 0; /* set after display_init() called */
#define INIT { if (!vt11_init) { display_init(vt11_display, vt11_scale); \
vt11_init = 1; vt11_reset(); } }
/* state visible to host */
/* The register and field names are those used in the VS60 manual (minus the
trailing "flag", "code", "status", or "select"); the VT11 manual uses
somewhat different names. */
/*
* Display Program Counter
* Read/Write (reading returns the *relocated* DPC bits [15:0])
* DPC address 15:1
* resume 0
*/
#define DPC sp->_dpc /* Display PC (always even) */
static uint16 bdb = 0; /* Buffered Data Bits register;
see comment in vt11_get_dpc() */
/*
* Mode Parameter Register
* Read Only, except that writing to it beeps the LK40 keyboard's bell
* internal stop flag 15
* graphic mode code 14:11
* intensity level 10:8
* LP con. 0 hit flag 7
* shift out status 6
* edge indicator 5
* italics status 4
* blink status 3
* edge flag status 2 (VS60 only)
* line type register status 1:0
*/
static unsigned char internal_stop = 0; /* 1 bit: stop display */
#define graphic_mode sp->_mode /* 4 bits: sets type for graphic data */
enum mode { CHAR=0, SVECTOR, LVECTOR, POINT, GRAPHX, GRAPHY, RELPOINT, /* all */
BSVECT, CIRCLE, ABSVECTOR /* VS60 only */
};
#define intensity sp->_intens /* 3 bits: 0 => dim .. 7 => bright */
static unsigned char lp0_flag = 0; /* 1 bit: light pen #0 detected hit */
#define shift_out sp->_so /* 1 bit: chars using shift-out codes */
static unsigned char edge_indic = 0; /* 1 bit: end pt outside visible area;
on-to-off transition only! */
#define italics sp->_italics /* 1 bit: use italic font */
#define blink_ena sp->_blink /* 1 bit: blink graphic item */
static unsigned char edge_flag = 0; /* 1 bit: any edge transition (VS60) */
#define line_type sp->_ltype /* 2 bits: style for drawing vectors */
enum linetype { SOLID=0, LONG_DASH, SHORT_DASH, DOT_DASH };
/*
* Graphplot Increment and X Position Register
* Read Only
* graphplot increment register value 15:10
* X position register value 9:0
*/
static unsigned char graphplot_step = 0;/* (scaled) graphplot step increment */
static int32 xpos = 0; /* X position register * PSCALEF */
/* note: offset has been applied! */
static int lp_xpos; /* (normalized) */
static int edge_xpos; /* (normalized) */
/*
* Character Code and Y Position Register
* Read Only
* character register contents 15:10
* Y position register value 9:0
*/
static unsigned char char_buf = 0; /* (only lowest 6 bits reported) */
static int32 ypos = 0; /* Y position register * PSCALEF */
/* note: offset has been applied! */
static int lp_ypos; /* (normalized) */
static int edge_ypos; /* (normalized) */
/*
* Relocate Register (VS60 only)
* Read/Write
* spare 15:12
* relocate register value[17:6] 11:0
*/
static uint32 reloc = 0; /* relocation, aligned with DPC */
/*
* Status Parameter Register (VS60 only)
* Read Only, except for bit 7 (1 => external stop request)
* display busy status 15
* stack overflow status 13
* stack underflow status 12
* time out status 11
* char. rotate status 10
* char. scale index 9:8
* external stop flag 7
* menu status 6
* relocated DPC bits [17:16] 5:4
* vector scale 3:0
*/
#define busy (!(internal_stop || ext_stop || lphit_irq || lpsw_irq || edge_irq \
|| char_irq || stack_over || stack_under || time_out || name_irq))
/* 1 bit: display initiated | resumed */
static unsigned char stack_over = 0; /* 1 bit: "push" with full stack */
static unsigned char stack_under = 0; /* 1 bit: "pop" with empty stack */
static unsigned char time_out = 0; /* 1 bit: timeout has occurred */
#define char_rotate sp->_crotate /* 1 bit: rotate chars 90 degrees CCW */
#define cs_index sp->_csi /* character scale index 0..3 */
static unsigned char ext_stop = 0; /* 1 bit: stop display */
#define menu sp->_menu /* 1 bit: VS60 graphics in menu area */
#define vector_scale sp->_vscale /* non-character scale factor * 4 */
/*
* X Offset Register (VS60 only)
* Read/Write
* upper X position bits 15:12 (read)
* sign of X dynamic offset 13 (write)
* X dynamic offset 11:0
*/
static unsigned char s_xoff = 0; /* sign bit for xoff (needed for -0) */
static int32 xoff = 0; /* X offset register * PSCALEF */
/*
* Y Offset Register (VS60 only)
* Read/Write
* upper Y position bits 15:12 (read)
* sign of Y dynamic offset 13 (write)
* Y dynamic offset 11:0
*/
static unsigned char s_yoff = 0; /* sign bit for yoff (needed for -0) */
static int32 yoff = 0; /* Y offset register * PSCALEF */
/*
* Associative Name Register (VS60 only)
* Write Only
* search code change enable 14
* search code 13:12
* name change enable 11
* associative name 10:0
*/
static unsigned char search = 0; /* 00=> no search, no interrupt
01 => intr. on 11-bit compare
10 => intr. on high-8-bit compare
11 => intr. on high-4-bit compare */
static unsigned assoc_name = 0; /* compare value */
/*
* Slave Console/Color Register (VS60 only)
* Read/Write *
* inten enable con. 0 15
* light pen hit flag con. 0 14 *
* LP switch on flag con. 0 13 *
* LP switch off flag con. 0 12 *
* LP flag intr. enable con. 0 11
* LP switch flag intr. enable con. 0 10
* inten enable con. 1 9
* light pen hit flag con. 1 8 *
* LP switch on flag con. 1 7 *
* LP switch off flag con. 1 6 *
* LP flag intr. enable con. 1 5
* LP switch flag intr. enable con. 1 4
* color 3:2
*
* * indicates that maintenance switch 3 must be set to write these bits;
* the other bits are not writable at all
*/
#define int0_scope sp->_inten0 /* enable con. 0 for all graphic data */
/* lp0_flag has already been defined, under Mode Parameter Register */
#define lp0_sw display_lp_sw /* (defined in display.c) */
#define lp0_intr_ena sp->_lp0intr /* generate interrupt on LP #0 hit */
#define lp0_sw_intr_ena sp->_lp0swintr /* generate intr. on LP #0 switch chg */
#define int1_scope sp->_inten1 /* enable con. 1 for all graphic data */
/* following 2 flags only mutable via writing this register w/ MS3 set: */
static unsigned char lp1_flag = 0; /* 1 bit: light pen #1 detected hit */
static unsigned char lp1_sw = 0; /* 1 bit: LP #1 switch changed state */
#define lp1_intr_ena sp->_lp1intr /* generate interrupt on LP #1 hit */
#define lp1_sw_intr_ena sp->_lp1swintr /* generate intr. on LP #1 switch chg */
enum scolor { GREEN=0, YELLOW, ORANGE, RED };
#define color sp->_color /* 2 bits: VS60 color option */
/*
* Name Register (VS60 only)
* Read Only
* name/assoc name match flag 15
* search code 13:12
* name 10:0
*/
static unsigned char name_flag = 0; /* 1 bit: name matches associative nm */
/* search previously defined, under Associative Name Register */
#define name sp->_name /* current name from display file */
/*
* Stack Data Register (VS60 only)
* Read Only
* stack data 15:0 (as selected by Stk. Addr./Maint. Reg.)
*
* On the actual hardware there are 2 32-bit words per each of 8 stack levels.
* At the PDP-11 these appear to be 4 16-bit words ("stack bytes") per level.
*/
/* Two alternatives: keep active data in globals or use directly from stack.
The former is presumably more efficient when the stack isn't being used,
as in VT11 mode, but on most host processors the difference is small.
POPR is faster if we do not have to restore data to all those globals.
There are thus 9 levels of data, the initial state and 8 stacked sets.
Mimicking the actual hardware, the stack level *decreases* upon JSR.
*/
static struct frame
{
vt11word _dpc; /* Display Program Counter (even) */
unsigned _name; /* (11-bit) name from display file */
enum mode _mode; /* 4 bits: sets type for graphic data */
unsigned char _vscale; /* non-character scale factor * 4 */
unsigned char _csi; /* character scale index 0..3 */
unsigned char _cscale; /* character scale factor * 4 */
unsigned char _crotate; /* rotate chars 90 degrees CCW */
unsigned char _intens; /* intensity: 0 => dim .. 7 => bright */
enum linetype _ltype; /* line type (long dash, etc.) */
unsigned char _blink; /* blink enable */
unsigned char _italics; /* italicize characters */
unsigned char _so; /* chars are using shift-out codes */
unsigned char _menu; /* VS60 graphics in menu area */
unsigned char _cesc; /* perform POPR on char. term. match */
unsigned char _edgeintr; /* generate intr. on edge transition */
unsigned char _lp1swintr; /* generate intr. on LP #1 switch chg */
unsigned char _lp0swintr; /* generate intr. on LP #0 switch chg */
unsigned char _lp1intr; /* generate interrupt on LP #1 hit */
unsigned char _inten1; /* blank cons. 1 for all graphic data */
unsigned char _lp0intr; /* generate interrupt on LP #0 hit */
unsigned char _inten0; /* blank cons. 0 for all graphic data */
unsigned char _bright; /* visually indicate hit on entity */
unsigned char _stopintr; /* generate interrupt on stop */
enum scolor _color; /* scope display color (option) */
unsigned char _zdata; /* flag: display file has Z coords */
unsigned char _depth; /* flag: display Z using depth cue */
} stack[9] = { { 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, CHAR, 4, 1, 0, 4, SOLID,
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 },
};
/* (stack pointer "sp" defined under Stack Address/Maintenance Register) */
#define char_scale sp->_cscale /* character scale factor * 4 */
static const unsigned char csi2csf[4] = { 2, 4, 6, 8 }; /* maps cs_index to " */
#define char_escape sp->_cesc /* perform POPR on char. term. match */
#define edge_intr_ena sp->_edgeintr /* generate intr. on edge transition */
#define lp_intensify sp->_bright /* if VT11, 20us bright spot;
if VS60, "bright down" the entity */
#define stop_intr_ena sp->_stopintr /* generate intr. on internal stop */
#define file_z_data sp->_zdata /* flag: display file has Z coords */
#define depth_cue_proc sp->_depth /* flag: display Z using depth cue */
/*
* Character String Terminate Register (VS60 only)
* Read/Write
* char. term. reg. enable 7
* character terminate code 6:0
*/
static int char_term = 0; /* char. processing POPRs after this */
/*
* Stack Address/Maintenance Register (VS60 only)
* Read/Write
* maint. sw. 4 15
* maint. sw. 3 14
* maint. sw. 2 13
* maint. sw. 1 12
* offset mode status 10
* jump to subr. ?rel. status 9 (diagnostic requires this be JSR abs.!)
* word 2 status 8
* word 1 status 7
* word 0 status 6
* stack reset status 5
* stack level select 4:2 (manual has this messed up)
* stack halfword select 1:0 (manual has this messed up)
*/
static unsigned char maint4 = 0; /* 1 bit: maintenance switch #4 */
static unsigned char maint3 = 0; /* 1 bit: maintenance switch #3 */
static unsigned char maint2 = 0; /* 1 bit: maintenance switch #2 */
static unsigned char maint1 = 0; /* 1 bit: maintenance switch #1 */
static unsigned char offset = 0; /* 1 bit: last data loaded offsets */
static unsigned char jsr = 0; /* 1 bit: last control was JSR ?rel. */
static unsigned char word_number = 0; /* tracks multiple data words etc. */
static struct frame *sp = &stack[8];
#define STACK_EMPTY (sp == &stack[8])
#define STACK_FULL (sp == stack)
static unsigned char stack_sel = 8<<2; /* 8 levels, 4 PDP-11 words per level */
/* stack_sel must track sp! */
/*
* Z Position Register, Depth Cue Option (VS60 only)
* Read/Write
* Z position register value[13:2] 11:0
*/
static int32 zpos = 0; /* (Z "position" reg. * 4) * PSCALEF */
/* note: offset has been applied! */
static int32 lp_zpos; /* (scaled) */
static int32 edge_zpos; /* (scaled) */
/*
* Z Offset Register, Depth Cue Option (VS60 only)
* Read/Write
* sign of X dynamic offset 15 (read) (VT48 manual has this confused)
* sign of Y dynamic offset 14 (read) (VT48 manual has this confused)
* sign of Z dynamic offset 13
* Z dynamic offset 11:0
*/
static unsigned char s_zoff = 0; /* sign bit for zoff (needed for -0) */
static int32 zoff = 0; /* Z offset register * PSCALEF */
/*
* Invisible state:
*/
static unsigned char char_irq = 0; /* intr. on illegal char in SO mode */
static unsigned char lphit_irq = 0; /* intr. on light-pen hit */
static unsigned char lpsw_irq = 0; /* intr. on tip-switch state change */
static unsigned char edge_irq = 0; /* intr. on edge transition */
static unsigned char name_irq = 0; /* intr. on name match */
static unsigned char lp0_sw_state = 0; /* track light-pen tip-switch state */
static unsigned char blink_off = 0; /* set when blinking graphics is dark */
static unsigned char finish_jmpa = 0; /* reminder to fetch JMPA address */
static unsigned char finish_jsra = 0; /* reminder to fetch JSRA address */
static unsigned char more_vect = 0; /* remembers LP hit in middle of vec. */
static unsigned char more_arc = 0; /* remembers LP hit in middle of arc */
static int32 save_x0, save_y0, save_x1, save_y1;/* CRT coords for rest of vec */
static unsigned char lp_suppress = 0; /* edge columns of char. (VT11 only) */
static unsigned char stroking = 0; /* set when drawing VS60 char strokes */
static unsigned char skip_start = 0; /* set between vis. char./arc strokes */
static unsigned char sync_period = 0; /* frame sync period (msec) */
static unsigned char refresh_rate = 0; /* 2 bits:
00 => continuous display refresh
01 => 30 fps (60 fps if VT11)
10 => 40 fps (VS60)
11 => external sync (VS60) */
#if 0 /* this is accurate in simulated "real" time */
#define BLINK_COUNT 266 /* 266 milliseconds */
#else /* this looks better in actual real time (adjust for your host speed) */
#define BLINK_COUNT 67 /* 67 milliseconds */
#endif
unsigned char vt11_csp_w = VT11_CSP_W; /* horizontal character spacing */
unsigned char vt11_csp_h = VT11_CSP_H; /* vertical character spacing */
/* VS60 has a menu area to the right of the "main working surface" */
#define MENU_OFFSET (1024 + VR48_GUTTER) /* left edge of menu on CRT */
#define VR48_WIDTH (MENU_OFFSET + 128) /* X beyond this is not illuminated */
static int reduce; /* CRT units per actual pixel */
static int x_edge; /* 1023 or VR48_WIDTH-1, depending */
static int y_edge; /* 767 or 1023, depending on display */
#define ONCRT(x,y) ((x) >= 0 && (x) <= x_edge && (y) >= 0 && (y) <= y_edge)
/*
* Uncertain whether VS60 edge transitions in menu area are flagged and whether
* clipping takes menu width into account. Three possibilities:
*/
#if 0 /* menu area never clipped (seems wrong) */
#define ONSCREEN(x,y) (menu || ((x)>=0 && (x)<=1023 && (y)>=0 && (y)<=y_edge))
#elif 0 /* menu area correctly clipped */
#define ONSCREEN(x,y) ((x) >= 0 && (x) <= (menu ? 127 : 1023) \
&& (y) >= 0 && (y) <= y_edge)
#else /* menu area clipped same as main area */
#define ONSCREEN(x,y) ((x) >= 0 && (x) <= 1023 && (y) >= 0 && (y) <= y_edge)
#endif
static void lineTwoStep(int32, int32, int32, int32); /* forward reference */
/*
* calls to read/write VT11/VS60 CSRs
*
* presumably the host looks at our state less often than we do(!)
* so we keep it in a form convenient to us!
*/
int32
vt11_get_dpc(void)
{ INIT
/*
* The VT48 manual says that Maintenance Switch 1 causes the Buffered
* Data Bits register to be "entered into the DPC" so it can be
* examined by reading the DPC address, but details of when and how
* often that happens are not provided. Examination of the diagnostic
* test listings show that relocation is applied and that only the DPC
* is involved when this switch is set.
*/
return ((maint1 ? bdb : DPC) + reloc) & 0177777;
}
void
vt11_set_dpc(uint16 d)
{ INIT
bdb = d; /* save all bits in case maint1 used */
DEBUGF(("set DPC 0%06o\r\n", (unsigned)d));
if (!TESTBIT(d,0)) {
sp = &stack[8]; /* important! do this first */
stack_sel = 8<<2;
DPC = d; /* load DPC */
sync_period = 0;
ext_stop = 0;
/* the following seem reasonable, but might be wrong */
finish_jmpa = finish_jsra = jsr = 0;
word_number = 0;
#if 0 /* probably accurate mimicry, but ugly behavior */
if (edge_irq) {
xpos = PSCALE(edge_x);
ypos = PSCALE(edge_y);
}
#endif
} else { /* RESUME (after intr); DPC unchanged */
/* if resuming from LP hit interrupt, finish drawing rest of vector */
/* (if resuming from edge interrupt, vector is *not* drawn) */
if (more_vect) {
unsigned char save_ena = lp0_intr_ena;
lp0_intr_ena = 0; /* one hit per vector is plenty */
lphit_irq = 0; /* or else lineTwoStep aborts again! */
/* line_counter is intact; draw rest of visible vector */
lineTwoStep(save_x0, save_y0, save_x1, save_y1);
lp0_intr_ena = save_ena;
}
if (more_arc) { /* remainder of chord was just drawn */
unsigned char save_ena = lp0_intr_ena;
lp0_intr_ena = 0; /* one hit per arc is plenty */
lphit_irq = 0; /* or else lineTwoStep aborts again! */
/* line_counter is intact; draw rest of visible arc */
/*XXX not yet implemented [conic{23}(<saved params>) needed]*/
lp0_intr_ena = save_ena;
}
if (!maint2) /* kludge to satify diagnostic test */
ext_stop = 0;
}
internal_stop = time_out = stack_over = stack_under = 0;
more_vect = more_arc = stroking = skip_start = 0;
edge_indic = edge_flag = lp0_flag = lp1_flag = lp_suppress = 0;
char_irq = lphit_irq = lpsw_irq = edge_irq = name_irq = 0;
/* next vt11_cycle() will perform a fetch */
}
int32
vt11_get_mpr(void)
{
int32 ret;
INIT
ret = (internal_stop<<15) | (graphic_mode<<11) | (intensity<<8) |
(lp0_flag<<7) | (shift_out<<6) | (edge_indic<<5) | (italics<<4) |
(blink_ena<<3) | line_type;
if (VS60)
ret |= edge_flag<<2;
return ret;
}
void
vt11_set_mpr(uint16 d)
{ INIT
/* beeps the "bell" on the LK40 keyboard */
#if 0 /* probably doesn't hurt to do it for the VS60 also */
if (VT11) /* according to the VS60 specs */
#endif
display_beep();
}
int32
vt11_get_xpr(void)
{
int32 pos;
INIT
pos = lphit_irq ? lp_xpos : edge_irq ? edge_xpos : PNORM(xpos);
return (graphplot_step << 10) | GETFIELD(ABS(pos),9,0);
}
void
vt11_set_xpr(uint16 d)
{ INIT
DEBUGF(("set XPR: no effect\r\n"));
}
int32
vt11_get_ypr(void)
{
int32 pos;
INIT
pos = lphit_irq ? lp_ypos : edge_irq ? edge_ypos : PNORM(ypos);
return (GETFIELD(char_buf,5,0) << 10) | GETFIELD(ABS(pos),9,0);
}
void
vt11_set_ypr(uint16 d)
{ INIT
DEBUGF(("set YPR: no effect\r\n"));
}
/* All the remaining registers pertain to the VS60 only. */
int32
vt11_get_rr(void)
{ INIT
return reloc >> 6;
}
void
vt11_set_rr(uint16 d)
{ INIT
reloc = (uint32)GETFIELD(d,11,0) << 6;
}
int32
vt11_get_spr(void)
{ INIT
return (busy<<15) | (stack_over<<13) | (stack_under<<12) | (time_out<<11) |
(char_rotate<<10) | (cs_index<<8) | (ext_stop<<7) |
(menu<<6) | (((DPC + reloc) & 0600000L) >> 12) | vector_scale;
}
void
vt11_set_spr(uint16 d)
{ INIT
ext_stop = TESTBIT(d,7);
if (ext_stop /* && stop_intr_ena */)/* not maskable? */
vt_stop_intr(); /* post stop interrupt to host */
/* (asynchronous with display cycle) */
}
int32
vt11_get_xor(void)
{
int32 off, pos;
INIT
off = PNORM(xoff);
pos = lphit_irq ? lp_xpos : edge_irq ? edge_xpos : PNORM(xpos);
return (GETFIELD(ABS(pos),13,10)<<12) | GETFIELD(ABS(off),11,0);
}
void
vt11_set_xor(uint16 d)
{ INIT
xoff = PSCALE(GETFIELD(d,11,0));
if (s_xoff = TESTBIT(d,13))
xoff = -xoff;
}
int32
vt11_get_yor(void)
{
int32 off, pos;
INIT
off = PNORM(yoff);
pos = lphit_irq ? lp_ypos : edge_irq ? edge_ypos : PNORM(ypos);
return (GETFIELD(ABS(pos),13,10)<<12) | GETFIELD(ABS(off),11,0);
}
void
vt11_set_yor(uint16 d)
{ INIT
yoff = PSCALE(GETFIELD(d,11,0));
if (s_yoff = TESTBIT(d,13))
yoff = -yoff;
}
int32
vt11_get_anr(void)
{ INIT
DEBUGF(("get ANR: no effect\r\n"));
return (search << 12) | assoc_name; /* [garbage] */
}
void
vt11_set_anr(uint16 d)
{ INIT
if (TESTBIT(d,14))
search = GETFIELD(d,13,12);
if (TESTBIT(d,11))
assoc_name = GETFIELD(d,10,0);
}
int32
vt11_get_scr(void)
{ INIT
return (int0_scope<<15) | (lp0_flag<<14) | (lp0_sw<<13) | ((!lp0_sw)<<12) |
(lp0_intr_ena<<11) | (lp0_sw_intr_ena<<10) | (int1_scope<<9) |
(lp1_flag<<8) | (lp1_sw<<7) | ((!lp1_sw)<<6) | (lp1_intr_ena<<5) |
(lp1_sw_intr_ena<<4) | (color << 2);
}
void
vt11_set_scr(uint16 d)
{ INIT
if (maint3) {
unsigned char old_sw0 = lp0_sw;
unsigned char old_sw1 = lp1_sw;
if (TESTBIT(d,14)) {
if (!lphit_irq) { /* ensure correct position registers reported */
lp_xpos = PNORM(xpos);
lp_ypos = PNORM(ypos);
lp_zpos = PNORM(zpos);
}
lp0_flag = 1;
if (lp0_intr_ena)
lphit_irq = 1;
}
if (TESTBIT(d,13)) {
lp0_sw = 1; /* the manual seems to have it backward */
if (lp0_sw_intr_ena && lp0_sw != old_sw0)
lpsw_irq = 1;
}
if (TESTBIT(d,12)) {
lp0_sw = 0; /* the manual seems to have it backward */
if (lp0_sw_intr_ena && lp0_sw != old_sw0)
lpsw_irq = 1;
}
if (TESTBIT(d,8)) {
if (!lphit_irq) { /* ensure correct position registers reported */
lp_xpos = PNORM(xpos);
lp_ypos = PNORM(ypos);
lp_zpos = PNORM(zpos);
}
lp1_flag = 1;
if (lp1_intr_ena)
lphit_irq = 1;
}
if (TESTBIT(d,7)) {
lp1_sw = 1;
if (lp1_sw_intr_ena && lp1_sw != old_sw1)
lpsw_irq = 1;
}
if (TESTBIT(d,6)) {
lp1_sw = 0;
if (lp1_sw_intr_ena && lp1_sw != old_sw1)
lpsw_irq = 1;
}
if (lphit_irq || lpsw_irq)
vt_lpen_intr();
}
}
int32
vt11_get_nr(void)
{ INIT
return (name_flag<<15) | (search<<12) | name;
}
void
vt11_set_nr(uint16 d)
{ INIT
DEBUGF(("set NR: no effect\r\n"));
}
int32
vt11_get_sdr(void)
{
struct frame *p;
INIT
p = &stack[GETFIELD(stack_sel,4,2)];
switch (GETFIELD(stack_sel,1,0)) { /* 16-bit "byte" within frame */
case 0:
return p->_dpc; /* DPC bit#0 is always 0 */
case 1:
return (p->_name << 4) | p->_mode;
case 2:
return (p->_italics << 15) | (p->_vscale << 11) | (p->_cscale << 9) |
(p->_crotate << 7) | (p->_intens << 4) | ((int)p->_color << 2) |
p->_ltype;
case 3:
return (p->_blink << 15) | (p->_so << 14) | (p->_menu << 13) |
(p->_cesc << 12) | (p->_edgeintr << 11) | (p->_zdata << 10) |
(p->_depth << 8) | (p->_lp1swintr << 7) |
(p->_lp0swintr << 6) | (p->_lp1intr << 5) | (p->_inten1 << 4) |
(p->_lp0intr << 3) | (p->_inten0 << 2) | (p->_bright << 1) |
p->_stopintr; /* XXX should that be !p->_so ? */
}
/*NOTREACHED*/
}
void
vt11_set_sdr(uint16 d)
{ INIT
DEBUGF(("set SDR: no effect\r\n"));
}
int32
vt11_get_str(void)
{ INIT
return char_term;
}
void
vt11_set_str(uint16 d)
{ INIT
if (TESTBIT(d,7))
char_term = GETFIELD(d,6,0);
}
int32
vt11_get_sar(void)
{
int32 ret;
INIT
ret = (maint4<<15) | (maint3<<14) | (maint2<<13) | (maint1<<12) |
(offset<<10) | (jsr<<9) | stack_sel /*includes bit 5, TOS [level 8]*/;
switch (word_number) {
case 0:
ret |= 1<<6;
break;
case 1:
ret |= 1<<7;
break;
case 2:
ret |= 1<<8;
break;
/* others not reportable */
}
return ret;
}
void
vt11_set_sar(uint16 d)
{ INIT
maint4 = TESTBIT(d,15); /* 1 => synch. processing pipeline */
maint3 = TESTBIT(d,14); /* 1 => copy delta,tangent to x,y pos */
maint2 = TESTBIT(d,13); /* 1 => set single-step mode */
maint1 = TESTBIT(d,12); /* 1 => vt11_get_dpc will return bdb */
if (TESTBIT(d,5)) {
sp = &stack[8]; /* reset stack pointer */
stack_sel = 8<<2; /* TOS amounts to level 8 */
#if 1 /* the following seems wrong, but is needed to pass diagnostic test! */
stack_sel |= 1;
#endif
} else {
stack_sel = GETFIELD(d,4,0);
sp = &stack[GETFIELD(stack_sel,4,2)];
}
}
/* registers used with the VS60 depth cueing option */
/*
* Since there is no support for hardware 3D rotation or viewing transform, the
* only effect of the Z coordinate is to modulate beam intensity along a vector
* to give the illusion that greater Z coordinates are closer (brighter).
* This is known as "depth cueing" and is implemented in illum3().
*/
int32
vt11_get_zpr(void)
{
int32 pos;
INIT
pos = lphit_irq ? lp_zpos : edge_irq ? edge_zpos : PNORM(zpos);
return GETFIELD(ABS(pos)/4,11,0); /* sign not reported? */
}
void
vt11_set_zpr(uint16 d)
{ INIT
DEBUGF(("set ZPR: no effect\r\n"));
}
int32
vt11_get_zor(void)
{
int32 off, ret;
INIT
off = PNORM(zoff);
ret = GETFIELD(ABS(off),11,0);
if (s_xoff) /* (VT48 manual has this confused) */
ret |= 1<<15;
if (s_yoff) /* (VT48 manual has this confused) */
ret |= 1<<14;
if (s_zoff)
ret |= 1<<13;
return ret;
}
void
vt11_set_zor(uint16 d)
{ INIT
zoff = PSCALE(GETFIELD(d,11,0));
if (s_zoff = TESTBIT(d,13))
zoff = -zoff;
}
void
vt11_reset(void)
{
/* XXX call display layer? destroy window? */
/* make sure display code has been initialized */
if (!vt11_init) /* (SIMH invokes before display type is set) */
return; /* wait until last moment */
if (VS60) {
/* XXX verify that this is correct VS60 character spacing */
vt11_csp_w = 14; /* override VT11 options */
vt11_csp_h = 24;
} /* else assume already set up for desired VT11 behavior */
x_edge = display_xpoints() - 1;
y_edge = display_ypoints() - 1;
reduce = display_scale();
/* reset VT11/VT48 to initial default internal state: */
/* clear interrupts, BDB, etc. */
vt11_set_dpc(0); /* important! do this first */
/* some of the following should probably be moved to vt11_set_dpc([even]) */
internal_stop = int0_scope = 1; /* idle, console 0 enabled */
lp0_sw_state = lp0_sw; /* sync with mouse button #1 */
lp1_sw = 0;
shift_out = int1_scope = stop_intr_ena = blink_off = 0;
italics = blink_ena = char_rotate = menu = search = offset = 0;
lp0_sw_intr_ena = lp1_sw_intr_ena = lp0_intr_ena = lp1_intr_ena = 0;
file_z_data = edge_intr_ena = depth_cue_proc = char_escape = 0;
maint1 = maint2 = maint3 = maint4 = 0;
refresh_rate = 0;
char_buf = char_term = 0;
assoc_name = name = 0;
reloc = 0;
xpos = ypos = zpos = xoff = yoff = zoff = 0;
s_xoff = s_yoff = s_zoff = 0;
graphplot_step = 0;
graphic_mode = CHAR;
line_type = SOLID;
color = GREEN;
lp_intensify = 1;
cs_index = 1;
char_scale = vector_scale = 4;
intensity = 4;
/* following just in case the stack is inspected via stack data reg. */
{ int i;
for (i = 0; i < 8; ++i)
memset(&stack[i], 0, sizeof(struct frame));
}
}
/* VS60 display subroutine support (see stack layout for SDR, above) */
static void
push()
{
stack_over = STACK_FULL;
if (!stack_over) {
--sp;
sp[0] = sp[1]; /* initially have same parameters */
/* (including *old* DPC) */
stack_sel -= 1<<2;
/* XXX should stack_sel stack-byte bits be cleared? */
}
/* else will generate interrupt soon after return */
}
static void
pop(int restore)
{
stack_under = STACK_EMPTY;
if (!stack_under) {
++sp; /* that's all! */
stack_sel += 1<<2;
/* XXX should stack_sel stack-byte bits be cleared? */
}
/* else will generate interrupt soon after return */
}
/* illuminate pixel in raster image */
static void
illum3(int32 x, int32 y, int32 z) /* XXX should supply intensity also */
/* virtual CRT units (offset and normalized) */
{
int i;
/* don't update position registers! */
/* coords might be outside viewable area, e.g. clipped italic character */
if (!ONCRT(x, y) || !int0_scope)
return;
if (blink_ena && blink_off) /* blinking & in dark phase */
return;
i = intensity;
if (depth_cue_proc) { /* apply depth cue */
i += i * z / 1024; /* XXX is z scaled properly? */
if (i > 7)
i = 7;
else if (i < 0)
i = 0;
}
i += DISPLAY_INT_MAX - 7;
if (i < DISPLAY_INT_MIN)
i = DISPLAY_INT_MIN;
if (display_point((int)x, (int)y, i, 0) /* XXX VS60 might switch color */
/* VT11, per maintenance spec, has threshold 6 for CHAR, 4 for others */
/* but the classic Lunar Lander uses 3 for its menu and thrust bar! */
/* I seem to recall that both thresholds were 4 for the VS60 (VR48). */
#if 0
&& (i >= (DISPLAY_INT_MAX-1) /* (using i applies depth cueing) */
|| (graphic_mode != CHAR && i >= (DISPLAY_INT_MAX-3)))
#else
/* The following imposes thresholds of 3 for all graphic objects. */
&& (i >= (DISPLAY_INT_MAX-4)) /* (using i applies depth cueing) */
#endif
&& !lp_suppress) {
lp0_flag = 1;
if (lp0_intr_ena)
lphit_irq = 1; /* will lead to an interrupt */
/*
* Save LP hit coordinates so CPU can look at them; the virtual position
* registers cannot be reported on LP interrupt, since they track the
* (pre-clipping) end of the vector that was being drawn.
*/
lp_xpos = x;
if (menu)
lp_xpos -= MENU_OFFSET;
lp_ypos = y;
lp_zpos = z;
if (lp_intensify) /* [technically shouldn't exceed max] */
display_point((int)x, (int)y, DISPLAY_INT_MAX, 0);
/* XXX appropriate for VT11; what about VS60? chars? */
}
}
#define illum2(x,y) illum3(x, y, PNORM(zpos)) /* may be depth cued */
/* the extra overhead if not depth cueing is not much */
static void
point3(int i, int32 x1, int32 y1, int32 z1, int detect_edge)
/* unscaled, unoffset display-file units */
{
int32 x0 = PNORM(xpos), y0 = PNORM(ypos);
if (detect_edge) {
edge_indic = ONSCREEN(x0, y0); /* first test */
edge_flag = !ONSCREEN(x0, y0); /* first test */
} else {
edge_indic = 0;
edge_flag = 0;
}
xpos = VSCALE(x1) + xoff;
ypos = VSCALE(y1) + yoff;
zpos = VSCALE(z1 * 4) + zoff;
x1 = PNORM(xpos);
y1 = PNORM(ypos);
z1 = PNORM(zpos);
if (detect_edge) {
edge_indic &= !ONSCREEN(x1, y1); /* second test */
edge_flag &= ONSCREEN(x1, y1); /* second test */
edge_flag |= edge_indic;
if (edge_flag && edge_intr_ena) {
edge_xpos = x1;
edge_ypos = y1;
edge_zpos = z1;
edge_irq = 1;
#if 1 /* XXX uncertain whether point is displayed during edge intr. */
return; /* point not displayed */
#endif
}
}
if (i && ONSCREEN(x1, y1))
if (menu)
illum3(x1 + MENU_OFFSET, y1, z1);
else
illum3(x1, y1, z1);
}
#define point2(i,x,y,e) point3(i, x, y, PNORM(zpos - zoff) / 4, e)
/* the extra overhead if not depth cueing is not much */
/* 4 bit counter, fed from div 2 clock (to compensate for raster algorithm) */
/* XXX check display against example photos to see if div 2 is right */
static unsigned char line_counter;
#define LC1 02
#define LC2 04
#define LC3 010
#define LC4 020
/* point on a line (apply line style) */
static void
lpoint(int32 x, int32 y) /* window-system screen pixel units */
{
int i, on;
/* convert back from actual screen pixels to emulated CRT coordinates */
x *= reduce;
y *= reduce;
if (!(on = (line_type == SOLID) || stroking))
for (i = 0; i < reduce; ++i) {
switch (line_type) {
case LONG_DASH:
if (line_counter & LC4)
on = 1;
break;
case SHORT_DASH:
if (line_counter & LC3)
on = 1;
break;
case DOT_DASH:
/* LC(2:1)H * LC3L + LC4L */
if (((line_counter & (LC1|LC2)) == (LC1|LC2)
&& !(line_counter & LC3)) || !(line_counter & LC4))
on = 1;
break;
}
--line_counter;
}
if (on)
illum2(x, y);
}
/*
* 2-step algorithm, developed by Xiaolin Wu
* from http://graphics.lcs.mit.edu/~mcmillan/comp136/Lecture6/Lines.html
*
* The two-step algorithm takes the interesting approach of treating
* line drawing as a automaton, or finite state machine. If one looks
* at the possible configurations that the next two pixels of a line,
* it is easy to see that only a finite set of possiblities exist.
* If line styles weren't involved, the line could be drawn symmetrically
* from both ends toward the midpoint.
* Rasterization is done using actual screen pixel units, not emulated device
* coordinates!
*/
static void
lineTwoStep(int32 x0, int32 y0, int32 x1, int32 y1)
/* virtual CRT units (offset and normalized) */
{
int32 dx, dy;
int stepx, stepy;
/* after clipping is implemented, coords should always be on-screen */
/* convert from emulated CRT units to actual screen pixels */
x0 /= reduce;
y0 /= reduce;
x1 /= reduce;
y1 /= reduce;
dx = x1 - x0;
dy = y1 - y0;
/* XXX there could be fast special cases for "basic vectors" */
if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; }
if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; }
#define TPOINT do { if (lphit_irq && !stroking) goto hit; \
/* XXX would longjmp be more efficient? */ \
lpoint(x0, y0); \
} while (0)
if (!skip_start) /* not for continuing stroke when VS60 char. or arc */
lpoint(x0, y0); /* (could have used TPOINT) */
if (dx == 0 && dy == 0) /* following algorithm won't work */
return; /* just the one dot */
if (dx > dy) {
int32 length = (dx - 1) / 2;
int extras = (dx - 1) & 1;
int32 incr2 = (dy * 4) - (dx * 2);
if (incr2 < 0) {
int32 c = dy * 2;
int32 incr1 = c * 2;
int32 d = incr1 - dx;
int32 i;
for (i = 0; i < length; i++) {
x0 += stepx;
if (d < 0) { /* Pattern: */
TPOINT; /* x o o */
x0 += stepx;
TPOINT;
d += incr1;
}
else {
if (d < c) { /* Pattern: */
TPOINT; /* o */
y0 += stepy; /* x o */
} else { /* Pattern: */
y0 += stepy; /* o o */
TPOINT; /* x */
}
x0 += stepx;
TPOINT;
d += incr2;
}
}
if (extras > 0) {
x0 += stepx;
if (d >= c)
y0 += stepy;
TPOINT;
}
} else {
int32 c = (dy - dx) * 2; /* negative */
int32 incr1 = c * 2; /* negative */
int32 d = incr1 + dx;
int32 i;
for (i = 0; i < length; i++) {
x0 += stepx;
if (d > 0) { /* Pattern: */
y0 += stepy; /* o */
TPOINT; /* o */
x0 += stepx; /* x */
y0 += stepy;
TPOINT;
d += incr1;
} else {
if (d < c) { /* Pattern: */
TPOINT; /* o */
y0 += stepy; /* x o */
} else { /* Pattern: */
y0 += stepy; /* o o */
TPOINT; /* x */
}
x0 += stepx;
TPOINT;
d += incr2;
}
}
if (extras > 0) {
x0 += stepx;
if (d >= c)
y0 += stepy;
TPOINT;
}
}
} else {
int32 length = (dy - 1) / 2;
int extras = (dy - 1) & 1;
int32 incr2 = (dx * 4) - (dy * 2);
if (incr2 < 0) {
int32 c = dx * 2;
int32 incr1 = c * 2;
int32 d = incr1 - dy;
int32 i;
for (i = 0; i < length; i++) {
y0 += stepy;
if (d < 0) { /* Pattern: */
TPOINT; /* o */
y0 += stepy; /* o */
TPOINT; /* x */
d += incr1;
} else {
if (d < c) { /* Pattern: */
TPOINT; /* o */
x0 += stepx; /* o */
/* x */
} else { /* Pattern: */
x0 += stepx; /* o */
TPOINT; /* o */
/* x */
}
y0 += stepy;
TPOINT;
d += incr2;
}
}
if (extras > 0) {
y0 += stepy;
if (d >= c)
x0 += stepx;
TPOINT;
}
} else {
int32 c = (dx - dy) * 2; /* nonpositive */
int32 incr1 = c * 2; /* nonpositive */
int32 d = incr1 + dy;
int32 i;
for (i = 0; i < length; i++) {
y0 += stepy;
if (d > 0) { /* Pattern: */
x0 += stepx;
TPOINT; /* o */
y0 += stepy; /* o */
x0 += stepx; /* x */
TPOINT;
d += incr1;
} else {
if (d < c) { /* Pattern: */
TPOINT; /* o */
x0 += stepx; /* o */
/* x */
} else { /* Pattern: */
x0 += stepx; /* o */
TPOINT; /* o */
/* x */
}
y0 += stepy;
TPOINT;
d += incr2;
}
}
if (extras > 0) {
y0 += stepy;
if (d >= c)
x0 += stepx;
TPOINT;
}
}
}
lpoint(x1, y1); /* not TPOINT (0-length vector on resume) */
return;
/* here if LP hit interrupt during rendering */
hit:
more_vect = 1;
save_x0 = x0 * reduce;
save_y0 = y0 * reduce;
save_x1 = x1 * reduce;
save_y1 = y1 * reduce;
/* XXX should also save Z coord. for use when completing the line */
/* line_counter is static and thus will be intact upon resume */
} /* lineTwoStep */
/* draw a 3D relative vector, depth-cued when appropriate */
static void
vector3(int i, int32 dx, int32 dy, int32 dz) /* unscaled display-file units */
{
int32 x0, y0, z0, x1, y1, z1;
int on0, on1; /* ONSCREEN(x0,y0), ONSCREEN(x1,y1) */
line_counter = 037; /* reset line-style counter */
dx = VSCALE(dx); /* apply vector scale (VS60) */
dy = VSCALE(dy);
dz = VSCALE(dz * 4);
x0 = PNORM(xpos); /* (includes offset) */
y0 = PNORM(ypos);
z0 = PNORM(zpos);
xpos += dx;
ypos += dy;
zpos += dz;
x1 = PNORM(xpos);
y1 = PNORM(ypos);
z1 = PNORM(zpos);
dx = PNORM(dx);
dy = PNORM(dy);
dz = PNORM(dz);
DEBUGF(("offset, normalized vector i%d (%ld,%ld,%ld) to (%ld,%ld,%ld)\r\n",
i, (long)x0, (long)y0, (long)z0, (long)x1, (long)y1, (long)z1));
/* Maintenance Switch 3 => store delta length and tangent in xpos,ypos */
if (maint3) {
int32 adx = ABS(dx), ady = ABS(dy);
if (adx == ady) {
xpos = adx; /* or ady */
ypos = 07777; /* (12 bits?) ~ 1.0 */
} else if (adx > ady) {
xpos = dx;
ypos = 010000L * ady / adx; /* truncates */ /* XXX 07777L? */
} else /* (adx < ady) */ {
xpos = dy;
ypos = 010000L * adx / ady; /* truncates */ /* XXX 07777L? */
}
DEBUGF(("delta=0%o, tangent=0%o\r\n", xpos, ypos));
xpos = PSCALE(xpos); /* compensates for eventual PNORM */
ypos = PSCALE(ypos); /* compensates for eventual PNORM */
}
/* clip to viewport ("working surface") if necessary */
/* XXX not implemented yet */
/* check for edge conditions (XXX will change when clipping implemented) */
on0 = ONSCREEN(x0, y0);
on1 = ONSCREEN(x1, y1);
edge_indic = on0 && !on1;
edge_flag = edge_indic || (!on0 && on1);
if (edge_flag && edge_intr_ena) { /* need to clip to viewport */
/* XXX edge positions aren't right; need proper clipping,
then recompute using tangent register */
edge_xpos = x1;
edge_ypos = y1;
edge_zpos = z1;
return;
}
if (dx == 0 && dy == 0)
return; /* hardware skips null vector */
/* XXX for now, resort to scissoring:
illuminates only pixels that lie in the visible display area */
/* draw OK even when Maintenance Switch 3 is set */
/* (but updated position registers must not be used to draw vector) */
if (i && int0_scope)
if (menu)
lineTwoStep(x0 + MENU_OFFSET, y0, x1 + MENU_OFFSET, y1);
else
lineTwoStep(x0, y0, x1, y1);
/* XXX Depth cueing not yet right. Probably the way to do this is to
adapt a copy of lineTwoStep() to step intensity values along the
vector as it rasterizes it, calling illum3() instead of illum2().
The unmodified lineTwoStep() should continue to be used for 2D
vectors, to avoid the overhead of recomputing constant i values. */
/*
* In case of LP hit, recompute coords using "tangent register", because:
* (1) pixelization can lead to off-by-1 or -2
* (2) rasterization might not be same as VT48 computation
*/
if (lp0_flag) {
long tangent;
int32 adx = ABS(dx), ady = ABS(dy);
if (adx >= ady) {
tangent = 010000L * dy / dx; /* signed */
lp_ypos = y0 + tangent * (lp_xpos - x0) / 010000L;
tangent = 010000L * dz / dx;
lp_zpos = z0 + tangent * (lp_xpos - x0) / 010000L;
} else {
tangent = 010000L * dx / dy; /* signed */
lp_xpos = x0 + tangent * (lp_ypos - y0) / 010000L;
tangent = 010000L * dz / dy;
lp_zpos = z0 + tangent * (lp_ypos - y0) / 010000L;
}
DEBUGF(("adjusted LP coords (0%o,0%o)\r\n", lp_xpos, lp_ypos));
/* xpos,ypos,zpos still pertain to the original endpoint
(assuming that Maintenance Switch 3 isn't set) */
}
}
/* draw a 2D relative vector, depth-cued (constant Z) when appropriate */
/* for the sake of efficiency, vector2() does not invoke vector3() */
static void
vector2(int i, int32 dx, int32 dy) /* unscaled display-file units */
{
int32 x0, y0, x1, y1;
int on0, on1; /* ONSCREEN(x0,y0), ONSCREEN(x1,y1) */
dx = stroking ? CSCALE(dx) : VSCALE(dx); /* apply scale factor (VS60) */
dy = stroking ? CSCALE(dy) : VSCALE(dy);
x0 = PNORM(xpos); /* (includes offset) */
y0 = PNORM(ypos);
xpos += dx;
ypos += dy;
x1 = PNORM(xpos);
y1 = PNORM(ypos);
dx = PNORM(dx);
dy = PNORM(dy);
if (stroking) { /* drawing a VS60 character */
DEBUGF(("offset, normalized stroke i%d (%ld,%ld) to (%ld,%ld)\r\n",
i, (long)x0,(long)y0, (long)x1,(long)y1));
if (dx == 0 && dy == 0) { /* just display a point */
if (menu)
illum2(x0 + MENU_OFFSET, y0); /* [checks ONCRT, int0_scope] */
else
illum2(x0, y0); /* [checks ONCRT, int0_scope] */
return;
}
} else {
DEBUGF(("[offset, normalized] vector i%d (%ld,%ld) to (%ld,%ld)\r\n",
i, (long)x0,(long)y0, (long)x1,(long)y1));
line_counter = 037; /* reset line-style counter */
/* Maintenance Switch 3 => store delta length,tangent in xpos,ypos */
if (maint3) {
int32 adx = ABS(dx), ady = ABS(dy);
if (adx == ady) {
xpos = adx; /* or ady */
ypos = 07777; /* (12 bits?) ~ 1.0 */
} else if (adx > ady) {
xpos = dx;
ypos = 010000L * ady / adx; /* truncates *//* XXX 07777L? */
} else /* (adx < ady) */ {
xpos = dy;
ypos = 010000L * adx / ady; /* truncates *//* XXX 07777L? */
}
DEBUGF(("delta=0%o, tangent=0%o\r\n", xpos, ypos));
xpos = PSCALE(xpos); /* compensates for eventual PNORM */
ypos = PSCALE(ypos); /* compensates for eventual PNORM */
}
/* clip to viewport ("working surface") if necessary */
/* XXX not implemented yet */
/* check for edge conditions (XXX changes when clipping implemented) */
on0 = ONSCREEN(x0, y0);
on1 = ONSCREEN(x1, y1);
edge_indic = on0 && !on1;
edge_flag = edge_indic || (!on0 && on1);
if (edge_flag && edge_intr_ena) { /* need to clip to viewport */
/* XXX edge positions aren't right; need proper clipping,
then recompute using tangent register */
edge_xpos = x1;
edge_ypos = y1;
edge_zpos = PNORM(zpos);
return;
}
if (dx == 0 && dy == 0)
return; /* hardware skips null vectors */
}
/* XXX for now, resort to scissoring:
illuminates only pixels that lie in the visible display area */
/* draw OK even when Maintenance Switch 3 is set */
/* (but updated position registers must not be used to draw vector) */
if (i && int0_scope)
if (menu)
lineTwoStep(x0 + MENU_OFFSET, y0, x1 + MENU_OFFSET, y1);
else
lineTwoStep(x0, y0, x1, y1);
/*
* In case of LP hit, recompute coords using "tangent register", because:
* (1) distinct virtual CRT points can be mapped into the same pixel
* (2) raster computation might not match that of the actual VT48
*/
if (lp0_flag) {
long tangent;
int32 adx = ABS(dx), ady = ABS(dy);
if (adx >= ady) {
tangent = 010000L * dy / dx; /* signed */
lp_ypos = y0 + tangent * (lp_xpos - x0) / 010000L;
} else {
tangent = 010000L * dx / dy; /* signed */
lp_xpos = x0 + tangent * (lp_ypos - y0) / 010000L;
}
DEBUGF(("adjusted LP coords (0%o,0%o)\r\n", lp_xpos, lp_ypos));
/* xpos,ypos,zpos still pertain to the original endpoint
(assuming that Maintenance Switch 3 isn't set) */
}
}
/* basic vector (multiple of 45 degrees; directions numbered CCW, #0 => +X) */
static void
basic_vector(int i, int dir, int len) /* unscaled display-file units */
{
int32 dx, dy;
/* Alternatively, could be rasterized specially for each case; then
the general vector2() function could detect these special cases and
invoke this function to handle them, instead of the other way around. */
switch (dir) {
case 0:
dx = len;
dy = 0;
break;
case 1:
dx = len;
dy = len;
break;
case 2:
dx = 0;
dy = len;
break;
case 3:
dx = -len;
dy = len;
break;
case 4:
dx = -len;
dy = 0;
break;
case 5:
dx = -len;
dy = -len;
break;
case 6:
dx = 0;
dy = -len;
break;
case 7:
dx = len;
dy = -len;
break;
default: /* "can't happen" */
DEBUGF(("BUG: basic vector: illegal direction %d\r\n", dir));
return;
}
DEBUGF(("basic "));
vector2(i, dx, dy);
}
/*
* support for VS60 circle/arc option
*
* Since the literature that I have access to does not handle the case where
* starting and ending radii differ, I invented a solution that should be
* "good enough" for now: an approximation of an Archimedean spiral is drawn
* as connected individual chords, with the line-type counter applied (without
* being reset) over the entire curve.
*
* It is not known whether the direction is supposed to be clockwise or
* counterclockwise (the latter is assumed in the following code); it is
* assumed that if the starting and ending directions from the center point
* are identical, that a full circle is being specified.
*
* Although throughout the display simulation substantial effort has been
* invested to avoid using floating point, this preliminary implementation
* of the circle/arc generator does use floating point. Presumably this
* is avoidable, but the algorithmic details would need to be worked out.
* If use of floating point is a problem, #define NO_CONIC_OPT when compiling.
*/
static void
conic2(int i, int32 dcx, int32 dcy, int32 dex, int32 dey)
/* unscaled display-file units */
{
#ifdef NO_CONIC_OPT
/* just draw vector to endpoint (like real VS60 with option missing) */
vector2(i, dex, dey);
#else
int32 xs, ys, xc, yc, xe, ye, x, y, n;
double rs, re, dr, as, da;
int ons, one; /* ONSCREEN(xs,ys), ONSCREEN(xe,ye) */
static double two_pi = -1.0; /* will be set (once only) to 2*Pi */
static double k; /* will be set to 2-sqrt(4-(Pi/4)^2) */
if (two_pi < 0.0) { /* (initial entry only) */
k = atan2(1.0, 1.0);
two_pi = 8.0 * k;
k = 2.0 - sqrt(4.0 - k*k);
}
dcx = VSCALE(dcx); /* apply vector scale factor */
dcy = VSCALE(dcy);
dex = VSCALE(dex);
dey = VSCALE(dey);
xs = PNORM(xpos); /* starting pos. (includes offset) */
ys = PNORM(ypos);
xc = PNORM(xpos + dcx); /* center pos. (includes offset) */
yc = PNORM(ypos + dcy);
xe = PNORM(xpos + dex); /* ending pos. (includes offset) */
ye = PNORM(ypos + dey);
/* determine vector from center to finish */
dex -= dcx; /* PSCALEd */
dey -= dcy;
DEBUGF(("offset, normalized arc i%d s(%ld,%ld) c(%ld,%ld) e(%ld,%ld)\r\n",
i, (long)xs,(long)ys, (long)xc,(long)yc, (long)xe,(long)ye));
/* XXX not known whether Maintenance Switch 3 has any effect for arcs */
/* clip to viewport ("working surface") if necessary */
/* XXX not implemented yet [could check each chord individually] */
/* check for edge conditions (XXX changes when clipping implemented) */
/* XXX this test is very crude; should be much more complex */
ons = ONSCREEN(xs, ys);
one = ONSCREEN(xe, ye);
edge_indic = ons && !one;
edge_flag = edge_indic || (!ons && one);
if (edge_flag && edge_intr_ena) { /* need to clip to viewport */
/* XXX edge positions aren't right; need proper clipping */
edge_xpos = xe;
edge_ypos = ye;
edge_zpos = PNORM(zpos);
goto done;
}
/* XXX for now, resort to scissoring:
illuminates only pixels that lie in the visible display area */
if (dcx == 0 && dcy == 0 && dex == 0 && dey == 0)
goto done; /* skip null curve */
/* determine starting, ending radii and their maximum */
rs = PNORM(sqrt((double)dcx*dcx + (double)dcy*dcy)); /* (f.p.) */
re = PNORM(sqrt((double)dex*dex + (double)dey*dey));
dr = rs >= re ? rs : re;
/* determine starting direction from center, and included angle */
as = dcx == 0 && dcy == 0 ? 0.0 : atan2((double)-dcy, (double)-dcx);
da = (dex == 0 && dey == 0 ? 0.0 : atan2((double)dey, (double)dex)) - as;
while (da <= 0.0) /* exactly 0.0 implies full cycle */
da += two_pi;
/* determine number of chords to use;
make deviation from true curve no more than approximately one pixel */
dr = reduce / dr;
if (dr > k)
dr = k;
n = (int32)(da / sqrt(4.0*dr - dr*dr) + 1.0);
if (n < 1) /* "can't happen" */
n = 1;
else if (n > 360)
n = 360; /* arbitrarily chosen upper limit */
/* determine angular and radial step sizes */
dr = (re - rs) / n;
da /= n;
if (menu) {
xs += MENU_OFFSET;
xc += MENU_OFFSET;
xe += MENU_OFFSET;
}
line_counter = 037; /* reset line-style counter */
/* draw successive chords */
while (--n > 0) {
rs += dr;
as += da;
re = rs * cos(as);
x = xc + (re >= 0 ? (int32)(re + 0.5) : -(int32)(-re + 0.5));
re = rs * sin(as);
y = yc + (re >= 0 ? (int32)(re + 0.5) : -(int32)(-re + 0.5));
lineTwoStep(xs, ys, x, y); /* (applies continuing line style) */
skip_start = 1; /* don't double-illuminate junctions */
xs = x;
ys = y;
if (lphit_irq)
goto done; /* light-pen hit interrupted drawing */
}
lineTwoStep(xs, ys, xe, ye); /* draw final chord to exact endpoint */
skip_start = 0; /* important! */
done:
xpos += dcx + dex; /* update virtual beam position */
ypos += dcy + dey;
if (lp0_flag) {
DEBUGF(("LP hit on arc at (0%o,0%o)\r\n", lp_xpos, lp_ypos));
if (lphit_irq) {
/* XXX save parameters for drawing remaining chords */
}
}
#endif
}
static void
conic3(int i, int32 dcx, int32 dcy, int32 dcz, int32 dex, int32 dey, int32 dez)
/* unscaled display-file units */
{
#ifdef NO_CONIC_OPT
/* just draw vector to endpoint (like real VS60 with option missing) */
vector3(i, dex, dey, dez);
#else
conic2(i, dcx, dcy, dex, dey); /* XXX not properly depth cued */
zpos += PSCALE(dez);
#endif
}
/*
* VT11 character font;
* 6x8 matrix, not serpentine encoded, decenders supported as in real VT11
*/
static const unsigned char dots[0200][6] = {
{ 0x8f, 0x50, 0x20, 0x10, 0x08, 0x07 }, /* 000 lambda */
{ 0x1e, 0x21, 0x22, 0x14, 0x0c, 0x13 }, /* 001 alpha */
{ 0x00, 0x18, 0x24, 0xff, 0x24, 0x18 }, /* 002 phi */
{ 0x83, 0xc5, 0xa9, 0x91, 0x81, 0xc3 }, /* 003 SIGMA */
{ 0x00, 0x46, 0xa9, 0x91, 0x89, 0x06 }, /* 004 delta */
{ 0x03, 0x05, 0x09, 0x11, 0x21, 0x7f }, /* 005 DELTA */
{ 0x00, 0x20, 0x20, 0x3f, 0x01, 0x01 }, /* 006 iota */
{ 0x46, 0x29, 0x11, 0x2e, 0x40, 0x80 }, /* 007 gamma */
{ 0x7f, 0x80, 0x80, 0x80, 0x80, 0x7f }, /* 010 intersect */
{ 0x40, 0x3c, 0x04, 0xff, 0x04, 0x78 }, /* 011 psi */
{ 0x00, 0x10, 0x10, 0x54, 0x10, 0x10 }, /* 012 divide by */
{ 0x00, 0x60, 0x90, 0x90, 0x60, 0x00 }, /* 013 degree */
{ 0x00, 0x01, 0x00, 0x10, 0x00, 0x01 }, /* 014 therefore */
{ 0x01, 0x02, 0x3c, 0x02, 0x02, 0x3c }, /* 015 mu */
{ 0x11, 0x7f, 0x91, 0x81, 0x41, 0x03 }, /* 016 pound sterling */
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* 017 SHIFT IN */
{ 0x20, 0x40, 0x7f, 0x40, 0x7f, 0x40 }, /* 020 pi */
{ 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 }, /* 021 parallel */
{ 0x1d, 0x23, 0x40, 0x42, 0x25, 0x19 }, /* 022 OMEGA */
{ 0x1c, 0x22, 0x61, 0x51, 0x4e, 0x40 }, /* 023 sigma */
{ 0x20, 0x40, 0x40, 0x7f, 0x40, 0x40 }, /* 024 UPSILON */
{ 0x00, 0x1c, 0x2a, 0x49, 0x49, 0x00 }, /* 025 epsilon */
{ 0x10, 0x38, 0x54, 0x10, 0x10, 0x10 }, /* 026 left arrow */
{ 0x10, 0x10, 0x10, 0x54, 0x38, 0x10 }, /* 027 right arrow */
{ 0x00, 0x20, 0x40, 0xfe, 0x40, 0x20 }, /* 030 up arrow */
{ 0x00, 0x04, 0x02, 0x7f, 0x02, 0x04 }, /* 031 down arrow */
{ 0x00, 0xff, 0x80, 0x80, 0x80, 0x80 }, /* 032 GAMMA */
{ 0x00, 0x01, 0x01, 0xff, 0x01, 0x01 }, /* 033 perpendicular */
{ 0x2a, 0x2c, 0x28, 0x38, 0x68, 0xa8 }, /* 034 unequal */
{ 0x24, 0x48, 0x48, 0x24, 0x24, 0x48 }, /* 035 approx equal */
{ 0x00, 0x20, 0x10, 0x08, 0x10, 0x20 }, /* 036 vel */
{ 0xff, 0x81, 0x81, 0x81, 0x81, 0xff }, /* 037 box */
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* 040 space */
{ 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00 }, /* 041 ! */
{ 0x00, 0xe0, 0x00, 0x00, 0xe0, 0x00 }, /* 042 " */
{ 0x00, 0x24, 0xff, 0x24, 0xff, 0x24 }, /* 043 # */
{ 0x22, 0x52, 0xff, 0x52, 0x4c, 0x00 }, /* 044 $ */
{ 0x42, 0xa4, 0x48, 0x12, 0x25, 0x42 }, /* 045 % */
{ 0x66, 0x99, 0x99, 0x66, 0x0a, 0x11 }, /* 046 & */
{ 0x00, 0x00, 0x20, 0x40, 0x80, 0x00 }, /* 047 ' */
{ 0x00, 0x00, 0x3c, 0x42, 0x81, 0x00 }, /* 050 ( */
{ 0x00, 0x00, 0x81, 0x42, 0x3c, 0x00 }, /* 051 ) */
{ 0x00, 0x44, 0x28, 0xf0, 0x28, 0x44 }, /* 052 * */
{ 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10 }, /* 053 + */
{ 0x00, 0x01, 0x06, 0x00, 0x00, 0x00 }, /* 054 , */
{ 0x00, 0x10, 0x10, 0x10, 0x10, 0x10 }, /* 055 - */
{ 0x00, 0x00, 0x06, 0x06, 0x00, 0x00 }, /* 056 . */
{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x40 }, /* 057 / */
{ 0x7e, 0x85, 0x89, 0x91, 0xa1, 0x7e }, /* 060 0 */
{ 0x00, 0x41, 0xff, 0x01, 0x00, 0x00 }, /* 061 1 */
{ 0x47, 0x89, 0x91, 0x91, 0x91, 0x61 }, /* 062 2 */
{ 0x42, 0x81, 0x91, 0xb1, 0xd1, 0x8e }, /* 063 3 */
{ 0x0c, 0x14, 0x24, 0x44, 0xff, 0x04 }, /* 064 4 */
{ 0xf2, 0x91, 0x91, 0x91, 0x91, 0x8e }, /* 065 5 */
{ 0x3c, 0x46, 0x89, 0x89, 0x89, 0x46 }, /* 066 6 */
{ 0x40, 0x87, 0x88, 0x90, 0xa0, 0xc0 }, /* 067 7 */
{ 0x6e, 0x91, 0x91, 0x91, 0x91, 0x6e }, /* 070 8 */
{ 0x62, 0x91, 0x91, 0x91, 0x62, 0x3c }, /* 071 9 */
{ 0x00, 0x66, 0x66, 0x00, 0x00, 0x00 }, /* 072 : */
{ 0x00, 0x00, 0x61, 0x66, 0x00, 0x00 }, /* 073 ; */
{ 0x00, 0x18, 0x24, 0x42, 0x81, 0x00 }, /* 074 < */
{ 0x00, 0x28, 0x28, 0x28, 0x28, 0x28 }, /* 075 = */
{ 0x00, 0x81, 0x42, 0x24, 0x18, 0x00 }, /* 076 > */
{ 0x00, 0x40, 0x80, 0x9d, 0x90, 0x60 }, /* 077 ? */
{ 0x3c, 0x42, 0x91, 0xa9, 0xa9, 0x72 }, /* 100 @ */
{ 0x3f, 0x48, 0x88, 0x88, 0x48, 0x3f }, /* 101 A */
{ 0x81, 0xff, 0x91, 0x91, 0x91, 0x6e }, /* 102 B */
{ 0x3c, 0x42, 0x81, 0x81, 0x81, 0x42 }, /* 103 C */
{ 0x81, 0xff, 0x81, 0x81, 0x42, 0x3c }, /* 104 D */
{ 0x81, 0xff, 0x91, 0x91, 0x91, 0xc3 }, /* 105 E */
{ 0x81, 0xff, 0x91, 0x90, 0x80, 0xc0 }, /* 106 F */
{ 0x3c, 0x42, 0x81, 0x89, 0x89, 0x4f }, /* 107 G */
{ 0xff, 0x10, 0x10, 0x10, 0x10, 0xff }, /* 110 H */
{ 0x00, 0x81, 0xff, 0x81, 0x00, 0x00 }, /* 111 I */
{ 0x0e, 0x01, 0x01, 0x81, 0xfe, 0x80 }, /* 112 J */
{ 0xff, 0x08, 0x10, 0x28, 0x44, 0x83 }, /* 113 K */
{ 0x81, 0xff, 0x81, 0x01, 0x01, 0x03 }, /* 114 L */
{ 0xff, 0x40, 0x30, 0x30, 0x40, 0xff }, /* 115 M */
{ 0xff, 0x20, 0x10, 0x08, 0x04, 0xff }, /* 116 N */
{ 0x3c, 0x42, 0x81, 0x81, 0x42, 0x3c }, /* 117 O */
{ 0x81, 0xff, 0x90, 0x90, 0x90, 0x60 }, /* 120 P */
{ 0x3c, 0x42, 0x81, 0x8f, 0x42, 0x3d }, /* 121 Q */
{ 0x81, 0xff, 0x90, 0x98, 0x94, 0x63 }, /* 122 R */
{ 0x22, 0x51, 0x91, 0x91, 0x89, 0x46 }, /* 123 S */
{ 0xc0, 0x80, 0x81, 0xff, 0x81, 0xc0 }, /* 124 T */
{ 0xfe, 0x01, 0x01, 0x01, 0x01, 0xfe }, /* 125 U */
{ 0xff, 0x02, 0x04, 0x08, 0x10, 0xe0 }, /* 126 V */
{ 0xff, 0x02, 0x0c, 0x0c, 0x02, 0xff }, /* 127 W */
{ 0xc3, 0x24, 0x18, 0x18, 0x24, 0xc3 }, /* 130 X */
{ 0x00, 0xe0, 0x10, 0x0f, 0x10, 0xe0 }, /* 131 Y */
{ 0x83, 0x85, 0x89, 0x91, 0xa1, 0xc1 }, /* 132 Z */
{ 0x00, 0x00, 0xff, 0x81, 0x81, 0x00 }, /* 133 [ */
{ 0x00, 0x40, 0x20, 0x10, 0x08, 0x04 }, /* 134 \ */
{ 0x00, 0x00, 0x81, 0x81, 0xff, 0x00 }, /* 135 ] */
{ 0x00, 0x10, 0x20, 0x40, 0x20, 0x10 }, /* 136 ^ */
{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x00 }, /* 137 _ */
/* for all lowercase characters, first column is just a "descender" flag: */
{ 0x00, 0x00, 0x80, 0x40, 0x20, 0x00 }, /* 140 ` */
{ 0x00, 0x26, 0x29, 0x29, 0x2a, 0x1f }, /* 141 a */
{ 0x00, 0xff, 0x12, 0x21, 0x21, 0x1e }, /* 142 b */
{ 0x00, 0x1e, 0x21, 0x21, 0x21, 0x12 }, /* 143 c */
{ 0x00, 0x1e, 0x21, 0x21, 0x12, 0xff }, /* 144 d */
{ 0x00, 0x1e, 0x29, 0x29, 0x29, 0x19 }, /* 145 e */
{ 0x00, 0x20, 0x7f, 0xa0, 0xa0, 0x80 }, /* 146 f */
{ 0x01, 0x78, 0x85, 0x85, 0x49, 0xfe }, /* 147 g */
{ 0x00, 0xff, 0x10, 0x20, 0x20, 0x1f }, /* 150 h */
{ 0x00, 0x00, 0x21, 0xbf, 0x01, 0x00 }, /* 151 i */
{ 0x01, 0x02, 0x01, 0x81, 0xfe, 0x00 }, /* 152 j */
{ 0x00, 0xff, 0x08, 0x14, 0x22, 0x21 }, /* 153 k */
{ 0x00, 0x00, 0xfe, 0x01, 0x01, 0x00 }, /* 154 l */
{ 0x00, 0x3f, 0x20, 0x3f, 0x20, 0x3f }, /* 155 m */
{ 0x00, 0x3f, 0x10, 0x20, 0x20, 0x1f }, /* 156 n */
{ 0x00, 0x1e, 0x21, 0x21, 0x21, 0x1e }, /* 157 o */
{ 0x01, 0xff, 0x48, 0x84, 0x84, 0x78 }, /* 160 p */
{ 0x01, 0x78, 0x84, 0x84, 0x48, 0xff }, /* 161 q */
{ 0x00, 0x3f, 0x08, 0x10, 0x20, 0x20 }, /* 162 r */
{ 0x00, 0x12, 0x29, 0x29, 0x29, 0x26 }, /* 163 s */
{ 0x00, 0x20, 0xfe, 0x21, 0x21, 0x00 }, /* 164 t */
{ 0x00, 0x3e, 0x01, 0x01, 0x02, 0x3f }, /* 165 u */
{ 0x00, 0x3c, 0x02, 0x01, 0x02, 0x3c }, /* 166 v */
{ 0x00, 0x3e, 0x01, 0x1e, 0x01, 0x3e }, /* 167 w */
{ 0x00, 0x23, 0x14, 0x08, 0x14, 0x23 }, /* 170 x */
{ 0x01, 0xf8, 0x05, 0x05, 0x09, 0xfe }, /* 171 y */
{ 0x00, 0x23, 0x25, 0x29, 0x31, 0x21 }, /* 172 z */
{ 0x00, 0x18, 0x66, 0x81, 0x81, 0x00 }, /* 173 { */
{ 0x00, 0x00, 0xe7, 0x00, 0x00, 0x00 }, /* 174 | */
{ 0x00, 0x00, 0x81, 0x81, 0x66, 0x18 }, /* 175 } */
{ 0x00, 0x0c, 0x10, 0x08, 0x04, 0x18 }, /* 176 ~ */
{ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff } /* 177 rubout */
};
/*
* VS60 character stroke table
*
* stroke[] contains "prototype" encodings for all vector strokes (visible and
* invisible) needed to draw each character at a standard size. The actual
* display is of course properly italicized, positioned, scaled, and rotated.
*
* Variable-length entries are used; each character stroke sequence is
* terminated by a 0-valued byte. Pointers to the appropriate data for all
* characters are stored into sstroke[] during a one-time initialization.
*
* The prototype strokes are for the most part constrained to a 4x6 unit area,
* except for a few cases that are handled by kludging the coordinates.
* Coordinates are relative to the left end of the character baseline.
*
* A prototype stroke is encoded as 8 bits SVXXXYYY:
* S = 0 if YYY is correct as is
* 1 if YYY needs to have 2 subtracted
* V = 0 if stroke is invisible (move)
* 1 if stroke is visible (draw)
* XXX = final X coord of stroke (0..4; 7 => -1)
* YYY = final Y coord of stroke (0..6)
*/
static const unsigned char stroke[] = {
/*
* While based on the actual VT48 strokes, these have been tweaked
* (especially the lower-case letters, which had erratic sizes) to
* improve their appearance and/or reduce the number of strokes.
* Several of the special symbols (e.g. alpha, delta, iota) could
* be further improved, but I didn't want to make them look too
* different from the original. Note that VS60 screen photos
* disagree, for several characters, with the (incomplete) chart of
* strokes given in the VT48 manual. (There could have been ROM changes.)
*
* The simulated character sizes are not exact at all scales, but there
* is no really good way to fix this without spoiling the appearance.
* char. scale VS60 units simulation units (pixel has size!)
* 1/2 5 x 7 5 x 7
* 1 10 x 14 9 x 13
* 3/2 15 x 21 13 x 19
* 2 20 x 28 17 x 25
*/
0111, 0123, 0006, 0115, 0131, 0140, 0, /* 000 lambda */
0042, 0132, 0114, 0103, 0112, 0134, 0144, 0, /* 001 alpha */
0011, 0103, 0115, 0135, 0143, 0131, 0111, 0010,
0146, 0, /* 002 phi */
0040, 0100, 0133, 0106, 0146, 0, /* 003 SIGMA */
0022, 0111, 0120, 0131, 0113, 0115, 0124, 0, /* 004 delta */
0140, 0124, 0100, 0, /* 005 DELTA */
0006, 0126, 0120, 0140, 0, /* 006 iota */
0006, 0115, 0131, 0120, 0111, 0135, 0146, 0, /* 007 gamma */
0104, 0116, 0136, 0144, 0140, 0, /* 010 intersect */
0010, 0136, 0044, 0142, 0131, 0111, 0102, 0104, 0, /* 011 psi */
0022, 0122, 0003, 0143, 0024, 0124, 0, /* 012 divide by */
0024, 0115, 0126, 0135, 0124, 0, /* 013 degree */
0001, 0101, 0025, 0125, 0041, 0141, 0, /* 014 therefore */
0111, 0115, 0012, 0121, 0131, 0142, 0045, 0142,
0151, 0, /* 015 mu */
0105, 0116, 0126, 0135, 0013, 0173, 0001, 0120,
0130, 0141, 0, /* 016 pound sterling */
0, /* 017 SHIFT IN */
0003, 0114, 0144, 0034, 0130, 0010, 0114, 0, /* 020 pi */
0010, 0116, 0036, 0130, 0, /* 021 parallel */
0110, 0111, 0102, 0104, 0115, 0135, 0144, 0142,
0131, 0130, 0140, 0, /* 022 OMEGA */
0025, 0134, 0132, 0120, 0110, 0102, 0104, 0146, 0, /* 023 sigma */
0010, 0136, 0046, 0116, 0105, 0, /* 024 UPSILON */
0003, 0133, 0045, 0136, 0116, 0105, 0101, 0110,
0130, 0141, 0, /* 025 epsilon */
0042, 0102, 0113, 0011, 0102, 0, /* 026 left arrow */
0002, 0142, 0133, 0031, 0142, 0, /* 027 right arrow */
0020, 0124, 0133, 0013, 0124, 0, /* 030 up arrow */
0024, 0120, 0131, 0011, 0120, 0, /* 031 down arrow */
0106, 0146, 0144, 0, /* 032 GAMMA */
0140, 0026, 0120, 0, /* 033 perpendicular */
0001, 0145, 0044, 0104, 0002, 0142, 0, /* 034 unequal */
0001, 0112, 0131, 0142, 0044, 0133, 0114, 0103, 0, /* 035 approx equal */
0016, 0125, 0135, 0146, 0, /* 036 vel */
0106, 0146, 0140, 0100, 0, /* 037 box */
0, /* 040 space */
0020, 0120, 0021, 0125, 0, /* 041 ! */
0004, 0126, 0046, 0124, 0, /* 042 " */
0012, 0116, 0036, 0132, 0043, 0103, 0005, 0145, 0, /* 043 # */
0001, 0110, 0130, 0141, 0142, 0133, 0113, 0104,
0105, 0116, 0136, 0145, 0026, 0120, 0, /* 044 $ */
0146, 0116, 0105, 0114, 0125, 0116, 0032, 0141,
0130, 0121, 0132, 0, /* 045 % */
0040, 0104, 0105, 0116, 0126, 0135, 0134, 0101,
0110, 0120, 0142, 0, /* 046 & */
0014, 0136, 0, /* 047 ' */
0030, 0112, 0114, 0136, 0, /* 050 ( */
0010, 0132, 0134, 0116, 0, /* 051 ) */
0002, 0146, 0026, 0122, 0042, 0106, 0, /* 052 * */
0021, 0125, 0003, 0143, 0, /* 053 + */
0211, 0120, 0121, 0, /* 054 , */
0003, 0143, 0, /* 055 - */
0020, 0120, 0, /* 056 . */
0146, 0, /* 057 / */
0001, 0145, 0136, 0116, 0105, 0101, 0110, 0130,
0141, 0145, 0, /* 060 0 */
0010, 0130, 0020, 0126, 0115, 0, /* 061 1 */
0005, 0116, 0136, 0145, 0144, 0100, 0140, 0, /* 062 2 */
0001, 0110, 0130, 0141, 0142, 0133, 0113, 0005,
0116, 0136, 0145, 0144, 0133, 0, /* 063 3 */
0030, 0136, 0025, 0102, 0142, 0, /* 064 4 */
0001, 0110, 0130, 0141, 0143, 0134, 0114, 0103,
0106, 0146, 0, /* 065 5 */
0002, 0113, 0133, 0142, 0141, 0130, 0110, 0101,
0105, 0116, 0136, 0145, 0, /* 066 6 */
0006, 0146, 0120, 0, /* 067 7 */
0013, 0133, 0142, 0141, 0130, 0110, 0101, 0102,
0113, 0104, 0105, 0116, 0136, 0145, 0144, 0133, 0, /* 070 8 */
0001, 0110, 0130, 0141, 0145, 0136, 0116, 0105,
0104, 0113, 0133, 0144, 0, /* 071 9 */
0022, 0122, 0024, 0124, 0, /* 072 : */
0010, 0121, 0122, 0024, 0124, 0, /* 073 ; */
0030, 0103, 0136, 0, /* 074 < */
0002, 0142, 0004, 0144, 0, /* 075 = */
0010, 0143, 0116, 0, /* 076 > */
0020, 0120, 0021, 0122, 0144, 0145, 0136, 0116,
0105, 0104, 0, /* 077 ? */
0030, 0110, 0101, 0104, 0115, 0145, 0141, 0121,
0112, 0113, 0124, 0134, 0131, 0, /* 100 @ */
0104, 0116, 0136, 0144, 0140, 0042, 0102, 0, /* 101 A */
0106, 0136, 0145, 0144, 0133, 0103, 0033, 0142,
0141, 0130, 0100, 0, /* 102 B */
0041, 0130, 0110, 0101, 0105, 0116, 0136, 0145, 0, /* 103 C */
0106, 0136, 0145, 0141, 0130, 0100, 0, /* 104 D */
0003, 0133, 0046, 0106, 0100, 0140, 0, /* 105 E */
0106, 0146, 0033, 0103, 0, /* 106 F */
0023, 0143, 0141, 0130, 0110, 0101, 0105, 0116,
0136, 0145, 0, /* 107 G */
0106, 0003, 0143, 0046, 0140, 0, /* 110 H */
0010, 0130, 0020, 0126, 0016, 0136, 0, /* 111 I */
0001, 0110, 0120, 0131, 0136, 0, /* 112 J */
0106, 0046, 0102, 0024, 0140, 0, /* 113 K */
0006, 0100, 0140, 0, /* 114 L */
0106, 0123, 0146, 0140, 0, /* 115 M */
0106, 0140, 0146, 0, /* 116 N */
0001, 0105, 0116, 0136, 0145, 0141, 0130, 0110,
0101, 0, /* 117 O */
0106, 0136, 0145, 0144, 0133, 0103, 0, /* 120 P */
0030, 0110, 0101, 0105, 0116, 0136, 0145, 0141,
0130, 0031, 0140, 0, /* 121 Q */
0106, 0136, 0145, 0144, 0133, 0103, 0033, 0140, 0, /* 122 R */
0001, 0110, 0130, 0141, 0142, 0133, 0113, 0104,
0105, 0116, 0136, 0145, 0, /* 123 S */
0020, 0126, 0006, 0146, 0, /* 124 T */
0006, 0101, 0110, 0130, 0141, 0146, 0, /* 125 U */
0006, 0120, 0146, 0, /* 126 V */
0006, 0100, 0123, 0140, 0146, 0, /* 127 W */
0146, 0006, 0140, 0, /* 130 X */
0020, 0123, 0106, 0046, 0123, 0, /* 131 Y */
0006, 0146, 0100, 0140, 0033, 0113, 0, /* 132 Z */
0030, 0110, 0116, 0136, 0, /* 133 [ */
0006, 0140, 0, /* 134 \ */
0010, 0130, 0136, 0116, 0, /* 135 ] */
0003, 0126, 0143, 0, /* 136 ^ */
0140, 0, /* 137 _ */
0016, 0134, 0, /* original was backward */ /* 140 ` */
0032, 0112, 0101, 0110, 0130, 0133, 0124, 0114, 0, /* 141 a */
0006, 0100, 0120, 0131, 0133, 0124, 0104, 0, /* 142 b */
0033, 0124, 0114, 0103, 0101, 0110, 0120, 0131, 0, /* 143 c */
0036, 0130, 0110, 0101, 0103, 0114, 0134, 0, /* 144 d */
0002, 0132, 0133, 0124, 0114, 0103, 0101, 0110,
0120, 0, /* 145 e */
0010, 0115, 0126, 0136, 0145, 0023, 0103, 0, /* 146 f */
0200, 0320, 0331, 0134, 0114, 0103, 0101, 0110,
0130, 0, /* 147 g */
0106, 0004, 0124, 0133, 0130, 0, /* 150 h */
0020, 0124, 0025, 0125, 0, /* 151 i */
0201, 0310, 0320, 0331, 0134, 0035, 0135, 0, /* 152 j */
0105, 0034, 0101, 0023, 0130, 0, /* 153 k */
0010, 0130, 0020, 0126, 0116, 0, /* 154 l */
0104, 0114, 0122, 0134, 0144, 0140, 0, /* 155 m */
0104, 0124, 0133, 0130, 0, /* 156 n */
0010, 0120, 0131, 0133, 0124, 0114, 0103, 0101,
0110, 0, /* 157 o */
0200, 0104, 0124, 0133, 0131, 0120, 0100, 0, /* 160 p */
0030, 0110, 0101, 0103, 0114, 0134, 0330, 0341, 0, /* 161 q */
0104, 0124, 0133, 0, /* 162 r */
0001, 0110, 0120, 0131, 0122, 0112, 0103, 0114,
0124, 0133, 0, /* 163 s */
0030, 0121, 0125, 0034, 0114, 0, /* 164 t */
0014, 0111, 0120, 0130, 0141, 0144, 0, /* 165 u */
0004, 0120, 0144, 0, /* 166 v */
0004, 0102, 0110, 0122, 0130, 0142, 0144, 0, /* 167 w */
0134, 0004, 0130, 0, /* 170 x */
0210, 0120, 0134, 0004, 0120, 0, /* 171 y */
0004, 0134, 0100, 0130, 0, /* 172 z */
0030, 0121, 0122, 0113, 0124, 0125, 0136, 0, /* 173 { */
0020, 0122, 0024, 0126, 0, /* 174 | */
0010, 0121, 0122, 0133, 0124, 0125, 0116, 0, /* 175 } */
0003, 0114, 0132, 0143, 0, /* 176 ~ */
0140, 0146, 0106, 0100, 0010, 0116, 0026, 0120,
0030, 0136, 0 /* 177 rubout */
};
/* pointers to start of stroke data for each character */
static const unsigned char *sstroke[128] = { NULL }; /* init. at run time */
/* character generator;
* supports control characters, POPR on terminating character (VS60)
*/
static int /* returns nonzero iff VS60 char terminate feature triggered */
character(int c)
{
/* following 3 tables map cs_index to adjustments for sub/superscript */
static const unsigned char sus_left[4] =
{PSCALE(0), PSCALE(1), PSCALE(2), PSCALE(3)};
static const unsigned char sub_down[4] =
{PSCALE(2), PSCALE(4), PSCALE(6), PSCALE(8)};
static const unsigned char sup_up[4] =
{PSCALE(5), PSCALE(10), PSCALE(15), PSCALE(20)};
int x, y;
int32 xbase, ybase, xnext, ynext;
char_buf = c;
if (shift_out) {
if (c >= 040) {
char_irq = 1; /* will generate a char intr. */
return 0; /* presumably, no POPR on term? */
}
if (c == 017) { /* SHIFT IN */
shift_out = 0;
goto cesc;
}
} else { /* !shift_out */
if (c <= 040) {
switch (c) {
case 010: /* BACKSPACE */
if (char_rotate)
ypos -= CSCALE(vt11_csp_w);
else
xpos -= CSCALE(vt11_csp_w);
break;
case 012: /* LINE FEED */
if (char_rotate)
xpos += CSCALE(vt11_csp_h);
else
ypos -= CSCALE(vt11_csp_h);
break;
case 015: /* CARRIAGE RETURN */
if (char_rotate)
ypos = yoff;
else
xpos = xoff;
break;
case 016: /* SHIFT OUT */
shift_out = 1;
break;
case 021: /* SUPERSCRIPT */
if (VT11)
break;
if (char_rotate) {
xpos -= sup_up[cs_index];
ypos -= sus_left[cs_index];
} else {
xpos -= sus_left[cs_index];
ypos += sup_up[cs_index];
}
if (cs_index > 0)
char_scale = csi2csf[--cs_index];
break;
case 022: /* SUBSCRIPT */
if (VT11)
break;
if (char_rotate) {
xpos += sub_down[cs_index];
ypos -= sus_left[cs_index];
} else {
xpos -= sus_left[cs_index];
ypos -= sub_down[cs_index];
}
if (cs_index > 0)
char_scale = csi2csf[--cs_index];
break;
case 023: /* END SUPERSCRIPT */
if (VT11)
break;
if (cs_index < 3)
char_scale = csi2csf[++cs_index];
if (char_rotate) {
xpos += sup_up[cs_index];
ypos += sus_left[cs_index];
} else {
xpos += sus_left[cs_index];
ypos -= sup_up[cs_index];
}
break;
case 024: /* END SUBSCRIPT */
if (VT11)
break;
if (cs_index < 3)
char_scale = csi2csf[++cs_index];
if (char_rotate) {
xpos -= sub_down[cs_index];
ypos += sus_left[cs_index];
} else {
xpos += sus_left[cs_index];
ypos += sub_down[cs_index];
}
break;
case 040: /* SPACE */
goto space;
default: /* other control codes ignored */
break;
}
goto cesc;
}
}
/* VT11/VS60 doesn't draw any part of a character if its *baseline* is
(partly) offscreen; thus the top of a character might be clipped */
/* (no allowance for descender, italic, or interchar. spacing) */
/* virtual CRT coordinates of this and the next character's "origin": */
xbase = xnext = PNORM(xpos);
ybase = ynext = PNORM(ypos);
if (char_rotate)
ynext += (vt11_csp_w <= 12 ? 10 : 11);
else
xnext += (vt11_csp_w <= 12 ? 10 : 11);
edge_indic = ONSCREEN(xbase, ybase) && !ONSCREEN(xnext, ynext);
edge_flag = edge_indic ||
!ONSCREEN(xbase, ybase) && ONSCREEN(xnext, ynext);
/* (scaling cannot make spacing so large that it crosses the
"working surface" while going from offscreen to offscreen) */
if (edge_flag && edge_intr_ena) {
edge_irq = 1;
goto space;
}
if (!ONSCREEN(xbase, ybase) || !ONSCREEN(xnext, ynext))
goto space;
/* plot a (nominally on-screen) graphic symbol */
if (VT11) {
unsigned char col, prvcol;
/* plot a graphic symbol (unscaled, unrotated) using a dot matrix */
/* not drawn in a serpentine manner; supports control characters */
/* draw pattern using 2x2 dot size, with fudges for spacing & italics */
/* (looks very nice under all conditions at full resolution) */
if (c >= 0140) { /* lower-case */
if (dots[c][0]) /* flag: with descender */
ybase -= 4;
x = 1; /* skip first column (descender flag) */
} else /* no descender */
x = 0;
prvcol = 0;
col = dots[c][x]; /* starting column bit pattern */
for (; x < 6; ++x) {
int xllc = 2*x, yllc = 0;
unsigned char nxtcol = (x == 5) ? 0 : dots[c][x+1];
/* no LP hit on first or last column */
lp_suppress = x == 0 || x == 5;
for (y = 0; y < 8; ++y) {
int delay_skew;
int compress = vt11_csp_w <= 12 && x == 2;
int dot = col & (1<<y), nxtdot;
if (dot) {
illum2(xbase + xllc, ybase + yllc);
if (!compress || (nxtdot = nxtcol & (1<<y)) == 0)
illum2(xbase + xllc + 1, ybase + yllc);
}
if (italics) {
delay_skew = 0;
if ((y % 3) != 0
&& !(delay_skew = ((prvcol & (3<<y))>>y) == 2))
++xllc; /* shift within selected dots */
}
++yllc;
if (dot) {
illum2(xbase + xllc, ybase + yllc);
if (!compress || nxtdot == 0)
illum2(xbase + xllc + 1, ybase + yllc);
}
if (italics && delay_skew)
++xllc; /* shift between selected dots */
++yllc;
}
if (vt11_csp_w <= 12 && x == 2) /* narrow spacing: */
--xbase; /* slight compression */
prvcol = col;
col = nxtcol;
}
lp_suppress = 0;
} else { /* VS60 */
const unsigned char *p; /* -> stroke data */
unsigned char s; /* encoded stroke */
int32 xlast, ylast; /* "beam follower" within character */
int32 xp = xpos, yp = ypos; /* save these (altered by vector2()) */
/* plot a graphic symbol using vector strokes */
/* initialize starting stroke pointers upon first use only */
if (sstroke[0] == NULL) {
p = stroke; /* -> stroke data */
for (s = 0; s < 128; ++s) { /* for each ASCII code value s */
sstroke[s] = p; /* code's stroke list starts here */
while (*p++) /* 0 terminates the data */
;
}
}
stroking = 1; /* prevents stroke clipping etc. and
tells vector2() to apply global
character scale factor */
xlast = ylast = 0;
for (p = sstroke[c]; (s = *p) != 0; ++p) {
xnext = (s & 0070) >> 3;
if (xnext == 7)
xnext = -1; /* (kludge needed for pound sterling) */
ynext = s & 0007; /* delay stretching for just a moment */
if (s & 0200)
ynext -= 2; /* kludge for stroke below baseline */
xnext *= 2;
if (italics)
xnext += ynext;
ynext *= 2; /* safe to stretch now */
if (s & 0100) { /* visible stroke */
int32 dx = xnext - xlast, /* (okay if both 0) */
dy = ynext - ylast;
if (char_rotate)
vector2(1, -dy, dx);
else
vector2(1, dx, dy);
} else /* invisible stroke, can do faster */
if (char_rotate) {
xpos = xp - CSCALE(ynext);
ypos = yp + CSCALE(xnext);
} else {
xpos = xp + CSCALE(xnext);
ypos = yp + CSCALE(ynext);
}
xlast = xnext;
ylast = ynext;
skip_start = (s & 0100) && (p[1] & 0100); /* avoid bright dot */
}
/* skip_start was reset to 0 by the last iteration! */
stroking = 0;
xpos = xp; /* restore for use in spacing (below) */
ypos = yp;
} /* end of graphic character drawing */
space:
if (char_rotate)
ypos += CSCALE(vt11_csp_w);
else
xpos += CSCALE(vt11_csp_w);
/* There may have been multiple LP hits during drawing;
the last one is the only one that can be reported. */
cesc:
if (char_escape && c == char_term) { /* (VS60) */
pop(1);
return 1;
} else
return 0;
}
/*
* Perform one display processor "cycle":
* If display processor is halted or awaiting sync, just performs "background"
* maintenance tasks and returns 0.
* Otherwise, completes any pending second CHAR or BSVECT (must be a RESUME
* after interrupt on first CHAR or BSVECT), or fetches one word from the
* display file and processes it. May post an interrupt; returns 1 if display
* processor is still running, or 0 if halted or an interrupt was posted.
*
* word_number keeps track of the state of multi-word graphic data parsing;
* word_number also serves to keep track of half-word for graphic data having
* two independent entities encoded within one word (CHAR or BSVECT).
* Note that, for the VT11, there might be control words (e.g. JMPA) embedded
* within the data! (We don't know of any application that exploits this.)
*/
int
vt11_cycle(int us, int slowdown)
{
static vt11word inst;
static int i;
static int32 x, y, z, ex, ey, sxo, syo, szo;
int c;
int32 ez;
static uint32 usec = 0; /* cumulative */
static uint32 msec = 0; /* ditto */
uint32 new_msec;
INIT
/* keep running time counter; track state even when processor is idle */
new_msec = (usec += us) / 1000;
if (msec / BLINK_COUNT != new_msec / BLINK_COUNT)
blink_off = !blink_off;
/* if awaiting sync, look for next frame start */
if (sync_period && (msec / sync_period != new_msec / sync_period))
sync_period = 0; /* start next frame */
msec = new_msec;
if ((sync_period || maint1 || !busy) && !maint2)
goto age_ret; /* just age the display */
/* fetch next word from display file (if needed) and process it */
if (word_number != 1 || (graphic_mode != CHAR && graphic_mode != BSVECT)) {
time_out = vt_fetch((uint32)((DPC+reloc)&0777777), &inst);
DPC += 2;
if (time_out)
goto bus_timeout;
DEBUGF(("0%06o: 0%06o\r\n",
(unsigned)(DPC - 2 + reloc) & 0777777, (unsigned)inst));
if (finish_jmpa)
goto jmpa;
if (finish_jsra)
goto jsra;
}
/* else have processed only half the CHAR or BSVECT data word so far */
fetched:
if (TESTBIT(inst,15)) { /* control */
unsigned op = GETFIELD(inst,14,11); /* bits 14-11 */
#if 1 /* XXX not sure about VT11 behavior */
if (VS60)
#endif
word_number = 0; /* according to VT48 ES */
switch (op) {
case 7: /* Set Graphic Mode 0111 */
case 011: /* Set Graphic Mode 1001 */
if (VT11)
goto bad_ins;
/*FALLTHRU*/
case 010: /* Set Graphic Mode 1000 */
if (VT11) {
DEBUGF(("SGM 1000 IGNORED\r\n"));
break;
}
/*FALLTHRU*/
case 0: /* Set Graphic Mode 0000 */
case 1: /* Set Graphic Mode 0001 */
case 2: /* Set Graphic Mode 0010 */
case 3: /* Set Graphic Mode 0011 */
case 4: /* Set Graphic Mode 0100 */
case 5: /* Set Graphic Mode 0101 */
case 6: /* Set Graphic Mode 0110 */
DEBUGF(("Set Graphic Mode %u", op));
graphic_mode = op;
word_number = 0; /* XXX redundant? (see above) */
shift_out = 0; /* XXX is this right? */
if (TESTBIT(inst,10)) {
intensity = GETFIELD(inst,9,7);
DEBUGF((" intensity=%d", (int)intensity));
}
if (TESTBIT(inst,6)) {
lp0_intr_ena = TESTBIT(inst,5);
DEBUGF((" lp0_intr_ena=%d", (int)lp0_intr_ena));
}
if (TESTBIT(inst,4)) {
blink_ena = TESTBIT(inst,3);
DEBUGF((" blink=%d", (int)blink_ena));
}
if (TESTBIT(inst,2)) {
line_type = GETFIELD(inst,1,0);
DEBUGF((" line_type=%d", (int)line_type));
}
DEBUGF(("\r\n"));
break;
case 012: /* 1010: Load Name Register */
if (VT11)
goto bad_ins;
name = GETFIELD(inst,10,0);
DEBUGF(("Load Name Register name=0%o\r\n", name));
{ static unsigned nmask[4] = { 0, 03777, 03770, 03600 };
if (search != 0 && ((name^assoc_name) & nmask[search]) == 0)
name_irq = 1; /* will cause name-match interrupt */
}
break;
case 013: /* 1011: Load Status C */
if (VT11)
goto bad_ins;
DEBUGF(("Load Status C"));
if (TESTBIT(inst,9)) {
char_rotate = TESTBIT(inst,8);
DEBUGF((" char_rotate=d", (int)char_rotate));
}
if (TESTBIT(inst,7)) {
cs_index = GETFIELD(inst,6,5); /* 0, 1, 2, 3 */
char_scale = csi2csf[cs_index]; /* for faster CSCALE macro */
DEBUGF((" cs_index=%d(x%d/4)", (int)cs_index, (int)char_scale));
}
if (TESTBIT(inst,4)) {
vector_scale = GETFIELD(inst,3,0);
DEBUGF((" vector_scale=%d/4", (int)vector_scale));
}
DEBUGF(("\r\n"));
break;
case 014: /* 1100__ */
if (VT11) /* other bits are "spare" */
op = 0; /* always Display Jump Absolute */
else
op = GETFIELD(inst,10,9);
switch (op) {
case 00: /* 110000: Display Jump Absolute */
finish_jmpa = 1;
break;
jmpa:
finish_jmpa = 0;
DPC = inst & ~1;
DEBUGF(("Display Jump Absolute 0%06o\r\n", (unsigned)inst));
break;
case 01: /* 110001: Display Jump Relative */
ez = GETFIELD(inst,7,0);/* relative address (words) */
ez *= 2; /* convert to bytes */
/* have to be careful; DPC is unsigned */
if (TESTBIT(inst,8))
DPC -= ez;
else
DPC += ez;
/* DPC was already incremented by 2 */
DEBUGF(("Display Jump Relative %c0%o\r\n",
"+-"[TESTBIT(inst,8)], (unsigned)ez));
break;
case 02: /* 110010: Display Jump to Subroutine Absolute */
finish_jsra = 1;
break;
jsra:
finish_jsra = 0;
push(); /* save return address and parameters */
DPC = inst & ~1;
DEBUGF(("Display Jump to Subroutine Absolute 0%06o\r\n",
(unsigned)inst));
#if 1 /* VT48 manual and ES disagree with the diagnostic test! */
jsr = 1; /* the diagnostic test needs this */
goto check; /* (break would set jsr = 0) */
#else
break;
#endif
case 03: /* 110011: Display Jump to Subroutine Relative */
ez = GETFIELD(inst,7,0);/* relative address (words) */
ez *= 2; /* convert to bytes */
push(); /* save return address and parameters */
/* have to be careful; DPC is unsigned */
if (TESTBIT(inst,8))
DPC -= ez;
else
DPC += ez;
/* DPC was already incremented by 2 */
DEBUGF(("Display Jump to Subroutine Relative %c0%o\r\n",
"+-"[TESTBIT(inst,8)], (unsigned)ez));
#if 0 /* VT48 manual and ES disagree with the diagnostic test! */
jsr = 1; /* the hardware actually needs this? */
goto check; /* (break would set jsr = 0) */
#else
break;
#endif
}
break;
case 015: /* 1101__ */
if (VT11)
DEBUGF(("Display NOP\r\n"));
else {
op = GETFIELD(inst,10,9);
switch (op) {
case 00: /* 110100: Load Scope Selection */
/* also used as Display NOP */
DEBUGF(("Load Scope Selection"));
c = TESTBIT(inst,8);
DEBUGF((" console=%d", c));
if (TESTBIT(inst,7)) {
ez = TESTBIT(inst,6);
DEBUGF((" blank=%d", (int)!ez));
if (c)
int1_scope = ez;
else
int0_scope = ez;
}
if (TESTBIT(inst,5)) {
ez = TESTBIT(inst,4);
DEBUGF((" lp_intr_ena=%d", (int)ez));
if (c)
lp1_intr_ena = ez;
else
lp0_intr_ena = ez;
}
if (TESTBIT(inst,3)) {
ez = TESTBIT(inst,2);
DEBUGF((" lp_sw_intr_ena=%d", (int)ez));
if (c)
lp1_sw_intr_ena = ez;
else
lp0_sw_intr_ena = ez;
}
DEBUGF(("\r\n"));
break;
case 01: /* 110101: Display POP Not Restore */
DEBUGF(("Display POP Not Restore\r\n"));
pop(0); /* sets new DPC as side effect */
break;
case 10: /* 110110: Display POP Restore */
DEBUGF(("Display POP Restore\r\n"));
pop(1); /* sets new DPC as side effect */
break;
default: /* 110111: undocumented -- ignored? */
DEBUGF(("Display NOP?\r\n"));
}
}
break;
case 016: /* 1110: Load Status A */
DEBUGF(("Load Status A"));
if ((internal_stop = TESTBIT(inst,10)) != 0)/* 11101 Display Stop */
DEBUGF((" stop"));
if (TESTBIT(inst,9)) {
stop_intr_ena = TESTBIT(inst,8);
DEBUGF((" stop_intr_ena=%d", (int)stop_intr_ena));
}
if (TESTBIT(inst,7)) {
lp_intensify = !TESTBIT(inst,6);
DEBUGF((" lp_intensify=%d", (int)lp_intensify));
}
if (TESTBIT(inst,5)) {
italics = TESTBIT(inst,4);
DEBUGF((" italics=%d", (int)italics));
}
refresh_rate = GETFIELD(inst,VS60?3:2,2);
DEBUGF((" refresh=%d", refresh_rate));
switch (refresh_rate) {
case 0: /* continuous */
sync_period = 0;
break;
case 1: /* VT11: 60 Hz; VS60: 30 Hz */
sync_period = VT11 ? 17 : 33;
break;
case 2: /* VS60: 40 Hz */
sync_period = 25;
break;
default: /* (case 3) VS60: external sync */
sync_period = 17; /* fake a 60 Hz source */
break;
}
if (internal_stop)
sync_period = 0; /* overridden */
if (VS60 && TESTBIT(inst,1)) {
menu = TESTBIT(inst,0);
DEBUGF((" menu=%d", (int)menu));
}
DEBUGF(("\r\n"));
break;
case 017: /* 1111_ */
if (VS60 && TESTBIT(inst,10)) { /* 11111: Load Status BB */
DEBUGF(("Load Status BB"));
if (TESTBIT(inst,7)) {
depth_cue_proc = TESTBIT(inst,6);
DEBUGF((" depth_cue_proc=%d", (int)depth_cue_proc));
}
if (TESTBIT(inst,5)) {
edge_intr_ena = TESTBIT(inst,4);
DEBUGF((" edge_intr_ena=%d", (int)edge_intr_ena));
}
if (TESTBIT(inst,3)) {
file_z_data = TESTBIT(inst,2);
DEBUGF((" file_z_data=%d", (int)file_z_data));
}
if (TESTBIT(inst,1)) {
char_escape = TESTBIT(inst,0);
DEBUGF((" char_escape=%d", (int)char_escape));
}
} else { /* 11110: Load Status B */
DEBUGF(("Load Status B"));
if (VS60 && TESTBIT(inst,9)) {
color = GETFIELD(inst,8,7);
DEBUGF((" color=%d", (int)color));
}
if (TESTBIT(inst,6)) {
graphplot_step = GETFIELD(inst,5,0);
DEBUGF((" graphplot_step=%d", (int)graphplot_step));
}
}
DEBUGF(("\r\n"));
break;
default:
bad_ins: DEBUGF(("SPARE COMMAND 0%o\r\n", op));
/* "display processor hangs" */
DPC -= 2; /* hang around scene of crime */
break;
} /* end of control instruction opcode switch */
jsr = 0;
} else { /* graphic data */
lp0_flag = 0; /* XXX maybe not for OFFSET? */
if (word_number == 0)
offset = 0;
#define MORE_DATA { ++word_number; goto check; }
switch (graphic_mode) {
case CHAR:
if (word_number == 0) {
c = GETFIELD(inst,6,0);
DEBUGF(("char1 %d (", c));
DEBUGF((040 <= c && c < 0177 ? "'%c'" : "0%o", c));
DEBUGF((")\r\n"));
if (character(c)) /* POPR was done; end chars */
break;
MORE_DATA /* post any intrs now */
}
c = GETFIELD(inst,15,8);
DEBUGF(("char2 %d (", c));
DEBUGF((040 <= c && c < 0177 ? "'%c'" : "0%o", c));
DEBUGF((")\r\n"));
(void)character(c);
break;
case SVECTOR:
if (word_number == 0) {
i = TESTBIT(inst,14); /* inten_ena: beam on */
x = GETFIELD(inst,12,7);/* delta_x */
if (TESTBIT(inst,13))
x = -x;
y = GETFIELD(inst,5,0); /* delta_y */
if (TESTBIT(inst,6))
y = -y;
if (file_z_data)
MORE_DATA
}
if (file_z_data) { /* (VS60) */
z = GETFIELD(inst,9,2); /* delta_z */
if (TESTBIT(inst,13))
z = -z;
DEBUGF(("short vector i%d (%d,%d,%d)\r\n",
i, (int)x, (int)y, (int)z));
vector3(i, x, y, z);
} else {
DEBUGF(("short vector i%d (%d,%d)\r\n", i, (int)x, (int)y));
vector2(i, x, y);
}
break;
case LVECTOR:
if (word_number == 0) {
ex = VS60 && TESTBIT(inst,12);
i = TESTBIT(inst,14);
x = GETFIELD(inst,9,0); /* delta_x */
if (TESTBIT(inst,13))
x = -x;
MORE_DATA
}
if (word_number == 1) {
y = GETFIELD(inst,9,0); /* delta_y */
if (TESTBIT(inst,13))
y = -y;
if (file_z_data)
MORE_DATA
}
if (file_z_data) { /* (VS60) */
if (ex)
goto norot;
z = GETFIELD(inst,9,2); /* delta_z */
if (TESTBIT(inst,13))
z = -z;
DEBUGF(("long vector i%d (%d,%d,%d)\r\n",
i, (int)x, (int)y, (int)z));
vector3(i, x, y, z);
} else {
if (ex)
norot: /* undocumented and probably nonfunctional */
DEBUGF(("ROTATE NOT SUPPORTED\r\n"));
else {
DEBUGF(("long vector i%d (%d,%d)\r\n", i, (int)x, (int)y));
vector2(i, x, y);
}
}
break;
case POINT: /* (or OFFSET, if VS60) */
/* [VT48 manual incorrectly says point data doesn't use sign bit] */
if (word_number == 0) {
ex = GETFIELD(inst,(VS60?11:9),0);
offset = VS60 && TESTBIT(inst,12); /* offset flag */
if (!offset)
i = TESTBIT(inst,14); /* for point only */
if (VS60)
if (sxo = TESTBIT(inst,13)) /* sign bit */
ex = -ex;
/* XXX if VT11, set xpos/xoff now?? */
MORE_DATA
}
if (word_number == 1) {
ey = GETFIELD(inst,(VS60?11:9),0);
if (VS60)
if (syo = TESTBIT(inst,13)) /* sign bit */
ey = -ey;
if (file_z_data)
MORE_DATA
}
if (file_z_data) { /* (VS60) */
ez = GETFIELD(inst,11,2);
if (szo = TESTBIT(inst,13)) /* sign bit */
ez = -ez;
if (offset) { /* OFFSET rather than POINT */
DEBUGF(("offset (%d,%d,%d)\r\n", (int)ex,(int)ey,(int)ez));
xoff = PSCALE(ex);
yoff = PSCALE(ey);
zoff = PSCALE(ez * 4); /* XXX include bits 1:0 ? */
s_xoff = sxo;
s_yoff = syo;
s_zoff = szo;
} else {
DEBUGF(("point i%d (%d,%d,%d)\r\n", i,
(int)ex, (int)ey, (int)ez));
point3(i, ex, ey, ez, VS60);
}
} else {
if (offset) { /* (VS60) OFFSET rather than POINT */
DEBUGF(("offset (%d,%d)\r\n", (int)ex, (int)ey));
xoff = PSCALE(ex);
yoff = PSCALE(ey);
s_xoff = sxo;
s_yoff = syo;
} else {
DEBUGF(("point i%d (%d,%d)\r\n", i, (int)ex, (int)ey));
point2(i, ex, ey, VS60);
}
}
break;
case GRAPHX: /* (or BLVECT if VS60) */
i = TESTBIT(inst,14);
if (VS60 && TESTBIT(inst,10))
goto blv; /* (VS60) BLVECT rather than GRAPHX */
else {
ex = GETFIELD(inst,9,0);
DEBUGF(("graphplot x i%d (%d)\r\n", i, (int)ex));
ey = ypos - yoff + VSCALE(graphplot_step);
/* XXX VT48 ES says first datum doesn't increment Y?? */
point2(i, ex, PNORM(ey), VS60); /* approx. */
ypos = ey; /* more precise, if PSCALEF > 1 */
}
break;
case GRAPHY: /* (or BLVECT if VS60) */
i = TESTBIT(inst,14);
if (VS60 && TESTBIT(inst,10)) {
blv: /* (VS60) BLVECT rather than GRAPHY */
x = GETFIELD(inst,13,11); /* direction */
y = GETFIELD(inst,9,0); /* length */
DEBUGF(("basic long vector i%d d%d l%d\r\n",
i, (int)x, (int)y));
basic_vector(i, (int)x, (int)y);
} else {
ey = GETFIELD(inst,9,0);
DEBUGF(("graphplot y i%d (%d)\r\n", i, (int)ey));
ex = xpos - xoff + VSCALE(graphplot_step);
/* XXX VT48 ES says first datum doesn't increment X?? */
point2(i, PNORM(ex), ey, VS60); /* approx. */
xpos = ex; /* more precise, if PSCALEF > 1 */
}
break;
case RELPOINT:
if (word_number == 0) {
i = TESTBIT(inst,14);
ex = GETFIELD(inst,12,7);
if (TESTBIT(inst,13))
ex = -ex;
ey = GETFIELD(inst,5,0);
if (TESTBIT(inst,6))
ey = -ey;
if (file_z_data)
MORE_DATA
}
if (file_z_data) { /* (VS60) */
ez = GETFIELD(inst,9,2);
if (TESTBIT(inst,13))
ez = -ez;
DEBUGF(("relative point i%d (%d,%d,%d)\r\n",
i, (int)ex, (int)ey, (int)ez));
ex = xpos - xoff + VSCALE(ex);
ey = ypos - yoff + VSCALE(ey);
ez = zpos - zoff + VSCALE(ez * 4);
point3(i, PNORM(ex), PNORM(ey), PNORM(ez) / 4, 1); /* approx */
zpos = ez; /* more precise, if PSCALEF > 1 */
} else {
DEBUGF(("relative point i%d (%d,%d)\r\n", i, (int)ex, (int)ey));
ex = xpos - xoff + VSCALE(ex);
ey = ypos - yoff + VSCALE(ey);
point2(i, PNORM(ex), PNORM(ey), 1); /* approx. */
}
xpos = ex; /* more precise, if PSCALEF > 1 */
ypos = ey;
break;
/* the remaining graphic data types are supported by the VS60 only */
case BSVECT: /* (VS60) */
if (word_number == 0) {
i = TESTBIT(inst,14);
x = GETFIELD(inst,6,4); /* direction 0 */
y = GETFIELD(inst,3,0); /* length 0 */
ex = GETFIELD(inst,13,11); /* direction 1 */
ey = GETFIELD(inst,10,7); /* length 1 */
DEBUGF(("basic short vector1 i%d d%d l%d\r\n",
i, (int)x, (int)y));
basic_vector(i, (int)x, (int)y);
MORE_DATA
}
DEBUGF(("basic short vector2 i%d d%d l%d\r\n", i, (int)ex,(int)ey));
basic_vector(i, (int)ex, (int)ey);
break;
case ABSVECTOR: /* (VS60) */
/* Note: real VS60 can't handle a delta of more than +-4095 */
if (word_number == 0) {
i = TESTBIT(inst,14);
x = GETFIELD(inst,11,0);
if (TESTBIT(inst,13))
x = -x;
MORE_DATA
}
if (word_number == 1) {
y = GETFIELD(inst,11,0);
if (TESTBIT(inst,13))
y = -y;
if (file_z_data)
MORE_DATA
}
if (file_z_data) {
z = GETFIELD(inst,11,2);
if (TESTBIT(inst,13))
z = -z;
DEBUGF(("absolute vector i%d (%d,%d,%d)\r\n",
i, (int)x, (int)y, (int)z));
ex = VSCALE(x) + xoff;
ey = VSCALE(y) + yoff;
ez = VSCALE(z * 4) + zoff;
vector3(i, PNORM(ex - xpos), PNORM(ey - ypos),
PNORM(ez - zpos) / 4); /* approx. */
zpos = ez; /* more precise, if PSCALEF > 1 */
} else {
DEBUGF(("absolute vector i%d (%d,%d)\r\n", i, (int)x, (int)y));
ex = VSCALE(x) + xoff;
ey = VSCALE(y) + yoff;
vector2(i, PNORM(ex - xpos), PNORM(ey - ypos)); /* approx. */
}
xpos = ex; /* more precise, if PSCALEF > 1 */
ypos = ey;
break;
case CIRCLE: /* (VS60) */
if (word_number == 0) {
i = TESTBIT(inst,14);
x = GETFIELD(inst,9,0); /* delta cx */
if (TESTBIT(inst,13))
x = -x;
MORE_DATA
}
if (word_number == 1) {
y = GETFIELD(inst,9,0); /* delta cy */
if (TESTBIT(inst,13))
y = -y;
MORE_DATA
}
if (word_number == 2) {
if (file_z_data) {
z = GETFIELD(inst,11,2); /* delta cz */
if (TESTBIT(inst,13))
z = -z;
MORE_DATA
}
}
if (word_number == 2 + file_z_data) {
ex = GETFIELD(inst,9,0); /* delta ex */
if (TESTBIT(inst,13))
ex = -ex;
MORE_DATA
}
if (word_number == 3 + file_z_data) {
ey = GETFIELD(inst,9,0); /* delta ey */
if (TESTBIT(inst,13))
ey = -ey;
if (file_z_data)
MORE_DATA
}
if (file_z_data) {
ez = GETFIELD(inst,11,2); /* delta ez */
if (TESTBIT(inst,13))
ez = -ez;
DEBUGF(("circle/arc i%d C(%d,%d,%d) E(%d,%d,%d)\r\n",
i, (int)x, (int)y, (int)z, (int)ex, (int)ey, (int)ez));
conic3(i, x, y, z, ex, ey, ez); /* approx. */
} else {
DEBUGF(("circle/arc i%d C(%d,%d) E(%d,%d)\r\n",
i, (int)x, (int)y, (int)ex, (int)ey));
conic2(i, x, y, ex, ey);
}
break;
default: /* "can't happen" */
DPC -= 2; /* hang around scene of crime */
break;
} /* end of graphic_mode switch */
word_number = 0;
} /* end of instruction decoding and execution */
goto check;
bus_timeout:
DEBUGF(("TIMEOUT\r\n"));
/* fall through to check (time_out has already been set) */
check:
/* post an interrupt if conditions are right;
because this simulation has no pipeline, only one is active at a time */
if (lp0_sw_state != lp0_sw) {
lp0_sw_state = lp0_sw; /* track switch state */
if (lp0_sw_intr_ena)
lpsw_irq = 1;
}
/* lphit_irq triggering should await data mode,
but this is simpler and (probably) good enough */
if (lphit_irq || lpsw_irq || edge_irq)
vt_lpen_intr(); /* post graphic interrupt to host */
else if ((internal_stop && stop_intr_ena) || (ext_stop/*&& stop_intr_ena*/))
vt_stop_intr(); /* post stop interrupt to host */
else if (char_irq || stack_over || stack_under || time_out)
vt_char_intr(); /* post character interrupt to host */
else if (name_irq)
vt_name_intr(); /* post name-match interrupt to host */
#if 1 /* XXX this might be a mistake */
else /* handle any pending 2nd CHAR/BSVECT */
if (word_number == 1 && (graphic_mode==CHAR || graphic_mode==BSVECT))
goto fetched;
#endif
/* fall through to age_ret */
age_ret:
display_age(us, slowdown);
return !maint1 && !maint2 && busy;
} /* vt11_cycle */