Home > other >  How do I draw a pixel in VESA VBE?
How do I draw a pixel in VESA VBE?

Time:07-25

I'm trying to convert my kernel.cpp to use VESA, though all I've gotten is an array of weird errors. Following a tutorial, I got the bootloader side of the VESA Graphics working rather smoothly using this bit of code:

mov ax, 0x4F02   ; set VBE mode
mov bx, 0x4118   ; VBE mode number
int 0x10         ; call VBE BIOS
cmp ax, 0x004F   ; test for error
jne error

;Get video mode info
mov ax, 4f01h
mov cx, 105h
mov di, modeInfo 
int 10h

The issues arose, however, when I tried drawing pixels from kernel.cpp. No matter what I changed in my code, I got an error (with a surprising range of variety too, might I add). I tried drawing pixels from the bootloader (code from here), and it worked just fine, so the issue in in kernel.cpp. One common error I got was a strip of random colors across the top of the screen as seen here. Here I'll leave that file and my GitHub (for the rest of the code):

kernel.cpp:

//VIDEO MODE IN 1024x768x32bpp
typedef unsigned char uint8_t;
typedef unsigned char u8;
typedef unsigned short uint16_t;
typedef unsigned int u32;
typedef u32 size_t;
#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 768
#define BPP 32
#define SCREEN_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT)
#define FPS 30
#define PIT_HERTZ 1193131.666
#define CLOCK_HIT (int)(PIT_HERTZ/FPS)
#define KEY_LEFT 0x4B
#define KEY_UP 0x48
#define KEY_RIGHT 0x4D
#define KEY_DOWN 0x50

static u32 *BUFFER = (u32 *) 0xA0000;

// double buffers
u32 _sbuffers[2][SCREEN_SIZE];
u32 _sback = 0;

#define CURRENT (_sbuffers[_sback])
#define SWAP() (_sback = 1 - _sback)

#define screen_buffer() (_sbuffers[_sback])

// #define screen_set(_p, _x, _y)\
//     (_sbuffers[_sback][((_y)/* * pitch */   ((_x) * (BPP/8)))]=(_p))

//u32 pixel_offset = y * pitch   (x * (bpp/8))   framebuffer;

static inline void outb(uint16_t port, uint8_t val)
{
    asm volatile ( "outb %0, %1" : : "a"(val), "Nd"(port) );
}

//           This is the troublesome function                  
void screen_set(u32 color,int x,int y) {
    _sbuffers[_sback][y * SCREEN_WIDTH   (x * (BPP/8))]=color;
}
//                                                             
static inline uint8_t inb(uint16_t port)
{
    uint8_t ret;
    asm volatile ( "inb %1, %0"
                   : "=a"(ret)
                   : "Nd"(port) );
    return ret;
}

const unsigned char font[128-32][8] = {
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},   // U 0020 (space)
           /*hidden to help length if code...*/
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}    // U 007F
};

static inline void *memcpy(void *dst, const void *src, size_t n)
{
    u8 *d = (u8*)dst;
    const u8 *s = (const u8*)src;

    while (n-- > 0) {
        *d   = *s  ;
    }

    return d;
}

void screen_swap() {
    memcpy(BUFFER, CURRENT, SCREEN_SIZE);
    SWAP();
}

unsigned read_pit(void) {
    unsigned count = 0;
 
    // al = channel in bits 6 and 7, remaining bits clear
    outb(0x43,0b0000000);
 
    count = inb(0x40);          // Low byte
    count |= inb(0x40)<<8;      // High byte
 
    return count;
}
 
void draw_char(char c, int x, int y, u32 color)
{
    const unsigned char *glyph = font[(int)c-32];
 
    for(int cy=0;cy<8;cy  ){
        for(int cx=0;cx<8;cx  ){
            if(((int)glyph[cy]&(1<<cx))==(1<<cx)){
                screen_set(color,x cx,y cy);
            }
        } 
    }
}

void draw_string(const char * s, int x, int y, u32 color) {
    int i = 0;
    while(s[i] != false) {
        draw_char(s[i],x (i*8),y,color);
        i  ;
    }
}

void draw_rect(int pos_x, int pos_y, int w, int h, u32 color) {
    for(int y = 0; y<h; y  ) {
        for(int x = 0; x<w; x  ) {
            screen_set(color,x pos_x,y pos_y);
        }
    }
}

static void render(int c0, int c1) {
    //draw_rect(0,0,SCREEN_WIDTH,SCREEN_HEIGHT,0x09);
    //draw_string("Hello, reader. This is written text.", 100, 180, 16777215);
    //draw_string("If this is displayed, my code works.", 100 c0, 100 c1, 16777215);
}

