; ============================================================================= ; 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 SEG_VIDEO 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 ; ; bit : descr ; 0 : text color : 0=black, 1=white ; 1 : transparent : 1=apply background attribut ; %define GFX_TXT_WHITE_TRANSPARENT 00000000b %define GFX_TXT_BLACK_TRANSPARENT 00000001b %define GFX_TXT_WHITE 00000010b %define GFX_TXT_BLACK 00000011b %define GFX_TXT_TRANSPARENT_BKG 11111111b %define GFX_TXT_TRANSPARENT_TEXT 00000010b %macro GFX 1-* ; %1 est l'index de la fonction ; On itère sur les arguments suivants en sens inverse (Convention C) %rep %0 - 1 ; Répéter pour (Nombre d'args - 1) %rotate -1 ; Prend le dernier argument push %1 ; L'empile %endrep %rotate -1 ; On revient au premier argument (l'index de fonction) call word [cs:graph_driver + ((%1)*2)] ; Nettoyage de la pile (Convention CDECL: l'appelant nettoie) ; Chaque argument = 2 octets (1 word). add sp, (%0 - 1) * 2 %endmacro ; ------------------------------------------------------------ ; 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 1 %define GETPIXEL 2 %define GOTOXY 3 %define TXT_MODE 4 %define PUTCH 5 %define WRITE 6 %define LINE 7 %define RECTANGLE 8 %define RECTANGLE_FILL 9 %define RECTANGLE_ROUND 10 %define MOUSE_HIDE 11 %define MOUSE_SHOW 12 %define MOUSE_MOVE 13 %define MOUSE_DRAW 14 ; ------------------------------------------------------------ ; COMMENTAIRE SUR LA MÉTHODE D'APPEL ; ; GFX FUNCTION, Arg1, Arg2, Arg3 ; ; GFX GFX_PUTPIXEL, 320, 100, 1 ; ; 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. ; ------------------------------------------------------------ align 2 graph_driver: dw cga_init ; init de la carte graphique dw cga_putpixel ; dessin d'un pixel dw cga_getpixel ; lecture d'un pixel dw cga_set_charpos ; gotoxy dw cga_set_writemode ; mode texte dw cga_putc ; dessin d'un caractère dw cga_write ; dessin d'une chaine de caractère dw cga_line ; dessin d'une ligne (Bresenham) avec décision horizontale/verticale dw cga_draw_rect ; dessin d'un rectangle dw cga_fill_rect ; dessin d'un rectangle plein dw cga_draw_rounded_frame ; dessin d'un rectangle arrondi dw cga_mouse_hide ; déplacement du curseur dw cga_mouse_show ; déplacement du curseur dw cga_mouse_cursor_move ; déplacement du curseur dw cga_cursor_draw ; ------------------------------------------------------------ ; initialise le mode graphique (via l'int 10h) ; ; ce mode est entrelacé, un bit/pixel, 8 pixels par octet ; ------------------------------------------------------------ cga_init: mov ax, SEG_BDA_CUSTOM mov ds, ax ; init graphics mode mov ah, 0x00 ; AH=00h set video mode mov al, GFX_MODE int 0x10 ; dessine un background "check-board" call cga_background ret ; dummy functions cga_none: ret ; ------------------------------------------------------------ ; Calcule DI + AH=mask pour (CX=x, DX=y) en mode CGA 640x200 ; ; Out: ES=SEG_VIDEO, DI=offset, AH=bitmask (0x80 >> (x&7)) ; ------------------------------------------------------------ cga_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 ; x / 3 add di, ax ; di = offset banque paire mov bx, dx and bx, 1 ; BX = 0 ou 1 neg bx ; BX = 0 ou 0xFFFF (-1) and bx, 0x2000 ; BX = 0 ou 0x2000 (CGA_ODD_BANK) add di, bx ; Application de l'offset ; calcul du masque du pixel and cl, 7 mov ah, 0x80 shr ah, cl ret ; --------------------------------------------------------------------------- ; gfx_set_writemode (mode) ; Défini le mode d'écriture : ; ; bit : descr ; 0 : text color : 0=black, 1=white ; 1 : transparent : 1=apply background attribut ; ; si le mode n'est pas transparent, la couleur de fond ; est l'inverse de la couleur du texte ; --------------------------------------------------------------------------- ; --- Définition des arguments --- %define .mode word [bp+4] cga_set_writemode: push bp mov bp, sp push fs push ax mov ax, SEG_BDA_CUSTOM mov fs,ax mov ax, .mode mov byte [fs:PTR_GFX + gfx.cur_mode], al pop ax pop fs leave ret %undef .mode ; --------------------------------------------------------------------------- ; gfx_set_charpos (x,y) ; In : CX = x (pixels), DX = y (pixels) ; Out: variables DS:GFX_CUR_* ; Notes: ; - calcule l'offset VRAM de la scanline y: base = (y&1?2000:0) + (y>>1)*80 + (x>>3) ; - stocke aussi shift = x&7 ; --------------------------------------------------------------------------- cga_set_charpos: push bp mov bp, sp ; --- Définition des arguments --- %define .x word [bp+4] %define .y word [bp+6] pusha push fs mov ax,SEG_BDA_CUSTOM mov fs,ax ; store x,y en pixel mov cx, .x mov dx, .y mov [fs:PTR_GFX + gfx.cur_x], cx mov [fs:PTR_GFX + gfx.cur_y], dx mov ax, cx and ax, 0x07 mov [fs:PTR_GFX + gfx.cur_shift], al ; calcul de l'offset de la position 'x,y': ; si 'y' est paire, DI < 0x2000 & add = 0x2000 ; si 'y' est impaire, DI> 0x2000 & add = -0x2000 ; DI = (y>>1)*80 + (x>>3) + (y&1)* mov ax, dx shr ax, 1 mov bx, ax ; bx = dx>>1 shl bx, 4 ; bx = bx * 16 shl ax, 6 ; ax = ax * 64 add bx, ax mov ax, cx shr ax, 3 ; ax = x/8 add bx, ax mov ax, CGA_ODD_BANK test dl, 1 jz .even add bx, ax neg ax add ax, CGA_STRIDE .even: mov [fs:PTR_GFX + gfx.cur_line_ofs], ax mov [fs:PTR_GFX + gfx.cur_offset], bx pop fs ; clean defs %undef .x %undef .y popa leave ret ; --------------------------------------------------------------------------- ; get_glyph_offset ; In : AL = char (ASCII) ; Out: CS:SI -> 8 bytes ; --------------------------------------------------------------------------- get_glyph_offset: cmp al, 0x20 jb .qmark ; al < 20h (' ' cmp al, 0x7E ja .qmark ; al > 7Eh ('~') sub al, 0x20 jmp .ok .qmark: mov al, '?' sub al, 0x20 .ok: xor ah, ah mov si, ax shl si, 3 add si, font8x8 ret ; --------------------------------------------------------------------------- ; cga_putc_unalign (car) ; - x non aligné (x&7 != 0) ; - écrit sur 2 bytes (di et di+1) dans chaque banque ; --------------------------------------------------------------------------- ; convert al -> AX aligned with "cl" %macro ALIGN_BYTE 0 mov ah,al xor al,al shr ax,cl xchg ah,al %endmacro cga_putc: push bp mov bp, sp sub sp, 2 ; 1 word variable locale ; --- Définition des arguments --- %define .car word [bp+4] ; --- Définition des variables locales --- %define .cpt word [bp-2] pusha push fs push es call cga_mouse_hide ; Protection souris mov bx, SEG_VIDEO mov es, bx mov bx, SEG_BDA_CUSTOM mov fs, bx mov ax, .car call get_glyph_offset ; Base offset VRAM pour la scanline Y mov di, [fs:PTR_GFX + gfx.cur_offset] mov bx, [fs:PTR_GFX + gfx.cur_line_ofs] mov ch, [fs:PTR_GFX + gfx.cur_mode] mov cl, [fs:PTR_GFX + gfx.cur_shift] mov .cpt, 4 .row_loop: ; gestion du fond de texte test ch, 00000010b ; transparent background ? jz .skip_attribut ; oui test ch, 00000001b ; background blanc ? je .bkg_black ; background = white mov ax, 0xFF00 shr ax, cl xchg al, ah or [es:di], ax or [es:di+bx], ax jmp .skip_attribut .bkg_black: ; OK ; background = black mov ax, 0xFF00 shr ax, cl xchg al, ah not ax and [es:di], ax and [es:di+bx], ax .skip_attribut: ; ligne "paire" du gylphe xor ax, ax mov al, [cs:si] inc si ALIGN_BYTE mov dx,ax ; ligne "impaire" du gylphe mov al, [cs:si] inc si ALIGN_BYTE xchg ax, dx test ch,00000001b jz .black_text ; white text not ax not dx and [es:di], ax and [es:di+bx], dx jmp .next .black_text: or [es:di], ax or [es:di+bx], dx .next: add di,CGA_STRIDE dec .cpt jnz .row_loop ; Avancer curseur d'un caractère (8 pixels) inc word [fs:PTR_GFX + gfx.cur_offset] add word [fs:PTR_GFX + gfx.cur_x], 8 call cga_mouse_show ; Restauration souris pop es pop fs popa leave ret ; ; write string from [DS:SI] to screen ; ; --- Définition des arguments --- %define .txt_seg word [bp+4] %define .txt_ofs word [bp+6] cga_write: push bp mov bp, sp push ax push ds mov ax, .txt_seg mov ds, ax mov si, .txt_ofs cld .loops: lodsb cmp al,0 je .done push ax call cga_putc jmp .loops .done: pop ds pop ax leave ret %undef .txt_seg %undef .txt_ofs ; ------------------------------------------------------------ ; cga_putpixel (x,y,color) ; Dessine un pixel, accès VRAM direct. ; ; CX = x (0..639) ; DX = y (0..199) ; ES = Target Segment (usually SEG_VIDEO) ; BL = color (0=black, !=0=white) ; ------------------------------------------------------------ ; --- Définition des arguments --- %define .x word [bp+4] %define .y word [bp+6] %define .color byte [bp+8] cga_putpixel: push bp mov bp, sp pusha mov ax, SEG_VIDEO mov es, ax call cga_mouse_hide ; Protection souris mov cx, .x mov dx, .y call cga_calc_addr ; write cmp .color, 0 je .clear .set: or byte [es:di], ah jmp .done .clear: not ah and byte [es:di], ah .done: call cga_mouse_show ; Restauration souris popa leave ret ; clean defs %undef .x %undef .y %undef .color ; ------------------------------------------------------------ ; cga_getpixel (x,y) ; Lit un pixel, accès VRAM direct. ; ; Out: AL=0/1 ; ------------------------------------------------------------ cga_getpixel: push bp mov bp, sp ; --- Définition des arguments --- %define .x word [bp+4] %define .y word [bp+6] push di push es mov cx, .x mov dx, .y mov ax, SEG_VIDEO mov es, ax call cga_calc_addr mov al, [es:di] and al, ah setnz al pop es pop di ; clean defs %undef .x %undef .y leave ret ; ------------------------------------------------------------ ; Dessine un background "check-board", accès VRAM direct. ; ; ------------------------------------------------------------ cga_background: mov ax,SEG_VIDEO mov es,ax mov di,0x0000 mov eax,0xaaaaaaaa cld mov cx,0x800 rep stosd mov di,0x2000 mov eax,0x55555555 mov cx,0x800 ; 640/8/4 rep stosd ret ; ------------------------------------------------------------ ; cga_line_vertical (x, y0, y1, color) ; ; Dessine une ligne x, y0, y1, color ; ------------------------------------------------------------ %define .x word [bp+4] %define .y1 word [bp+6] %define .y2 word [bp+8] %define .color byte [bp+10] cga_line_vertical: push bp mov bp, sp pusha push es ; Setup ES = Video Segment mov ax, SEG_VIDEO ; Utiliser AX plutot que SI pour setup ES mov es, ax call cga_mouse_hide mov bx, .y1 mov dx, .y2 ; 1. Ordonner Y (BX = Debut, DX = Fin) cmp bx, dx jle .y_sorted xchg bx, dx .y_sorted: ; 2. Calculer la hauteur MAINTENANT (avant que DX/BX ne soient modifiés) sub dx, bx ; DX = Hauteur - 1 inc dx ; DX = Hauteur en pixels push dx ; Sauvegarde de la hauteur (compteur) sur la pile [1] ; 3. Calculer l'adresse de départ mov cx, .x ; CX = X mov dx, bx ; DX = Y_Start (Argument pour cga_calc_addr) call cga_calc_addr ; DI = Offset, AH = Masque Bit ; 4. Préparer les registres pour la boucle mov al, ah ; AL = Masque not al ; AL = ~Masque (pour effacer/noir) ; Charger la couleur (CORRECTION BUG COULEUR) mov cl, .color ; On charge la couleur dans CL maintenant ; Récupérer le compteur de hauteur pop dx ; DX = Hauteur restaurée [1] .v_loop: cmp cl, 0 ; Test de la couleur (CL) jz .draw_black .draw_white: or byte [es:di], ah ; Allumer le bit (AH) jmp .next_line .draw_black: and byte [es:di], al ; Eteindre le bit (AL) .next_line: ; --- Logique Next Line CGA --- xor di, 0x2000 ; Bascule banque 0 <-> 1 test di, 0x2000 ; Si on est passé à la banque 1 (0x2000) jnz .skip_add ; On a visuellement descendu d'une ligne, c'est bon. add di, 80 ; Si on revient banque 0, il faut avancer de 80 octets. .skip_add: dec dx ; Décrémenter le compteur de hauteur jnz .v_loop call cga_mouse_show pop es popa leave ; Leave gère le 'mov sp, bp / pop bp' ret ; clean defs %undef .x %undef .y1 %undef .y2 %undef .color ; ------------------------------------------------------------ ; cga_line_horizontal ; Dessine une ligne horizontale de (x1, y) à (x2, y) ; ------------------------------------------------------------ ; --- Définition des arguments --- %define .x1 word [bp+4] %define .x2 word [bp+6] %define .y word [bp+8] %define .color byte [bp+10] cga_line_horizontal: push bp mov bp, sp pusha push es ; Setup ES = Video Segment mov ax, SEG_VIDEO mov es, ax call cga_mouse_hide mov ax, .x1 mov bx, .x2 ; 1. Ordonner X (AX = Gauche, BX = Droite) cmp ax, bx jle .x_sorted xchg ax, bx .x_sorted: ; 2. Calculer l'adresse de départ mov cx, ax ; CX = X_Start pour cga_calc_addr mov dx, .y ; DX = Y push bx ; Sauve X_End push ax ; <--- AJOUT CRITIQUE : Sauve X_Start (AX) call cga_calc_addr ; DI = Offset VRAM (AX est détruit ici !) pop ax ; <--- RESTAURATION : On récupère le bon X1 pop bx ; Restaure X_End ; 3. Préparer la couleur mov dl, .color ; DL = Couleur ; --- ANALYSE DES OCTETS --- mov si, ax shr si, 3 ; SI = Index octet start (Calcul correct maintenant) mov cx, bx shr cx, 3 ; CX = Index octet end ; Cas spécial : Tout dans le même octet ? cmp si, cx je .single_byte ; ============================================ ; CAS MULTI-BYTE (Gauche -> Milieu -> Droite) ; ============================================ ; --- A. PARTIE GAUCHE (Start) --- push cx ; Sauve Index End mov cx, ax and cx, 7 ; CL = X1 % 8 mov dh, 0xFF shr dh, cl ; DH = Masque Gauche call .apply_mask ; Applique DH sur [ES:DI] inc di ; Octet suivant inc si ; Index suivant pop cx ; Restaure Index End ; --- B. PARTIE CENTRALE (Full Bytes) --- cmp si, cx jge .do_right_part mov al, 0x00 ; Noir cmp dl, 0 jz .fill_loop mov al, 0xFF ; Blanc .fill_loop: mov byte [es:di], al inc di inc si cmp si, cx jl .fill_loop .do_right_part: ; --- C. PARTIE DROITE (End) --- mov cx, bx and cx, 7 inc cx mov dh, 0xFF shr dh, cl not dh ; DH = Masque Droite call .apply_mask jmp .done ; ============================================ ; CAS SINGLE BYTE ; ============================================ .single_byte: ; Masque Gauche mov cx, ax and cx, 7 mov dh, 0xFF shr dh, cl ; Masque Droite mov cx, bx and cx, 7 inc cx mov ah, 0xFF shr ah, cl not ah ; Intersection and dh, ah call .apply_mask jmp .done ; --- Helper local --- .apply_mask: cmp dl, 0 jz .apply_black or byte [es:di], dh ret .apply_black: not dh and byte [es:di], dh not dh ret .done: call cga_mouse_show pop es popa leave ret ; clean defs %undef .x1 %undef .x2 %undef .y ; ------------------------------------------------------------ ; cga_line (x1, y1, x2, y2, color) ; Algorithme de Bresenham pour tracer une ligne arbitraire ; ------------------------------------------------------------ %define .x1 word [bp+4] %define .y1 word [bp+6] %define .x2 word [bp+8] %define .y2 word [bp+10] %define .color byte [bp+12] cga_line: push bp mov bp, sp pusha ; Sauvegarde de tous les registres (AX, BX, CX, DX, SI, DI) push es ; Sauvegarde ES ; --- Optimisation : Lignes H/V --- mov ax, .y1 cmp ax, .y2 je .do_horiz mov ax, .x1 cmp ax, .x2 je .do_vert ; --- Bresenham --- mov ax, SEG_VIDEO mov es, ax call cga_mouse_hide sub sp, 10 ; Variables locales: dx, dy, sx, sy, err ; --- Definitions locales --- ; BP pointe sur l'ancien BP. ; PUSHA (16 octets) + PUSH ES (2 octets) = 18 octets ; Les variables locales commencent donc à BP-18 %define .dx word [bp-20] %define .dy word [bp-22] %define .sx word [bp-24] %define .sy word [bp-26] %define .err word [bp-28] ; --- Initialisation Bresenham --- ; dx = abs(x2 - x1), sx = sign(x2 - x1) mov ax, .x2 sub ax, .x1 mov bx, 1 ; sx = 1 jge .calc_dx neg ax neg bx ; sx = -1 .calc_dx: mov .dx, ax ; save dx mov .sx, bx ; save sx ; dy = abs(y2 - y1), sy = sign(y2 - y1) mov ax, .y2 sub ax, .y1 mov bx, 1 ; sy = 1 jge .calc_dy neg ax neg bx ; sy = -1 .calc_dy: mov .dy, ax ; save dy mov .sy, bx ; save sy ; err = dx - dy mov ax, .dx ; dx sub ax, .dy ; dy mov .err, ax ; err ; Coordonnées courantes mov cx, .x1 mov dx, .y1 .loop: ; Plot pixel (CX, DX) push cx push dx call cga_calc_addr ; Out: DI=offset, AH=mask mov bl, .color cmp bl, 0 je .plot_black or byte [es:di], ah jmp .plot_next .plot_black: not ah and byte [es:di], ah .plot_next: pop dx pop cx ; Check fin cmp cx, .x2 jne .step cmp dx, .y2 je .done .step: mov ax, .err ; e2 = err shl ax, 1 ; e2 = 2*err mov bx, .dy ; dy neg bx ; -dy cmp ax, bx jle .check_y add .err, bx ; err += -dy add cx, .sx ; x += sx .check_y: mov bx, .dx ; dx cmp ax, bx jge .loop ; if e2 >= dx, skip y step add .err, bx ; err += dx add dx, .sy ; y += sy jmp .loop .done: call cga_mouse_show add sp, 10 ; Libération variables locales jmp .exit .do_horiz: push word [bp+12] ; color push word [bp+6] ; y push word [bp+8] ; x2 push word [bp+4] ; x1 call cga_line_horizontal add sp, 8 jmp .exit .do_vert: push word [bp+12] ; color push word [bp+10] ; y2 push word [bp+6] ; y1 push word [bp+4] ; x call cga_line_vertical add sp, 8 .exit: pop es popa pop bp ret %undef .x1 %undef .y1 %undef .x2 %undef .y2 %undef .color %undef .dx %undef .dy %undef .sx %undef .sy %undef .err ; ------------------------------------------------------------ ; cga_draw_rect ; Dessine un rectangle vide (contour) ; Entrée : x1, y1, x2, y2, color ; ------------------------------------------------------------ ; Arguments %define .x1 word [bp+4] %define .y1 word [bp+6] %define .x2 word [bp+8] %define .y2 word [bp+10] %define .color word [bp+12] ; word pour l'alignement pile (mais on utilise byte) cga_draw_rect: push bp mov bp, sp pusha ; 1. Ordonner X (x1 < x2) mov ax, .x1 mov bx, .x2 cmp ax, bx jle .x_ok xchg ax, bx mov .x1, ax mov .x2, bx .x_ok: ; 2. Ordonner Y (y1 < y2) mov ax, .y1 mov bx, .y2 cmp ax, bx jle .y_ok xchg ax, bx mov .y1, ax mov .y2, bx .y_ok: call cga_mouse_hide ; 3. Dessiner les 4 lignes ; Pour éviter les pixels en double dans les coins, on peut ajuster légèrement, ; mais pour un driver simple, tracer les 4 lignes brutes est acceptable. GFX LINE, .x1, .y1, .x2, .y1, .color GFX LINE, .x1, .y2, .x2, .y2, .color GFX LINE, .x1, .y1, .x1, .y2, .color GFX LINE, .x2, .y1, .x2, .y2, .color call cga_mouse_show popa leave ret ; clean defs %undef .x1 %undef .y1 %undef .x2 %undef .y2 %undef .color ; ------------------------------------------------------------ ; cga_fill_rect ; Dessine un rectangle plein ; Entrée : x1, y1, x2, y2, pattern_offset (CS:Offset) %define .x1 word [bp+4] %define .y1 word [bp+6] %define .x2 word [bp+8] %define .y2 word [bp+10] %define .pat_id word [bp+12] ; ------------------------------------------------------------ cga_fill_rect: push bp mov bp, sp sub sp, 6 ; Reserve espace pour .pat_idx et .x_end_idx ; Variable locale pour l'index du motif (y % 8) %define .pat_idx word [bp-2] %define .x_end_idx word [bp-4] %define .pattern_offset word [bp-6] pusha push es mov ax, SEG_VIDEO mov es, ax ; calcul de l'offset de départ du pattern mov ax, .pat_id shl ax, 3 ; * 8 add ax, pattern_8x8 mov .pattern_offset, ax call cga_mouse_hide ; --- Tri des coordonnées (X et Y) --- ; Tri X mov ax, .x1 mov bx, .x2 cmp ax, bx jle .x_sorted xchg ax, bx mov .x1, ax mov .x2, bx .x_sorted: ; Tri Y mov ax, .y1 mov bx, .y2 cmp ax, bx jle .y_sorted xchg ax, bx mov .y1, ax mov .y2, bx .y_sorted: ; --- Init Pattern Index --- mov ax, .y1 and ax, 7 mov .pat_idx, ax ; --- Calcul de la hauteur --- mov bx, .y2 sub bx, .y1 inc bx ; BX = Hauteur finale push bx ; SAUVEGARDER LA HAUTEUR SUR LA PILE [STACK A] ; (Car on va utiliser BX dans cga_calc_addr ou juste après) ; --- Calcul adresse de départ --- mov cx, .x1 mov dx, .y1 ; Note: cga_calc_addr attend CX=x, DX=y et retourne DI ; Si cga_calc_addr modifie BX, la sauvegarde [STACK A] est vitale. call cga_calc_addr ; DI = Offset début ligne ; --- Préparer les masques --- ; Masque Gauche (DH) mov cx, .x1 and cx, 7 mov dh, 0xFF shr dh, cl ; Masque Droite (AH) mov cx, .x2 and cx, 7 inc cx mov ah, 0xFF shr ah, cl not ah ; Index octets mov si, .x1 shr si, 3 ; SI = Index start byte mov cx, .x2 shr cx, 3 ; CX = Index end byte mov .x_end_idx, cx ; Sauvegarde de l'index de fin pour la boucle horizontale ; --- Récupérer la hauteur dans un registre sûr --- pop bx ; RESTAURER HAUTEUR DANS BX [STACK A] ; BX est maintenant notre compteur de boucle. ; --- BOUCLE VERTICALE --- .row_loop: push di ; Sauve début ligne push si ; Sauve index byte ; --- Récupérer le byte du motif pour cette ligne --- push bx mov bx, .pat_idx push si mov si, .pattern_offset mov dl, [cs:si + bx] ; DL = Pattern Byte pop si pop bx ; --- Remplissage Horizontal --- mov cx, .x_end_idx ; Restaurer l'index de fin pour la ligne ; Cas Single Byte (Start == End) cmp si, cx je .single_byte_row ; Partie Gauche mov al, dh call .apply_mask_fill inc di inc si ; Partie Centrale (Boucle tant que SI < CX) jmp .check_mid .mid_loop: mov byte [es:di], dl inc di inc si .check_mid: cmp si, cx jl .mid_loop ; Partie Droite mov al, ah call .apply_mask_fill jmp .row_done .single_byte_row: mov al, dh and al, ah ; Intersection des masques call .apply_mask_fill .row_done: pop si pop di ; Incrémenter index motif pour la prochaine ligne inc word .pat_idx and word .pat_idx, 7 ; --- Passage ligne suivante (CGA Bank Switch) --- xor di, 0x2000 test di, 0x2000 jnz .bank_switch_ok add di, 80 .bank_switch_ok: dec bx ; On décrémente BX (Hauteur) au lieu de BP jnz .row_loop call cga_mouse_show pop es popa leave ret ; helper .apply_mask_fill: push ax not al and byte [es:di], al pop ax and al, dl ; Motif masqué or byte [es:di], al ; Applique ret ; Nettoyage des defines %undef .x1 %undef .y1 %undef .x2 %undef .y2 %undef .pat_idx ; ------------------------------------------------------------ ; cga_draw_rounded_frame ; dessiner un cadre arrondi ; Entrée : AX=x1, BX=y1, CX=x2, DX=y2 ; ------------------------------------------------------------ %define .x1 word [bp+4] %define .y1 word [bp+6] %define .x2 word [bp+8] %define .y2 word [bp+10] %define .color word [bp+12] ; Variable locale pour l'index du motif (y % 8) %define .coord1 word [bp-2] %define .coord2 word [bp-4] cga_draw_rounded_frame: push bp mov bp, sp sub sp, 4 ; Reserve espace pour .coord1 et .coord2 pusha call cga_mouse_hide ; Lignes horizontales (raccourcies de 2px pour laisser place à l'arrondi) mov ax, .x1 add ax, 2 mov .coord1, ax mov bx, .x2 sub bx, 2 mov .coord2, bx GFX LINE, .coord1, .y1, .coord2, .y1, .color GFX LINE, .coord1, .y2, .coord2, .y2, .color ; Lignes verticales (raccourcies de 1px en haut et en bas) mov cx, .y1 add cx, 2 mov .coord1, cx mov dx, .y2 sub dx, 2 mov .coord2, dx GFX LINE, .x1, .coord1, .x1, .coord2, .color GFX LINE, .x2, .coord1, .x2, .coord2, .color ; Ajout des pixels de transition pour adoucir les coins (chanfrein) mov ax, .x1 inc ax ; x1 + 1 mov bx, .y1 inc bx ; y1 + 1 GFX PUTPIXEL, ax, bx, .color ; Coin haut-gauche mov ax, .x1 inc ax mov bx, .y2 dec bx ; y2 - 1 GFX PUTPIXEL, ax, bx, .color ; Coin bas-gauche mov ax, .x2 dec ax ; x2 - 1 mov bx, .y1 inc bx ; y1 + 1 GFX PUTPIXEL, ax, bx, .color ; Coin haut-droite mov ax, .x2 dec ax mov bx, .y2 dec bx ; y2 - 1 GFX PUTPIXEL, ax, bx, .color ; Coin bas-droite call cga_mouse_show popa leave ret %undef .x1 %undef .y1 %undef .x2 %undef .y2 %undef .color ; ----------------------------------------------------------------------------- ; ------------------------------------------------------------ ; GESTION DU CURSEUR ; ------------------------------------------------------------ ; ; ------------------------------------------------------------ ; gère la demande d'effacement du curseur souris ; ; ------------------------------------------------------------ cga_mouse_hide: pushf ; Sauver l'état des flags (interrupts) cli ; Désactiver les interruptions (CRITIQUE) pusha ; Sauvegarder TOUS les registres 32-bits push ds mov ax, SEG_BDA_CUSTOM mov ds, ax ; Décrémenter le compteur dec byte [PTR_MOUSE + mouse.cur_counter] ; Vérifier si on vient juste de passer en mode caché (c-à-d on est à -1) cmp byte [PTR_MOUSE + mouse.cur_counter], -1 jne .skip_restore ; Si on est à -2, -3... elle est déjà cachée call cga_cursor_restorebg .skip_restore: pop ds popa popf ; Restaure les interruptions (STI si elles étaient là) ret ; ------------------------------------------------------------ ; gère la demande d'affichage du curseur souris ; ; ------------------------------------------------------------ cga_mouse_show: pushf cli pusha push ds mov ax, SEG_BDA_CUSTOM mov ds, ax ; Incrémenter le compteur inc byte [PTR_MOUSE + mouse.cur_counter] ; Vérifier si on est revenu à 0 (Visible) cmp byte [PTR_MOUSE + mouse.cur_counter], 0 jne .skip_draw ; Si on est encore à -1, -2... on reste caché ; C'est la transition Caché -> Visible : On affiche le curseur ; IMPORTANT : On sauve le fond ACTUEL (qui a peut-être changé pendant le hide) call cga_cursor_savebg call cga_cursor_draw .skip_draw: pop ds popa popf ; restore également le flag d'interruption ret ; ------------------------------------------------------------ ; gère le déplacement du curseur souris ; ; ------------------------------------------------------------ cga_mouse_cursor_move: pushad push ds push es ; move Data Segment mov ax, SEG_BDA_CUSTOM mov ds, ax cmp byte [PTR_MOUSE + mouse.cur_counter], 0 jl .done ; Si < 0, on ne dessine rien ! cmp byte [PTR_MOUSE + mouse.cur_drawing],0 jne .done mov byte [PTR_MOUSE + mouse.cur_drawing],1 mov ax, SEG_VIDEO mov es, ax call cga_cursor_restorebg call cga_cursor_savebg call cga_cursor_draw mov byte [PTR_MOUSE + mouse.cur_drawing],0 .done: pop es pop ds popad ret ; ------------------------------------------------------------ ; cga_cursor_savebg ; Sauve 16 lignes (4 bytes/ligne) sous le curseur. ; Stocke 16 DWORDs: chaque DWORD contient les 4 bytes de la ligne ; ------------------------------------------------------------ cga_cursor_savebg: cmp byte [PTR_MOUSE + mouse.bkg_saved], 0 ; le buffer n'a pas encore été restauré jne .done push es mov ax, SEG_VIDEO mov es, ax ; mémoriser position courante (utilisée pour restore) mov cx, [PTR_MOUSE + mouse.x] mov dx, [PTR_MOUSE + mouse.y] mov [PTR_MOUSE + mouse.cur_x], cx mov [PTR_MOUSE + mouse.cur_y], dx ; calcule ES:DI pour (x,y) call cga_calc_addr mov [PTR_MOUSE + mouse.cur_addr_start], di lea si, [PTR_MOUSE + mouse.bkg_buffer] mov cx,16 .row_loop: ; ---- ligne bank courant ---- mov eax, [es:di] ; lit b0 b1 b2 b3 mov [ds:si], eax ; stocke 4 bytes (le byte haut sera 0) add si, 4 xor di, 0x2000 test di, 0x2000 jnz .next_line add di, CGA_STRIDE ; avance de 2 lignes dans bank courant .next_line: loop .row_loop pop es .done: mov byte [PTR_MOUSE + mouse.bkg_saved], 1 ; Flag mis à jour après la copie ret ; ------------------------------------------------------------ ; cga_cursor_restorebg ; Restaure 16 lignes sauvegardées. ; ------------------------------------------------------------ cga_cursor_restorebg: cmp byte [PTR_MOUSE + mouse.bkg_saved], 0 je .done push es mov ax, SEG_VIDEO mov es, ax ; restaurer depuis la dernière position sauvegardée mov di, [PTR_MOUSE + mouse.cur_addr_start] lea si, [PTR_MOUSE + mouse.bkg_buffer] mov cx, 16 .row_loop: ; ---- ligne bank courant ---- mov eax, [ds:si] ; saved (32 bits) mov [es:di], eax ; restore line add si, 4 xor di, 0x2000 test di, 0x2000 jnz .next_line add di, CGA_STRIDE ; avance de 2 lignes dans bank courant .next_line: loop .row_loop pop es .done: mov byte [PTR_MOUSE + mouse.bkg_saved], 0 ; Flag mis à jour après la restauration ret ; ============================================================ ; cga_cursor_draw ; Détruit: registres sauvegardés/restaurés (PUSHA/POPA) ; ============================================================ cga_cursor_draw: push bp mov bp, sp ; variables %define .height word [bp-2] %define .mask dword [bp-6] sub sp, 6 ; Reserve space for locals pushad push ds push es push gs cld ; Sécurité : Direction avant mov ax, SEG_BDA_CUSTOM mov ds, ax mov ax, SEG_VIDEO mov es, ax ; --- CLIPPING VERTICAL --- mov dx, [ds:PTR_MOUSE + mouse.y] mov bx, GFX_HEIGHT cmp dx, bx jae .exit_total mov ax, 16 sub bx, dx ; 200-y cmp ax, bx jbe .y_ok ; y<= 200-16 mov ax, bx .y_ok: mov .height, ax ; On calcule si les 4 octets vont dépasser la ligne (80 octets) ; X est en bits (0-639). X >> 3 donne l'octet de départ (0-79). ; Si StartByte >= 77, on déborde. mov cx, [PTR_MOUSE + mouse.x] shr cx, 3 ; CX = Byte Offset (0-79) xor esi, esi ; ESI = Masque de protection (0 = tout dessiner) cmp cx, 76 ; 76 est le dernier offset sûr (76,77,78,79) jbe .mask_ready ; Si <= 76, pas de débordement ; Calcul du débordement ; Si CX=77 (Déborde 1 byte) -> Masque 0x000000FF (LSB car bswap) ; Si CX=78 (Déborde 2 bytes)-> Masque 0x0000FFFF ; Si CX=79 (Déborde 3 bytes)-> Masque 0x00FFFFFF cmp cx, 77 jne .check_78 mov esi, 0x000000FF jmp .mask_ready .check_78: cmp cx, 78 jne .check_79 mov esi, 0x0000FFFF jmp .mask_ready .check_79: mov esi, 0x00FFFFFF ; Cas extrême (bord droit) .mask_ready: mov .mask, esi ; --- ADRESSAGE --- mov cx, [PTR_MOUSE + mouse.x] mov dx, [PTR_MOUSE + mouse.y] call cga_calc_addr ; DI = Offset VRAM mov ax, [PTR_MOUSE + mouse.cur_seg] mov gs, ax mov si, [PTR_MOUSE + mouse.cur_ofs] ; --- CALCUL de l'offset entre 2 lignes +0x2000 ou -0x2000 --- mov ax, 0x2000 ; Banque +1 mov cx, [PTR_MOUSE + mouse.y] test cx, 1 jz .setup_done neg ax ; banque -1 .setup_done: mov cx, ax .row_loop: ; --- CONSTRUCTION DES MASQUES (EAX/EBX) --- push cx ; préserver la banque "suivante" mov cl, [PTR_MOUSE + mouse.x] and cl, 0x07 ; --- MASQUE AND (EAX) --- mov ax, [gs:si] ; masque AND rol eax, 16 ; Bascule les 16 bits de poids fort vers le bas mov ax, 0xFFFF xchg ah, al ; Échange les nouveaux octets de poids faible ror eax, cl or eax, .mask ; Force les bits clippés à 1 (garde le fond) ; --- MASQUE XOR (EBX) --- xor ebx, ebx mov bx, [gs:si+32] ; masque XOR rol ebx, 16 xchg bh, bl ror ebx, cl mov edx, .mask ; charge le masque de protection not edx ; inverse le masque de protection and ebx, edx ; applique le masque de protection ; --- LECTURE ET MODIFICATION (EDX) --- mov edx, [es:di] ; lecture des 16 bits a la position du curseur ; bswap edx xchg dh, dl rol edx, 16 xchg dh, dl and edx, eax ; application du masque AND (AX) xor edx, ebx ; application du masque XOR (BX) ; bswap edx xchg dh, dl rol edx, 16 xchg dh, dl mov [es:di], edx ; ecriture du résultat ; --- GESTION DES BANQUES --- pop cx add di, cx ; banque suivante test cx,0x8000 jz .next_line add di,80 .next_line: neg cx ; prochaine banque = -banque add si, 2 ; Prochaine ligne du sprite dec .height jnz .row_loop .exit_total: pop gs pop es pop ds popad leave ret %undef .height %undef .mask