5
\$\begingroup\$

I'm working on a hobby kernel, with graphics and rendering fonts. The font format is BDF. Some of them (like for Ubuntu Mono) I used was ttf2bdf. Then I use a program called bdf2c which converts it to a .c file. From there, I can use the data in my program.

First, let me start off by showing you code that won't change, but will help you understand the rest of the program:

Exposition; don't review

multiboot.h

struct multiboot_info
{
 /* irrelevant stuff */

 /* Video */
 multiboot_uint32_t vbe_control_info;
 multiboot_uint32_t vbe_mode_info;
 multiboot_uint16_t vbe_mode;
 multiboot_uint16_t vbe_interface_seg;
 multiboot_uint16_t vbe_interface_off;
 multiboot_uint16_t vbe_interface_len;
};
typedef struct multiboot_info multiboot_info_t;

struct vbe_mode_info 
{
  uint16_t attributes;
  uint8_t  winA,winB;
  uint16_t granularity;
  uint16_t winsize;
  uint16_t segmentA, segmentB;
  uint32_t winFuncPtr; // ptr to INT 0x10 Function 0x4F05
  uint16_t pitch; // bytes per scanline

  uint16_t Xres, Yres;
  uint8_t  Wchar, Ychar, planes, bpp, banks;
  uint8_t  memory_model, bank_size, image_pages;
  uint8_t  reserved0;

  uint8_t  red_mask_size, red_position;
  uint8_t  green_mask_size, green_position;
  uint8_t  blue_mask_size, blue_position;
  uint8_t  rsv_mask, rsv_position;
  uint8_t  directcolor_attributes;

  uint32_t physbase;  // your LFB (Linear Framebuffer) address ;)
  uint32_t reserved1;
  uint16_t reserved2;

  // VBE 3.0, use these if LFB is present
  uint16_t LinBytesPerScanLine;
  uint8_t  BnkNumberofImagePages;
  uint8_t  LinNumberofImagePages;
  uint8_t  LinRedMaskSize;
  uint8_t  LinRedFieldPosition;
  uint8_t  LinGreenMaskSize;
  uint8_t  LinGreenFieldPosition;
  uint8_t  LinBlueMaskSize;
  uint8_t  LinBlueMaskPosition;
  uint8_t  LinRsvdMaskSize;
  uint8_t  LinRsvdFieldPosition;
  uint32_t MaxPixelClock;
  uint8_t  Reserved;
};
typedef struct vbe_mode_info vbe_mode_info_t;

GRUB fills in the proper information to the header, so there's no code to show for that.

font.h

  /// bitmap font structure
struct bitmap_font {
  unsigned char Width;        ///< max. character width
  unsigned char Height;       ///< character height
  unsigned short Chars;       ///< number of characters in font
  const unsigned char *Widths;    ///< width of each character
  const unsigned short *Index;    ///< encoding to character index
  const unsigned char *Bitmap;    ///< bitmap of all characters
};

// masking defines omitted

font.c

  /// bitmap font structure
const struct bitmap_font font = {
  .Width = 17, .Height = 18,
  .Chars = 1224,
  .Widths = __font_widths__,
  .Index = __font_index__,
  .Bitmap = __font_bitmap__,
};

And an example of what a glyph looks like:

//  33 $21 '0021'
//    width 9, bbx 3, bby 0, bbw 2, bbh 11
  ___XX___,________,________,
  ___XX___,________,________,
  ___XX___,________,________,
  ___XX___,________,________,
  ___XX___,________,________,
  ___XX___,________,________,
  ___XX___,________,________,
  ___XX___,________,________,
  ________,________,________,
  ___XX___,________,________,
  ___XX___,________,________,
  ________,________,________,
  ________,________,________,
  ________,________,________,
  ________,________,________,
  ________,________,________,
  ________,________,________,
  ________,________,________,

Review this

Now for the actual kernel code. I'm trying to write as much C++ code as possible and avoid C code. That doesn't make my C++ code any less C-ish, but I'm uncomfortable writing in C. Let's start with the helper functions:

cpplib.hpp

#ifndef CPPLIB_
#define CPPLIB_

template< class T > struct remove_reference      {typedef T type;};
template< class T > struct remove_reference<T&>  {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};

