In part 1, we talked about the basics of how GEM VDI works and how that applies to the concept of VDI functions being passed from an application to a device driver.

This time around, we’ll talk about the printer driver kit that Atari sent out to selected developers. Printers were by far the most commonly supported device, and also had perhaps the greatest variety in technology.

Before we talk about the printer driver kit, let’s take a look at the state of technology for printers back in the mid-80’s.

The Printer Market At The Dawn Of Time (the mid 80’s)

Today, you can walk into a store and for $200 or so, maybe less, you can buy a fairly decent color laser printer that prints 10 pages a minute or more at resolutions upwards of 1200 dpi. To those of us who survived using computers in the mid’80’s, that is simply insane. You get so much more bang for the buck from printers these days that some people buy new printers instead of replacing toner cartridges.

In the mid-80’s, the printer market was much different than it is now. Aside from the differences in technology, printers were relatively much more expensive.  A basic 9-pin printer in 1985 would cost you $250 or $300. That’d be like $500-$600 today. You could literally buy a half-dozen cheap laser printers today for what it cost for a good 9-pin dot matrix printer back then. A good 24-pin printer would set you back $500 or more.

Laser printers, in 1985, were about $2500 for a basic Hewlett Packard LaserJet or similar model. Apple introduced the LaserWriter in March with a price tag of almost $7000. Fortunately, more and more manufacturers were entering the market, and prices were starting to drop. I paid about $1300 for my first laser printer in late ’86, and that was as cheap as they came back then. It was compatible with PCL 2 (printer control language version 2) which meant that most drivers for the HP LaserJet would work with it.

Today, the typical printer found in most people’s homes is an inkjet dot-matrix printer. That kind of printer wasn’t really a mainstream thing yet in 1985. The first truly popular model would be the HP DeskJet in 1988.

Graphics Printing Was SLOW!

Today, most printer output, other than speciality devices like receipt printers, is done using bitmapped graphics. The printer driver on your computer builds an image in the computer’s memory, and then when the page is complete, sends it to the printer. This gives the application and printer driver nearly complete control over every pixel that is printed.

However, in 1985, sending everything to the printer as one or more large bitmaps didn’t work so well, for a couple of reasons. First was the fact that sending data from the computer to the printer was fairly slow. Most printers connected to the computer via a Centronics-style parallel data port, which typically used the system’s CPU to handshake the transfer of data. Typical transfer speeds were rarely more than a couple of dozen kilobytes per second, even though the hardware was theoretically capable of much faster speeds.

Even though the data connection was fairly slow, the main bottleneck in most cases was the printer’s ability to receive the data and output it. Most printers had no more than a couple of kilobytes of buffer space to receive data, generally no more than about one pass of the print head when doing graphics. It was the speed of the print head moving back-and-forth across the page that was the ultimate bottleneck.

A popular add-on in those days was a print buffer, basically a little box filled with RAM that connected in-between the printer and the computer. This device would accept data from the computer as fast as the computer could send it, and store it in its internal RAM buffer. Then it would feed the data out the other end as fast as the printer could accept it. The print buffer could accept data from the computer more quickly than the printer could, and assuming it had enough RAM to hold the entire print job, it would free up the computer to do other things.

But even with a print buffer, if you had an impact dot-matrix printer and wanted to produce graphics output, you simply had to get used to it taking awhile to print. For those with bigger budgets, there were other options. Laser printer manufacturers started to make smarter printers that were capable of generating graphics in their own local memory buffers. This was generally done using what we call a Page Description Language, or PDL.

Page Description Languages

With a PDL, instead of sending a bitmap of a circle, you would send a series of commands that would tell the printer where on the page to draw it, what line thickness to use, how big it should be, the fill pattern for the interior, etc. This might only take a couple dozen or perhaps a few hundred bytes, rather than several hundred kilobytes.

One of the most capable and popular PDLs was PostScript, which was introduced to the world with the release of the Apple LaserWriter printer. PostScript was actually a programming language, so you could define a fairly complex bit of output and then use it as a subroutine over and over, varying things like the scale factor, rotation, and so forth. PostScript also popularized the concept of using outline scalable fonts.

The downside to Postscript or other PDLs was that the printer needed a beefy processor and lots of RAM, making the printer fairly expensive. Often more expensive than the computer you used to generate the page being printed.The Apple LaserWriter actually had a faster version of the Motorola 68020 processor and more memory than early models of the Mac computer.

The other downside was that even if you’re printing a couple of dozen pages everyday, the printer is actually sitting idle most of the time. Meaning that extra processing power and RAM isn’t really fully utilized.

Graphics Output On A Budget

Back in the 8-bit days and early PC days, most people didn’t have thousands of dollars to drop on a laser printer. If you had a basic 9-pin dot matrix printer, it had relatively primitive graphics and it was fairly slow to output a page using graphics mode. Most of the time you made a printout of something text-oriented, it used the printer’s built-in text capabilities. Basic printing modes were fast but low-quality, but more and more printers introduced a “letter quality” mode which was somewhat slower, but still much faster than doing graphics output.

