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?

Hash
  • 141
  • 4
  • 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 '23 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 '23 at 17:05
  • @Sneftel My bad. I was referring to the latter, as in the 'clipping' of demoscene graphics as they would make it past the screen border, though I was also curious as to how regular text/blocks of text in the form of ascii art were clipped once they'd moved past the screen border (The BB demo comes to mind for this case) – Hash Jan 31 '23 at 18:42
  • @Tommy I am assuming by that, your method involves intersecting the set of points making up one of the borders/walls and the points making up the graphic asset, with the intersection being among the point to be removed? – Hash Jan 31 '23 at 18:46
  • 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 '23 at 23:47
  • @Hash more like Justme's answer; instead of for(all y) memcpy(all pixels) think more like for(all y on screen) memcpy(region of pixels on screen). So it's four tests before you start copying data, then just supplying bounds to whatever copies the data. That said, with scrollers you might know that you're never going to clip on y, only on x, in which case you might store your graphics as columns rather than rows and just copy the visible columns. – Tommy Feb 01 '23 at 02:25
  • @ssokolow Could you clarify what you mean by a terminal condition here exactly? As in, they find the min of the two edges, and blit from that edge to the rest of the asset to be drawn onwards? – Hash Feb 01 '23 at 19:21
  • 1
    @Hash Exactly. In abstract terms, take the intersection of the two rectangles and blit only that. – ssokolow Feb 01 '23 at 22:29
  • @Hash Your edited question now assumes that a naive approach would not work perfectly, or be efficient. May I ask why do you think a naive approach won't work perfectly, and in what way would it fail to work? – Justme Feb 06 '23 at 19:54
  • @Justme I figured performing 4 checks for every graphics blocks every frame would be particularly slow, especially since it doesn't wrangle with the various esoteric features on the display card. As for the 'perfectly' bit, the approach would break down if the graphics block is intersecting 2 edges of the screen as opposed to just one, since it would involve extracting the 'union' of the portions of the graphics blocks intersecting the two edges. Wouldn't that just slow down things further? – Hash Feb 07 '23 at 14:36
  • @Hash But that's exactly how it must be done, for a general case of plotting a software sprite anywhere on screen. Check separately in X and Y directions how much from an asset should be painted and where on screen. That already handles the union, it does not require separate magick tricks. – Justme Feb 07 '23 at 17:35
  • 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 07 '23 at 21:35
  • @Justme Is there not more room for optimization though? Atleast off the top of my head, checking if it intersects either of the edges everytime it is drawn would be inefficient as opposed to only doing so when it 'occupies' regions close to the edges (which I suppose could be implemented by 'subdividing' the screen into grid-based regions) and only performing such checks in grid cells bordering the edges. I had also there was more room for optimization since I struggle at coming up with novel methods of optimization unlike a lot of demosceners – Hash Feb 08 '23 at 13:54

3 Answers3

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.

Justme
  • 31,506
  • 1
  • 73
  • 145
  • 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 01 '23 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 01 '23 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 01 '23 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 01 '23 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 01 '23 at 21:33
  • @Hash It's the same piece of code. Calculate from coordinates and size if the asset can be drawn in full size, or if the starting offsets and lengths needs to be adjusted in X and/or Y directions so that only the visible part is copied, or nothing copied if the asset is fully outside visible screen. Or rather, a painter wants to paint something like big tree to a canvas, if it does not fully fit into canvas, ignore that part, don't paint outside the canvas, paint only the zoomed-and-panned part of the big tree you want to have on the canvas. – Justme Feb 01 '23 at 22:01
  • I think you should provide both a link and a screenshot to the effect you'd like us to investigate. – knol Feb 02 '23 at 04:53
  • So a general algorithm would simply involve intersecting (clipping) with the 4 edges (2 horizontal and 2 vertical) and printing the result? In the case it's within all 4 edges, the asset to be printed is the asset itself. Am I getting this right? – Hash Feb 04 '23 at 07:09
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.

