RTR logo

BBC BASIC for SDL 2.0

Hints and Tips



This page contains some hints, tips and advanced techniques in the use of BBC BASIC for SDL 2.0 which you won't find elsewhere in the manual. Don't worry if some of them seem like gobbledegook: if you can't understand them you don't need to know about them! If you have any suggestions for additions to this section please let us know.

Using windows larger than 2048 x 2048 pixels

The default output bitmap used by BBC BASIC for SDL 2.0 is normally 2048 x 2048 pixels. Whilst this should be large enough for the majority of display settings and applications you may occasionally want to increase its size. For example you might want to extend the technique used in the example program SCROLL.BBC to scroll over an even larger 'canvas'. The procedure below allows you to do that:

DEF PROChugewindow(W%, H%)
LOCAL P%, old%%, new%%
IF (@platform% AND &F) < 3 P% = &16362004 ELSE P% = &16762004
SYS "SDL_GetRenderTarget", @memhdc% TO old%%
SYS "SDL_CreateTexture", @memhdc%, P%, 2, W%, H% TO new%%
IF @platform% AND &40 ELSE old%% = !^old%% : new%% = !^new%%
IF new%% = 0 ERROR 100, "Couldn't create target texture"
SYS "SDL_SetRenderTarget", @memhdc%, new%%
SYS "SDL_DestroyTexture", old%%, @memhdc%
SYS "SDL_SetRenderDrawColor", @memhdc%, &FF, &FF, &FF, &FF
SYS "SDL_RenderClear", @memhdc%
SYS "SDL_SetWindowSize", @hwnd%, W%, H%, @memhdc%
@size.x% = W%
@size.y% = H%
VDU 24,0;0;W%*2-2;H%*2-2;16
ENDPROC
Note that increasing the size of the text viewport beyond 2048 x 2048 pixels (e.g. using VDU 26 or VDU 28) is liable to crash BASIC. The routine above deliberately leaves the text viewport unchanged.

Passing floats to assembler code or DLLs

If you need to speed up floating point (real number) calculations you may want to do some of the work in assembly language. The BBC BASIC for SDL 2.0 x86 assemblers make this relatively easy by incorporating all the floating point instructions but you have to be careful when passing floating point numbers from BASIC to the assembler code. Specifically you should pass only numeric variables with a # suffix (64-bit doubles):

CALL code, fpvalue#
The same applies if you are passing a floating point value to a function in a DLL. If this requires a pointer to the value pass that as follows:
SYS "function", ^fpvalue#
It is rather more complicated if you are passing the value itself, rather than a pointer, because then you need to use a different method depending on whether your program is running on a 32-bit or 64-bit CPU:
IF @platform% AND &40 THEN
  IF fpvalue#=0 THEN ?(^fpvalue#+7)=&80
  SYS "function", fpvalue# TO result%
ELSE
  SYS "function", !^fpvalue#, !(^fpvalue#+4) TO result%
ENDIF
In the event that you need to pass an array of floating point values to a DLL function the address passed must be that of the first element of the array:
SYS "function", ^fparray#(0)
Note that the assembler code or DLL must be expecting double precision (64-bit) floating-point numbers. BBC BASIC for SDL 2.0 has no built-in support for ordinary 32-bit floats (but see the FN_f4 library routine for a conversion function).

Precautions when using LOCAL arrays

LOCAL arrays are stored on the stack and special precautions are required as a result. Firstly, avoid swapping a local array with a global array (i.e. one stored on the heap). Because SWAP exchanges the array pointers (rather than the array data) the 'global' array may end up pointing to data on the stack, which will become invalid on exit from the function or procedure in which the array was defined. Any subsequent attempt to access the array data will fail, and may crash BASIC. You can safely copy the array, because the data is copied rather than the pointer:

DEF PROChint
LOCAL localarray()
DIM localarray(10)
SWAP globalarray(),localarray() : REM Don't do this!
globalarray() = localarray() : REM This is OK
ENDPROC
Secondly, be careful if your program uses LOCAL arrays and ON ERROR. If an error occurs in a procedure or function in which a local array is defined, BASIC will jump immediately to the ON ERROR routine without passing through the ENDPROC or end-of-function statement. The 'local' array will still exist, but will point to an invalid area of memory (errors cause the stack to be discarded), so again any subsequent attempt to access the contents of the array will fail and may cause a crash. To protect against this either ensure errors (even Escape) cannot occur in those functions or procedures, use ON ERROR LOCAL and RESTORE LOCAL to clear the local array(s), or make sure all your local arrays have different names from any global arrays:
DIM temp(10) : REM Global array
DEF PROChint
LOCAL temp() : REM Don't do this if ON ERROR is active
ENDPROC
Simply changing the name of the global array from temp to (for example) Temp would avoid any problems in the event of an error occurring inside the procedure.

Using DATA in installed modules

