The Graphics System eXtension
Overview
GSX is an attempt at implementing a CP/M version of the Graphical Kernel System, an international standard for device-independent graphics output. Dr Martin Hepperle's HP Stuff page includes a section on GSX explaining its origin in the world of graphical terminals. In use, a program uses a standard set of drawing operations (line, filled area, text etc.) and a suitable driver will translate this to drawing on a screen / plotter / printer etc.
A conventional GSX system comes in three parts:
- GSX.SYS / GENGRAF.COM contain the device-independent portion of GSX (the GDOS). This is normally added to GSX-aware programs when they are distributed.
- ASSIGN.SYS is a text file listing the device drivers on a system;
- DD*.PRL are the actual device drivers (the GIOS).
Under CP/M-86, the files are:
- GRAPHICS.CMD is the resident portion of GSX, including the GDOS.
- ASSIGN.SYS, as for the CP/M-80 version, lists the device drivers in the system.
- DD*.SYS are the device drivers, in CP/M-86 CMD format.
GSX went on to become the Virtual Device Interface of the GEM environment. GEM versions 1 and 2 support the GSX API as well as the GEM one.
JOYCE incorporates a GSX driver allowing CP/M programs to use 800x600 colour graphics.
The GSX API
GSX is accessed using one BDOS call - number 115. It is entered with C=73h (115) and DE=address of parameter block. On exit, values in the arrays indicated by the parameter block are changed.
In CP/M-86, GSX should be invoked with CX=0473h and DS:DX giving the address of the parameter block. If CH has a value other than 4, it will invoke one of the GDOS internal functions.
The parameter block format is:
DEFW CONTRL ;Address of control array DEFW INTIN ;Address of integer input array DEFW PTSIN ;Address of pixel input array DEFW INTOUT ;Address of integer output array DEFW PTSOUT ;Address of pixel output array
(in CP/M-86, these are 4-byte segment/offset addresses).
The control array is:
CONTRL: DEFW function ;Input: GSX function, 1-33 DEFW #ptsin ;Input: Number of points in PTSIN array. DEFW #ptsout ;Output: Number of points in PTSOUT array. DEFW #intin ;Input: Number of integers in INTIN array. DEFW #intout ;Output: Number of integers in INTOUT array. XCTRL: DEFW special ;Input: For special uses.The coordinates used in PTSIN and PTSOUT are signed 16-bit integer values, 0-7FFFh. The x value comes first. Occasionally, reference is made to device units; these are the actual pixels used on the screen, printer, plotter etc.
Note that if a function takes a fixed number of arguments (for example, the function to set text colour always takes a single integer) some programs will not bother to set the corresponding entry in the control array. So if you happen to be writing something that marshals GSX parameters, it is important to sanity-check these counts before trusting them. Otherwise you may find that you have not transferred all the parameters you need, or transferred vastly too many.
The same caution also needs to be exercised with functions transferring data in the other direction. Function 5 subfunction 1 returns two integers, but many drivers neglect to set #intout to 2 as they should.
Function 1 - Open workstation
Entered with:- function=1;
- #ptsin=0;
- #intin=10;
- INTIN contains initial settings.
- #ptsout=6;
- #intout=45;
- INTOUT contains device data.
This loads a device driver and initialises it. Valid device driver numbers are found in the ASSIGN.SYS file, and follow this pattern:
1- 9: Screen 11-19: Plotter 21-29: Printer 31-39: GKS metafile.
Short of parsing ASSIGN.SYS for yourself, there is no way of knowing what driver numbers are present on the system. Applications tend to assume 1=screen, 11=plotter, 21=printer and ignore other devices.
If an unrecognised driver number is passed, GSX-80 will use whatever driver was most recently in use; when the program starts, this will be the first driver in the list.
The format of the INTIN array is:
INTIN: DEFW device_number DEFW line_style DEFW line_colour DEFW marker_style DEFW marker_colour DEFW text_style DEFW text_colour DEFW fill_style DEFW fill_index DEFW fill_colour
According to the GSX documentation, colour numbers used are:
Monochrome CRT Monochrome printer/plotter Colour 0 Black 0 White 0 Black 1 White 1 Black 1 Red 2 Green 3 Blue 4 Cyan 5 Yellow 6 Magenta 7+ White
Note that if a colour device supports fewer than eight colours, then by default none of the colours will be white, and if you want white you will need to use function 14 to select it. This differs from GEM where the same colour numbers are used for black and white in both colour and mono drivers.
If a device supports more than eight colours, numbers 8 and up can be given non-white values with function 14.
The INTOUT return array gives:
INTOUT: DEFW Screen width, device units DEFW Screen height, device units DEFW 0 if device is capable of continuous scaling (eg a printer), 1 if it is not (eg a CRT) DEFW Width of a pixel, in thousandths of a millimetre. DEFW Height of a pixel, in thousandths of a millimetre. DEFW Number of character sizes, 0 for continuous sizing. DEFW Number of line styles. DEFW Number of line widths. DEFW Number of marker styles. DEFW Number of marker sizes. DEFW Number of fonts. DEFW Number of patterns. DEFW Number of hatch styles. DEFW Number of colours displayable at once. DEFW Number of General Drawing Primitives DEFS 20 ;General Drawing Primitive numbers. ;-1 => End of list ; 1 => Bar ; 2 => Arc ; 3 => Pie slice ; 4 => Circle ; 5 => Ruling characters DEFS 20 ;General Drawing Primitive attributes ;-1 => End of list ; 0 => Polyline ; 1 => Polymarker ; 2 => Text ; 3 => Filled area ; 4 => None DEFW 0 for black/white, 1 for colour. DEFW 0 if text rotation is not possible, 1 if it is. DEFW 0 if filled areas are not possible, 1 if they are. DEFW 0 if cannot read cell array, 1 if can. DEFW Number of colours in the palette. ; 0 => More than 32767 ; 2 => Black and white DEFW Number of locator devices (mice, tablets, lightpens) DEFW Number of valuator devices DEFW Number of choice devices DEFW Workstation type ; 0 => Output only ; 1 => Input only ; 2 => Input and output ; 3 => Segment storage ; 4 => GKS metafile output.
The PTSOUT return array gives:
DEFW ?, minimum character height DEFW ?, maximum character height DEFW minimum line width,? DEFW maximum line width,? DEFW ?, minimum marker height DEFW ?, maximum marker height.
Function 2 - Close workstation
Entered with:- function=2;
- #intin=0.
- #intout=0.
GSX can only use one device at a time. When you have finished with a device, close it.
Function 3 - Clear picture
Entered with:- function=3;
- #intin=0.
- #intout=0.
Empties the buffer containing the current picture. This may be the screen, or buffered data for something like a printer.
Function 4 - Output graphics
Entered with:- function=4;
- #intin=0.
- #intout=0.
Ensures that all graphics have been displayed which should be displayed. On a device such as a printer, this will cause the picture to be printed.
Function 5 - Escape
Entered with:- function=5;
- special=escape number;
- Other values as required by the escape.
The escapes are:
- Get text screen size in characters. The first two words of INTOUT will hold the height and the width respectively, and this therefore ought to return #intout=2. In practice many implementations don't bother to set #intout. If the size returned is 0FFFFh × 0FFFFh (ie, -1 × -1) then the other text mode subfunctions are not available; this is the case on non-screen devices like printers or plotters.
- Enter graphics mode.
- Enter text mode.
- Text cursor up.
- Text cursor down.
- Text cursor right.
- Text cursor left.
- Text cursor home (to top left corner of screen).
- Clear from text cursor to end of screen.
- Clear from text cursor to end of line.
- Move text cursor to coordinates given in INTIN. Therefore this should be entered with #intin=2. The first two words of INTIN will hold row and column respectively, with (1,1) being the top left-hand corner of the screen.
- Print (to the text screen) a string whose characters are stored in INTIN (one character to each word). #intin = length of string.
- Select reverse video.
- Cancel reverse video.
- Return the coordinates of the text cursor in the first two words of INTOUT.
- Is a tablet (or mouse etc.) available? Returns the first word of INTOUT as 0 if no, 1 if yes.
- Dump the current screen to the printer.
- Place a graphic cursor (a mouse pointer or similar). Its coordinates are given in PTSIN, so enter with #ptsin=1.
- Remove the graphic cursor.
Most of these escapes are not supported by the drivers supplied with GEM.
Function 6 - Draw a polyline
Entered with:- function=6;
- #ptsin=number of points;
- Coordinates of points in PTSIN.
Function 7 - Plot a group of markers
Entered with:- function=7;
- #ptsin=number of markers;
- Coordinates of markers in PTSIN.
BUG:: The Digital Research Epson drivers DDFXHR8 and DDFXLR8 do not draw markers until at least one instruction to draw text has been processed.
Function 8 - Draw text
Entered with:- function=8;
- #intin=number of characters;
- #ptsin=1.
- Coordinates of text in PTSIN.
- Text in INTIN.
Function 9 - Draw a filled polygon.
Entered with:- function=9;
- #ptsin=number of vertices;
- Coordinates of vertices in PTSIN.
Returns: Nothing.
Not all drivers can support filled polygons; usually, the fallback position is to draw the outline of the polygon. The Digital Research documentation is stronger, saying that the driver 'must' at least outline the polygon in the current fill colour. However some drivers don't; as far as I can see from disassembly, the BBC Micro drivers just ignore this function entirely.
Function 10 - Output colour index array.
Entered with:- function=10;
- #ptsin=2;
- #intin=length of array;
- Array in INTIN;
- Coordinates in PTSIN for bottom left and top right corners;
- Additional parameters in contrl as follows:
XCTRL: DEFW length of each row DEFW number of elements used in each row DEFW number of rows DEFW modeMode is:
- replace
- overstrike
- XOR
- erase
Although GSX coordinates are measured from the bottom of the screen, the cell array is held in memory in 'top down' format — the first bytes define the top line of the bitmap, not the bottom one.
If the device doesn't support this operation "it must at least outline the area in the current line color." As with fill, there are devices which don't.
Returns: Nothing.Function 11 - General Drawing Primitive
Entered with:- function=11;
- special=primitive ID
- #ptsin, #intin, PTSIN, INTIN vary.
General Drawing Primitives may not be present on all systems; they are normally provided if the hardware can do one of these operations faster than the generic GSX functions. For example, many raster graphic systems can draw a filled bar very quickly since it aligns with the scan lines.
- ID=1
- Filled bar. #ptsin=2; PTSIN gives diagonally opposite corners.
- ID=2
- Arc. #ptsin=4, #intin=2. INTIN holds start angle and end angle in 10ths of a degree; PTSIN holds coords of centre, start point, end point and (radius,0).
- ID=3
- Pie slice. As for arc.
- ID=4
- Filled circle. #ptsin=3; PTSIN holds coordinates of the centre, a point on the circumference and (radius,0).
- ID=5
- Draw text. #ptsin=1; #intin=no. chars. PTSIN holds text coordinates; INTIN holds 16-bit characters. This is intended for writing printer-specific line drawing characters.
- ID=6-7
- Reserved.
- ID=8-10
- Available to implementor (used in GEM).
Function 12 - Set text size
Entered with:- function=12;
- #ptsin=1
- PTSIN holds (0, height)
- PTSOUT[0]=(width,height) of a W
- PTSOUT[1]=(width,height) of a cell
The CBASIC demonstration program passes a height of 0 to select a minimal (but still readable) character height.
Function 13 - Set text direction
Entered with:- function=13;
- #intin=3
- INTIN holds:
- INTIN[0] = Angle to turn through in tenths of a degree (anticlockwise, 0 = normal)
- INTIN[1] = 100 cos (angle)
- INTIN[2] = 100 sin (angle)
In practice many drivers only support text rotations of 0, 90, 180 and 270 degrees.
Function 14 - Set colour index (palette registers)
Entered with:- function=14;
- #intin=4;
- INTIN holds:
- INTIN[0] = Colour number
- INTIN[1] = Red 0-1000
- INTIN[2] = Green 0-1000
- INTIN[3] = Blue 0-1000
DR Graph uses this to move white to colour 1, with all the other colours going higher (so 0=black 1=red 2=green 3=blue... becomes 0=black 1=white 2=red 3=green 4=blue...).
Function 15 - Set line style
Entered with:- function=15;
- #intin=1;
- INTIN[0]=style.
Devices should provide at least five styles, but may support more:
1. Solid ################ 2. Dash ########-------- 3. Dot ###-----###----- 4. Dash dot #######---###--- 5. Long dash ############----
Out-of-range styles get mapped to 1.
Returns the style actually used in INTOUT.
Function 16 - Set line width
Entered with:- function=16;
- #ptsin=1;
- PTSIN[0]=(width,0)
Returns the width actually used in PTSOUT.
Function 17 - Set line colour
Entered with:- function=17;
- #intin=1;
- INTIN[0]=colour.
Returns the colour actually used in INTOUT.
Function 18 - Set marker type
Entered with:- function=18;
- #intin=1;
- INTIN[0]=type.
Devices should provide at least five marker types, but may support more:
- Single pixel.
- Plus
- Asterisk
- Circle
- ×
Out-of-range values will be mapped to 3.
Returns the type actually used in INTOUT.
Function 19 - Set marker height
Entered with:- function=19;
- #ptsin=1;
- PTSIN[0]=(0, height)
Returns the height actually used, which may be less, in PTSOUT.
Function 20 - Set marker colour
Entered with:- function=20;
- #intin=1;
- INTIN[0]=colour.
Returns the colour actually used in INTOUT.
Function 21 - Set text font
Entered with:- function=21;
- #intin=1;
- INTIN[0]=font.
Returns the font actually used in INTOUT.
Function 22 - Set text colour
Entered with:- function=22;
- #intin=1;
- INTIN[0]=colour.
Returns the colour actually used in INTOUT.
Function 23 - Set fill style
Entered with:- function=23;
- #intin=1;
- INTIN[0]=style.
Fill style is 0-3: 0=transparent, 1=solid, 2=Pattern, 3=Hatch. Higher values are treated as 0.
Returns the style actually used in INTOUT.
Function 24 - Set fill index
Entered with:- function=24;
- #intin=1;
- INTIN[0]=fill index.
The fill index is used only with styles 2 & 3. For style 2, the first six patterns should be greyscale shade patterns, with 1 as the lightest and 6 as the darkest.
The first six hatches should be:
- Vertical lines
- Horizontal lines
- Diagonal lines /
- Diagonal lines \
- Vertical and horizontal
- Diagonal / and \
Returns the index actually used in INTOUT.
Function 25 - Set fill colour
Entered with:- function=25;
- #intin=1;
- INTIN[0]=colour.
Returns the colour actually used in INTOUT.
Function 26 - Inquire colour representation (read palette)
Entered with:- function=26;
- #intin=2;
- INTIN[0]=style;
- INTIN[1]=0 to request return values (values requested when palette was set).
- INTIN[1]=1 to request real values (values actually in use at the moment).
Returns INTOUT[0]=Colour value, INTOUT[1-3]=RGB values 0-1000.
Function 27 - Inquire cell array
Entered with:- function=27;
- #ptsin=2;
- #intin=maximum length of colour index array;
- CONTRL[5]=length of each row;
- CONTRL[6]=number of rows.
- PTSIN holds coordinates of bottom LH and top RH corners of array.
Returns:
- CONTRL[7]=no. elements used in each row.
- CONTRL[8]=no. rows used.
- CONTRL[9]=error flag (0=OK 1=error).
- Array in INTOUT.
If INTOUT[x]=-1, the corresponding pixel could not be read.
The next few functions can be operated in Request mode or Sample mode:
Function 28 - Read locator (eg tablet or mouse)
In Request mode:
Entered with:- function=28;
- #ptsin=1;
- #intin=1;
- INTIN[0]=locator number (normally 1 for keyboard, 2 for mouse or tablet);
- PTSIN gives initial coordinates.
Returns:
- PTSOUT=coordinates when key/button pressed;
- INTOUT[0]=terminator (key pressed, or mouse button +20h); #intout=0 if error.
In Request mode, the driver is responsible for drawing the graphic cursor over the selected point, and removing it before returning.
In Sample mode:
Entered with:- function=28;
- #intin=1;
- #ptsin=1;
- INTIN[0]=locator number (normally 1 for keyboard, 2 for mouse etc.)
- PTSIN gives current pointer coordinates.
Returns:
- If coordinates changed:
- #ptsout=1 to indicate coordinates changed; new coordinates in PTSOUT[0];
- If key or button pressed:
- #intout=1 to indicate key/button pressed; and key or button in INTOUT[0].
If you are developing a GSX driver, note that some programs (including DR DRAW) only check for button presses if there are no new coordinates.
In sample mode, the driver does not draw a mouse pointer; this is left to the calling program using escapes 18/19.
Function 29 - Read valuator
In Request mode:
Entered with:- function=29;
- #intin=2;
- INTIN[0]=valuator number.
- INTIN[1]=initial value.
- INTOUT[0]=final value when key/button pressed;
- INTOUT[1]=terminator (key pressed, or button + 20h); #intout=0 if error.
In Sample mode:
Entered with:- function=29;
- #intin=1;
- INTIN[0]=valuator number.
Returns:
- If value changed:
- #intout=1; new value in INTOUT[0].
- If key or button pressed:
- #intout=2 if key or button pressed; final value in INTOUT[0] and key or button in INTOUT[1].
The keyboard valuator (no. 1) moves in steps of 10 using the cursor keys, or 1 using SHIFT+cursor keys.
Function 30 - Read choice
In Request mode:
Entered with:- function=30;
- #intin=1;
- INTIN[0]=choice number (1=function keys on keyboard).
Returns #intout=1, INTOUT[0]=choice (1-n).
In Sample mode:
Entered with:- function=30;
- #intin=1;
- INTIN[0]=choice number (1=function keys on keyboard).
Returns:
- #intout=0 if nothing happened;
- #intout=1 if choice (choice in INTOUT[0]);
- #intout=2 if non-choice key (choice in INTOUI[0], key in INTOUT[1]).
Function 31 - Read string
In Request mode:
Entered with:- function=31;
- #ptsin=INTIN[2];
- #intin=3;
- INTIN[0]=device number;
- INTIN[1]=maximum length;
- INTIN[2]=1 to echo, 0 not to.
- If echoing is on, PTSIN[0] gives coordinates of where to echo the string.
Returns #intout=length of string returned, string in INTOUT.
On a two-head system with separate text and graphics screens, the input text may be echoed on the text screen, not the graphics one.
In Sample mode:
Entered with:- function=31;
- #ptsin=0;
- #intin=2;
- INTIN[0]=device number;
- INTIN[1]=maximum length.
Returns #intout=length of string returned, string in INTOUT.
Function 32 - Set writing mode
Entered with:- function=32;
- #intin=1;
- INTIN[0]=mode.
Note: "background" is the second colour used in dashed lines etc. When such a line is being drawn, the dashes are drawn as "foreground" areas and the gaps as "background" areas.
Modes are:
- Replace. "Foreground" and "background" areas are replaced ("background" areas with GSX colour 0).
- Transparent. "Foreground" areas are replaced but "background" areas stay the same.
- XOR. "Foreground" areas are XOR'ed with previous colour; "background" areas stay the same.
- Erase. "Foreground" areas are written in GSX colour 0; "background" areas stay the same.
Drivers are not required to implement this function. This is understandable on devices like plotters, since if the drawing opcodes are implemented by drawing on physical paper with physical pens, it becomes impossible to erase or XOR the resulting output. Unfortunately there is no way to enquire of a given driver if the function is supported; none of the information returned from the Open Workstation call covers this point. The GSX manual advises to check the returned value to see if it matches the value requested, but this is sadly not foolproof either. Some drivers, like the Epson QX-10 screen driver in monochrome mode, implement the function in that they accept the value, save a copy of it, and return it in INTOUT, but it doesn't seem to affect what appears onscreen.
BUG: The BBC Micro drivers DDBBC0 and DDBBC1 appear to look for the mode in the second word of INTIN, not the first. They also implement the transparent mode as OR and the erase mode as AND, which doesn't match the specification or the behaviour of other GSX drivers.
Function 33 - Set input mode
Entered with:- function=33;
- #intin=2;
- INTIN[0]=device type (1=locator, 2=valuator, 3=choice, 4=string).
- INTIN[1]=mode (1=Request, 2=Sample).
Activity extensions
The 'Activity' GUI on the Apricot PC range uses GSX video drivers, with the admixture of some extensions. The numbering scheme used by the extra functions seems to match the GEM VDI, but the parameters often differ.
Function 104 - Set Perimeter
Entered with:- function=104;
- #intin=1;
- INTIN[0]=perimeter (1 to give filled shapes a perimeter, 0 not to).
Lacunae
The GSX API appears to be intended for output of business/scientific graphics. Compared to later, more comprehensive portable graphics systems like the GEM VDI, there are some gaps in functionality:
- There is no easy way to get the size of a text string. Setting the text height will return character and cell sizes for a 'W', but that will only be useful if the device font is fixed-pitch.
- The only functions resembling support for raster graphics (blitting from screen to screen, or screen to memory, or memory to screen) are the two rarely-implemented 'cell array' functions. Lack of a screen-to-screen blit means that (for example) a scrolling pane could only be implemented by clearing and redrawing the entire pane.
- There is no way to read back the line / marker / text / fill settings, so a program has to keep track of what it has selected.
GDOS internal functions
In GSX-86, the function passed in CX to INT 0E0h is interpreted as follows:
CL = 73h CH low nibble = function, 0-7 CH high nibble = layer number, 0-14
('Layer' is the index into the internal driver table).
Functions are:
- Function=0: KillLayer
Unbind any driver in the specified layer.
- Function=1: BindIndirect
Load a driver into the specified layer by ASSIGN.SYS ID (1-9 for screen, 10-19 for plotter, etc.)
Pass DX = ASSIGN.SYS ID. If succeeded returns AL=0. If failed returns AL=0FFh, AH = error number.
- Function=2: BindFCB
Load a driver into the specified layer by FCB. DS:DX = FCB address.
- Function=3: BindDirect
Map an already-loaded driver into the specified layer. DS:DX = address of entry point.
- Function=4: InvokeDriver
The public GSX API, as described above.
- Function=5: PassThrough
Call the entry point of the driver in the specified layer with all caller registers as passed to GSX-86. No coordinate scaling will be done on entry or exit.
- Function=6: ReturnPtr
Return ES:BX = address within the GDOS of a table describing the specified layer:
00: DB HowBound 0FFh: Driver not loaded 000h: Driver loaded by BindIndirect 001h: Driver loaded by BindFCB 002h: Driver loaded by BindDirect 01: DD ProcPtr Far address of driver entry point 05: DW BasePageSegment Segment of driver's base page [data segment] 07: DW ModuleSize Number of paragraphs allocated to this driver 09: DW ProcId ASSIGN.SYS device ID of this driver 0B: DW XPixels Device resolution, horizontal 0D: DW YPixels Device resolution, vertical 0F: DB 0 Filler byte
- Function=7: ReturnMaxProc
Returns AX = 0Fh (maximum number of layers)
The API design suggests that GSX-86 was designed to be able to cope with multiple concurrently-loaded drivers, using the layer number to distinguish them (ie, calling GSX with CX=0473h, CX=1473h, CX=2473h etc.). In the current GSX-86, only layers 0 and 1 have meaning. Layer 0 is for the current graphics driver, and layer 1 is for character I/O.
GDOS character I/O
GSX-86 device drivers are expected to do keyboard and screen I/O using layer 1 functions. The example in the GDOS source suggests calling INT 0E0h / CX=1573h (PassThrough to layer 1). The GSX 1.1 drivers do it a bit differently, by using INT 0E0h / CX=1673h to get the address of the driver definition, and extracting its entry point (ProcPtr).
The entry point is called with:
BH = function: 0 = Reset channel 1 = Get input status, return status in AX 2 = Wait for input, return input status in AX, input in DX 3 = Poll input, return input status in AX, input (if any) in DX 4 = Reserved 5 = Get output status, return status in AX 6 = Wait for output ready, then send character in DX. Returns status in AX. 7 = If output is ready, send character in DX. Returns status in AX. 8 = Line input, DS:DX as for BDOS function 0Ah. BL = channel number: 0 = CON: (screen / keyboard) 1 = AUX: (serial) 2 = LST: (printer) 3 = CON: (graphics) 4 = AUX: (sgraphics) 5 = LST: (graphics) 6,7 not assigned DX = data, if appropriate
Status returned in AX:
Bit 0: On receive: Parity error Bit 1: On receive: Overrun error Bit 2: On receive: Framing error Bit 13: On receive: End of file. On send: Channel has hardware queue, data queued and being sent. Bit 14: No connection to channel Bit 15: Ready