• Please review our updated Terms and Rules here

CGA 160x100x16 bidirectional scrolling

resman

Veteran Member
Joined
Jan 1, 2014
Messages
597
Location
Lake Tahoe
This is a feasibility study in bidirectional hardware scrolling using the hacked lores/alpha-graphics mode of the CGA. I'm convinced this is the best graphics mode for games on a low-end PC with CGA graphics. But doing a fast full screen update is out-of-reach when using a lowly 8088. Of course 8088MPH! does amazing things with this mode, so I wanted to try my hand at implementing vertical and horizontal scrolling in anticipation of something fun. I thought I would share the progress I've made in implementing a 2D tile based scroller. This demo first scrolls around the map using software to fill the entire screen every frame. Press a key and it will switch to using hardware scrolling. Again, press a key and it will exit.

Things to note:
  • Code is written for clarity, not performance (and maybe the clarity isn't so good, either)
  • There are anomalies along the edges. Yes, I know. See above.
  • No attempt to avoid snow on IBM CGA. See above.
  • Don't laugh at my poor tileset. At least I made up a few different tiles!
  • Compile the code yourself using MSC 5.1: 'cl /Ox tiler.c'
I've tried it under DOSBox (set CGA as video card) and on my Compaq Deskpro using composite output. No idea if it will run on other compatibles like PCjr or Tandy.

Let me know what you think...
 

Attachments

  • tiler.zip
    5.1 KB · Views: 14
Excellent start! I can tell you that it is possible to eliminate the edge anomalies and avoid snow at the same time, though it isn't easy. I have made a similar proof of concept in the past which does so (and which I'd eventually like to turn into a full game).
 
I figure I’ll pre-render the edges into a buffer which can be quickly updated during vertical refresh. But other sprite assets wil be more interesting to avoid snow. My Compaq CGA doesn’t have snow issues so I may need help from others to see if I got it right
 
Gotta wonder if you could get a mostly 320x200 image (with some artifacts and color clash)
By using more of the available text character options ?

Would have to map useful characters for filling the bottom / top (2lines) only and the center

Would need to look at the top row of all characters to see what options are available.
 

Attachments

  • 0828AD8E-1EE2-44F0-8323-BE841E98FD7E.png
    0828AD8E-1EE2-44F0-8323-BE841E98FD7E.png
    2.5 KB · Views: 8
Last edited:
I was able to validate pre-rendering the edges and then writing them out during vertical retrace will clean up the edge anomalies. At least on my Deskpro in "fast" mode; it still can't get the top scan line fully rendered in "compatibility" mode. However, this is still using unoptimized C code for everything, so I feel confident that an optimized assembly version will fill in the top edge even on a stock IBM PC.

I discovered something during this last exercise: DOSBox's CGA emulation doesn't quite match what my hardware does when accessing past the 16K CGA buffer. With my experiments and what I've read online indicates that the CGA maps the full 32K memory area from B800:0000 to B800:7FFF but simply mirrors the upper 16K into the lower 16K. The CGA video scan out also wraps at 16K. My code relies on this wrap-around when drawing past the end of the first 16K. Interestingly, using DOSBox would show anomalous scan line data every once in awhile. I figured it was my new fast horizontal edge filling routine that masked the original address calculation but skipped it for the two scan lines filled. This should work fine given that there is a full 16K of mirrored address space and I was only potentially going an additional 320-2 bytes past the first 16K. It works fine on my actual hardware, but I left the additional masking in the code so it would work with DOSBox. The only reason I could see that DOSBox would behave this way is it may be emulating a 32K CGA (some could implement a 640x400 mode) or the emulation code simply doesn't mirror the full second 16K.

Thanks for everyone's replies. I'm glad to see there is still some interest in this mode.

Frankel, is this a full port or did you just adjust the video initialization and screen resolution? Is it available anywhere to play with?

reenigne, thanks for the encouragement. I figured one of you guys had already investigated this scrolling mode.

carlos12, yes, Paku Paku is proof positive of this mode for gaming. Working around the snow issues isn't difficult, per se, but getting something working at 60fps without snow is probably going to be the biggest hurdle.

And may635703, that would indeed turn it to 11. One thing I like about this mode is that is should look similar regardless of composite output or RGB output. As I only have composite color available on my Deskpro but use DOSBox when I'm feeling lazy (or not relegated to the man-cave where all the old computers are), I like the common output. Someone more diligent than I will have to figure out clever character hacks.