template<class _Ty> inline
typename remove_reference<_Ty>::type&&
move(_Ty&& _Arg)
{   // forward _Arg as movable
    return ((typename remove_reference<_Ty>::type&&)_Arg);
}

template<typename T> void swap(T& t1, T& t2) {
    T temp = move(t1); // or T temp(move(t1));
    t1 = move(t2);
    t2 = move(temp);
}

template<class ForwardIt1, class ForwardIt2>
void iter_swap(ForwardIt1 a, ForwardIt2 b)
{
   swap(*a, *b);
}

template<class BidirIt>
void reverse(BidirIt first, BidirIt last)
{
    while ((first != last) && (first != --last)) {
        iter_swap(first++, last);
    }
}

template<class InputIt, class UnaryPredicate>
InputIt find_if(InputIt first, InputIt last, UnaryPredicate p)
{
    for (; first != last; ++first) {
        if (p(*first)) {
            return first;
        }
    }
    return last;
}

template< class InputIt, class UnaryPredicate >
bool any_of(InputIt first, InputIt last, UnaryPredicate p)
{
    return find_if(first, last, p) != last;
}

bool islower(wchar_t c)
{
    static const wchar_t *lowercase = L"abcdefghijklmnopqrstuvwxyz";
    return any_of(lowercase, lowercase + 26, [&] (wchar_t c2) -> bool { return c == c2; });
}

template <typename Iter, typename T>
size_t index_of(Iter first, Iter last, const T& x)
{
    size_t i = 0;
    while (first != last && *first != x)
      ++first, ++i;
    return i;
}

#endif

<iterator> is not implemented, so for all intents and purposes everything is assumed to be a random access iterator. index_of is used later to search the font indexes array to map a given char to its place in the array.

main.cpp

#define CHECK_BIT(var,pos) ((var) & (1<<(pos)))

vbe_mode_info_t *vbe;
uint8_t *mem;

// 24bpp
void draw_pixel(int x, int y, uint32_t color)
{
    auto pos = y * vbe->LinBytesPerScanLine + x * (vbe->bpp / 8);
    mem[pos] = color & 0xFF;
    mem[pos + 1] = (color >> 8) & 0xFF;
    mem[pos + 2] = (color >> 16) & 0xFF;
}

void draw_rectangle(int x, int y, int width, int height, uint32_t color)
{
    for (int i = x; i < x + width; ++i)
    {
        for (int j = y; j < y + height; ++j)
        {
            draw_pixel(i, j, color);
        }
    }
}

extern bitmap_font font;

struct FontSettings
{
    const size_t font_index_size;
    const size_t glyph_height;
    const size_t glyph_width;
    const size_t letter_spacing;
    const size_t num_columns;
    const size_t offset_kerning;
};

const FontSettings UbuntuMonoSettings{1224, 17, 8, 4, 3, 7};
const FontSettings GohuFontSettings  {1597, 10, 8, 0, 1, 3};
const FontSettings MechaSettings     {193,  16, 14, 0, 2, 5};

FontSettings currentFont = MechaSettings;

int get_yoffset(wchar_t c)
{
    int cx, cy;
    const unsigned char *glyph = font.Bitmap + 
        (index_of(font.Index, font.Index + currentFont.font_index_size, (int)c)) 
            * ((currentFont.glyph_height + 1) * currentFont.num_columns);

    int offset = 0;
    bool quit = false;
    for (cy = currentFont.glyph_height; cy > 0; cy--) 
    {
        for (cx = 0; cx < currentFont.glyph_width * currentFont.num_columns; cx++) 
        {
            // 1 << x = powers of 2
            // glyph_height - cx because the glyph is backwards
            if (glyph[cy * currentFont.num_columns] 
                & (1 << (currentFont.glyph_height - cx)))
            {
                quit = true;
                break;
            }
        }
        if (quit)
            return offset;
        ++offset;
    }
    return offset;
}

