68k ScrnBase and its alternate screen address

Relating to a 68k application

Mu0n

Active Tinkerer
Oct 29, 2021
609
560
93
Quebec
www.youtube.com
Let's get it over with: paging @Crutch since I know he has tips inside his mind at the ready, with no need to consult any reference, like always.

Goal: I want to prepare an alternate screen buffer graphic a few seconds in advance so I can instantly 'screen flip' at a precise time when needed.

Motivation: under a modern day emulator, DrawPicture is so fast that it gives the impression it's instantenous - when you need it done, you call it and get it. When you bring the same program on a real Mac Plus, you obviously see a split second lag, causing problem with an application where I need to keep precise timing for a few minutes while needing to occasionally change the graphics on screen.

In the first volumes of Inside Macintosh, we get this tidbit of info:
1673907515607.png


Various ScrnBase (screen 'base' adress of the first byte of memory of the top left screen bitmap, and its alternate screen buffer, which seems 0x8000 lower than the base. Example: for a 4 MB equipped Mac Plus, its ScrnBase is at $3FA700 and its alternate address is at $3F2700).

ScrnBase is a global symbol recognized by THINK C and acts as expected. I've successfully pointed a char * to it, and started filling in the whole screen (all 21888 bytes) with a fixed byte, effectively doing the same thing as filling in a pattern-like graphic everywhere.

What do I need to do to is effectively flip flop between the 2 buffers:
1) prep the alternate screen
2) make the Mac point to it
3) prep the main screen buffer
4) make the Mac point back to main
5) goto 1)

I've actually checked out the source code of Megaroids (made under MegaMax C) from this physical copy of this book:
Programming C on the Macintosh (1986) (from Terry A. Ward)
1673907960483.png

The code does keep pointers to both buffers and seems to flip between them, but I haven't established if it's continually used on the fly, for fluid arcade like movement, or if it's to prepare a main game menu screen in advance. There's still something I don't understand about the whole technique. Here is where I have problems to come to grip with it:

Problem: If it's already too long to prepare a frame of game animation within a 1/60th of a second before the screen has to be redrawn, why would an alternate screen buffer be any useful? Say it takes 1.2 ticks (1.2 * 1/ 60 in seconds) to prep a whole screen, it's gonna take the same time to prep the alternate buffer and you just have to accept to draw at every 2 frames (every 2 ticks, or every 2 / 60 = 1 / 30 s). Just prep an offscreen bitmap for a duration that's under 2 frames and copy it onto the main screen before you hit the 2nd screen retrace and voilà! No need to mess with ScrnBase and Alternate Screen.

In my case, I never need to do things so fast, but I thought it'd be an interesting question for later purposes. There's easily 3+ s between screen changes, so 'screen flipping' might make sense in my use case.

Question: The book mentions a VIA address at 0xEFFFFE that controls which buffer is being used. How do you use it? Do I have to invalidate the screen to use it? Think C doesn't seem to have a global variable associated with it, I think? It's ok if the code only runs under a specific subclass of compact Macs. I don't care! That technique wasn't used much beyond 1984-1986 iirc. How do the pixels ACTUALLY change? Just part of the vertical retrace, automagically? No need to InvalidateRect or something like that?
 
Last edited:

Mu0n

Active Tinkerer
Oct 29, 2021
609
560
93
Quebec
www.youtube.com
This quick code seems to work under mini-vMac, and I assume (!!) on a real Mac Plus as well.
I just used the swap_screens() function of the book on p.301

Still got to find the best way to draw something onto that location in memory. Do I use *viaThing to redirect qd.thePort->portBits.baseAddr?
Or prep a large offscreen GrafPort with several images in advance and just BlockMove them into place?


