In DOS you had direct access to the hardware; so you grabbed some good source of information about the card you wanted to support, and got down to code your routines.
A book which was often cited as a good source was "Programmer's Guide to the Ega, Vga, and Super Vga Cards", by Richard F. Ferraro; I hadn't the luck to own it or read it, but it was fondly remembered by those who did.
Another invaluable source of information was Ralph Brown's Interrupt List; you can find a HTML conversion of the list here: http://www.delorie.com/djgpp/doc/rbinter/
The original was just made of (long) text files; and, if memory serves me correctly, there were some programs to navigate it more easily, at least in the later versions.
Another nice collection of infomation was contained in the "PC Game Programmer's Encyclopedia", or PC-GPE; a HTML conversion can be found here: http://qzx.com/pc-gpe/
You had at least three different ways to interact with a given piece of hardware; io ports, interrupts, and memory mapped registers. Graphic cards used all three of them.
The situation with audio cards was very similar.
Another thing to consider is that attached to the video card was an analog CRT monitor. The older/cheaper ones were only able to sync to a given set of vertical and horizontal rates; but the newer/best ones were basically able to sync to any signal in a given range. That means that with the right parameters written to the video card registers, you could create some custom (or weird) resolutions.
Games aimed for broad compatibility, so they rarely used weird ones, while in the demoscene it was quite common (and custom resolutions were the norm in arcade games too.)
But, for example, Mode X was very popular with games!
It was popularized by Michael Abrash on the pages of Dr. Dobb's Journal;
you got a 320x240 resolution, that, viewed on a 4:3 monitor, meant the pixels were square. So, for example, you could naively draw circles and they would look like circles; in 320x200 they were stretched, as the pixel aspect ratio was not 1:1, and you had to calculate and compensate for that while drawing.
It was a planar mode, so by setting a register you could decide which planes would receive a write in the memory mapped area. For example, for a quick fill operation you would set all planes, and a single byte write would affect four pixels (one for each plane). That also helped to address all the 256 KB of the VGA memory using only a 64 KB segment.
I am positive there was a little utility which let you explore the VGA registers, where you could put whatever values you fancied, and, when you applied your settings,
you could finally see if your monitor supported the resulting output. But my memory is too weak right now to remember the name or the author of this program.
Another common trick was to change a part of the color palette during the horizontal retrace; done correctly, you could have more than 256 colours on screen. There was not enough time to change the whole palette on each line, so you had to be creative.
(During vertical retraces instead there was enough time to change every colour, and it was done for example for fade in/fade out effects).
(The most popular palette trick was probably changing the background color during tape loading on 8 bit machines (C64 for example).)
One thing that is often overlooked, was that the VGA card was effectively a small three channel DAC; creative people found ways to use and abuse that as well.
To a similar effect, Tempest for Eliza used the radio waves emitted by the monitor to transmit a radio signal which could be listened to with a common AM radio.
Whoa! This was a nice trip on memory lane! :)