void drawchar(wchar_t c, int x, int y, int fgcolor, int bgcolor = -1)
{
    int cx, cy;
    const unsigned char *glyph = font.Bitmap + 
        (index_of(font.Index, font.Index + currentFont.font_index_size, (int)c)) 
            * ((currentFont.glyph_height + 1) * currentFont.num_columns);

    for (cy = 0; cy <= currentFont.glyph_height; cy++) 
    {
        for (cx = 0; cx < currentFont.glyph_width * currentFont.num_columns; cx++) 
        {
            // 1 << x = powers of 2
            // glyph_height - cx because the glyph is backwards
            if (glyph[cy * currentFont.num_columns] 
                & (1 << (currentFont.glyph_height - cx)))
            {
                draw_pixel(x + cx, y + cy, fgcolor);
            } else {
                // unimplemented: background color
            }
        }
    }
}

void draw_string(const wchar_t *s, int x, int y, int fgcolor, int bgcolor = -1) 
{
    size_t len = strlen(s);
    int xoffset = 0;
    int yoffset = 0;
    for (int i = 0; i < len; ++i)
    {
        if (s[i] == '\n') 
        {
            y += currentFont.glyph_height;
            xoffset = 0;
        }

        if (islower(s[i]))
        {
            yoffset = get_yoffset(s[i]) - currentFont.offset_kerning;
        }

        drawchar(s[i], x + xoffset * (currentFont.glyph_width + currentFont.letter_spacing), 
            y + yoffset, fgcolor, bgcolor);
        ++xoffset;        
    }
}

const wchar_t *doge = LR"doge(░░░░░░░░░▄░░░░░░░░░░░░░░▄
░░░░░░░░▌▒█░░░░░░░░░░░▄▀▒▌
░░░░░░░░▌▒▒█░░░░░░░░▄▀▒▒▒▐
░░░░░░░▐▄▀▒▒▀▀▀▀▄▄▄▀▒▒▒▒▒▐
░░░░░▄▄▀▒░▒▒▒▒▒▒▒▒▒█▒▒▄█▒▐
░░░▄▀▒▒▒░░░▒▒▒░░░▒▒▒▀██▀▒▌
░░▐▒▒▒▄▄▒▒▒▒░░░▒▒▒▒▒▒▒▀▄▒▒▌
░░▌░░▌█▀▒▒▒▒▒▄▀█▄▒▒▒▒▒▒▒█▒▐
░▐░░░▒▒▒▒▒▒▒▒▌██▀▒▒░░░▒▒▒▀▄▌
░▌░▒▄██▄▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▌
▀▒▀▐▄█▄█▌▄░▀▒▒░░░░░░░░░░▒▒▒
)doge";