void main() { EventRecord wutup; PicHandle img1, img2; Ptr mainScreen = ScrnBase; Ptr altScreen = ScrnBase - 0x8000; Ptr ultraPen; char *viaThing = (char *) 0xEFFFFE; Rect r; int i; InitMacStuff(); WholeScreen(white, false); SetRect(&r,0,0,512,300); ultraPen = altScreen; for(i=0 ; i<21888; i++) { *ultraPen = 0xA8; ultraPen++; } while(!Button()); //Flip the VIA to alternate screen buffer *viaThing &= ~64; FlushEvents(everyEvent,0); while(true) { GetNextEvent(everyEvent, &wutup); if(wutup.what == keyDown) { // Flip the VIA back to main screen buffer or it'll be hard times in the Finder *viaThing |= 64; ShowMyMenuBar(); ExitToShell(); } } }
 
Last edited:

YMK

Active Tinkerer
Nov 8, 2021
358
285
63
How do the pixels ACTUALLY change?

The memory doesn't trade places. Your old screen should still be intact.

The video logic flips an address bit and pumps out data from the second half of VRAM.

In the Plus's case, this is address bit 15.

I imagine if you don't want screen shearing, you should do this in the vblank period.
 

Mu0n

Active Tinkerer
Oct 29, 2021
609
560
93
Quebec
www.youtube.com
The memory doesn't trade places. Your old screen should still be intact.

The video logic flips an address bit and pumps out data from the second half of VRAM.

In the Plus's case, this is address bit 15.

I imagine if you don't want screen shearing, you should do this in the vblank period.

Ok, lemme get this straight.

let's assume X is the number of cycles needed to complete a frame of high speed video game graphics.
let's assume V is the number of cycles between vertical blanks of the screen retrace (V being as long as 1 tick, 1/60 th)
let's assume X is such that X < V

Show the main ScrnBase buffer
Spend a partial tick to copy your offscreen gfx (from a self made BitMap tied to an offscreen GrafPort that will never become a directly visible GrafPort), using CopyBits or BlockMove
Wait until the end of the tick (by having a VBL task reach its completion)
Switch to the alternate screen buffer - can this be directly be put inside the VBL method?
Rinse and repeat the other way around

If X is such that V > X > 2V, then only do switching every other CRT retrace, accept a lower 30 fps.

Did I get that right?
 

YMK

Active Tinkerer
Nov 8, 2021
358
285
63
If X is such that V > X > 2V, then only do switching every other CRT retrace, accept a lower 30 fps.

Right, but it's not a good idea to assume a fixed frame rate and tie your game's physics to frame count.

Better to use tick count for game physics and render as quickly as possible, whether that's 60, 30, 20, etc FPS.

That will allow your game to maintain a steady speed even if it drops frames during busy periods.

Switch to the alternate screen buffer - can this be directly be put inside the VBL method?

I don't see why not.

Spend a partial tick to copy your offscreen gfx (from a self made BitMap tied to an offscreen GrafPort that will never become a directly visible GrafPort), using CopyBits or BlockMove

Do you mean rendering entire screens in memory, then copying them to VRAM or rendering individual sprites directly to VRAM? The latter is probably faster.

The best drawing strategy will depend on the type of game you're writing.
 

Crutch

Tinkerer
Jul 10, 2022
293
228
43
Chicago
Cool! Can’t wait to see what you’re using this for.

To sum up/slightly clarify, to use the alternate screen buffer, you need to do three things:
  • _Launch yourself requesting the alternate screen buffer (I assume you are already doing this since you got your code working), by default it doesn’t exist.
  • To draw on the alternate screen, just SetPort to the GrafPort corresponding to the main screen (thePort is set to this when your app first starts running) and change baseAddr to the address of the alternate screen buffer (ScrnBase - 0x8000). That’s it. (If you draw into some other buffer and BlockMove it there, you are probably wasting precious time if running on a real Mac.)
  • To display the alternate screen, flip the VIA bit (which you are already doing). To be clear, this takes ~zero time and does zero drawing or memory transferage: it literally just sets one bit that tells the hardware “next time you draw the screen, read the video data from a different spot in RAM”.
Here’s some old MacTutor sample code from MacTutor Volume I. It’s in Modula-2 but if we just squint our eyes we can pretend it’s Pascal :)

http://preserve.mactech.com/articles/mactech/Vol.01/01.09/ScreenBuffer/index.html
 
  • Like
Reactions: Mu0n

Mu0n

Active Tinkerer
Oct 29, 2021
609
560
93
Quebec
www.youtube.com
To sum up/slightly clarify, to use the alternate screen buffer, you need to do three things:
  • _Launch yourself requesting the alternate screen buffer (I assume you are already doing this since you got your code working), by default it doesn’t exist.
I don't know what this is and I guess I didn't need it since my stuff works!


To sum up/slightly clarify, to use the alternate screen buffer, you need to do three things:
  • To draw on the alternate screen, just SetPort to the GrafPort corresponding to the main screen (thePort is set to this when your app first starts running) and change baseAddr to the address of the alternate screen buffer (ScrnBase - 0x8000). That’s it. (If you draw into some other buffer and BlockMove it there, you are probably wasting precious time if running on a real Mac.)

To appreciate how screen flipping can truly help me, I'd need to know common quickdraw function timings.

I need help understanding how scenario B helps things:

Scenario A:
Draw as fast as you can on the main screen buffer and keep it between refreshes. If impossible, then draw on an offscreen buffer on odd retraces and copy it over on even retraces and accept 30 fps instead of 60.

Scenario B:
While displaying main, prepare the alternate as fast as you can. When VBL hits, flip the bit to get a full screen costless switch.
Then prepare main while displaying the alternate - however, main is now outdated compared to alternate, so some form of copying to bring main up to speed with alternate has to occur....

