[自制操做系統] 圖形界面&VBE工具&MMIO顯存&圖形庫/字庫

這裏寫圖片描述

本文記錄了在JOS(或在任意OS)上實現圖形界面的方法與一些圖形庫的實現。
本文中支持的新特性:java

  • 支持基本圖形顯示
  • 支持中英文顯示(中英文點陣字庫)

相關:VBE VESA MMIO 點陣字庫
Github : https://github.com/He11oLiu/MOSgit

About VESA

Video Electronics Standards Association(視頻電子標準協會,簡稱「VESA」)是制定計算機和小型工做站視頻設備標準的國際組織,1989年由NEC及其餘8家顯卡製造商贊助成立。創立VESA的原始目的是要制定分辨率爲800x600的SVGA視頻顯示標準。其後,VESA公告一系列的我的電腦視頻周邊功能的相關標準。github

VBE 功能調用

參考博客CSDN博客編程

VBE功能調用ruby

  • AH必須等於4FH,代表是VBE標準
  • AL等於VBE功能號,0<= AL <= 0BH
  • BL等於子功能號,也能夠沒有子功能
  • 調用INT 10H
  • 返回值在AX中
    • AL=4FH:支持該功能
    • AL!=4FH:不支持該功能
    • AH=00H:調用成功
    • AH=01H:調用失敗
    • AH=02H:當前硬件配置不支持該功能
    • AH=03H:當前的顯示模式不支持該功能

具體功能

此部分參考VESA編程——GUI離咱們並不遙遠,原做者博客已關閉。markdown

功能0x00:返回控制器信息

輸入: AX = 4F00h 返回VBE控制器信息 ES:DI = 指向存放VbeInfoBlock結構體的緩衝區指針 輸出: AX = VBE返回狀態

這個函數返回一個VbeInfoBlock結構體,該結構體定義以下:數據結構

// Vbe Info Block
typedef struct {
    unsigned char       vbe_signature;
    unsigned short      vbe_version;
    unsigned long       oem_string_ptr;
    unsigned char       capabilities;
    unsigned long       video_mode_ptr;
    unsigned short      total_memory;
    unsigned short      oem_software_rev;
    unsigned long       oem_vendor_name_ptr;
    unsigned long       oem_product_name_ptr;
    unsigned long       oem_product_rev_ptr;
    unsigned char       reserved[222];
    unsigned char       oem_data[256];  
} VbeInfoBlock;
  • vbe_signature是VBE標識,應該填充的是」VESA」
  • vbe_version是VBE版本,若是是0300h則表示3.0版本
  • oem_string_ptr是指向oem字符串的指針,該指針是一個16位的selector:offset形式的指針,在實模式下能夠直接使用。
  • video_mode_ptr是指向視頻模式列表的指針,與oem_string_ptr類型同樣
  • total_memory是64kb內存塊的個數
  • oem_vendor_name_ptr是指向廠商名字符串的指針
  • oem_product_name_ptr是指向產品名字符串的指針

功能01 返回VBE模式信息

輸入: AX = 0x4F01 返回VBE模式信息 CX = 模式號 ES:DI = 指向VBE特定模式信息塊的指針 輸出: AX = VBE返回值 

這個函數返回一個ModeInfoBlock結構體,其中重要的部分以下:tcp

  • mode_attributes字段,這個字段描述了圖形模式的一些重要屬性。其中最重要的是第4位和第7位。第4位爲1表示圖形模式(Graphics mode),爲0表示文本模式(Text mode)。第7位爲1表示線性幀緩衝模式(Linear frame buffer mode),爲0表示非線性幀緩衝模式。咱們主要要檢查這兩個位。
  • xresolution,表示該視頻模式的X分辨率。
  • yresolution,表示該視頻模式的Y分辨率。
  • bits_per_pixel,表示該視頻模式每一個像素所佔的位數。
  • phys_base_ptr,這是一個很是重要的字段,它給出了平坦內存幀緩衝區的物理地址,你能夠理解爲顯存的首地址。若是每一個像素佔32位的話,屏幕左上角第一個點所佔的緩衝區就是phys_base_ptr所指的第一個4個字節。按照先行後列的順序,每一個像素點所佔緩衝區依次緊密排列。咱們要想在屏幕上畫出像素點,就得操做以phys_base_ptr爲起始的物理內存空間。

功能02 設置VBE模式信息

輸入:
    AX      = 4F02h     設置VBE模式
    BX      =           須要設置的模式
            D0 - D8     = 模式號
            D9 - D10    = 保留(必須爲0)
            D11         = 0 使用當前缺省刷新率
                        = 1 使用用戶指定的CRTC值爲刷新率             D12 - D13   = 爲VBE/AF保留(必須爲0)
            D14         = 0 使用窗口幀緩衝區模式
                        = 1 使用線性/平坦幀緩衝區模式             D15         = 0 清除顯示內存
                        = 1 不清除顯示內存     ES:DI   =           指向CRTCInfoBlock結構體的指針

輸出:
    AX      =           VBE返回狀態

