4

What'd especially piqued my interest with respect to demoscene scrollers was how they had implemented asset clipping (as in the culling of assets once they 'move' past screen edges/boundaries)

An example of what I am referring to can be found here: Phenomena - Enigma

Notice how the blue glyphs representing text at the bottom scroll past the screen and are effectively 'culled' once they reach the screen edges?

Here's another example: Unreal 1.0 by Future Crew

The yellow text at the bottom just like the previous example, scrolls past the screen and is culled

I will clarify that by "text", I am referring to giant blocks of graphics representing text and art.

Note I have next to no experience with low-level programming, and by implication much of assembly. With the NES (using hardware clipping from what I assume), sprites would clip off one by one as they'd make it to edge of the screen from what I've observed (As for how it worked exactly is something I'm not sure about myself)

I'm assuming the feat would be harder on the PC owing to the fact that it has to be done in software

I can think of naive techniques myself, such as checking if the asset's position is past either of the 4 edges and clipping it accordingly, but this wouldn't work perfectly, nor would it be particularly efficient. How was asset clipping implemented efficiently by demoscene programmers on the PC then?

13
  • 4
    It's not clear what you mean by "clipping actual text on a terminal window" -- are you still talking about a demoscene-like graphics application here? Are you talking about smoothly scrolling through a large block of text, or individual text "sprites" zooming around, or....? Obviously there's a number of approaches to "not drawing stuff you shouldn't draw".
    – Sneftel
    Jan 31, 2023 at 15:36
  • To be clear: you’re looking for an answer more complicated than “determine the intersection of two rectangles, copy bytes contained within”?
    – Tommy
    Jan 31, 2023 at 17:05
  • 1
    For blitting, I imagine they'd just take a couple of instructions to calculate the minimum of the edge of the screen and the edge of the thing they want to draw and then use that as the terminal condition in their blitting loop.
    – ssokolow
    Jan 31, 2023 at 23:47
  • 1
    @Hash Exactly. In abstract terms, take the intersection of the two rectangles and blit only that.
    – ssokolow
    Feb 1, 2023 at 22:29
  • 1
    Your terminology seems to be a bit off - clipping is fine, it just means "don't draw what doesn't fit the screen", but cullling is typically used for 3D graphics and means "don't draw what's hidden behind other objects" (and thus needs a z-order) and can certainly not be applied to scrollers.
    – tofro
    Feb 7, 2023 at 21:35

3 Answers 3

7

There are different techniques that can be applied depending on the situation.

The obvious trick is that if you have an asset like a ball and it would be drawn only partly inside the screen, then, you would simply copy the part that fits the screen, and don't copy the parts that are outside the screen. Not very difficult to calculate every time the ball is moved and needs to be redrawn. Simple rectangle copy of smaller source area than original asset to screen.

For things like text scrollers, it may be better to think it so that you don't have a fixed asset that you must copy to screen and clip it like in the ball example, but rather, you have a fixed screen area for which you must generate the content from source assets which include the text and graphical glyphs (i.e. the font) it needs to be drawn, plus offset into starting character from which to start drawing the message, plus offset into the glyph coordinate from which to start copying it on screen.

Using tweaked video modes enables to have small buffer area left and right (or up and down, or both) so when text is scrolled it can be drawn in chunks like one glyph/character at a time and then use the video start address and pixel panning offset registers to move viewport to graphics buffer one pixel (or line) at a time.

I recall some games that used tweaked modes and larger virtual canvas that they did not care to draw only visible area of sprites, they were copied fully so when the sprites extended beyond visible viewport it did not show as it was in a non-visible part of frame buffer.

8
  • You can see an example of not clipping because there's an offscreen buffer to draw into in the gifs I made for my Commander Keen answer - enemies will be drawn whole and leave invisible debris in the black unshown region rather than be clipped.
    – knol
    Feb 1, 2023 at 18:40
  • "you would simply copy the part that fits the screen" - I am assuming to know what fits on the screen, we find the min of the edges of the asset's bounding box and the edges that others had mentioned here? As for the text-glyph clipping, would it be right to assume we don't use the bounding box approach due to the large amount of assets we would have to clip? I don't entirely understand the method for the text/glyph 'clipping' here. We draw into a small region offscreen and move it from the buffer to viewport , pixels at a time, and repeat by adding more glyphs into offscreen buffer?
    – Hash
    Feb 1, 2023 at 19:56
  • @knol and how were these enemies in the unshown region ultimately removed from the region if they'd remained there?
    – Hash
    Feb 1, 2023 at 19:57
  • @Hash If you have an asset, say 24x24 block of wall tile graphics, or maybe a mouse cursor with a hotspot in the middle, if you want to move it around and end up to draw it 8 pixels beyond the left screen edge, then you know you must skip leftmost 8 cursor pixels and you copy remaining 16 right pixels. It's all how coordinates of asset map to coordinates of screen. The text scroller could be done in same way, but in some cases it might be best viewed how the screen coordinates map to the asset, e.g. when zooming in or out. As it would skip pixels if you zoomed in by mapping asset to screen.
    – Justme
    Feb 1, 2023 at 20:31
  • @Justme But that would only work in the case where I specifically wish to clip x pixels beyond the left screen and draw the remaining ones right? How could a 'general' scroller algorithm that allows for both 1) moving the graphics around the screen 2) also handling the case of clipping them when a portion of them moves beyond a screen edge?
    – Hash
    Feb 1, 2023 at 21:33
