mailto: blog -at- heyrick -dot- eu

Navi: Previous entry Display calendar Next entry
Switch to desktop version

FYI! Last read at 03:14 on 2024/12/18.

The first half of my holiday

On Monday, hacking brambles.

On Tuesday, went shopping. Turns out the short period of tranquility for autistic people is from 13h30 to 15h30, which is not what it said on the poster on the door (13h00-15h00). Ironically, actual autistic people are the ones most likely to be upset by a blatantly incorrect statement.
When it happened, two minutes late, they simply turned off the musak, which meant the shouty advertising screens at the ends of some aisles were even more distracting. They didn't bother dimming the lights either, so there was still the usual bright glare. If I'd known that, I'd have worn my sunglasses.
So, kudos for attempting to cater for autistic people (for two hours a week), but I'm afraid I'm going to have to rate them a big fat 'F' for an utterly half-arsed implementation.
Came home, fitted my new wheels to the big mower - which was a comical "nope, ain't gonna". Put the old crap wheels back on and mowed the grass (except potager and picnic lawn).
Wednesday, nice morning, so I mowed picnic lawn with the little mower. Well, I felt like going for a walk so this allowed me to do that and something useful at the same time. Then mowed by the pond. The ground is soft and it's uneven so big mower won't work there.
Then mowed the potager. Because of the many obstacles, trying to do that with the big mower may well take longer than using the little one.
Then I went around with the strimmer (with the whip head on instead of the blades) tidying up edges. It's actually pretty uncomfortable to use. The control handle is really bad. But it works and it was cheap, so...
Finally, I turned the ground of the former potato patch. This year, carrots and onions, I think. I forget. Too lazy to go into the house and look. After turning the ground, I raked it flat. Backbreaking work. It would have been a lot less bother to have gone to work! But, no, can't say that. I'm out here, enjoying the sun, not stuck inside a modern factory building.

Here's a video with some comical high speed bits.

 

Screen banking and redraws

Something that comes up from time to time is how to handle redrawing the screen in order that movement and animation be smooth. This is not only in having the objects physically appear in a non-jerky fashion, but more importantly that the screen is actually drawn in the correct manner so as to avoid corruption.

Regardless of the specifics of the display technology in use, the screen is still drawn from top to bottom, line by line. This is a hangover from the days of cathode ray monitors that, like televisions, actually worked by directly controlling the output of an electron gun that fired electrons towards the front of the screen as it swept from side to side, and down the screen. These electrons would cause little coloured dots of phosphor to glow and, voilà you have an image on the screen.
Even with today's flat panels (LCD or OLED) and digital technology such as HDMI, the computer is still sending the image data top to bottom, line by line, just like it did in the old days. The only difference is that it can clock it out more rapidly, so resolutions with four thousand pixels in one direction and two thousand lines running at something like 144Hz is entirely possible . . . assuming, of course, that both the computer and the display can physically manage it.
There's also a lot more intelligence built into display devices. For example, the display I'm currently using is a little 7 inch LCD panel by Uniroi. This is because I'm sitting outside in the sunshine running the display and my older Pi off of a battery. The native resolution of the display is 1024×600. Not great, but usable. It will happily accept an input of 1920×1080 and attempt to scale it to fit. Back in the cathode ray days, if a certain style of output is not accepted, then depending on the monitor you would either get a blue screen (perhaps with a warning), a horrible noise as it tried anyway, and once in a while even smoke as it literally burnt out driver transistors trying to handle an unsupported resolution.

Anyway, that's all a distraction. For our purposes, all we need to know is that the display is sent to the monitor from the top to the bottom, over and over again.

Which raises the obvious question. What happens if we are drawing a circle on the screen and it's already sent half of that part of the screen to the display? Well, the answer is simple - you will, for one frame, see half a circle. If your circles are moving and you are redrawing the screen anew each time, then you'll get horrible flickering as a part of the display will be showing the newly redrawn screen, and a part will be showing the previous one. It depends upon when the transition point actually happens, and note that it is rare for anything drawn to the screen to exactly match the display's drawing rate, so the actual end result will be a mess that would be nasty for photosensitive people, and headache inducing in everybody else.

 