However, the whole situation with printers was on the cusp of a paradigm shift. RAM was getting cheaper by the day. Computers were getting faster. The quality of graphics printing was improving. And, perhaps more than anything, the release of the Apple Macintosh computer in 1984 had whetted the market’s interest in the flexibility of bitmapped graphics output, and the subsequent release of Microsoft Windows and GEM with similar capabilities had added fuel to the fire.

Being able to combine text and graphics side by side was the new target, even for people with basic 9-pin dot matrix printers, and even though it was often orders of magnitude slower than basic text output, people were willing to wait. And for higher-quality output, they were willing to wait a bit longer.

Printer Drivers In The Wild West

Today, when you buy a printer, you get a driver for Windows, maybe one for Mac OS X. I would imagine Linux users recompile the kernel or something to get things going there.  (Kidding!)  And once you install that driver on your computer, that’s pretty much all you need to worry about. You tell an application to print, and it does.

By comparison, back when the ST first came out, printing was the wild wild west, and getting your printer to produce output could make you feel like you were in an old-fashioned gunfight. Before GUI-based operating systems became popular, every single program required its own printer driver.

And then we have the fact that there were about fourteen billion different ways of outputting graphics to a printer. Even within the product line of a single manufacturer, you’d find compatibility issues between devices that had more or less the same functionality as far as graphics output went. Even with the same printer, two different programs might have different ways of producing what appeared to be the same exact result.

Back in those days, most dot-matrix printer manufacturers followed the standards set by Epson. For example, when Star Micronics came out with their Gemini 10x 9-pin dot matrix printer, it used most of the same printer codes as the Epson FX and MX printers. Likewise with many other manufacturers. Overall, there was often as much as approximately 95% compatibility between one device and another.

The problem was, most of the efforts towards compatibility were oriented around text output, not graphics. That is, the same code would engage bold printing on most printers, but the code for “Advance the paper 1/144th inch” used for graphics printing might be different from one printer to the next.  This was further complicated by the fact that printers sometimes differed somewhat in capability. One printer might be able to advance the paper 1/144″ at a time, while another could do 1/216″.

The one good thing was that in most cases it was possible for users to create their own driver, or more accurately, a printer definition file. For most programs, this was nothing more than a text file containing a list of the printer command codes required by the program. In some cases it was a small binary file created by a separate utility program that let you enter the codes into a form on screen.

The Transition To OS-Based Printing

The main reason every DOS application (or Atari 8-bit program, or Commodore 64 program, etc.) had its own proprietary printing solution was, of course, the fact that the operating system did not offer any alternative. It facilitated the output of raw data to the printer, but otherwise provided no management of the printing process.

That started to change for desktop computer users in 1984, when Apple introduced the Macintosh. The Mac’s OS provided developers with the means to create printer output using the same Quickdraw library calls that they used to create screen output. And it could manage print jobs and take care of all the nitty-gritty details like what printer codes were required for specific printer functions. Furthermore, using that OS-based printing wasn’t simply an option. If you wanted to print, you had to go through the system. Sending data directly to a printer was a big no-no.

One significant issue with the whole transition to OS-based printing was the fact that printer drivers were significantly more complex. It generally wasn’t possible, or at least not practical, for users to create their own.

Apple addressed the potentially murky driver situation by simply not supporting third party printers. They had two output devices in those early years, the ImageWriter 9-pin dot-matrix printer, and then the LaserWriter. It would be a couple of years before third party printing solutions got any traction on Macintosh.

When Microsoft Windows came out a short time later, it addressed the question of printing in largely the same way as the Macintosh, except that it supported a variety of third-party printer devices. 

When the Atari ST came out, the situation regarding printing with GEM should have been theoretically similar to the Mac and Windows, except for two little things.

First was the minor tripping point that the part of GEM responsible for printing (GDOS) wasn’t included with the machine at first. What was included was BIOS and GEMDOS functions for outputting raw data to the printer. As a result, application programmers ended up using their own proprietary solutions.

Second was the fact that even after GDOS was released, there were only a few printer drivers included. And Atari didn’t seem to be in any big rush to get more out the door. As a result, application developers were slow to embrace GEM-based printing.

GDOS Printing On The Atari

As far as I know, the first commercial product to ship with GDOS support included was Easy Draw from Migraph at the start of 1986, about six months after the ST was released, and about two months after Atari starting shipping machines with the TOS operating system in ROM rather than being disk-loaded.

Migraph included pretty much exactly what Atari had given them as a redistributable setup: the GDOS.PRG file which installed the GEM VDI functionality missing from the ROM, the OUTPUT program for printing GEM metafiles, and a set of GEM device drivers and matching bitmapped fonts. The device drivers included a GEM Metafile driver and printer drivers for Epson FX 9-pin dot-matrix printers and Epson LQ 24-pin dot-matrix printers.

Compared to most other programs, this situation had a significant drawback. This was not Migraph’s fault in any way. It was a GEM issue, not an Easy-Draw issue. So what was the problem? Well, basically it comes down to device support. The GDOS printer drivers supplied by Atari simply didn’t work with a lot of printers. They targeted the most popular brand and models, but if you had something else, you had to take your chances regarding compatibility. This was a major problem for users, not to mention something of a surprise.

