But was there anything other than video that was a source of hardware compatibility issues in the first wave? Or put another way: after video, what was the second most common source of compatibility issues for the semi-compatible DOS machines of the first wave?
On the software side I'd say sound may lead a tiny bit before any other hardware device - then again, fast serial did need direct hardware access on the PC, so serial might be top sound.
On the hardware side it was simply incompatible expansion slots, limiting the hardware available.
Beside that, I'd like to add some remarks:
The first wave relied on MS-DOS as the compatibility layer
And BIOS, as next to all compatible manufacturers did as well support all BIO calls as well.
The theory was that applications wouldn't hit the hardware directly, they would use DOS calls instead, so you could sell slightly incompatible hardware
Incompatible in itself wasn't a goal. Beside cases where it was about using existing x86 machines with DOS - which in facts predates the PC, as SCP sold its DOS to several x86 boards, including their own, before licencing it to Microsoft.
as long as you had an x86 CPU and a version of DOS tweaked for your machine.
Tweeked sound like patching and a somehow lesser quality. DOS is based on a clear separation between the DOS itself, and its hardware abstraction layer (IO.SYS).
DOS was neither originally written for the IBM-PC nor used first with it. All adaptations to any specific machine had to be done by the manufacturer (see an in part explanation here). Microsoft offered a set of examples and guidelines how to do it, the rest was up to each vendor.
Adapting DOS to hardware was a straight job and standard procedure.
The problem was that the DOS display code was weirdly slow. [...]
I knew in theory you were supposed to go via DOS, so I tried that. To this day I have no idea how they managed to make it that slow without deliberately putting delay loops in the code.
The negative performance impact depends in part thru the layered structure IBM used. While DOS calls all functions in IO.SYS as far calls, IBM's IO.SYS is for much of the time a warper around BIOS, invoking it via INT calls. Not exactly the shortest path. But ...
Second attempt via BIOS: faster but still much too slow. Third attempt straight to hardware: screen updated instantly.
This usually depends way more on your program code (and language runtime) than DOS (*2):
For example if the language hands over each and every character on its own to DOS instead of doing strings. Sending "Hello World\n\r" via Function 02h (Write Character) adds 13 times the overhead of calling DOS, IO.SYS and in turn BIOS compared to using a single Function 09h (Write String) call. Each invocation of DOS (INT) and call from DOS to IO.SYS (CALLF) with its returns alone are ~182 cycles (72+36+34+44) (*3,*4) plus whatever is needed in parameter conversion and alike. It's easy to see that outputting a whole screen takes close to 0.1 seconds (only these 4 instructions) overhead between using either function. And that's still before invoking BIOS, which in case of IBM takes another INT call, adding another 116 cycles.
And yes, many language runtimes acted exactly that way.
Related to this but entirely program dependant was how a screen was handled. Of course, this depended a lot on the application, but staying within text base, it's again a huge difference between brute force updating, like cranking out the whole screen or large parts thereof with each update, or only update fields that have changed. The later was already a huge difference when working with serial terminals and it was as well true for the PC.
One value I remember is that for one system we had the average screen size in 'uncompressed' mode was about 1.2 KiB per screen. This includes all characters attributes and field markers. By using switching this for only updating what has been changed, i.e. calculating a delta and send only changed values, after positioning. It wasn't easy, but the savings were astonishing. The average dropped from over 1200 bytes to less than 300. Our terminals were connected on 288 kBit lines, so comparable fast for 1980. Still, reducing it to less than a quarter changed it from being fast to instant.
Long story short, it's exactly the same situation with the IBM-PC (*5). It depends on the way the terminal output is organized, with an over all goal of minimizing output.
I did several programs on the original PC (I was young and needed the money) at first in BASIC but soon (1984?) in Turbo PASCAL. Applications were entirely text based and for an architecture firm, so lots of numbers. some screens looked more like spread-sheets. Still it was no problem to keep updates fast - by only positioning and writing what has changed. And yes, all was done using DOS output and terminal like sequences - not at least due the reason all development was done on an Apple II using CP/M. I simply didn't want to switch at the time (*6). I guess it can't get less compatible :)
So I shrugged and kept doing it that way. So did everyone else, which is what killed off the semi-compatible DOS machines.
Mind to add what region you live(d) in? (*7) As this does not match my (European) experience. A large number of machines with less than perfect (or no) hardware compatibility prevailed during the 1980s. Sirius, Apricot, Amstrad, Siemens, Philips, Thomson, Olivetti, and many others did their own designs, only partial or not compatible at all. Some, like the Olivetti M24 SP, a 10 MHz 8086 version coming as late as 1986 (like the Amstrad 1512) and sold until the early 1990s.
The second wave understood the need to be one hundred percent compatible at the hardware level.
Again, this may differ in region.
Also there's a large number of what I would call minimum compatible machines. In the US the Tandy model 1000 might be the best known one. They may have featured some basically compatible video modes to make software run, but for serious use they did need specific drivers. I guess we all remember the marking on game boxes - some even differentiating between Tandy 1000 models. This lasted well into the late 1990s.
[...] compatibility issues for the semi-compatible DOS machines of the first wave?
By now (aka 40 years later) we should have learned to stay away from single sided and twisted wording as it was used back then to promote a certain agenda. These machines were DOS (and usually BIOS) compatible computers. There is no semi-compatible when it was about these software interfaces. Only the hardware side had a continuum of compatibility, starting with processor speed and type, over video memory location (*8), all the way to fully incompatible.
*1 - Here BTW non compatible machines provided additional speed just by the way of calling. Like the AT&T PC6300 (a rebadged Olivetti M24) directly called the ROM routines, not going thru another INT but using a far call to ROM , thus speeding up every invocation (36 cycles vs. 72). Others, like the Siemens PC-D, were the BIOS was integrated within IO.SYS, shaved of even more overhead.
*2 - But of course the way it is implemented. See *1.
*3 - All cycle counting in 8088 reference. 80286 might be faster, but not much (47+23+22+28=120).
*4 - Or it can be, depending on the implementation in IO.SYS. In every case it saves at least 12 invocations of DOS and DOS calls to IO.SYS.
*5 - In fact, it's even close numerical with what's possible on a PC.
*6 - Which had the advantage of being able to program using my Apple II. Development cycle included programming and debugging on the Apple, moving it to a PC, that has only be bought to compile and write PC floppies to deliver.
*7 - Filling up ones RC.SE profil, including the location, does help in communicating, doesn't it?
*8 - I used a PC-D until the late 1990 as my main work machine. I simply loved the keyboard and it was fast enough for editing and compiling. It was in no way hardware compatible by using a 186 CPU and not a single interface at the same address, even more, using different chips for all I/O. In my memory much software did run right away, and a great number needed only small patches - like changing the segment address used for the video buffer. Of course all Windows and GEM software did run right away as supposed.