Saving and restoring the VDU 'state'

for discussion of bbc basic for windows/sdl, brandy and more
Post Reply
Deleted User 9295

Saving and restoring the VDU 'state'

Post by Deleted User 9295 »

Occasionally I find myself wanting to write a BBC BASIC procedure or function which unavoidably changes the colour(s), palette, viewport(s), cursor(s), VDU 4/5 setting etc. but which I want to restore everything back to how it was before returning. Typically this will be because it's a library routine that could be called from any program in any context, and I don't want it to trample on the program's environment.

Most recently I have been writing a 'message box' implementation which prompts the user for a response. Typically it draws a graphic like this, using standard DRAW and PLOT commands etc.:
msgbox.png
It's relatively easy to save and restore the bulk of the VDU 'state' in BBC BASIC for Windows and BBC BASIC for SDL 2.0 because the 'VDU variables' are held in a structure @vdu{}, so the skeleton code you need is:

Code: Select all

      DEF PROCmessagebox(parameters)
      LOCAL savevdu{} : DIM savevdu{} = @vdu{} : savevdu{} = @vdu{} : REM save
      REM now it's safe to change the VDU state....
      
      @vdu{} = savevdu{} : REM restore
      ENDPROC
although in practice there's quite a lot more that you need to do (for example the colour palette needs to be handled separately). But it's manageable.

So I've been thinking, how would one do this in other BBC BASIC implementations such as on an Acorn machine or in Matrix Brandy for example? Is there perhaps an OSWORD call that will copy the entire VDU state into a block of memory (or the reverse)? This must be something that people have wanted to do, but I can't remember ever having seen a solution.
User avatar
BeebMaster
Posts: 7379
Joined: Sun Aug 02, 2009 5:59 pm
Location: Lost in the BeebVault!
Contact:

Re: Saving and restoring the VDU 'state'

Post by BeebMaster »

The Econet utility *VIEW, which copies a remote station's screen to the current station, needs to read the VDU environment of the remote machine so that it knows what screen mode is in use in the remote station, the block of RAM in the remote station to grab, the scroll state of the remote screen, what colour palette is in use, etc. To do this it remote-peeks the remote machine's VDU variables in page 3. My own take on this, in BBC BASIC, for 8-bit Acorns, does a similar thing:

Code: Select all

 5950DEFPROCvdu
 6000PROCosw10(peek,data,data+&7F,&FFFF0300)
 6050REM read VDU workspace in rem machine starting at &300
 6100himem=(data?&4E)*256
 6150oldmode=mode
 6200mode=data?&55
 6250memsize=data?&54*256
 6300xcur=data?&18:ycur=data?&19
 6350scroll=data?&50+data?&51*256
 6400REM transfer logical colours to host machine
 6450FORL%=0TOdata?&60
 6500VDU19,L%,?(data+&6F+L%),0,0,0
 6550NEXT
 6600PROCosw10(peek,data,data+&F,&FFFF00D0)
 6650REM Read VDU status etc in remote machine D0-DF
 6700IF?data AND16 shadow=&FFFE0000 ELSE shadow=&FFFF0000
 6750ENDPROC
