[英]How do I draw a pixel in VESA VBE?
我正在嘗試將我的kernel.cpp
轉換為使用 VESA,盡管我得到的只是一系列奇怪的錯誤。 按照教程,我使用這段代碼讓 VESA 圖形的引導加載程序工作得相當順利:
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
但是,當我嘗試從kernel.cpp
繪制像素時,問題就出現了。 無論我在代碼中進行了哪些更改,都會出現錯誤(我可以補充一下,還有令人驚訝的變化范圍)。 我嘗試從引導加載程序( 此處的代碼)中繪制像素,它工作得很好,所以問題在kernel.cpp
中。 我遇到的一個常見錯誤是屏幕頂部出現一條隨機顏色,如此處所示。 在這里,我將保留該文件和我的GitHub (用於其余代碼):
內核.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;
}
使用您已經獲得的模式信息結構(在實模式下的modeInfo
中),您將需要 3 條信息:
幀緩沖區的物理地址。 請注意(啟用線性幀緩沖區)這不會像 VGA 那樣是 0x0A0000,並且可能是像 0xE0000000 這樣的 32 位物理地址。 這直接取自模式信息結構中的PhysBasePtr
字段。
每行像素的字節數。 請注意,每行末尾可能有任何數量的未使用填充,您需要考慮到這一點。 這取決於 VBE 的版本 - 對於 VBE 3.0,您可以從模式信息結構的LinBytesPerScanLine
字段中獲取它; 對於舊版本的 VBE,您可以從BytesPerScanLine
字段中獲取它。
每像素的字節數。 這取決於像素格式和 VBE 版本。 對於古代 VBE(VBE 1.0?),您必須根據視頻模式編號進行猜測; 對於 VBE 1.2 及更高版本,它可以從模式信息結構中的BitsPerPixel
字段中確定並向上取整,如bytesPerPixel = (modeInfo->BitsPerPixel + 7) / 8;
,因此“每像素 15 位”是每像素 2 個字節。
一旦有了這 3 個值,計算像素物理地址的公式為:
physical_address = physical_address_of_frame_buffer + y * bytes_per_scan_line + x * bytes_per_pixel
當然,更常見的情況是,您將幀緩沖區映射到任何您喜歡的虛擬地址空間,但公式基本相同(只是使用虛擬地址和您選擇的virtual_address_of_frame_buffer
)。
其他注意事項:
您的screen_set()
沒有考慮每行像素末尾可能的填充(並且沒有正確四舍五入)。
您的screen_swap()
沒有考慮每行像素末尾可能的填充。 它需要更像for(y = 0; y < vertical_resolution; y++) { address = virtual_address_of_frame_buffer + y * bytes_per_scan_line; memcpy(address, ...
for(y = 0; y < vertical_resolution; y++) { address = virtual_address_of_frame_buffer + y * bytes_per_scan_line; memcpy(address, ...
單獨執行每一行。當且僅當沒有填充時,您可以(我會)檢測何時沒有填充並作為單個memcpy()
執行。
固定模式編號(例如“0x118 = 1024 x 768,每像素 24 位”)在 VBE 2.0 中已停用並且不應使用(例如,視頻模式 0x118 可以是任何東西,可以是“640 x 480,每像素 8 位”和其他一些視頻模式編號可能是“1024 x 768,每像素 24 位”)。 您應該/必須獲取可能的模式編號列表,然后搜索您實際想要確定哪個模式是“1024 x 768,每像素 24 位”。
您不應該假設存在特定模式。 對於“每像素 24 位的 1024 x 768”尤其如此,原因有兩個:每像素 24 位(或每像素 3 字節)對對齊很煩人,因此許多顯卡不支持任何分辨率(並且而是支持每像素 32 位,保留 8 位填充); 並且 1024 x 768 分辨率沒有很好地標准化(與現代屏幕的縱橫比不匹配,並且沒有在“VESA 安全模式”標准中定義為所有顯示器都應該支持的模式),因此 1024 x 768 分辨率可能不支持任何像素格式。
您還應該檢查像素內的字段布局(對於 VBE 2.0 使用RedMaskSize
、 RedFieldPosition
、 ... 字段,對於 VBE 3.0 使用LinRedMaskSize
、 LinRedFieldPosition
、 ... 字段)。 不能保證(例如)每像素 24 位是“紅、綠、藍”而不是“藍、綠、紅”或其他東西。
理想情況下; 您的代碼將搜索您的代碼和視頻卡(以及顯示器,但事情變得更加困難)支持的最佳視頻模式; 並適應任何分辨率(這很容易 - 只需在任何地方使用諸如horizontal_resolution
和vertical_resolution
之類的變量)並支持多種不同的像素格式。 支持多種不同像素格式的最簡單方法是在 RAM 中擁有一個始終使用特定格式(例如,每個像素可能是 32 位)的緩沖區,然后具有多種不同的功能將像素數據從您的特定格式轉換為任何視頻模式需要同時將結果復制到視頻卡的幀緩沖區。
理想情況下(為了性能)你的screen_set()
函數不會被任何東西使用。 對於線條,矩形,字符,... ; 最好計算一次左上角像素的地址,然后調整地址(例如用address += bytes_per_pixel;
找到右邊的下一個像素,或address += bytes_per_scan_line;
找到下一個像素)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.