Sunday, 27 September 2020

Lunchtime Coder #2 - BBC Micro Screen Graphics Viewer

After some success with the ZX Spectrum screen buffer interpreter ( See Post Here ) , I decided to have a go at an Acorn BBC Micro (Model B) screen interpreter.

It turned out to be a lot tougher going than I expected..

GOAL - To take a ".uef" BBC Micro Memory snapshot file, and interpret the screen buffer area of the memory into a display screen as would have been seen on the TV / monitor.

Fool that I am, I thought that this would be a reasonably straight forward process as it had been on the ZX spectrum however, the BBC Micro has different screen modes which have different resolutions and different colour depths. Also The start of the screen buffer is user definable and the colours available in that mode are also user definable.

This means that a lot of different values need to be read from the BBC Micro's memory before you can start to interpret the screen buffer into something understandable.

For the table below, the graphic resolutions for modes 3, 6 and 7 can be ignored as they are either text only or teletext based modes. Also there is only 8 colours listed for modes 2 and 7 as even though you can assign them to flash, there is still only 8 base colours.

I'm going to ignore the text only modes (Modes 3, 6 and 7) and concentrate specifically on the graphics screen modes.

A lot of BBC Micro games were done in Mode 2 or Mode 5. The horizontal resolution for those screens is reduced (160 instead of 320) however, each pixel in the screen buffer is drawn two pixels wide on screen, so that the overall picture on screen is still 320 pixels wide, but takes half the memory. The downside of this is that the screen is only half as detailed horizontally.

There are a lot of values in various BBC Micro memory locations that define how the graphics are drawn, and reading all of these is essential to be able to draw the screen correctly.

DEV 001 - A program that skips the ".euf" file header and footer, that reads the bytes from the BBC Micro memory dump part of the file, converts them into bits, writes the bits into an array so that the array is set out like the BBC Micro memory would be, and then reads the values required to draw the screen from the array. The output should be some values read from the array and converted into decimal.

There are three types of value to read and convert to decimal values:
1 - straight forward single Byte values
2 - Higher value spread over two bytes
3 - Higher two byte values with imaginary low byte that is always at 255.

You can see from the values below in DEV 001 that a number of other screen drawing related values can be derived from various BBC Micro memory locations.

DEV 002 - Now that we can read the values from the memory dump, lets try drawing something. The maximum resolution that the BBC micro can draw at is 640 x 256, so if we base the actual output display around a 1280 x 1024 size, this means that:

Mode 0 pixel is 2 x 4 interpreted pixels
Mode 1 Pixel is 4 x 4 interpreted pixels
Mode 2 pixel is 8 x 4 interpreted pixels
Mode 4 pixel is 4 x 4 interpreted pixels
Mode 5 pixel is 8 x 4 interpreted pixels

One thing I cant find in the BBC Micro memory or derive from the values in the memory is how it knows to draw wide pixels for mode 2 and mode 5, other than using a hard coded value of 320 for the screen width and then using the Current Graphics Window Right Column value.

A handy thing that I do remember from my childhood watching BBC micro games load first from tape, and then later on from disk, is that sometimes the loading game ran into the screen buffer and you could see the bits in the memory being loaded.

This would then draw garbage to parts of the screen, character by character, starting at the top left of the screen and working left to right and top to bottom.

When I say "Character by Character", a character is essentially an 8 x 8 square of pixels, and as the bits were drawn, they would again be drawn left to right, and top to bottom. (Later found to be incorrect.. see DEV 003).

With this in mind, the drawing "loop" should be 4 loops inside each other:

Outer loop 1 - Character Y position (derived from current text window top and bottom row
Outer Loop 2 - Character X psoition (derived from current text window left and right column)
Inner Loop 1 - Inside Character Pixel Y position ( 0 to 7 )
Inner Loop 2 - Inside Character Pixel X position ( 0 to 7 )

Then an x and y multiplication factor will be needed to draw the correct pixel size, depending on the mode being drawn.

Inside the four loops is the drawing program in four parts. Read from the screen display start memory location (adding an offset as you read and draw the bits) and decide what value the pixel is. Check what colour corresponds to that value. Draw the pixel. Update the offset to read the next pixels data.

Lets see how it looks:

What I've Got

What it should look like..

Obviously there is some issues here. Apart from the pixels being all over the place, there is also colour issues but.. I seem to have picked an odd one to start anyway as the screen mode is 1 (4 colours) and yet I can see 6 colours on screen on the proper one ?!

This routine works fine with mode 0, but mode 0 is simple to draw; 1 bit=1 pixel so read the bit and draw the pixel. Mode 1 and 4 have 2 bits per pixel, and to complicate this, the two associated bits are 3 bits apart. Four pixels per byte. Pixel 1 is bit 0 and 4, Pixel 2 is bit 1 and 5. Pixel 3 is bit 2 and 6 and Pixel 4 is bit 3 and 7.

Mode 0 - Works perfectly but is somewhat easier to draw than other modes; 1 bit per pixel

DEV 003 - After a lot of head scratching and staring at what I was drawing compared to what it should look like, I had a lightbulb moment!

I assumed that the pixels were drawn for evert mode as 8 pixels in a row before going to next row, and this is why Mode 0 worked, however I was wrong. Mode 0 works that way because it has 8 pixels per byte. What actually happens is a bytes worth of pixels is drawn (8, 4 or 2 pixels) and then you drop a row and draw the next byte and so on. So for mode 0, it will be draw 8 pixels then start the next row. For mode 1 and 4, it will draw 4 pixels and then start the next row and for mode 2 and 5, it will draw two pixels and start the next row.

This means that the amount of columns drawn is the difference between current text window left column and current text window right column but then multiplied by Bit Depth or Bits per pixel.

Bingo! Now we have something that looks a bit more like a BBC Micro screen.

However, While this looks good, It doesn't always work.

Case 1 - Citadel

Garbage and squished to the bottom of the display
Should be no garbage, and vertically central

There are two things wrong with this image;

1. You can see some garbage at the top of the screen. 2. The image is at the bottom of the screen, not centred as it should be.

The garbage at the top of the screen (The Top 8 rows of characters or top 64 rows of pixels) is because part of the screen buffer memory has been used as general memory.

You can also see from the values at the left that "Current Text Window Top Row" is set to 8, not 0. You can also see that the "Current Graphics Window Top Row" is set to 176, not 255

So its plausable that because the "Current Text Window Top Row" is set to 8, and the top 8 rows of characters are garbage, that the top 8 rows shouldn't be drawn. Thats pretty easy to fix.

There is also an offset for the picture of 24 pixels from the top, and 40 pixels from the bottom. Where that comes from, I do not know.. And what part the "Current Graphics Window Top Row" is set to 176, not 255 plays, again I do not know. None of these numbers leave you with 24 rows of pixels at the top.

Case 2 - Ultimate Title Screens

Whats in the screen buffer
What it Should look like

All of the screen data is there, but clearly the picture is narrower than the screen, although the current text window and current graphics window width settings are all set to max.

I guess that there is some different trickery going on here that doesn't conform to standard screen displaying, or some additional memory locations with settings that I should have included in the program.

Final Thought:

The BBC Micro used to get seriously knocked when compared to the ZX Spectrum and the Commodore 64 because of its limitations, however if you take a look at the screen shots below, you can see that this wasn't always deserved.

ZX Spectrum Atic Atac Start Screen
BBC Micro Atic Atac Start Screen

No comments:

Post a Comment