JOS實現

  • Qemu須要添加-vga stdide

    QEMUOPTS = -drive file=$(OBJDIR)/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::$(GDBPORT) -vga std
  • boot中實模式中獲取VBE,設置VBE函數

    sti
    call    getvideomode  
    call    setvideomode  
    cli

    先獲取VBE模式,填充di,而後切換模式,設置VBE模式。

    根據上面查的VESA資料,調用函數,實現這兩個功能。

    getvideomode:  
          mov $0x4f01, %ax  # get mode 
          mov $0x105,  %cx   # mode 0x105
          mov $0x8000, %di  # mode info block address
          int $0x10         # VBE int 
          ret
    
    setvideomode:  
          movw $0x4f02, %ax   # set mode 
          movw $0x105,  %bx 
          movw $0x8000, %di  
          int $0x10           # VBE int
          movl 40(%di), %eax  # get memory address 
          movl %eax, info_vram
          movw 18(%di), %ax   # get x resolution 
          movw %ax, info_scrnx
          movw 20(%di), %ax   # get y resolution 
          movw %ax, info_scrny
          ret

    這裏設計了一個結構體來存放從boot傳來的東西。

    struct boot_info
    {
    short scrnx, scrny;
    char *vram;
    };
  • init的時候,設計一個獲取boot_info的模塊

    static void get_boot_info(void)
    {
    struct boot_info *info = (struct boot_info *)(KADDR(0x0ff0));
    // Init Graph info
    graph.scrnx = info->scrnx;
    graph.scrny = info->scrny;
    graph.vram = info->vram;
    }

    這個地方我選擇初始化memory layout以後,開啓真正的頁表的時候才獲取信息。因此這裏要用KADDR進行物理地址到KVA的轉化。

  • 設計一個全局用於保存圖像相關信息的結構體

    struct graph_info
    {
      short scrnx,scrny;
      char *vram;
    };
    
    extern struct graph_info graph;
  • 利用MMIO映射一片顯存

    void graph_init()
    {
      int i;
      // Init Graph MMIO
      graph.vram =
          (char *)mmio_map_region((physaddr_t)graph.vram,
                                  graph.scrnx * graph.scrny);
      cprintf("====Graph mode on====\n");
      cprintf(" scrnx = %d\n",graph.scrnx);
      cprintf(" scrny = %d\n",graph.scrny);
      cprintf("MMIO VRAM = %#x\n",graph.vram);
      cprintf("=====================\n");
      // Draw Screen
      for (i = 0; i < graph.scrnx * graph.scrny; i++)
          *(graph.vram + i) = 0x34;
    }

補充圖像庫

上面基本已經實現了圖像顯示基本平臺。如今補充一些經常使用的圖像庫。

#define PIXEL(x, y) *(graph.vram + x + (y * graph.scrnx))
int draw_screen(uint8_t color)
{
    int i;
    for (i = 0; i < graph.scrnx * graph.scrny; i++)
        *(graph.vram + i) = color;
    return 0;
}

int draw_pixel(short x, short y, uint8_t color)
{
    if ((x >= graph.scrnx) || (y >= graph.scrny))
        return -1;
    *(graph.vram + x + (y * graph.scrnx)) = color;
    return 0;
}

int draw_rect(short x, short y, short l, short w, uint8_t color)
{
    int i, j;
    w = (y + w) > graph.scrny ? graph.scrny : (y + w);
    l = (x + l) > graph.scrnx ? graph.scrnx : (x + l);
    for (j = y; j < w; j++)
        for (i = x; i < l; i++)
            *(graph.vram + i + j * graph.scrnx) = color;
    return 0;
}

字庫實現

這部分也是老生常談了,板子上各類系統都實現過點陣字庫。

int draw_ascii(short x, short y, char *str, uint8_t color)
{
    char *font;
    int i, j, k = 0;
    for (k = 0; str[k] != 0; k++)
    {
        font = (char *)(ascii_8_16 + (str[k] - 0x20) * 16);
        for (i = 0; i < 16; i++)
            for (j = 0; j < 8; j++)
                if ((font[i] << j) & 0x80)
                    PIXEL((x + j), (y + i)) = color;
        x += 8;
    }
    return k;
}

int draw_cn(short x, short y, char *str, uint8_t color)
{
    uint16_t font;
    int i, j, k;
    int offset;
    for (k = 0; str[k] != 0; k += 2)
    {
        offset = ((char)(str[k] - 0xa0 - 1) * 94 +
                  ((char)(str[k + 1] - 0xa0) - 1)) *
                 32;
        for (i = 0; i < 16; i++)
        {
            font = cn_lib[offset + i * 2] << 8 |
                   cn_lib[offset + i * 2 + 1];
            for (j = 0; j < 16; j++)
                if ((font << j) & 0x8000)
                    PIXEL((x + j), (y + i)) = color;
        }
        x += 16;
    }
    return 0;
}

直接把以前單片機的點陣字庫拿過來,不過單片機當時開發環境是win,找的字庫尋址模式是GB2312的。這裏把這個文件的編碼改成GB2312來正確編碼中文便可。實現效果見文章頭。

frambuffer

實際上,直接對顯存寫是很不負責任的行爲。很早以前在寫java的界面的時候,就接觸了雙緩衝技術,其實與顯示有關的思想都是差很少的,咱們應該提供一個framebuffer。當完成一個frame後,再將這個frame update到顯存中。

uint8_t *framebuffer;
void init_framebuffer(){
    if((framebuffer = (uint8_t *) kmalloc((size_t)(graph.scrnx*graph.scrny)))== NULL)
        panic("Not enough memory for framebuffer!");
}

void update_screen(){
    memcpy(graph.vram,framebuffer,graph.scrnx*graph.scrny);
}

通過實現kmallockfree,已經能夠分配這個緩衝區,並直接向緩衝區寫入,最後再進行update

#define PIXEL(x, y) *(framebuffer + x + (y * graph.scrnx))
int draw_xx()
{
    xxx;
    update_screen();
}

總結

至此,基本的GUI底層接口已基本實現,後面的就是各類數據結構的設計,窗口樹設計之類。這裏暫不打算繼續深究,轉而研究其他內核的東西。

相關文章
相關標籤/搜索