{"id":1428,"date":"2020-09-02T23:46:43","date_gmt":"2020-09-02T21:46:43","guid":{"rendered":"https:\/\/www.pagetable.com\/?p=1428"},"modified":"2020-09-02T23:46:43","modified_gmt":"2020-09-02T21:46:43","slug":"inside-geowrite-2-screen-recovery","status":"publish","type":"post","link":"https:\/\/www.pagetable.com\/?p=1428","title":{"rendered":"Inside geoWrite \u2013 2: Screen Recovery"},"content":{"rendered":"<p>In the series about the internals of the geoWrite WYSIWYG text editor for the C64, this article discusses how the app manages to extend its usable RAM by 5 KB using a custom screen recovery solution.<\/p>\n<p><img decoding=\"async\" src=\"docs\/geowrite\/geowrite_2_scrrecovery.gif\" alt=\"\" \/><\/p>\n<h2 id=\"article-series\">Article Series<\/h2>\n<ol>\n<li><a href=\"https:\/\/www.pagetable.com\/?p=1425\">The Overlay System<\/a><\/li>\n<li><strong>Screen Recovery<\/strong> \u2190 this article<\/li>\n<li><a href=\"https:\/\/www.pagetable.com\/?p=1436\">Font Management<\/a><\/li>\n<li><a href=\"https:\/\/www.pagetable.com\/?p=1442\">Zero Page<\/a><\/li>\n<li><a href=\"https:\/\/www.pagetable.com\/?p=1449\">Copy Protection<\/a><\/li>\n<li><a href=\"https:\/\/www.pagetable.com\/?p=1460\">Localization<\/a><\/li>\n<li><a href=\"https:\/\/www.pagetable.com\/?p=1471\">File Format and Pagination<\/a><\/li>\n<li><a href=\"https:\/\/www.pagetable.com\/?p=1481\">Copy &amp; Paste<\/a><\/li>\n<li><a href=\"https:\/\/www.pagetable.com\/?p=1490\">Keyboard Handling<\/a><\/li>\n<\/ol>\n<h2 id=\"screen-recovery\">Screen Recovery<\/h2>\n<p>Any graphical user interface that allows overlapping items has to deal with the problem of recovery: When dialogs and pull-down menus are shown, they cover parts of the screen, and when they are dismissed, the underlying UI needs to be revealed again.<\/p>\n<p>There are two basic approaches to this:<\/p>\n<ol>\n<li>\n<p>When the dialog or menu is dismissed, the system calls the same text or image rendering code again that drew it in the first place.<\/p>\n<\/li>\n<li>\n<p>Before the dialog or menu is drawn, the system saves the pixel data that will be overwritten, and after the item is dismissed, the saved pixels get written back onto the screen.<\/p>\n<\/li>\n<\/ol>\n<p>The latter solution requires additional memory, but is a lot faster and doesn&rsquo;t create a potentially jarring redraw.<\/p>\n<h2 id=\"screen-recovery-in-geos\">Screen Recovery in GEOS<\/h2>\n<p>GEOS uses the &ldquo;restore the pixel data&rdquo; approach, but with a twist.<\/p>\n<p>Let&rsquo;s look at the GEOS memory layout again:<\/p>\n<pre><code>-----------------------------------------\n$0000  Zero page, stack, system variables\n-----------------------------------------\n$0400\n\n\n\n\n       Application memory\n\n\n\n\n\n-----------------------------------------\n$6000\n       Background bitmap\n\n-----------------------------------------\n$8000  System buffers and variables\n-----------------------------------------\n$9000  Disk driver\n-----------------------------------------\n$A000\n       Screen bitmap\n\n-----------------------------------------\n$C000  GEOS KERNAL\n\n\n-----------------------------------------\n<\/code><\/pre>\n<p>The 8 KB of 320&#215;200 monochrome bitmap data resides at $A000-$BF40. In addition, GEOS statically reserves a whole 8 KB just for screen recovery: The &ldquo;background bitmap&rdquo; at $6000-$7F40 allows GEOS to recover the whole screen area.<\/p>\n<h3 id=\"\u00a0imprint-and-recover\">\u00a0Imprint and Recover<\/h3>\n<p>This background bitmap is really just another (invisible) framebuffer with the same layout as the actual screen bitmap. When saving pixels, they get copied from the screen (&ldquo;foreground&rdquo;) bitmap to the same offset in the background bitmap and vice versa.<\/p>\n<p>These two functions are the core of the API:<\/p>\n<ul>\n<li><code>ImprintRectangle<\/code> \u2013\u00a0copy rectangle from fg bitmap to bg bitmap<\/li>\n<li><code>RecoverRectangle<\/code> \u2013\u00a0copy rectangle from bg bitmap to fg bitmap<\/li>\n<\/ul>\n<p>While applications are free to use the API this way, the system-suggested way is actually different.<\/p>\n<h3 id=\"display-buffering\">Display Buffering<\/h3>\n<p>When using the core GEOS API, it&rsquo;s not actually necessary to save the pixels (<code>ImprintRectangle<\/code>) before overwriting them. The background buffer already contains a copy!<\/p>\n<p>That&rsquo;s because all graphics code can be configured to either draw to the foreground bitmap, the background bitmap, or both.<\/p>\n<p>To calculate the offset in the bitmap of a y coordinate, all internal drawing code calls <code>GetScanLine<\/code>. This function usually returns two pointers in virtual registers r5 and r6:<\/p>\n<ul>\n<li>r5: pointer to the pixel line in the <em>foreground<\/em> screen<\/li>\n<li>r6: pointer to the pixel line in the <em>background<\/em> screen<\/li>\n<\/ul>\n<p>Any code in the GEOS drawing library then stores the pixel data in both the locations pointed to by r5 and r6:<\/p>\n<pre><code>    [...]\n    sta (r5),y\n    sta (r6),y\n<\/code><\/pre>\n<p>For as little as an extra 6 clock cycles for every byte to be stored (which is 8 pixels), the drawing code stores it into both bitmaps, so there is usually no need to copy data from the foreground to the background bitmap.<\/p>\n<p>Now when the system draws a menu or a dialog, it only draws it into the foreground buffer. That&rsquo;s because the <code>GetScanLine<\/code> call can actually return different kinds of pointers depending on the global system variable <code>dispBufferOn<\/code>:<\/p>\n<table>\n<thead>\n<tr>\n<th> <code>dispBufferOn<\/code>        <\/th>\n<th> r5            <\/th>\n<th> r6            <\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td> <code>%11000000<\/code> (default) <\/td>\n<td> fg screen ptr <\/td>\n<td> bg screen ptr <\/td>\n<\/tr>\n<tr>\n<td> <code>%10000000<\/code>           <\/td>\n<td> fg screen ptr <\/td>\n<td> fg screen ptr <\/td>\n<\/tr>\n<tr>\n<td> <code>%01000000<\/code>           <\/td>\n<td> bg screen ptr <\/td>\n<td> bg screen ptr <\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>By only setting <em>one<\/em> of bits 6 and 7, all drawing code can effectively be instructed to only draw to the foreground or the background bitmap. In this case, the two <code>sta<\/code> instructions will just store the pixel data to the same location twice.<\/p>\n<p>The system default is to draw everything to both bitmaps \u2013\u00a0except menus and dialogs, which are only ever drawn to the foreground bitmap. There is therefore no need to call <code>ImprintRectangle<\/code>: All that the built-in code has to do is call <code>RecoverRectangle<\/code> on the rectangle that it overwrote.<\/p>\n<h2 id=\"screen-recovery-in-geowrite\">Screen Recovery in geoWrite<\/h2>\n<p>geoWrite is a very complex application that is very tight on memory, so it was designed to reclaim as much as possible of the 8 KB normally used for screen recovery. Text rendering is very slow, so redrawing the page as a recovery strategy is out of the question.<\/p>\n<p>Instead, geoWrite stores the saved pixels more efficiently: The buffer only really needs to be as big as is required for the largest rectangle ever saved while in the app. And for geoWrite, that&rsquo;s dialogs, which are 200&#215;104 pixels. So that&rsquo;s 2600 bytes instead of 8000 bytes.<\/p>\n<p>The code to save a screen rectangle is called with the following arguments in virtual registers:<\/p>\n<ul>\n<li><code>r1<\/code>: the pointer to the recovery buffer<\/li>\n<li><code>r2L<\/code>: x \u00f7 8<\/li>\n<li><code>r2H<\/code>: y<\/li>\n<li><code>r3L<\/code>: width \u00f7 8<\/li>\n<li><code>r3H<\/code>: height<\/li>\n<\/ul>\n<p>X coordinates have to be divisible by 8, so that the pixels are at byte boundaries.<\/p>\n<h3 id=\"the-code\">The Code<\/h3>\n<p>The following is the main code, slightly edited for readability. It iterates over all lines of the rectangle and, on save, appends the bitmap bytes to the array of saved data. On recover, it does the opposite.<\/p>\n<pre><code>@loop1: ldx     r2H         ; y coord\n        jsr     GetScanLine ; r5 := ptr to bitmap\n        lda     r2L         ; x coord \/ 8\n        asl     a\n        asl     a\n        asl     a           ; * 8\n        bcc     :+\n        inc     r5H\n:       tay\n        MoveB   r3L, r4L    ; copy byte count\n@loop2: bit     r4H         ; save or recover?\n        bpl     @1\n        jsr     @recv\n        bra     @2\n@1:     jsr     @save\n@2:     IncW    r1          ; advance buffer pointer\n        add     #8          ; account for quirky VIC-II memory layout\n        bcc     :+\n        inc     r5H\n:       tay\n        dec     r4L         ; dec byte counter\n        bne     @loop2      ; loop for horizontal bytes\n        inc     r2H\n        dec     r3H         ; dec line counter\n        bne     @loop1      ; loop for lines\n        rts\n<\/code><\/pre>\n<p>This is the subroutine for copying a byte from the screen to the buffer&hellip;<\/p>\n<pre><code>@save:  lda     (r5),y      ; read screen byte\n        tax\n        tya\n        pha                 ; save offset\n        ldy     #0\n        txa\n        sta     (r1),y      ; write into buffer\n        pla                 ; restore offset\n        rts\n<\/code><\/pre>\n<p>&hellip;and the code for copying a byte from the buffer to the screen:<\/p>\n<pre><code>@recv:  tya                 ; save offset\n        pha\n        ldy     #0\n        lda     (r1),y      ; read from buffer\n        tax\n        pla\n        tay                 ; restore offset\n        txa\n        sta     (r5),y      ; write onto screen\n        tya\n        rts\n<\/code><\/pre>\n<h3 id=\"the-tables\">The Tables<\/h3>\n<p>geoWrite uses a table with the buffer pointer, x, y, width and height values for each use case, so only an offset within the table has to be passed:<\/p>\n<pre><code>;              r1L\/r1H  r2L  r2H  r3L  r3H\n;                ptr      x    y wdth hght\nscrrecvtabs:\nscrrecvtab_geos:\n        .word   MEM_SCRREST\n        .byte             0,  15,  10, 128\nscrrecvtab_file:\n        .word   MEM_SCRREST\n        .byte             3,  15,   7, 100\n[...]\n<\/code><\/pre>\n<p>The first item in the table is for saving and restoring the rectangle under the &ldquo;geos&rdquo; menu, and so on.<\/p>\n<p><code>MEM_SCRREST<\/code> is the address of the start of the recover buffer. The buffer pointer isn&rsquo;t always <code>MEM_SCRREST<\/code> though:<\/p>\n<pre><code>scrrecvtab_font:\n        .word   MEM_SCRREST\n        .byte            17,  15,  10, 114\nscrrecvtab_fontsize:\n        .word   MEM_SCRREST + 1408\n        .byte            26,  15,   9, 114\n<\/code><\/pre>\n<p>This is because the font menu has a sub-menu for the point size:<\/p>\n<p><img decoding=\"async\" src=\"docs\/geowrite\/font_menu.png\" alt=\"\" \/><\/p>\n<p>When the font menu is open, the rectangle covered by it is already saved in the buffer. The rectangle under the point size menu has to be saved in the area following the already occupied data.<\/p>\n<h3 id=\"memory-layout\">Memory Layout<\/h3>\n<p>Conveniently, the RAM area for the background bitmap immediately follows the application&rsquo;s memory. Apps are free to just not use the background bitmap at all. By setting <code>dispBufferOn<\/code> to <code>%10000000<\/code> globally, the system will never touch the $6000-$7FFF area.<\/p>\n<p>Here&rsquo;s a mostly to scale overview of the geoWrite memory map compared to the GEOS default:<\/p>\n<pre><code>       GEOS Default                    geoWrite\n-------------------------      -------------------------\n$0400                                              $0400\n\n                                     Main Code\n\n\n       Application memory      -------------------------\n                                     Overlay Code  $3244\n                               -------------------------\n                                                   $4310\n                                     Page Data\n\n-------------------------      -------------------------\n$6000                                Font Heap     $5E68\n       Background bitmap       _________________________\n                                     Recovery Data $75D8\n-------------------------      -------------------------\n<\/code><\/pre>\n<p>geoWrite puts the recovery buffer at the topmost 2600 bytes of the $0400-$8000 window that is available to the application if it doesn&rsquo;t use the background screen.<\/p>\n<p>The reason for this location is because the range $7900-$7F40 is where GEOS requires the printer driver to be loaded once the application wants to print. It overlaps the background screen on purpose: As long as the application is not printing, so no space for the driver is wasted. And once it is printing, it just can&rsquo;t use the background buffer any more. The same is true in geoWrite&rsquo;s model.<\/p>\n<h3 id=\"triggers\">Triggers<\/h3>\n<p>Aside from setting <code>dispBufferOn<\/code> to just the foreground, using one&rsquo;s own save\/recover logic requires the app to make sure the save and recover code gets called at the right times.<\/p>\n<p>For dialogs, this is easy: Apps have to explicitly show them by calling the <code>doDlgBox<\/code> API, so before calling it, geoWrite saves the respective rectangle, and once the API returns, it recovers it.<\/p>\n<p>For menus, it&rsquo;s more tricky. The application has to trigger on the open event of a sub-menu.<\/p>\n<p>Usually, the data structure of a menu for the <code>doMenu<\/code> API contains pointers to sub-menu data structures, forming the complete menu tree, like this:<\/p>\n<pre><code>menu_main:\n        .byte   0, 14, 0, 191 ; pos &amp; size\n        .byte   HORIZONTAL | UN_CONSTRAINED | 3 ; #items\n\n        .word   txt_geos  ; string \"geos\"\n        .byte   SUB_MENU  ; =below is a sub-menu ptr\n        .word   menu_geos ; sub-menu data structure\n\n        .word   txt_file\n        .byte   SUB_MENU\n        .word   menu_file\n\n        .word   txt_edit\n        .byte   SUB_MENU\n        .word   menu_edit\n<\/code><\/pre>\n<p>This way, an application can describe a complete menu tree with just data structures and no code.<\/p>\n<p>But instead of a pointer to a sub-menu (code <code>SUB_MENU<\/code>), the data structure can alternatively contain a pointer to <em>code<\/em> that returns a sub-menu data structure (code <code>DYN_SUB_MENU<\/code>):<\/p>\n<pre><code>        .word   txt_geos     ; string \"geos\"\n        .byte   DYN_SUB_MENU ; =below is a code ptr\n        .word   callbackGeos ; code, called on sub-menu open\n<\/code><\/pre>\n<p>In this example, whenever the &ldquo;geos&rdquo; item is clicked, the <code>callbackGeos<\/code> function is called, which saves the rectangle that will be covered by the &ldquo;geos&rdquo; sub-menu, and returns a pointer to the sub-menu to be presented (edited for clarity):<\/p>\n<pre><code>callbackGeos:\n        ldx     #scrrecvtab_geos-scrrecvtabs\n        stx     a2L\n        jsr     screenSave\n        LoadW   r0, menu_geos\n        rts\n<\/code><\/pre>\n<p>For the recovery of the rectangle, there is actually a GEOS system variable supporting this use case: If the application sets the <code>RecoverVector<\/code> pointer to anything but 0, closing a menu will call that code instead of doing its own <code>RecoverRectangle<\/code>-based recovery.<\/p>\n<p>This is where it points in geoWrite (again edited for clarity):<\/p>\n<pre><code>appRecoverVector:\n        ldx     a2L\n        jsr     screenRecover\n        rts\n<\/code><\/pre>\n<p>The function reuses the previously set offset in the screen recovery table (in virtual register a2L) for buffer location, the origin and size of the rectangle.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>Berkeley Softworks wrote the GEOS operating system as well as many major applications like geoWrite, geoPaint, geoPublish and geoCalc, yet they implemented two different methods for screen recovery: The system way of doing it wastes space, but is very simple to use. And the way they do it in the applications is very much tied to the specific use case, but very optimized.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the series about the internals of the geoWrite WYSIWYG text editor for the C64, this article discusses how the app manages to extend its usable RAM by 5 KB using a custom screen recovery solution. Article Series The Overlay System Screen Recovery \u2190 this article Font Management Zero Page Copy Protection Localization File Format &#8230; <a title=\"Inside geoWrite \u2013 2: Screen Recovery\" class=\"read-more\" href=\"https:\/\/www.pagetable.com\/?p=1428\" aria-label=\"Read more about Inside geoWrite \u2013 2: Screen Recovery\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,5,41,8,15,22],"tags":[],"class_list":["post-1428","post","type-post","status-publish","format-standard","hentry","category-2","category-archeology","category-c64","category-commodore","category-geos","category-operating-systems"],"_links":{"self":[{"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/posts\/1428","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1428"}],"version-history":[{"count":0,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=\/wp\/v2\/posts\/1428\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.pagetable.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1428"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1428"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.pagetable.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1428"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}