-
Notifications
You must be signed in to change notification settings - Fork 344
/
concept.txt
2775 lines (1916 loc) · 88.4 KB
/
concept.txt
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<--- -------------------------------------------------------------------- --->
Code Generator
<--- -------------------------------------------------------------------- --->
KONZEPT
einige ideen benötigen einen flexiblen generator für code:
- textur generator
- ausdrücke im editor
eine methode wäre es, den code in c zu schreiben über die shell einen compiler
zu starten und das ergebniss dynamisch zu linken. das hat aber den nachteil,
das die turnaround-zeiten langsam wären. auch ist das einlinken des
generierten codes eventuell schwer. und da der compiler auch mit MMX und ISSE
klarkommen soll ist die auswahl nicht groß, kommerzielle pakete haben dazu
lizensnachteile. eventuell macht auch das zusammenpuzzeln des c-sources
probleme.
alternativ kann ich auch mein eigenes optimierendes backend schreiben. merke:
der code wird hierbei nicht im intro erzeugt, sondern im tool!
ich erwarte dabei nicht zu viel von meinem compiler. es geht um einfache
programme die ausdrücke ausrechnen mit if/else/endif aber ohne schleifen
(sprünge zurück) und unterprogramme.
AUSGABE
ausgabe ist ein maschienenprogramm. diesem muß in register EBX ein zeiger
auf den "workspace" übergeben werden, in dem alle "variablen" des programms
enthalten sind. hier kann man startwerte hineinschreiben und ergebnisswerte
heraushohlen.
da der compiler eventuell optimiert, werden nur die ergebnisswerte garantiert
zurückgeschrieben die als resultat markiert wurden. alle anderen werte bleiben
möglicherweise in registern.
das programm rettet selber alle register.
EINGABE
das programm muß in form einer befehlsliste vorliegen. alle befehle haben
das format:
[31...................................0]
[76543210][76543210][76543210][76543210]
operator dest src-a src-b
dest, src-a und src-b sind wortoffsets (wort=32 bit) in den workspace. die
möglichen operatoren enthalten rechenbefehle, if/else/endif, das garantierte
zurückschreiben eines resultats, lesen und schreiben von speicher über zeiger,
array-adressierung innerhalb des workspace und immediate-ladebefehle.
die immediate ladebefehle sind eine ausnahme vom befelsformat weil die
immediate daten dirent hinter dem befehl folgen.
die aritmetisch/logischen befehle unterstützen die datentypen int, float,
4x16 bit mmx und 4x32 bit floats. achtung, bei den vektor-typen wird mehr als
ein wort im workspace verbraucht!
einige beispiel-befehle
; input/output
.def.i x
.def.i y
.def.i bitmap
.def.v color
; internal
.def.i index
.def.i ptr
.def.i t0
.def.i t1
.def.v pink
; convert pink pixels to alpha
in.i x ; mark as input
in.i y ; (generates no code)
in.i bitmap ; (just for type-checking)
imm.i t0,256 ; xsize = 256
mul.i t0,t0,y ; adr = x+y*256
add.i index,t0,x
imm.i t1,16 ; one vector is 16 bytes
mul.i t0,index,t1
add.i t0,t0,bitmap ; add pointer to scaled index
load.v color,t0 ; load vector
imm.v pink,1.0:1.0:0.0:1.0 ; this is ARGB pink with alpha 1
ifeq.v pink,color ; if pink
imm.v color,0:0:0:0 ; then make black with alpha 0
store.v color,t0
endif
out.v color ; write back result
end
TYPEN
die typen sind:
name typ register suffix
-------------------------------------------------------------
int sS32 EAX/ECX/EDX/ESI/EDI/EBP .i
float sF32 st0 .. st7 .f
mmx sS16[4] mmx0 .. mmx7 .x
vector sF32[4] st0 .. st7 (fpu) .v
xmm1 .. xmm7 (isse)
mmx0/mmx1 .. mmx6/mmx7 (3dnow)
sie benötigen 1, 2 oder 4 indizes platz auf dem workspace (4, 8 oder 16
bytes).
da sich mmx und float nicht vertragen kann muß man sich zwischen zwei typen-
sets entscheiden:
- int mmx
- int float vector
REGISTERVERTEILUNG
die integer-register sind wie folgt verwendet:
- EAX: frei
- EBX: workspace ptr
- ECX: frei, vermeiden wegen shift
- EDX: frei
- ESI: frei
- EDI: frei
- EBP: frei
- ESI: return stack, eventuell frei :-)
COMPILER PHASE 1: konvertieren
die assemblerbefehle werden in ein aufgeblähtes format konvertiert bei dem
immediate-werte durch indizes ersetzt werden und platz für interne
kommentare geschaffen wird. jetzt sind alle befehle gleich lang.
COMPILER PHASE 2: isse/3dnow emulation
wenn die cpu keine isse oder 3dnow befehle kann, dann müssen alle vector
operatoren in 4 float operatoren umgewandelt werden. dieser vorgang ist
sehr mechanisch und einfach.
COMPILER PHASE 3: immediate verteilen
die immediate operanden werden nicht mehr in register gespeichert sondern
direkt dort eingetragen, wo sie gebraucht werden.
COMPILER PHASE 4: gleiche befehle in if und else zweig
wird ein if mit else zweig gefunden, so kann man eventuell gleiche
befehle aus diesen zweigen herausziehen und darunter oder darüber
schreiben. der algorithmus wird zuerst auf innere if's und dann auf
äußere angewendet. dazu werden zuerst alle if's mit else erkannt und
nach verschachtelung sortiert.
dann wird für jeden befehl im if zweig ein binär gleicher im else
zweig gesucht. dann wird mit der unten beschriebenen methode geprüft
ob die befehle beide nach oben oder beide nach unten aus dem if
verschoben werden können. wenn ja, so wird das getan.
ist der else zweig jetzt leer so wird er entfernt. ist der if zweig
leer so wird die bedingung umgekehrt und der else zweig zum if zweig.
sind beide zweige leer so werden if, else und endif entfernt.
COMPILER PHASE 5: move befehle
prinzipiell kann man bei einem move befehl einfach alle nachfolgenden
verwendungen von dest durch src-a ersetzen bis dest erneut beschrieben wird.
nur wenn der move befehl in einem if/else zweig steht und nach dem endif dest
noch gelesen wird und nicht vorher neu geschrieben wurde geht das nicht.
COMPILER PHASE 6: dest und src sortieren
einige befehle sollten nach oben geschoben werden, damit besserer code
generiert wird.
- befehle die dest als src benutzen
von dem befehl an wird nach unten gescannt. dazu wird für jede variable
ein "verwendet" flag gebraucht, am anfang sind alle flags gelöscht. es
wird auch ein if-count gebraucht der mit 0 initialisiert wird.
ein befehl kann nach oben geschoben werden wenn
- der if-count null ist und
- dest, src-a und src-b unbenutzt sind
- der befehl wird nicht geschoben wenn er ein load oder store befehl
ist und ein volatile operator gefunden wurde.
wird der befehl nicht nach oben geschoben so wird:
- dest, src-a und src-b werden als verwendet gekennzeichnet.
- ist der befehl ein IF so wird der if-count erhöht
- ist der befehl ein ENDIF so wird der if-count erniedrigt.
- geht der if-count unter null so wird abgebrochen
auch über if/else/endif werden die befehle hinweg geschoben, aber nicht aus
if/else/endif hinaus.
COMPILER PHASE 7: operator peephole
der code wird auf bestimmte sequenzen untersucht die durch einfachere
sequenzen ersetzt werden können. dabei wird versucht, die befehle zu
verschieben. dabei handelt es sich meistens um spezielle interne opcodes für
assembler-tricks. ein beispiel:
imm.i t0,4
mul.i t0,t0,i
add.i t0,t0,p
load.i v,t0
wird zu
load4.i v,i,p
und das ist
mov eax,[eax+ecx*4]
COMPILER PHASE 8: letze verwendung einer variablen
ab jetzt wird die reihenfolge nicht mehr verändert.
von unten nach oben wird überprüft, ob dest-ergebnisse noch gebraucht werden.
wenn nicht, wird der befehl gelöscht.
von oben nach unten: für src-a und src-b jedes operators wird markiert, ob die
gelesene variable jemals wieder benötigt wird. eine variable wird nicht mehr
benötigt wenn sie nie wieder als src gebraucht wird oder ihre nächste
verwendung als dest ist.
der register scheduler kann also das register das die variable enthält sofort
wieder verwenden, eventuell sogar für das ergebniss.
COMPILER PHASE 9a: register verteilen (integer)
da es keine inner-loops gibt die bevorzugt behandelt werden müssen können die
register einfach von oben nach unten verteit werden. dabei werden einfach
immer register benutzt, und wenn keine mehr frei sind wird LRU eines
geflushed.
für jede variable wird der typ und zustand gespeichert:
- unbenutzt
- gespeichert
- register (n)
- konstante (n)
- illegal (weil dies der hintere teil eines vektors ist)
zusätzlich wird für jedes register gespeichert, welche variable es gerade
enthält (oder ob es frei ist).
für jeden befehl können verschiede anforderungen daran gestellt werden,
wie src-a und src-b addressiert werden können. dazu können mehrere varianten
für gültige kombinationen gespeichert werden die dann bestimmten befehlsfolgen
entsprechen. die "billigste" wird dabei zuerst genannt.
die anforderungen für eine src-a und src-b sind jeweils einer von:
- kein src
- irgendein register des richtigen typs
- ein spezielles register (für DIV, shift)
- das selbe register wie dest
- immediate
- offset[EBX]
die anforderungen für dest sind:
- kein dest
- irgendein register des richtigen tpys
- ein spezielles register (für DIV)
- offset[EBX] (für FIST, FILD)
dazu kommt die möglichkeit:
- src-a und src-b sind vertauschbar (für add, mul)
- beim ersten pass nicht verwenden, lieber ein register opfern.
für alle kombinationen wird ausgerechnet, wie viele move-befehle gebraucht
werden um es benutzen zu können. die billigste variante wird genommen.
wird eine kommutative vertauschung vorgenommen, so wird das dem codegenerator
mitgeteilt. der kann dann FSUBR anstelle von FSUB benutzen, z.B..
[...] so genau wie fpu, bitte!
nun geht es los
nachdem alle register als frei und alle variablen als unbenutzt deklariert
wurden wird pro operator folgende sequenz abgearbeitet:
- fehler ausgeben wenn input unbenutzt, illegal oder vom falschen typ ist.
- für out-befehle: wenn die variable bereits gespeichert ist, so kann
(und muß) dieser befehl gestrichen werden.
- ein muster aussuchen
- merken und mit welcher adressierungsart die src-a, src-b und dest
arbeiten.
- die nötigen move-befehle einschieben. auch für diese addressierungsarten
merken.
jetzt sind alle informationen vorhanden um maschienencode auszugeben.
COMPILER PHASE 9b: register verteilen (fpu-stack)
leider haben wir mit dem 80386 ganz groß in die scheiße gegriffen...
wir notieren den stack umgekehrt. bei stack-ptr = 3:
intel compiler
st(3) f0
st(2) f1
st(1) f2
st f3
-- f4
-- f5
-- f6
-- f7
jetzt können wir jeder variable speichern in welchem register sie ist, und für
jedes register sagen welche variable es tragt ohne ständig alles zu ändern.
es gibt nun folgende befehlesmuster:
- binär (FADD, FSUB)
- unär (FSIN, FCHS)
- FCMP
- mov.f
mit FLD, FST und FXCH mus der stack so umsortiert werden, daß das
befehlsmuster passt. fpu-befehle können nicht direkt mit offset[EBX] und
immediate umgehen, so das alle werte auf den stack müssen.
dazu muß der stack im schlimmsten falle erst einmal aufgeräumt werden. es kann
sein das sich auf dem stack noch variablen befinden, die gar nicht mehr
gebrauch werden (leichen). in diesem falle hilft ein FXCH. ansonsten muß mit
FXCH FSTP platz gemacht werden, das ist ein FXCH zu viel, den man hätte den
wert ja auch gleich wegstoren können, aber den stack umzusortieren ist echt
arbeit...
das laden von immediate muß wie das laden von offset[EBX] mit FLD
implementiert werden.
binäre operatoren haben folgende sinvolle möglichkeiten:
beste:
parameter (*=discard) code
st(n) st(m) FLD st(n) FSUB st,st(m)
st(n) *st(m) FLD st(n) FSUBRP st(m),st
*st(n) st(m) FLD st(m) FSUBP st(n),st
*st(n) *st(m) FXCH st(n) FSUBRP st(m),st
st(0) st(m) FLD st(0) FSUB st,st(m)
st(0) *st(m) FSUBR st(m),st
*st(0) st(m) FSUB st,st(m)
*st(0) *st(m) FSUBRP st(m),st
st(n) st(0) FLD st(0) FSUB st,st(m)
st(n) *st(0) FSUBR st,st(m)
*st(n) st(0) FSUB st(m),st
*st(n) *st(0) FSUBRP st(m),st
compare-befehle (FCOM) erzeugen kein ergebniss ausser gesetzten flags.
deshalb gelten andere regeln. FCOMR bedeutet in diesem zusammenhang das
die flags anders-herum interpretiert werden:
parameter (*=discard) code
st(n) st(m) FXCH st(n) FCOM st(m)
st(n) *st(m) FXCH st(n) FCOMP st(m)
*st(n) st(m) FXCH st(m) FCOMRP st(n)
*st(n) *st(m) FXCH st(n) FCOMP st(m) (leiche!)
st(0) st(m) FCOM st(m)
st(0) *st(m) FXCH st(m) FCOMRP st(m)
*st(0) st(m) FCOMP st(m)
*st(0) *st(m) FCOMPP st(m)
st(n) st(0) FCOMR st(n)
st(n) *st(0) FCOMRP st(n)
*st(n) st(0) FXCH st(n) FCOMP st(n)
*st(n) *st(0) FCOMRPP st(m)
unäre befehle sind glücklicherweise einfacher:
parameter (*=discard) code
st(n) - FLD st(n) FSIN
st(0) - FLD st(0) FSIN
*st(n) - FXCH st(n) FSIN
*st(0) - FSIN
die sonstigen befehle:
operator parameter code
immediate - - FLD mem
load - - FLD mem
store st(n) - FXCH st(n) FST mem
st(0) - FST mem
*st(n) - FXCH st(n) FSTP mem
*st(0) - FSTP mem
itof - - FILD mem
ftoi st(n) - FXCH st(n) FIST mem
st(0) - FIST mem
*st(n) - FXCH st(n) FISTP mem
*st(0) - FISTP mem
load und store dienen sowohl als implementation für eben load und store, als
auch zum laden und speichern von variablen.
COMPILER PHASE 10: register peephole
eigentlich steht der assemblercode jetzt schon fest. aber vieleicht kann
man ja doch noch etwas machen.
die folgende sequenz kann optimiert werden
op ecx,???
op eax,???
op ecx,???
op ???,ecx
mov ecx,eax
der move ist überflüssig wenn alle verwendungen von src-a und src-b davor bis
zum schreiben keine speziellen registereinschränkungen haben:
op ecx,???
op ecx,???
op eax,???
op ???,eax
COMPILER PHASE 11: code ausgabe
eigentlich muß jetzt nur noch für jeden befehl unter berücksichtigung der
addressierungsarten ein stück code ausgegeben werden. für ein-operant
befehle sind das 3 varianten
- offset[EBX]
- immediate
- register
für zwei-operanten sind das prinzipiell die permutationen. aber da immer
ein register beteidigt sein muß reichen 5 varianten:
- offset[EBX] , register
- immediate , register
- register , offset[EBX]
- register , immediate
- register , register
kommutative operatoren bekommen das register immer als src-a. eventuell
[...]
BEFEHLSSATZ ALLE
in.? d ; mark as input
out.? a ; mark as output and write back
imm.? d,imm ; load immediate data
mov.? d,a ; d = a
load.? d,a ; d = *a
store.? a,b ; *a = b
BEFEHLSSATZ INTEGER
add.i d,a,b ; d = a + b
sub.i d,a,b ; d = a - b
mul.i d,a,b ; d = a * b
div.i d,a,b ; d = a / b
mod.i d,a,b ; d = a % b
and.i d,a,b ; d = a & b
or.i d,a,b ; d = a | b
shr.i d,a,b ; d = a >> b
shl.i d,a,b ; d = a << b
neg.i d,a ; d = -a
not.i d,a ; d = ~a
abs.i d,a ; d = |a|
addr.i d,a ; d = &a
ifnot.i a ; if(a!=0)
if.i a ; if(a==0)
ifeq.i a,b ; if(a==b)
ifne.i a,b ; if(a!=b)
ifgt.i a,b ; if(a>b)
ifge.i a,b ; if(a>=b)
iflt.i a,b ; if(a<b) (macro for ifgt b,a)
ifle.i a,b ; if(a<=b) (macro for ifge b,a)
BEFEHLSSATZ FLOAT
add.f d,a,b ; d = a + b
sub.f d,a,b ; d = a - b
mul.f d,a,b ; d = a * b
div.f d,a,b ; d = a / b
neg.f d,a ; d = -a
abs.f d,a ; d = |a|
sin.f d,a ; d = sin(a)
cos.f d,a ; d = cos(a)
atan.f d,a ; d = atan(a)
sqrt.f d,a ; d = sqrt(a)
pow.f d,a ; d = pow(a)
atan2.f d,a,b ; d = atan2(a,b)
ifeq.f a,b ; if(a==b)
ifne.f a,b ; if(a!=b)
ifgt.f a,b ; if(a>b)
ifge.f a,b ; if(a>=b)
iflt.f a,b ; if(a<b) (macro for ifgt b,a)
ifle.f a,b ; if(a<=b) (macro for ifge b,a)
BEFEHLSSATZ COLOR
add.c d,a,b ; d = a + b (clamped)
sub.c d,a,b ; d = a - b (clamped)
mul.c d,a,b ; d = a * b (element wise)
max.c d,a,b ; d = max(a,b)
min.c d,a,b ; d = min(a,b)
BEFEHLSSATZ VECTOR
add.v d,a,b ; d = a + b
sub.v d,a,b ; d = a - b
mul.v d,a,b ; d = a * b (element wise)
dot.v d,a,b ; dotproduct (result is float!)
cross.v d,a,b ; crossproduct
scale.v d,a,b ; d = a * b (b is scalar!)
abs.v d,a ; absolute value (result is float!)
unit.v d,a ; unit vector
BEFEHLSSATZ GEMISCHTES
volatile ; don't move "load" or "store" over this
else ; after if
endif ; after if or else
end ; end of program
ftoi d,a ; (a -> d) float to integer
itof d,a ; (a -> d) integer to float
ctov d,a ; (a -> d) color to vector (not implemented)
vtoc d,a ; (a -> d) vector to color (not implemented)
setx d,a ; d.x = a
sety d,a ; d.y = a
setz d,a ; d.z = a
setw d,a ; d.w = a
getx d,a ; d = a.x
gety d,a ; d = a.y
getz d,a ; d = a.z
getw d,a ; d = a.w
setr d,a ; d.r = a
setg d,a ; d.g = a
setb d,a ; d.b = a
seta d,a ; d.a = a
getr d,a ; d = a.r
getg d,a ; d = a.g
getb d,a ; d = a.b
geta d,a ; d = a.a
ENDE. VIEL SPASS BEIM IMPLEMENTIEREN !!!
<--- -------------------------------------------------------------------- --->
Texture Generator III
<--- -------------------------------------------------------------------- --->
IDEE
Wir haben eine Baumstruktur mit texturoperatoren. es gibt nun zwei typen
von operatoren: SINGLE operatoren brauchen keine nachbarpixel und
PAGE operatoren brauchen alle pixel des bildes als eingabe. wir wollen
die operatoren so umordnen, das die singles auf tiles arbeiten,
und nur für die page wird die gesammte textur gespeichert.
Beispiel für eingabe: (s = single, f = page, a = add, d=done)
s0 s3
f1 f4
s2 s5
aaaaa6
s7
f8
s9
dd
die ausgabe besteht aus einer zeile für jeden page operator mit den singles
die für den page aufgerufen werden. >n speichert die volle textur für
weitergehende verwendung, und <n holt sich einen tile aus der gespeicherten
textur.
s0 : f1 >1
s3 : f4 >0
<0 s2 <1 s5 a6 s7 : f8 >2
<2 s9 : dd
weiteres beispiel:
s0 s1 s2 s3 s0 : f0 >0
f0 f1 f2 f3 s1 : f1 >1
aaaaa0 aaaaa1 s2 : f2 >2
aaaaaa2 s3 : f3 >3
s4 <0 <1 a0 <2 <3 a1 a2 s4 : dd
dd
TRANSFORMATION:
die transformation funktioniert so:
es wird ein page-op gesucht der keine weiteren page-op als child hat. der sub-baum
des page-op wird ausgegen und mit dem page-op und einem buffer ": fn >m"
abgeschlossen. dann wird der page-op und seine childs durch "<m" ersetzt.
das wird solange getan, bis außer der wurzel keine page-ops mehr
vorhanden sind.
beim rückweg aus der baum-rekursion ist der erste gefundene page-op der gesuchte
operator.
DER DONE OPERATOR
Letztendlich muß die textur in das zeilformat konvertiert und in die
grafikkarte geschoben werden. ein 1024² textur ist 8MB groß, aber eventuell
wird sie nur als 16 bit textur gebraucht und es gibt gar keinen page-op,
die textur muß also niemals vollständig gespeichert werden. also
bietet es sich an, das der DONE operator die tiles gleich konvertiert und in
einen Ziel-Texturbuffer konvertiert. der wird dann übertragen und
gemip-mapped.
BUMP
der bump operator braucht einen page-op-input und einen tile-input. wird er
als erster page-op gefunden, so wird sein page-child abgeschnitten und mit
einem nop-page-op versehen, so das der einzige effekt der ">n" ist. der
bump op selber behält den anderen input und wird in einen tile-op umgewandelt.
ungewöhnlich an diesem tile-op ist das er die nummer des ">n" mitgeteilt
bekommt um so direkt auf die page zuzugreifen.
<--- -------------------------------------------------------------------- --->
Anti Aliased Zoomable Gui
<--- -------------------------------------------------------------------- --->
IDEE
Die Gui der Zukunft muß Auflösungsunabhängig arbeiten. Je höher die Auflösung,
desto schärfer werden die Bildelemente abgebildet, aber Alle Linien, Kanten,
Flächen und Fonts sind in pixelunabhängig Skalierbar. Dadurch lassen sich (a)
Fensterinhalte beliebig zoomen und (b) interessante Animationseffekte
implementieren.
Im Schnitt lassen sich dadurch alle GUI-ELemente einen halben Pixel kleiner
zeichnen: Wenn z.b. 16 Pixel zu klein ist und deshalb auf 17 Pixel ausgewichen
wird, so können bei eine frei skalierbarer GUI 16.5 Pixel benutzt werden.
Auch kann man schnell zwischen Übersichtsansicht und Arbeitsansicht wechseln.
Gui-Elemente können sich dazu so konfigurieren, das bei bestimmten Auflösungen
Details wegfallen.
Schrift und dünne Linien haben nun eine spezielle Auflösung in der sie am
besten zu lesen sind. Sollte sich erweisen das man darauf rücksicht nehmen
muß so kann man Nach dem umzoomen der Oberfläche "scharf" rendern, in dem die
Koordinaten langsam in richtung ganze Zahlen gezogen werden.
Man kann auch die ganze Oberfläche echt in 3d gestalten, und dann Schatten
"in echt" hinein rechnen. Gerendert wird das ganze Orthogonal von oben, aber
wer will kann das teil auch drehen...
DATENSTRUKTUR
Das, was gezeichnet werden muß, besteht aus:
- Rechtecken
- Schrift
- Horizontalen und Vertikalen Linien
- Spline-Kurven
- Design-Elementen
- Bitmaps
Spline-Kurven werden für einen Spline-Editor benötigt. Da es nur
horizontale und vertikale Linien gibt können Splines nicht aus primitives
zusammengesetzt werden. Die Verwendung von Bitmaps ist unbedingt zu vermeiden,
weil sie naturgemäß nicht in eine frei Skalierbare Welt passen. Trotzdem wären
Buttons mit abgerundeten Ecken und Schatten nicht schlecht, dafür muß also
eine eigene Darstellungsart gefunden werden, eventuell Spline basiert.
Der Bildschirminhalt wird durch eine Draw-List dargestellt. Jedes Draw-Element
definiert eine Liste von Primitives, Die äußere Bounding-Box die sagt was
überschrieben werden könnte und die innere Bounding-Box die sagt was
garantiert überschrieben wird. Der Gui-Code muß nur die sich jeweils
ändernden Elemente neu definieren. Es wird allerdings jeden Frame alles
gezeichnet was nicht komplett verdeckt ist. Eventuell werden die Draw-Elements
noch optimiert.
<--- -------------------------------------------------------------------- --->
Mesh Generator
<--- -------------------------------------------------------------------- --->
IDEE
Die Operatoren werden in einen index und einen vertex teil zerlegt. in der
precalc phase werden die indexlisten erstellt und für jede Vertex wird ein
programm gebaut. In der Runtime phase werden die vertex-programme
ausgeführt.
Zum Beispiel:
- Torus
- Select Random
- Extrude
- Normalise
- Subdivide
In der Precalc-Phase
- Torus Vertices und Faces erstellen
- Select Random auf Faces
- Extrude erzeugt neue Faces und vertices
für jede neue vertex wird gespeichert, wie die koordinate aus den alten
berechnet werden soll
- Subdivide erzeugt neue Faces und vertices
für jede neue vertex wird gespeichert, wie die koordinate aus den alten
berechnet werden soll
- Normalize erzeugt informationen wie eine face-normal-liste erstellt werden
soll und trägt in die vertices ein wie aus der face-normal-liste vertex
normalen erzeugt werden sollen.
- aus den faces werden indexlisten erstellt und optimiert.
In der Runtime Phase
- für jede gespeicherten listen werden abgearbeitet
DER RUNTIME INTERPRETER
es handelt sich nicht um vertex-programme im herkömmlichen sinne da für jede
vertex informationen über die nachbarvertices verwendet werden können.
deswegen macht es wenig sinn, den loop pro vertex abzuarbeiten. Der
algorithmus "frisst" sich durch ein array von floats das linear geschrieben
wird. dabei ist es egal ob die floats nun position, normale, farbe oder
texturkoordinaten enthalten, egal ob es 2, 3 oder 4-stellige vectoren sind,
egal ob diese als 32 bit float oder 32 / 16 / 8 bit integer gespeichert
werden. die zuletzt geschriebenen daten werden in den vertex-buffer kopiert.
die kommandos sind
00 : END
01 : COPYVB ab jetzt gibt OUT in den vertex-buffer aus!
02 : COLOR store value directly to stream, or with 0xff000000
03 : EMIT store value directly to stream, shift with <<8
10 : UNIT Normalize Vector
11 : ROT Rotate Vector with Parameter-Matrix
12 : SCALE Scale Vector with Parameter-Scalar
13 : ADD Add Vector with Parameter-Vector
20 : LCX Load constant to x
21 : LCY Load constant to y
22 : LCZ Load constant to z
23 : LCW Load constant to w
24 : LC Load same constant to xyzw
25 : LCS Load constant to scale
26 : LOOP führe die befehle entsprechend oft aus
27 : REWIND rewind the output stream
30 : READS read scale per vertex from program data
31 : READO read offset per vertex from program data
8x : LD Load Vector from offset
9x : LDS Load and Scale Vector from offset
ax : LA Add Vector from offset
bx : LAS Add and Scale from offset
cx : STORE Store Vector at offset
dx : OUT Store vector at stream
jedes Kommando kann einen typ haben
00 : .b unsigned 8 bit int
01 : .s signed 16 bit int
02 : .i signed 32 bit int
03 : .f 32 bit float
und der vektor hat eine länge von 1 bis 4.
Das Befehlsformat ist also:
0ccccccc vvvvvvvv vvvvvvvv vvvvvvvv -> simple format
1cccttos 00000000 00000000 000000ll -> special format
t = typ
l = vector size
c = command
v = offset, count or 24 upper bits of a float value
s = read new scale factor
o = read new offset
parallel zum programm wird ein datenstrom generiert der offsets in den float
buffer und skalierungswerte enthält. mit dem 'o' bit wird der offset-zeiger
neu gesetzt, dazu wird ein langwort gelesen und als zeiger interpretiert.
's' lädt den scale-wert mit einem float. das ist zwar verschwendung von
bandbreite, aber ich will auch keine integer->float conversion im inner-loop!
wenn beides angegeben ist wird zuerst der offset geladen.
Die Implementation sieht so aus:
sU32 *sCreateVectorProgram(sU32 *Program,sU32 *Para);
void sExecuteVectorProgram(
sU32 *PrgHandle, // handle returned by sCreateVectorProgram()
sU32 *PrgData, // scale and offset
sF32 *source, // source data to be copied into work area
sInt sourcesize, // anzahl floats
sF32 *dest, // zeiger auf den zu füllenden vertex buffer
sF32 *para // animation parameters, like matrices etc.
);
BEISPIELE
das programm zum normalisieren könnte so aussehen:
LDS.f3os ; face normals for faces with 4 vertices
LAS.f3os
LAS.f3os
LAS.f3os
OUT.f3
LOOP 42
LDS.f3os ; face normals for faces with 3 vertices
LAS.f3os
LAS.f3os
OUT.f3
LOOP 42
LDS.f3os ; calc normals for vertices with 4 neighbours
LAS.f3os
LAS.f3os
LAS.f3os
UNIT
STORE.f3o
LOOP 42
LDS.f3os ; calc normals for vertices with 3 neighbours
LAS.f3os
LAS.f3os
UNIT
STORE.f3o
LOOP 42
REWIND 42*2*3 ; throw away face normals
END
extrude srt by normal, 3 times :
LD.f3o ; load normal
SCALE 48 ; scale
LA.f3o ; add position
ROT 0 ; srt
STORE.f3
LOOP 42 ; 1st level
LD.f3o ; load normal
SCALE 49 ; scale
LA.f3o ; add position
ROT 16 ; srt
STORE.f3
LOOP 42 ; 2nd level
LD.f3o ; load normal
SCALE 50 ; scale
LA.f3o ; add position
ROT 32 ; srt
STORE.f3
LOOP 42 ; 3rd level
END
<--- -------------------------------------------------------------------- --->
Flat Gui
<--- -------------------------------------------------------------------- --->
TECHNISCHE UMSETZUNG
Die Applikation erzeugt eine Liste von Rechtecken. Jedes Rechteck enthält
- Position
- Scrolling
- Flags
- User-Ptr
- User-Code
- User-Local
- OnPaint-Code
- OnDrag-Code
- OnKey-Code
Es gibt keine Hirarchische Window-Struktur. Die Rechtecke sind in "z-order"
angeordnet, aber sie müssen nicht verschachtelt sein. Für OnPaint() wird
die liste von oben nach unten abgearbeitet, für OnDrag() oder OnKey() von
unten nach oben. Um z.B. eine Dialogbox zu erzeugen muß man diese nur
"hinten" an die liste heranhängen, sie wird zuletzt gezeichnet und bekommt
user-input als erstes.
Das erste rect is das "app level rect". es kann jeden focus bekommen und
muß OnLayout() und OnCmd() verarbeiten.
Wenn sich die position des rechteckes ändert oder das HIDE flag, dann muß
die rechteck-liste nicht neu aufgebaut werden. wenn aber das layout
grundlegender geändert wird, zum beispiel weil ein popup-menü dazukommt,
dann muß die rechteck-liste komplett neu gesetzt werden.
Wird mit einer Maustaste in den bildschirm geklickt, so wird der "Focus Point"
gesetzt. die liste der rechtecke wird rückwärts abgearbeitet, bis ein rechteck
mit BLOCKFOCUS getroffen wird. alle getroffenen rechtecke mit GETSECFOCUS
bekommen einen sekundären focus, was soviel bedeutet wie "ein child window
hat den focus". damit kann man zum beispiel die rahmenfarbe ändern oder
so. das erste getroffene fenster (von hinten) mit GETKEYFOCUS kann den
(exklusiven) OnKey() focus bekommen, das selbe gilt für GETDRAGFOCUS und
OnDrag().