extern "C" void kernel_main(uint32_t magic, uint32_t addr)  {
    multiboot_info_t *mbi = (multiboot_info_t *)addr;
    vbe = (vbe_mode_info_t *)mbi->vbe_mode_info;
    printf("attributes: %u\nwinA: %u\nwinB: %u\ngranularity: %u\nwinsize: %u\nsegmentA: %x\nsegmentB: %x\nwinFuncPtr: %x\npitch: %u\nXres: %u\nYres: %u\nWchar: %u\nYchar: %u\nplanes: %u\nbpp: %u\nbanks: %u\nmemory_model: %u\nbank_size: %u\nimage_pages: %u\nreserved0: %u\nred_mask_size: %u\nred_position: %u\ngreen_mask_size: %u\ngreen_position: %u\nblue_mask_size: %u\nblue_position: %u\nrsv_mask: %u\nrsv_position: %u\ndirectcolor_attributes: %u\nphysbase: %x\nreserved1: %u\nreserved2: %u\nLinBytesPerScanLine: %u\nBnkNumberofImagePages: %u\nLinNumberofImagePages: %u\nLinRedMaskSize: %u\nLinRedFieldPosition: %u\nLinGreenMaskSize: %u\nLinGreenFieldPosition: %u\nLinBlueMaskSize: %u\nLinBlueMaskPosition: %u\nLinRsvdMaskSize: %u\nLinRsvdFieldPosition: %u\nMaxPixelClock: %u\nReserved: %u", vbe->attributes,vbe->winA,vbe->winB,vbe->granularity,vbe->winsize,vbe->segmentA,vbe->segmentB,vbe->winFuncPtr,vbe->pitch,vbe->Xres,vbe->Yres,vbe->Wchar,vbe->Ychar,vbe->planes,vbe->bpp,vbe->banks,vbe->memory_model,vbe->bank_size,vbe->image_pages,vbe->reserved0,vbe->red_mask_size,vbe->red_position,vbe->green_mask_size,vbe->green_position,vbe->blue_mask_size,vbe->blue_position,vbe->rsv_mask,vbe->rsv_position,vbe->directcolor_attributes,vbe->physbase,vbe->reserved1,vbe->reserved2,vbe->LinBytesPerScanLine,vbe->BnkNumberofImagePages,vbe->LinNumberofImagePages,vbe->LinRedMaskSize,vbe->LinRedFieldPosition,vbe->LinGreenMaskSize,vbe->LinGreenFieldPosition,vbe->LinBlueMaskSize,vbe->LinBlueMaskPosition,vbe->LinRsvdMaskSize,vbe->LinRsvdFieldPosition,vbe->MaxPixelClock,vbe->Reserved);
    uint16_t uses_lfb = CHECK_BIT(mbi->vbe_mode, 14);
    if (uses_lfb)
        write_serial_string("uses LFB");

    mem = (uint8_t *)vbe->physbase;

    /*int32_t colors[] = {
        0xFFFFFF, 0xC0C0C0, 0x808080,
        0x000000, 0xFF0000, 0x800000,
        0xFFFF00, 0x808000, 0x00FF00,
        0x008000, 0x00FFFF, 0x008080,
        0x0000FF, 0x000080, 0xFF00FF,
        0x800080
    };
    int idx = 0;
    int chunk_size = 8;
    int xchunk = (vbe->Xres / chunk_size);
    int ychunk = (vbe->Yres / chunk_size);
    for (int i = 0; i < chunk_size; ++i)
    {
        for (int j = 0; j < chunk_size; ++j)
        {
            draw_rectangle(j * xchunk, i * ychunk, xchunk, ychunk, 
                colors[idx]);
            if (++idx == 16)
                idx = 0;
        }
    }*/
    draw_string(L"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0, 0, 0xFFFFFFF);
    draw_string(L"abcdefghijklmnopqrstuvwxyz", 0, currentFont.glyph_height, 0xFFFFFFF);
    draw_string(L"!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ ", 0, currentFont.glyph_height * 2, 0xFFFFFF);
    draw_string(L"0123456789", 0, currentFont.glyph_height * 3, 0xFFFFFF);
    draw_string(L"The quick brown fox jumps over the lazy dog", 0, currentFont.glyph_height * 4, 0xFFFFFF);
    draw_string(doge, 0, currentFont.glyph_height * 5, 0xFFFFFF);
}

write_serial_string is an irrevelant debug function (it writes to the COM1 port, which is redirected to a file via my emulator.)

Now for my questions:

  • How do I make my font handling more generic? I rely too much on hard-coded values, and I strongly suspect there's several off-by-one errors. While Ubuntu Mono and Gohu Font work just fine, Mecha is slightly buggy, which means my code is not as generic as I thought.

  • How do I reduce code duplication? There's two sources of code duplication:

    • Function overloads for library functions (like strlen) that need to handle both char and wchar_t

    • Algorithms that are very similar but perform different purposes: i.e, getting the offset or printing

  • Can I improve readability? There's a compromise between terseness and verbosity: or usability vs. readability. Simply changing variable and function names isn't enough, how can the code be refactored? That ugly printf.

  • Any comments on API, separation of library and kernel code, etc.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

cache lines

draw_rectangle() is perfectly clear, implemented in an obvious way. But it's weird to be drawing vertical lines in that hot inner loop. We're doing LinBytesPerScanLine strides across main memory, dirtying cache lines which we will soon revisit on the next vertical line.

Prefer to make \$Y\$ the outer loop, so each inner loop will march through adjacent bytes of a given raster.

white

I don't understand this foreground color parameter:

    draw_string(L"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0, 0, 0xFFFFFFF);

Breaking apart bytes it is 0xF_FF_FF_FF. Maybe it is RGBA with just a small alpha value? Maybe we intended 24-bit RGB with the high byte zero? Maybe it used to be 32-bits of \$1\$ until an accidental DEL keystroke?

Please define a color constant for this quantity, so you can refer to it symbolically. This is especially important given that you're passing several similar color values into draw_string() and they don't vertically align in the source code, so it's hard to compare them by eyeballing them.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.