I have spent a bit more time tonight working on hardware support for proportional fonts.
For those coming in late, the VIC-IV already has the ability to draw skinny characters 2, 4 or 6 pixels wide as well as the usual 8. This can be used to construct large characters from one or more 8x8 character blocks to make any even number of pixels in width on screen. For a large type face, each character may be several 8x8 character blocks wide.
This means that a row of proportional text may have a variable number of characters, because if there are skinny characters, then more will fit on a line. Conversely, if there is no text on the right of the display, then it doesn't make sense to waste RAM describing empty characters. Thus I have followed Jeremy's idea of implementing a special end of line marker, so that each row can differ in length, and we can hopefully use RAM much more efficiently when faced with large high-resolution text displays.
In the previous post I describe the work on skinny characters.
Now I have just about finished implementing the end of line markers, although as I write there is one remaining bug which is quite obvious in the screenshot below in the form of the vertical bars that shouldn't be there:
At first glance, this looks mostly like a normal C64 text mode display. However the entire screen is described using only about 80 bytes each of screen and colour RAM:
The screen RAM:
:0400 01 C0 02 00 03 00 04 00 05 00 FF FF 06 00 07 00
:0410 08 00 FF FF FF FF FF FF FF FF FF FF 09 00 0A 00
:0420 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
:0430 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
:0440 0B 00 FF FF 0C 00 FF FF 0D 00 FF FF
The colour RAM:
:D800 00 01 02 03 04 05 07 01 02 02 02 02 02 0E 0F 0E
:D810 0E 0E 0E 0E 00 00 00 00 02 03 04 05 02 08 01 02
:D820 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E
:D830 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E
:D840 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E
:D850 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E 0E
To follow what is going on, remember that in this mode ($D054 = $01), two bytes are used to describe each character. The first byte is the low 8 bits of the character number, and the low nybl of the second byte are extra character number bits. The top two bits of the second byte set the width of the character as it is displayed on screen.
Thus $01 $C0 at $0400 draws only the left two pixels of the letter A, which shows up as the stumpy black line in the screen shot. The rest of the row of text is now offset by 6 pixels compared to normal.
Normal characters are encoded from $0402 - $0409. This is followed by $FF $FF which tells the VIC-IV that there is an early end of line. Thus the letter F described by $06 $00 at $040C-$040D appears at the beginning of the next line, and no other characters appear to the right of the letter E.
Colour RAM is drawn in a somewhat strange way, that I will probably fix. Within a row, the colour RAM bytes are read one per character, and so $D801 has $01 (white), and this is applied to the letter B encoded in $0402-$0403. That seems quite reasonable. But following an end of line, the colour RAM address catches up with the screen RAM. What I intend to do is make the colour RAM address advance two bytes for each character, so that the extra byte of information can be used. I might use this to allow skinny characters to be odd widths, and also to have a kind of super-extended-background-mode, where the other bits select the background colour.
However, before I do any of that, I need to fix the bug that happens when a character row consists only of a $FF $FF end of row marker. In that case the length of the character data is incorrectly set to the maximum value, instead of zero, and so whatever rubbish was hanging around in the character raster buffer gets redrawn.
Although, as I write that, I am not entirely convinced that this is the whole story. Indeed, it seems that the three bars are the contents of $3894, $300C, and $80CB. Very strange.
Hopefully my little bug-fix will work, otherwise I will just specify that each row must have at least one character, so you would use something like $20 $00 $FF $FF to make a row empty with one space at the beginning.
Friday, 31 October 2014
Thursday, 30 October 2014
Hardware support for proportional fonts and other text mode effects
I was talking with Jeremy in the lab today, and we got talking about hardware support for proportional fonts on the C65GS. I had thought about doing this before, but had put it on the back-burner for a while, because I hadn't really come up with an elegant solution.
While we were talking today, however, we got talking about how I had 2 spare bits in the character number in 16-bit text mode, where two screen RAM bytes are used for each character. I don't remember exactly who came up with which part of it, but by the end of it, we had come up with a workable solution not only for hardware support for proportional fonts, but anti-aliased fonts, too (more on that in a future post).
Proportional fonts really just requires specifying the width of each character, so that some can be narrower than others. With two spare bits, it was easy enough to allow characters to be 8, 6, 4 or 2 pixels wide. Characters more than 8 pixels wide can then be constructed with any number of 8 pixel wide characters, and one narrow character to make it easy to obtain any even number of pixels in width. It would be nice to allow odd widths, but it feels like it is a reasonable trade-off to have to round to the nearest 2 pixels.
Similarly, tall fonts can be constructed with multiple rows of text. For fonts not a multiple of 8 pixels high, a raster split could be used to skip one or more rows of pixels.
The result is conceptually very simple. The main trade-off is that skinny characters still use the same amount of RAM as full-width ones, but that seems a reasonable trade-off. In fact, it was so simple that I was able to implement it in about an hour, and the main functionality worked first time, as can be seen below:
Each row has a different width specified for the "A" at the beginning of the line.
To use this feature, you first have to enable 16-bit text mode, where two bytes of screen memory describe each character on screen. This is done by setting bit 0 in $D054.
In terms of screen RAM, the memory for the rows here looks something like:
0400 01 00 02 00 03 00 04 00 05 00
0428 01 40 02 00 03 00 04 00 05 00
0450 01 80 02 00 03 00 04 00 05 00
0447 01 c0 02 00 03 00 04 00 05 00
The 01, 02 ... 05 is the character numbers for A through E, and these stay the same. For all but the A's, the 2nd byte is 00 to indicate that no special attributes are set for the characters B through E. However, the high-byte for the A characters is modified in each row to have all possible combinations in bits 6 and 7: the higher the value, the narrowerer the character.
While I am fiddling with text attributes, I should explain what the other bits in the high byte mean:
bits 0 - 3 = bits 11 - 8 of the character number. i.e., there can now be 4,096 characters in a character set.
bit 4 = flip character horizontally
bit 5 = flip character vertically
The following screen shot shows the flip bits in action:
The contents of screen RAM is:
0400 01 00 02 10 03 20 04 30 05 00
The ability to flip characters is designed to be used with full-colour text mode, where (some or all) characters on the screen consist of 64 8-bit pixels, providing a graphics mode that can be quickly scrolled.
Flipping characters in such a mode allows 64-byte characters to be reused in a graphical display without too much obvious repetition, e.g., for in textures in games.
Combining this with variable width characters introduces even more opportunity to reuse characters, and thus allow more interesting and complex high-resolution graphics within the limits of the 128KB of chipram.
While we were talking today, however, we got talking about how I had 2 spare bits in the character number in 16-bit text mode, where two screen RAM bytes are used for each character. I don't remember exactly who came up with which part of it, but by the end of it, we had come up with a workable solution not only for hardware support for proportional fonts, but anti-aliased fonts, too (more on that in a future post).
Proportional fonts really just requires specifying the width of each character, so that some can be narrower than others. With two spare bits, it was easy enough to allow characters to be 8, 6, 4 or 2 pixels wide. Characters more than 8 pixels wide can then be constructed with any number of 8 pixel wide characters, and one narrow character to make it easy to obtain any even number of pixels in width. It would be nice to allow odd widths, but it feels like it is a reasonable trade-off to have to round to the nearest 2 pixels.
Similarly, tall fonts can be constructed with multiple rows of text. For fonts not a multiple of 8 pixels high, a raster split could be used to skip one or more rows of pixels.
The result is conceptually very simple. The main trade-off is that skinny characters still use the same amount of RAM as full-width ones, but that seems a reasonable trade-off. In fact, it was so simple that I was able to implement it in about an hour, and the main functionality worked first time, as can be seen below:
Each row has a different width specified for the "A" at the beginning of the line.
To use this feature, you first have to enable 16-bit text mode, where two bytes of screen memory describe each character on screen. This is done by setting bit 0 in $D054.
In terms of screen RAM, the memory for the rows here looks something like:
0400 01 00 02 00 03 00 04 00 05 00
0428 01 40 02 00 03 00 04 00 05 00
0450 01 80 02 00 03 00 04 00 05 00
0447 01 c0 02 00 03 00 04 00 05 00
The 01, 02 ... 05 is the character numbers for A through E, and these stay the same. For all but the A's, the 2nd byte is 00 to indicate that no special attributes are set for the characters B through E. However, the high-byte for the A characters is modified in each row to have all possible combinations in bits 6 and 7: the higher the value, the narrowerer the character.
While I am fiddling with text attributes, I should explain what the other bits in the high byte mean:
bits 0 - 3 = bits 11 - 8 of the character number. i.e., there can now be 4,096 characters in a character set.
bit 4 = flip character horizontally
bit 5 = flip character vertically
The following screen shot shows the flip bits in action:
The contents of screen RAM is:
0400 01 00 02 10 03 20 04 30 05 00
The ability to flip characters is designed to be used with full-colour text mode, where (some or all) characters on the screen consist of 64 8-bit pixels, providing a graphics mode that can be quickly scrolled.
Flipping characters in such a mode allows 64-byte characters to be reused in a graphical display without too much obvious repetition, e.g., for in textures in games.
Combining this with variable width characters introduces even more opportunity to reuse characters, and thus allow more interesting and complex high-resolution graphics within the limits of the 128KB of chipram.
Wednesday, 29 October 2014
My real C65 keyboard has arrived, very well wrapped
A while back I won an e-bay auction for a genuine C65 keyboard (without printing on the top of the keys). Today it arrived, as you can see below. It was nice to again touch a C65 keyboard after about four years of not having one. The lack of printing on the top of the keys is not a big problem, as the key positions are fairly easy to figure out, and the printing on the front provides strong clues as well.
I also have to commend the seller for his meticulous packing. Below you can see lots of soft packing material, which I could only get to once I had cut through quite a lot of tape that held the box very well closed.
Inside that was the following inner part, also completely mummified in tape. This keyboard had no hope of escaping in transit.
Then once I de-mummified that, it was itself a further very large bag, sealed at the mouth with further tape:
Removing that tape, the quite large bag looked to also be heat-sealed.
Finally inside that the keyboard was sitting in its original light-blue Mitsumi packing bag, and in very good condition -- better than the original one I had, on which I had to repair a track on the cable when it first arrived.
So now I have a real C65 keyboard, a recreation C65 motherboard, lacking only the custom chips, and my FPGA design. I am getting closer to being able to make a complete working unit with real hardware. I am still leaning towards making a laptop form-factor C65GS, if only I can find a nice LCD panel that can do 1920x1200 or 1920x1080 and isn't too big. About 13" would be ideal, I think.
Hardware thumbnail generation is now usable
Today I had a chance to fix a few bugs with the hardware thumbnail generation, and actually test it out by writing a small program that does a raster split, with the thumbnail being draw in 256-colour mode in the bottom corner of the screen every frame, as you can see below:
There are still a few glitches, but is is working pretty nicely. In particular, you can see that it is being drawn in real-time, because the thumbnail contains an image of the thumbnail that contains an image of the thumbnail :)
Things you can't see, is that the thumbnail is a bit different every frame, I think because the counter that decides which raster to look at isn't reset at the start of each frame. This causes some weird things to happen. Also, the thumbnail just contains the value of chosen pixels. I am considering changing this so that it shows the average of the pixels in the sample area of the raster line, but at the same time, the current scheme works fairly well.
This is a nice milestone for a few reasons.
First, the hardware thumbnail generator is clearly working to some reasonable degree.
Second, full-colour text mode is working fairly well as well, such that I could write this program.
And finally, I have actually written a programme that does something (slightly) useful, using C65GS special features, and it works :)
Next stop is to fix the counter problem, and see if I am then happy enough with it, and if so, to move on to some of the other interesting things in my queue, like enhanced sprites.
There are still a few glitches, but is is working pretty nicely. In particular, you can see that it is being drawn in real-time, because the thumbnail contains an image of the thumbnail that contains an image of the thumbnail :)
Things you can't see, is that the thumbnail is a bit different every frame, I think because the counter that decides which raster to look at isn't reset at the start of each frame. This causes some weird things to happen. Also, the thumbnail just contains the value of chosen pixels. I am considering changing this so that it shows the average of the pixels in the sample area of the raster line, but at the same time, the current scheme works fairly well.
This is a nice milestone for a few reasons.
First, the hardware thumbnail generator is clearly working to some reasonable degree.
Second, full-colour text mode is working fairly well as well, such that I could write this program.
And finally, I have actually written a programme that does something (slightly) useful, using C65GS special features, and it works :)
Next stop is to fix the counter problem, and see if I am then happy enough with it, and if so, to move on to some of the other interesting things in my queue, like enhanced sprites.
Thursday, 23 October 2014
Hardware thumbnail generator for task-switcher
One of the main reasons for implementing the hypervisor is so that it will be possible to switch between different tasks running on the machine. The tasks won't be running at the same time, but rather they will be suspended while another task is running.
For a task-switcher to be nice, it would be really handy to be able to show a low-res screen-shot of the last state of each task so that the user can visually select which one they want. In other words, to have something that is not too unlike the Windows and OSX window/task switcher interfaces.
However, this is tricky on an 8-bit computer that has no frame buffer, and may be using all sorts of crazy raster effects.
Thus I need some way to have the VIC-IV update a little low-res screen shot, i.e., a thumbnail image, that the hypervisor can read out, and retain for later task-switching calls to show the user what was running in each task before they were suspended.
So I set about implementing a little 4KB thumbnail buffer which is automatically written to by the VIC-IV, and which can be read from the hypervisor. This resolution allows for 80x50, which should be sufficient to get the idea of what is on a display. Each pixel is an 8-bit RRRGGGBB colour byte.
Because the VIC-IV writes the thumbnail data directly from the pixel stream, it occurs after palette selection, sprites and all raster effects. That is, the thumbnails it generates should be "true".
After a bit of fiddling around, it is mostly working.
To test it, I wrote a little BASIC programme that reads from the one-byte access to the 4KB buffer, copying it to $4000-$4FFF. Then I used the serial monitor to grab that copy of the data, and wrote some UNIX shell scripts and a little C programme to munge it into an 80x50 Windows BMP file.
Here is how it looks, with the image rather enlarged to make it easier to see:
While not perfect, it is an improvement on the first capture, where I forgot to read from the start of the thumbnail buffer, so it was all out of whack:
In need to find out what is causing the "clouds", and also why it is writing only 77 pixels per line instead of 80 pixels per line.
But other than these problems, I am well on the way to being able to present a nice graphical display to allow for switching between tasks from the hypervisor.
For a task-switcher to be nice, it would be really handy to be able to show a low-res screen-shot of the last state of each task so that the user can visually select which one they want. In other words, to have something that is not too unlike the Windows and OSX window/task switcher interfaces.
However, this is tricky on an 8-bit computer that has no frame buffer, and may be using all sorts of crazy raster effects.
Thus I need some way to have the VIC-IV update a little low-res screen shot, i.e., a thumbnail image, that the hypervisor can read out, and retain for later task-switching calls to show the user what was running in each task before they were suspended.
So I set about implementing a little 4KB thumbnail buffer which is automatically written to by the VIC-IV, and which can be read from the hypervisor. This resolution allows for 80x50, which should be sufficient to get the idea of what is on a display. Each pixel is an 8-bit RRRGGGBB colour byte.
Because the VIC-IV writes the thumbnail data directly from the pixel stream, it occurs after palette selection, sprites and all raster effects. That is, the thumbnails it generates should be "true".
After a bit of fiddling around, it is mostly working.
To test it, I wrote a little BASIC programme that reads from the one-byte access to the 4KB buffer, copying it to $4000-$4FFF. Then I used the serial monitor to grab that copy of the data, and wrote some UNIX shell scripts and a little C programme to munge it into an 80x50 Windows BMP file.
Here is how it looks, with the image rather enlarged to make it easier to see:
While not perfect, it is an improvement on the first capture, where I forgot to read from the start of the thumbnail buffer, so it was all out of whack:
In need to find out what is causing the "clouds", and also why it is writing only 77 pixels per line instead of 80 pixels per line.
But other than these problems, I am well on the way to being able to present a nice graphical display to allow for switching between tasks from the hypervisor.
A 3rd party operating system for the C65GS?
I was quite surprised today in a happy way to see that someone is considering making an operating system for the C65GS:
http://65.theace.sk/index.html
This would be a port of the yet-to-be-complete ACE128 operating system.
I understand that Miro could do with some contributors to help, whether for the 65GS or 64/128 version.
http://65.theace.sk/index.html
This would be a port of the yet-to-be-complete ACE128 operating system.
I understand that Miro could do with some contributors to help, whether for the 65GS or 64/128 version.
Sunday, 19 October 2014
Virtualisation and Task Switching
One of the features I have wanted to include in the C65GS from early on is some sort of task switching and rudimentary multi-tasking.
Given the memory and processor constraints, I don't see the C65GS as running lots of independent processes at the same time. Rather, I want it to be possible to easily switch between different tasks you have running.
For example, you might be using Turbo Assembler to write some code, and decide to take a break playing a game for a few minutes, but don't want to have to reload Turbo Assembler and your source code again.
Or better, with a patched version of Turbo Assembler you might want to edit in one task and have it assemble into a separate task, and switch back and forth between them as you see fit.
It would also be nice to be able to have certain types of background processing supported. For example, being able to leave IRC or a download running in the background, with it waking up whenever a packet arrives or a timeout occurs.
For all these scenarios, it also makes sense to be able to quarantine one task from another, so that they cannot write to one another' memory or IO without permission. This implies the need for some sort of memory protection, and supervisor mode that can run a small operating system to control the tasks (and their own operating systems) running under it.
Thus, what we really want is something like VirtualBox that can run a hypervisor to virtualise the C65GS, so that it can have C64 or C65 "guest operating systems" beneath, and keep them all separate from each other.
This doesn't actually need much extra hardware to do in a simplistic manner.
First, we need the supervisor/hypervisor CPU mode that maps some extra registers. I have already implemented this with registers at $D640-$D67F.
Second, to make hypervisor calls fast, the CPU should save all CPU registers and automatically switch the memory map when entering and leaving the hypervisor. I have already implemented this, so that a call-up into the hypervisor takes just one cycle, as does returning from the hypervisor.
Third, we need to make the hypervisor programme memory only visible from in hypervisor mode. I have already implemented this. The hypervisor program is mapped at $8000-$BFFF, with the last 1KB reserved as scratch space, relocated zero-page (using the 4510's B register), and relocated stack (again, using the 4510's SPH register). I am in the process of modifying kickstart so that it works as a simple hypervisor.
Fourth, we need some registers that allow us to control which address lines on the 16MB RAM are available to a given task, and what the value of the other address lines should be. This would allow us to allocate any power-of-two number of 64KB memory blocks to a task. When a task is suspended, it's 128KB chipram and 64KB colour RAM and IO status can be saved into other 64KB memory blocks that are not addressable by the task when it is running. This I have yet to do.
Fifth, we need to be able to control what events result in a hypervisor trap, so that background processes can run, and also so that the hypervisor can switch tasks. The NMI line is one signal I definitely want to trap, so that pressing RESTORE can activate the hypervisor.
By finishing these things, and then writing the appropriate software for the hypervisor, it shouldn't be too hard to get task switching running on the C65GS.
Given the memory and processor constraints, I don't see the C65GS as running lots of independent processes at the same time. Rather, I want it to be possible to easily switch between different tasks you have running.
For example, you might be using Turbo Assembler to write some code, and decide to take a break playing a game for a few minutes, but don't want to have to reload Turbo Assembler and your source code again.
Or better, with a patched version of Turbo Assembler you might want to edit in one task and have it assemble into a separate task, and switch back and forth between them as you see fit.
It would also be nice to be able to have certain types of background processing supported. For example, being able to leave IRC or a download running in the background, with it waking up whenever a packet arrives or a timeout occurs.
For all these scenarios, it also makes sense to be able to quarantine one task from another, so that they cannot write to one another' memory or IO without permission. This implies the need for some sort of memory protection, and supervisor mode that can run a small operating system to control the tasks (and their own operating systems) running under it.
Thus, what we really want is something like VirtualBox that can run a hypervisor to virtualise the C65GS, so that it can have C64 or C65 "guest operating systems" beneath, and keep them all separate from each other.
This doesn't actually need much extra hardware to do in a simplistic manner.
First, we need the supervisor/hypervisor CPU mode that maps some extra registers. I have already implemented this with registers at $D640-$D67F.
Second, to make hypervisor calls fast, the CPU should save all CPU registers and automatically switch the memory map when entering and leaving the hypervisor. I have already implemented this, so that a call-up into the hypervisor takes just one cycle, as does returning from the hypervisor.
Third, we need to make the hypervisor programme memory only visible from in hypervisor mode. I have already implemented this. The hypervisor program is mapped at $8000-$BFFF, with the last 1KB reserved as scratch space, relocated zero-page (using the 4510's B register), and relocated stack (again, using the 4510's SPH register). I am in the process of modifying kickstart so that it works as a simple hypervisor.
Fourth, we need some registers that allow us to control which address lines on the 16MB RAM are available to a given task, and what the value of the other address lines should be. This would allow us to allocate any power-of-two number of 64KB memory blocks to a task. When a task is suspended, it's 128KB chipram and 64KB colour RAM and IO status can be saved into other 64KB memory blocks that are not addressable by the task when it is running. This I have yet to do.
Fifth, we need to be able to control what events result in a hypervisor trap, so that background processes can run, and also so that the hypervisor can switch tasks. The NMI line is one signal I definitely want to trap, so that pressing RESTORE can activate the hypervisor.
By finishing these things, and then writing the appropriate software for the hypervisor, it shouldn't be too hard to get task switching running on the C65GS.
Subscribe to:
Posts (Atom)