If there’s any aspect of GEM’s design or implementation where the blame for something wrong can be pointed at Atari rather than Digital Research, it’s got to be the poor selection of printer drivers.

With a word processor like First Word, if your printer wasn’t supported by a driver out of the box, chances were pretty good you’d be able to take your printer manual and figure out how to modify one of the existing drivers to work. Or, maybe you’d pass the ball to a more tech-savvy friend and they’d figure it out for you, but one way or the other, you probably weren’t stuck without a way to print. Not so with Easy-Draw, or any other program that relied on GDOS for output. GDOS printer drivers weren’t simply a collection of printer codes required for specific functions. If there was no driver for your printer, and chances of that were pretty good, you couldn’t print. Period.

The GDOS Printer Driver Kit

When I was at Neocept (aka “Neotron Engineering“) and our WordUp! v1.0 word processor shipped, we included basically the same GDOS redistributable files that Migraph had included with Easy-Draw, except for the OUTPUT program which we didn’t need because WordUp! did its own output directly to the printer device. It wasn’t long before we started getting a lot of requests from users who had printers that weren’t supported, or which were capable of better results with a more customized driver.

We asked Atari repeatedly for the information necessary to create our own drivers. I dunno if they simply eventually got tired of our incessant begging, or if they thought it was a way to get someone else to do the work of creating more drivers, but eventually we got a floppy disk in the mail with a hand-printed label that read “GDOS Printer Driver Kit” that had the source code and library files we needed.

There weren’t really a lot of files on that floppy disk, so I’ll go ahead and list some of them here:

  • FX80DEP.S
  • FX80DATA.S
  • LQ800DAT.S
  • LQ800DEP.S
  • DO.BAT

That might not be 100% accurate as I’m going from memory, but it’s close enough. I think there might have “DEP” and “DATA” files for the Atari SMM804 printer as well, but it’s possible those were added later.

The “*DEP” files were the device-dependent code for a specific device.  Basically there was a version for 9-pin printers and one for 24-pin printers.  There were some constants unique to individual printers that should have been elsewhere.

The “*DATA” files were the related data, things like printer codes and resolution-based constants.

INDEP.LIB” was the linkable library for what amounted to a GEM VDI bitmap driver.

The STYLES.C file contained definitions for the basic pre-defined VDI line styles and fill styles.

The DO.BAT file was a batch file that did the build.

Figuring It Out

There were no instructions or documentation of any kind. That may have been why Atari was originally reluctant to send anything out. It took a little experimenting but eventually I figured out what was what. The idea here was that the bulk of the code, the routines that actually created a page from the VDI commands sent to the driver, was in the INDEP.LIB library. The actual output routine that would take the resulting bitmap and send it to the printer was in the *DEP file. By altering that routine and placing the other information specific to an individual printer into the DEP and DATA files, you customized the library’s operation as needed for a specific printer.

The ****DATA file would contain things like the device resolution, the printer codes required to output graphics data, and so forth. This included the various bits of information returned by the VDI’s Open Workstation or Extended Inquire functions.

The first drivers I created were relatively simple variations on the existing drivers, but fortunately that’s mainly what was needed. There were a ton of 9-pin dot-matrix printers in those days, and while many of them worked fine with the FX80 driver, some were ever so slightly different. Like literally changing one or two printer codes would make it work. The situation was a little better with the 24-pin printers but again there were a few that needed some changes.

The first significant change we made was probably when I created a 360 DPI driver for the NEC P-series 24-pin printers. These were compatible with the Epson printers at 180 DPI, but offered a higher-resolution mode that the Epson did not. I’ll admit I had a personal stake here, as I’d bought a nice wide-carriage NEC P7 printer that I wanted to use with the Atari. That thing was slower than crap but oh, gosh was the output good looking. At the time, for a dot-matrix impact printer, that is.

One thing that was confusing at first was that the startup code for the drivers was actually contained in the library. The code in the ****DEP.S files was called as subroutines from the v_opnwk and v_updwk functions.

Anatomy Of A GDOS Printer Driver, Circa 1986

The INDEP.LIB library (or COLOR.LIB for color devices) contained the vast bulk of the driver code. It contained all of the functions necessary to handle all of the VDI functions supported by the device. It would spool VDI commands until the v_updwk function was called. That was the call which triggered the actual output. At that point, it would create a GEM standard raster format bitmap and render all of the VDI commands which had been spooled up since the open workstation, or previous update workstation.

In order to conserve memory, the printer drivers were designed to output the page in slices. A “slice” was basically a subsection of the overall page that extended the entire width, but only a fraction of the height. The minimum slice size was typically set to whatever number of lines of graphics data you could send to the printer at once. For example, with a 9-pin printer, the minimum “slice height” would be 8 scanlines tall. If the horizontal width of the page was 960 pixels (120 dots per inch), then the minimum slice size would be 960 pixels across by 8 pixels tall. The maximum slice height could be the entire page height, if enough memory was available to the driver.

