Fork me on GitHub

What

What we are trying to achieve

Up until now we’ve

With that we’re able to boot into our Custom OS, display text and get user input. We’re also able to write files and increment a simple boot counter.

What to expect

This time I’d like to add graphics. We can use a BIOS feature for that. It will give us an area in memory that represents a two-dimensional array representing the pixels of the screen. We can then easily write values to it in order to display graphics on the screen.

With that we can then set up a graphics library to display buttons and react to mouse input. I’ve done something very similar when writing to a raw graphics buffers in WebAssembly (see WebAssembly Particle Simulation user interface )

Setting up that graphics buffer is actually surprisingly easy.

When

How to go about setting up graphics was actually one of the first things I had researched when I started this project. It seemed like the most difficult part as I was under the assumption we’d need proprietary display drivers.

As it turns out we don’t - at least for low-resolution graphics.

Why

With graphics we can write basic graphical user interfaces, we will be able to display images and also write coloured text.

It does come at the expense of not being able to directly write text to a buffer, but having to render a text graphically by using a font. It isn’t particularly difficult to write a function that does this for us automatically.

Background

BIOS Mode 13h

Simple graphics are easily achieved with a BIOS feature. Most BIOS today, next to pure text-based mode, also support “standard graphics modes” or “Mode 13h”.

What Mode 13h does for us is to make a region of memory availble that represents a raw graphics buffer that we can write to.

If you’ve ever noticed how, during boot of a Linux without UEFI, it first displays corse grained black and white text and the suddenly switches to colourful text on boot - that is the point where the system switches from pure text-based to low-resolution, usually 640x480 pixel Mode 13h. Later during boot Linux will load the real graphics driver and display higher quality graphics.


(Screenshot taken from [1])

There is an excellent description of the Bios Mode 13h and Set Mode 10h at [1].

How

Implementation

Luckily I didn’t have to implement the BIOS Mode 13h code entirely myself. Instead I found some code, “graphics.asm”, on an Online Forum “Protected Mode BIOS Call Functionailty v2.0 - by Napalm” (see [2]). Due to its simplicity it is being used across many other Custom OSes.

Assembly Code

In order to use it we need to extend our Makefile:

By extending the object we want to link

OBJ := boot.o printf.o kernel.o fatfs/fatfs_ff.o fatfs/fatfs_ffunicode.o graphics/graphics.o 

and adding a rule to compile “graphics.asm”

graphics/graphics.o: graphics/graphics.asm
	${AS} -felf32 -i graphics/ graphics/graphics.asm -o graphics/graphics.o

C Code

To switch to 13h Mode and write data to the graphics buffer we write a simple C function:

#pragma once

typedef struct __attribute__ ((packed)) {
  unsigned short di, si, bp, sp, bx, dx, cx, ax;
  unsigned short gs, fs, es, ds, eflags;
} regs16_t;

extern void int32(unsigned char intnum, regs16_t *regs);

void graphicsInit() {
  int y;
  regs16_t regs;

  uint16_t screenWidth = 320;
  uint16_t screenHeight = 200;

  regs.ax = 0x0013;
  int32(0x10, &regs);

  // -- fill screen in black
  memset((char *)0xA0000, 1, (screenHeight*screenWidth));
  char * VGA = (char *)0xA0000;

  // -- draw color bands
  int pos = 0;
  for(int y = 0; y < screenHeight/2; y++) {
    for(int x = 0; x < screenWidth/2; x++) {
      pos = y * screenWidth + x;
      VGA[pos] = y % 16;
    }
  }

  // -- wait for key
  regs.ax = 0x0000;
  int32(0x16, &regs);

  // -- switch to 80x25x16 text mode
  regs.ax = 0x0003;
  int32(0x10, &regs);
}

include

#include "graphics.h"

and then call it from our kernel.c

graphicsInit();

With that we can render the color bands shown in the image above.

Rendering Text

We can now enter graphics mode, but at the expense of not being able to write text to the screen as easily. As we’re now dealing with a graphics buffer we need to “render” text to it - that is take the text string and convert it to an image.

In order to do this there are several options:

For my Custom OS I’ve chosen the latter approach.

Preparing a Font

For the particle simulation I had already written a Python script “font-generator.py” that

It yields an 11,2 KB C-Header with the pre-rendered font that we can easily include and use to render fonts.

Write Text

With that we can write a simple function to go over each row/column of the character and colour the pixel, if it’s set in the array.

uint16_t fontWidth(uint8_t fontNo, uint16_t charArrId) {
  if(fontNo == 0) return fontRobotoMonoMedium10pt_width[charArrId];
  return 0;
}

uint16_t fontHeight(uint8_t fontNo, uint16_t charArrId) {
  if(fontNo == 0) return fontRobotoMonoMedium10pt_height[charArrId];
  return 0;
}

char * fontBuffer(uint8_t fontNo, uint16_t charArrId) {
  if(fontNo == 0) return fontRobotoMonoMedium10pt_buffer[charArrId];
  return 0;
}

void graphicsRenderChar(char * graphicsBuffer, uint16_t x, uint16_t y, char charToWrite, uint8_t fontNo)  {
  uint16_t curx = x;
  uint16_t cury = y;
  
  uint16_t charArrId = charToWrite - 33;

  uint16_t charPixelCount = fontWidth(fontNo, charArrId) * fontHeight(fontNo, charArrId);
  for(uint16_t byteNo = 0; byteNo < charPixelCount / 8; byteNo++) {
  
    char bits = fontBuffer(fontNo, charArrId)[byteNo];
    for(int bit = 0; bit < 8; bit++) {
    
      // -- start new row when width is reached
      if(curx - x == fontWidth(fontNo, charArrId)) {
        cury += 1;
        curx = x;
      }

      // -- draw pixel if bit is set
      if(bits & 0x01) {
        uint16_t pos = cury * screenWidth + curx;
        graphicsBuffer[pos] = 1; // 1=blue
      }
      
      // -- shift to next bit
      bits = bits >> 1; 
        
      // -- advance the row
      curx += 1;
    }
  }
}

Notice we can also support variable width fonts by storing the width of a char in an array.

to spell “Hello” we can simply:

graphicsRenderChar(VGA, 8, 10, 'H', 0);
graphicsRenderChar(VGA, 16, 10, 'e', 0);
graphicsRenderChar(VGA, 24, 10, 'l', 0);
graphicsRenderChar(VGA, 32, 10, 'l', 0);
graphicsRenderChar(VGA, 40, 10, 'o', 0);

Progress

Result

With that we’ve laid the groundwork for displaying images and text with colours in varying fonts.

In the next parts we’ll be setting up a proper shell (directory browsing, reading/writing and executing files) and possibly something simple (like TinyCC) to be able to develop software and thereby extend the Custom OS from inside it.


1] https://atrevida.comprenica.com/atrtut07.html
2] https://forum.osdev.org/viewtopic.php?f=1&t=29805