I'll move this over to GitHub when I break out the functionality into assembly routines and more than one monolithic file. Until then, attached is the current code and executable.
 

Attachments

  • tiler2.zip
    5.7 KB · Views: 4
Last edited:
Unwilling to leave well enough alone, here is a version using optimized assembly routines for setting the CRTC start address and filling the edges. It uses the trick of changing the border color to get visual feedback of the rendering duration. My 160x100 set mode code sets the border color to yellow to fake a color burst signal, so this code sets it to black while rendering the edges. On my Deskpro in "fast" mode you don't even see the black border. In "compatibility" mode it wraps up a few scan lines before the active period. Since I'm running a V30 in this machine, having a bit of overhead should compensate for an 8088 at 4.77 MHz. At least I hope so. Interested in feedback from anyone with an IBM PC+CGA. Source and executable included in ZIP.

Edit:

Well crap, I forgot about the DOSBox problems. Had to add in a few more masking operations on the destination video pointer. The timing is now on the hairy edge for my Deskpro in "compatibility" mode. Hope the IBM PC doesn't snow. The ZIP file is updated.
 

Attachments

  • tiler2b.zip
    6.5 KB · Views: 7
Last edited:
Yes, the CGA buffer wraps around at 16K. I remember DOSBox having problems with this, but AFAIK this was fixed... just not in the official release (yet). A more up-to-date svn build should probably have the fix, and maybe something like DOSBox-X as well.

BTW, there's a better way to get a passable color burst signal in +HRES modes than setting the border color to yellow - increase the horizontal sync width (CRTC R3) to 0xF. You can see that in the 8088MPH calibration screen; I also have some relevant code available (https://github.com/viler-int10h/TVCGAFix).
 
Yes, the CGA buffer wraps around at 16K. I remember DOSBox having problems with this, but AFAIK this was fixed... just not in the official release (yet). A more up-to-date svn build should probably have the fix, and maybe something like DOSBox-X as well.

BTW, there's a better way to get a passable color burst signal in +HRES modes than setting the border color to yellow - increase the horizontal sync width (CRTC R3) to 0xF. You can see that in the 8088MPH calibration screen; I also have some relevant code available (https://github.com/viler-int10h/TVCGAFix).
Thanks for the heads-up about DOSBox. I'll check for updated versions for MacOS. As for the Sync duration, I do max it out at 0x0F. I was setting the border color as well, as I wasn't sure about all the different monitors' ability to get a color burst. Didn't want to have to create a color calibration tool on top of everything else, but it looks like you already took care of that!

Update: Thanks to VileR, I found DOSBox-X which not only fixes the CGA wrap-around, it emulates CGA snow. So I can remove all the address wrap masks and have plenty of overhead for filling the edges during VBlank.
 
Last edited:
For the Tandy version of Commander Keen 4 I converted to the EGA graphics from planar to linear, so every byte contains 2 pixels. This is the same format as how pixels are stored in CGA 160x100 mode (ignoring every other byte that stores character 222). The game first draws everything to a buffer and when a frame is done, it is copied to video memory.

For the CGA 160x100 version I still create a frame of 320x200 pixels and only the copying to video memory is different. Creating 320x200 pixels but only using 160x100 pixels is wasteful, so I haven't released that code.
A CGA 160x100 version of Commander Keen deserves better :)
For example, the title screen should use more characters than only character 222 for more detail. The attachment shows what the title screen can look like. I've created it using CGAArt.
 

Attachments

  • keen4cga160100.png
    keen4cga160100.png
    293.4 KB · Views: 35
Turning this demo into more of a library for games. Here is a video showing of a preliminary sprite engine working with the scrolling. Source and executable now updated on GitHub.

 
60 FPS on a 4.77 MHz 8088!

After converting a great deal of the C code to assembly (like most of it), I have been able to get the demo running on my Compaq Portable at 60 FPS. However, this isn't just meant to be a demo, this will become a graphics library available to code games with (or other demos). Now, there are a couple of caveats - BLTing the sprite to the video memory doesn't check for snow. The sprite in this case is just a little too big to render completely during video retrace along with the screen edges. Since the Compaq CGA doesn't snow, you can't tell. An original IBM CGA would probably snow in the top few scan lines. I do have a version that checks to avoid snow, but timing is pretty tight at the moment. This would also be an issue if the sprite was being draw in the upper left hand corner of the screen - there would be a few scan lines of anomalies where the old sprite would be visible before the new image was rendered over it.