Because CALLed and INSTALLed modules cannot use line numbers, the traditional form of the RESTORE statement cannot be used to move the data pointer into such modules. However the relative form RESTORE +n can. A convenient way of arranging this is to associate a restore procedure with each independent block of data you might want to use:

DEF PROCrestore1 : RESTORE +1 : ENDPROC
DATA 1, 2, 3, 4, 5, 6, 7, etc
DATA ...

DEF PROCrestore2 : RESTORE +1 : ENDPROC
DATA 8, 9, 10, 11, 12, 13, etc
DATA ...
Then, when you want to READ the data (which can be done from the main program, or another installed module) you just call the appropriate restore procedure first:
PROCrestore1
READ A, B, C, D, E, F, G, etc

PROCrestore2
READ a, b, c, d, e, f, g, etc
Using this technique you can hide your data away in a separate module.

Re-entrant ON MOVE interrupts

The manual gives the following example of the use of ON MOVE:

ON MOVE PROCmove(@msg%,@lparam%) : RETURN
This will work, and guarantees that the PROCmove procedure will be called once for each ON MOVE event, but there is a snag: because the procedure can itself be interrupted by a subsequent ON MOVE, the events may not be processed in the order in which they arrive! It may well be more important to ensure that the last ON MOVE is processed last, even if it means that some earlier ones are discarded. You can achieve this behaviour by passing the parameters in a global array:
DIM Move%(2)
ON MOVE Move%()=@msg%,@wparam%,@lparam% : PROCmove : RETURN
......
DEF PROCmove
REM Use Move%(0) for @msg%, Move%(1) for @wparam% and Move%(2) for @lparam%
ENDPROC
It is important that the three elements of the Move%() array are used in the same statement, so another interrupt cannot occur between them.

Cunning use of the CASE statement

Testing for several mutually-exclusive possibilities using nested IF...ENDIF statements can be messy:

IF abc%<10 THEN
  state% = 1
ELSE IF abc%=10 THEN
    state% = 2
  ELSE IF abc%>20 THEN
      state% = 3
    ELSE
      state% = 99
    ENDIF
  ENDIF
ENDIF
The unhelpful indentation and the multiple ENDIFs make it unclear what is happening. The same thing can be achieved more elegantly by using the CASE statement in a cunning way:
CASE TRUE OF
  WHEN abc%<10: state%=1
  WHEN abc%=10: state%=2
  WHEN abc%>20: state%=3
  OTHERWISE: state%=99
ENDCASE 
Notice the use of CASE TRUE to force the interpreter to test the truth of each of the conditional expressions.

'Docking' a dialogue box

On occasions you may want your program's user interface to consist solely of a dialogue box, for example if all input/output is by means of buttons, list boxes etc.

To do that you can 'dock' the dialogue box to the main window so that it appears and behaves as a dialogue box but can be minimised just like an ordinary window. This involves resizing the main output window so it is exactly the same size as the dialogue box.

This can be achieved by replacing the normal code which you would use to display the dialogue box, typically something like this:

result% = FN_showdialog(dlg%, &FFFFFFFF80000000, &FFFFFFFF80000000)
with this:
PROC_getdlgrect(dlg%, X%, Y%, W%, H%)
SYS "SDL_SetWindowSize", @hwnd%, W% DIV 2 + 2, H% DIV 2, @memhdc%
SYS "SDL_SetWindowResizable", @hwnd%, FALSE, @memhdc%
VDU 26
result% = FN_showdialog(dlg%, 0, H%)
Where dlg% is the value returned from FN_newdialog.

Automatic window resizing

If the user resizes (or maximises) your window you may want to redraw the contents to fill the new size, but doing that can significantly complicate your program. A much simpler approach is to scale (stretch) the contents of your window to suit the new size.

You can get BBC BASIC for SDL 2.0 do that automatically by incorporating the following statements near the start of your program:

IF POS REM SDL thread sync
ON MOVE PROCresize(@msg%, @lparam%, @size.x%, @size.y%) : RETURN
and adding PROCresize at the end:
DEF PROCresize(M%, L%, X%, Y%) IF M% <> 5 ENDPROC
LOCAL W%, H%
W% = L% AND &FFFF
H% = L% >>> 16
IF W%/H% > X%/Y% THEN
  @zoom% = &8000 * H% / Y%
ELSE
  @zoom% = &8000 * W% / X%
ENDIF
IF @zoom% < &8000 @zoom% = &8000
IF (@platform% AND 7) < 3 OR (@platform% AND 7) > 4 THEN
  @panx% = (X% - W% * &8000 / @zoom%) / 2
  @pany% = (Y% - H% * &8000 / @zoom%) / 2
ENDIF
ENDPROC
This code will also handle orientation changes between portrait and landscape on a mobile device.

Left CONTENTS

HOME Right


Best viewed with Any Browser Valid HTML 3.2!
© Richard Russell 2023