The black art of UDGs on the QL

Anything QL Software or Programming Related.
User avatar
TMD2003
Trump Card
Posts: 168
Joined: Sat Oct 10, 2020 12:18 pm

The black art of UDGs on the QL

Post by TMD2003 »

I'm keeping this separate from the Dumping Ground as I could be digging into a very deep hole. Still, it's one that I think is worth digging into, if it means that I can create UDGs on the QL as easily as I can on the Spectrum.

I'll start with David Nowotnik's Lunar Lander - this was the program where I can get the UDGs to show up on QemuLator, but not on QLAY (or QPC2 either, I just tried that and the graphics are shown as the standard accented characters, though I'll put that down to SMSQ/E being strange about it - just thought it was worth trying).

Here are the processes I've been through, with the observations:

(1) I deleted everything in the Lunar Lander listing except the procedures "initialise" (sets up the screen with all the windows), "udgs" (to perform the black arts, redefining the characters 128-137), and "rocket (x)" (to draw the lander with thrust level x from 0 to 9). So all it would do was draw the lander in window #3 now, and it worked. I tried different CSIZEs and MODE 4, and the UDGs still showed up and made the lander.

(2) I opened a new channel #9, gave it a PAPER colour and CLS, to provide a background to what I was going to do - with the line in the program before opening channels #3, #4 and #5. Trying the "rocket" procedure no longer showed the UDGs - they were returned to the usual accented lower-case characters. Opening #9 and performing all its colour operations after channels #3, #4 and #5 made the UDGs show up again in channel #3. This does at least show that the order that channels are opened is important - presumably they're being assigned to different parts of memory. But, as long as channel #3 is opened first, I should be able to get UDGs to print in it if I don't change the code too much.

(3) Deleting or closing all the channels above #3 did not affect the UDGs.

(4) A more radical change was to open channels #3 to #9 first (just OPEN #n,scr without all the 512x256a0x0 afterwards), then define all the windows 1-8 so that these are small, strip-shaped windows on top of #9. I wrote a short routine that would print the characters 128-190 in all windows 0-8, to see what would show up. As I expected, the UDGs are only shown in channel #3, with the initially-defined "failure" character printed for CHR$ 138 to 190, which ar undefined by this program.

(5) I also tried changing the pattern of the "failure" character, and this showed up in channel #3 as expected.

The listing in the "code" tags below is what I've done so far - the "udgs" procedure is still recognisable as David Nowotnik's original, even if I have annotated it with a bunch of REMs:

Code: Select all

100 windows
110 udgs
120 FOR w=0 to 8: test w: NEXT w
650 STOP
1000 REMark *****************************
1010 DEFine PROCedure windows
1020 REMark *****************************
1030 LOCal n
1040 MODE 4
1050 FOR n=3 TO 8: OPEN #n,scr: NEXT n
1060 OPEN #9,scr_512x206a0x0: PAPER #9,2: CLS #9
1070 FOR n=1 TO 8
1080 WINDOW #n,500,20,6,(n*22)-10
1090 PAPER #n,0
1100 INK #n,4
1110 CSIZE #n,0,0
1120 CLS #n
1130 NEXT n
1140 REM old command was: WINDOW #3,40,70,350,20: CSIZE #3,0,0
1150 END DEFine
1200 REMark *****************************
1210 DEFine PROCedure udgs
1220 REMark *****************************
1230 LOCal a,n,byte,udg_start
1240 a= RESPR(0):IF a>261900 THEN a=RESPR (244)
1250 udg_start=261900
1260 POKE_L (PEEK_L (166764)+46), udg_start
1270 RESTORE 1080
1280 FOR n=0 TO 100
1290   READ byte: POKE (udg_start+n),byte
1300 END FOR n
1310 DATA 127: REM 1 less than CODE of first UDG
1320 DATA 10: REM number of UDGs defined (was 15 in the original - even though only 10 characters were defined)
1330 DATA 124,0,124,0,124,0,124,0,124: REM failure character
1340 DATA 0,32,60,60,60,32,32,32,32: REM CHR$(128)
1350 DATA 56,56,56,56,124,124,124,124,124: REM CHR$(129)
1360 DATA 0,0,4,12,28,60,124,124,124: REM CHR$(130)
1370 DATA 124,124,124,124,124,124,124,124,124: REM CHR$(131)
1380 DATA 0,0,64,96,112,120,124,124,124: REM CHR$(132)
1390 DATA 12,12,24,48,96,96,96,96,112: REM CHR$(133)
1400 DATA 96,96,48,24,12,12,12,12,28: REM CHR$(134)
1410 DATA 124,56,56,16,0,0,0,0,0: REM CHR$(135)
1420 DATA 124,124,56,56,56,56,16,0,0: REM CHR$(136)
1430 DATA 124,124,56,56,56,56,56,16,0: REM CHR$(137)
1990 END DEFine 
2000 REMark *****************************
2010 DEFine PROCedure test (w)
2020 REMark *****************************
2025 LOC n
2030 AT #w,0,0: PRINT #w,"TEST WINDOW #";w
2040 FOR n=128 TO 190
2050 AT #w,0,(n-112): PRINT #w; CHR$(n);
2060 NEXT n
2070 END DEF
Lines 1240 to 1260 are where all the action seems to be:
1240 a= RESPR(0):IF a>261900 THEN a=RESPR (244)
1250 udg_start=261900
1260 POKE_L (PEEK_L (166764)+46), udg_start

I don't understand the function RESPR particularly well (yet), but it seems that it's the QL's equivalent of the CLEAR command on the Spectrum, just not used in a way that's obvious. When defining a new character set on the Spectrum, if required 768 bytes are to be poked at 64512 onwards, the program will start with CLEAR 64511 - hence, reserving an area of memory (usually for machine code - character sets are treated the same way) that is off-limits to any BASIC program or variables.

But then, 1260 reveals an equivalent to what I know on the Spectrum. And that is, for those who aren't up to speed on that: POKE USR "a",(value) pokes the first byte of the graphics character "A" (CHR$ 144), POKE USR "a"+1,(value) pokes the second byte, and so on and so on up to POKE USR "u"+7, which is the highest address it's possible to POKE. Hence, the only difference is where USR "a" is between a 16K Spectrum and a 48K-or-more Spectrum - 32600 on a 16K model and 65368 on a 48K-and-upwards model. In effect, it's a convenient shorthand so that the same program could be used on both models in the early days (provided it would fit into the 16K's tiny memory).