This is why I still don't undersand screen flipping in the context of fast moving animations. I get that you can prep very slow and complex designs (full screen if you want!) to give the illusion of a massive instantaneous graphical update at some point, but because of its prep time, you can only do complex things occasionally, not every frame so it begs the question, what was gained for fast moving games?
 
Last edited:

Crutch

Tinkerer
Jul 10, 2022
293
228
43
Chicago
Here’s a better article, but it’s in assembly. http://preserve.mactech.com/articles/mactech/Vol.02/02.06/VideoScreenAnim/index.html

I am pretty sure you do need to relaunch with the request for the alt screen buffer as described here and in the other article. Otherwise you are asking the VIA to have hardware draw from the alt screen buffer — but not asking the Segment Loader to reserve the alt screen buffer. As a result at some point a call to NewPtr or NewHandle will allocate space that overlaps that (unreserved in your case) buffer, and whatever data you put in that pointer/handle will get clobbered by your graphics drawing routines. In other words I think without this relaunch, your code will work fine … until it doesn’t.

Here’s how (as you know) double buffering animation works and why it helps in your scenario B:

1. Draw stuff in a buffer using QuickDraw, CopyBits, etc.
2. CopyBits the buffer to the screen (ideally on a VBL retrace)

The alt screen can prevent you from having to do the second CopyBits at all, much less multiple CopyBitses if you have multiple moving sprites. Of course you have to do step 1, which still takes as long as it takes. But if you are blitting a large-ish bitmaps around, the CopyBits in step 2 can be quite slow. Switching screen buffers reduces that time to 0, with the caveat that you must now keep both screen buffers up to date.
 
Last edited:
  • Like
Reactions: Mu0n

Crutch

Tinkerer
Jul 10, 2022
293
228
43
Chicago
(That’s why the Vanlandingham demo in the second MacTutor article I linked is a perfect use case. It draws a giant spinning beachball offscreen with QuickDraw, which is slow, then on the next retrace after that’s done, it switches the screen buffer, which is instant and saves a super-slow full-screen CopyBits. Repeat and repeat … )
 

YMK

Active Tinkerer
Nov 8, 2021
358
285
63
This is why I still don't undersand screen flipping in the context of fast moving animations. I get that you can prep very slow and complex designs (full screen if you want!) to give the illusion of a massive instantaneous graphical update at some point, but because of its prep time, you can only do complex things occasionally, not every frame so it begs the question, what was gained for fast moving games?

Think about a tile based Zelda style game. You have mostly static tile based backgrounds with foreground sprites that are drawn on top. With buffer flipping, you can avoid drawing most of the background tiles.

There's the main working tile map that game logic works from, and a tile map for each screen buffer that mirrors the main map according to what was drawn.

Buffer A is currently active and we're now painting buffer B.

Tile map B reflects what was drawn in the previous frame. As long as the tile is the same as what's in the main buffer, and no sprite was drawn over it, it doesn't have to be redrawn.

For every sprite, projectile, etc drawn over a background tile, the tile's entry in the map is invalidated so that it will be redrawn next time.

When buffer B is done being painted, the VIA bit is flipped and buffer B becomes visible.

The process now repeats with buffer and tile map A.

As a bonus, you can create effects by using tiles that have their own A and B versions which alternate rapidly (shimmering water for example) and this effect incurs no performance penalty.

If you can maintain 60fps buffer switching, you could even pull off a sort of 1.5-bit grayscale with rapidly flipping pixels, though some might find the flicker annoying.
 

Mu0n

Active Tinkerer
Oct 29, 2021
609
560
93
Quebec
www.youtube.com
Cool! Can’t wait to see what you’re using this for.

To sum up/slightly clarify, to use the alternate screen buffer, you need to do three things:
  • _Launch yourself requesting the alternate screen buffer (I assume you are already doing this since you got your code working), by default it doesn’t exist.
  • To draw on the alternate screen, just SetPort to the GrafPort corresponding to the main screen (thePort is set to this when your app first starts running) and change baseAddr to the address of the alternate screen buffer (ScrnBase - 0x8000). That’s it. (If you draw into some other buffer and BlockMove it there, you are probably wasting precious time if running on a real Mac.)
  • To display the alternate screen, flip the VIA bit (which you are already doing). To be clear, this takes ~zero time and does zero drawing or memory transferage: it literally just sets one bit that tells the hardware “next time you draw the screen, read the video data from a different spot in RAM”.
Here’s some old MacTutor sample code from MacTutor Volume I. It’s in Modula-2 but if we just squint our eyes we can pretend it’s Pascal :)

http://preserve.mactech.com/articles/mactech/Vol.01/01.09/ScreenBuffer/index.html