No banking and no sync

Here is a simple program. It will draw eighty circles that will be moving either left or right, reversing their direction when they get to the edge of the screen.
REM >nobank
REM
REM Redraw example with NO banking
REM

ON ERROR PRINT REPORT$+" at "+STR$(ERL) : END


maxx% = 800 << 1
maxy% = 600 << 1


REM Random start positions of our balls
DIM pos%(80, 2)
FOR loop% = 1 TO 80
  pos%(loop%, 1) = RND(maxx%)
  pos%(loop%, 2) = RND(2) - 2
NEXT


REM Select 800x600@60Hz, 256 colours
DIM mode% 32

mode%!0  = 1
mode%!4  = 800
mode%!8  = 600
mode%!12 = 3  : REM 8bpp
mode%!16 = -1 : REM 60Hz
mode%!20 = -1 : REM No additional data
SYS "XOS_ScreenMode", 0, mode% TO ; flags%
IF ((flags% AND 1) = 1) THEN ERROR 17, "Unable to select SVGA screen mode."

PRINT "Switched to 256 colour SVGA mode."

CLS
OFF                   : REM Turn the cursor off

ON ERROR PROCrecover



REM The main loop
REPEAT
  REM Clear the screen
  CLG

  REM Note the current ticker value
  t% = TIME

  FOR loop% = 1 TO 80
    col% = (loop% << 2)
    PROCdraw_circle(pos%(loop%,1 ), (loop% * 15), 32)
               REM  Xmid,           Ymid,         Size

     IF ( pos%(loop%, 2) = TRUE ) THEN
       pos%(loop%, 1) += 16
       IF (pos%(loop%, 1) > maxx%) THEN pos%(loop%, 2) = FALSE
     ELSE
       pos%(loop%, 1) -= 16
       IF (pos%(loop%, 1) < 0) THEN pos%(loop%, 2) = TRUE
     ENDIF
  NEXT

  REM Let two ticks elapse (forces 50Hz refresh)
  REPEAT : UNTIL ((t% + 2) < TIME)

UNTIL INKEY(-1) : REM Either Shift key

CLS
PRINT "Finished."
END


:


DEFPROCdraw_circle(x%, y%, r%)
  CIRCLE FILL x%, y%, r%
ENDPROC


DEFPROCrecover
  ON ERROR OFF
  CLS
  PRINT REPORT$+" at "+STR$(ERL)
  END
ENDPROC

Here's a video:

That looks pretty poor, really.

 

No banking, but sync

Now there is a trick that might work if what you are drawing is very simple. This is to wait for a marker that signals the top of the display has been reached.
You see, in the old days, a PAL television (analogue era) was quoted as having 625 lines, however only 576 were actually visible. If you're an American, then for NTSC it was 525 lines with 486 visible). Why the discrepancy? It's because it takes time for the vertical scanning to shift itself back up to the top of the screen; because of magnetic inertia in the scanning coils it was simply not possible for the position to jump back to the top, it had to be moved up there. So the electron gun would be blanked and the position dragged up to the top to restart drawing the screen again.

How display scanning works
How display scanning works.

The missing lines were called the Vertical Blanking Interval, and some of those were dedicated to specific signals in order that the receiver be able to reliably detect where the top of each frame actually is. Because of a guard period (for older equipment), there were a few lines that could have been used for display but were not. These instead were used for services such as subtitling (closed captioning), teletext, time codes, copy protection, and so on.

Which means, there's a little bit of space between the top being reached, and the start of the visible display. It isn't a lot. For SVGA as we're using, it's 28 lines, or about 740 microseconds at 60Hz; or a little under three quarters of a thousandth of a second.

Note, also, that a modern flat panel display doesn't actually need this, so there may be a reduced blanking period in use - about 25-30% shorter, just enough to allow reliable frame syncronisation. Which means we could be looking at one two-thousandth of a second in which to draw our entire output.

