Thursday, 29 March 2012

In Which We Discuss Turning Text Into Pictures


With basic screen management working, I can now start to look at the more advanced tasks involved in implementing a full-screen editor facility - that is, being able to plot text across the screen in an optimised way, manage row-overflow, allow in-place editing, and manipulate the bitmap for events like adding a new line to the bottom of the screen and scrolling everything up to accommodate it. This functionality, whilst being a little on the unexciting side, is a key element to how the OS will behave later when there's a programming language to use - the visible part of the screen will be a moving viewport over the users' application code, so it's important to get the fundamentals of the editor right at this point.

The first step is to improve how the text-plotting process works. At the moment each character of a string to be plotted is processed one-at-a-time, so the row/column index is updated as the string is traversed and the appropriate byte in the screen bitmap derived - but that means that each bitmap byte is updated twice, once for the current string character, and then again for the next. An obvious optimisation is to process string characters in pairs, so that both can be masked into the byte at the same time and then updated in one hit. There are some specific considerations that this technique has to cater for:

  • There is an even number of characters in the string to be plotted, and the first character is targeting the first glyph nybble in the first bitmap byte
  • There is an even number of characters in the string to be plotted, and the first character is targeting the second glyph nybble in the first bitmap byte
  • There is an odd number of characters in the string to be plotted, and the first character is targeting the first glyph nybble in the first bitmap byte

The first case is the easy one - we can just process the string characters in pairs, mask them together, and splat them into the bitmap bytes without regard for whatever might already be in those bytes (because with an even number of characters to process, starting with the first glyph in the first byte, we're replacing everything in every byte we write to). The second case is tricky, because starting at the second glyph means we have to take the first character of the string to begin with and mask it into the byte so that we preserve the first glyph. Then we can process the rest of the string in character-pairs, just replacing bytes as in the first case - although at the other end of the string we'll have the reverse of the beginning, in which the last character will be going into the first glyph of the last byte we're updating, and we'll have to mask it in. Finally, the third case is a variant of the second - we can plot characters in pairs from the beginning, but the last character will need to be masked into the last byte. Note that an odd number of characters starting at the second glyph is just an inversion of this variation - we have to mask the first character in, but the rest of the string is plottable as character-pairs.

As complex as this sounds, there is a little short-cut we can take for those tricky edge-conditions where a single character has to be masked into an existing byte, because we can rely on the underlying text storage to tell us what the preceding or following character is (depending on which glyph we have to preserve), and then just grab that character and stick it on the beginning (or end) of the string we're plotting - and then all strings become easy-to-process even-numbered character pairs, and all cases are the first easy case. And you're probably thinking "What underlying text storage?".

OK, so think about it - the OS is to feature a full-screen editor, which will be a viewport over a larger body of text (the users' application sourcecode). As it happens, that text will be generated on-the-fly from a deeper layer of code* but it still has to be put somewhere so that it can be displayed and edited - and that 'somewhere' is a 2K text buffer that holds the current screens-worth of sourcecode, and which the OS draws onto the screen bitmap. The user can then navigate around that visual representation making changes at will, but those changes are actually made to the text buffer and instantly redrawn in the bitmap. So the refresh logic can take the changed section plus a character either side of it as necessary, ensuring the low-level draw routine always gets nice even-length strings which are aligned at the first glyph in the first bitmap byte to be updated. By marking lines as 'dirty' when they're edited, we can make sure the refresh routine stays nice and fast by only replacing the sections of the bitmap that need to change, rather than repainting the whole screen every time.

Incidentally, I've now stepped firmly into Expansion RAM territory, because that text buffer lives at $0400 which is the 3K Expansion block (BLK0). The OS is intended to use as much expansion RAM as is available (you may remember we spent some time on this in the RAMTAS phase) and  it has always been the intention that the quirky VIC-20 memory map would be subsumed into a continuous, fully-populated memory space under VIC++. This marks the first step beyond the built-in 5K though!

So we define a chunk of storage at $0400 as the text buffer, which would logically be 1000 bytes (40x25 characters) but is in fact twice that, because each character in the buffer has an accompanying control byte which currently supports bit-flags for inverse-mode, underline, and strike-through. There's also a 50-byte table in ROM which holds the start addresses for each row in the buffer, and 4 bytes in Zero Page which embody 25 bits (with 7 unused) indicating whether a row is 'dirty' or not - any change to a row will cause the appropriate bit in these ZP bytes to be set, so that the refresh logic can do a very quick test to identify which lines, if any, need redrawing.

I'm having a minor attack of 'where do I start?' at the moment. I've got the memory structures defined, and of course the code already exists to find the bitmap byte to be updated - I think I need to write the bit of code that splats strings into the text buffer next, so that I can then write the routine that finds dirty lines and calls the redraw routine. Which I can then code as a simple paired-character mask-and-update module, and then write a high-level 'take this string and print it at this row/column' routine. Ah, Russian Dolls again...





* The code you write is dynamically compiled into 6502 Assembly Language for speed; when you view the code on-screen, it is translated back into what you originally wrote. It may sound almost impossible, but it isn't - just fiendishly difficult. :)

No comments:

Post a Comment