2

Did they have to treat pixels like rocks on the beach, pick each one up, paint it, put it down, then move to the next one?

I'm talking about the days before vector extensions.

Honeywell machines had cool hardware that would increment a loop index in the same cycle it executed an instruction, but the 8088 didn't even have that.

5
  • 5
    Read up on graphics modes for CGA/EGA/VGA. You could have multiple pixels in one byte, and EGA and VGA had a funny way of doing several graphic memory writes (to different color planes) with one CPU write. So yes, pick up a handful pixels separately, one handful after the other. You also did flood-fill in a line-oriented fashion, because going horizontally was cheaper than going vertically. Flood-fill was sometimes implemented so slowly you could watch the shape being filled.
    – dirkt
    Commented 12 hours ago
  • 1
    How would SIMD or a GPU help much with floodfill? It doesn't feel especially parallel in that sense. The per-pixel test is: correct colour, and has a route back to the source. The latter condition means serial state or, at least, inspecting an arbitrary number of arbitrarily-placed inputs, repeating a lot of work just to reproduce serial state.
    – Tommy
    Commented 4 hours ago
  • 1
    Laughs in Amiga Poor people without a blitter.
    – pipe
    Commented 3 hours ago
  • @pipe the blitter couldn't flood fill either; only fill in the horizontal area between premarked locations. So, ummm, actually I guess it could fill but it couldn't flood?
    – Tommy
    Commented 2 hours ago
  • 1
    @Tommy: For a convex shape at least, CPU SIMD is useful if you can branch on it efficiently like with x86 MMX / SSE2, i.e. if it's useful for memchr. Like pcmpgtb or pcmpeqb and pmovmskb eax, xmm0 / test eax,eax/jnz any_matches, and maybe bit-scan the integer bitmap of element matches. Horizontally within a line, you want to search for a start point (probably starting near the start column of the previous line, so maybe only a couple vectors). And find an end point, either as you go reading + filling, or find then start+end with scalar then basically SIMD memset vs. rep stosd. Commented 1 hour ago

3 Answers 3

5

The basic way to do flood fill worked something like this:

  1. Start at the designated point.
  2. Read the colour at that point from the screen buffer. Call that C.
  3. Search to left or right until you hit a different colour.
  4. Do the same in the other direction.
  5. Draw a line in the flood-fill colour between the two points you've found. If the screen layout is remotely sane, this is much faster than setting pixels individually.
  6. For the two points you found, look immediately above and below them for pixel of colour C.
  7. If you find them, treat each as a designated starting point, as above.
  8. If you don't, search back towards your starting point for them. If you find them, treat them as a designated starting point. If you don't, go on to the next point you have.
  9. When you run out of points, you've finished.

On the CGA, this is simple, because all of a pixel is in a single byte, and you may have several pixels in a byte.

On the EGA and VGA, it's a bit more complicated, because of the separate colour planes. It might be worth reading a whole scan line into RAM at once so that you can work on it quickly. Writing is easier, because you can write to several colour planes with one CPU write.

I've never implemented flood-fill on a PC, although I did it on the BBC Micro. That had some OS primitives for implementing flood-fill, and I was able to make it quite fast in a BBC BASIC program. In contrast, the Computer Concepts Advanced Graphics ROM for the same machine was almost slow enough to see individual pixels being processed. I still don't know how they made it so slow.

1
  • That's fascinating! I guess the answer to what I was actually curious about is, before SIMD, bitplanes, vector extensions, and GPUs; you had to do it one pixel at a time unless the pixels were so small that you could fit more than one in a byte. Cool, thanx! Commented 44 mins ago
4

(Possibly off-topic, since this is not specifically about an 8088 implementation, but perhaps of interest.)

A piece of software called The Graphics Magician, by Penguin Software, was used to create the graphics on a number of old adventure games. It used lines, pre-defined brushes, and flood-fill to paint the screens in games like Transylvania. The games were ported to multiple platforms.

The manual described the algorithm they used for the flood fill:

  1. Scan directly up from the selected point until a border is found.
  2. Move down one line, filling to the left and right borders.
  3. Average the left and right borders to find the midpoint, and move down one line from there.
  4. Check to see if the point moved down to is a border. If not, go back to step 2.

The interesting bit is in step 3. Shifting the center allowed the flood fill to handle some concave shapes in a single operation. For example, imagine a crescent moon shaped like a parenthesis. If you start at the top, the center point would swerve around the inside as the algorithm moved downward.

(You can find a disassembly of the Apple II hi-res graphics version here, but the memory layout for that graphics mode is... unique.)

The adventure-game algorithms prized speed over thoroughness. If you want something that flows around corners, you'll need something that uses more memory and time. For example, this video of Micro-Painter demonstrates what someone once referred to as a "screen etcher". The code does essentially what is described in @tofro's answer here, probing each pixel and adding neighbors to a work list.

1
  • Does this answer "Did they have to treat pixels like rocks on the beach, pick each one up, paint it, put it down, then move to the next one?"
    – RonJohn
    Commented 52 mins ago
2

More or less like that:

void fill (x, y, col) {
  setPixel (x, y, col)
  if (getPixel (x + 1, y) != col then fill (x + 1, y, col);
  if (getPixel (x, y + 1) != col then fill (x, y + 1, col);
  if (getPixel (x - 1, y) != col then fill (x - 1, y, col);       
  if (getPixel (x, y - 1) != col then fill (x, y - 1, col);
}

Flood fill is an inherently recursive activity. (Too) simple implementations can easily run into a stack overflow (the real thing, not the web site ;)) More sophisticated implementations wouldn't use the CPU stack, but a separately allocated one to be able to observe memory usage and avoid that. With some more code invested, you can improve the algorithm to make sure every pixel is only visited once.

Note the getPixel() and putPixel() complexity stands and falls with the hardware implementation of the frame buffer. Implementations that use separate bit planes/color can make the code pretty expensive, while hardware implementations that use one byte (or one word) per pixel make for a much faster implementation.

5
  • I think that one is the most naive one you'd start with, I'd doubt anything actually used would do it that way. And since you usually have multiple pixels per byte, that's usually where the optimization starts (on standard CGA/EGA/VGA cards with no extra bells and whistles.)
    – dirkt
    Commented 10 hours ago
  • @dirkt Agree. But it's also the most easy to understand :)
    – tofro
    Commented 8 hours ago
  • I understood the question more along the lines of "how was flood fill TYPICALLY implemented back then".
    – dirkt
    Commented 7 hours ago
  • 1
    @dirkt ...and you would be surprised how many naive implementations you can find in real ROMs (here:retrocomputing.stackexchange.com/questions/2998/… , for example)
    – tofro
    Commented 5 hours ago
  • >simple implementations can easily run into a stack overflow (the real thing, not the web site ==== Oh my God! Commented 47 mins ago

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.