To do this, simply change the top of the main loop to look like this:

REM The main loop
REPEAT
  REM Await VSync
  SYS "OS_Byte", 19

  REM Clear the screen
  CLG

It looks like this:

You can see that it is much better. There is, however, some corruption and flickering at the top of the screen. This may or may not occur on other devices. I'll explain.

Traditionally, the processor controlled and send data to a video chip that assembled the display output by reading memory, doing colour lookups, and so forth. While this device was entirely responsible for the output that appears, the processor (and thus the operating system) was entirely in charge of things.

This is often not the case any more. Rather than being a simple video output chip, a modern graphics system is referred to as a GPU - Graphical Processor Unit. That is to say, it is a computer in its own right, often running firmware and maybe even more powerful than the processor. It is, like an older video chip, capable of signalling back to the host processor when the top of the screen has been reached. This, however, may or may not actually arrive on time. Or, in the case of the Pi, have much actual relationship between the signal and the actual top of the screen. This is partially due to the odd arrangement of the Pi's processor where the ARM is technically the co-processor. If you ever try looking at the binary data that boots the Pi, you might notice that it's gibberish and not ARM code. This is because it's actually the GPU that starts the boot process. On the older models, a tiny RISC core is used to access the SD card and, with firmware built into it, load the bootcode.bin file into the GPU's L2 cache. This in turn sets up the RAM and loads start.elf into memory for the GPU to execute. This then loads the configuration, starts up the ARM, and finally loads the kernel into memory and releases the reset on the ARM so that it can begin starting up the operating system.
The start.elf file isn't unloaded. It is, actually, a proprietory mini operating system known as VideoCore OS. It remains running, and the ARM's operating system talks to it using a set of mailboxes to send and receive messages. But, certainly, the ARM is subordinate here.

 

Double buffering

So now we are arriving at a potential solution. If it isn't possible to draw a screen whilst the blanking period is happening, then why don't we maintain two distinct screens? The video hardware can be sending one to the monitor, while we are drawing into the other one. When the top of the display is reached, simply switch between them.

This is a very common solution called double buffering. In order to implement this in our program, we need to allocate screen memory and then include code to switch the screen banks. Instead of telling you what to insert, as the program is small I'll just list it again.

REM >banking
REM
REM Redraw example with double or triple banking
REM

ON ERROR PRINT REPORT$+" at "+STR$(ERL) : END


maxx% = 800 << 1
maxy% = 600 << 1


REM Random start positions of our balls
DIM pos%(80, 2)
FOR loop% = 1 TO 80
  pos%(loop%, 1) = RND(maxx%)
  pos%(loop%, 2) = RND(2) - 2
NEXT

banks% = 2


REM Select 800x600@60Hz, 256 colours
DIM mode% 32

mode%!0  = 1
mode%!4  = 800
mode%!8  = 600
mode%!12 = 3  : REM 8bpp
mode%!16 = -1 : REM 60Hz
mode%!20 = -1 : REM No additional data
SYS "XOS_ScreenMode", 0, mode% TO ; flags%
IF ((flags% AND 1) = 1) THEN ERROR 17, "Unable to select SVGA screen mode (800x600,60Hz)."

PRINT "Switched to 256 colour SVGA mode."

REM Now allocate memory for screen buffers
SYS "OS_ReadModeVariable", -1, 7 TO ,,size% : REM 7 = ScreenSize
PRINT "Screen size is "+STR$(size%)+" bytes, we want 2x that."
want% = size% * banks% : REM Allow for buffering
SYS "OS_ReadDynamicArea", 2 TO , current% : REM 2 = Screen area
expand% = want% - current%
IF (expand% < 0) THEN SYS "XOS_ChangeDynamicArea", 2, expand% TO ; flags%
IF ((flags% AND 1) = 1) THEN ERROR 17, "Unable to allocate memory for screen buffering."
PRINT "Allocated."

SYS "OS_Byte", 114, 0 : REM Modes will be shadow
CLS
OFF                   : REM Turn the cursor off
bank% = 1             : REM Select first bank