Hitting a bit of a snag when I'm trying to show the alternate screen's content, it shows up as a really dark screen:
1674023490860.png


Here's my code of a small program simply aimed at testing the technique - I had to look up how to use branch statement with labels with asm blocks in THINK C:

#include <retrace.h> typedef struct VBLRec { VBLTask myVBLTask; long vblA5; } VBLRec, *VBLRecPtr; VBLRec myVBLRec; Boolean flipFlopScreen = 0; OSErr InstallVBL(void); pascal long GetVBLRec(void); void DoVBL(void); void Relaunch(void); pascal long GetVBLRec(void) = 0x2E88; char *viaThing = (char *) 0xEFFFFE; void Relaunch() { asm { tst.l 0x0936 bpl.s @relaunch rts @relaunch: sub.l #32+4+2,SP // for Name, RefNum, Param move.l SP,A0 // point to base of this stuff pea 4+2(A0) // push arg1: VAR pointer to apName pea 4(A0) // push arg2: VAR ptr to apRefNum pea 0(A0) //push arg3: VAR pointer to apParam _GetAppParms // fill in name, other junk; pop params //Now apParam, apRefNum and (importantly) apName are // in stack temps. lea 4+2(SP),A0 // point to the name we just got move.l #-1,-(SP) // -1 asks for alternate screen... move.l A0,-(SP) // ...and point to name to launch move.l SP,A0 // point to name ptr and flags with A0 _Launch // launch ourselves, with alt screen } } OSErr InstallVBL() { myVBLRec.myVBLTask.qType = vType; myVBLRec.myVBLTask.vblAddr = (ProcPtr) DoVBL; myVBLRec.myVBLTask.vblCount = 60; //nice fat seconds to see what's happening myVBLRec.vblA5 = (long)CurrentA5; return VInstall((QElemPtr) &myVBLRec.myVBLTask); } void DoVBL() { long curA5; VBLRecPtr recPtr; recPtr = (VBLRecPtr) GetVBLRec(); curA5 = SetA5(recPtr->vblA5); // My screen flipping happens here flipFlopScreen = !flipFlopScreen; if(flipFlopScreen == 1) *viaThing &= ~64; else *viaThing |= 64; // END OF screen flipping recPtr->myVBLTask.vblCount = 60; curA5 = SetA5(curA5); } void main() { int i,j; EventRecord wutup; BitMap BM1, BM2; Boolean loadSuccess; PicHandle pic1, pic2; Ptr mainScreen = ScrnBase; Ptr altScreen = ScrnBase - 0x8000; Rect r; Relaunch(); InitMacStuff(); WholeScreen(white, false); //custom function that sets the main GrafPort as the entire screen and disables the menu pic1=GetPicture(128); pic2=GetPicture(133); SetRect(&r,0,0,512,300); //300 is not a mistake, that's how high the source images are in this case //Draw on main screen here qd.thePort->portBits.baseAddr = mainScreen; DrawPicture(pic1,&r); //Draw on alternate screen here qd.thePort->portBits.baseAddr = altScreen; DrawPicture(pic2,&r); InstallVBL(); while(!Button()); *viaThing |= 64; //restore to main screen before going back to Finder }


edit - I've also tested that the program does reach the @relaunch branch by adding a _SysBeep trap, which I do hear inside mini-vMac.
 
Last edited:

Mu0n

Active Tinkerer
Oct 29, 2021
609
560
93
Quebec
www.youtube.com
Meanwhile, no problem manually splattering a character all over the alternate screen like so:
1674023929586.png


with this chunk of code replacing DrawPicture(img2,&r);

char *paint; //.... //Draw on alternate screen here qd.thePort->portBits.baseAddr = altScreen; //DrawPicture(pic2,&r); paint = altScreen; for(i=0; i<21888; i++) { *paint = 0x88; paint++; }
 

Mu0n

Active Tinkerer
Oct 29, 2021
609
560
93
Quebec
www.youtube.com
Another bug found:

mini-vMac doesn't care if you check if an unassigned PicHandle is nil or not, but the real Mac will give you a address bus error ID=02.
 

Crutch

Tinkerer
Jul 10, 2022
293
228
43
Chicago
This is probably not a difference between mini-vMac and a real Mac but rather a coincidence depending on whether the longword at memory location 0 is even or odd. From TN#7:
1674148880589.png
 

YMK

Active Tinkerer
Nov 8, 2021
358
285
63
Checking an unassigned PicHandle amounts to reading a random, junk address.

On the 68000, 16 and 32 bit operations on odd addresses throw an address bus exception.

68020+ will carry on, using several bus cycles if needed.

Whether MiniVMac's behavior is a bug depends on which CPU it's emulating.