This is a guide for creating your own VBE driver for your custom operating system in C. This is a continuation of my previous blog post Create your own graphics library in C++, as I realised that not everyone may have a VBE or VGA driver. This post will also guide you through how to transfer the data from the uBuffer32
to the framebuffer.
Before we get started, you’ll need to find things like the address of your framebuffer (E.g. 0xFD000000
). You’ll probably also want to define the width and height of the framebuffer (E.g. 1920
x1080
).
I suggest putting the vbe.c
and vbe.h
files in a drivers directory, so they don’t get mixed up. We’ll need to define quite a few things in the vbe.h
before we do anything else.
#define VBE_DISPI_BANK_ADDRESS 0xA0000
#define VBE_DISPI_BANK_SIZE_KB 64
#define VBE_DISPI_MAX_XRES 1024
#define VBE_DISPI_MAX_YRES 768
#define VBE_DISPI_IOPORT_INDEX 0x01CE
#define VBE_DISPI_IOPORT_DATA 0x01CF
#define VBE_DISPI_INDEX_ID 0x0
#define VBE_DISPI_INDEX_XRES 0x1
#define VBE_DISPI_INDEX_YRES 0x2
#define VBE_DISPI_INDEX_BPP 0x3
#define VBE_DISPI_INDEX_ENABLE 0x4
#define VBE_DISPI_INDEX_BANK 0x5
#define VBE_DISPI_INDEX_VIRT_WIDTH 0x6
#define VBE_DISPI_INDEX_VIRT_HEIGHT 0x7
#define VBE_DISPI_INDEX_X_OFFSET 0x8
#define VBE_DISPI_INDEX_Y_OFFSET 0x9
#define VBE_DISPI_ID0 0xB0C0
#define VBE_DISPI_ID1 0xB0C1
#define VBE_DISPI_ID2 0xB0C2
#define VBE_DISPI_ID3 0xB0C3
#define VBE_DISPI_ID4 0xB0C4
#define VBE_DISPI_DISABLED 0x00
#define VBE_DISPI_ENABLED 0x01
#define VBE_DISPI_VBE_ENABLED 0x40
#define VBE_DISPI_NOCLEARMEM 0x80
#define VBE_DISPI_LFB_PHYSICAL_ADDRESS 0xE0000000
#define VBE_DISPI_BPP_4 0x04
#define VBE_DISPI_BPP_8 0x08
#define VBE_DISPI_BPP_15 0x0F
#define VBE_DISPI_BPP_16 0x10
#define VBE_DISPI_BPP_24 0x18
#define VBE_DISPI_BPP_32 0x20
#define VBE_DISPI_LFB_ENABLED 0x40
#define HD 1280,720
#define FHD 1920,1080
#define UHD 3840,2160
void BgaSetVideoMode(unsigned int Width, unsigned int Height, unsigned int BitDepth, int UseLinearFrameBuffer, int ClearVideoMemory);
void BgaSetBank(unsigned short BankNumber);
void vbe_putpixel(uint32_t x, uint32_t x y, uint32_t x color); // Draws a pixel on the framebuffer
uint32_t vbe_getpixel(uint32_t x x, uint32_t x y); // Gets a pixel from the framebuffer
void vbe_draw_buffer(uint32_t* buffer, uint32_t width, uint32_t height); // Draws the uBuffer32->buffer to the framebuffer
In the vbe.c
file, we’ll also need to add a few things:
#include "vbe.h"
#define ADDRESS 0xFD000000 // Example, yours might be different
#define FONT_WIDTH 8 // Font width
#define FONT_HEIGHT 20 // Font height
int VBE_WIDTH = 1920;
int VBE_HEIGHT = 1080;
static inline void outpw(uint16_t port, uint16_t value) {
__asm__ __volatile__ ("outw %0, %1" : : "a"(value), "Nd"(port));
}
static inline uint16_t inpw(uint16_t port) {
uint16_t value;
__asm__ __volatile__ ("inw %1, %0" : "=a"(value) : "Nd"(port));
return value;
}
void BgaWriteRegister(unsigned short IndexValue, unsigned short DataValue){
outpw(VBE_DISPI_IOPORT_INDEX, IndexValue);
outpw(VBE_DISPI_IOPORT_DATA, DataValue);
}
unsigned short BgaReadRegister(unsigned short IndexValue){
outpw(VBE_DISPI_IOPORT_INDEX, IndexValue);
return inpw(VBE_DISPI_IOPORT_DATA);
}
int BgaIsAvailable(void){
return (BgaReadRegister(VBE_DISPI_INDEX_ID) == VBE_DISPI_ID4);
}
void BgaSetVideoMode(unsigned int Width, unsigned int Height, unsigned int BitDepth, int UseLinearFrameBuffer, int ClearVideoMemory){
BgaWriteRegister(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED);
BgaWriteRegister(VBE_DISPI_INDEX_XRES, Width);
BgaWriteRegister(VBE_DISPI_INDEX_YRES, Height);
BgaWriteRegister(VBE_DISPI_INDEX_BPP, BitDepth);
BgaWriteRegister(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_ENABLED |
(UseLinearFrameBuffer ? VBE_DISPI_LFB_ENABLED : 0) |
(ClearVideoMemory ? 0 : VBE_DISPI_NOCLEARMEM));
VBE_WIDTH = Width;
VBE_HEIGHT = Height;
}
void BgaSetBank(unsigned short BankNumber){
BgaWriteRegister(VBE_DISPI_INDEX_BANK, BankNumber);
}
Now we can begin with the actual main part of it. If you want to initialize VBE and get it set up so you can use it, you’ll need to run BgaSetVideoMode(FHD,VBE_DISPI_BPP_32,1,1);
. We’ll implement vbe_putpixel
and vbe_getpixel
.
void vbe_putpixel(uint32_t x, uint32_t y, uint32_t color) {
uint32_t *framebuffer = (uint32_t*)ADDRESS;
uint32_t *pixel = &framebuffer[y * VBE_WIDTH + x];
uint32_t existing_color = *pixel;
uint8_t alpha = (color >> 24) & 0xFF;
if (alpha == 0xFF) {
return;
} else if (alpha == 0x00) {
*pixel = color & 0xFFFFFF;
} else {
uint8_t src_r = (color >> 16) & 0xFF;
uint8_t src_g = (color >> 8) & 0xFF;
uint8_t src_b = color & 0xFF;
uint8_t dst_r = (existing_color >> 16) & 0xFF;
uint8_t dst_g = (existing_color >> 8) & 0xFF;
uint8_t dst_b = existing_color & 0xFF;
uint8_t blended_r = ((src_r * (255 - alpha)) + (dst_r * alpha)) / 255;
uint8_t blended_g = ((src_g * (255 - alpha)) + (dst_g * alpha)) / 255;
uint8_t blended_b = ((src_b * (255 - alpha)) + (dst_b * alpha)) / 255;
*pixel = (blended_r << 16) | (blended_g << 8) | blended_b;
}
}
uint32_t vbe_getpixel(uint32_t x, uint32_t y) {
uint32_t *framebuffer = (uint32_t*)ADDRESS;
return framebuffer[y * VBE_WIDTH + x];
}
Now we can implement the copying of the uBuffer32
to the framebuffer.
void vbe_draw_buffer(uint32_t* buffer, uint32_t width, uint32_t height) {
for(uint32_t y = 0; y < height; y++) {
for(uint32_t x = 0; x < width; x++) {
vbe_putpixel(x, y, buffer[y * width + x]);
}
}
}
Discover more from WTDawson
Subscribe to get the latest posts sent to your email.