knol
  • 11,922
  • 1
  • 44
  • 76
  • 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 02 '23 at 05: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 02 '23 at 06:24
  • How did you create the visualization of the graphics ram? – Thorbjørn Ravn Andersen Feb 02 '23 at 18:56
  • For the gif, I recompiled dosbox with changes to the video card implementation to force the start address and pixel panning registers to zero and recorded a video. This is why it doesn't show a region that's the full width of the logical screen – knol Feb 03 '23 at 01:21
  • @knol I'd come across the Commander Keen post very recently, and did some reading. I still have a few questions pertaining to how it worked. For instance, the sprites being 'stowed' away in the black region. What exactly is preventing them from moving beyond this region? They move from the Fuchsia over to the Black region. What would happen if they were pushed even further? – Hash Feb 04 '23 at 07:47
  • @knol And to add on, could you explain how the text mode scrolling worked? I am a little confused. So this, much like CK, possesses a 'black region' holding a character in this case. As I scroll towards the black region, the character held within is revealed slowly. Upon it being fully drawn (When I have scrolled h or w pixels vertically and horizontally) , I write another character from memory into this black region and "jolt" the screen back by say, h pixels. What happens to the characters on the other end that should be 'clipped' by the jolt as it jolted leftward? Where do they go? – Hash Feb 04 '23 at 07:55
  • @Hash The fuschia rectangle represents the currently visible region through the game's camera based on its x,y coordinates. Checking whether a sprite is within the fuschia rectangle is simply a case of checking if the bounding boxes of the sprite and the fuschia rectangle overlap at all. If any part of the sprite is on the current visible screen, then stamp the sprite into graphics ram without clipping. There's no way for a sprite to wander fully into the black area (unless it is a small sprite with a big bounding rect) because then it wouldn't be on screen and there'd be no need to draw it. – knol Feb 04 '23 at 16:36
  • @Hash The leftmost cell in a text mode scroller (and in Keen as you can see) is just overwritten. Say you start with the letters ABCDE in vram and you can only see ABCD on your 4-cell wide display. As you scroll to the right using the registers, E becomes visible on the right and A moves off the left. When you have scrolled all the way, you can only see BCDE. At this instant, you need to overwrite ABCDE with BCDEF and set the registers to display the first character again, and you can continue. From the user's perspective you've smoothly scrolled over multiple letters ABCD->BCDE->CDEF. – knol Feb 04 '23 at 16:53
  • @knol Could you explain what you'd meant by - " If any part of the sprite is on the current visible screen, then stamp the sprite into graphics ram without clipping" ? If only a portion is visible, why stamp the entire sprite without clipping? – Hash Feb 04 '23 at 19:15
  • @knol I see. So as the screen moves to the right, the left 'offscreen' buffer is constantly overwritten by characters approaching it from the right? – Hash Feb 04 '23 at 19:16
  • @Hash Calculating and performing clipping can be more expensive than dumbly copying a rectangle. To clip you need to calculate how much needs to be clipped and then set up your algorithm (and potentially slow gfx card state) to support it. Drawing a rectangular sprite of predefined size is simpler since all the numerical parts of the algorithm are fixed, so instructions can be repeated rather than a loop counter cpu register used. A clipped sprite requires a variable amount of drawing each frame based on its position. The less time spent on loop tracking, the faster (potentially). – knol Feb 04 '23 at 20:51
  • @knol So as opposed to clipping, we instead shift to displaying "preclipped" versions of the sprite at "predefined" periods in their movement? For instance, say I have a ball that moves to the left till it's clipped in half by the left edge, followed by it stopping. I use two loops, one up to it reaching the edge, wherein the entire ball is displayed, followed by the "preclipped" half-ball variant being displayed afterwards? – Hash Feb 06 '23 at 16:28
  • @knol On that note, if I instead had to scroll multiple graphics blocks/sprites across and around the screen, (say glyphs representing characters, such as in scrollers), would the best approach be to move them around and check for edge clipping everytime? Keen's approach to sprite "clipping" by drawing them into an offscreen buffer can't work here right? In Keen, the enemies don't enter or move into the OSB of their own 'will', it is merely a consequence of scrolling past them. If I have multiple text glyphs all scrolling at different speeds, this approach wouldn't work right? – Hash Feb 06 '23 at 16:29
  • @hash Precalculating part-clipped objects is something you could do if you really had to, I suppose? It sounds like something that would be done on the ZX Spectrum perhaps, which has no per-pixel panning registers and no accelerated drawing. Memory use would be an issue. I don't think any PC game would do this. – knol Feb 06 '23 at 16:39
  • @hash If your scroller is made of independent flying letters moving in different directions, they'd have to be treated like sprites - erased if previously shown and redrawn if now within view. The offscreen buffer method is simple, clipping with a mask is precise but unnecessary. If your scroller text is a single continuous large element then you'd treat it like a background - you'd redraw only dirty parts or parts incoming from the edge of the screen and use the panning registers to make it move. Everything is case specific depending on your goal - experiment and write benchmarks. – knol Feb 06 '23 at 16:52
  • @knol Wait, so if the approach you had mentioned does not use precalculate preclipped objects, what exactly were you referring to then by 'Drawing a rectangular sprite of predefined size is simpler since all the numerical parts of the algorithm are fixed'? – Hash Feb 06 '23 at 18:14
  • @knol Which leads me to a problem that I've been mulling over for quite a while and can't quite crack - say we use the sprite based approach in conjunction with the offscreen buffer method

    In the case of tilemaps + OSB, if I scroll to the right, the former left-most tile ends up in the OSB. Scroll to the right once more and the next left-most tile overwrites it. However, with sprites + OSB where I specifically direct sprites to move past the screen,what happens when I force a sprite in the left-most OSB to move further left? Or do I just encode the movement to stop at the bounds of the OSB?

    – Hash Feb 06 '23 at 18:17
  • 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 07 '23 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 07 '23 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. :)

