Preface π
This should act as your complete guide to implementing and understanding VBE graphics in x86 assembly, if you are unfamiliar with BIOS programming I recommend my article about making pong (https://dev.to/willy1948/making-pong-in-x86-assembly-11li). So, what is VBE graphics? VBE (VESA bios extension) graphics is a standard that aims to improve upon VGA graphics which was created by IBM. What are the benefits of using VBE graphics? Well, the biggest benefit is the improvement in screen resolution. VGA at best gives you 320x200 whereas using VBE one can have up to 1280x1024. It's not just the resolution, but in certain video you can have up to 255^3 colours, compare that to just 255 for VGA. If you are looking to make a OS or just a bootloader game you should definitely consider using VBE graphics.
οΌοΌγ γ
| γ_γ_|
οΌ` γοΌΏxγ
/γγγγ |
/γ γ½γγ οΎ
βγγ|γ|γ|
οΌοΏ£|γγ |γ|
(οΏ£γ½οΌΏ_γ½_)__)
Setting the video mode ποΈ
We need to tell BIOS that we want to use VBE graphics. luckily there exists an interrupt that we can call with specific arguments to do this. Using int 0x10 with ax set to 0x4f02 and bx used to specify the video mode, (https://en.wikipedia.org/wiki/INT_10H).
mov ax, 0x4f02 ; code to set video mode
mov bx, 0x4118 ; specifies video + flags
int 0x10
cmp ax, 0x4f ;Check if video mode is supported by hardware
je .successLabel
To understand what the cmp line does we can look at the VBE specification document (https://pdos.csail.mit.edu/6.828/2011/readings/hardware/vbe3.pdf).
We want to check if the video mode in bx is supported and therefore we check to see if ax is 0x4f. I mentioned earlier that bx stores the mode, this is partly true but it also stores flags which specify certain behaviours.
It is a bit confusing but D means bit number. Breaking down 0x4118 into binary we get 0b|0|1|00|0|00|1|00011000| (I used | character to separate the different sections). Moving from left to right the sections are defined as follows:
- 0=clear display, 1=preserve display. This means weather or not what is in video memory will be cleared to black. Majority of the time it makes sense to clear the display when switching modes. Hence why the first bit is 0.
- 0=Use banked frame buffer, 1=linear frame buffer. The reason this exists is because with 16 bits one can only address so much memory, even with segment offsets. Video memory could need up to 1280x1024x3 = 3,932,160 bytes, which is greater than 64kb. To get around this issue video card manufactures split up memory in banks. Only one bank exists in memory at one time and one can switch to a particular bank through a BIOS call. Linear frame buffer is where the frame buffer is just stored as normal in memory. Because I am going to use 32 bit registers (this can be done in real mode), I am going to use a linear buffer. If you are developing for 16-bit retro hardware then you would need to set this bit to 0.
- Must be 0
- 0=Uses BIOS default refresh rate, 1=Use user specified CRTC (Cathode ray tube controller). You may think, well, I'm not using a CRT monitor. But you still specify values through the CRTC to control your up to date monitor. Using BIOS defaults will be fine. If you do want to specify the refresh rate, I believe you specify a pointer in ES:DI to a CRTC information struct.
- Should be 0
- 0=mode specifies VGA mode, 1=mode specifies super VGA mode. This should be set to 1 because we want access to higher resolutions that are available only in super VGA (super VGA is VBE).
- Specifies the video mode:
Entry one has mode number of 0 and so on, this is a little unclear from the table. 7 bit mode number means that the 8th bit(zero indexed) is set to 0.
Great, using this information you should be able to run your program and it should show a bigger window than before. There are still some problems we need to fix. Firstly we don't know important information like where video memory is, and secondly we do not have enough address lines to write into all of video memory. Lets take a look at how to solve these issues.
Finding video memory πΎ
We can call a function that will give us all the information we need about the current mode. It takes a pointer to a struct and fills in all the information.
mov ax, 0x4F01 ; VBE function 01h which returns mode info
; mode number (remember to include the 8th bit)
; cx is mode 5 with 8th bit set because we are using super VGA
mov cx, 0x105
mov di, video_info ; pointer to structure
int 0x10
Next we need to define the structure
That's a bit of a pain to write out by hand so here is a text version with labels for the important attributes.
video_info:
dw 0h ; ModeAttributes found on page 40 of VBE spec pdf
db 0h ; WinAAttributes
db 0h ; WinBAttributes
dw 0h ; WinGranularity
dw 0h ; WinSize
dw 0h ; WinASegment
dw 0h ; WinBSegment
dd 0h ; WinFuncPtr
video_bytesPerScanLine: dw 0h ; BytesPerScanLine
dw 0h ; XResolution
dw 0h ; YResolution
db 0h ; XCharSize
db 0h ; YCharSize
db 0h ; NumberOfPlanes
db 0h ; BitsPerPixel
db 0h ; NumberOfBanks
db 0h ; MemoryModel
db 0h ; BankSize
db 0h ; NumberOfImagePages
db 0h ; Reserved1
db 0h ; RedMaskSize
db 0h ; RedFieldPosition
db 0h ; GreenMaskSize
db 0h ; GreenFieldPosition
db 0h ; BlueMaskSize
db 0h ; BlueFieldPosition
db 0h ; RsvdMaskSize
db 0h ; RsvdFieldPosition
db 0h ; DirectColorModeInfo
video_address: dd 0h ; PhysBasePtr
dd 0h ; OffScreenMemOffset
dw 0h ; OffScreenMemSize
times 206 db 0h
The video address label stores the pointer to the first pixel.
Accessing more memoryπ
When the computer boots in real mode only 20 address lines are usable. This means we can only address 2^20=1MB of memory. This is not good enough, say if we want to use video mode 0x11B which is 1280x1024 with 3 colour channels, we would need about 4MB of memory just to access all of video memory. To fix this we can do something called enabling the A20 line. This doesn't just give us one more address line, which still wouldn't be enough, but it gives us access to all available address lines, circa 32 probably. This means we can now address up to 4GB of RAM.
Great, so how do I enable the A20 line? Most BIOS's provide a fast and simple way of doing this by setting some port signals. In fact some BIOS's come with the A20 lines enabled on boot, but this is not always the case so we better make sure.
in al, 0x92
or al, 2
out 0x92, al
;A20 lines now enabled
Drawing pixels π¨
Jolly good, now we can start drawing pixels to the screen.
mov edi, dword[video_address]
add edi, 5; determins the x cord of the pixel / bytes per pixel
mov al, 0x4
mov byte[edi], al
Depending on the resolution of the video mode you choose, you may find it difficult to see the pixel because it is so small. Here's some code that should set all the pixels on screen.
mov ecx, dword[video_address]
;set this to the number of pixels for your chosen video mode
mov edx, 1280*1024*3;resolution X * resolution Y * bytes per pix
add edx, ecx
;edx is now the end value for the loop
;ecx being the start
.loopHead:
;set the colour the counter for more interesting display
mov al, cl
mov byte[ecx], al
inc ecx ; move onto next pixel
cmp ecx, edx
jle .loopHead
jmp $ ; infinate loop, $ means address of current instruction
Now you have all the tools you need to create something awesome.
Going further π§
I decided to draw the hello world of images, Lena. It took me a little while to get it working but in the end I managed it.
The pixelated effect is due to my code not a limitation of VBE.
This just leaves one question. What are you going to do with this knowledge? Let me know in the comments below. Perhaps coding a game, or maybe adding nicer graphics for your kernel.
Wrapping up π
I have produced a GitHub repo to pair with this tutorial so if you want to look at sections in more detail head to (https://github.com/asdf-a11/VBE_Tutorial). If you have any questions make sure to leave them in the comments below. If you have enjoyed this article then make sure to check out my other articles, I think they'll be of interest to you. Have a great day!
Top comments (0)