extern "C" void main(){
    
    int clock = 0;
    int incC1 = 0;
    int incC0 = 0;
    while(true) {
        uint16_t scancode = (uint16_t) inb(0x60);
        clock  ;
        if(read_pit()!= 0 && clock == CLOCK_HIT) {
            if(scancode == KEY_LEFT) {
                incC0--;
            }else if(scancode == KEY_RIGHT) {
                incC0  ;
            }
            if(scancode == KEY_DOWN) {
                incC1  ;
            }else if(scancode == KEY_UP) {
                incC1--;
            }
            clock = 0;
            render(incC0,incC1);
            screen_swap();
        }
    }

    return;
}

CodePudding user response:

Using the mode info structure you already got (at modeInfo in real mode) you'll need 3 pieces of information:

  • the physical address of the frame buffer. Note that (with linear frame buffer enabled) this will not be 0x0A0000 like it was for VGA, and might be a 32-bit physical address like 0xE0000000. This is taken directly from the PhysBasePtr field in the mode info structure.

  • the bytes per row of pixels. Note there may be any amount of unused padding at the end of each row and you need to take that into account. This depends on the version of VBE - for VBE 3.0 you take it from the LinBytesPerScanLine field of the mode info structure; and for older versions of VBE you take it from the BytesPerScanLine field instead.

  • the bytes per pixels. This depends on the pixel format and VBE version. For ancient VBE (VBE 1.0?) you have to guess based on the video mode number; and for VBE 1.2 and later it can be determined from the BitsPerPixel field in the mode information structure and rounding up, like bytesPerPixel = (modeInfo->BitsPerPixel 7) / 8;, so that "15 bits per pixel" is 2 bytes per pixel.

Once you have these 3 values the formula for calculating the physical address of a pixel is:

physical_address = physical_address_of_frame_buffer   y * bytes_per_scan_line   x * bytes_per_pixel

Of course more often you map the frame buffer into the virtual address space wherever you feel like, but the formula is mostly the same (just with virtual addresses and a virtual_address_of_frame_buffer that you chose).

Other notes:

  • Your screen_set() doesn't take into account possible padding at the end of each row of pixels (and wasn't rounding up properly).

  • Your screen_swap() doesn't take into account possible padding at the end of each row of pixels. It needs to be more like for(y = 0; y < vertical_resolution; y ) { address = virtual_address_of_frame_buffer y * bytes_per_scan_line; memcpy(address, ... doing each row individually. You can (I would) detect when there is no padding and do it as a single memcpy() if and only if there's no padding.

  • Fixed mode numbers (e.g. "0x118 = 1024 x 768 with 24 bits per pixel") were discontinued in VBE 2.0 and should not be used (e.g. video mode 0x118 could be anything, and could be "640 x 480 with 8 bits per pixel" and some other video mode number might be "1024 x 768 with 24 bits per pixel"). You should/must get the list of possible mode numbers and then search for the mode you actually want to determine which one is "1024 x 768 with 24 bits per pixel".

  • You shouldn't assume a specific mode exists. This is especially true for "1024 x 768 with 24 bits per pixel" for 2 reasons: 24 bits per pixel (or 3 bytes per pixel) is annoying for alignment so a lot of video cards don't support it for any resolution (and support 32 bits per pixel instead, with 8 bits of reserved padding); and a 1024 x 768 resolution isn't well standardized (not matching the aspect ratio of modern screens and not defined in the "VESA safe mode" standards as a mode that is supposed to be supported on all monitors) so the 1024 x 768 resolution might not be supported with any pixel format.

  • You should also check the layout of fields within a pixel (using the RedMaskSize, RedFieldPosition, ... fields for VBE 2.0, or the LinRedMaskSize, LinRedFieldPosition, ... fields for VBE 3.0). There is no guarantee that (e.g.) 24 bits per pixel is "red, green, blue" and not "blue, green, red" or something else.

  • Ideally; your code would search for the best video mode that's supported by your code and the video card (and the monitor but that's were things get significantly more difficult); and adapt to any resolution (which is easy - just use variables like horizontal_resolution and vertical_resolution everywhere) and support multiple different pixel formats. The easiest way to support multiple different pixel formats is to have a buffer in RAM that always uses a specific format (e.g. 32-bit per pixel maybe) and then have multiple different functions to convert pixel data from your specific format into whatever the video mode wants while copying the result to the video card's frame buffer.

  • Ideally (for performance) your screen_set() function wouldn't be used by anything. For lines, rectangles, characters, ... ; it's better to calculate the address of the top left pixel once, and then adjust the address (e.g. with address = bytes_per_pixel; to find the next pixel to the right, or address = bytes_per_scan_line; to find the next pixel down).

  • Related