simh-testsetgenerator/Ibm1130/ibm1130_gdu.c
Bob Supnik 2c2dd5ea33 Notes For V2.10-0
WARNING: V2.10 has reorganized and renamed some of the definition
files for the PDP-10, PDP-11, and VAX.  Be sure to delete all
previous source files before you unpack the Zip archive, or
unpack it into a new directory structure.

WARNING: V2.10 has a new, more comprehensive save file format.
Restoring save files from previous releases will cause 'invalid
register' errors and loss of CPU option flags, device enable/
disable flags, unit online/offline flags, and unit writelock
flags.

WARNING: If you are using Visual Studio .NET through the IDE,
be sure to turn off the /Wp64 flag in the project settings, or
dozens of spurious errors will be generated.

WARNING: Compiling Ethernet support under Windows requires
extra steps; see the Ethernet readme file.  Ethernet support is
currently available only for Windows, Linux, NetBSD, and OpenBSD.

1. New Features

1.1 SCP and Libraries

- The VT emulation package has been replaced by the capability
  to remote the console to a Telnet session.  Telnet clients
  typically have more complete and robust VT100 emulation.
- Simulated devices may now have statically allocated buffers,
  in addition to dynamically allocated buffers or disk-based
  data stores.
- The DO command now takes substitutable arguments (max 9).
  In command files, %n represents substitutable argument n.
- The initial command line is now interpreted as the command
  name and substitutable arguments for a DO command.  This is
  backward compatible to prior versions.
- The initial command line parses switches.  -Q is interpreted
  as quiet mode; informational messages are suppressed.
- The HELP command now takes an optional argument.  HELP <cmd>
  types help on the specified command.
- Hooks have been added for implementing GUI-based consoles,
  as well as simulator-specific command extensions.  A few
  internal data structures and definitions have changed.
- Two new routines (tmxr_open_master, tmxr_close_master) have
  been added to sim_tmxr.c.  The calling sequence for
  sim_accept_conn has been changed in sim_sock.c.
- The calling sequence for the VM boot routine has been modified
  to add an additional parameter.
- SAVE now saves, and GET now restores, controller and unit flags.
- Library sim_ether.c has been added for Ethernet support.

1.2 VAX

- Non-volatile RAM (NVR) can behave either like a memory or like
  a disk-based peripheral.  If unattached, it behaves like memory
  and is saved and restored by SAVE and RESTORE, respectively.
  If attached, its contents are loaded from disk by ATTACH and
  written back to disk at DETACH and EXIT.
- SHOW <device> VECTOR displays the device's interrupt vector.
  A few devices allow the vector to be changed with SET
  <device> VECTOR=nnn.
- SHOW CPU IOSPACE displays the I/O space address map.
- The TK50 (TMSCP tape) has been added.
- The DEQNA/DELQA (Qbus Ethernet controllers) have been added.
- Autoconfiguration support has been added.
- The paper tape reader has been removed from vax_stddev.c and
  now references a common implementation file, dec_pt.h.
- Examine and deposit switches now work on all devices, not just
  the CPU.
- Device address conflicts are not detected until simulation starts.

1.3 PDP-11

- SHOW <device> VECTOR displays the device's interrupt vector.
  Most devices allow the vector to be changed with SET
  <device> VECTOR=nnn.
- SHOW CPU IOSPACE displays the I/O space address map.
- The TK50 (TMSCP tape), RK611/RK06/RK07 (cartridge disk),
  RX211 (double density floppy), and KW11P programmable clock
  have been added.
- The DEQNA/DELQA (Qbus Ethernet controllers) have been added.
- Autoconfiguration support has been added.
- The paper tape reader has been removed from pdp11_stddev.c and
  now references a common implementation file, dec_pt.h.
- Device bootstraps now use the actual CSR specified by the
  SET ADDRESS command, rather than just the default CSR.  Note
  that PDP-11 operating systems may NOT support booting with
  non-standard addresses.
- Specifying more than 256KB of memory, or changing the bus
  configuration, causes all peripherals that are not compatible
  with the current bus configuration to be disabled.
- Device address conflicts are not detected until simulation starts.

1.4 PDP-10

- SHOW <device> VECTOR displays the device's interrupt vector.
  A few devices allow the vector to be changed with SET
  <device> VECTOR=nnn.
- SHOW CPU IOSPACE displays the I/O space address map.
- The RX211 (double density floppy) has been added; it is off
  by default.
- The paper tape now references a common implementation file,
  dec_pt.h.
- Device address conflicts are not detected until simulation starts.

1.5 PDP-1

- DECtape (then known as MicroTape) support has been added.
- The line printer and DECtape can be disabled and enabled.

1.6 PDP-8

- The RX28 (double density floppy) has been added as an option to
  the existing RX8E controller.
- SHOW <device> DEVNO displays the device's device number.  Most
  devices allow the device number to be changed with SET <device>
  DEVNO=nnn.
- Device number conflicts are not detected until simulation starts.

1.7 IBM 1620

- The IBM 1620 simulator has been released.

1.8 AltairZ80

- A hard drive has been added for increased storage.
- Several bugs have been fixed.

1.9 HP 2100

- The 12845A has been added and made the default line printer (LPT).
  The 12653A has been renamed LPS and is off by default.  It also
  supports the diagnostic functions needed to run the DCPC and DMS
  diagnostics.