The driver would allocate a buffer for a slice, then render all of the VDI commands with the clipping set to the rectangle represent by that slice.  Then it would call the PRT_OUT function.  This was a bit of code in the DEP.S file that would output whatever was in the slice buffer to the printer, using whatever printer codes and other information were defined by the DATA.S file. After a slice was output to the printer, the library would clear the buffer and repeat the whole process for the next slice down the page.  For example, the first slice might output scanlines 0-95, then the next slice would do scanlines 96-191, and so forth until it had worked its way all the way down to the bottom of the page.

Once it got to the bottom of the last slice, the code in DEP.S would send a form feed code to the printer to advance the paper to the start of the next page.

This all may sound inefficient, since it had to render all of the VDI commands for the page over and over again, but the bottleneck here was sending the data to the printer so that didn’t really matter.

A Semi-Universal Printer Driver

Something I always kind of wanted to do, but never got around to, was creating a reasonably universal GDOS printer driver that stored printer codes and other parameters in an external configuration file that could be edited by the user. Or, perhaps, stored within the driver but with a utility program that could edit the data.

You see, the main part of the library didn’t have any clue if the printer was 9-pin, 24-pin, or whatever. So there’s no reason it shouldn’t have been possible to create an output routine that would output to any kind of printer.

In hindsight, that probably should have been the goal as soon as I had a good handle on how the driver code worked.

Next Time

Next time we’ll jump right into creating our driver shell.

Related Articles

I recently got an interesting email from an Atari fan named Wong CK. Wong has been programming the Atari ST computers as a hobby, and was interested in creating a GEM printer driver that would create PDF files. He had read some of my articles here and after not finding a lot of information elsewhere online, was hoping that I might be able to provide some useful information and advice.

Here’s a portion of Wong’s original message:

One of the software that I wanted to do is a GDOS printer driver to create a PDF file. On the web I found some PDF source code and so my idea was just to map the PDF library code to each VDI functions. I also researched on how to make a Atari GDOS printer driver but there was very little information. I found the now public released GEM 3 GDOS printer drivers as well as CPM GSX printer driver source codes, but I have not figured out what it needs to be done, confused futher by the assembly codes and the x86 codes as I program in C language. This is the stumbling block and I have been stuck at this stage for nearly 2 year plus. Even the guys over at do not know (or they are not telling).

I thought that a PDF driver was an interesting idea, and Wong’s request kind of overlapped a long- unresolved ambition of my own regarding GEM  drivers.  I replied to Wong, telling him that…

Well, actually, that’s sort of the point of the article so let’s just jump in.

I’m expecting this to be a four-part series, as outlined below. The good news is that I’ve already got parts 2 and 3 mostly done, so there hopefully shouldn’t be a huge delay between installments.

  • Part 1 – Overview of How GEM Works & How Device Drivers Are Called
  • Part 2 – The GDOS Printer Driver Kit
  • Part 3 – Creating A Basic GEM Device Driver Shell
  • Part 4 – Sample Device Driver

Beyond part 3 I don’t have it completely mapped out yet, so that could get expanded a bit when the time comes.

Back To The Beginning

We’re going to start by going back to the beginning and talking about some of the basic fundamentals about GEM VDI.  First, let’s recognize that there are two targets for an application’s VDI requests, VDI itself, and the device driver (for whatever device is involved). This idea ties into the original GEM VDI documentation from Digital Research.  On page 1-2, you’ll find this tidbit (sic):

GEM VDI is composed of two components:

* Graphics Device Operating System (GDOS)
* device drivers and face files

When you open a VDI workstation, you’re asking GDOS to do something.  It has to figure out what device driver is required for the request. For some devices like the screen, the driver may be in ROM, for others it might have to load it from disk.  Then it has to wait for the result of the “open workstation” request so it knows if it should unload the driver or not.

On the other hand, when you draw a circle, you’re not really asking VDI to do it. Really, you’re asking the device driver that’s in charge of the specified workstation to do it. In the latter case, VDI is responsible for routing the request to the correct device driver, but doesn’t otherwise involve itself in the drawing of the circle, because VDI knows nothing about what’s required to draw a circle on a particular device. That’s what the device driver is for.

Atari ST users have typically referred to GDOS as though it was some sort of bolted-on extra piece of GEM VDI that you didn’t need unless you wanted to use loadable fonts or device drivers for things like printers. There’s a grain of truth in there, but it’s also somewhat misleading, because what Atari users call “GDOS” actually is GEM VDI. The term “GDOS” is supposed to refer to everything that’s not a font or device driver, but that idea got corrupted on the Atari side of things for some reason. We used to say that the TOS ROM didn’t include GDOS. Maybe it would have been more accurate to say it didn’t include VDI.

The majority of the code in the Atari’s TOS ROM that everybody has traditionally referred to as “the VDI” is actually a device driver for the screen.  But the “GDOS” aka the rest of VDI is missing. The TOS ROM includes just a tiny piece of code, a mini-VDI you might call it, that catches the GEM system trap and passes through VDI commands to the screen driver.  It doesn’t know anything about other devices or drivers, doesn’t know how to load fonts, or do anything else. In fact, the assembly language source file for it is under 150 lines long.