Now, this took some effort and required some very sophisticated timing tools. You can see them sitting on top of the Compaq. Yep, masking tape and a pencil ;-) Because the rendering is synced to the video output, simply changing the border color on the fly during different parts of the code gives a nice color bar graph on the side of the monitor of whats going on. Use the masking tape and pencil to mark the current color transition, make changes, see if the bar graph got shorter. Simple but effective.

Also, I was using the C function kbhit() to check for a keypress to exit the demo. It turns out this isn't a very fast routine - I was seeing a large bar in what should have been some very simple tests. The kbhit() routine was being called in the while() clause and I didn't immediately recognize the impact it was having. Live and learn. One of the big additions I had to make was programming the PIT to sync to the last scan line instead of waiting for the VBlank signal on the CGA. VBlank comes along much too late to get all the rendering I wanted to do, so using the PIT to interrupt on the last scan line provides a bit more time to get things done. It actually wasn't a problem for my Deskpro with a V30 running at 7.16 Mhz. so I pulled the EGA out of my Portable and stuck a CGA back in there. Ugh, that thing is slow.

Of course everything is available on GitHub. I'll now spend some effort cleaning up the sprite code and getting a playable demo together.
 
Paku Paku avoids the snow on this mode. As most of you know, the source is available within the download.
No actually, it doesn't. On a real CGA there's snow galore. There is NOT sufficient processing time at 4.77mhz to run snow-free, audio, and actual game-logic all at once, without introducing other issues like screen tearing.

It's part of why demo's don't impress me as much as they do others. It's easy to push the hardware when you don't have user input, unpredictable logic, and changing game speeds in the way. When it's all pre-scripted it's easy-peasy.

Though that's just part of why the demoscene and I always got on like sodium and water; making me utterly unwelcome in Commodore circles with their stupid trainers and loaders and pointless demo's.
 
I just tried this on my 1tk sx, and I'm getting like 6fps in slow, not 60. Just what is the minimum requirement to hit 60 because 4.77mhz isn't it.

In fact that you're blitting the whole screen (If I'm reading the code right) is kind of setting off my BS alarm unless min spec is a 386... or am I not seeing where you're tweaking the page counts?

I mean I couldn't even blit flat-out alternating colourspace values on two-thirds the screen in less than 3 elapsed frames.
 
I just tried this on my 1tk sx, and I'm getting like 6fps in slow, not 60. Just what is the minimum requirement to hit 60 because 4.77mhz isn't it.

In fact that you're blitting the whole screen (If I'm reading the code right) is kind of setting off my BS alarm unless min spec is a 386... or am I not seeing where you're tweaking the page counts?

I mean I couldn't even blit flat-out alternating colourspace values on two-thirds the screen in less than 3 elapsed frames.
Jeez, doesn't anyone read anymore?


You must be building from source - make sure you #define the code that uses the hardware scrolling. You are running the software rendering code that does exactly what you are seeing. You can build it to run software, hardware assisted. or both. If you build for both, press a key to continue from software to hardware scrolling.
 
The tile scrolling and sprite library is coming along nicely. To actually try it out with an interactive demo, I've created a maze runner "game". I always wanted to write a maze generator, so this was my chance. Generate a maze, create a tile map, and walk through looking for the exit. Not much on game play, but it should show off a couple of features of the library: scrolling (of course), sprites (well, one in this case), and dynamic tile updates (for the exit). The borders are again used for a visible bar graph of the rendering code. As this is about as trivial a "game" as one could come up with, it should be able to do all the rendering during inactive video, even on a 4.77 MHz 8088. I'd like to hear other's experience with CGA snow, as my Compaq Portable completes rendering during inactive video according to the border bars.

The "game" starts by generating a 20x20 maze and displaying it on screen as it runs the solver. Unfortunately, every once in a while it can get stuck in an unsolvable loop. So, it will regenerate a new maze in this case. You will see the maze get created twice in these rare cases. You enter on the left border and will find the exit on the along border. Game controls are simple - 'i', 'j', 'k', 'm' create a diamond to move up, left, right, and down. 'q' quits.

Quick video of Maze Runner work-in-progress:

You will find source and binary here:

Don't forget that this is a work-in-progress, not a complete project.
 
Back
Top