Post

CZE | Plamínek v x86 assembleru.

Retro-průvodce vytvářením grafického efektu plamínku v x86 assembleru pro MS-DOS s příkladem kódu. Initicializace VGA režimu 320x200 s 256 barevnou paletou.

CZE | Plamínek v x86 assembleru.

Úvod

Článek by měl vysvětlit, jak fungují efekty typu plamínek/oheň a případně pomoci s napsáním vlastního programu. Přiložený kód je můj vlastní krom generátoru náhodných čísel. Algoritmus je fakt úplně primitivní, kdesi na internetu lze při troše štěstí najít tinyfire na 67 bytů. Můj program vypadá trochu složitěji, ale velkou část tvoří vygenerování palety.

Inicialize

Na začátku programu je dobré inicializovat generátor náhodných čísel, přepnout do grafického módu, vymazat videopaměť (občas v ní něco je) a vhodně nastavit paletu (může být vygenerována za běhu programu nebo pevně daná jako data).

Základ algoritmu

Plamínek je generován asi takto: Máme pole (matici) uložené přímo ve videopaměti. Ve spodním řádku se vygenerují náhodná čísla. Potom se pole projede shora dolů a každý bod se spočítá z okolních například podle vzorce:

1
2
3
bod[x,y] = ( bod[x,y] + bod[x-1,y+1] + bod[x,y+1] + bod [x+1,y+1] ) / 4;
if bod[x,y]>1 then 
  bod[x,y]=bod[x,y] - 1

Samozřejmě lze použít i jiný vzorec, důležité je, aby aktuální bod byl průměrem několika sousedních. Krátce se počká na z5ný běh paprsku (kvůli zdržení) a cyklus se opakuje, do stisku tlačítka, ať už na klávesnici nebo (v horším případě) na přední části kompu.

Ukončení programu

Na konci programu je nutné nastavit textový mód a vymazat buffer klávesnice, jinak klávesu zachytí shell nebo správce souborů, ze kterého byl program spuštěn.

Paleta

Aby plamínek vypadal přirozeně, je zapotřebí mít vhodnou paletu. 0 musí být černá a 255 nejsvětlejší barva. Přechod by měl být plynulý. Paleta nemusí být nutně laděná do červena, docela hezky vypadá i hořící plyn nebo plamen zabarvený do zelena. Z vlastní zkušenosti však vím, že plyn se dělá trochu hůř. Na začátek doporučuji paletu černobílou a experimentovat, až bude fungovat základní algoritmus. Ve svém programu používám přechod černá -> modrá -> oranžová -> žlutá -> bílá, s tím, že bílá vyplňuje asi půlku palety a přechod černá -> modrá je velmi krátký.

Postřehy

  • Dema tohoto typu obecně doporučují místo provádění výpočtu přímo ve videopaměti použít druhý buffer, který se při zpětném běhu paprsku do videopaměti zkopíruje. Přístup k videopaměti je pomalejší než u běžné RAM a u ohně přes celou obrazovku je tento rozdíl znatelný. Velkou výhodu vidím také v tom, že se snadno skryje nehezký spodek plamínku (nekopíruje se).
  • Videopaměť na adrese A000:0000 má délku 65536 (64K), ale 320x200=64000 (64k). Rozdíl je 1536b. Využít toho lze zase k odstranění spodku plamene generováním náhodných číslel na 204. řádku. Pokud kód nepřesahuje 1280b a požívá se druhý buffer, není nutno pro něj alokovat paměť. (255b PSP, 1280b kód, 320x200b buffer).
  • Spodek plamene mohou tvořit náhodná čísla z intervalu 0-255 a nebo pouze čísla 0 a 255. U druhého způsobu vypadá spodek plamínku škaredě, avšak u ohně širokého přes celou obrazovku je tento způsob lepší a dokonce lze generovat místo bodů krátké čárky. V opačném případě by kvůli postupnému rozmazávání vznikl nahoře spíš barevný přechod než oheň.

rand()&0xff

(rand()&1)*0xff

  • Každý bod je průměrem několika okolních. Čím více bodů se zprůměruje, tím rychleji se plamínek rozmazává. Není vhodné vespod generovat čísla 0-255 a průměrovat 8 okolních bodů. Druhý extrém je generovat dvoubodové čárky, a ve smyčce pak průměrovat 4 body.
  • Pokud vypadá spodek plamene škaredě, lze možné to zachránit vygenerováním palety tak, aby přibližně půlku tvořila bílá barva, jak je to v mém programu.

Programy

flame_ex.zip Ukázkové programy. Jeden je v Netwide assembleru, čtyři další pro DJGPP (až na jeden potřebují knihovnu allegro). Dva programy jsou i jako binárky. Možná chybí komentáře co je co.

Kód

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
[bits 16]
[org 0x0100]
[section .text]
start:	;jmp	 zac		;testovani plaminku
;------ Nastaveni generatoru nahodnych cisel
	xor	ax,ax
	int	0x1A
	mov	[seed],dx

