; =============================================================================
; Project : Custom BIOS / ROM
; File : gfx_cgam.asm
; Author : frater
;
; License : GNU General Public License v3.0 or later (GPL-3.0+)
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see .
;
; =============================================================================
;
; graphics drivers pour carte video/mode CGA-Mono (640x200x2)
;
%define VIDEO_SEG 0xB800 ; ou 0xB000
%define GFX_MODE 0x06 ; MCGA HiRes (B/W)
%define GFX_WIDTH 640
%define GFX_HEIGHT 200
%define CGA_STRIDE 80
%define CGA_ODD_BANK 0x2000
; ------------------------------------------------------------
; TABLE DE SAUT (VECTEURS API)
; Cette table doit être située au début du driver pour être
; accessible par la GUI à des offsets fixes.
; ------------------------------------------------------------
%define INIT 0
%define PUTPIXEL 3
%define GETPIXEL 6
%define MOUSE_UPDATE 9
gfx_api:
jmp gfx_init ; Offset +0: Initialisation
jmp gfx_putpixel ; Offset +3: Dessin pixel
jmp gfx_getpixel ; Offset +6: Lecture pixel
jmp gfx_cursor_move ; Offset +9: mouse update
; jmp gfx_fill_rect ; Remplissage rectangle (Nouveau)
; jmp gfx_invert_rect ; Offset +12: Inversion (Nouveau pour Menus)
; jmp gfx_draw_hline ; Offset +15: Ligne horizontale rapide (Nouveau)
; ------------------------------------------------------------
; COMMENTAIRE SUR LA MÉTHODE D'APPEL
;
; Pour appeler une fonction du driver depuis votre GUI :
; 1. Définissez l'adresse de base du driver (ex: GFX_DRIVER_BASE)
; 2. Utilisez un call indirect via l'offset de la table.
;
; Exemple en ASM pour remplir un rectangle :
; call [GFX_DRIVER_BASE + 9] ; Appelle gfx_fill_rect
;
; Cette méthode isole totalement votre bibliothèque GUI du driver.
; Si vous changez le code du driver, tant que la table au début
; ne change pas d'ordre, la GUI n'a pas besoin d'être recompilée.
; ------------------------------------------------------------
; ------------------------------------------------------------
; initialise le mode graphique (via l'int 10h)
;
; ce mode est entrelacé, un bit/pixel, 8 pixels par octet
; ------------------------------------------------------------
gfx_init:
; init graphics mode
mov ah, 0x00 ; AH=00h set video mode
mov al, GFX_MODE
int 0x10
mov byte [BDA_MOUSE + mouse.bkg_saved],0 ; flag image saved
; dessine un background "check-board"
call gfx_background
ret
; ------------------------------------------------------------
; Calcule DI + AH=mask pour (CX=x, DX=y) en mode CGA 640x200
;
; Out: ES=VIDEO_SEG, DI=offset, AH=bitmask (0x80 >> (x&7))
; ------------------------------------------------------------
gfx_calc_addr:
; calcul de l'offset 'y':
; si y est impaire, DI+=0x2000
; DI = (y>>1)*80 + (x>>3) + (y&1)*0x2000
mov ax, dx
shr ax, 1 ; ax = y/2
mov di, ax
shl di, 4 ; (y/2)*16
shl ax, 6 ; (y/2)*64
add di, ax ; *80
mov ax, cx
shr ax, 3
add di, ax
test dl, 1
jz .even
add di, CGA_ODD_BANK
.even:
; masque bit = 0x80 >> (x&7)
push cx
and cl, 7
mov ah, 080h
shr ah, cl
pop cx
ret
; ------------------------------------------------------------
; Dessine un pixel, accès VRAM direct.
;
; CX = x (0..639)
; DX = y (0..199)
; ES = Target Segment (usually VIDEO_SEG)
; BL = color (0=black, !=0=white)
; ------------------------------------------------------------
gfx_local_putpixel:
call gfx_calc_addr
; write
cmp bl, 0
je .clear
.set:
or byte [es:di], ah
jmp .done
.clear:
not ah
and byte [es:di], ah
.done:
ret
; ------------------------------------------------------------
; Dessine un pixel, accès VRAM direct.
;
; CX = x (0..639)
; DX = y (0..199)
; BL = color (0=black, !=0=white)
; ------------------------------------------------------------
gfx_putpixel:
push ax
push di
push es
mov ax, VIDEO_SEG
mov es,ax
call gfx_calc_addr
; write
cmp bl, 0
je .clear
.set:
or byte [es:di], ah
jmp .done
.clear:
not ah
and byte [es:di], ah
.done:
pop es
pop di
pop ax
ret
; ------------------------------------------------------------
; Lit un pixel (CX=x, DX=y)
; Out: AL=0/1
; ------------------------------------------------------------
gfx_getpixel:
push di
push es
call gfx_calc_addr
mov al, [es:di]
and al, ah
setnz al
pop es
pop di
ret
; ------------------------------------------------------------
; Dessine un background "check-board", accès VRAM direct.
;
; ------------------------------------------------------------
gfx_background:
mov ax,VIDEO_SEG
mov es,ax
xor di,di
mov ax,0xaaaa
mov cx,0x1000
rep stosw
mov ax,0x5555
mov cx,0x1000 ; 640/8/2
rep stosw
ret
; ------------------------------------------------------------
; Calcule les infos de l'alignement du curseur, basé sur cx=X
; il est important que DS pointe sur le BDA_MOUSE_SEG
; Out: BDA_CURSOR_BITOFF, BDA_CURSOR_BYTES
; ------------------------------------------------------------
gfx_cursor_calc_align:
mov al, cl
and al, 7 ; offset = x&7
mov byte [BDA_MOUSE + mouse.cur_bit_ofs], al
ret
; ------------------------------------------------------------
; Dessine un pixel, en [ES:DI] accès VRAM direct.
;
; CX = x (0..639)
; DX = y (0..199)
; BL = color (0=black, !=0=white)
; ------------------------------------------------------------
gfx_putpixel_fast:
cmp bl, 0
je .clear
.set:
or byte [es:di], ah
jmp .done
.clear:
not ah
and byte [es:di], ah
.done:
ret
; ------------------------------------------------------------
; Lit un pixel, en [ES:DI] accès VRAM direct.
;
; ------------------------------------------------------------
gfx_getpixel_fast:
mov al, [es:di]
and al, ah
setnz al
ret
gfx_cursor_move:
pusha
push ds
push es
; move Data Segment
mov ax, BDA_DATA_SEG
mov ds, ax
cmp byte [BDA_MOUSE + mouse.cur_drawing],0
jne .done
mov byte [BDA_MOUSE + mouse.cur_drawing],1
mov ax, VIDEO_SEG
mov es, ax
call gfx_cursor_restorebg
call gfx_cursor_savebg
call gfx_cursor_draw
mov byte [BDA_MOUSE + mouse.cur_drawing],0
.done:
pop es
pop ds
popa
ret
; ------------------------------------------------------------
; Dessine le curseur 16x16 AND/XOR à [mouse.x,mouse.y]
;
; sprite (AND[16] suivi de XOR[16] à +32 bytes)
; ------------------------------------------------------------
gfx_cursor_draw:
; pusha
mov ax, BDA_DATA_SEG
mov ds, ax
mov ax, VIDEO_SEG
mov es, ax
mov ax, word [BDA_MOUSE + mouse.cur_ofs]
mov si, ax
mov ax, word [BDA_MOUSE + mouse.cur_seg]
mov gs, ax
mov cx, word [BDA_MOUSE + mouse.x]
mov dx, word [BDA_MOUSE + mouse.y]
mov word [BDA_MOUSE + mouse.cur_x], cx ; sauvegarde la nouvelle position
mov word [BDA_MOUSE + mouse.cur_y], dx
; calcule offset et bytes/ligne (2 ou 3)
call gfx_cursor_calc_align
mov bl, byte [BDA_MOUSE + mouse.cur_bit_ofs]; 0..7
; calcule DI de départ (attention: gfx_calc_addr modifie CL)
push cx
call gfx_calc_addr ; DI ok, AH ignoré
pop cx
; dxbank = +0x2000 si DI < 0x2000, sinon dxbank = -0x2000
mov bp, 02000h
cmp di, bp
jl .bank_ok
neg bp ; bp = -0x2000
.bank_ok:
; on va dessiner 16 lignes: 8 paires (ligne dans bank courant + ligne dans autre bank)
mov bh, 0 ; row index (0..15)
mov dh, 8
.pair:
; ----- ligne row (bank courant) -----
; push di
; push si
call .draw_line ; utilise DI, row=BX
; pop si
; pop di
; ----- ligne row+1 (autre bank) -----
add di, bp
inc bh
; push di
; push si
call .draw_line
; pop si
; pop di
sub di, bp
; avancer de 2 lignes (dans le bank courant): +80 bytes
add di, CGA_STRIDE
inc bh
dec dh
jnz .pair
; popa
ret
; ------------------------------------------------------------
; .draw_line: applique AND/XOR sur une ligne de 16 pixels à ES:DI
; row index dans BX (0..15)
; offset dans BL (0..7)
; sprite base GS:SI
; ------------------------------------------------------------
.draw_line:
push ax
push cx
push dx
push di
; charger masks de la ligne: AND = [SI + row*2], XOR = [SI + 32 + row*2]
xor ax, ax
mov al, bh
shl ax, 1 ; row*2
push si
add si, ax
mov cx, [gs:si] ; AND mask
add si, 32
mov dx, [gs:si] ; XOR mask
pop si
; lire bytes source
mov al, [es:di] ; b0
inc di
mov ah, [es:di] ; b1
dec di
; w0 = (b0<<8)|b1 dans AX déjà
cmp bl, 0
je .aligned
; unaligned: besoin b2 et w1 = (b1<<8)|b2
add di, 2
mov dl, [es:di] ; b2
sub di, 2
mov dh, ah ; b1
; DX = w1 (b1<<8)|b2
; seg = (w0<>(8-off))
; off = BL, rshift = (8-BL)
push bx ; sauvegarde row/off
mov cl, bl
shl ax, cl ; AX = w0<>(8-off)
or ax, dx ; AX = seg (16 bits alignés)
pop bx
jmp .apply_masks
.aligned:
; seg = w0 déjà dans AX
.apply_masks:
; newseg = (seg & AND) ^ XOR
and ax, cx
xor ax, dx ; AX = newseg
cmp bl, 0
je .write_aligned
; --- write unaligned: impact sur b0 (bas 8-off bits), b1 (tout), b2 (haut off bits) ---
; n = 8-off
mov cl, 8
sub cl, bl ; cl = n (1..7)
; val0 = newseg >> (8+off)
; (8+off) = 8 + BL
mov dx, ax
mov cl, bl
add cl, 8
shr dx, cl ; DL = val0 (0..(2^(8-off)-1))
; mask0 = (1<> off) & 0xFF
mov dx, ax
mov cl, bl
shr dx, cl
inc di
mov [es:di], dl
dec di
; b2 high off bits = (newseg & ((1<