4

This answer is targeted towards a previous revision of the question which asked about generic assets and sprites.


In chunky linear modes like VGA Mode 13h, the problem for copying a rectangular region onto another is simple as the following variables can be calculated:

  • The number of visible horizontal pixels and rows of the sprite remaining after taking a union with the destination clipping rectangle.
  • The offset into the sprite's data to begin reading if the sprite is clipped on its left or top sides.
  • The destination pixel on the destination to begin drawing.

With these you calculate the following two constants which allow the source and data points to jump to their respective next lines after drawing a line of the sprite.

  • The offset between successive lines of the sprite data.
  • The offset between successive lines of the destination surface.

Then your drawing loop sets up the source and destination pointers, then for for effective_height rows: draws effective_width pixels, then skips pixels_to_next_line_sprite/destination.

In bitplaned video (CGA/EGA), horizontal clipping is difficult because it requires manipulating bits rather than bytes - masking out some number of individual bits from the left or right sides of the drawn sprite when they exceed the destination rectangle. In tweaked video modes (Mode X) your clipping algorithm has to consider a model where VGA RAM addresses manipulate groups of four pixels at a time - you'll have to change the currently enabled planes at certain times depending on whether you're drawing columns or rows.

In both cases, if it's at all possible to simply not do clipping and blindly copy full bytes in bitplaned video, or full quads in unchained mode, then that's best. Clipping requires calculation and results in non-constant values (the values calculated up above). Instead, if it's possible to write a hard-coded 'draw a 16x16 thing at x,y' routine with no clipping, then all the constants get folded into the instructions and loops can be unrolled. Drawing some variable number of rows is much simpler because that's simply the number of repetitions of the loop that draws rows as in the chunky case.

This answer expands on my comment in Justme's answer.

Justme:

I recall some games that used tweaked modes and larger virtual canvas that they did not care to draw only visible area of sprites, they were copied fully so when the sprites extended beyond visible viewport it did not show as it was in a non-visible part of frame buffer.

These gifs are from my previous answer about Commander Keen: What is 'Adaptive Tile Refresh' in the context of Commander Keen?

This is an animation of the first stage of the game - I believe you want to know how clipping for the alien sprites is done.

enter image description here

The graphics card registers allow you to specify the stride (or offset) between adjacent rows of graphics data in graphics card RAM. By default this is exactly the minimum number of bytes needed to display a row on screen - the byte after the one containing the furthest right's pixel data immediately precedes the byte for the leftmost pixel of the next line. If the stride value is bigger than this, then this reserves additional horizontal space on the right side of the row in graphics card RAM which is not displayed on screen, and the effect is to create a logical virtual screen which is wider than the display region (whose size remains unchanged).

The start address and pixel panning registers together specify what position in the graphics card RAM, and therefore in the virtual screen, the display should begin from. Because the virtual screen is wider than the displayed region, anything that is in the border isn't displayed. This is how Keen does scrolling, which is linked to when and where clipping is necessary.

This is a visualisation of the contents of graphics RAM during Keen. Watch the left side of the screen where the green aliens are drawn into the black. If these sprites were clipped then the reserved region there would not be drawn into. Unfortunately this only shows a 320x wide region whereas the real stride is much wider to allow for the extra tiles, so here's a single frame as example (sorry it's less than 100% accurate but it's illustrative).

enter image description here

enter image description here

The fuschia region here is the displayed window, which moves from left to right within the green rectangle. When the fuschia region hits the right side, it's jolted back by 16 pixels and anything that needs to be redrawn is redrawn. Notice that the black border is never shown because the fuschia region can't move that far, it's locked in the green zone. The Keen engine erases sprites every frame by redrawing the tiles on top of the sprites every time they're overdrawn by a moving object or the camera moves. Drawing a 16x16 tile aligned to display memory is fast because it's a fixed size and no panning is involved. The sprites aren't clipped in Keen horizontally at all; they're drawn fully but appear to be clipped because the excess pixels land in a safe location offscreen rather than wrap to the opposite side.