REM This is VERY important
ON ERROR PROCrecover



REM The main loop
REPEAT
  REM Await VSync
  SYS "OS_Byte", 19

  REM Select bank
  SYS "OS_Byte", 113, bank% : REM Which one we're showing
  bank% += 1
  IF bank% > banks% THEN bank% = 1
  SYS "OS_Byte", 112, bank% : REM Which one we're writing to

  REM Clear the screen
  CLG

  REM Note the current ticker value
  t% = TIME

  FOR loop% = 1 TO 80
    col% = (loop% << 2)
    PROCdraw_circle(pos%(loop%,1 ), (loop% * 15), 32)
               REM  Xmid,           Ymid,         Size

     IF ( pos%(loop%, 2) = TRUE ) THEN
       pos%(loop%, 1) += 16
       IF (pos%(loop%, 1) > maxx%) THEN pos%(loop%, 2) = FALSE
     ELSE
       pos%(loop%, 1) -= 16
       IF (pos%(loop%, 1) < 0) THEN pos%(loop%, 2) = TRUE
     ENDIF
  NEXT

  REM Let two ticks elapse (forces 50Hz refresh)
  REPEAT : UNTIL ((t% + 2) < TIME)

UNTIL INKEY(-1) : REM Either Shift key

SYS "OS_Byte", 112, 0 : REM Default VDU bank
SYS "OS_Byte", 113, 0 : REM Default display bank
SYS "OS_Byte", 114, 1 : REM Turn off banks
CLS
PRINT "Finished."
END


:


DEFPROCdraw_circle(x%, y%, r%)
  CIRCLE FILL x%, y%, r%
ENDPROC


DEFPROCrecover
  ON ERROR OFF
  CLS
  SYS "OS_Byte", 112, 0 : REM Default VDU bank
  SYS "OS_Byte", 113, 0 : REM Default display bank
  SYS "OS_Byte", 114, 1 : REM Turn off banks
  PRINT REPORT$+" at "+STR$(ERL)
  END
ENDPROC

And, as before, here's a video:

Hang on! Wait! This is worse! Like insanely horrible (worse in reality than the video suggests). What gives?

Well, there's a reason I talked above about how the Pi works. On some other machines, such as the RiscPC, this may well have been good enough for a smooth display.
But on the Pi, since the top of the screen marker (or VSync) is pretty much fake, we may end up switching our screen banks at the wrong time.

 

Triple buffering!

To the rescue is the idea of triple buffering. That is to say, the display will be seeing frame 1, frame 2 will have been drawn, and frame 3 will be the one you're currently drawing. Which means that the video chip, instead of lagging a frame behind, will be lagging two behind. So there are two stable drawn frames that can be switched between. The end result being a much smoother display.

To see this in action, simply change banks% = 2 to banks% = 3 at the top.

Here's a video:

 

Four? Five? Six?

Try it.

You may notice it start to stutter at seven banks, jerky at eight, and flickering beyond. This may be due to how memory is being set up to cope with the banking - remember each bank is a complete area of screen memory and above a certain number it might require paging? I don't know, I've not looked into it. Suffice to say, you'll notice that three banks is the sweet spot and beyond that doesn't offer any benefit.

 

How I'm writing this

On my old Pi, with a little LCD panel, running off battery, while sitting under a tree. I'll need to transfer the data to something else to add the video IDs, and then upload it. But for now, it's an old keyboard "translated into English" and Zap. ☺

It's, oh, so quiet...shhh!
It's, oh, so quiet...shhh!

Please let me win something useful on the lottery (<cough>not €3,60!</cough>) so I can spend the rest of my days doing stuff like this!

 

 

Your comments:

J.G.Harston, 24th March 2022, 19:29
I first encountered screen banking in Jet Swt Willy on the Spectrum. It used three banks, it drew the room to bank 1, copied it to bank 2, overlaid the moving objects, then copied it to the video memory. That way there was no flickering to remove moving objects form their old position as it just started afresh with a copy of the static screen and drew moving objects on at their updated positions. 
 