How Does A VDI Request Get From Application To Driver?

GEM uses a “parameter block” to pass information back and forth between the application and the VDI.  This is a table of pointers to five arrays which contain the input parameters, and which receive the output parameters.  They are:


Each of these arrays consists of 16-bit values.  The CONTROL array is used for both input and output.  On input, it tells GEM what function is being requested, and how much information is contained in the INTIN and PTSIN arrays. When the function is done, it tells the application how much information was returned in the INTOUT and PTSOUT arrays.

The “PTS*” arrays are used to pass pixel coordinate values. These are always done in pairs. That is, there’s an x-axis value and a y-axis value. The CONTROL array specifies how many coordinate pairs are passed back and forth.

The “INT*” arrays are used to pass integer values back and forth.  The CONTROL array specifies how many values are in INTIN or INTOUT.

To call VDI, an application puts the required input parameters into the CONTROL, INTIN, and PTSIN arrays, then it loads the address of the GEM parameter block into register d1 of the 680×0 CPU, and the magic number $73 into register d0.  Finally, it calls trap #2.

Wondering what “trap #2” means? For you new kids who haven’t ever written assembly code, or accessed things at that low-level, most microprocessors since the 16-bit days have implemented the concept of a system trap.  This is a special processor instruction that causes the processor to jump to whatever code is pointed to by a specific, pre-defined pointer in memory.  It’s sort of an “interrupt on demand” and it allows system programmers to install bits of code that can be called without the calling program known where it resides in memory, as would otherwise need to be the case.

Here’s a bit of assembly code that demonstrates the definition of the parameter arrays, the parameter block, and the actual system trap call.  This assumes the arrays have been loaded with the correct parameters:

    .bss        ; Block storage segment, aka uninitialized data space
_control:       ; Adding underscore makes symbol accessible from C
    ds.w 20     ; Reserve space for 20 elements
    ds.w 128    ; 128 elements
    ds.w 128
    ds.w 128
    ds.w 128

    .data        ; initialized data
    dc.l _control
    dc.l _intin
    dc.l _ptsin
    dc.l _intout
    dc.l _ptsout

    move.l #_VDIParams,d1
    move.l #$73,d0
    trap   #2

That’s likely the first 68000 assembly code I’ve written in probably at least 15 years, maybe more… excuse me while I catch my breath…

The high-level language binding for a call like vsf_style might look like this:

WORD vsf_style( WORD wsHandle, WORD newStyle )
    control[0] = 24;       /* VDI opcode for this function */
    control[3] = 1;        /* # of values in INTIN array */
    control[6] = wsHandle; /* Workstation handle */
    intin[0] = newStyle;   /* Requested fill style */
    return intout[0];      /* return style actually set */

These are just examples, of course. Most of these details are generally managed by the function bindings that came with your C compiler (or whatever other language) so that most programmers creating GEM applications don’t have to worry about it, but it’s important for those of us who are doing system stuff like creating device drivers from scratch. We need to make sure the underlying concept is clear here because it ties into the big secret.

What’s the big secret?

The Big Secret

Here’s the big secret of GEM VDI. A secret that wasn’t really a secret, but which nevertheless it seems very few people properly understood, going by the questions that people still ask to this day.

A device driver’s point of entry, where it first starts executing code, is what sits on the other side of the trap #2 call.  Register d0 contains the VDI magic number, and register d1 contains a pointer to the parameter block.  So at that point it’s up to the driver to take that information and do something meaningful with it.

It’s that simple.

How GEM Calls The Device Driver

Oh, technically, the driver isn’t EXACTLY on the other side. The system trap call doesn’t actually point directly into the driver. That would be stupid. But from the driver’s point of view, it looks pretty much like that.

When the system trap is made, VDI/GDOS will first verify that it’s an API call by checking for the magic number in register d0.  If the magic number is found, VDI grabs the address of the parameter block from register d1.  The first entry is a pointer to the CONTROL array, where it grabs the workstation handle and the opcode of the function being requested.

Next, it looks at the function opcode to figure out if the request should be routed to the driver, or handled by GDOS.  Something like v_opnwk (Open Workstation) would be handled by GDOS, while v_pline (Draw Poly Line) would be handled by the driver.

For functions that need to be handled to the driver, GDOS first has to figure out which workstation and driver should receive the command.  GDOS maintains a table of information for each open workstation, including the entry point for the driver. It searches that table until it finds a matching workstation handle.  Then it simply grabs the driver’s entry point, and jumps into the driver.  Something like this:

;; Note that I'm not including important things like
;; saving and restoring registers in this sample code

    cmp.w  #$73,d0      ; d0 have magic number?
    beq.s  .VDIcall     ; no, so not a VDI call
    rte                 ; return from system trap

    move.l d1,a0        ; Get address of parameter block
    move.l (a0),a0      ; Get first entry in parameter block
    move.w (a0),d2      ; Get control[0] into register d2

