iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
自我挑戰組

30 天 hypervisor 入門系列 第 22

Day 22 實現螢幕設備(2)

  • 分享至 

  • xImage
  •  

用 SDL2 把 BIOS 文字模式跑起來

在前面的實作中,我們雖然已經完成了螢幕設備的 API 介面與記憶體寫入行為的監控,但並沒有模擬一個真正的畫面,有的也只是在終端做文字輸出。
為了能夠真實輸出畫面,我們將使用 SDL2 把 frame buffer 畫出來。
而由於文字模式下向 0xB8000 寫入的東西其實是屬性 + 字元,並非單純像素,所以我們這裡會做查表並將自符打印到 SDL2 開的視窗中。

建立畫面

下方這個 screen_text_sdl_create 是整個顯示的建構器。它接收畫面尺寸與縮放倍數,建立一個視窗與渲染器 renderer,以及一張可被我們每幀以 CPU 更新的 streaming texture(ARGB8888)。為了保持像素銳利、不讓字糊掉,renderer 的設定為畫面原始像素,視窗只用 scale 去放大顯示。

struct screen_text_sdl *screen_text_sdl_create(size_t pixel_width,
                                               size_t pixel_height,
                                               int scale,
                                               screen_text_input_cb input_cb,
                                               void *opaque)
{
    if (pixel_width == 0 || pixel_height == 0)
        return NULL;

    if (!screen_text_sdl_init_video())
        return NULL;

    struct screen_text_sdl *ui = calloc(1, sizeof(*ui));
    if (!ui)
        return NULL;

    ui->tex_width = (int)pixel_width;
    ui->tex_height = (int)pixel_height;
    ui->scale = (scale > 0) ? scale : 1;
    ui->input_cb = input_cb;
    ui->input_opaque = opaque;

    ui->window = SDL_CreateWindow("KVM Screen",
                                  SDL_WINDOWPOS_CENTERED,
                                  SDL_WINDOWPOS_CENTERED,
                                  ui->tex_width * ui->scale,
                                  ui->tex_height * ui->scale,
                                  SDL_WINDOW_SHOWN);
    if (!ui->window)
        goto fail;

    ui->renderer = SDL_CreateRenderer(ui->window, -1, SDL_RENDERER_SOFTWARE);
    if (!ui->renderer)
        goto fail;

    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest");
    SDL_RenderSetLogicalSize(ui->renderer, ui->tex_width, ui->tex_height);
    SDL_SetRenderDrawColor(ui->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(ui->renderer);
    SDL_RenderPresent(ui->renderer);

    ui->texture = SDL_CreateTexture(ui->renderer,
                                    SDL_PIXELFORMAT_ARGB8888,
                                    SDL_TEXTUREACCESS_STREAMING,
                                    ui->tex_width,
                                    ui->tex_height);
    if (!ui->texture)
        goto fail;

    SDL_StartTextInput();
    ui->active = true;
    return ui;

fail:
    screen_text_sdl_destroy(ui);
    return NULL;
}

把 VRAM 展平成一張圖

BIOS 的文字模式不是單除的像素,而是一個紀錄字元的陣列。每格為 16 bit(低 8 位是 ASCII, 高 8 位是屬性)。要畫到 SDL 視窗上,我們要先把它轉成一塊真正的像素陣列。
在這裡我們準備一個 8×8 的字型表(font8x8),為了讓字更好讀,我們把每一列垂直複製一次,從 8 行拉成 16 行(8×16)。並使用一個固定的 VGA 調色盤,逐像素把結果寫進 ARGB8888 的 pixel_buffer。
概念上,對於一個 rows × cols 的文字畫面,占用像素大小就是:

  • pixel_width = cols * 8
  • pixel_height = rows * 16

更新與顯示

當我們有了 pixel_buffer 後可直接將這塊 ARGB8888 的資料塞入 ui->texture,並將整張 texture 貼到當前 renderer 的畫面上,最後調用 Present 做會直接顯示。

int screen_text_sdl_update(struct screen_text_sdl *ui,
                           const uint32_t *pixels,
                           size_t width,
                           size_t height)
{
    if (!ui || !pixels || width == 0 || height == 0)
        return -1;

    if (!ui->active)
        return -1;

    if ((int)width != ui->tex_width || (int)height != ui->tex_height)
        return -1;

    if (SDL_UpdateTexture(ui->texture, NULL, pixels,
                          ui->tex_width * (int)sizeof(uint32_t)) != 0) {
        fprintf(stderr, "SDL_UpdateTexture error: %s\n", SDL_GetError());
        return -1;
    }

    if (SDL_RenderClear(ui->renderer) != 0) {
        fprintf(stderr, "SDL_RenderClear error: %s\n", SDL_GetError());
        return -1;
    }

    if (SDL_RenderCopy(ui->renderer, ui->texture, NULL, NULL) != 0) {
        fprintf(stderr, "SDL_RenderCopy error: %s\n", SDL_GetError());
        return -1;
    }

    SDL_RenderPresent(ui->renderer);
    return 0;
}

另外這裡做的是整張更新而非分區更新,這是因為文字模式下的緩衝區大小為 80 * 25 (~4KB),恰好接近一個 page。

執行結果

https://ithelp.ithome.com.tw/upload/images/20251006/20178814YdRt6Nso8D.png


上一篇
Day 21:BIOS 顯示服務(INT 10h)
系列文
30 天 hypervisor 入門22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言