/* * $Id: vt11.c,v 1.17 2004/01/24 20:44:46 phil Exp - revised by DAG $ * Simulator Independent VT11/VS60 Graphic Display Processor Simulation * Phil Budne * September 13, 2003 * Substantially revised by Douglas A. Gwyn; last edited 05 Aug 2005 * * 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 */ /* * 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. */ /* * 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 supports * dual consoles (CRTs with light pens), 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. * Apparently a later option substituted a digitizing-tablet correlator * for the light pen. The VS60 has a 4-level silo (graphic data pipeline) * which for reasons of simplicity is not implemented in this simulation; * the only visible effect is that DZVSC diagnostic tests 110 & 111 will * report failure. * * 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. * * There were further models in this series, but they appear to have * little if any compatibility with the VT11. * * Much later, DEC produced a display system it called the VS60S, but * that doesn't seem to bear any relationship to the original VS60. * * 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. * * STATUS: * * Clipping is not implemented properly for arcs. * * This simulation passes all four MAINDEC VS60 diagnostics and the * DEC/X11 VS60 system exerciser, with the following exceptions: * * MD-11-DZVSA-A, VS60 instruction test part I, test 161: * Failure to time-out an access to a "nonexistent" bus address, when the * system is configured with so much memory that the probed address * actually responds; this is a deficiency in the diagnostic itself. * * MD-11-DZVSB-A, VS60 instruction test part II: * No exceptions. * * MD-11-DZVSC-B, VS60 instruction test part III, tests 107,110,111: * Memory address test fails under SIMH, due to SIMH not implementing * KT11 "maintenance mode", in which the final destination address (only) * is relocated. When SIMH is patched to fix this, the test still fails * due to a bug in the diagnostic itself, namely a call to DPCONV1 which * tests a condition code that is supposed to pertain to R0 but which * hasn't been set up. Swapping the two instructions before the call to * DPCONV1 corrects this, and then this test passes. * Graphic silo content tests fail, since the silo pipeline is not * simulated; there are no plans to fix this, since it serves no other * purpose in this simulation and would adversely affect performance. * * MD-11-DZVSD-B, VS60 visual display test, frame 13: * "O" character sizes are slightly off, due to optimization for raster * display rather than true stroking; there are no plans to change this. * * MD-11-DZVSE-A0, XXDP VS60 visual display exerciser: * No visible exceptions. Light-pen interrupts might not be handled * right, since they're reported as errors and cause display restart. * (XXX Need to obtain source listing to check this.) */ #ifdef DEBUG_VT11 #include #endif #include /* memset */ #ifndef NO_CONIC_OPT #include /* 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) static void *vt11_dptr; static int vt11_dbit; #if defined(DEBUG_VT11) || defined(VM_PDP11) #include #if defined(__cplusplus) extern "C" { #endif #define DEVICE void #define DBG_CALL 1 int vt11_debug; #if defined(VM_PDP11) extern void _sim_debug_device (unsigned int dbits, DEVICE* dptr, const char* fmt, ...); #define DEBUGF(...) _sim_debug_device (vt11_dbit, vt11_dptr, ## __VA_ARGS__) #else /* DEBUG_VT11 */ #define DEBUGF(...) do {if (vt11_debug & DBG_CALL) { printf(## __VA_ARGS__); fflush(stdout); };} while (0) #endif /* defined(DEBUG_VT11) || defined(VM_PDP11) */ #if defined(__cplusplus) } #endif #else #define DEBUGF(...) #endif /* * Note about coordinate signedness and wrapping: * * The documentation for these devices says confusing things about coordinate * wrapping and signedness. The VT11 maintains 12-bit coordinate registers * (wrapping 4096 -> 0), while the VS60 maintains 14-bit coordinate registers. * Coordinate arithmetic (such as adding a vector "delta" to the current * position) that overflows merely drops the extra bits; this can be treated * as use of twos-complement representation for the position registers, whereas * the VS60 offset registers and the display file itself use a signed-magnitude * representation. (Except that JMP/JSR-relative delta uses twos-complement!) * This simulation tracks position using at least 32 bits including sign; this * can overflow only for a pathological display file. * * 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 has been * confirmed by diagnostic DZVSB test 31). This simulation maintains position * values and offsets both multiplied by PSCALEF, which should be 4 to obtain * maximum drawing precision, or 1 to mimic the actual 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 0 /* XXX enable only 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)) + ((x)>=0 ? 1 : -1)) / 4) #define CSCALE(x) ((PSCALE(char_scale * (int32)(x)) + ((x)>=0 ? 1 : -1)) / 4) /* (The "+ ((x)>=0 ? 1 : -1)" above is needed to pass the diagnostics.) */ #define ABS(x) ((x) >= 0 ? (x) : -(x)) #define TWOSCOMP(x) ((x) >= 0 ? (x) : ~(-(x)-1)) 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_dptr); \ vt11_init = 1; vt11_reset(vt11_dptr, vt11_dbit); } } /* 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 stack[8]._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 */ static unsigned char mode_field = 0; /* copy of control instr. bits 14-11 */ #define graphic_mode stack[8]._mode /* 4 bits: sets type for graphic data */ enum gmode { CHAR=0, SVECTOR, LVECTOR, POINT, GRAPHX, GRAPHY, RELPOINT, /* all */ BSVECT, CIRCLE, ABSVECTOR /* VS60 only */ }; #define intensity stack[8]._intens /* 3 bits: 0 => dim .. 7 => bright */ static unsigned char lp0_hit = 0; /* 1 bit: light pen #0 detected hit */ static unsigned char so_flag = 0; /* 1 bit: illegal char. in SO mode */ static unsigned char edge_indic = 0; /* 1 bit: crossing visible area edge */ #define italics stack[8]._italics /* 1 bit: use italic font */ #define blink_ena stack[8]._blink /* 1 bit: blink graphic item */ static unsigned char edge_flag = 0; /* 1 bit: edge intr if enabled (VS60) */ #define line_type stack[8]._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 int32 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 (!(stopped || 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 stack[8]._crotate /* 1 bit: rotate chars 90 degrees CCW */ #define cs_index stack[8]._csi /* character scale index 0..3 */ static unsigned char ext_stop = 0; /* 1 bit: stop display */ #define menu stack[8]._menu /* 1 bit: VS60 graphics in menu area */ #define vector_scale stack[8]._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 stack[8]._inten0 /* enable con 0 for all graphic data */ /* lp0_hit has already been defined, under Mode Parameter Register */ static unsigned char lp0_down = 0; /* 1 bit: LP #0 switch was depressed */ static unsigned char lp0_up = 0; /* 1 bit: LP #0 switch was released */ #define lp0_intr_ena stack[8]._lp0intr /* generate interrupt on LP #0 hit */ #define lp0_sw_intr_ena stack[8]._lp0swintr /* generate intr. on LP #0 sw chg */ #define int1_scope stack[8]._inten1 /* enable con 1 for all graphic data */ /* following 2 flags only mutable via writing this register w/ MS3 set: */ static unsigned char lp1_hit = 0; /* 1 bit: light pen #1 detected hit */ static unsigned char lp1_down = 0; /* 1 bit: LP #1 switch was depressed */ static unsigned char lp1_up = 0; /* 1 bit: LP #1 switch was released */ #define lp1_intr_ena stack[8]._lp1intr /* generate interrupt on LP #1 hit */ #define lp1_sw_intr_ena stack[8]._lp1swintr /* generate intr. on LP #1 sw chg */ enum scolor { GREEN=0, YELLOW, ORANGE, RED }; #define color stack[8]._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_irq = 0; /* 1 bit: name matches associative nm */ /* (always interrupts on name match!) */ /* search previously defined, under Associative Name Register */ #define name stack[8]._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. */ /* It is important to note that setting the stack level via SAR doesn't change the parameters currently in effect; only JSR/POPR does that. To speed up JSR/POPR, the current state is implemented as an extra stack frame, so that push/pop is done by copying a block rather than lots of individual variables. There are thus 9 stack elements, 8 stack entries [0..7] and the current state [8]. 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 gmode _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; /* currently in shift-out mode */ 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 intern. 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, CHAR, 0, 0, 0, 0, 0, SOLID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GREEN, 0, 0 }, { 0, 0, CHAR, 0, 0, 0, 0, 0, SOLID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GREEN, 0, 0 }, { 0, 0, CHAR, 0, 0, 0, 0, 0, SOLID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GREEN, 0, 0 }, { 0, 0, CHAR, 0, 0, 0, 0, 0, SOLID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GREEN, 0, 0 }, { 0, 0, CHAR, 0, 0, 0, 0, 0, SOLID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GREEN, 0, 0 }, { 0, 0, CHAR, 0, 0, 0, 0, 0, SOLID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GREEN, 0, 0 }, { 0, 0, CHAR, 0, 0, 0, 0, 0, SOLID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GREEN, 0, 0 }, { 0, 0, CHAR, 0, 0, 0, 0, 0, SOLID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, GREEN, 0, 0 }, { 0, 0, CHAR, 4, 1, 4, 0, 4, SOLID, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, GREEN, 0, 0 }, }; #define char_scale stack[8]._cscale /* character scale factor * 4 */ /* _cscale must track _csi! */ static const unsigned char csi2csf[4] = { 2, 4, 6, 8 }; /* maps cs_index to " */ #define shift_out stack[8]._so /* flag: using shift-out char. set */ #define char_escape stack[8]._cesc /* perform POPR on char. term. match */ #define edge_intr_ena stack[8]._edgeintr /* generate intr. on edge transit */ #define lp_intensify stack[8]._bright /* if VT11, 20us bright spot; if VS60, brighten the entity */ #define stop_intr_ena stack[8]._stopintr /* generate intr. on internal stop */ #define file_z_data stack[8]._zdata /* flag: display file has Z coords */ #define depth_cue_proc stack[8]._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 int word_number = -2; /* tracks multiple data words */ #define CONTROL_MODE() (word_number == -1) /* true when in control mode */ #define DATA_MODE() (word_number >= 0) /* true when in data mode */ static struct frame *sp = &stack[8]; /* -> selected stack frame, or TOS */ #define STACK_EMPTY (sp == &stack[8]) /* "TOS" */ #define STACK_FULL (sp == &stack[0]) /* "BOS" */ static unsigned char stack_sel = 8<<2; /* 8 levels, 4 PDP-11 words per level */ /* stack_sel must track sp and TOS! */ /* * 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 lp0_sw_state = 0; /* last known LP 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_z0, save_x1, save_y1, save_z1; /* CRT coords for rest of vector */ 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 stopped = 1; /* display processor frozen */ 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 int32 vt11_csp_w = VT11_CSP_W; /* horizontal character spacing */ int32 vt11_csp_h = VT11_CSP_H; /* vertical character spacing */ /* VS60 spacing depends on char scale; above are right for char scale x1 */ /* 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) /* * Clipping-specific stuff. * When a vector crosses the edge of the viewing window, the "edge flag" is set * and the "edge indicator" indicates whether the first point on the visible * segment is clipped. Apparently the VT11 does not draw the visible segment, * but the VS60 will draw the segment (after a resume from an edge interrupt, * if the interrupt was enabled). The VS60 will also post a second interrupt * corresponding to the end of the visible segment, after setting the edge flag * (again) and setting the edge indicator according to whether the last point * on the visible segment was clipped. * Note: a light-pen hit is possible on a drawn clipped segment. */ static int clip_vect = 0; /* set when clipped coords saved; bit-coded: 1 => entry clipped 2 => exit clipped */ static int clip_i; /* saved "intensify" bit */ static int32 clip_x0, clip_y0, clip_z0; /* CRT coords for entry point */ static int32 clip_x1, clip_y1, clip_z1; /* CRT coords for exit point */ /* * Uncertain whether VS60 edge transitions in menu area are flagged and whether * clipping takes menu width into account. Three possibilities: */ #define CLIPYMAX y_edge #if 0 /* menu area never clipped (seems wrong) */ #define CLIPXMAX 1023 #define ONSCREEN(x,y) (menu || ((x) >= 0 && (x) <= CLIPXMAX \ && (y) >= 0 && (y) <= CLIPYMAX)) #elif 0 /* menu area correctly clipped (unlikely) */ #define CLIPXMAX (menu ? 127 : 1023) #define ONSCREEN(x,y) ((x) >= 0 && (x) <= CLIPXMAX \ && (y) >= 0 && (y) <= CLIPYMAX) #else /* menu area clipped same as main area */ #define CLIPXMAX 1023 #define ONSCREEN(x,y) ((x) >= 0 && (x) <= CLIPXMAX \ && (y) >= 0 && (y) <= CLIPYMAX) #endif static void lineTwoStep(int32, int32, 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, rather than as bit fields * packed into "registers". The simulated VT48 register contents are * converted to/from our internal variables by the following functions. */ 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 shows 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); /* Stack level is unaffected, except that stack_sel==037 goes to 040; this fudge is necessary to pass DZVSC test 3, which misleadingly calls it setting top-of-stack upon START (vt11_set_dpc(even)). If one instead were to set TOS upon START, then several DZVSC diagnostics would fail! */ if (VS60 && !STACK_EMPTY && GETFIELD(stack_sel,1,0) == 3) { stack_sel = GETFIELD(stack_sel,4,2) + 1; /* 1..8 */ sp = &stack[stack_sel]; /* [1..8] */ stack_sel <<= 2; } if (!TESTBIT(d,0)) { /* START */ 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 = -2; clip_vect = 0; /* discard clipped vector data */ #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 (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_z0, save_x1, save_y1, save_z1); 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}() needed]*/ lp0_intr_ena = save_ena; } if (!maint2) /* kludge to satify diagnostic test */ ext_stop = 0; } stopped = internal_stop = time_out = stack_over = stack_under = 0; more_vect = more_arc = stroking = skip_start = 0; so_flag = edge_indic = edge_flag = lp0_hit = lp1_hit = lp_suppress = 0; lp0_down = lp0_up = lp1_down = lp1_up = 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) | (mode_field<<11) | (intensity<<8) | (lp0_hit<<7) | (so_flag<<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(TWOSCOMP(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(TWOSCOMP(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); /* stop occurs at end of next display cycle */ if (ext_stop /* not maskable */) { stopped = 1; /* (asynchronous with display cycle) */ vt_stop_intr(); /* post stop interrupt to host */ } } 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(TWOSCOMP(pos),13,10)<<12) | GETFIELD(ABS(off),11,0); } void vt11_set_xor(uint16 d) { INIT xoff = PSCALE(GETFIELD(d,11,0)); s_xoff = TESTBIT(d,13); if (s_xoff) 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(TWOSCOMP(pos),13,10)<<12) | GETFIELD(ABS(off),11,0); } void vt11_set_yor(uint16 d) { INIT yoff = PSCALE(GETFIELD(d,11,0)); s_yoff = TESTBIT(d,13); if (s_yoff) 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_hit<<14) | (lp0_down<<13) | (lp0_up<<12) | (lp0_intr_ena<<11) | (lp0_sw_intr_ena<<10) | (int1_scope<<9) | (lp1_hit<<8) | (lp1_down<<7) | (lp1_up<<6) | (lp1_intr_ena<<5) | (lp1_sw_intr_ena<<4) | (color << 2); } void vt11_set_scr(uint16 d) { INIT if (maint3) { if (TESTBIT(d,14) && lp0_intr_ena) { if (!lphit_irq) { /* ensure correct position registers reported */ lp_xpos = PNORM(xpos); lp_ypos = PNORM(ypos); lp_zpos = PNORM(zpos); } lp0_hit = lphit_irq = 1; } if (TESTBIT(d,13)) { lp0_down = 1; /* the manual seems to have this backward */ if (lp0_sw_intr_ena) lpsw_irq = 1; } if (TESTBIT(d,12)) { lp0_up = 1; /* the manual seems to have this backward */ if (lp0_sw_intr_ena) lpsw_irq = 1; } if (TESTBIT(d,8) && lp1_intr_ena) { if (!lphit_irq) { /* ensure correct position registers reported */ lp_xpos = PNORM(xpos); lp_ypos = PNORM(ypos); lp_zpos = PNORM(zpos); } lp1_hit = lphit_irq = 1; } if (TESTBIT(d,7)) { lp1_down = 1; if (lp1_sw_intr_ena) lpsw_irq = 1; } if (TESTBIT(d,6)) { lp1_up = 1; if (lp1_sw_intr_ena) lpsw_irq = 1; } if (lpsw_irq || lphit_irq /* && DATA_MODE() */) vt_lpen_intr(); } } int32 vt11_get_nr(void) { INIT return (name_irq<<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)]; /* [0..7], 8 (TOS) => 0 */ 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->_csi << 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; } /*NOTREACHED*/ return 0; } 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 -1: /* control mode reported as word 0, according to VT48 ES */ case 0: ret |= 1<<6; break; case 1: ret |= 1<<7; break; case 2: ret |= 1<<8; break; /* others (including -1) 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 to TOS */ stack_sel = (8<<2) /* TOS amounts to level 8 */ | TESTBIT(stack_sel,0); /* preserve PDP-11 word sel. */ } else { stack_sel = GETFIELD(d,4,0); sp = &stack[GETFIELD(stack_sel,4,2)]; /* [0..7] */ } } /* 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 dintens(). */ int32 vt11_get_zpr(void) { int32 pos; INIT pos = lphit_irq ? lp_zpos : edge_irq ? edge_zpos : PNORM(zpos); return GETFIELD(TWOSCOMP(pos),13,2); } 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)); s_zoff = TESTBIT(d,13); if (s_zoff) zoff = -zoff; } void vt11_reset(void *dev, int debug) { if (dev) { vt11_dptr = dev; vt11_dbit = debug; } /* make sure display code has been initialized */ if (!vt11_init) /* (SIMH invokes before display type is set) */ return; /* wait until last moment */ if (VS60) { /* VS60 character spacing depends on char scale; these are for x1 */ 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); /* some of the following should probably be moved to vt11_set_dpc([even]) */ stopped = int0_scope = 1; /* idle, console 0 enabled */ lp0_sw_state = display_lp_sw; /* sync with mouse button #1 */ 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; mode_field = 0; graphic_mode = CHAR; line_type = SOLID; color = GREEN; lp_intensify = 1; cs_index = 1; char_scale = vector_scale = 4; intensity = 4; sp = &stack[8]; stack_sel = 8<<2; /* PDP-11 word selector also cleared */ /* following necessary 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; /* BOS? */ if (!stack_over) { --sp; *sp = stack[8]; /* copy current 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; /* TOS? */ if (!stack_under) { stack[8] = *sp; /* restore parameters (including DPC) */ ++sp; stack_sel += 1<<2; /* XXX should stack_sel stack-byte bits be cleared? maybe for TOS? */ } /* else will generate interrupt soon after return */ } /* compute depth-cued display intensity from current display-file intensity */ int dintens(int32 z) { int 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; return i >= DISPLAY_INT_MIN ? i : DISPLAY_INT_MIN; } /* * Note: It would be more efficient to work directly with display intensity * levels than with Z coordinates, since the vast majority of dintens() * computations result in the same display intensity level as the previous * such computation. However, compared to the rest of the processing per * pixel, this computation doesn't seem too expensive, so optimization isn't * necessary. */ /* illuminate pixel in raster image */ static void illum3(int32 x, int32 y, int32 z) /* virtual CRT units (offset and normalized) */ { int i; /* display intensity level */ /* 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 = dintens(z); 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_hit = 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) /* VSCALEd, offset coordinates (z1 * 4) */ { 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 = x1; ypos = y1; zpos = z1; 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) { if (edge_intr_ena) { edge_xpos = x1; edge_ypos = y1; edge_zpos = z1; edge_irq = 1; #if 0 /* XXX uncertain whether point is displayed during edge intr. */ return; /* point not displayed */ #endif } else edge_flag = 0; } } 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, zpos, 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, int32 z) /* X, Y are in window-system screen pixel units */ /* Z is in virtual CRT units (offset and normalized) */ { int i, on = (line_type == SOLID) || stroking; /* on for sure */ if (!on) { /* see if in visible portion of cycle */ 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; case SOLID: break; } --line_counter; } } if (on) /* convert back from actual screen pixels to emulated CRT coordinates */ /* note: Z coordinate is already in virtual CRT units */ illum3(x * reduce, y * reduce, z); } /* * 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! * * The Z coordinate just goes along for the ride. It is computed thusly: * Let N = # steps in major direction (X or Y) * i = step number * dZ = Z1 - Z0 * Then Zi = floor(Z0 + dZ*(i+0.5)/N) 0.5 centers steps * Zi = floor((2*N*Z0 + dZ + 2*i*dZ) / (2*N)) * The numerator at step i is * Znum(i) = Znum(i-1) + 2*dZ * with Znum(0) = 2*N*Z0 + dZ */ static void lineTwoStep(int32 x0, int32 y0, int32 z0, int32 x1, int32 y1, int32 z1) /* virtual CRT units (offset and normalized) */ { int32 dx, dy, dz; int stepx, stepy; /* when 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; /* note: Z coords remain in virtual CRT units */ dx = x1 - x0; dy = y1 - y0; dz = z1 - z0; /* XXX there could be fast special cases for "basic vectors" */ if (dx >= 0) stepx = 1; else { dx = -dx; stepx = -1; } if (dy >= 0) stepy = 1; else { dy = -dy; stepy = -1; } #define TPOINT do { znum += dz; /* 2 * original_dz */ \ z0 = znum / twoN; /* truncates */ \ if (lphit_irq && !stroking) goto hit; \ /* XXX longjmp from hit detector may be more efficient */ \ lpoint(x0, y0, z0); \ } while (0) if (!skip_start) /* not for continuing stroke when VS60 char. or arc */ lpoint(x0, y0, z0); /* (could have used TPOINT) */ if (dx == 0 && dy == 0) /* following algorithm won't work */ return; /* just the one dot */ /* XXX not accurate for vector in Z direction */ if (dx > dy) { int32 length = (dx - 1) / 2; int extras = (dx - 1) & 1; int32 incr2 = (dy * 4) - (dx * 2); long twoN = 2 * dx, znum = twoN * z0 + dz; dz *= 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 { /* dy >= dx */ int32 length = (dy - 1) / 2; int extras = (dy - 1) & 1; int32 incr2 = (dx * 4) - (dy * 2); long twoN = 2 * dy, znum = twoN * z0 + dz; dz *= 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, z1); /* 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_z0 = z0; save_x1 = x1 * reduce; save_y1 = y1 * reduce; save_z1 = z1; /* line_counter is static and thus will be intact upon resume */ } /* lineTwoStep */ /* * Clip segment to only that portion, if any, visible within the window. * Returns: -1 visible and not clipped * 0 invisible * 1,2,3 visible and clipped (clipped coords stashed); * bit-coded: 1 => entry clipped, 2=> exit clipped * * The Z coordinate just goes along for the ride. */ int clip3(int32 x0, int32 y0, int32 z0, int32 x1, int32 y1, int32 z1) { int code0, code1; /* Cohen-Sutherland endpoint codes */ /* remaining variables are used in modified Liang-Barsky algorithm: */ int32 rdx, rdy, rdz; /* x0-x1, y0-y1, z0-z1 */ int32 tn; /* Edge parameter: numerator */ int32 tPEn, tPEd, tPLn, tPLd; /* Enter/Leave params: numer, denom */ int clipped; /* potential clip_vect value */ /* * Use the first parts of the Cohen-Sutherland algorithm to detect * all IN-to-IN cases and OUT-to-OUT along the same side, each of * which is trivially handled without needing any clipping actions. * The idea is that the extended window edges divide the plane into * 9 regions; the segment endpoints are assigned bit-codes that * indicate which of the 3 X sections and which of the 3 Y sections * each point lies in; then simple tests on the codes can detect * the desired "trivial" cases, which are the most common. */ /* assign X/Y region codes to the endpoints */ if (y0 > CLIPYMAX) code0 = 1; else if (y0 < 0) code0 = 2; else code0 = 0; if (x0 > CLIPXMAX) code0 |= 4; else if (x0 < 0) code0 |= 8; if (y1 > CLIPYMAX) code1 = 1; else if ( y1 < 0 ) code1 = 2; else code1 = 0; if (x1 > CLIPXMAX) code1 |= 4; else if ( x1 < 0 ) code1 |= 8; if (code0 == code1) { /* endpoints lie in same region */ if (code0 == 0) /* ON to ON; trivially visible */ return -1; else /* OFF to OFF and trivially invisible */ return 0; } /* Endpoints are now known to lie in different regions. */ if ((code0 & code1) != 0) /* OFF to OFF and trivially invisible */ return 0; /* Handle horizontal and vertical cases separately, both for speed and to simplify later computations. */ rdx = x0 - x1; rdy = y0 - y1; rdz = z0 - z1; if (rdx == 0) { /* vertical; has a visible portion! */ clipped = 0; /* Using the direction allows us to save one test. */ if (rdy < 0) { /* directed upward */ if (y1 > CLIPYMAX) { clipped = 2; y1 = CLIPYMAX; /* clip */ z1 = z0 + rdz * (y1 - y0) / rdy; } if (y0 < 0) { clipped |= 1; z0 -= rdz * y0 / rdy; y0 = 0; /* clip */ } } else { /* directed downward */ if (y0 > CLIPYMAX) { clipped = 1; y0 = CLIPYMAX; /* clip */ z0 = z1 + rdz * (y0 - y1) / rdy; } if (y1 < 0) { clipped |= 2; z1 -= rdz * y1 / rdy; y1 = 0; /* clip */ } } goto stash; } if (rdy == 0) { /* horizontal; has a visible portion! */ clipped = 0; /* Using the direction allows us to save one test. */ if (rdx < 0) { /* directed rightward */ if (x1 > CLIPXMAX) { clipped |= 2; x1 = CLIPXMAX; /* clip */ z1 = z0 + rdz * (x1 - x0) / rdx; } if (x0 < 0) { clipped = 1; z0 -= rdz * x0 / rdx; x0 = 0; /* clip */ } } else { /* directed leftward */ if (x0 > CLIPXMAX) { clipped = 1; x0 = CLIPXMAX; /* clip */ z0 = z1 + rdz * (x0 - x1) / rdx; } if (x1 < 0) { clipped |= 2; z1 -= rdz * x1 / rdx; x1 = 0; /* clip */ } } goto stash; } /* * Hardest cases: use modified Liang-Barsky algorithm. * * Not only is this computation supposedly faster than Cohen- * Sutherland clipping, but also the original direction is * preserved, which is necessary to accurately emulate the * VT48 behavior (association of coordinates with interrupts). */ /* * t is a line parameter: P(t) = P0 + t * (P1 - P0). * N is an outward normal vector. * L, R, B, T denote edges (left, right, bottom, top). * PE denotes "potentially entering", PL "potentially leaving". * n, d denote numerator, denominator (avoids floating point). */ /* * We know at this point that the endpoints lie in different * regions and that there must be at least one PE or PL crossing * at some value of t in [0,1]. Indeed, there will be *both* PE * and PL crossings *unless* one endpoint is IN the window. * * As a result of the previous filtering, denominators are never 0. */ tPEn = -1; /* tPE = -1, lower than any candidate */ tPEd = 1; tPLn = 2; /* tPL = 2, higher than any candidate */ tPLd = 1; /* * Left: tL = NL . (PL - P0) / NL . (P1 - P0) * NL = (-1,0) * PL = (0,y) * => * tL = x0 / rdx * * if ( tL >= 0 & tL <= 1 ) * if ( NL . (P1 - P0) < 0 & tL > tPE ) * tPE := tL * if ( NL . (P1 - P0) > 0 & tL < tPL ) * tPL := tL * => * if ( rdx < 0 ) * if ( rdx <= x0 & x0 <= 0 ) * if ( tPEd > 0 ) * if ( x0 * tPEd < tPEn * rdx ) * tPE := tL * else * if ( x0 * tPEd > tPEn * rdx ) * tPE := tL * else * if ( 0 <= x0 & x0 <= rdx ) * if ( tPLd > 0 ) * if ( x0 * tPLd < tPLn * rdx ) * tPL := tL * else * if ( x0 * tPLd > tPLn * rdx ) * tPL := tL */ /* The following code is commented out here and written more compactly below since tPEn, tPEd, tPLn and tPLd are constants. if (rdx < 0) { if (x0 <= 0 && x0 >= rdx) { if (tPEd > 0) { if (x0 * (long)tPEd < (long)tPEn * rdx) tPEn = x0, tPEd = rdx; } else // tPEd < 0 if (x0 * (long)tPEd > (long)tPEn * rdx) tPEn = x0, tPEd = rdx; } } else { // rdx > 0 if (x0 >= 0 && x0 <= rdx) { if (tPLd > 0) { if (x0 * (long)tPLd < (long)tPLn * rdx) tPLn = x0, tPLd = rdx; } else // tPLd < 0 if (x0 * (long)tPLd > (long)tPLn * rdx) tPLn = x0, tPLd = rdx; } } */ if (rdx < 0) { if (x0 <= 0 && x0 >= rdx) /* x0 not positive but less negative than rdx? */ tPEn = x0, tPEd = rdx; } else { if ((x0 >= 0 && x0 <= rdx) && /* x0 not negative but less than or equal to rdx */ (rdx != 0)) /* and rdx not zero */ tPLn = x0, tPLd = rdx; } /* * Right: tR = NR . (PR - P0) / NR . (P1 - P0) * NR = (1,0) * PR = (XMAX,y) * => * tR = (x0 - XMAX) / rdx * * if ( tR >= 0 & tR <= 1 ) * if ( NR . (P1 - P0) < 0 & tR > tPE ) * tPE := tR * if ( NR . (P1 - P0) > 0 & tR < tPL ) * tPL := tR * => * if ( rdx < 0 ) * if ( rdx <= TRn & TRn <= 0 ) * if ( tPLd > 0 ) * if ( TRn * tPLd > tPLn * rdx ) * tPL := tR * else * if ( TRn * tPLd < tPLn * rdx ) * tPL := tR * else * if ( 0 <= TRn & TRn <= rdx ) * if ( tPEd > 0 ) * if ( TRn * tPEd > tPEn * rdx ) * tPE := tR * else * if ( TRn * tPEd < tPEn * rdx ) * tPE := tR */ tn = x0 - CLIPXMAX; if (rdx < 0) { if (tn <= 0 && tn >= rdx) { if (tPLd > 0) { if (tn * (long)tPLd > (long)tPLn * rdx) tPLn = tn, tPLd = rdx; } else // tPLd < 0 if (tn * (long)tPLd < (long)tPLn * rdx) tPLn = tn, tPLd = rdx; } } else { // rdx > 0 if (tn >= 0 && tn <= rdx) { if (tPEd > 0) { if (tn * (long)tPEd > (long)tPEn * rdx) tPEn = tn, tPEd = rdx; } else // tPEd < 0 if (tn * (long)tPEd < (long)tPEn * rdx) tPEn = tn, tPEd = rdx; } } /* * Bottom: tB = NB . (PB - P0) / NB . (P1 - P0) * NB = (0,-1) * PB = (x,0) * => * tB = y0 / rdy * * if ( tB >= 0 & tB <= 1 ) * if ( NB . (P1 - P0) < 0 & tB > tPE ) * tPE := tB * if ( NB . (P1 - P0) > 0 & tB < tPL ) * tPL := tB * => * if ( rdy < 0 ) * if ( rdy <= y0 & y0 <= 0 ) * if ( tPEd > 0 ) * if ( y0 * tPEd < tPEn * rdy ) * tPE := tB * else * if ( y0 * tPEd > tPEn * rdy ) * tPE := tB * else * if ( 0 <= y0 & y0 <= rdy ) * if ( tPLd > 0 ) * if ( y0 * tPLd < tPLn * rdy ) * tPL := tB * else * if ( y0 * tPLd > tPLn * rdy ) * tPL := tB */ if (rdy < 0) { if (y0 <= 0 && y0 >= rdy) { if (tPEd > 0) { if (y0 * (long)tPEd < (long)tPEn * rdy) tPEn = y0, tPEd = rdy; } else /* tPEd < 0 */ if (y0 * (long)tPEd > (long)tPEn * rdy) tPEn = y0, tPEd = rdy; } } else /* rdy > 0 */ if (y0 >= 0 && y0 <= rdy) { if (tPLd > 0) { if (y0 * (long)tPLd < (long)tPLn * rdy) tPLn = y0, tPLd = rdy; } else { /* tPLd < 0 */ if (y0 * (long)tPLd > (long)tPLn * rdy) tPLn = y0, tPLd = rdy; } } /* * Top: tT = NT . (PT - P0) / NT . (P1 - P0) * NT = (0,1) * PT = (x,YMAX) * => * tT = (y0 - YMAX) / rdy * * if ( tT >= 0 & tT <= 1 ) * if ( NT . (P1 - P0) < 0 & tT > tPE ) * tPE := tT * if ( NT . (P1 - P0) > 0 & tT < tPL ) * tPL := tT * => * if ( rdy < 0 ) * if ( rdy <= TRn & TRn <= 0 ) * if ( tPLd > 0 ) * if ( TRn * tPLd > tPLn * rdy ) * tPL := tT * else * if ( TRn * tPLd < tPLn * rdy ) * tPL := tT * else * if ( 0 <= TRn & TRn <= rdy ) * if ( tPEd > 0 ) * if ( TRn * tPEd > tPEn * rdy ) * tPE := tT * else * if ( TRn * tPEd < tPEn * rdy ) * tPE := tT */ tn = y0 - CLIPYMAX; if (rdy < 0) { if (tn <= 0 && tn >= rdy) { if (tPLd > 0) { if (tn * (long)tPLd > (long)tPLn * rdy) tPLn = tn, tPLd = rdy; } else /* tPLd < 0 */ if (tn * (long)tPLd < (long)tPLn * rdy) tPLn = tn, tPLd = rdy; } } else { /* rdy > 0 */ if (tn >= 0 && tn <= rdy) { if (tPEd > 0) { if (tn * (long)tPEd > (long)tPEn * rdy) tPEn = tn, tPEd = rdy; } else /* tPEd < 0 */ if (tn * (long)tPEd < (long)tPEn * rdy) tPEn = tn, tPEd = rdy; } } /* * if ( tPL < tPE ) * invisible * => * if ( tPLd > 0 && tPEd < 0 || tPLd < 0 && tPEd > 0 ) * if ( tPLn * tPEd > tPEn * tPLd ) * invis * else * if ( tPLn * tPEd < tPEn * tPLd ) * invis */ if (((tPLd > 0) && (tPEd < 0)) || ((tPLd < 0) && (tPEd > 0))) { if (tPLn * (long)tPEd > (long)tPEn * tPLd) return 0; /* invisible */ } else if (tPLn * (long)tPEd < (long)tPEn * tPLd) return 0; /* invisible */ /* * if ( tPE < 0 ) tPE := 0 [code0 is 0] * if ( tPL > 1 ) tPL := 1 [code1 is 0] * draw from P(tPE) to P(tPL) * * P(t) = P0 + t * (P1 - P0) * => * xE = x0 - tE * rdx, yE = y0 - tE * rdy * xL = x0 - tL * rdx, yL = y0 - tL * rdy */ /* note: update P1 first since it uses original P0 coords */ if (code1 == 0) clipped = 0; else { clipped = 2; /* XXX might not be rounded the same as on the VT48: */ x1 = x0 - rdx * tPLn / tPLd; y1 = y0 - rdy * tPLn / tPLd; z1 = z0 - rdz * tPLn / tPLd; } if (code0 != 0) { clipped |= 1; /* XXX might not be rounded the same as on the VT48: */ x0 -= rdx * tPEn / tPEd; y0 -= rdy * tPEn / tPEd; z0 -= rdz * tPEn / tPEd; } /* Stash clipped coords and set global "vector was clipped" flag. */ stash: clip_x0 = x0; clip_y0 = y0; clip_x1 = x1; clip_y1 = y1; clip_z0 = z0; clip_z1 = z1; return clipped; } /* draw a 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; dx = stroking ? CSCALE(dx) : VSCALE(dx); /* apply scale factor (VS60) */ dy = stroking ? CSCALE(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 = x1 - x0; dy = y1 - y0; dz = z1 - z0; 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 (i) { if (menu) illum3(x0 + MENU_OFFSET, y0, z0); else illum3(x0, y0, z0); /* illum3() checks ONCRT, int0_scope */ } return; } } else { 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); 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 = 07777; /* ~ 1.0 */ ypos = adx; /* or ady */ } else if (adx > ady) { xpos = adx; ypos = 010000L * ady / adx + 1; /* truncates */ } else /* (adx < ady) */ { xpos = 010000L * adx / ady + 1; /* truncates */ ypos = ady; /* according to DZVSC test 100 */ } 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 */ /* * Note about edge conditions and interrupts: * * The VT48 documentation isn't very clear about this, but the expected * behavior has been determined from one of the VS60 diagnostics. The * "edge flag" flip-flop (bit) corresponds directly to an edge interrupt * (controlled by the "edge interrupt enable" bit in a Load Status BB * instruction) and is set precisely twice for *each* vector that is * clipped in *any* way (on->off, off->off, off->on), assuming that * after each interrupt is caught a RESUME (set DPC with odd value) is * issued. The X,Y position registers at the time of the first edge * interrupt for a clipped vector give the starting position of the * *visible* segment; the position registers at the time of the second * edge interrupt for a clipped vector give the ending position of the * *visible* segment. The "edge indicator" flip-flop (bit) at the time * of an edge interrupt is set if and only if the vector has been * clipped at that position. Thus for on-to-off, the edge indicator is * set for just the second edge interrupt; for off-to-off, the edge * indicator is set for both edge interrupts; for off-to-on, the edge * indicator is set for just the first interrupt. Resuming after a * vector has gone off-screen updates the position registers to the * location (off-screen) specified in the display file. Edge interrupts * share an interrupt vector with other "surface" interrupts such as * light-pen hits. * * It appears from diagnostic DZVSD that the menu area might not be * clipped. * * Note that the VT11 cannot generate edge interrupts, and its edge * indicator provides less information than on the VS60. */ switch (clip_vect = clip3(x0, y0, z0, x1, y1, z1)) { case 1: /* clipped only on entry */ case 3: /* clipped on entry and exit */ edge_indic = 1; /* indicate clipped going in */ /* XXX might not be correct for VT11 */ case 2: /* clipped only on exit */ edge_flag = edge_intr_ena; /* indicate vector-clip interrupt */ if (edge_flag) { edge_xpos = clip_x0; edge_ypos = clip_y0; edge_zpos = clip_z0; edge_irq = 1; } clip_i = i; return; /* may be drawn later by vt_cycle() */ case 0: /* invisible */ return; default: DEBUGF("clip() bad return: %d\n", clip_vect); /* Fallthrough */ case -1: /* visible, not clipped */ clip_vect = 0; break; /* draw immediately */ } } if (dx == 0 && dy == 0 && dz == 0) return; /* hardware skips null vector */ /* for character strokes, resort to scissoring: illum3() 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 && !clip_vect) {/* clipped vector drawn by vt_cycle() */ if (menu) lineTwoStep(x0 + MENU_OFFSET, y0, z0, x1 + MENU_OFFSET, y1, z1); else lineTwoStep(x0, y0, z0, x1, y1, z1); } /* * 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_hit) { long tangent = 0; int32 adx = ABS(dx), ady = ABS(dy); if (adx >= ady) { if (dx) tangent = 010000L * dy / dx; /* signed */ lp_ypos = y0 + tangent * (lp_xpos - x0) / 010000L; if (dx) tangent = 010000L * dz / dx; lp_zpos = z0 + tangent * (lp_xpos - x0) / 010000L; } else { if (dy) tangent = 010000L * dx / dy; /* signed */ lp_xpos = x0 + tangent * (lp_ypos - y0) / 010000L; if (dy) tangent = 010000L * dz / dy; lp_zpos = z0 + tangent * (lp_ypos - y0) / 010000L; } DEBUGF("adjusted LP coords (0%o,0%o,0%o)\r\n", lp_xpos, lp_ypos, lp_zpos); /* xpos,ypos,zpos still pertain to the original endpoint (assuming that Maintenance Switch 3 isn't set) */ } } #define vector2(i,dx,dy) vector3(i,dx,dy,0) /* the extra overhead for Z computation is not much */ /* 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. * * The Z coordinate is linearly interpolated. */ 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 int32 xs, ys, zs, xc, yc, zc, xe, ye, ze, x, y, z, nseg, seg; double rs, re, dr, as, da, zo, dz; 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); dcz = VSCALE(dcz * 4); dex = VSCALE(dex); dey = VSCALE(dey); dez = VSCALE(dez * 4); xs = PNORM(xpos); /* starting pos. (includes offset) */ ys = PNORM(ypos); zs = PNORM(zpos); xc = PNORM(xpos + dcx); /* center pos. (includes offset) */ yc = PNORM(ypos + dcy); zc = PNORM(zpos + dcz); xe = PNORM(xpos + dex); /* ending pos. (includes offset) */ ye = PNORM(ypos + dey); ze = PNORM(zpos + dez); /* determine vector from center to finish */ dex -= dcx; /* PSCALEd */ dey -= dcy; dez -= dcz; DEBUGF("offset, normalized arc i%d s(%ld,%ld,%ld) c(%ld,%ld,%ld) e(%ld,%ld,%ld)\r\n", i, (long)xs,(long)ys,(long)zs, (long)xc,(long)yc,(long)zc, (long)xe,(long)ye,(long)ze); /* 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 change when conic 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) { if (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 = ze; edge_irq = 1; goto done; } else edge_flag = 0; } /* XXX for now, resort to scissoring: illuminates only pixels that lie in the visible display area */ if (dcx == 0 && dcy == 0 && dcz == 0 && dex == 0 && dey == 0 && dez == 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; nseg = (int32)(da / sqrt(4.0*dr - dr*dr) + 1.0); if (nseg < 1) /* "can't happen" */ nseg = 1; else if (nseg > 360) nseg = 360; /* arbitrarily chosen upper limit */ /* determine angular, radial, and Z step sizes */ dr = (re - rs) / nseg; da /= nseg; dz = (double)(ze - zs) / nseg; if (menu) { xs += MENU_OFFSET; xc += MENU_OFFSET; xe += MENU_OFFSET; } line_counter = 037; /* reset line-style counter */ /* draw successive chords */ zo = zs; for (seg = 0; ++seg < nseg; ) { 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)); z = (int32)(zo + seg * dz); /* truncates */ lineTwoStep(xs, ys, zs, x, y, z);/* (continuing line style) */ skip_start = 1; /* don't double-illuminate junctions */ xs = x; ys = y; zs = z; if (lphit_irq) goto done; /* light-pen hit interrupted drawing */ } lineTwoStep(xs, ys, zs, xe, ye, ze);/* draw final chord to exact endpoint */ done: skip_start = 0; /* important! */ xpos += dcx + dex; /* update virtual beam position */ ypos += dcy + dey; zpos += dcz + dez; if (lp0_hit) { DEBUGF("LP hit on arc at (0%o,0%o,0%o)\r\n", lp_xpos, lp_ypos, lp_zpos); if (lphit_irq) { /* XXX save parameters for drawing remaining chords */ } } #endif } #define conic2(i,dcx,dcy,dex,dey) conic3(i,dcx,dcy,0,dex,dey,0) /* the extra overhead for Z computation is not much */ /* * 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 chars, POPR on term character (VS60) */ static int /* returns nonzero iff VS60 char terminate feature triggered */ character(int c) { /* following table maps cs_index to line-feed spacing for VS60 */ static const unsigned char vs60_csp_h[4] = {PSCALE(12), PSCALE(24), PSCALE(46), PSCALE(62)}; /* following tables map cs_index to adjustments for sub/superscript */ /* (cs_index 0 just a guess; others from VS60 Instruction Test Part II) */ static const unsigned char sus_left[4] = {PSCALE(0), PSCALE(2), PSCALE(4), PSCALE(3)}; static const unsigned char susr_left[4] = {PSCALE(0), PSCALE(2), PSCALE(4), PSCALE(0)}; static const unsigned char sub_down[4] = {PSCALE(2), PSCALE(3), PSCALE(6), PSCALE(7)}; static const unsigned char sup_up[4] = {PSCALE(5), PSCALE(9), PSCALE(18), PSCALE(24)}; static const unsigned char esus_right[4] = {PSCALE(0), PSCALE(2), PSCALE(0), PSCALE(0)}; static const unsigned char esub_up[4] = {PSCALE(2), PSCALE(3), PSCALE(6), PSCALE(8)}; int x, y; int32 xbase, ybase, xnext, ynext; if (shift_out) { if (c >= 040) { so_flag = char_irq = 1; /* will generate a char intr. */ char_buf = c; return 0; /* presumably, no POPR on term? */ } if (c == 017) { /* SHIFT IN */ shift_out = 0; goto copy; } } else { /* !shift_out */ if (c <= 040) { switch (c) { case 000: /* NULL */ goto cesc; /* apparently not copied to char_buf */ 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 += (VT11 ? CSCALE(vt11_csp_h) : vs60_csp_h[cs_index]); else ypos -= (VT11 ? CSCALE(vt11_csp_h) : vs60_csp_h[cs_index]); 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 -= susr_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 -= susr_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 += esus_right[cs_index]; } else { xpos += esus_right[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 -= esub_up[cs_index]; ypos += esus_right[cs_index]; } else { xpos += esus_right[cs_index]; ypos += esub_up[cs_index]; } break; case 040: /* SPACE */ goto space; default: /* other control codes ignored */ break; } goto copy; } } /* 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) { if (edge_intr_ena) { edge_irq = 1; goto space; } else edge_flag = 0; } 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) == 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. */ copy: char_buf = c; 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, draws any pending clipped vector (VS60 only). * 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 */ /* draw a clipped vector [perhaps after resume from edge interrupt] */ if (clip_vect) { int32 dx = clip_x1 - clip_x0, dy = clip_y1 - clip_y0, dz = clip_z1 - clip_z0; DEBUGF("clipped vector i%d (%ld,%ld,%ld) to (%ld,%ld,%ld)\r\n", clip_i, (long)clip_x0, (long)clip_y0, (long)clip_z0, (long)clip_x1, (long)clip_y1, (long)clip_z1); if (VS60 /* XXX assuming VT11 doesn't display */ && (dx != 0 || dy != 0 || dz != 0) /* hardware skips null vects */ && clip_i && int0_scope) { /* show it */ if (menu) lineTwoStep(clip_x0 + MENU_OFFSET, clip_y0, clip_z0, clip_x1 + MENU_OFFSET, clip_y1, clip_z1); else lineTwoStep(clip_x0, clip_y0, clip_z0, clip_x1, clip_y1, clip_z1); } /* * 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_hit) { long tangent; int32 adx = ABS(dx), ady = ABS(dy); if (adx >= ady) { tangent = 010000L * dy / dx; /* signed */ lp_ypos = clip_y0 + tangent * (lp_xpos - clip_x0) / 010000L; tangent = 010000L * dz / dx; lp_zpos = clip_z0 + tangent * (lp_xpos - clip_x0) / 010000L; } else { tangent = 010000L * dx / dy; /* signed */ lp_xpos = clip_x0 + tangent * (lp_ypos - clip_y0) / 010000L; tangent = 010000L * dz / dy; lp_zpos = clip_z0 + tangent * (lp_ypos - clip_y0) / 010000L; } DEBUGF("adjusted LP coords (0%o,0%o,0%o)\r\n", lp_xpos, lp_ypos, lp_zpos); /* xpos,ypos,zpos still pertain to the original endpoint (assuming that Maintenance Switch 3 isn't set) */ } if (VS60) { /* XXX assuming just 1 intr for VT11 */ edge_xpos = clip_x1; edge_ypos = clip_y1; edge_zpos = clip_z1; edge_indic = (clip_vect & 2) != 0; /* indicate clipped going out */ edge_flag = edge_intr_ena; if (edge_flag) { edge_irq = 1; vt_lpen_intr(); /* post graphic interrupt to host */ } } clip_vect = 0; /* this finishes the condition */ goto check; /* possibly post more interrupts; age */ } /* 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; mode_field = GETFIELD(inst,14,11); /* save bits 14-11 for diags. */ word_number = -1; /* flags "control mode"; ersatz 0 */ switch (mode_field) { 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", (unsigned)mode_field); graphic_mode = (enum gmode)mode_field; offset = 0; shift_out = 0; /* seems to be 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 = (enum linetype)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 0: /* 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 1: /* 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)) { #if 0 /* manual seems to say this, but it's wrong: */ DPC -= ez; DEBUGF("Display Jump Relative -0%o\r\n", (unsigned)ez); #else /* sign extend, twos complement add, 16-bit wrapping */ DPC = (DPC + (~0777 | ez)) & 0177777; DEBUGF("Display Jump Relative -0%o\r\n", ~((~0777 | ez) - 1)); #endif } else { DPC += (vt11word)ez; DEBUGF("Display Jump Relative +0%o\r\n", (unsigned)ez); } /* DPC was already incremented by 2 */ break; case 2: /* 110010: Display Jump to Subroutine Absolute */ finish_jsra = 1; jsr = 1; /* diagnostic test needs this here */ /* but the documentation says JSR bit set only for JSR REL! */ goto check; /* (break would set jsr = 0) */ 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); goto check; /* (break would set jsr = 0) */ case 3: /* 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)) { #if 0 /* manual seems to say this, but it's wrong: */ DPC -= (vt11word)ez; DEBUGF("Display Jump to Subroutine Relative -0%o\r\n", (unsigned)ez); #else /* sign extend, twos complement add, 16-bit wrapping */ DPC = (DPC + (~0777 | ez)) & 0177777; DEBUGF("Display Jump to Subroutine Relative -0%o\r\n", ~((~0777 | ez) - 1)); #endif } else { DPC += (vt11word)ez; DEBUGF("Display Jump to Subroutine Relative +0%o\r\n", (unsigned)ez); } /* DPC was already incremented by 2 */ break; /* jsr = 0 ?? */ } break; case 015: /* 1101__ */ if (VT11) DEBUGF("Display NOP\r\n"); else { op = GETFIELD(inst,10,9); switch (op) { case 0: /* 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 = (unsigned char)(ez & 0xFF); else int0_scope = (unsigned char)(ez & 0xFF); } if (TESTBIT(inst,5)) { ez = TESTBIT(inst,4); DEBUGF(" lp_intr_ena=%d", (int)ez); if (c) lp1_intr_ena = (unsigned char)(ez & 0xFF); else lp0_intr_ena = (unsigned char)(ez & 0xFF); } if (TESTBIT(inst,3)) { ez = TESTBIT(inst,2); DEBUGF(" lp_sw_intr_ena=%d", (int)ez); if (c) lp1_sw_intr_ena = (unsigned char)(ez & 0xFF); else lp0_sw_intr_ena = (unsigned char)(ez & 0xFF); } DEBUGF("\r\n"); break; case 1: /* 110101: Display POP Not Restore */ DEBUGF("Display POP Not Restore\r\n"); pop(0); /* sets new DPC as side effect */ break; case 2: /* 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"); internal_stop = TESTBIT(inst,10); /* 11101 Display Stop */ if (internal_stop) { stopped = 1; /* (synchronous with display cycle) */ 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); if (sync_period != refresh_rate) DEBUGF("old sync_period=%d, new refresh=%d", sync_period, 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 = (enum scolor)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", mode_field); /* "display processor hangs" */ DPC -= 2; /* hang around scene of crime */ break; } /* end of control instruction opcode switch */ jsr = 0; } else { /* graphic data */ #if 0 /* XXX ? */ lp0_hit = 0; /* XXX maybe not for OFFSET? */ #endif if (word_number < 0) /* (after reset or control instr.) */ word_number = 0; if (word_number == 0) offset = 0; #define MORE_DATA { ++word_number; goto check; } switch (mode_field = graphic_mode) { /* save for MPR read */ case CHAR: if (word_number > 1) word_number = 0; 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 > 1 || (!file_z_data && word_number > 0)) word_number = 0; 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 > 2 || (!file_z_data && word_number > 1)) word_number = 0; 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 > 2 || (!file_z_data && word_number > 1)) word_number = 0; 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) { sxo = TESTBIT(inst,13); /* sign bit */ if (sxo) ex = -ex; } /* XXX if VT11, set xpos/xoff now?? */ MORE_DATA } if (word_number == 1) { ey = GETFIELD(inst,(VS60?11:9),0); if (VS60) { syo = TESTBIT(inst,13); /* sign bit */ if (syo) ey = -ey; } if (file_z_data) MORE_DATA } if (file_z_data) { /* (VS60) */ ez = GETFIELD(inst,11,2); szo = TESTBIT(inst,13); /* sign bit */ if (szo) 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 = (unsigned char)(sxo & 0xFF); s_yoff = (unsigned char)(syo & 0xFF); s_zoff = (unsigned char)(szo & 0xFF); } else { DEBUGF("point i%d (%d,%d,%d)\r\n", i, (int)ex, (int)ey, (int)ez); point3(i, VSCALE(ex) + xoff, VSCALE(ey) + yoff, VSCALE(ez * 4) + zoff, 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 = (unsigned char)(sxo & 0xFF); s_yoff = (unsigned char)(syo & 0xFF); } else { DEBUGF("point i%d (%d,%d)\r\n", i, (int)ex, (int)ey); point2(i, VSCALE(ex) + xoff, VSCALE(ey) + yoff, VS60); } } break; case GRAPHX: /* (or BLVECT if VS60) */ word_number = 0; 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 (%d) i%d\r\n", (int)ex, i); ey = ypos + VSCALE(graphplot_step); /* VT48 ES says first datum doesn't increment Y; that's wrong */ /* diagnostic DZVSD shows that "i" bit is ignored! */ point2(1, VSCALE(ex) + xoff, ey, VS60); } break; case GRAPHY: /* (or BLVECT if VS60) */ word_number = 0; 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 (%d) i%d\r\n", (int)ey, i); ex = xpos + VSCALE(graphplot_step); /* VT48 ES says first datum doesn't increment X; that's wrong */ /* diagnostic DZVSD shows that "i" bit is ignored! */ point2(1, ex, VSCALE(ey) + yoff, VS60); } break; case RELPOINT: if (word_number > 1 || (!file_z_data && word_number > 0)) word_number = 0; 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); point3(i, xpos + VSCALE(ex), ypos + VSCALE(ey), zpos + VSCALE(ez * 4), 1); } else { DEBUGF("relative point i%d (%d,%d)\r\n", i, (int)ex, (int)ey); point2(i, xpos + VSCALE(ex), ypos + VSCALE(ey), 1); } break; /* the remaining graphic data types are supported by the VS60 only */ case BSVECT: /* (VS60) */ if (word_number > 1) word_number = 0; 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); if (lphit_irq || edge_irq) /* MORE_DATA skips this */ vt_lpen_intr(); /* post graphic interrupt to host */ 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 > 2 || (!file_z_data && word_number > 1)) word_number = 0; 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 > 5 || (!file_z_data && word_number > 3)) word_number = 0; 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; /* LP hit & edge interrupts triggered only while in data mode */ if (lphit_irq || edge_irq) vt_lpen_intr(); /* post graphic interrupt to host */ } /* 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 != display_lp_sw) { /* tip-switch state change */ lp0_sw_state = display_lp_sw; /* track switch state */ lp0_up = !(lp0_down = lp0_sw_state); /* set transition flags */ if (lp0_sw_intr_ena) lpsw_irq = 1; } if (lpsw_irq) /* (LP hit or edge interrupt already triggered above) */ vt_lpen_intr(); /* post graphic interrupt to host */ else if (internal_stop && stop_intr_ena) /* ext_stop does immediately */ 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 /* risky? */ 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 */