;; At this point, we need to determine if the requested 
;; operation is a VDI/GDOS thing like opening a workstation 
;; or a device driver thing like drawing something.
;; That's too much code to include here, so just assume 
;; this comment does that and then jumps to the label 
;; below if it's a driver thing, and exits otherwise.

    move.w 12(a0),d2      ; Get workstation handle from 
                          ; control[6] into register d2

;; OK, now VDI would search its internal data to find 
;; the workstation and device driver associated with the
;; workstation handle passed in. Again, too much code, so
;; let's just assume that we found the information and that
;; the driver entry point is now contained in register a0.

    jsr    (a0)           ; Jump to driver entry point
    rte                   ; Return back to application

Once it gets control, the driver is expected to do whatever is called for by the specific function opcode, and return whatever data is appropriate.

The big secret here is that VDI doesn’t really have any big secrets. The VDI manual pretty much tells you exactly what GDOS does and what’s expected of a drivers. It was actually pretty mundane stuff when you get down to it.

In The Next Installment

We’ll discuss the GDOS Printer Driver Kit that Atari sent out to some developers.  We’ll go over how one used it to create new drivers and why it’s not really that suitable as a general-purpose driver kit.

Related Articles

The previous entries in this series have dealt almost exclusively with issues regarding the GEM VDI graphics library, but finally we get to GEM AES.

AES stands for Application Environment Services, which mostly translates into “handling the user interface stuff“.  There are a few other things in there, but mostly it’s responsible for drawing windows and other user interface objects on screen and for managing the user’s interaction with them.

Like VDI, GEM’s graphics library, AES has its share of design issues.  Overall I think it’s probably better off in that regard than VDI, but there are a few particular areas where the design had significant flaws. In contrast to VDI, however, it’s not always clear what the better solution might have been, or how big a difference it would have made.

This article will focus on the AES event processing model and how it tied into GEM’s support for cooperative multitasking.

Also, before we dive in, I’d like to say for the record that we’re talking about vanilla GEM here, version one point oh. I don’t care if somebody added more features to some post-Atari version of GEM in 1997 or something like that. I generally don’t even talk about the stuff Atari added for the TT030 or Falcon030.

GUI Event Processing

Since the earliest examples of the genre, Graphic User Interface systems all have a certain similarity in their basic structure.  While they may have significant differences from one to the next in regards to what sort of controls or widgets they support, in terms of how they work, they’re all fundamentally wrapped around the idea of event processing. Some event occurs, and the application responds. The event in question is usually some action the user has performed which interacts with something on screen, like moving the mouse, clicking a mouse button, or pressing a key on the keyboard. When something like happens, the GUI manager captures the event and then it lets the application know about it so that the appropriate response can take place.

Let’s compare the event processing methods for Windows and GEM.  Not that Windows is necessarily the best thing around, but it’s been around for about as long as GEM, whereas other examples like the current version of Apple’s OS X or something like Java Swing, are much newer.

If you’re not familiar with programming either system, some things may not make sense at first but we’ll try to fill in the details as we go.

Also, be aware that we’re talking about the basic, original, low-level structure of Windows event processing.  If your experience with Windows programming is mainly using class libraries and frameworks like MFC, Windows Forms, or WPF, this is how they get things done internally.

A Look At Windows Event Processing

Generally, when a Windows applications starts, it performs whatever basic initialization is needed, registers at least one window class with the system, then opens a window.

What Is A Window Class?

A “window class” in the context of talking about low-level Windows programming isn’t a class in an object-oriented programming language like C++ or C#. It’s basically a definition of attributes that are applied to a particular user interface element (the “window”). These attributes include things like style flags that define what the borders of the element should look like, or it should be borderless, etc.

Each of the standard Windows controls like buttons, edit boxes, etc., has its own window class associated with it. You can also create a new type of element that is based on another type, which is useful when you want to add a feature without having to rewrite the entire thing.

One of the most important bits of a Window Class definition is a pointer to the WNDPROC function, which is the function that will be called any time Windows has information about an event that might be of interest to that particular UI element. This is what we’re referring to as the “event handler” function.

Some specialized types of program may not open a window at startup, like screen savers, or programs that hook into the system tray, or something like a patch installer that doesn’t need to interact with the user.  However, we’re concentrating on the more typical example of a standard program that is going to be interacting with the user using the graphic user interface.

After opening the main window, it likely does something like specify a menu bar, or a tool bar, and whatever other configuration might be required. Eventually, the startup code exits and control returns to Windows. Now the application sits idle until an event occurs. This could be a keypress, mouse click, window redraw message, or any number of other things.

Once an event occurs, Windows looks at what UI element was involved, figures out the window class that element belongs to, then it calls the event handler routine which was specified when the window class was registered.  One of the parameters would be a window handle, a unique ID which would indicate which specific instance of that UI element was referenced.

If the window class for that element was defined as part of your program, then it would regain control at the entry point to the event handler function. If the window class was created elsewhere and only referenced by your program, then that code would be called instead, and your program wouldn’t even know an event had taken place.