So it reads the whole of page 3 from the remote machine (some of which is workspace allocated to other functions but it's easier to do one peek operation for everything needed than trying to peek a byte at a time), and uses a selection of this data to set up the same environment on the local machine (I don't think just copying the VDU variables direct into the local machine's page 3 would work as it would leave things in an inconsistent state).

Then it reads more of the VDU workspace at &D0-&DF, currently only &DF is used in this code as it contains a flag determining whether the shadow screen RAM or main screen RAM is active in the remote machine.

Advanced User Guide chapter 11 discusses the use of the VDU workspace (&D0-&DF on pp.268-9 and page 3 on pp.274-279).

For saving and restoring on a local 8-bit Acorn machine there are official OS calls which can be used to read various bits of the VDU state, eg. OSBYTEs &84-&87 for reading such thing as HIMEM and text cursor positions, and OSBYTE &A0 is an official method of reading the VDU variables in page 3. I'm not sure you can read the current colour palette without peeking it from page 3 though, and I haven't yet discovered how you can read the current shape and blink status of the cursor, I'm not sure that is possible remotely, and on the local machine I think would involve reading the 6845 registers directly.
Image
Deleted User 9295

Re: Saving and restoring the VDU 'state'

Post by Deleted User 9295 »

BeebMaster wrote: Mon Jun 21, 2021 8:31 pm I don't think just copying the VDU variables direct into the local machine's page 3 would work as it would leave things in an inconsistent state.
Yes, that's something I have to watch in my BASICs too. Fortunately the great majority of the VDU variables can just be copied and restored, but a few need extra work to establish a consistent state on return. The cursor/caret shape is one of those (well, it is in BBC BASIC for Windows, I think you can safely just restore the variables in BBC BASIC for SDL 2.0). Another is the graphics viewport.
User avatar
jgharston
Posts: 5319
Joined: Thu Sep 24, 2009 12:22 pm
Location: Whitby/Sheffield
Contact:

Re: Saving and restoring the VDU 'state'

Post by jgharston »

I worked out something for my "multiple windows" routine. Let's see if I can track it down...

(An aside, if the program itself is defining all the windows, it's easier for the program itself to keep track of the VDU state, which is what *TALK does with its three windows.)

Ah ha, found it. PROC_DWIND() defines a window, FN_WIND() selects a window within a PRINT sequence, saving the current state and selecting the selected state.

Code: Select all

 1000 DEFPROC_DWIND(wind%,a%,b%,c%,d%)
 1010 LOCAL BA%
 1020 IF wind%>7 OR wind%<0 THEN ENDPROC
 1030 BA%=wind%*9+&411
 1040 ?BA%=0:?(BA%+1)=0
 1050 ?(BA%+2)=a%:?(BA%+3)=b%:?(BA%+4)=c%:?(BA%+5)=d%
 1060 ?(BA%+6)=?&D2:?(BA%+7)=?&D3:?(BA%+8)=?&358
 1070 ENDPROC
 1080 DEFFN_WIND(w%)
 1090 IF w%<0 OR w%>7 THEN =""
 1100 LOCAL BA%
 1110 BA%=(7 AND (?&410))*9+&411
 1120 ?BA%=POS:BA%?1=VPOS:?(BA%+6)=?&D2:?(BA%+7)=?&D3:?(BA%+8)=?&358
 1130 BA%=w%*9+&411
 1140 VDU 28,?(BA%+2),?(BA%+3),?(BA%+4),?(BA%+5)
 1150 VDU31,?BA%,?(BA%+1)
 1160 ?&410=w%:?&D2=?(BA%+6):?&D3=?(BA%+7):?&358=?(BA%+8)
 1170 =""
POS and VPOS are found and restored the usual way. The rest is very messy.
It looks like it saves each window's data in BASIC's workspace in page 4.
The text window size isn't saved from the VDU state, it's saved from what was set with PROC_DWIND()
The colours are saved and written with the VDU workspace colour masks.

If I was writing that now, I'd:
* Read window state with OSBYTE 160 from VDU variables 0-7
* Read POS,VPOS with POS,VPOS
* Read current colours with OSBYTE 160
* Read the graphics positions with OSWORD 13
* Read the palette with OSWORD 11 (though the palette is a global state)
and
* Write the window state with VDU
* Write the POS,VPOS with VDU
* Write the current colours with VDU
* Write the graphics positions with VDU

Ideally, get the saved state to be such that you can just plough through a block of VDUs to restore it. So, starting from the target, I'd want to build up:
VDU 28,a,b,c,d, 24,a,b,c,d,e,f,g,h, 29,a,b,c,d, 25,k,a,b,c,d, 25,k,a,b,c,d, 31,a,b, 17,a, 17,128+b, 18,a,b, 18,a,128+b
textwindow ..... graphicwindow ....... origin .... previous ..... current ... pos/vpos txfgd .. txbgd .. gfgd .... gbgd

That would be 44 bytes.

A quick'n'dirty solution could be to save/restore:
* VDU zero page &D0-&D5
* VDU workspace &00-&19 window state
* VDU workspace &57-&5C colour state
* VDU workspace &66-&6E graphics state

That would be 47 bytes.

Something like:
REM Save VDU state
store%=saved state
addr%=&D0:off%=0
REPEAT
store%?off%=FNmem_rd(addr%)
addr%=addr%+1
IF addr%=&0D6:addr%=&300
IF addr%=&31A:addr%=&357
IF addr%=&35D:addr%=&366
off%=off%+1
UNTIL off%=48

REM Restore VDU state
store%=saved state
addr%=&D0:off%=0
REPEAT
PROCmem_wr(addr%,store%?off%)
addr%=addr%+1
IF addr%=&0D6:addr%=&300
IF addr%=&31A:addr%=&357
IF addr%=&35D:addr%=&366
off%=off%+1
UNTIL off%=48

I've a suspision that you need to do VDU 31,x,y to program the hardware cursor, just writing the variables won't be enough.

Code: Select all

$ bbcbasic
PDP11 BBC BASIC IV Version 0.45
(C) Copyright J.G.Harston 1989,2005-2024
>_
User avatar
jgharston
Posts: 5319
Joined: Thu Sep 24, 2009 12:22 pm
Location: Whitby/Sheffield
Contact:

Re: Saving and restoring the VDU 'state'

Post by jgharston »

First iteration, by reading and writing VDU workspace.

PROCvdu_save(address) stores the current VDU state in memory at address.
PROCvdu_restore(address) restores the VDU state from the opaque data at address.
Requires X%=>5 bytes, Y%=X%DIV256

To make it cross-platform you'd read the host inside the routines and do other host-specific stuff.

Code: Select all

   10 REM > VDUState
   20 :
   30 REM Read and restore VDU state by read/writing memory
   40 REM Requires X%=>5 bytes, Y%=X%DIV256
   50 :
   60 REM Save VDU state
   70 REM store% => 48 bytes
   80 DEFPROCvdu_save(store%)
   90 PROCmem_rd(store%+&00,&FFFF00D0,0,&06)
  100 PROCmem_rd(store%+&06,&FFFF0300,0,&1A)
  110 PROCmem_rd(store%+&20,&FFFF0357,0,&06)
  120 PROCmem_rd(store%+&26,&FFFF0366,0,&09)
  130 ENDPROC
  140 :
  150 REM Restore VDU state
  160 REM store% => 48 bytes
  170 DEFPROCvdu_restore(store%)
  180 PROCmem_wr(store%+&00,&FFFF00D0,0,&06)
  190 PROCmem_wr(store%+&06,&FFFF0300,0,&1A)
  200 PROCmem_wr(store%+&20,&FFFF0357,0,&06)
  210 PROCmem_wr(store%+&26,&FFFF0366,0,&09)
  220 VDU31,store%?30-store%?14,store%?31-store%?17
  230 ENDPROC
  240 :
  250 DEFPROCmem_rd(mem%,io%,rom%,num%)
  260 A%=5:REPEAT:!X%=io%:CALL&FFF1:?mem%=X%?4:io%=io%+1:mem%=mem%+1:num%=num%-1
  270 UNTILnum%<1:ENDPROC
  280 :
  290 DEFPROCmem_wr(mem%,io%,rom%,num%)
  300 A%=6:REPEAT:!X%=io%:X%?4=?mem%:CALL&FFF1:io%=io%+1:mem%=mem%+1:num%=num%-1
  310 UNTILnum%<1:ENDPROC

Code: Select all

$ bbcbasic
PDP11 BBC BASIC IV Version 0.45
(C) Copyright J.G.Harston 1989,2005-2024
>_
User avatar
jgharston
Posts: 5319
Joined: Thu Sep 24, 2009 12:22 pm
Location: Whitby/Sheffield
Contact:

Re: Saving and restoring the VDU 'state'

Post by jgharston »

Very limited testing:
vdusave.gif

Code: Select all

$ bbcbasic
PDP11 BBC BASIC IV Version 0.45
(C) Copyright J.G.Harston 1989,2005-2024
>_
Deleted User 9295

Re: Saving and restoring the VDU 'state'

Post by Deleted User 9295 »

jgharston wrote: Tue Jun 22, 2021 2:17 pm An aside, if the program itself is defining all the windows, it's easier for the program itself to keep track of the VDU state
As I said, my use case was for a library routine which can be called from any program in any context, without mucking up what the calling program is doing. Therefore the saving and restoring of the VDU state has to be done in a 'universal' way.

This all stems from trying to port code from a Windows-specific environment to being platform-independent. Windows provides useful API functions for displaying dialogue boxes, message boxes and the like which are completely independent of the calling program, in that they are composited 'in front' by the window manager, like a hardware sprite. Therefore they don't interfere with the operation of the calling program in any way.

Attempting to reproduce this functionality entirely in BBC BASIC code is 'interesting'. Not only must the VDU state be saved on entry and restored on exit, but the region of the window 'obscured' by the dialogue/message box must be saved and restored as well (easy in BB4W/BBCSDL using *GSAVE and *DISPLAY).

I've even gone to the extent of allowing the dialogue/message box to be dragged (although it only draws an outline - using exclusive-or plotting of course, whilst being moved). The result, if I say so myself, is uncanny. It behaves exactly as if the message box is being composited in a different layer, but it's all drawn with bog standard BBC BASIC graphics statements!

I've written the code to be compatible with both BBC BASIC for Windows and BBC BASIC for SDL 2.0, fortunately there are only minor differences in the way fonts, the palette and the graphics viewport are handled. It makes heavy use of features unique to those versions (such as structures) but it's interesting to speculate on how one might port it to other versions.

Not surprisingly it sounds like quite a lot of work on the BBC Micro, but that's an unlikely target (resources are probably too limited to make it viable). But ARM BASIC V and Matrix Brandy are more practical platforms, and I wonder how saving and restoring the VDU state would be done on them.
Soruk
Posts: 1136
Joined: Mon Jul 09, 2018 11:31 am
Location: Basingstoke, Hampshire
Contact:

Re: Saving and restoring the VDU 'state'

Post by Soruk »

Richard Russell wrote: Tue Jun 22, 2021 3:50 pm But ARM BASIC V and Matrix Brandy are more practical platforms, and I wonder how saving and restoring the VDU state would be done on them.
For newer versions of ARM BBC BASIC V/VI (available as a softload for older machines, and built into the RISC OS version for Raspberry Pi), also in Matrix Brandy, the VDU variables can be read using the VDU() function (only 0-12. 128-147 and 153-161 are currently supported in Matrix Brandy, though 148-152 should be relatively easy to implement). Older versions of ARM BBC BASIC (also works for Matrix Brandy) have to use SYS "OS_ReadVduVariables" (documented here). The SYS call also has the advantage that you can build a parameter block of what you're interested in, make the call and get the answers in one fell swoop.

(Checking this out, I discovered a bug in Matrix Brandy that the TINT variables were being returned incorrectly, this is fixed in the nightly that will be built at 1am.)
Matrix Brandy BASIC VI (work in progress) The Distillery (another work in progress) Note Quiz (New educational software for the BBC and modern kit)
BBC Master 128, PiTubeDirect (Pi 3B), Pi1MHz, 5.25+3.5in dual floppy.
Deleted User 9295

Re: Saving and restoring the VDU 'state'

Post by Deleted User 9295 »

Soruk wrote: Thu Jun 24, 2021 12:08 am VDU variables can be read using the VDU() function (only 0-12. 128-147 and 153-161 are currently supported in Matrix Brandy, though 148-152 should be relatively easy to implement).
I know about those, but it's not clear how or if they can be restored 'en masse'. And I don't see any mention there of the palette; saving and restoring COLOUR and GCOL numbers is of limited value if the palette has been changed!

One interesting thing in the linked table is that the VDU 4 and VDU 5 characters seem to have independent sizes and spacing. I've not encountered that before; is it exposed in BASIC? There's nothing similar in my BASICs: VDU 4 and VDU 5 characters are always the same (and I've never felt it to be a limitation); you can of course change the font at will anyway, although it's not fast.
Older versions of ARM BBC BASIC (also works for Matrix Brandy) have to use SYS "OS_ReadVduVariables"
So is there also an OS_WriteVduVariables? Would you be able to list some code that could be used as the prologue and epilogue of a PROC or FN so that it can safely change graphics and/or text settings and then restore them to how they were before return?

Here's the complete code (E&OE) needed to fully save and restore the VDU state, including the font, in BBC BASIC for Windows and BBC BASIC for SDL 2.0. It can be simplified if it needs to run on only one of those platforms:

Code: Select all

      DEF PROCsomething(parameters)
      REM Save:
      IF POS REM SDL thread sync
      LOCAL B%, F% : B% = INKEY(-256) = &57 : F% = @flags%
      LOCAL @char.x%, @char.y%, pal{}, svdu{} : DIM pal{0%(15)}, svdu{} = @vdu{} : svdu{} = @vdu{}
      IF B% SYS "GetPaletteEntries", @hpal%, 0, 16, pal{} ELSE SYS "SDL_memcpy", pal{}, @hpal%, 64
      IF NOT B% IF @vdu.hr% THEN LOCAL rc{} : DIM rc{0%(3)} : SYS "SDL_memcpy", rc{}, @vdu.hr%, 16
      @vdu.hf% = 0 : @vdu.hr% = 0 : *ESC OFF

      REM Now it's safe to change the font, palette, viewports, text & graphics settings etc....

      REM Restore:
      VDU 26 : *FONT
      IF B% IF @vdu.hr% SYS "DeleteObject", @vdu.hr%
      @vdu{} = svdu{}
      IF NOT B% IF @vdu.hr% SYS "SDL_memcpy", @vdu.hr%, rc{}, 16
      IF B% IF @vdu.hf% SYS "SelectObject", @memhdc%, @vdu.hf%
      IF B% SYS "SetPaletteEntries", @hpal%, 0, 16, pal{} ELSE SYS "SDL_memcpy", @hpal%, pal{}, 64
      IF B% SYS "SendMessage", @hwnd%, &402, 0, 0 : REM Update caret shape
      IF F% AND &40000000 ELSE *ESC ON
      ENDPROC
Soruk
Posts: 1136
Joined: Mon Jul 09, 2018 11:31 am
Location: Basingstoke, Hampshire
Contact:

Re: Saving and restoring the VDU 'state'

Post by Soruk »

There is no OS_WriteVduVariables, you'd need some VDU calls to set them.

To read the palette you need OS_ReadPalette or OSWORD 11, and the palette can be written with OSWORD 12 or VDU19. Currently, none of these are supported on Matrix Brandy, but that should be relatively easy to fix for <=16 colour modes.

I have never known VDU4 and VDU5 characters to have different sizes, certainly this is not supported by Matrix Brandy.
Matrix Brandy BASIC VI (work in progress) The Distillery (another work in progress) Note Quiz (New educational software for the BBC and modern kit)
BBC Master 128, PiTubeDirect (Pi 3B), Pi1MHz, 5.25+3.5in dual floppy.
User avatar
jgharston
Posts: 5319
Joined: Thu Sep 24, 2009 12:22 pm
Location: Whitby/Sheffield
Contact:

Re: Saving and restoring the VDU 'state'

Post by jgharston »

Reading and writing the palette is fairly simple.

Code: Select all

   60 REM Set palette from "palette file" VDU sequence at store%
   70 DEFPROCvdu_paletteset(store%)
   80 LOCAL A%:FOR A%=store% TO store%+95:VDU ?A%:NEXT
   90 ENDPROC
  100 :
  110 REM Get palatte as "palette file" VDU sequence to store%
  120 DEFPROCvdu_paletteget(store%)
  130 LOCAL X%,Y%,A%,col%:A%=11
  140 A%=INKEY-256:IFA%<>ASC"W":A%=11
  150 FOR col%=0 TO 15
  160   X%=store%+col%*6+1:Y%=X%DIV256:X%?-1=19:X%?0=col%
  170   IF A%=11:CALL &FFF1
  180   IF A%=ASC"W":X%?1=16:SYS "GetPaletteEntries",@hpal%,col%,1,X%+2
  190   IF A%=ASC"S":REM....
  200 NEXT col%
  210 ENDPROC

Code: Select all

$ bbcbasic
PDP11 BBC BASIC IV Version 0.45
(C) Copyright J.G.Harston 1989,2005-2024
>_
Post Reply

Return to “modern implementations of classic programming languages”