Close

Font Rendering Speed++

A project log for VT-69 Handheld Terminal

It's a dumb terminal. You can connect a Raspberry Pi to it.

benchoffBenchoff 04/29/2020 at 16:451 Comment

On the advice of someone who's actually helpful on Twitter I've spent the last few days finishing the font set (ASCII Extended code page 437, the old DOS extended ASCII with those weird 'box' characters), and optimizing the font rendering speed. First, the results:

This is significant. Each of these runs reboots the display, shows the boot graphic, delays for one second, then loops through an ASCII table, displaying the character at an increasing address on the display. First one to finish wins.

The reason to optimize this bit of code was inspired by @fast_code_r_us, who has written a few things on optimizing text rendering on small displays:

My display is not serial, it's using an 8080-style bus, but the same thinking applies. Most graphics libraries are horribly inefficient. To draw a character (or a sprite, or whatever), an area of memory is allocated (basically a bounding box), and pixels are drawn one at a time, each time resetting the XY coordinate the microcontroller is drawing to.

In most graphic libraries, there is an edge case to make drawing faster: straight vertical or horizontal lines. If the bounding box is only one pixel tall (or wide), the microcontroller can simply stream pixels directly to the display. 

Unfortunately most graphics libraries don't do this for text characters or sprites. For each pixel in a character, the microcontroller sends a command to change the x coordinate, the y coordinate, the second x coordinate, and the second y coordinate (we're defining a window here, remember), then it draws the pixel. 

What should be one byte of data has suddenly turned into twenty-six bytes. This bit of code runs every time I want to change the active window of the graphics RAM:

void setXY(uint16_t x1, uint16_t y1, 
        uint16_t x2, uint16_t y2)
{
    LCD_Write_COM16(0x2a,0x00);
    LCD_Write_DATA8(x1>>8);
    LCD_Write_COM16(0x2a,0x01);
    LCD_Write_DATA8(x1);
    LCD_Write_COM16(0x2a,0x02);
    LCD_Write_DATA8(x2>>8);
    LCD_Write_COM16(0x2a,0x03);
    LCD_Write_DATA8(x2);

    LCD_Write_COM16(0x2b,0x00);
    LCD_Write_DATA8(y1>>8);
    LCD_Write_COM16(0x2b,0x01);
    LCD_Write_DATA8(y1);
    LCD_Write_COM16(0x2b,0x02);
    LCD_Write_DATA8(y2>>8);
    LCD_Write_COM16(0x2b,0x03);
    LCD_Write_DATA8(y2);

    LCD_Write_COM16(0x2c,0x00);
}

That's sending 26 bytes for each pixel in a character. Across an entire display, that's a lot of wasted time. Instead of this, I'm simply setting the active window of graphics RAM to the size of a 10x20 pixel character, then streaming the pixel data out. The letter 'a', for example, looks like this:

// CHARACTER char: <a>  => int: 97 => hex: 0x61
{0x00, 0x0c, 0x00, 0x00, 0xc0,
0x03, 0x33, 0x00, 0x33, 0x30,
0x03, 0x33, 0x00, 0x33, 0x30,
0x03, 0x33, 0x00, 0x3f, 0x30,
0x00, 0xff, 0x00, 0x00, 0x00},

 Just define the window in GRAM, stream the pixels out, and you have a faster display.

As for what the final display and font looks like, here you go:

Now on to the next task.

Discussions

Krzysztof wrote 05/06/2020 at 10:12 point

Better yet: set your window to whole character line (or from start character to last pixel of screen), set writing direction to be vertical, then just stream vertical lines of each character, this way you can fill entire line of characters without needing to change your window.

  Are you sure? yes | no