--

For a text mode screen, the graphics card parameters are the same: you can set the start address of the screen and have various finer-grained panning parameters for horizontal and vertical panning, allowing the programmer to manipulate the display of the text mode tile plane very much like it was a games console tile plane.

Like in Keen, you can specify a data stride to produce a logical screen of characters that is wider than the displayed region. Only a single extra character cell is necessary in this case to allow full free scrolling horizontally. With the extra character cell, you can pan within a character to smoothly slide the position of the text mode tile plane across the screen horizontally. When a full character size has been reached, the contents of the tile map can be updated to reflect what should be displayed at the camera's new position. You'd have to copy the contents of the text mode screen 'left' one row (or in whatever direction you wanted) each time.

Alternatively, the graphics card RAM can be used to store a large region of text or ASCII art characters in its entirety, and the start address and horizontal and vertical panning parameters can be used to freely fly around this prepared region without the contents or attributes of the displayed cells needing to be altered at all.

In these cases, the ability to direct the graphics card's text mode display to pan across partial tiles horizontally or vertically allows for things to 'fall off' the visible screen and it's the programmers responsibility to ensure the resulting content of the screen is acceptable with all the parameters in their new positions reflecting the scrolling to the new tile.

19
  • But the "Mode X", or any other unchained 256 color mode for that matter, should not require any bit manipulation for clipping calculations, it would look identical in coordinates. The fact that there are actually four 8-bit pixels in same video memory address should not change the clipping coordinate calculations. Only copying the data to video memory is slightly different. To blit the data into video memory, it just is done in four sub-parts, once for each data map/plane. Which is why some games internally store the background or sprite data converted to four sub-parts for copying.
    – Justme
    Feb 2, 2023 at 5:52
  • 1
    @Justme That's correct, I'll rewrite that. It's a similar concept though if you're doing latch copies from video ram to video ram.
    – knol
    Feb 2, 2023 at 6:24
  • How did you create the visualization of the graphics ram? Feb 2, 2023 at 18:56
  • 1
    @hash What I was referring to was if there's an OSB, you don't clip, you can draw whole sprites, and uncomplicated algorithms are fast.
    – knol
    Feb 7, 2023 at 18:47
  • 1
    @hash The OSB approach requires that your widest sprite be narrower than the combined OSB between one line of the screen and the next. If this is the case, the object would not be visible when it moves that far left and then wouldn't need to be drawn on the successive frame. For large enemies (like Rainbow Islands bosses), they'd have to be considered as a kind of composite sprite made of strips of tiles, perhaps. Also when you say 'a sprite in the left-most OSB', keep in mind software sprites aren't persistent frame to frame. They don't reside anywhere. They get erased and potentially redrawn
    – knol
    Feb 7, 2023 at 18:53
1

Since you've added the video examples as context, I can take a more educated guess at what's happening.

As Piiperi points out in a comment, Enigma is for the Amiga which has its own convenient mechanisms for scrolling (a coprocessor capable of altering registers relating to panning on a per-line basis) which a 90's VGA card does not have, but you've asked about the PC so for the purposes of this answer lets pretend it's an MS-DOS demo. In Enigma, the effect is taking place at the same time as a displaying a rotating textured cube with lit up edges and a starfield texture. This would rule out being an unchained mode effect (or at least it would be of little benefit to use it), so I'll assume linear, chunky Mode 13h. In this mode you cannot benefit from the registers allowing panning or altering the stride: every effect you can do relating to motion is done manually through redrawing (or in some cases palette cycling). This means any technique involving off screen borders in video RAM is not possible.


To scroll a row of text like in Enigma, the naive approach is to draw each letter as a sprite in turn and first detect if any clipping is necessary for a letter (full visible/partially visible/totally obscured), then draw either clipped or accelerated-whole letters until the full visible text are is drawn or you run out of letters. This is what you'd have to do if the letters were all independently moving objects like in a sine wave. If the letters move together in groups then we might be able to use part of the already-drawn image to help us update the image. Since the scroller here is one big chunk, that's great. We can't pan the image with registers - we will have to redraw the contents of the video RAM - but we might be able to do something with what we have.

Two ways to use the already drawn text to our advantage come to mind. The simplest way conceptually would be to copy the pixel data for the rectangular area of the scroller you've already drawn in video RAM speed pixels to the left, and draw in the new rightmost speed columns of the graphic. This might be slow because reading from video RAM is likely slower than reading from system RAM. We avoid reading from video RAM if we can.