An event handler can decline to process an event and not mark it as handled. When that happens, Windows finds the original element’s parent object and gives it a chance to process the event.  The event bubbles up the tree of UI elements until it gets processed, or it runs into the top most object.

If some of this seems unfamiliar, keep in mind that modern application frameworks like Windows Forms or WPF do a lot of work to simplify the event handling process so that your application only has to worry about creating functions to handle the specific events it thinks are important.

Once the handler is done processing the event, it exits, returning control back to Windows. Now the program once again sits idle until Windows has another event to be handled.  In the meantime, Windows is passing control to other applications as needed to handle their events. The result is that everything generally looks nice and responsive and coexisting peacefully.

For now, anyway. But we’ll come back to that.

And GEM’s Version Of That Is?

When a GEM application starts, it normally starts by loading its primary resource file, which specifies definitions for menu bars, dialog boxes, icons, etc.  This information can also be embedded into the main executable, which is how Windows usually does it, but this is uncommon under GEM.

Next, it probably turns on the menu bar using one of the resource trees that was in the loaded resource file.  Now it might open a window, or maybe not.  GEM puts the menu bar at the top of the overall screen, not inside a window, so it’s not necessary for GEM applications to open a window right off the bat.

Alternately, if the application is loaded as a GEM desk accessory, then instead of activating a menu bar, it registers a menu item in the system menu, along with the other loaded desk accessories.

Just like with our Windows example, this represents the typical mode of operation for a typical program that interacts with the user using the graphic user interface.  And as with Windows, specialized types of programs can be different,

Finally, after whatever initialization is performed, GEM applications go into their primary event loop.  This normally starts with the program calling the AES evnt_multi() function.  This asks GEM to look in the event queue for any events that require processing, but it also passes control back to GEM.

Note: GEM AES actually has a variety of functions for requesting specific types of event, but to simplify things we’re going to refer only to evnt_multi(), which can request multiple types of event at the same time.

When evnt_multi() is called and AES gets control back from the application, it looks in the system’s event queue for the oldest unprocessed event it can find. The event may belong to the same application that was just in control, or it could be a different application. The original version of GEM could have a main application and up to six desk accessories, giving us up to seven programs running together in cooperative multitasking harmony.  In theory, that is.

Once AES figures out which application the oldest event in the queue belongs to, it looks at what other events might be waiting in the queue for the same application.  In some cases, multiple events can be handled at once.  Once it figures out all the events it can pass to the application, it returns back from that application’s call to evnt_multi() so the event can be processed.

If there are multiple events waiting in the queue for the same application, then evnt_multi() will attempt to combine them to a certain degree so that as many events can be processed at once as possible.  However, there are limits to what events can be combined.  For example, only one message event at a time can be processed because of the way the information is returned to the application.

At this point, the application takes whatever actions are required to process the events, then ultimately it calls evnt_multi() again so that the whole process can happen all over again.

Sounds Pretty Similar, But Is It?

At first glance, Windows and GEM sound pretty similar in their approach to event processing, but note that they’re actually approaching the problem from opposite sides.

Windows remains in control until an event needs processing, then it temporarily gives control to the application while that is done. This means that idle time always belongs to Windows.

With GEM, it’s the application that stays in control until it decides to give the system a chance to process events.  Although it looks like it’s only asking GEM for its own events, it’s actually giving GEM the opportunity to let other applications take control for awhile, if they have pending events to be processed. So, idle time can belong to either GEM or the application, depending on how the application decides to poll for events.

Another big difference is that Windows processes one event at a time, while GEM can process several at once. At first, you might think that means GEM is more efficient, but the problem is that this means events can be processed in a different order than they occurred.  The information returned back from evnt_multi() tells the application which events occurred, but it doesn’t say anything about which one happened first.  What if you get a message event for a window redraw and also a mouse click event at the same time?  Maybe in some cases it makes no difference, but in other cases maybe it does.

The other issue is that Windows offers a much finer granularity over the processing of events.  For example, when GEM returns a mouse-click event to an application, it’s up to the application to figure out if that click happened inside a button, or an editable field, etc., and respond accordingly.  All events happen at the application level and it’s up to the application to figure out how they should be trickled down to individual UI elements.

Except… windows.  Message events for windows are different in that they specify the handle for the specific window.  So when you get a message saying a window was minimized, for example, so you know which one.  However, this only applies to the message events about things happening with the window frame.  Except for a window redraw message, events that occur within the window’s client area aren’t really tied to the window in any way.

For example, if you get a mouse click event, AES tells the application the screen coordinates where the mouse was at when it happened.  It doesn’t tell you which window, if any, or anything else.  Just “Here’s a mouse click.  Deal with it.”  It’s then up to the application to figure out if that was inside a window, and if so, which one.

With Windows, the application only owns the events that occur within the windows, not in the desktop area.  And everything the application draws ultimately tracks back to a UI element with a window class and event handler.  There’s never any ambiguity about what was supposed to receive the event.

For example, if a button gets clicked, it’s sent a mouse-click event message. The button’s event handler is called, where it has the option of either processing the event, or ignoring it.  If it ignores the event, then Windows looks for whatever object contains the button and passes the event to it. This process repeats until one of the event handlers claims responsibility.  Thus, the event can be handled at the lowest-level, or the highest level.