knol
  • 11,922
  • 1
  • 44
  • 76
  • @hash Please let me know if you find this useful or need more information – knol Feb 10 '23 at 05: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 '23 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 '23 at 07: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 '23 at 17:15
  • I am slightly confused, however. In ATR, we use tiles and 'jolt' them back (or forwards depending on the direction we move in). IN Enigma, exactly what are we jolting back if the phrases within each region are bitmaps? How does the 'overdrawing' of the old letters happen exactly? Does it happen as a natural consequence of my loading in the red region from the left as in your example) which overwrites the green region? – Hash Feb 18 '23 at 17:36
  • @Hash I've altered the gif to make it nicer and rewritten the explanation. The gist is that 13h requires you to redraw the screen; clipping takes place by virtue of choosing where to copy from; you construct your engine to perform minimal updates to any preparatory RAM areas; and contiguous rectangular copies are fast. – knol Feb 19 '23 at 01:40
  • 1
    Enigma is an Amiga demo. Are you speculating how a visually similar effect might be done on a PC? – piiperi Reinstate Monica Feb 19 '23 at 11:21
  • I hadn't checked that haha. I didnt know. Well the questions specficially asks about PC coding in the last sentence so I made an assumption. – knol Feb 19 '23 at 11:28
  • @knol So if the two regions are discontinuous, how would one go about combining them continuously without the use of pointer checks and what you? Those seem like the most obvious way to combine the two. Is this essentially ATR? – Hash Mar 03 '23 at 18:50
  • @Hash Pointer math would be used; pointer checks not really. The programmer defines how the scroller works: think of it as deliberate presentation of an area rather than clipping a randomly moving object. The green region will always be at least 1px wide. If the green region fills the output (no discontinuity), no red region exists. All scrolling algorithms will tend to resemble software emulation (this scroller, and ATR) of a hardware tilemap because all approaches pursue the same visual goal and a hw tilemap is a logical system to present+scroll tiles with minimal programmer intervention. – knol Mar 04 '23 at 10:04
  • @knol Why won't the green region collapse over to 0pxs? When the red region fills the output, wouldn't there be no greens left to display in that instant? – Hash Mar 09 '23 at 13:28
  • @knol Given we are working with a ring buffer, how would we move around pointers when the green region is continually shrinking and the red region is growing in size? – Hash Mar 09 '23 at 15:45
  • @Hash We want to copy gfx into vram starting at pixel 0. We want WIDTH pixels total beginning from point X in system memory. If we start at X and copy every pixel until we bump into the right edge of system memory, that's the green region. X wraps and is always within source memory - there's -always- a green region. To copy the remaining part, we start copying from pixel 0 in source memory, so that the second copy joins the right edge of the first. This is the red region - only used if the first copy didn't have enough source ram to fill the screen.(Watch when 'U' appears in the anim) – knol Mar 09 '23 at 16:28
  • @knol So if I am understanding this right, the ring buffer is ideally WIDTH pixels wide. We begin copying from the beginning of the green region (X) until we have copied out WIDTH pixels. If we reach the end of the buffer before copying WIDTH pixels, we wrap back to the beginning of the buffer and start copying until the beginning of the green region (X). Am I getting this right? – Hash Mar 10 '23 at 14:25
  • @Hash In my example, the ring buffer in system memory has to be bigger than the display width by the size of one letter graphic, since this allows us to quickly blit a complete letter graphic with no clipping into the extra space when the copy source regions move across by one letter. If the ring buffer was exactly the width of the screen, then you could still do this scroller but you'd have to draw the new letter one pixel column at a time as its revealed instead of copying a whole letter. If your algorithm for drawing letters can do pixel column draws quickly, great! – knol Mar 10 '23 at 22:47
  • @knol I see, I see. I think I've understood most of it, just one final question. I understand the green and red regions are merely 'abstractions' but regardless, would it be right to assume that the setup requires just two pointers - the pointer to the beginning of the buffer and a pointer to the beginning of the green region? When the red region 'takes over' the buffer, I set the green pointer to the beginning of the buffer pointer, starting up the process again? – Hash Mar 11 '23 at 19:19
  • @knol And I am assuming this is what Commander Keen (and ATR at its heart) used? A ring buffer setup? – Hash Mar 11 '23 at 19:19
  • @Hash Storing and updating pointers is one way to do it. To make the gif, I stored only the frame counter value F. From there I can work out the size and source and destination pixels of the green and red regions and also knowing that these values repeat every time F=ring buffer size. Storing pointers is possible but not necessary or all that much faster - it'd stop you printf-ing out all the intermediate values you calculated (since they wouldn't exist) when you were trying to debug the effect. – knol Mar 11 '23 at 23:00
  • @Hash Keen 1-3 do not use a ring buffer. You wouldnt see jolting in vram with a ring buffer, youd see new tiles being drawn over the old ones left to right like the letters in the top of my gif.Later Keens -do- use a ring buffer vertically where you move down, the scrolling regs move down through video ram until it loops back to the top. (Some cards didn't support overflow looping, so the games have a non ring buffer mode too) ATR is only dirty tile tracking on a software drawn tilemap. It can apply when the visible screen is static in ram, it moves through ram, or there's no scrolling at all. – knol Mar 11 '23 at 23:15
  • @knol But this frame counter method would only work if the screen scrolls every frame right? What if the scrolling was controlled by, say, the player moving the 'camera' to the right manually? This would mean the camera would stand 'still' if the player chooses to not move it. Or could I just create a 'frame' variable and increment it each time the player chooses to move the camera? – Hash Mar 19 '23 at 06:33
  • @knol Why wouldn't the ring buffer method utilize jolting? In an earlier comment, you'd mentioned: 'in my example, the ring buffer in system memory has to be bigger than the display width by the size of one letter graphic'. Wouldn't the existence of a character offscreen (similar to the OSB) require some jolting when I read in the character offscreen when scrolling towards it? If I scroll it enough, I'll have to read it in, jolt it back and repeat the process right? – Hash Mar 19 '23 at 06:36
  • @knol Also, in the ring buffer method, while I understand how I shift around the ends of the red and green region, how would I move around the 'middle portions' of the red and green region as their size changes? Shifting the ends would be as simple as swapping pointers and overwriting them, or using your frame variable. But how would I say, move the rest of the red region to the right without iterating over all of them and moving them in the buffer to the right, manually one by one? This one has been bugging me for a while – Hash Mar 19 '23 at 06:37
  • @Hash The frame counter method the easiest way to track a scroller that moves at a predefined speed. You can generalise it to arbitrary scrolling by using camera pos/speed variables, and then you're making a (1 dimensional) tilemap system - the task becomes how to update the system memory to reflect motion of +/-X arbitrary pixels as cheaply as possible. ATR checks for tiles that are different and redraws them, but a ring buffer can track the 'front' edge and draw new tiles that this 'hot' edge encounters, which is what the right side of the red region is doing in system memory in my gif. – knol Mar 19 '23 at 16:40
  • @Hash Keen 1 uses jolting because Carmack's design keeps the visible display region (two pages) located in a constant location in VRAM. This means that when the camera moves it has to 'move' everything back a tile by redrawing what's different, just as a consequence of the design. A -hardware- ring buffer like Keen 4-6 moves all over the VRAM - as the camera moves right or down, the visible region moves right or down 'infinitely' in VRAM. A -software- mode 13h ring buffer like in my gif -simulates- this concept by copying the regions to vga rgm it needs to to provide the correct output result. – knol Mar 19 '23 at 16:49
  • @Hash I don't understand the question in your third comment. All that happens in my gif each frame is three fast repetitive operations: a new letter is conditionally drawn in system ram, the green rectangle is blitted from system ram to video ram, and the red rectangle is blitted from system ram to video ram. Nothing else happens. – knol Mar 19 '23 at 16:58
  • This comment thread is getting very long now. I suggest the best thing you can do is try to program some of these effects yourself. Not necessarily in DOS, but on papero r maybe in a friendlier environment for 2D graphics like Blitz BASIC Plus. Then you can experiment with copying rectangles both with accelerated functions and manually pixel by pixel. I'm happy to try to explain things further, but I feel like you might be missing some background concepts which explain why certain operations are necessary, or fast, or slow (which you can of course open a new question for RC to explain as well) – knol Mar 21 '23 at 16:12