Search code examples
assemblygraphicsbootloaderosdev24-bit

24 bit graphics mode in assembly


I want to make an OS (64 bit). I made a compiler in it, input, output, I just need to have a 24 bit graphics mode to make a GUI. I tried to display all of the colors but I just get this mess:

this is the mess

the code which ran it: (just in a test 16 bit environment)

[org 0x7c00]
  mov ax,4f02h
  mov bx,103h
  int 10h


        mov ax, 0a000h
mov es, ax
mov al, 0
 

mov edi, 0
mov eax, 0
sl:
stosw
inc edi
inc eax
jmp sl
times 510-($-$$) db 0
dw 0AA55h

And I need to make it interrupt free because of the cli instruction when switching to 64 bit mode. I hope that I can make it to be compatible with any resolution but I need to output a pixel for now. I hope someone knows how to do it, because I didn't find anything is this topic.

I tried to switch the mov bx, 103h to other things, but it just made it worse. And I can't even run it in 64 bit mode because of the mov es, ax.


Solution

  • First I strongly recommend to read this:

    The idea behind pixel rendering is to compute address where pixel lies in VRAM and copy the color or index at that address (using segment where the VRAM is located usually A000h for video and B800h for text modes).

    address = (x + y*x_resolution) * bytes_per_pixel
    

    However normally we can access only single segment of VRAM so wee need also to select which one it is (page) so:

    page = address / 65536
    offs = address % 65536
    

    now you switch in page (if not already) and copy the pixel color (1/2/4 bytes) at that address for example by using stosb/w/d. Now I am not sure if pixel can be at 2 pages at once (the code bellow does not handle that) so if true you should handle that too (however as x resolution for standard video modes is always multiple of 4 I do not think its possible).

    Looks like you really a rookie so here is small simple (ugly and slooooow) example (MS-DOS com executable using VESA BIOS in NASM) of rendering pixels in 800x600x32bit mode 115h... just inspect the function pixel , it expects 800x600x32bit video mode and ax,bx as x,y and ecx as 32bit color:

    Firs main source file:

    [BITS 16]
    [ORG 100h]
    
    [SEGMENT .text]
    
    start:  pusha
        mov ax,115h ; init 800x600 32bit
        call    vesamod
    
        mov al,0    ; set page to 0
        mov [cs:page],al
        call    vesapag
    
    mainl0: mov ax,10
        mov bx,10
        mov ecx,003F7FFFh
    l0: call    pixel
        inc ax
        inc bx
        cmp ax,110
        jnz l0
    
        mov ax,256  ; stop on any key hit
        int 16h
        jz  mainl0
    
        mov ax,3    ; return to 80x25 text mode
        int 10h
        popa
        ret
    
    error:  mov ax,3
        int 10h
        popa
        popa
        ret
    
    pixel:  pusha           ; ax=x, bx=y, ecx = color
    
        mov di,ax       ; eax = ( 800*y + x ) * 4
        mov eax,800
        and ebx,0FFFFh
        mul ebx
        mov bx,di
        add eax,ebx
        shl eax,2
    
        mov di,ax       ; di = eax % FFFFh (offset)
        mov ax,0A000h   ; es = A000h
        mov es,ax
    
        shr eax,16      ; ax = eax / FFFFh (page)
        cmp al,[cs:page]
        jz  .pixel0     ; change page if needed
        mov [cs:page],al
        call    vesapag
    
    .pixel0:mov eax,ecx     ; eax = color
        stosd           ; render pixel
        popa
        ret
    
    page:   db  0
    
    %include 'vesa.lib'
    

    and the helper vesa.lib:

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;VESA librrary ver: 1.0
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;vesamod    set VESA videomode ax
    ;vesapag    al=page  switch vesa video page window A
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;; Vesa: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    vesamod:pusha               ;set VESA videomode ax
        mov     bx,ax
        mov     ax,4f02h
        int     16
        lea     si,[cs:.err]
        or      ah,ah
        jnz     near error
        popa
        ret
    .err:   db  'VESA mode set error.',0
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    vesapag:pusha               ;al=page  switch vesa video page window A
        mov     dl,al
        sub     dh,dh
        sub     bx,bx
        mov     ax,4f05h    ; window A
        int     16
        lea     si,[cs:.err]    ; error msg
        or      ah,ah
        jnz     near error
        popa
        ret
    .err:   db  'VESA page switch error.',0
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;; End. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    

    note that I did not code in asm in ages and have no mod to tweak this in DOS-BOX too much so its ugly and non optimized.

    This renders blue diagonal line using function pixel which means the layout of color is:

    ecx = 00RRGGBBh
    

    there are many pusha/popa and I am using slow computation using mul to improve performance you should have array where you store the starting address and page of each scan line precomputed once (after changing video mode) and then just access it using y. Also the actual page variable could be moved directly into vesapag function but I was not in the mood to edit 2 files as I am really rusty in MS-DOS and asm now and DOS BOX is conflicting key shortcuts with my editors not to mention VC editors stopped working for god knows what reason (took me a lot more time to workaround that than code the example itself)...

    Also if you want to use 32/64 bit code you will have to use some kind of extender to have access to VESA BIOS (or any BIOS) functions or their replacement. Have never done such thing but people used DOS4GW for this back in the day...

    Also in 32/64 bit mode you can use LFB (linear frame buffer) and forget about page switching completely (improving speed a lot more).

    If you want to have this working for any resolution then you should change the hardcoded 800 to real resolution you use. It can be obtained from VESA BIOS (along with starting segment) just disect this:

    It obtains all available VESA video modes along with information about them and once you chose one from the list it renders a control image so you see the difference between 8/15/16/32 bit modes...