   #Start Contents

                        Bezposredni dostep do ekranu

   Jesli myslicie, ze odpowiednie funkcje przerwan 10h i 21h sa jedynym
   sposobem na to, aby napisac cos na ekranie, to ten kurs pokaze Wam,
   jak bardzo sie mylicie.

   Na ekran w trybie tekstowym sklada sie 80x25 = 2000 znakow. Nie
   oznacza to jednak 2000 bajtow, gdyz kazdy znak "zaopatrzony" jest w
   pewna wartosc (1 bajt) mowiaca o jego wygladzie. Lacznie jest wiec
   2000 slow (word, 16 bitow = 2 bajty), czyli 4000 bajtow. Malo, w
   porownaniu z wielkoscia 1 segmentu (64kB). Te 4000 bajtow "zyje" sobie
   w pewnym segmencie pamieci - 0B800h (kolorowe karty graficzne) lub
   0B000h (mono).

   Struktura tego bloku nie jest skomplikowana i wyglada nastepujaco:

   b800:0000 - znak 1, w lewym gornym rogu
   b800:0001 - atrybut znaku 1
   b800:0002 - znak 2, znajdujacy sie o 1 pozycje w prawo od znaku 1
   b800:0003 - atrybut znaku 2
   i tak dalej

   Czym zas jest atrybut?
   Jest to bajt mowiacy o kolorze danego znaku i kolorze tla dla tego
   znaku. Bity w tym bajcie oznaczaja:
   3-0 - kolor znaku (16 mozliwosci)
   6-4 - kolor tla (8 mozliwosci)
   7 - miganie znaku (jesli nie dziala, to oznacza, ze mamy 16 kolorow
   tla zamiast 8)

   Jeszcze tylko wystarczy omowic kolory odpowiadajace poszczegolnym
   bitom i mozemy cos pisac.
   Oto te kolory:
   Czarny - 0, niebieski - 1, zielony - 2, blekitny - 3, czerwony - 4,
   rozowy - 5, brazowy - 6, jasnoszary (ten standardowy) - 7, ciemnoszary
   - 8, jasnoniebieski - 9, jasnozielony - 10, jasnoblekitny - 11,
   jasnoczerwony - 12, jasnorozowy - 13, zolty - 14, bialy - 15.

   To powinno mowic samo za siebie: chcemy bialy znak na czarnym tle?
   Odpowiedni bajt = 0fh.
   A moze zolty znak na niebieskim tle? Bajt = 1eh.

   Ponizej zamieszczam takze programik, ktory szybko napisalem w celu
   przetestowania teorii tu przedstawionej (skladnia NASM):
   (przeskocz przykladowy program)
        ; nasm -O999 -o test.com -f bin test.asm

        org 100h

                mov ax, 0b800h
                mov bx, cs
                mov es, ax              ; es = 0b800 = segment pamieci ekranu
                mov ds, bx              ; ds = cs
                xor di, di              ; pozycja docelowa = di = 0
                mov si, tekst           ; skad brac bajty
                mov cx, dlugosc         ; ile bajtow brac

                rep movsb               ; przesun CX bajtow z DS:SI do ES:DI

                xor ah, ah
                int 16h

                mov ax, 4c00h
                int 21h

        tekst   db "T",1,"e",2,"k",3,"s",4,"t",5
                db " ",6,"w",7,"i",8,"e",9,"l",10,"o",11,"k",12,"o",13
                db "l",14,"o",15,"r",16,"o",27h,"w",38h,"y",49h

        dlugosc equ $-tekst

   Zastosowalem w nim stala typu equ, aby nie zmieniac CX po kazdorazowej
   nawet najdrobniejszej zmianie tekstu.

   Jak widac, wpisywanie kazdorazowo znaku z jego argumentem
   niekoniecznie sprawia przyjemnosc. Na szczescie z pomoca przychodzi
   nam BIOS, ale nie funkcja 0e przerwania 10h, lecz funkcja 13h tegoz
   przerwania (opis wyciety z Ralf Brown's Intterrupt List):
   (przeskocz opis int 10h, ah=13h)
        INT 10 - VIDEO - WRITE STRING (AT and later,EGA)
                AH = 13h
                AL = write mode
                   bit 0: update cursor after writing
                   bit 1: string contains alternating characters
                         and attributes
                   bits 2-7: reserved (0)
                BH = page number
                BL = attribute if string contains only characters
                CX = number of characters in string
                DH,DL = row,column at which to start writing
                ES:BP -> string to write

   I krotki przykladzik zastosowania (fragment kodu dla TASMa):
   (przeskocz przyklad zastosowania int 10h, ah=13h)
        mov cx,cs
        mov ax,1301h                    ; funkcja pisania ciagu znakow
        mov es,cx                       ; es = cs
        mov bx,j_czer                   ; atrybut (kolor)
        mov cx,info1_dl                 ; dlugosc ciagu
        mov bp,offset info1             ; adres ciagu
        mov dx,(11 shl 8) or (40 - (info1_dl shr 1))    ;wiersz+kolumna
        int 10h                         ; piszemy napis

        info1           db      "Informacja"
        info1_dl        equ     $ - info1

   Najwiecej watpliwosci moze wzbudzac linia kodu, ktora zapisuje wartosc
   do DX (wiersz i kolumne ekranu). Do DH idzie oczywiscie 11 (bo do DX
   idzie b=11 shl 8, czyli 0b00h). Napis (info1_dl shr 1) dzieli dlugosc
   tekstu na 2, po czym te wartosc odejmujemy od 40. Po co?
   Jak wiemy, ekran ma 80 znakow szerokosci. A tutaj od 40 odejmujemy
   polowe dlugosci tekstu, ktory chcemy wyswietlic. Uzyskamy wiec w taki
   sposob efekt wysrodkowania tekstu na ekranie. I to wszystko.

   No dobrze, a co jesli nie chcemy uzywac przerwan a i tak chcemy miec
   tekst w wyznaczonej przez nas pozycji?
   Trzeba wyliczyc "odleglosc" naszego miejsca od lewego gornego rogu
   ekranu. Jak nietrudno zgadnac, wyraza sie ona wzorem (gdy znamy
   wspolrzedne przed kompilacja):
   wiersz*80 + kolumna
   i to te wartosc umieszczamy w DI i wykonujemy rep movsb.
   Gdy zas wspolrzedne moga sie zmieniac lub zaleza od uzytkownika, to
   uzyjemy nastepujacej sztuczki (kolumna i wiersz to 2 zmienne po 16
   bitow):
   (przeskocz obliczanie adresu w pamieci ze wspolrzednych)
        mov ax, [wiersz]
        mov bx, ax              ; BX = AX
        shl ax, 6               ; AX = AX*64
        shl bx, 4               ; BX = BX*16 = AX*16
        add ax, bx              ; AX = AX*64 + AX*16 = AX*80
        add ax, [kolumna]       ; AX = 80*wiersz + kolumna

        mov di, ax
        shl di, 1       ; DI mnozymy przez 2, bo sa 2 bajty na pozycje

   i tez uzyskamy prawidlowy wynik. Odradzam stosowanie instrukcji
   (I)MUL, gdyz jest dosc powolna.

   Zajmiemy sie teraz czyms jeszcze ciekawszym: rysowanie ramek na
   ekranie. Oto programik, ktory na ekranie narysuje 2 wypelnione
   prostokaty (jeden bedzie wypelniony kolorem czarnym). Korzysta on z
   procedury, ktora napisalem specjalnie w tym celu. Oto ten programik:
   (przeskocz program rysujacy okienka z ramka)
; Rysowanie okienek z ramka
;
; Autor: Bogdan D.
;
; nasm -O999 -o ramki.com -f bin ramki.asm


org 100h

; ramki podwojne:

        mov     ah, 7
        xor     bx, bx
        xor     cx, cx
        mov     dx, 9
        mov     bp, 9
        call    rysuj_okienko

        mov     ah, 42h
        mov     bx, 10
        mov     cx, 10
        mov     dx, 20
        mov     bp, 16
        call    rysuj_okienko

        xor     ah, ah
        int     16h

        mov     ax, 4c00h
        int     21h

rysuj_okienko:

; wejscie:
;
;  AH = atrybut znaku (kolor)
;  BX = kolumna lewego gornego rogu
;  CX = wiersz lewego gornego rogu
;  DX = kolumna prawego dolnego rogu
;  BP = wiersz prawego dolnego rogu
;
; wyjscie:
;  nic


r_p     equ     0bah            ; prawa boczna
r_pg    equ     0bbh            ; prawa gorna (naroznik)
r_pd    equ     0bch            ; prawa dolna

r_g     equ     0cdh            ; gorna
r_d     equ     r_g             ; dolna

r_l     equ     r_p             ; lewa boczna
r_lg    equ     0c9h            ; lewa gorna
r_ld    equ     0c8h            ; lewa dolna

spacja  equ     20h


        push    di
        push    si
        push    es
        push    ax

        mov     di, cx
        mov     si, cx
        shl     di, 6
        shl     si, 4
        add     di, si          ; DI = DI*80 = numer pierwszego wiersza * 80


        mov     si, 0b800h
        mov     es, si          ; ES = segment ekranu

        mov     si, di
        add     di, bx          ; DI = pozycja poczatku
        add     si, dx          ; SI = pozycja konca

        shl     di, 1           ; 2 bajty/element
        shl     si, 1

        mov     al, r_lg
        mov     [es:di], ax     ; rysujemy lewy gorny naroznik

        add     di, 2

        mov     al, r_g         ; bedziemy rysowac gorny brzeg

.rysuj_gore:

        cmp     di, si          ; dopoki DI < pozycja koncowa
        jae     .koniec_gora

        mov     [es:di], ax
        add     di, 2
        jmp     short .rysuj_gore

.koniec_gora:
        mov     al, r_pg
        mov     [es:di], ax     ; rysujemy prawy gorny naroznik


.wnetrze:
        shr     di, 1

        add     di, 80          ; kolejny wiersz
        sub     di, dx          ; poczatek wiersza

        push    di

        mov     di, bp
        mov     si, bp
        shl     di, 6
        shl     si, 4
        add     si, di          ; SI = SI*80 = numer ostatniego wiersza * 80

        pop     di

        cmp     di, si          ; czy skonczylismy?
        je      .koniec_wnetrze

        mov     si, di
        add     di, bx          ; DI = pozycja poczatku
        add     si, dx          ; SI = pozycja konca

        shl     di, 1           ; 2 bajty / element
        shl     si, 1

        mov     al, r_l
        mov     [es:di], ax     ; rysujemy lewy brzeg
        add     di, 2

        mov     al, spacja      ; wnetrze okienka wypelniamy spacjami
.rysuj_srodek:

        cmp     di, si          ; dopoki DI < pozycja koncowa
        jae     .koniec_srodek

        mov     [es:di], ax
        add     di, 2
        jmp     short .rysuj_srodek

.koniec_srodek:

        mov     al, r_p
        mov     [es:di], ax     ; rysujemy prawy brzeg

        jmp     short .wnetrze

.koniec_wnetrze:


        mov     di, bp
        mov     si, bp
        shl     di, 6
        shl     si, 4
        add     di, si          ; DI = DI*80


        mov     si, di
        add     di, bx          ; DI = pozycja poczatku w ostatnim wierszu
        add     si, dx          ; SI = pozycja konca w ostatnim wierszu

        shl     di, 1           ; 2 bajty / element
        shl     si, 1

        mov     al, r_ld
        mov     [es:di], ax     ; rysujemy lewy dolny naroznik

        add     di, 2

        mov     al, r_d         ; bedziemy rysowac dolny brzeg

.rysuj_dol:

        cmp     di, si          ; dopoki DI < pozycja koncowa
        jae     .koniec_dol

        mov     [es:di], ax
        add     di, 2
        jmp     short .rysuj_dol

.koniec_dol:
        mov     al, r_pd
        mov     [es:di], ax     ; rysujemy prawy dolny naroznik


        pop     ax
        pop     es
        pop     si
        pop     di

        ret

   Program nie jest skomplikowany, a komentarze powinny rozwiac wszystkie
   watpliwosci. Nie bede wiec szczegolowo omawial, co kazda linijka robi,
   skupie sie jednak na kilku sprawach:
     * Oddzielanie instrukcji od jej argumentow tabulatorem
       Poprawia to nieco czytelnosc kodu.
     * Kropki przed etykietami
       Sprawiaja, ze te etykiety sa lokalne dla tej procedury. Nie beda
       sie mylic z takimi samymi etykietami umieszczonymi po innej
       etykiecie globalnej.
     * Stosowanie equ
       Wygodniejsze niz wpisywanie ciagle tych samych bajtow w kilkunastu
       miejscach. Szybko umozliwiaja przelaczenie sie na przyklad na
       ramki pojedynczej dlugosci.
     * Nie uzywam MUL, gdyz jest za wolne (co prawda tutaj nie zrobiloby
       to moze ogromnej roznicy, ale gdzie indziej mogloby).
     * Umieszczenie w programie sposobu kompilacji
       Moze oszczedzic innym duzego bolu glowy, ktorego by sie nabawili,
       szukajac kompilatora dla tego kodu.
     * Napisanie, co procedura przyjmuje i co zwraca
       Bardzo wazne! Dzieki temu uzytkownik wie, co ma wpisac do jakich
       rejestrow, co procedura zwraca i (ewentualnie) ktore rejestry
       modyfikuje (tego raczej nalezy unikac).

   Jak widac, "reczne" manipulowanie ekranem wcale nie musi byc trudne, a
   jest wprost idealnym rozwiazaniem, jesli zalezy nam na szybkosci i nie
   chcemy uzywac powolnych przerwan.

   Milego eksperymentowania!

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