- The 12557A/13210A disk defaults to the 13210A (7900/7901).
- The 12559A magtape is off by default.
- New CPU options (EAU/NOEAU) enable/disable the extended arithmetic
  instructions for the 2116.  These instructions are standard on
  the 2100 and 21MX.
- New CPU options (MPR/NOMPR) enable/disable memory protect for the
  2100 and 21MX.
- New CPU options (DMS/NODMS) enable/disable the dynamic mapping
  instructions for the 21MX.
- The 12539 timebase generator autocalibrates.

1.10 Simulated Magtapes

- Simulated magtapes recognize end of file and the marker
  0xFFFFFFFF as end of medium.  Only the TMSCP tape simulator
  can generate an end of medium marker.
- The error handling in simulated magtapes was overhauled to be
  consistent through all simulators.

1.11 Simulated DECtapes

- Added support for RT11 image file format (256 x 16b) to DECtapes.

2. Release Notes

2.1 Bugs Fixed

- TS11/TSV05 was not simulating the XS0_MOT bit, causing failures
  under VMS.  In addition, two of the CTL options were coded
  interchanged.
- IBM 1401 tape was not setting a word mark under group mark for
  load mode reads.  This caused the diagnostics to crash.
- SCP bugs in ssh_break and set_logon were fixed (found by Dave
  Hittner).
- Numerous bugs in the HP 2100 extended arithmetic, floating point,
  21MX, DMS, and IOP instructions were fixed.  Bugs were also fixed
  in the memory protect and DMS functions.  The moving head disks
  (DP, DQ) were revised to simulate the hardware more accurately.
  Missing functions in DQ (address skip, read address) were added.

2.2 HP 2100 Debugging

- The HP 2100 CPU nows runs all of the CPU diagnostics.
- The peripherals run most of the peripheral diagnostics.  There
  is still a problem in overlapped seek operation on the disks.
  See the file hp2100_diag.txt for details.

3. In Progress

These simulators are not finished and are available in a separate
Zip archive distribution.

- Interdata 16b/32b: coded, partially tested.  See the file
  id_diag.txt for details.
- SDS 940: coded, partially tested.
2011-04-15 08:33:49 -07:00

1118 lines
30 KiB
C