The second way is to use some system RAM to make a temporary work space and draw the text there first from its individual letters. When we have this rendered phrase, we then copy it as a block to video RAM in different places to display it in different locations. If we wrote the entire phrase in system RAM to begin with, and copied a sliding rectangle from there into video RAM every frame, we'd be simulating the effect of having panning registers. Another way of thinking about this approach is thinking of the rendered text phrase as one giant wide graphic that gets pasted as a clipped moving sprite onto the screen. (And so all the other answers about clipping sprites by checking extents etc. apply.)

The reason you'd do this is if you've benchmarked that copying a rectangle of bytes is faster than rasterising individual letters. This might be the case if you have a proportional font in a complex atlas or your text is a fancy set of curved and/or gradient realtime vectors.

Drawing the entire text phrase in system RAM isn't a bad approach. It wouldn't be a huge amount of RAM considering the small height of the text. And you'd only need that memory during the time the scroller is displayed.

There is a way that's closer to a tilemap approach used in consoles, and if you are low on system RAM and want to use the minimum amount of system RAM. This approach would be to use a ring buffer. This means a rectangular buffer that overwrites its start elements as you scroll through to view future ones. In this approach, you use an offscreen bitmap as if you'd drawn the whole phrase, but only keep the parts of it that will be used in subsequent frames. When a letter is no longer needed to be drawn, new text is prepared in its place in system RAM. Because the 'displayed' (that is 'copied from system to video RAM') region of ring buffer is no longer one continuous rectangle, the copy takes place in two stages from the edges of the buffer.

Here's an example scroller for RC:SE: 😊

enter image description here

The green region gets copied to the leftmost pixel in video RAM but 'moves' in system RAM as you copy from new locations. To perform the scroll on frames 2,3,4... , you copy to video RAM from further along the rendered phrase in system RAM. As new letters should appear that exceed the width of the offscreen system RAM bitmap (which can be any width), you overdraw old letters with new ones, and start copying from the two different disconnected regions to compose a complete image - the green region is copied to the leftmost video RAM pixel, followed by the data from the red region. It's up to the programmer and compiler to come up with the tightest loop to copy these two rectangular regions. Copying contiguous pixels is good and fast, performing random pointer accesses and doing lots of little checks is bad.

I've replaced the old gif in this post with this new one since the old one drew partial letters in system RAM which distracted from my point. Focus here on the way the system RAM is updated - an entire letter is drawn at a time when the 'red catches up to it', the draw is aligned to a grid, and not clipped. That's small and fast.


In Unreal 1.0, the single-pixel effects might be more compatible with an unchained-mode screen if all the dots are drawn with it in mind (as the registers scroll the screen in one direction to show the text further in graphics RAM, the dot drawing would have to move along to keep up). That's just a hypothetical scenario - if you want to check, you'll have to use something like Dosbox debug build.


Ironically, unchained-mode and the split-screen hardware features allow you to split the screen into a hardware pannable top section of the display with a non-panning bottom section underneath - all the Keen examples would work for an upper scroller. (This is why the DOS port of Pinball Dreams swaps the top score display for a bottom score display.) But all the scrollers' text in your examples is on the bottom so that doesn't help. :)

26
  • @hash Please let me know if you find this useful or need more information
    – knol
    Feb 10, 2023 at 5:22
  • Sorry for the late reply. If I am gathering this right, I am essentially recreating the buffer + OSBs in video-ram and drawing the result in the Enigma method? And how does Unreal pan the background like you've said with the characters being drawn pixel by pixel if the lower portion of the display is non-pannable? Wouldn't this mean only the top portion of the 'background' could scroll? Also if the characters are drawn 'perpixel', wouldn't the chained mode13h be easier to work with in this scenario? Why use unchained?
    – Hash
    Feb 15, 2023 at 19:57
  • @Hash Yes that's right. It's all about making minimal updates and copying large rectangles quickly. The note about Unreal was to say that the approach in the gif was more likely in most cases, but in some cases modex would let you use hw if you accounted for it in sw in the rest of the screen: pan display right thru vram to scroll text, draw sw effect moving thru vram to make it motionless. Just an idea. Run dosbox-debug to know for sure. :) The paragraph about split screen is a separate observation, saying VGA can support scrollers in hardware, but not like in Enigma or Unreal.
    – knol
    Feb 17, 2023 at 7:33
  • So if I am understanding right, there is little difference between the approach Carmack took with Adapative Tile Refresh and what Enigma does here?
    – Hash
    Feb 18, 2023 at 17:15
  • 1
    Enigma is an Amiga demo. Are you speculating how a visually similar effect might be done on a PC? Feb 19, 2023 at 11:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .