The C64 OS Programmer's Guide is being written
This guide is being written and released and few chapters at a time. If a chapter seems to be empty, or if you click a chapter in the table of contents but it loads up Chapter 1, that's mostly likely because the chapter you've clicked doesn't exist yet.
Discussion of development topics are on-going about what to put in this guide. The discusssions are happening in the C64 OS Community Support Discord server, available to licensed C64 OS users.
C64 OS PROGRAMMER'S GUIDE
Chapter 4: Using the KERNAL → Screen (module)
The Main Event Loop
The screen KERNAL module is responsible for implementing the operating system's main event loop. While an Application is running, the main event loop does the same set of steps, over and over again, in a loop:
- Mouse Events
- Checks the mouse event queue.
- Dispatches the top mouse event to the screen layers.
- Dequeues that event.
- Key Command Events
- Printable Key Events
- Checks the printable key event queue.
- Dispatches the top printable key event to the screen layers.
- Dequeues that event.
- Timers
- Redraw
- Performs a redraw cycle by calling redraw in the screen KERNAL module.
- Loop or Break
- Lastly, jumps through loopbrkvec (the loop break vector, defined in //os/s/:service.s).
- The loop break vector is configured to return to the start of the main event loop.
- When an Application is quit, the loop break vector is temporarily redirected to an Application switching routine that is internal to the service KERNAL module.
evtloop
Purpose | Pulls events from event queues and dispatches them to screen layers. |
---|---|
Module offset | 0 |
Communication registers | None |
Stack requirements | 4 bytes + |
Zero page usage | $19, Custom |
Registers affected | A, X, Y |
Input parameters | None |
Output parameters | None |
Description: This routine is at the very heart of C64 OS. When nothing else is happening, the majority of CPU time is spent looping through this routine. It is almost never necessary to call this routine manually. This routine is exited when an Application is quit by calling quitapp in the service KERNAL module. When the next Application is loaded and run, the service KERNAL module calls this routine to re-enter the main event loop automatically. See details in the description above for an overview of the steps taken during each cycle of the main event loop.
Because the main event loop dispatches events to processes that have pushed a screen layer, the amount of processing time, the stack space usage, and the zero page addresses that are used, is all dependent on what those other processes do.
Screen Layer Management
The low-level mechanisms for messaging, event dispatching and screen compositing, is done via a stack of screen layers. There are 4 layers, 0 through 3. The topmost layer, index 3, is for the menus and status bar. It is permanently at index 3 and is unaffected by pushing and popping layers from the stack. Pushing and popping layers thus affect the lower three layers only, indexes 0, 1 and 2. Typically, an Application's main user interface is at layer 0, and when a Utility is opened it claims layer 1. There is an additional layer for special use cases.
markredraw
Purpose | Marks a layer index from which a redraw and recompositing is required. |
---|---|
Module offset | 3 |
Communication registers | X |
Stack requirements | 2 bytes |
Zero page usage | None |
Registers affected | X |
Input parameters |
X → Layer index to redraw from. X → Use 0 to redraw all layers. |
Output parameters | None |
Description: This routine marks a layer as dirty and requiring a redraw cycle. It does not actually begin the redraw. The redraw cycle happens at the end of each main event loop iteration. The same layer may be marked as requiring a redraw more than once, for multiple reasons, before the redraw cycle takes place. To force a redraw of the entire screen, call markredraw with X equal to zero.
Note that this is a low-level screen layer redraw. Although this could lead to a full redraw, it usually results in recompositing. This happens, for example, when a Utility is moved. The Utility layer itself is in a new place on the screen and so it must be redrawn, but new areas of the Application layer beneath are exposed, so it must be redrawn too. Whether the need to recomposite leads to the need to do a full redraw is based upon how the layer is buffered.
See Chapter 2: Architectural Overview → The Drawing System for an overview of layer drawing options.
layerpush
Purpose | Push a screen layer structure on the screen layer stack. |
---|---|
Module offset | 6 |
Communication registers | X, Y |
Stack requirements | 4 bytes |
Zero page usage | $17, $18 |
Registers affected | A, X, Y |
Input parameters | RegPtr → Screen layer structure |
Output parameters |
C ← Set on error, no free layers C ← Clear on success X ← Index of pushed layer |
Description: This routine takes a RegPtr to a screen layer structure, and pushes the pointer onto the screen layer stack, from index 0 to 2. If the stack is already full, the carry is returned set and the layer was not pushed. If there was room on the stack, the layer is pushed, the carry is returned clear to indicate success, and the X register holds the index of the pushed layer. The index is also written into the screen layer structure itself.
See screen layer structure outline in the description of layerpop, below.
layerpop
Purpose | Pop the last screen layer structure pushed to the screen layer stack. |
---|---|
Module offset | 9 |
Communication registers | Status |
Stack requirements | 2 bytes |
Zero page usage | None |
Registers affected | A, X |
Input parameters | None |
Output parameters |
Z ← Set if all layers have been popped Z ← Clear if layers are still on the stack |
Description: This routine pops from the screen layer stack the last screen layer that was pushed to the stack. The zero flag indicates whether any layers are left on the stack. This can be used to call layerpop in a loop until all layers have been popped. The screen layer structure does not have its slindx property cleared or modified when the layer is poppped.
The screen layer structure is defined by //os/s/:screen.s and consists of the following fields:
field | index | size | Description |
---|---|---|---|
sldraw | 0 | 2 | Screen layer draw routine vector |
slmous | 2 | 2 | Mouse event handler vector |
slkcmd | 4 | 2 | Keyboard command event handler vector |
slkprt | 6 | 2 | Printable key event handler vector |
slindx | 8 | 1 | Index of this layer on the stack |
Context Drawing System
The context drawing system is a means to get characters laid out in a user interface in an abstracted way that makes the logic much more straightforward. Characters are output one at a time by calling the ctxdraw routine. However, how the character gets transformed and where it gets put in memory is based upon a context structure. A separate context is maintained for each screen layer, or screen layer's backing buffer. The context has pointers to character memory and corresponding color memory, and has information about the dimensions of the buffer and a viewing rectangle within that buffer. The viewing rectangle can also have top and left scroll offsets.
The scroll offsets are, usually, not used by manual draw routines. The Toolkit classes backend their drawing on the context drawing system. Certain classes, such as the TKSBar, TKView and its coordinating subclass TKScroll, manipulate the draw context's scroll offsets, and pay attention to the scroll offsets in order to efficiently redraw and dispatch events to their child views. This is a complex subject and is covered in an advanced topic, Context drawing found later in this guide.
setlrc
Purpose | Set the draw context's local row and column. |
---|---|
Module offset | 12 |
Communication registers | X, Y, Status |
Preparatory routines | setctx, setdprops |
Stack requirements | 2 bytes |
Zero page usage | $39, $3a, $3b, $3c, $3d, $3e |
Registers affected | A, X, Y |
Input parameters |
C → Clear to specify row RegWrd → 16-bit row number C → Set to specify column RegWrd → 16-bit column number |
Output parameters | None |
Description: This routine is used to specify the local row and column as the starting coordinates for drawing. Two preparatory steps must be taken before calling setlrc. Establish the draw context by calling setctx in the Toolkit KERNAL module. Then call setdprops to set the drawing properties and the direction of cursor travel. After making these two preparatory steps, then call setlrc twice; First with the carry clear and a 16-bit row number in a RegWrd. Second with the carry set and a 16-bit column number in a RegWrd. Note that setlrc must set the row number first and then the column number.
After these routines have been called, calls to ctxdraw are valid. Subsequent calls to setdprops may be made to change the color and/or character conversion options without needing to call setlrc again. This allows the color or conversion options to change mid-draw, and continue on drawing from the current cursor position. However, if setdprops is used to change the direction of cursor travel, it is then imperative to reset the local row and column with two more calls to setlrc.
The row and column index are described as local because they are the coordinates into a large (65536 by 65536) virtual canvas, defined by the origin and scroll offsets of the draw context. The local row and column are not an absolute offset into the layer buffer and bear no relationship at all to coordinates on the screen.
It is not necessary for the layer buffer to be as large in memory as the virtual coordinate system allows. For example, an algorithm that generates a pattern could be used for drawing. By setting the local row and column to some very large numbers, and then setting the scroll left and top offsets such that the local row and column fall within the visible rectangle, only the size of the visible rectangle needs to have representation in the layer buffer in memory. The algorithm could then draw the appropriate generated values for those row and column offsets. All the intermediate rows and columns, which would overflow the Commodore 64's total memory capacity, are only virtual, i.e., they are capable of being generated algorithmically, but don't have permanent representation in memory.
Here is an example of using setlrc to set the local row and column both to 0, with the preparatory routines called first.
setdprops
Purpose | Set the draw context's properties. |
---|---|
Module offset | 15 |
Communication registers | X, Y |
Preparatory routines | setctx |
Stack requirements | 2 bytes |
Zero page usage | $39, $3a, $3b, $3c, $3d, $3e |
Registers affected | A, X, Y |
Input parameters |
X → Draw context flags Y → Color code |
Output parameters | None |
Description: This routine is used to specify the draw context's properties. It is necessary to establish the the current draw context first, by calling setctx in the Toolkit KERNAL module. Then call setdprops to set the drawing properties. Load the draw context flags into the X register. Load the draw color into the Y register. Call setdprops.
One of the draw context flags is the direction of cursor travel. When the flag is set, the cursor travels horizontally to the right after each call to ctxdraw. When the flag is clear, the cursor travels vertically down after each call to ctxdraw. After setting the cursor direction, it is necessary to use setlrc to establish the start coordinates. Afterwards, calls the setdprops can be used to change the color and the other draw properties, and as long as the direction of cursor travel is not changed, it is not necessary to call setlrc again. This allows, for example, to draw a row of characters while changing their color along the way.
Here is an example of using setdprops to set the draw properties, with the preparatory routine called first.
The draw properties are defined in //os/s/:ctxdraw.s and the color codes are defined in //os/s/:colors.s. It is also possible, and recommended if applicable, to read the color code from the current theme. The theme's base address is tkcolors which is defined by //os/s/:ctxdraw.s and the theme elements are defined by //os/s/:ctxcolors.s.
Flag | Value | Description |
---|---|---|
d_crsr_h | %00000001 | The draw cursor travels right |
d_petscr | %01000000 | Convert characters from PETSCII to screencodes |
d_revers | %10000000 | Reverse the screencode |
Conversion from PETSCII to screencode is done first, and then reversing the screencode is done by flipping bit 7.
ctxclear
Purpose | Clear the draw context by filling with a single character. |
---|---|
Module offset | 18 |
Communication registers | A |
Preparatory routines | setctx, setdprops |
Stack requirements | 4 bytes |
Zero page usage | $39, $3a, $3b, $3c, $3d, $3e |
Registers affected | A, X, Y |
Input parameters | A → Fill character |
Output parameters | None |
Description: This routine is used to clear the draw context by filling it with a single character. In preparation the draw context must be established by calling setctx in the Toolkit KERNAL module, and the draw properties must be set by calling setdprops. Then load the fill character into the accumulator and call ctxclear. The fill character is modified by the draw properties.
Using ctxclear is much more efficient than using ctxdraw in a set of nested loops with calls to setlrc to move through the rows. ctxclear only clears the visible area of the layer buffer as defined by the draw context.
ctxdraw
Purpose | Output a character according to the draw context configuration. |
---|---|
Module offset | 21 |
Communication registers | A |
Preparatory routines | setctx, setdprops, setlrc |
Stack requirements | 4 bytes |
Zero page usage | $39, $3a, $3b, $3c, $3d, $3e |
Registers affected | A |
Input parameters | A → Character to draw |
Output parameters |
C ← Set if no more valid draws can be made C ← Clear if more valid draws can be made |
Description: This routine is used to send characters to be drawn to the layer buffer, according to the configuration of the draw context.
All of the preparatory routines must be called first. The draw context must be established by calling setctx in the Toolkit KERNAL module. The draw properties and cursor direction must be set with setdprops, and the starting row and column coordinates must be set with two calls to setlrc. Then, ctxdraw can be called repeatedly. Load a character to be drawn into the accumulator. Call ctxdraw. The character is transformed by the draw properties, output to the layer buffer according to the draw context, and the draw cursor is moved either one column to the right or one row down. The state of the carry indicates whether the draw cursor is still within the visible clipping rectangle. If the carry comes back set, the draw cursor has left the visible area and all subsequent calls to ctxdraw will be clipped. If the carry comes back clear, the next call to ctxdraw will output a character to the layer buffer.
The X and Y registers are not affected by calls to ctxdraw. This allows them to be used as looping and source buffer indexes.
ctx2scr
Purpose | Copy the layer buffer as defined by the draw context to the screen buffer. |
---|---|
Module offset | 24 |
Communication registers | X, Y |
Preparatory routines | setctx |
Stack requirements | 4 bytes |
Zero page usage | $f5, $f6 |
Registers affected | A, X, Y |
Input parameters |
X → Left start column of screen buffer (signed) Y → Top start row of screen buffer (unsigned) |
Output parameters | None |
Description: This routine is used to composite a layer buffer into the screen buffer. The current draw context must first be established by calling setctx in the Toolkit KERNAL module. Load the origin column in the X register. Load the origin row into the Y register. Call ctx2scr. The top left corner of the layer buffer is transcribed into the screen buffer at the origin point, and extends rightward and downward from that origin point.
The layer buffer's dimensions and the screen buffer's dimensions do not have to be the same. If the layer buffer is narrower than the screen, then it gets drawn inside a rectangle inside the screen. The X register, which specifies the origin column, is a signed int. This allows the starting column to be negative. The layer buffer is transferred into the screen buffer with its left edge clipped off the left side of the screen. If the origin column plus the layer width are wider than the screen, the right edge of the layer buffer gets clipped off the right side of the screen. The Y register, which specifies the origin row, is unsigned. A layer buffer cannot be composited to partially above the screen. If the origin row plus the layer buffer height is greater than the height of the screen. The bottom edge of the layer buffer gets clipped off the bottom side of the screen.
An Application's layer buffer is usually (though not necessarily) the size of the screen (40x25), and composited to the screen buffer at origin row 0, column 0. A Utility's layer buffer is usually smaller than the size of the screen, and gets composited into the screen buffer at varying offsets, allowing the Utility panel to be moved around the screen. The Utility Framework uses ctx2scr under the hood to composite the Utility's layer buffer to the screen buffer. This explains why Utilities may be dragged off the left, right and bottom edges of the screen, but cannot be dragged off the top edge of the screen.
The ctx2scr routine treats the screencode $60 (decimal 96) as transparent. This character has the same appearance as screencode $20 (decimal 32). No pixels are set for either one. When ctx2scr is copying characters from a layer buffer to the screen buffer and it reads a $60, it skips it and moves on to the next. This results in the characters already in the screen buffer remaining visible through the layer buffer that was composited above. This allows Utilities to have a non-rectangular shape. This is most evident in their short title bars. Transparent characters can be exploited in other creative ways to cut holes in the middle of Utility panels and other composited layers.
Drawing Helper Routines
The following is a collection of routines that assist in various drawing related tasks.
redraw
Purpose | Initiates a redraw cycle. Called by the main event loop. |
---|---|
Module offset | 27 |
Communication registers | None |
Stack requirements | 4 bytes + |
Zero page usage | $17, $18, Custom |
Registers affected | A, X, Y |
Input parameters | None |
Output parameters | None |
Description: This routine is used to recomposite the screen buffer. It uses the dirty layers flags set by markredraw to determine at which layer the redrawing has to begin. It then calls the redraw vector of each screen layer, starting at the lowest screen layer, and working its way up.
Most calls to the redraw routine of a layer result in the layer's buffer being recomposited to the screen buffer using ctx2scr. However, if a process has independently tracked that its layer buffer is dirty, it is this routine that usually triggers the process to update its layer buffer. Waiting on this routine before redrawing a dirty layer is the best way to minimize the number of redraws. Unnecessary redrawing of a layer buffer is usually a waste of CPU time.
The amount of stack and the zero page usage cannot be determined, because this routine makes calls through the redraw vectors of screen layers. These perform arbitrary amounts of work.
It is almost never necessary to manually call redraw. Redraw is called automatically as the last step of the main event loop. The Utility Framework calls redraw manually if the Utility loaded custom icons. It pops the Utility layer, then calls redraw so the Utility layer disappears from the screen, then it restores the character set. This prevents the restored characters appearing on the Utility layer before it gets a chance to be removed from the screen.
scrrow
Purpose | Get a pointer to the start of a row of a screen buffer. |
---|---|
Module offset | 30 |
Communication registers | A, X, Y |
Stack requirements | 2 bytes |
Zero page usage | $0c |
Registers affected | A, X, Y |
Input parameters |
A → Row index RegPtr → Pointer to a screen buffer (40 columns) |
Output parameters | RegPtr ← Pointer to start of row |
Description: This routine is used to fetch a pointer to the start of a row, within a 40 column screen buffer. Load the row number (row index 0 to 24) into the accumulator. Load a pointer to the start of a screen buffer (1000 bytes, 40 by 25) into a RegPtr. Call scrrow. A RegPtr is returned with pointer to the start of that row.
loadclr
Purpose | Clears screen memory of the area that is drawn by the loader's spash screen. |
---|---|
Module offset | 33 |
Communication registers | None |
Stack requirements | 4 bytes |
Zero page usage | None |
Registers affected | A, X |
Input parameters | None |
Output parameters | None |
Description: This is a very specific routine that is only called under a very specific circumstance. When loading an Application, the Loader reads the Application's icon, which it installs into part of the custom character set. It displays the icon and the name of the Application on a standard loading splash screen. Typically this splash screen is displayed the whole time while the Application's init routine is running.
If the Application loads custom icons which overwrite the character set that is currently displayed on the splash screen, this corrupts the icon in an unsightly way. The best way to handle this situation is to defer loading the custom icons until the very end of the init routine. Immediately prior to calling loadicns call loadclr first. This removes the icon and text from the loading spash screen. The screen displays as plain white for just a brief moment while the custom icons are loaded in. Then the Application's screen layer gets drawn to the screen, possibly with the new icons being displayed.
loadicns
Purpose | Load and install a table of icons from the system's icon library. |
---|---|
Module offset | 36 |
Communication registers | X, Y |
Stack requirements | 6 bytes + |
Zero page usage | $1a, $1b, $1c, $1d, $1e, $1f, $20, $21, $22, $23, $fb, $fc, $fd, $fe |
Registers affected | A, X, Y |
Input parameters | RegPtr → Pointer to an icon table |
Output parameters | None |
Description: This routine is used to load and install a set of 8x8 pixel icons from the C64 OS icon library. (Note: The "icon library" is a standardized collection of icon bitmap files, it does not refer to a shared library of code.) The icons are installed into the character set.
If the icon table is located within Utility memory space (i.e., from $e000 to $ff40) loadicns automatically messages the Application that there is a change to the character set, and passes a pointer to the icon table in the messsage. The Application can respond by hiding or substituting characters that it uses until it gets another message indicating that the character set has been restored. The Application must not disrupt zero page bytes $22 or $23 while responding to the message. If it needs them, it should back them up and restore them before returning.
This routine temporarily allocates, and then later frees, one page of memory using pgalloc.
The icon table consists of rows of three bytes, with a final $00 terminator byte. Each row has a character set index, a EOR bitmask, and an icon constant. The icon constants are defined by //os/s/:icons.s.
Charset index | EOR bitmask | Icon constant |
---|---|---|
$77 | $00 | icn_5disk |
$78 | $00 | icn_usb |
$f8 | $ff | icn_usb |
$00 |
The table above loads the 5.25" disk icon (non-reversed) to character $77, the usb icon (non-reversed) to character $78, and lastly, it loads the same usb icon but reversed to character $f8. It then encounters the $00 terminator byte and stops loading icons.
C64 OS dedicates 18 characters in the character set for general purpose use. The first set of 9 are at the end of block 4. The second set of 9 are at the end of block 8. These have corresponding character set indexes, except that the second set have their high bit set. When drawing, using ctxdraw with the d_revers flag set in the draw context, the output character is EOR'd with %10000000. Therefore, in any context where an icon may be drawn both reversed and non-reversed, the icon should be loaded into the two corresponding character indexes, but using the $ff bitmask when loading into the index with the high bit set. Such as the example above with the usb icon in $78 and the usb icon in $f8 with the $ff bitmask to reverse it.
Character indexes reserved for loading custom icons.
$77, $78, $79, $7a, $7b, $7c, $7d, $7e, $7f (last 9 characters of block 4) $f7, $f8, $f9, $fa, $fb, $fc, $fd, $fe, $ff (last 9 characters of block 8)
Example code showing how to load icons from the icon library using an icon table.
Private APIs
The screen KERNAL module contains one private API. A private API is a routine whose implementation is in flux, is not intended to be called by third party code. Private APIs are mentioned, but not documented.
copybufs
This routine is us to move data between the screen buffer and screen memory, while taking the position of the split into account. It is accelerated by the REU in the REU version of this module (//os/kernal/:screen.reu.o)
KERNAL Modules in Alphabetical Order
KERNAL Modules in Module Lookup Table Order
Return to Using the KERNAL → KERNAL Modules
Table of Contents
This document is subject to revision updates.
Last modified: Apr 20, 2023