This also means that with Windows, you have the ability to create custom controls that are completely self-contained and responsible for their own behavior, without needing the main application to do stuff for them.  For example, you could have a button which changes border color when the mouse enters or exits it.  In GEM this would be impossible without the application figuring out that the mouse moved over the button, or out of it, and then calling some function in the button code. It can be done but it’s a lot more work.

Even when you’re not using customized controls, with GEM if you wanted to have a non-modal window with UI widgets like buttons or editable fields in it, your event handling becomes a lot more complicated because of the need to manage them.  With Windows, it’s much more straightforward.

Another difference is that in Windows, all UI events are treated as messages. In GEM, we have message events, mostly for window related things, but we also have mouse click events, mouse movement events, keyboard events, and more. Maybe there’s some advantages to this setup I’m not seeing, but mainly it seems to just make GEM event processing more complicated.

To summarize, with Windows, events are something that happen with individual objects that can be combined to make more complex objects. With GEM, events happen at the application level, and it’s up to the application to figure out how to process them in a way that makes sense for whatever happens to be on screen at the moment.

The First AES Design Flaw

So, the first major design flaw of GEM AES is that event processing doesn’t have enough granularity built into the basic setup.  Developers have to write a fair amount of code to make up the difference, and some things like self-contained custom controls just aren’t really possible.

The Problem With Cooperative Multitasking Is…

The event processing model of GEM’s GUI system is intimately tied in with the concept of cooperative multitasking.  We already discussed how GEM gives applications a chance to respond to user input and events, but we kind of skipped past something that can be a major concern.

What happens when your application needs to do some sort of processing that takes a lot longer than your basic event handling?

In a modern application, an application is expected to create a worker thread when some sort of heavy lifting is required.  The event handler would create the worker thread and start it, and return control to Windows.  The worker thread could then run in the background.  Your user interface design might include a progress bar, and certain function might need to be disabled while the background task was running, but in general your user interface, and that of other programs, would remain responsive to user input.

However, the technique of using a worker thread was not available in the heyday of cooperative multitasking.  In those days, under systems such as GEM/Atari or Windows 3.1, everything, except interrupt handlers, ran in a single thread.  Worker threads simply weren’t an option in those days.

What would normally end up happening is that applications would simply do whatever heavy lifting they needed to do, in direct response to the user input that requested it, forgetting about event processing in the meantime. Of course, while they were doing whatever processing they needed to do, other applications weren’t getting a chance to do anything. The result was often that the system looked like things had locked up, at least momentarily.

This wasn’t a big issue if the processing only took a few seconds or less, but for bigger jobs where the system was unresponsive for longer periods, users might think the system had crashed.

Avoiding The Lockup

Windows has a function called PeekMessage that lets a program query the system for any pending messages. It’s probably the closest equivalent to the GEM evnt_multi function you’ll find in Windows, and works in such the same way. It retrieves the top message from the queue and returns it to the application, which would call another function called DispatchMessage that pushed the message to the appropriate event handler.

By calling these functions periodically when doing some sort of big processing task, the program and system stay responsive.

Note: The initial version of this article said there was no way to do this in Windows. I had mistakenly thought the functions mentioned above had not been available in early versions of Windows. I’ve updated this section of the article to correct the mistake.

With GEM you could do something similar. While doing your long processing task, you could periodically call a subroutine that would call evnt_multi() with timer events enabled, and a timer period of zero. This would give other applications a chance to do their own processing, but the timer event would ensure the system returned control to your application as soon as possible, even if no other events were pending. Once the evnt_multi() call returned, the subroutine could process any pending events for your own application and then return back to your long processing task.

Most likely, if you did this, your application would have many functions disabled while the long processing task was going on, but things like screen redraws would get processed in a timely fashion. You had to make sure that processing events didn’t step on what you were otherwise doing, or vice versa, but with a little care you could get it to work.

The main problem with this technique is that if a program wants to perform its processing in the shortest time possible, it doesn’t want to waste time passing off control to the system when there aren’t any events to worry about. But GEM is missing the equivalent of the PeekMessage function from Windows, so an application really has no way to know when there are events that need to be processed.

The Second AES Design Flaw

It’s a shame that GEM AES didn’t offer a better means for an application to perform long processing tasks without locking up the UI. Sprinkling your code with calls to an event processing subroutine did basically work, but it wasn’t ideal and it certainly wasn’t elegant. Every time I suggested the idea to a developer, they always kind of looked at me like they were thinking, “Seriously?”

There are probably better ideas I haven’t thought of, but what GEM really needed was an equivalent to the Windows PeekMessage function, let’s call it evnt_peek(), which would have simply looked at the event queue and returned a flag indicating if events were pending. As long as no events were pending, the program could continue processing without worrying about it looking like the system locked up.

Next Time

Next time around, we’ll talk about how GEM AES was inconsistent about how it defined and managed UI elements, and the repercussions of that.

« Previous Entries