The QL, by the looks of things, has no such shorthand, and the QL's equivalent of USR "a" isn't fixed, so it has to be calculated manually every time. Once this is known, the routine from lines 1280-1300 pokes the UDG values in much the same way as they'd be done on a Spectrum, just that there's none of them per character and they're all values 0-31 multiplied by 4.

What I need to discover is:
- What is being PEEKed in line 1260, why is it being PEEKed, why is it specifically 46 that is being added to it, and why does udg_start have to be POKEd to this address?
- Why do the values used by RESPR change between programs (I saw another one of David Nowotnik's programs that uses RESPR(1024) here), and what do they need to be in any specific case?

What this should lead to is a general version of lines 1240 to 1260 that can be applied to any QL program - i.e. if the channel number is known, and the CODE of the first character defined is known, then the value udg_start can always be calculated for those values, and UDGs can be POKEd to any one of the foreign-and-custom characters, on any channel, as far as the QL's channel numbers will reach.

BIG EDIT:

I immediately searched for "QL system variables", and found the Technical Manual on Dilwyn's site. This explains something:
2.1.2 System Variables -
The Qdos system variables are a block of memory containing information required by the operating system. This block is normally located at address $28000, but is not fixed at this address in principle. Applications programs should not rely on that fixed address, but should get the address of the base of system variables by calling the MT.INF trap (see section 13.0).
If I type in PRINT PEEK_L(166764) after running the program I quoted above, it returns the value 168768. Add this to it and we get 168814. Hence, the POKE we are making is POKE_L 168832,261900 (where 261900 is the calculated udg_start).

Does the value 166764 mean anything to anyone, the way 23607 means something to me? On a Spectrum, 23607 is the high byte of the two-byte system variable CHARS, which by default is set to 60, revealing the character set in ROM. (The low byte at 23606 is set to 0). Hence, POKE 23607,(value) is how the entire character set is changed - define it somewhere else, i.e. above RAMTOP, then just POKE a different value of CHARS - limiting the start address of the first byte to one exactly divisible by 256 means there's no need to POKE 23606 as well as 23607. Technically, then, the address of CHARS is 23606, but 23607 is the only one of th two bytes that usually needs to be considered.

Is 166764 some kind of pointer? If I PEEK_L 166764, as in the above program, does this reveal the location of the QL's equivalent of CHARS for channel #3 - or is the channel number covered by the value 46 in the POKE, somehow?

The truth is out there.


Spectribution: Dr. Jim's Sinclair computing pages.
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
User avatar
RalfR
Aurora
Posts: 872
Joined: Fri Jun 15, 2018 8:58 pm

Re: The black art of UDGs on the QL

Post by RalfR »

---- snip ----
1240 a= RESPR(0):IF a>261900 THEN a=RESPR (244)
1250 udg_start=261900
1260 POKE_L (PEEK_L (166764)+46), udg_start
---- snip ----

This remimds of old QL games. Seems to be a very bad behaviour.


4E75 7000
User avatar
dilwyn
Mr QL
Posts: 2761
Joined: Wed Dec 01, 2010 10:39 pm

Re: The black art of UDGs on the QL

Post by dilwyn »

You're thinking in Spectrum terms with these fonts - don't, the QL bears no resemblance to the Spectrum way of doing things I'm afraid.

Each channel on the QL can have a separate font if you want - changing the font for one window channel need not change one for other channels as you've found out. This is why the POKE_L commands should not be used - each channel has its own data structure, which includes pointers to the fonts. It's much, much easier to use the CHAR_USE command from Toolkit 2.

Also, RESPR works in a completely different way to CLEAR address on a Spectrum. On a Spectrum, you explicitly specify that address to lower top of memory, whereas on a QL the system does the memory allocation for you and returns the address of whatever area it chose to allocate.

There are two ways of allocating memory. RESPR allocates "permanent" memory and is generally used for loading system extensions such as adding new Superbasic commands.

ALCHP from Toolkit 2 is used for temporary allocations you can set up, use, then release (using RECHP) to free up the memory when finished with it.

Each channel can have two fonts, usually the first has the standard ASCII range 32 to 127, the second has the extended, accented characters (plus the default or "fail" character)

As you've found out, SuperBASIC wasn't originally designed to handle user defined graphics and new fonts. That's why the older programs POKEd everything in sight and programs suddenly stopped working when anything changed.

So, the best way to to install a font is a short routine like this. Either font address can be 0 to reset to use the one built into the ROM.

base = RESPR(memory_required) : rem or use ALCHP if using toolkit 2
rem poke the font data into place in the RESPRed memory
font1_address=0 : rem use font built into ROM
font2_address=base
CHAR_USE #channel,font1_address,font2_address

If you really don't want to use the Toolkit 2 font extensions, there are plenty of other font handling extensions in the various other extensions out there, including some on toolkits page on my website. You could even resort to using the CHAN_L extension from Simon Goodwin's DIY Toolkit series which would allow you to access channel data structures more reliably.


User avatar
TMD2003
Trump Card
Posts: 168
Joined: Sat Oct 10, 2020 12:18 pm

Re: The black art of UDGs on the QL

Post by TMD2003 »

Strike me down with a feather from the world's tiniest bird! All I had to do was RTFM a bit - or, rather, read David Nowotnik's article - the one I typed out by hand a few days ago! - and the answer was there.
David Nowotnik wrote:The easiest way to find the start of a particular channel information block is to refer to another information block in RAM, which (in the 'JM' version, at least) starts at 166752. Using PEEK_L(166752) will return the start address of the channel information block.
Fortunately, this returns the same value whether I'm using JM or JS.
David Nowotnik wrote:The same PEEK on addresses 166756 and 166760 will give channel 1 and channel 2 start addresses, respectively. As more channels are OPENed, their information block start addresses appear 166764, 166768, etc. So, the vector to the 'extra' character bit pattern block in channel 2 is: PEEK_L(166760)+46, and PEEK_L of the resultant address will give the start address of the bit pattern block in ROM (unless you have changed the vector).
Wham!
I changed 166764 to 166760 in line 1260, and the UDGs appeared in channel 2 instead of channel 3. Double up the line, and I can have both.

It shouldn't be too hard from here to bash this routine into one that can be called several times without running through the entire "udg" procedure every time. Bear with me a mo...

EDIT:

I am doing well! However, the snag I've now run into is if I wanted to define the entire UDG range. The loop n that POKEs all the bytes has to run 9 times per character, plus 10 - a total of 567 times. I've found it stops with an "End of file" when n=488... possibly I'd have to RESPR some more memory doing it this way? Either way, even with this restriction I can define a block of 53 characters, and given that I could only ever get 21 on the Spectrum (or 19 on the 128K models), this is luxury by comparison. (That said, I have an example program on Spectribution about what can be done with character sets, especially if I define two at once for 192 custom characters: et voilà.)

Still, it's working - and more to the point, it's working for the purpose I need it.

I have already awarded myself a corned beef sandwich... although that's mainly because my intended ingredients for dinner hadn't defrosted.


Spectribution: Dr. Jim's Sinclair computing pages.
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
User avatar
TMD2003
Trump Card
Posts: 168
Joined: Sat Oct 10, 2020 12:18 pm

Re: The black art of UDGs on the QL

Post by TMD2003 »

And just to prove that I don't give up easily:

Image

Image
Last edited by TMD2003 on Sat Aug 28, 2021 11:40 am, edited 1 time in total.


Spectribution: Dr. Jim's Sinclair computing pages.
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
User avatar
NormanDunbar
Forum Moderator
Posts: 2273
Joined: Tue Dec 14, 2010 9:04 am
Location: Leeds, West Yorkshire, UK
Contact:

Re: The black art of UDGs on the QL

Post by NormanDunbar »

It seems your images are "broken". :( I mean, they are showing with the "broken image" icon in the thread.

Cheers,
Norm.


Why do they put lightning conductors on churches?
Author of Arduino Software Internals
Author of Arduino Interrupts

No longer on Twitter, find me on https://mastodon.scot/@NormanDunbar.
User avatar
Pr0f
QL Wafer Drive
Posts: 1300
Joined: Thu Oct 12, 2017 9:54 am

Re: The black art of UDGs on the QL

Post by Pr0f »

The images are working for me...


User avatar
Andrew
Aurora
Posts: 794
Joined: Tue Jul 17, 2018 9:10 pm

Re: The black art of UDGs on the QL

Post by Andrew »

Images are not working for me - they are showing with the "broken image" icon in the thread.


User avatar
vanpeebles
Commissario Pebbli
Posts: 2821
Joined: Sat Nov 20, 2010 7:13 pm
Location: North East UK

Re: The black art of UDGs on the QL

Post by vanpeebles »

I have a feeling they will be http and not https, certain browsers have taken to blocking them now.


User avatar
TMD2003
Trump Card
Posts: 168
Joined: Sat Oct 10, 2020 12:18 pm

Re: The black art of UDGs on the QL

Post by TMD2003 »

...this was mentioned on Spectrum Computing as well - the https thing, not necessarily broken images. Until that day, I had no idea that such a thing as https was ever available for us mere mortals, rather than big corporations that handle vast amounts of data. I'll make it a project for November, though I have no idea how long it'll take to sort once I've held up my end of the bargain.

The images show up fine for me, incidentally... and I'm on Opera, whatever the latest version is.


Spectribution: Dr. Jim's Sinclair computing pages.
Features my own programs, modified type-ins, RZXs, character sets & UDGs, and QL type-ins... so far!
Post Reply