;------ Nastaveni vsech barev na bilou
	mov	di,pal		;Bila paleta
	mov	cx,192		;256*3barvy/4dword
	mov	eax,0x3f3f3f3f
	rep	stosd

;------ Nastaveni pal. na cerna->modra->oranzova->zluta->bila....
;	asi nejdelsi a nejhnusnejsi cast programu
	lea	di,[pal]

;Cerna(0,0,0) -> modra(0,0,24)
LP0:	xor	ax,ax		;cx=0 (viz rep stosd)
	stosw
	mov	ax,cx
	stosb
	add	cl,2
	cmp	cl,24
	jne	LP0

;Modra(16,8,24) -> oranzova(64,32,0)
	mov	dx,16		;cx=24
LP1:	mov	ax,dx
	stosb			;r (dx)
	shr	al,1
	stosb			;g = b>>1 (dx)
	xor	ax,ax
	or	cl,cl
	jz	nemodr		;if (b!=0) b--;
	dec	cx
	mov	al,cl
nemodr: stosb			;b (cx)
	inc	dx
	test	dl,64
	jz	LP1

;oranzova(64,32,0) -> zluta (64,64,0)
	inc	di		;r = 0x3f
	mov	cl,32
LP2:	mov	al,cl
	stosb			;g (cl)
	mov	ax,0x3f00	;br (opacne)
	stosw
	add	cl,2
	test	cl,64
	jz	LP2
	dec	di

;zluta(64,64,0)->bila(64,64,64)
	xor	ax,ax
	mov	bx,2
LP3:	mov	[di+bx],al	;pal[di+3*i+2]=2*i
	add	bl,3
	add	al,2
	test	al,64
	jz	LP3

;------ Nastaveni graf. modu 320x200x256c
zac:	mov	ax,0x0013
	int	0x10
;------ Nastaveni palety
	mov	ax,0x1012
	xor	ebx,ebx 	;Prvni registr
	mov	cl,0x00FF	;Pocet registru barev
	lea	dx,[pal]	;ds:dx paleta
	int	0x10
;------ Nastaveni ES na VRAM
	mov	ax,0xA000	;segment videopameti
	mov	es,ax
	mov	ds,ax
;------ Vymaz VRAM
	mov	eax,ebx 	;bx=0
	mov	di,bx
	mov	cx,16000
	repnz	stosd
;------ Nakresleni palety (muze a nemusi byt jako poznamka)
;	mov	 di,167*320
;	mov	  word[di-320],0xf0f0
;	mov	 cx,0020h
;ShowPal:push	 cx
;	xor	 ax,ax
;@ShowP:stosb
;	inc	 ax
;	test	 ax,256
;	jz	 @ShowP
;	add	 di,320-256	;proc asi?
;	pop	 cx
;	loop	 ShowPal

L1:
;------ update spodku ohne (pseudonahodna cisla)
	mov	ax,[cs:seed]
	mov	di,320*100+144	;144
	mov	cl,0x10 	;Sirka/2
L2:	add	ax,0x1234	;Pseudonahodny cislo
	xor	al,ah		;Outlaw triad
	rol	ah,1
	add	ax,0x4321
	rol	al,2
	xor	ah,al
	;imul	  ax,8905h	;fakedemo
	;inc	  ax
	stosw			;do VRAM
	loop	L2
	mov	[cs:seed],ax
;------ korekce spodku (neni to moc nutne, je to hezci)
	mov	bx,word[320*100+144] ;Leva strana
	and	bx,0x7f3f
	mov	word[320*100+144],bx
	mov	bx,word[320*100+174] ;Prava
	and	bx,0x3f7f
	mov	word[320*100+174],bx
	or	bx,0x8080
	mov	word[320*100+159],bx ;Prostredek

;------ zbytek

	mov	cl,100			;100 radku
	mov	di,144			;zacatek radku
L3:	push	cx
	mov	cl,0x20 		;sirka plaminku
					;Zprumerovani млм -> п
L4:	xor	bx,bx
	mov	ax,word[di+319] 	;o jeden radek niz, dva pix
	mov	bl,al			;1
	shr	ax,8			;mov al,ah + xor ah,ah
	add	bx,ax			;2
	mov	al,byte[di+321] 	;o radek niz, posledni pix
	add	bx,ax			;3
	mov	al,byte[di]		;horni pixel
	add	bx,ax			;4
	shr	bx,2			;/4
	or	bx,bx
	jz	dal			;cernej pixel nic
	dec	bx
dal:	mov	byte[di],bl             ;vykresleni
	inc	di
	loop	L4

	add	di,320-0x20
	pop	cx
	loop	L3

	mov	ah,0Bh		;cekani na klavesu
	int	0x21
	or	al,al		;00h-nic,  0ffh-je
	jnz	Konec		;opakuj, kdy neni klavesa
				;syncronizace-pro zpomaleni