#include "ibm1130_defs.h"
/* ibm1130_gdu.c: IBM 1130 2250 Graphical Display Unit
(Under construction)
// stuff to fix:
// "store revert" might be backwards?
// alpha keyboard is not implemented
// pushbuttons are not implemented
// there is something about interrupts being deferred during a subroutine transition?
Based on the SIMH package written by Robert M Supnik
* (C) Copyright 2002, Brian Knittel.
* You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
* RISK basis, there is no warranty of fitness for any purpose, and the rest of the
* usual yada-yada. Please keep this notice and the copyright in any distributions
* or modifications.
*
* This is not a supported product, but I welcome bug reports and fixes.
* Mail to sim@ibm1130.org
*/
#define BLIT_MODE // normally defined, undefine when debugging generate_image()
//#define DEBUG_LIGHTPEN // normally undefined, define to visualize light-pen sensing
#define DEFAULT_GDU_RATE 20 // default frame rate
#define DEFAULT_PEN_THRESHOLD 3 // default looseness of light-pen hit
#define INDWIDTH 32 // width of an indicator (there are two columns of these)
#define INITSIZE 512 // initial window size
#define GDU_DSW_ORDER_CONTROLLED_INTERRUPT 0x8000
#define GDU_DSW_KEYBOARD_INTERUPT 0x4000
#define GDU_DSW_DETECT_INTERRUPT 0x2000
#define GDU_DSW_CYCLE_STEAL_CHECK 0x1000
#define GDU_DSW_DETECT_STATUS 0x0800
#define GDU_DSW_LIGHT_PEN_SWITCH 0x0100
#define GDU_DSW_BUSY 0x0080
#define GDU_DSW_CHARACTER_MODE 0x0040
#define GDU_DSW_POINT_MODE 0x0020
#define GDU_DSW_ADDR_DISP 0x0003
#define GDU_FKEY_DATA_AVAILABLE 0x8000
#define GDU_FKEY_KEY_CODE 0x1F00
#define GDU_FKEY_OVERLAY_CODE 0x00FF
#define GDU_AKEY_DATA_AVAILABLE 0x8000
#define GDU_AKEY_END 0x1000
#define GDU_AKEY_CANCEL 0x0800
#define GDU_AKEY_ADVANCE 0x0400
#define GDU_AKEY_BACKSPACE 0x0200
#define GDU_AKEY_JUMP 0x0100
#define GDU_AKEY_KEY_CODE 0x00FF
/* -------------------------------------------------------------------------------------- */
#define UNIT_V_DISPLAYED (UNIT_V_UF + 0)
#define UNIT_V_DETECTS_ENABLED (UNIT_V_UF + 1)
#define UNIT_V_INTERRUPTS_DEFERRED (UNIT_V_UF + 2)
#define UNIT_V_LARGE_CHARS (UNIT_V_UF + 3)
#define UNIT_DISPLAYED (1u << UNIT_V_DISPLAYED) /* display windows is up */
#define UNIT_DETECTS_ENABLED (1u << UNIT_V_DETECTS_ENABLED) /* light pen detects are enabled */
#define UNIT_INTERRUPTS_DEFERRED (1u << UNIT_V_INTERRUPTS_DEFERRED) /* light pen interrupts are deferred */
#define UNIT_LARGE_CHARS (1u << UNIT_V_LARGE_CHARS) /* large character mode */
static t_stat gdu_reset (DEVICE *dptr);
static int16 gdu_dsw = 1; /* device status word */
static int16 gdu_ar = 0; /* address register */
static int16 gdu_x = 0; /* X deflection */
static int16 gdu_y = 0; /* Y deflection */
static int16 gdu_fkey = 0; /* function keyboard register */
static int16 gdu_akey = 0; /* alphanumeric keyboard register */
static int16 gdu_revert = 0; /* revert address register */
static int32 gdu_indicators = 0; /* programmed indicator lamps */
static int32 gdu_threshold = DEFAULT_PEN_THRESHOLD; /* mouse must be within 3/1024 of line to be a hit */
static int32 gdu_rate = DEFAULT_GDU_RATE; /* refresh rate. 0 = default */
UNIT gdu_unit = { UDATA (NULL, 0, 0) };
REG gdu_reg[] = {
{ HRDATA (GDUDSW, gdu_dsw, 16) }, /* device status word */
{ HRDATA (GDUAR, gdu_ar, 16) }, /* address register */
{ HRDATA (GDUXREG, gdu_x, 16) }, /* X deflection register */
{ HRDATA (GDUYREG, gdu_y, 16) }, /* Y deflection register */
{ HRDATA (GDUFKEY, gdu_fkey, 16) }, /* function keyboard register */
{ HRDATA (GDUAKEY, gdu_akey, 16) }, /* alphanumeric keyboard register */
{ HRDATA (GDUREVERT,gdu_revert, 16) }, /* revert address register */
{ HRDATA (GDUAKEY, gdu_indicators, 32) }, /* programmed indicators */
{ DRDATA (GDUTHRESH,gdu_threshold, 32) }, /* mouse closeness threshhold */
{ DRDATA (GDURATE, gdu_rate, 32) }, /* refresh rate in frames/sec */
{ NULL } };
DEVICE gdu_dev = {
"GDU", &gdu_unit, gdu_reg, NULL,
1, 16, 16, 1, 16, 16,
NULL, NULL, gdu_reset,
NULL, NULL, NULL};
/* -------------------------------------------------------------------------------------- */
#ifndef GUI_SUPPORT
static t_stat gdu_reset (DEVICE *dptr)
{
return SCPE_OK;
}
void xio_2250_display (int32 addr, int32 func, int32 modify)
{
// ignore commands to nonexistent device
}
t_bool gdu_active (void)
{
return 0;
}
/* -------------------------------------------------------------------------------------- */
#else // GUI_SUPPORT defined
/******* PLATFORM INDEPENDENT CODE ********************************************************/
static int32 gdu_instaddr; // address of first word of instruction
static int xmouse, ymouse, lpen_dist, lpen_dist2; // current mouse pointer, scaled closeness threshhold, same squared
static double sfactor; // current scaling factor
static t_bool last_abs = TRUE; // last positioning instruction was absolute
static t_bool mouse_present = FALSE; // mouse is/is not in the window
static void clear_interrupts (void);
static void set_indicators (int32 new_inds);
static void start_regeneration (void);
static void halt_regeneration (void);
static void draw_characters (void);
static void notify_window_closed (void);
// routines that must be implemented per-platform
static void DrawLine(int x0, int y0, int x1, int y1);
static void DrawPoint(int x, int y);
static void CheckGDUKeyboard(void);
static t_bool CreateGDUWindow(void);
static void StartGDUUpdates(void);
static void StopGDUUpdates(void);
static void GetMouseCoordinates(void);
static void UpdateGDUIndicators(void);
static void ShowPenHit (int x, int y);
static void EraseGDUScreen (void);
/* -------------------------------------------------------------------------------------- */
void xio_2250_display (int32 addr, int32 func, int32 modify)
{
switch (func) {
case XIO_SENSE_DEV:
ACC = (gdu_dsw & GDU_DSW_BUSY) ? GDU_DSW_BUSY : gdu_dsw;
if (modify & 1)
clear_interrupts();
break;
case XIO_READ: /* store status data into word pointed to by IOCC packet */
if (gdu_dsw & GDU_DSW_BUSY) /* not permitted while device is busy */
break;
WriteW(addr, gdu_ar); /* save status information */
WriteW(addr+1, gdu_dsw);
WriteW(addr+2, gdu_x & 0x7FF);
WriteW(addr+3, gdu_y & 0x7FF);
WriteW(addr+4, gdu_fkey);
WriteW(addr+5, gdu_akey);
gdu_ar = addr+6; /* this alters the channel address register? */
clear_interrupts(); /* read status clears the interrupts */
break;
case XIO_WRITE:
if (gdu_dsw & GDU_DSW_BUSY) /* treated as no-op if display is busy */
break;
if (modify & 0x80) { /* bit 8 on means set indicators, 0 means start regeneration */
set_indicators((ReadW(addr) << 16) | ReadW(addr+1));
}
else {
gdu_ar = addr;
gdu_fkey = 0;
gdu_akey = 0;
clear_interrupts();
start_regeneration();
}
break;
case XIO_CONTROL:
if (modify & 0x80) { /* bit 8 on means reset, off is no-op */
gdu_reset(&gdu_dev);
set_indicators((addr << 16) | addr);
}
break;
default: /* all other commands are no-ops */
break;
}
}
static t_stat gdu_reset (DEVICE *dptr)
{
halt_regeneration();
clear_interrupts();
set_indicators(0);
gdu_x = gdu_y = 512;
CLRBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED | UNIT_DETECTS_ENABLED | UNIT_LARGE_CHARS);
gdu_dsw = 1;
return SCPE_OK;
}
static void clear_interrupts (void)
{
CLRBIT(gdu_dsw, GDU_DSW_ORDER_CONTROLLED_INTERRUPT | GDU_DSW_KEYBOARD_INTERUPT | GDU_DSW_DETECT_INTERRUPT);
CLRBIT(ILSW[3], ILSW_3_2250_DISPLAY);
calc_ints();
}
static void gdu_interrupt (int32 dswbit)
{
SETBIT(gdu_dsw, dswbit);
SETBIT(ILSW[3], ILSW_3_2250_DISPLAY);
calc_ints();
halt_regeneration();
}
static void set_indicators (int32 new_inds)
{
gdu_indicators = new_inds;
if (gdu_unit.flags & UNIT_DISPLAYED)
UpdateGDUIndicators();
}
static void start_regeneration (void)
{
SETBIT(gdu_dsw, GDU_DSW_BUSY);
if (gdu_unit.flags & UNIT_DISPLAYED) {
StartGDUUpdates();
}
else {
if (! CreateGDUWindow())
return;
SETBIT(gdu_unit.flags, UNIT_DISPLAYED);
}
}
static void halt_regeneration (void)
{
if (gdu_dsw & GDU_DSW_BUSY) {
StopGDUUpdates();
CLRBIT(gdu_dsw, GDU_DSW_BUSY);
}
EraseGDUScreen();
}
static void notify_window_closed (void)
{
if (gdu_dsw & GDU_DSW_BUSY) {
StopGDUUpdates();
CLRBIT(gdu_dsw, GDU_DSW_BUSY);
}
CLRBIT(gdu_unit.flags, UNIT_DISPLAYED);
gdu_reset(&gdu_dev);
}
static int32 read_gduword (void)
{
int32 w;
w = M[gdu_ar++ & mem_mask];
gdu_dsw = (gdu_dsw & ~GDU_DSW_ADDR_DISP) | ((gdu_ar - gdu_instaddr) & GDU_DSW_ADDR_DISP);
return w;
}
#define DIST2(x0,y0,x1,y1) (((x1)-(x0))*((x1)-(x0))+((y1)-(y0))*((y1)-(y0)))
static void draw (int32 newx, int32 newy, t_bool beam)
{
int xmin, xmax, ymin, ymax, xd, yd;
double s;
int hit = FALSE;
if (beam) {
if (gdu_dsw & GDU_DSW_POINT_MODE) {
DrawPoint(newx, newy);
#ifdef DEBUG_LIGHTPEN
if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)
hit = TRUE;
#else
if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present)
if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)
hit = TRUE;
#endif
}
else {
DrawLine(gdu_x, gdu_y, newx, newy);
// calculate proximity of light pen to the line
#ifndef DEBUG_LIGHTPEN
if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present) {
#endif
if (gdu_x <= newx)
xmin = gdu_x, xmax = newx;
else
xmin = newx, xmax = gdu_x;
if (gdu_y <= newy)
ymin = gdu_y, ymax = newy;
else
ymin = newy, ymax = gdu_y;
if (newx == gdu_x) {
// line is vertical. Nearest point is an endpoint if the mouse is above or
// below the line segment, otherwise the segment point at the same y as the mouse
xd = gdu_x;
yd = (ymouse <= ymin) ? ymin : (ymouse >= ymax) ? ymax : ymouse;
if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)
hit = TRUE;
}
else if (newy == gdu_y) {
// line is horizontal. Nearest point is an endpoint if the mouse is to the left or
// the right of the line segment, otherwise the segment point at the same x as the mouse
xd = (xmouse <= xmin) ? xmin : (xmouse >= xmax) ? xmax : xmouse;
yd = gdu_y;
if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)
hit = TRUE;
}
else {
// line is diagonal. See if the mouse is inside the box lpen_dist wider than the line segment's bounding rectangle
if (xmouse >= (xmin-lpen_dist) && xmouse <= (xmax+lpen_dist) && ymouse >= (ymin-lpen_dist) || ymouse <= (ymax+lpen_dist)) {
// compute the point at the intersection of the line through the line segment and the normal
// to that line through the mouse. This is the point on the line through the line segment
// nearest the mouse
s = (double)(newy - gdu_y) / (double)(newx - gdu_x); // slope of line segment
xd = (int) ((ymouse + xmouse/s - gdu_y + s*gdu_x) / (s + 1./s) + 0.5);
// if intersection is beyond either end of the line segment, the nearest point to the
// mouse is nearest segment end, otherwise it's the computed intersection point
if (xd < xmin || xd > xmax) {
#ifdef DEBUG_LIGHTPEN
// if it's a hit, set xd and yd so we can display the hit
if (DIST2(gdu_x, gdu_y, xmouse, ymouse) <= lpen_dist2) {
hit = TRUE;
xd = gdu_x;
yd = gdu_y;
}
else if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2) {
hit = TRUE;
xd = newx;
yd = newy;
}
#else
if (DIST2(gdu_x, gdu_y, xmouse, ymouse) <= lpen_dist2 || DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)
hit = TRUE;
#endif
}
else {
yd = (int) (gdu_y + s*(xd - gdu_x) + 0.5);
if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)
hit = TRUE;
}
}
}
#ifndef DEBUG_LIGHTPEN
}
#endif
}
}
if (hit) {
#ifdef DEBUG_LIGHTPEN
ShowPenHit(xd, yd);
if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present)
SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
#else
SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
#endif
}
gdu_x = newx;
gdu_y = newy;
}
static void generate_image (void)
{
int32 instr, new_addr, newx, newy;
t_bool run = TRUE, accept;
if (! (gdu_dsw & GDU_DSW_BUSY))
return;
GetMouseCoordinates();
lpen_dist = (int) (gdu_threshold/sfactor + 0.5); // mouse-to-line threshhold at current scaling factor
lpen_dist2 = lpen_dist * lpen_dist;
while (run) {
if ((gdu_dsw & GDU_DSW_DETECT_STATUS) && ! (gdu_unit.flags & UNIT_INTERRUPTS_DEFERRED)) {
CLRBIT(gdu_dsw, GDU_DSW_DETECT_STATUS); // clear when interrupt is activated
gdu_interrupt(GDU_DSW_DETECT_INTERRUPT);
run = FALSE;
break;
}
gdu_instaddr = gdu_ar; // remember address of GDU instruction
instr = read_gduword(); // fetch instruction (and we really are cycle stealing here!)
switch ((instr >> 12) & 0xF) { // decode instruction
case 0: // short branch
case 1:
gdu_revert = gdu_ar; // save revert address & get new address
gdu_ar = read_gduword() & 0x1FFF;
if (gdu_dsw & GDU_DSW_CHARACTER_MODE) {
draw_characters(); // in character mode this means we are at character data
gdu_ar = gdu_revert;
}
break;
case 2: // long branch/interrupt
new_addr = read_gduword(); // get next word
accept = ((instr & 1) ? (gdu_dsw & GDU_DSW_LIGHT_PEN_SWITCH) : TRUE) && ((instr & 2) ? (gdu_dsw & GDU_DSW_DETECT_STATUS) : TRUE);
if (instr & 2) // clear after testing
CLRBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
if (instr & 0x0400) // NOP
accept = FALSE;
if (accept) {
if (instr & 0x0800) { // branch
gdu_revert = gdu_ar;
if (instr & 0x0080) // indirect
new_addr = M[new_addr & mem_mask];
gdu_ar = new_addr;
if (gdu_dsw & GDU_DSW_CHARACTER_MODE) {
draw_characters();
gdu_ar = gdu_revert;
}
}
else { // interrupt
gdu_interrupt(GDU_DSW_ORDER_CONTROLLED_INTERRUPT);
run = FALSE;
}
}
break;
case 3: // control instructions
CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
switch ((instr >> 8) & 0xF) {
case 1: // set pen mode
if ((instr & 0xC) == 8)
SETBIT(gdu_unit.flags, UNIT_DETECTS_ENABLED);
else if ((instr & 0xC) == 4)
CLRBIT(gdu_unit.flags, UNIT_DETECTS_ENABLED);
if ((instr & 0x3) == 2)
SETBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED);
else if ((instr & 0x3) == 1)
CLRBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED);
break;
case 2: // set graphic mode
if (instr & 1)
SETBIT(gdu_dsw, GDU_DSW_POINT_MODE);
else
CLRBIT(gdu_dsw, GDU_DSW_POINT_MODE);
break;
case 3: // set character mode
SETBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
if (instr & 1)
SETBIT(gdu_unit.flags, UNIT_LARGE_CHARS);
else
CLRBIT(gdu_unit.flags, UNIT_LARGE_CHARS);
break;
case 4: // start timer
run = FALSE; // (which, for us, means stop processing until next timer message)
CheckGDUKeyboard();
break;
case 5: // store revert
M[gdu_ar & mem_mask] = gdu_revert;
read_gduword(); // skip to next address
break;
case 6: // revert
gdu_ar = gdu_revert;
break;
default: // all others treated as no-ops
break;
}
break;
case 4: // long absolute
case 5:
CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
newx = instr & 0x3FF;
newy = read_gduword() & 0x3FF;
draw(newx, newy, instr & 0x1000);
last_abs = TRUE;
break;
case 6: // short absolute
case 7:
CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
newx = gdu_x;
newy = gdu_y;
if (instr & 0x0800)
newy = instr & 0x3FF;
else
newx = instr & 0x3FF;
draw(newx, newy, instr & 0x1000);
last_abs = TRUE;
break;
default: // high bit set - it's a relative instruction
CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
newx = (instr >> 8) & 0x3F;
newy = instr & 0x3F;
if (instr & 0x4000) // sign extend x - values are in 2's complement
newx |= -1 & ~0x3F; // although documentation doesn't make that clear
if (instr & 0x0040) // sign extend y
newy |= -1 & ~0x3F;
newx = gdu_x + newx;
newy = gdu_y + newy;
draw(newx, newy, instr & 0x0080);
last_abs = FALSE;
break;
}
}
}
static struct charinfo { // character mode scaling info:
int dx, dy; // character and line spacing
double sx, sy; // scaling factors: character units to screen units
int xoff, yoff; // x and y offset to lower left corner of character
int suby; // subscript/superscript offset
} cx[2] = {
{14, 20, 1.7, 2.0, -6, -7, 6}, // regular
{21, 30, 2.5, 3.0, -9, -11, 9} // large
};
static void draw_characters (void)
{
int32 w, x0, y0, x1, y1, yoff = 0, ninstr = 0;
t_bool dospace, didstroke = FALSE;
struct charinfo *ci;
ci = &cx[(gdu_unit.flags & UNIT_LARGE_CHARS) ? 1 : 0];
x0 = gdu_x + ci->xoff; // starting position
y0 = gdu_y + ci->yoff;
do {
if (++ninstr > 29) { // too many control words
gdu_interrupt(GDU_DSW_CYCLE_STEAL_CHECK);
return;
}
dospace = TRUE;
w = M[gdu_ar++ & mem_mask]; // get next stroke or control word
x1 = (w >> 12) & 7;
y1 = (w >> 8) & 7;
if (x1 == 7) { // this is a character control word
dospace = FALSE; // inhibit character spacing
switch (y1) {
case 1: // subscript
if (yoff == 0) // (ignored if superscript is in effect)
yoff = -ci->suby;
break;
// case 2: // no-op or null (nothing to do)
// default: // all unknowns are no-ops
// break;
case 4: // superscript
yoff = ci->suby;
break;
case 7: // new line
gdu_x = 0;
gdu_y -= ci->dy;
if (gdu_y < 0 && last_abs)
gdu_y = 1024 - ci->dy; // this is a guess
break;
}
}
else { // this is stroke data -- extract two strokes
x1 = gdu_x + (int) (x1*ci->sx + 0.5);
y1 = gdu_y + (int) ((y1+yoff)*ci->sy + 0.5);
if (w & 0x0800) {
didstroke = TRUE;
DrawLine(x0, y0, x1, y1);
}
x0 = (w >> 4) & 7;
y0 = w & 7;
x0 = gdu_x + (int) (x0*ci->sx + 0.5);
y0 = gdu_y + (int) ((y0+yoff)*ci->sy + 0.5);
if (w & 0x0008) {
didstroke = TRUE;
DrawLine(x1, y1, x0, y0);
}
}
if (dospace) {
gdu_x += ci->dx;
if (gdu_x > 1023 && last_abs) { // line wrap
gdu_x = 0;
gdu_y -= ci->dy;
}
}
} while ((w & 0x0080) == 0); // repeat until we hit revert bit
if (didstroke && mouse_present && (gdu_unit.flags & UNIT_DETECTS_ENABLED)) {
if (xmouse >= (gdu_x - ci->xoff/2) && xmouse <= (gdu_x + ci->xoff/2) &&
ymouse >= (gdu_y - ci->yoff/2) && ymouse <= (gdu_y + ci->yoff/2))
SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
}
}
/******* PLATFORM SPECIFIC CODE ***********************************************************/
#ifdef WIN32
#include <windows.h>
#include <windowsx.h>
#define APPCLASS "IBM2250GDU" // window class name
#define RGB_GREEN RGB(0,255,0) // handy colors
#define RGB_RED RGB(255,0,0)
static HINSTANCE hInstance;
static HWND hwGDU = NULL;
static HDC hdcGDU = NULL;
static HBITMAP hBmp = NULL;
static int curwid = 0;
static int curht = 0;
static BOOL wcInited = FALSE;
static int GDUPumpID = 0;
static HANDLE hGDUPump = INVALID_HANDLE_VALUE;
static HPEN hGreenPen = NULL;
static HBRUSH hRedBrush = NULL;
#ifdef DEBUG_LIGHTPEN
static HPEN hRedPen = NULL;
#endif
static HBRUSH hGrayBrush, hDarkBrush;
static HPEN hBlackPen;
static LRESULT APIENTRY GDUWndProc (HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
static DWORD WINAPI GDUPump (LPVOID arg);
static void destroy_GDU_window (void)
{
if (hwGDU != NULL)
SendMessage(hwGDU, WM_CLOSE, 0, 0); // cross thread call is OK
if (hGDUPump != INVALID_HANDLE_VALUE) { // this is not the most graceful way to do it
TerminateThread(hGDUPump, 0);
hGDUPump = INVALID_HANDLE_VALUE;
GDUPumpID = 0;
hwGDU = NULL;
}
if (hdcGDU != NULL) {
DeleteDC(hdcGDU);
hdcGDU = NULL;
}
if (hBmp != NULL) {
DeleteObject(hBmp);
hBmp = NULL;
}
if (hGreenPen != NULL) {
DeleteObject(hGreenPen);
hGreenPen = NULL;
}
if (hRedBrush != NULL) {
DeleteObject(hRedBrush);
hRedBrush = NULL;
}
#ifdef DEBUG_LIGHTPEN
if (hRedPen != NULL) {
DeleteObject(hRedPen);
hRedPen = NULL;
}
#endif
}
static t_bool CreateGDUWindow (void)
{
static BOOL did_atexit = FALSE;
if (hwGDU != NULL) { // window already exists
StartGDUUpdates();
return TRUE;
}
hInstance = GetModuleHandle(NULL);
if (hGDUPump == INVALID_HANDLE_VALUE)
hGDUPump = CreateThread(NULL, 0, GDUPump, 0, 0, &GDUPumpID);
if (! did_atexit) {
atexit(destroy_GDU_window);
did_atexit = TRUE;
}
return TRUE;
}
// windows message handlers ----------------------------------------------------
// close the window
static void gdu_WM_CLOSE (HWND hWnd)
{
DestroyWindow(hWnd);
}
// the window is being destroyed
static void gdu_WM_DESTROY (HWND hWnd)
{
notify_window_closed();
hwGDU = NULL;
}
// adjust the min and max resizing boundaries
static void gdu_WM_GETMINMAXINFO (HWND hWnd, LPMINMAXINFO mm)
{
mm->ptMinTrackSize.x = 100 + 2*INDWIDTH;
mm->ptMinTrackSize.y = 100;
}
static void PaintImage (HDC hDC, BOOL draw_indicators)
{
HPEN hOldPen;
RECT r;
int wid, ht, x, y, dy, i, j, ycirc;
unsigned long bit;
GetClientRect(hwGDU, &r);
wid = r.right+1 - 2*INDWIDTH;
ht = r.bottom+1;
sfactor = (double) MIN(wid,ht) / 1024.;
if (gdu_dsw & GDU_DSW_BUSY) {
#ifdef BLIT_MODE
// if compiled for BLIT_MODE, draw the image into a memory display context, then
// blit the new image over window. This eliminates the flicker that a normal erase-and-
// repaint would cause.
if (wid != curwid || ht != curht) { // dimensions have changed, discard old memory display context
if (hdcGDU != NULL) {
DeleteDC(hdcGDU);
hdcGDU = NULL;
}
curwid = wid;
curht = ht;
}
if (hdcGDU == NULL) { // allocate memory display context & select a bitmap into it
hdcGDU = CreateCompatibleDC(hDC);
if (hBmp != NULL)
DeleteObject(hBmp);
hBmp = CreateCompatibleBitmap(hDC, wid, ht);
SelectObject(hdcGDU, hBmp);
}
PatBlt(hdcGDU, 0, 0, wid, ht, BLACKNESS); // start with a black screen
hOldPen = SelectObject(hdcGDU, hGreenPen);
SetMapMode(hdcGDU, MM_ANISOTROPIC);
SetWindowExtEx(hdcGDU, 1024, -1024, NULL);
SetViewportExtEx(hdcGDU, wid, ht, NULL);
SetWindowOrgEx(hdcGDU, 0, 1023, NULL);
generate_image(); // run the display program to paint the image into the memory context
SetWindowExtEx(hdcGDU, wid, ht, NULL); // undo the scaling so the blit isn't distorted
SetViewportExtEx(hdcGDU, wid, ht, NULL);
SetWindowOrgEx(hdcGDU, 0, 0, NULL);
BitBlt(hDC, 0, 0, wid, ht, hdcGDU, 0, 0, SRCCOPY); // blit the new image over the old
SelectObject(hdcGDU, hOldPen);
#else
// for testing purposes -- draw the image directly onto the screen.
// Compile this way when you want to single-step through the image drawing routine,
// so you can see the draws occur.
hdcGDU = hDC;
hOldPen = SelectObject(hdcGDU, hGreenPen);
SetMapMode(hdcGDU, MM_ANISOTROPIC);
SetWindowExtEx(hdcGDU, 1024, -1024, NULL);
SetViewportExtEx(hdcGDU, wid, ht, NULL);
SetWindowOrgEx(hdcGDU, 0, 1023, NULL);
generate_image();
SelectObject(hdcGDU, hOldPen);
hdcGDU = NULL;
#endif
}
if (draw_indicators) {
x = r.right-2*INDWIDTH+1;
dy = ht / 16;
ycirc = MIN(dy-2, 8);
r.left = x;
FillRect(hDC, &r, hGrayBrush);
SelectObject(hDC, hBlackPen);
bit = 0x80000000L;
for (i = 0; i < 2; i++) {
MoveToEx(hDC, x, 0, NULL);
LineTo(hDC, x, r.bottom);
y = 0;
for (j = 0; j < 16; j++) {
MoveToEx(hDC, x, y, NULL);
LineTo(hDC, x+INDWIDTH, y);
SelectObject(hDC, (gdu_indicators & bit) ? hRedBrush : hDarkBrush);
Pie(hDC, x+1, y+1, x+1+ycirc, y+1+ycirc, x+1, y+1, x+1, y+1);
y += dy;
bit >>= 1;
}
x += INDWIDTH;
}
}
}
// repaint the window
static void gdu_WM_PAINT (HWND hWnd)
{
PAINTSTRUCT ps;
HDC hDC;
// code for display
hDC = BeginPaint(hWnd, &ps);
PaintImage(hDC, TRUE);
EndPaint(hWnd, &ps);
}
// the window has been resized
static void gdu_WM_SIZE (HWND hWnd, UINT state, int cx, int cy)
{
InvalidateRect(hWnd, NULL, FALSE);
}
// tweak the sizing rectangle during a resize to guarantee a square window
static void gdu_WM_SIZING (HWND hWnd, WPARAM fwSide, LPRECT r)
{
switch (fwSide) {
case WMSZ_LEFT:
case WMSZ_RIGHT:
case WMSZ_BOTTOMLEFT:
case WMSZ_BOTTOMRIGHT:
r->bottom = r->right - r->left - 2*INDWIDTH + r->top;
break;
case WMSZ_TOP:
case WMSZ_BOTTOM:
case WMSZ_TOPRIGHT:
r->right = r->bottom - r->top + r->left + 2*INDWIDTH;
break;
case WMSZ_TOPLEFT:
r->left = r->top - r->bottom + r->right - 2*INDWIDTH;
break;
}
}
// the refresh timer has gone off
static void gdu_WM_TIMER (HWND hWnd, UINT id)
{
HDC hDC;
if (running) { // if CPU is running, update picture
#ifdef BLIT_MODE
hDC = GetDC(hWnd); // blit the new image right over the old
PaintImage(hDC, FALSE);
ReleaseDC(hWnd, hDC);
#else
InvalidateRect(hWnd, NULL, TRUE); // repaint
#endif
}
}
// window procedure ------------------------------------------------------------
#define HANDLE(msg) case msg: return HANDLE_##msg(hWnd, wParam, lParam, gdu_##msg);
#ifndef HANDLE_WM_SIZING
// void Cls_OnSizing(HWND hwnd, UINT fwSide, LPRECT r)
# define HANDLE_WM_SIZING(hwnd, wParam, lParam, fn) \
((fn)((hwnd), (UINT)(wParam), (LPRECT)(lParam)), 0L)
#endif
static LRESULT APIENTRY GDUWndProc (HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
switch (iMessage) {
HANDLE(WM_CLOSE);
HANDLE(WM_GETMINMAXINFO);
HANDLE(WM_DESTROY);
HANDLE(WM_PAINT);
HANDLE(WM_SIZE);
HANDLE(WM_SIZING);
HANDLE(WM_TIMER);
default: // any message we don't process
return DefWindowProc(hWnd, iMessage, wParam, lParam);
}
return 0L;
}
// graphic calls ----------------------------------------------------------------
static void DrawLine (int x0, int y0, int x1, int y1)
{
MoveToEx(hdcGDU, x0, y0, NULL);
LineTo(hdcGDU, x1, y1);
}
static void DrawPoint (int x, int y)
{
SetPixel(hdcGDU, x, y, RGB_GREEN);
}
static void UpdateGDUIndicators(void)
{
if (hwGDU != NULL)
InvalidateRect(hwGDU, NULL, TRUE);
}
static void CheckGDUKeyboard (void)
{
}
static UINT idTimer = 0;
static void StartGDUUpdates (void)
{
int msec;
if (idTimer == 0) {
msec = (gdu_rate == 0) ? (1000 / DEFAULT_GDU_RATE) : 1000/gdu_rate;
idTimer = SetTimer(hwGDU, 1, msec, NULL);
}
}
static void StopGDUUpdates (void)
{
if (idTimer != 0) {
KillTimer(hwGDU, 1);
idTimer = 0;
}
}
static void GetMouseCoordinates()
{
POINT p;
RECT r;
GetCursorPos(&p);
GetClientRect(hwGDU, &r);
if (! ScreenToClient(hwGDU, &p)) {
xmouse = ymouse = -2000;
mouse_present = FALSE;
return;
}
if (p.x < r.left || p.x >= r.right || p.y < r.top || p.y > r.bottom) {
mouse_present = FALSE;
return;
}
// convert mouse coordinates to scaled coordinates
xmouse = (int) (1024./(r.right+1.-2*INDWIDTH)*p.x + 0.5);
ymouse = 1023 - (int) (1024./(r.bottom+1.)*p.y + 0.5);
mouse_present = TRUE;
}
t_bool gdu_active (void)
{
return gdu_dsw & GDU_DSW_BUSY;
}
static void EraseGDUScreen (void)
{
if (hwGDU != NULL) /* redraw screen. it will be blank if GDU is not running */
InvalidateRect(hwGDU, NULL, TRUE);
}
/* GDUPump - thread responsible for creating and displaying the graphics window */
static DWORD WINAPI GDUPump (LPVOID arg)
{
MSG msg;
WNDCLASS wc;
if (! wcInited) { /* register Window class */
memset(&wc, 0, sizeof(wc));
wc.style = CS_NOCLOSE;
wc.lpfnWndProc = GDUWndProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetStockObject(BLACK_BRUSH);
wc.lpszClassName = APPCLASS;
if (! RegisterClass(&wc)) {
GDUPumpID = 0;
return 0;
}
wcInited = TRUE;
}
if (hGreenPen == NULL)
hGreenPen = CreatePen(PS_SOLID, 1, RGB_GREEN);
#ifdef DEBUG_LIGHTPEN
if (hRedPen == NULL)
hRedPen = CreatePen(PS_SOLID, 1, RGB_RED);
#endif
if (hRedBrush == NULL)
hRedBrush = CreateSolidBrush(RGB_RED);
hGrayBrush = GetStockObject(GRAY_BRUSH);
hDarkBrush = GetStockObject(DKGRAY_BRUSH);
hBlackPen = GetStockObject(BLACK_PEN);
if (hwGDU == NULL) { /* create window */
hwGDU = CreateWindow(APPCLASS,
"2250 Display",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT, // initial x, y position
INITSIZE+2*INDWIDTH, INITSIZE, // initial width and height
NULL, // parent window handle
NULL, // use class menu
hInstance, // program instance handle
NULL); // create parameters
if (hwGDU == NULL) {
GDUPumpID = 0;
return 0;
}
}
ShowWindow(hwGDU, SW_SHOWNOACTIVATE); /* display it */
UpdateWindow(hwGDU);
StartGDUUpdates();
while (GetMessage(&msg, hwGDU, 0, 0)) { /* message pump - this basically loops forevermore */
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (hwGDU != NULL) {
DestroyWindow(hwGDU); /* but if a quit message got posted, clean up */
hwGDU = NULL;
}
GDUPumpID = 0;
return 0;
}
#ifdef DEBUG_LIGHTPEN
static void ShowPenHit (int x, int y)
{
HPEN hOldPen;
hOldPen = SelectObject(hdcGDU, hRedPen);
DrawLine(x-10, y-10, x+10, y+10);
DrawLine(x-10, y+10, x+10, y-10);
SelectObject(hdcGDU, hOldPen);
}
#endif
#endif // WIN32 defined
#endif // GUI_SUPPORT defined