I did an experimental version with the last step was omitted and the static room copied to the video memory, and then the moving object added on there, and it was less stuttery than I expected, and released up 4K of memory that could be used for another 16 rooms.
Rick, 24th March 2022, 20:32
Programmers these days just wouldn't understand. 😢
druck, 24th March 2022, 21:39
You are missing a REM before Await VSync, and your bank switching code already has banks%=3. 
 
The dangers of coding outside - the sunlight was probably reflecting off your screen! But a part from that a good intro to the technique. 
 
--  
Slava Ukraini
Rick, 24th March 2022, 21:57
The missing REM may well have been the sun. 
The banks being 3 is probably due to an edit to make the video, not undone when copying the code over. 
 
Thanks for spotting those. Now fixed. 
Steve Drain, 25th March 2022, 11:24
My first, and pretty well only, use of screen banking goes back to my Hypercude program, published in Archive in Oct 1998. 
 
This was, of course, under Arthur and in BASIC and uses double-banking. The display part still works now, and within druck's GraphTask. 
 
It displays a duel-image 3D representation of a hypercube (tesseract) that can be manipulated in real time, so speed was critical. It employs BASIC array operations and *vectors*. ;-) 
Steve Drain, 25th March 2022, 11:25
1988, not 1998. ;-(
Rick, 25th March 2022, 11:34
The years pass too quickly...
Rick, 25th March 2022, 11:38
I was listening to a radio station that, after playing "Only You" mentioned that it was a hit for Yazoo forty years ago. 
I nearly died.
Steve Drain, 25th March 2022, 11:46
Here's an interesting paragraph I wrote about BASIC back then. I was coming from a Spectrum with BetaBASIC, a really well structured language. I had been using SBasic on an RM480Z, which is even more structured, and I was teaching with Logo. 
 
"However, not everything was sweetness and light. Contrary to the 
'received version' in Archive, BBC Basic is not that wonderful a 
language: very flexible and adaptable, yes, and fast even on a 'B', but 
not friendly and intuitive. To make good use of the machine you need an 
intimate knowledge of the operating system calls and good program 
structure and readability are difficult to achieve, especially when 
memory saving is important. Well, Archie and Basic V should have 
changed that, but they have the problem of upward compatability and 
retain many of the awkwardnesses of the earlier machines. Nevertheless, 
a good editor, a much fuller set of structures and some improvements to 
the string handling make it worthwhile getting at all the other goodies 
hidden away inside Arthur."
J.G.Harston, 25th March 2022, 22:44
The capabilities of the hardware and/or OS are nothing to do with the language you happen to be using and its structuredness or intuitiveness. That's a feature of the language, not the platform. 
 
Rassen frassen Pascal refuses to saveblock("CON:"). 
 
No, *WINDOWS* refuses to saveblock("CON:"). 
 
The beauty of BBC BASIC is that anything that's missing is immediately creatable just by writing the required procedures/functions.
Rob, 1st April 2022, 11:43
I once implemented a form of double buffering in mode 7 (teletext mode) on the BBC Micro. 
It was for a viewdata terminal emulator I was writing for somebody. Because of the way the beeb implemented the teletext mode, when doing double height text, you had to copy the top line into the memory for the bottom line. The spec said that the display should just extend the first line over the second, which should be ignored. 
If you used a "real" viewdata terminal, wrote two lines of text, then added the double height code to the first, it would indeed work just like that. Removing the code revealed the second line again. On every emulated terminal I encountered, the second line was lost, and would usually end up as a copy of the first line, complete with double height code, which would now upset line three.. 
My code kept the "real" data in one buffer, and updated the screen on the vertical refresh. Remove the double height code, second line reappeared. 
It was satisfying to get working. I bet nobody ever noticed.

Add a comment (v0.11) [help?]
Your name:

 
Your email (optional):

 
Validation:
Please type 77910 backwards.

 
Your comment:

 

Navi: Previous entry Display calendar Next entry
Switch to desktop version

Search:

See the rest of HeyRick :-)