SyncW:	mov	dx,0x03DA	;port CGA
	in	al,dx		;sync
	and	al,8		;bit 3=1 -> sync
	or	al,al
	jz	SyncW

	jmp	L1
Konec:
;------ Textovy mod
	mov	ax,0x0003
	int	0x10
;------ Hlaska
	mov	ax,cs
	mov	es,ax
	mov	ds,ax
	mov	ax,0x1301
	mov	cx,20		;delka str
	mov	bx,0x0004	;str,atr-tm.cerv
	mov	dx,0x0010
	lea	bp,[msg00]
	int	0x10
	lea	bp,[msg01]
	mov	bl,0x06 	;hneda
	inc	dh		;radek
	int	0x10
	lea	bp,[msg02]
	mov	bl,0x0E 	;zl.
	inc	dh
	int	0x10
	lea	bp,[msg03]
	inc	bl		;bila
	inc	dh
	int	0x10
	mov	ah,0x02
	mov	dx,0x0600	;pozice kurzoru
	int	0x10
;------ Smaz buffer klavesnice
	xor	ax,ax
	mov	ds,ax
	mov	dx,word[0x041c]
	mov	word[0x041a],dx
;------ Konec programu
	int	0x20		;Konec
;------ Data	------------------------------------------
[section .data]
	msg00	db	'+------------------+'
	msg01	db	'|    Flame v1.4    |'
	msg02	db	'|   Pavel Perina   |'
	msg03	db	'+------------------+'
	EOF	db	'<PP>'
[section .bss]
	seed	resw	1
	pal	resb	768
;----------------------------------------------------------
;VERZE:
;~~~~~~
; 1.0(b,e,f)  puvodni verze
; 1.1         cekani na retrace (zpomaleni),
;             buhvi proc prekreslovani spodniho radku
; 1.2(k)      jina paleta (modra->oranz), kratsi kod
;             (jine instrukce), vymaz VRAM pred spustenim
;             neprekresluje se spodni radek
; 1.3         prepsano do NASM (2?.5.2000)
; 1.4         zprehledneni kodu (2.6.2000)
;
;ALGORITMUS:
;~~~~~~~~~~~
; - inicializace generatoru nahodnych cisel
; - priprav paletu
; - prepni do grafickeho modu
; - nastav paletu
; - nastav es na segment A000 (videopamet)
; - vycisti pamet
; - opakuj:
;   - vygeneruj nekolik nahodnych cisel
;   - (na kraji cisla zmensi)
;   - projed od vrchu cely plamen a pri tom provadej:
;	kdyz bod[x,y]!=0 pak:
;	  bod[x,y]=([x,y]+[x-1,y+1]+[x,y-1]+[x+1,y+1])/4-1
; - kdyz je zmackla klavesa pak ukonci cyklus
; - nastav textovy mod
; - (vypis hlasku)
; - smaz buffer klavesnice ([0:041a]=[0:041c])
; - ukonci program
;
;----------------------------------------------------------
;InSpIrAcE: Flamer (E. Vaughan)

Obsah níže přidán 2024-05-04

Animace

Tvorba v DOSBOXu:

1
2
3
4
5
6
keyb en
mount c c:\dos
c:
cd DEV
\TOOLS\NASM\NASM.EXE FLAME_N.ASM -o FLAME.COM
FLAME.COM

Zachytávání videa přes Ctrl+Alt+F5

Kódování videa

1
C:\apps\ffmpeg.exe -r 60 -i .\flame_000.avi -frames:v 1200 -vf "crop=160:120:80:0, scale=800:600:flags=neighbor" -c:v libsvtav1 asm_flame_av1.webm

Dodatek (2024)

Původní rogram jsem psal určitě ještě na gymnáziu v MASM/TASM, může být tak z roku 1997-99, stejně jako fotka. Jedná se o archeologickou vykopávku. Článek byl napsaný roku 2000 a bydlel na školní stránce.

Funguje v MS-DOSu, asi nejsnažší způsob jak ho spustit je v DOSBOXu. Verze napsaná v C používá DJGPP a knihovnu Allegro, což je dost exotická kombinace se kterou jsem experimentoval velmi krátce na podzim roku 2000, když mi nestačilo 64kB paměti a chtěl jsem něco dělat raytracing v chráněném režimu. Krátce na to jsem Pentium 120MHz vyměnil za Athlona XP 1700+ s Windows XP. Že existovalo DJGPP (port GCC pro DOS) jsem po pětadvaceti letech zapomněl. Nicméně kód je snadněji pochopitelný, než assembler.

Jediná úprava kódu je rámeček kolem jména, v té době o UTF-8 nikdo neslyšel a program používal nejspíš ASCII CP437.

Možná se najde ještě nějaký Tetris nebo Piškvorky v Pascalu.

This post is licensed under CC BY 4.0 by the author.