kategoria: ANSI C
[#1] Debugowanie ACE'a
Jak niektórzy z Was wiedzą, od 4 lat (jak to szybko leci!) rozwijam coś, co powstało na potrzeby CastleHacka o nazwie Amiga C Engine. Rozwój był burzliwy - najpeirw w oparciu o funkcje systemowe, a teraz z prawie kompletnym jeżdżeniem bezpośrednio po chipsecie.

Mam jednak parę problemów w kodzie, których rozwiązać albo nie umiem, albo po prostu poświęciłem na to zbyt mało czasu. Pomyślałem sobie, że jako że tu jest paru programistów, to może ktoś by mi chciał niezobowiązująco pomóc. Zwłaszcza jeśli pokażę palcem gdzie jest coś zepsute. ;)

Zacznijmy lekko, od loggingu czasu wykonywania, bo to w sumie ostatnio modny temat w obliczu Jubimarków ;). Są sobie funkcje logAvgCreate() i logAvgDestroy(), które odpowiednio tworzą strukturę do pomiaru minimalnego, średniego i maksymalnego czasu wykonywania rzeczy - z uwzględnieniem nazwy tego, co mierzymy i liczbą próbek, z której ma być liczona średnia. Potem w pętli gry robię sobie logAvgBegin() i logAvgEnd() w argumencie podając powyższą strukturę - funkcje te zbierają czas początkowy, czas końcowy i zapisują deltę w strukturze jako jedną z próbek. Wszystko fajnie i pięknie, ale czasy, zwłaszcza skrajne, wychodzą mi dziwne.

Winny jest prawdopodobnie timer.c a konkretnie funkcja timerGetPrec() lub późniejsze działające na jej wyniku, bo z nich korzystają funkcje logAvg. Tyle że gapię się na hardware reference, gapię się na tę funkcję i problemu nie widzę. Definicja vhPosRegs tutaj

Przykładowe działanie logu średniej:
Avg undraw explosions:   1.690 ms, min: >7min, max:   0.0 us
	Avg undraw projectiles:   1.700 ms, min: >7min, max:   0.0 us
	Avg undraw vehicles:   1.710 ms, min: >7min, max:   0.0 us
	Avg draw vehicles:  12.094 ms, min:  10.557 ms, max:  13.411 ms
	Avg draw projectiles:   2.156 ms, min:   1.536 ms, max:   3.403 ms
	Avg draw explosions:  38.4 us, min:   0.0 us, max: >7min

O ile w avg jestem w stanie uwierzyć, to min/max jest czasem totalnie z czapy. Zastrzegam że w kodzie timer.c jest dużo skamielin i trzeba tam trochę posprzątać, ale to po RKLE. ;)
[#2] Re: Debugowanie ACE'a

@teh_KaiN, post #1

Wiem że trochę tak od de strony zapytam, ale czy nie prościej było skorzystać z liczników w układach CIA?
[#3] Re: Debugowanie ACE'a

@teh_KaiN, post #1

Może częściej wołasz _logAvgEnd niż _logAvgBegin (np. w jakieś pętli) ?
Przy obecnej konstrukcji możesz mierzyć jedynie pojedyncze przebiegi.
Ciekawy jest przypadek z min, chyba gdzieś następuje rzutowanie na liczbę ze znakiem?

Ostatnia aktualizacja: 18.09.2017 07:56:46 przez makarsky
[#4] Re: Debugowanie ACE'a

@Krashan, post #2

@Krashan: to chyba bym je musiał rezerwować w OSie, gadać z nimi przy pomocy timer.device, ... A to trochę jednak jest roboty.

@Makarsky: nie no, pilnuję się by funkcje te obejmowały kod, więc wołam je tyle samo razy. W tej chwili w kodzie nie ma zagnieżdżonych logAvgBegin()/logAvgEnd() i raczej zagnieżdżeń nie planuję. Kolejne średnie są mierzone po sobie w pętli głównej kodu:

logAvgBegin(a);
// coś tam
logAvgEnd(a);

logAvgBegin(b);
// coś tam
logAvgEnd(b);


Ostatnia aktualizacja: 18.09.2017 07:56:44 przez teh_KaiN
[#5] Re: Debugowanie ACE'a

@teh_KaiN, post #4

Być może problem siedzi w timerGetPrec i ma związek z wykryciem zmiany ramki.
Zupełnie jakby zmiana ramki nie była czasem jednoznaczna ze zmianą pozycji.
Wówczas czas rozpoczęcia mógłby być większy od czasu zakończenia.
[#6] Re: Debugowanie ACE'a

@teh_KaiN, post #4

to chyba bym je musiał rezerwować w OSie, gadać z nimi przy pomocy timer.device
Nie, nie musiałbyś. Tzn. rezerwować tylko wtedy o ile nie wyłączasz systemu, poprzez ciaa.resource i ciab.resource. A gadać można wtedy już po rejestrach.
[#7] Re: Debugowanie ACE'a

@teh_KaiN, post #4

to chyba bym je musiał rezerwować w OSie, gadać z nimi przy pomocy timer.device, ... A to trochę jednak jest roboty.


No wez, skorzystanie z licznika to wywolanie jednej jedynej funkcji (ReadEClock) z timer.device:
http://amigadev.elowar.com/read/ADCD_2.1/Includes_and_Autodocs_2._guide/node04FB.html

Jeszcze dwa komentarze do kodu :) Globalna struktura g_sTimerManager nie jest zadeklarowana jako volatile. Czy chociaz pole uwFrameCounter jest volatile? Aktualizacja tego pola w przerwaniu jest dla kompilatora niewidoczna i w funkcji timerGetPrec moze ten kawalek kodu:
uwFr1 = g_sTimerManager.uwFrameCounter;
	*pRay1 = *pReg;
	uwFr2 = g_sTimerManager.uwFrameCounter;

zoptymalizowac do
uwFr1 = g_sTimerManager.uwFrameCounter;
	*pRay1 = *pReg;
	uwFr2 = uwFr1;


Druga sprawa funkcja timerGet(). Jezeli nie zwrocisz uwagi na nazwe pola (chwala notacji wegierskiej ) a tylko na naglowek to sam siebie oszukasz:

* Max time capacity: 33 months
 */
ULONG timerGet(void) {
	return g_sTimerManager.uwFrameCounter;
}


Popraw w naglowku na "Max time capacity: ca. 21 minutes", bo numer ramki trzymasz w typie UWORD :)

Ostatnia aktualizacja: 18.09.2017 09:06:10 przez mschulz
[#8] Re: Debugowanie ACE'a

@mschulz, post #7

Słowem wstępu: załóżmy, że mówimy o OCS i 000@7MHz.

Dzięki za feedback! Bonusowe punkty dla Was za funkcję ReadEClock() bo chyba wcześniej uciekła ona mojej uwadze. Minusy jej widzę dwa: jeden taki, że idą na to ze dwa cykle CIA a na nich samych cpu spali po 10 cykli, bo musi zwolnić do prędkości 700kHz, a drugi taki, że maksymalna precyzja jaką można wycisnąć wydaje się być dużo mniejsza. No i na siłę trzeci - timestamp z ReadEClock() waży 8 bajtów a rejestry pozycji tylko 4. ;) Jak nie uda się obecnej implementacji zdebugować, to może faktycznie się na to przerzucę, bo jednak wolę mniej dokładny lecz pewny wynik, niż superdokładny losowy.

@mschulz Dokopałeś się do skamielin jeszcze sięgających 2013. Funkcje timerów na pojedynczych klatkach już dawno nie są używane, bo nie ma takiej potrzeby. Wróci na nie zapotrzebowanie, to się je odświeży. Wcześniej do tego celu korzystaliśmy z Dice'owej funkcji clock() i ona zwracała ULONG, stąd wszystkie funkcje timerów klatkowych przyjmują i zwracają ULONGi. VBCC jej nie implementuje, więc przeszedłem na własny licznik klatek, a w sumie nie potrzebne nikomu jest mierzenie tak długiego czasu więc skróciłem do WORDa. Bo chyba to jest najszybsza długość inta (przekazywanie + liczenie) na 000?

Volatile na liczniku klatek niestety nic nie dał i raczej nigdzie go nie brakuje, bo kompilując bez optymalizacji dalej źle działa, a inny kod wymagający volatile'a tu i tam psuł się dopiero na -O1.

Sprawa dalej zostaje nierozwiązana. Czekam na kolejne pomysły. ;)

Ostatnia aktualizacja: 18.09.2017 15:59:10 przez teh_KaiN
[#9] Re: Debugowanie ACE'a

@teh_KaiN, post #8

Podbijam temat, bo rzeczy jest więcej a widzę nikomu nie chce się dalej myśleć nad pozycją wiązki rysującej. Zanim przejdziemy do perfidnych rejestrów blittera™ to zapytam Was o małą rzecz: używać uint_fast8_t czy nie?

C99 wprowadza w inttypes.h definicje, które mają działać mniej więcej tak:
uint#_t - typ #-bitowy
uint_least#_t - typ, który jest najmniejszy z możliwych ORAZ zawierający przynajmniej # bitów,
uint_fast#_t - typ, który jest najszybszy ORAZ zawiera co najmniej # bitów.

I teraz, VBCC 0.9e robi coś takiego: typedef unsigned int uint_fast8_t, gdzie int jest 32-bitowy.
Czy dobrym pomysłem jest korzystanie z tego typu na 68000? Wydaje mi się, że najszybszy będzie u16 ze względu na szynę danych. Jak z samym działaniem instrukcji? asm 68000 rozróżnia instrukcje względem u16 a u32? Różnią się liczbą cykli? Najbardziej mnie tu zastanawia dzielenie i mnożenie, ale z tego co wyczytałem na szybko to jest po prostu divu.w, który działa na 32-bitowcach i zwraca 16-bitowy wynik i 16-bitową resztę. Mylę się?

Ostatnia aktualizacja: 23.09.2017 10:29:24 przez teh_KaiN
[#10] Re: Debugowanie ACE'a

@teh_KaiN, post #9

Z mnożeniem i dzieleniem na 68000 jest o tyle nieprzyjemnie, że C nie umie zrobić czegoś takiego jak mnożenie dwóch liczb 16-bitowych z wynikiem 32-bitowym. Kompilator zawsze najpierw sobie wypromuje czynniki do 32 bitów, po czym... wywoła funkcję biblioteczną (albo built-ina, względnie wstawi kod inline). Ogólna zasada jest taka, że kompilator zawsze doprowadzi wszystkie operandy działania i wynik do jednego typu, takiego żeby nigdzie nie było przepełnienia, czyli „równa w górę”.

Dlatego nie używam tych wszystkich wynalazków ze stdint.h tylko po staroamigowemu BYTE, WORD i LONG (plus odmiany bez znaku). I jeżeli chcę mieć mnożenie w jednym rozkazie to trzymam się WORD-ów, a jeżeli jednocześnie muszę mieć 32-bitowe wyniki, to robię sobie makro z wstawką asemblerową.
[#11] Re: Debugowanie ACE'a

@Krashan, post #10

No ja właśnie w kodzie pełno UBYTE'ów stawiałem ze względu na to, że tylko taki zakres liczb potrzebowałem. Nie znam się jednak na asemblerze na tyle, by stwierdzić czy to wiąże się z dodatkowym obciążeniem, np. może program niepotrzebnie sobie czyści wyższe bajty rejestrów żeby po szynie danych nie słać śmieci jak zapisuje z powrotem bajtową zmienną na stos lub adres w pamięci.

Czy zatem najszybszym typem danych na 68000 jest 16-bit, tak jak by to podpowiadało wąskie gardło szyny danych? I czy wynikowy kod na UWORDach będzie z reguły krócej się wykonywał nawet, jeśli zmienne będą faktycznie korzystać tylko z zakresu 8-bitowego?

Teraz do mnie dotarło, że w sumie trochę na lenia pytam bo mógłbym jakiś benczmark na kolanie sklecić, ale w sumie odpowiedź od kogoś znającego asm byłaby cenniejsza. ;)

Ostatnia aktualizacja: 23.09.2017 21:40:08 przez teh_KaiN
[#12] Re: Debugowanie ACE'a

@teh_KaiN, post #11

680x0 może na szynę wrzucać pojedyncze bajty, to nie jest problem. Natomiast dodatkowe maskowanie może się pojawić przy operacjach na rejestrach, właśnie mnożeniu i dzieleniu. Większość prostych operacji, czyli przemieszczenia, dodawanie, odejmowanie i logika bitowa, może mieć „naturalnie” operandy o rozmiarze bajtu, ale mnożenie i dzielenie już nie i wtedy trzeba rozszerzać, co oznacza jedną, a czasem nawet dwie dodatkowe instrukcje (rozszerzanie z 8 do 32 bitów dla dzielnej).

Dlatego moim zdaniem optymalną szerokością słowa do obliczeń matematycznych jest dla 68000 [U]WORD, o ile zakres nas zadowala. Oczywiście jeżeli obliczenia są połączone z operacjami na pamięci, to dla 68000 będzie to również oznaczało zmniejszenie ilości dostępów do tejże pamięci względem liczb 32-bitowych.
[#13] Re: Debugowanie ACE'a

@teh_KaiN, post #1

Moje zdanie na temat źródeł to pewnie znasz i nie jest bynajmniej pohlebne :). Wymaga solidnej refaktoryzacji a być może przepisania od nowa. Oczywiście to moja osobista opinia i nie musisz się z nią zgadzać.
Problemów to tu jest wiele i być może ciężko sobie zdać sprawę z tego. Zacznijmy od początku.

1. Skoro pDeltas to są ULONGI to po dlaczego allokujesz uwCount*sizeof(tAvg). Chyba to błąd kopisty.
2. Załóżmy że uwCount = 100. i wywołajmy raz parę logAvgBegin i logAvgEnd. Potem gdzieś robimy logAvgWrite który i tak leci po wszytkich pDeltas, które nie zostały zainicjowane i mają przypadkowe wartości.
3. Wychodzi na to że po 7 minutach gry, Twoje mierzenie czasu idzie w łeb i cuda się dzieją.
[#14] Re: Debugowanie ACE'a

@asman, post #13

Najlepsze jest to, że to nie tylko Twoje zdanie, bo również moje i Proxy'ego - współtwórcę ACE'a. Tylko że gdybym miał nad refactorem siedzieć non-stop to nigdy bym niczego nie napisał. ;) A jak Ci się nudzi to czekam na pull requesty. ;)

1. Fixed, dzięki!
2. Tak, i z tego co pamiętam zrobiłem to w pełni świadomie żeby przyoszczędzić na kodzie, bo skoro ktoś ustawi sobie 50 próbek to chyba przez 50 klatek będzie robił pomiary. Tak czy inaczej zysk jest niewielki, więc fixed.
3. Rozwiń proszę myśl, bo widzisz coś, czego chyba ja nie widzę. Na pewno obecne rozwiązanie nie pozwala mierzyć dłużej niż 7 minut między beginem a endem, ale komu to potrzebne.

Bardzo mi się też podoba formuła "Asman dissuje ACE'a" bo to swoiste code review - z niektórych rzeczy się pewnie wybronię, z niektórych nie, więc je zmienię. Jak ktoś chce się dołączyć do jeżdżenia po tym co tam sobie popisałem to zapraszam do dyskusji. ;)
[#15] Re: Debugowanie ACE'a

@teh_KaiN, post #14

Nie zdążyłem zedytować, ale trochę przerobiłem funkcję od pomiaru czasu precyzyjnego na wiązce rysującej i jest tak:

Avg undraw explosions:  42.4 us, min:   0.0 us, max: 106.4 us
	Avg undraw projectiles:  62.0 us, min:   0.0 us, max:  64.4 us
	Avg undraw vehicles:   2.321 ms, min:   0.0 us, max:   2.494 ms
	Avg draw vehicles:   9.046 ms, min:   7.134 ms, max:  11.742 ms
	Avg draw projectiles: 319.6 us, min: 230.8 us, max: 895.6 us
	Avg draw explosions:  24.8 us, min:   0.0 us, max: >7min


W sumie we wszystko jestem w stanie uwierzyć oprócz ostatniego maxa. ;)
BTW. czasy undrawów w ogóle nie działały, bo nie były mierzone - wyszło po zasugerowanych przez Asmana zmianach.
[#16] Re: Debugowanie ACE'a

@teh_KaiN, post #14

Rozwijam myśl bo faktycznie zbyt enigmatycznie napisałem.
Timer VBlank server zwiększa frame counter. Ok. Robi to cały czas. Czyli po 7 minutach taki frame counter wynosi 7*60*50 = 21000. W timerGetPrec jest mnożony przez 160*313 co daje szestnastkowo $3eaf5d00 co jeszcze jest ok bo jest mniejsze niż $ffffffff>>2 (to jest $3fffffff ) ale już dla 7 minut i 3 sekund przekracza magiczną barierę $3fffffff. Ta bariera jest w funkcji timerFormatPrec i wtedy właśnie zaczynają się dziać cuda. Bo frame counter dalej jest zwiększany a ulong może zostać przekręcony później. Mam nadzieję że chwytasz o co kaman.
Na szybko nie widzę innej możliwości naprawy tego niż to, że możesz jedynie zwiększyć frame counter do ulonga i używać jakiejś struktury która będzie trzymać osobno frame counter i vhposr.
To co proponuje Krashan wydaje mi się bardzo dobrym podejściem ale to co proponuje mschulz z ReadEClock jest też bardzo dobre o ile masz kicka w odpowiedniej wersji (v36 i wyżej).

I to co tam teraz narobiłeś w TimerGetPrec to nie ogarniam i po prawdzie nie mam czasu. Ale trzeba być ostrożnym gdy w gre wchodzą przerwania.


Ostatnia aktualizacja: 25.09.2017 20:47:48 przez asman
[#17] Re: Debugowanie ACE'a

@asman, post #16

Warto też zadać pytanie, czy potrzebna jest tak duża dokładność pomiaru. Prawdopodobnie wielokrotność 64 us będzie wystarczająca, wtedy można zrezygnować ze składowej pozycji poziomej i problem nadmiaru w obliceniach samoistnie ustąpi.
[#18] Re: Debugowanie ACE'a

@makarsky, post #17

Nie no, oczywiście że jest to overkill, ale sam problem jest dość ciekawy i fajnie byłoby z tym jednak wygrać. ;) Może kiedyś przejdę na EClock, choć odstrasza mnie zwolnienie procka do częstotliwości CIA by pobrać tę informację, no i zależność od OSu. W tej chwili jest jednak bardziej palący problem z blitterem, ale to już jutro, bo dzisiaj nie dam rady o tym pisać.

Ostatnia aktualizacja: 28.09.2017 19:21:00 przez teh_KaiN
[#19] Re: Debugowanie ACE'a

@teh_KaiN, post #18

Zgodnie z zapowiedzią, pora na perfidne rejestry blittera™.

ACE ma coś takiego jak showcase, czyli zbiór krótkich interaktywnych kodów testujących różne aspekty silnika. Jest blitowany kwadrat chodzący sobie po ekranie, są copperbary zmieniające kolory w przestrzeni kolorów HSL. Jest też testowa bitmapa interleaved. Jest też menu którym to wszystko się wybiera i tam wychodzi na jaw główna bolączka ACE'a - problemy z funkcjami blitującymi.

Chwila dla uzasadnienia czemu po prostu nie używać systemowych funkcji. Na KS1.3 BltBitMap() nie wspiera trybu interleaved, który dodaje blitterowi mocnego kopa, więc skodziłem własną funkcję blitCopy. Na systemie blitowanie przez maskę wymaga rastportu (ale nie tego Krashanowego ;)), a ten z kolei ma mechanizm regionów po których można blitować, którego raz że nie potrzebowałem, a dwa płakał mi figle przy scrolling tricku. Poza tym, po co kolejna warstwa abstrakcji ma mnie spowalniać jeśli jest mi ona zupełnie zbędna. Tak powstała funkcja blitCopyMask.

Wyszły przy tym różne ciekawe kwiatki, m.in. to że blitowanie z mintermem 0xC0 funkcją BltBitMap() używa tak naprawdę 0xCA, bo inaczej byłyby czarne pasy obok blitowanego regionu jeśli obszary źródłowe i końcowe nie miałyby współrzędnej x będącej wielokrotności 16. Poza tym moja implementacja napisana w durnym C jest ciut szybsza od systemowej (bo widać idzie delikatnie na skróty pomijając abstrakcję), a jak już skodziłem wariant blitowania kafli właśnie na "równych" współrzędnych x samym D=A, to uzyskałem przy tym coś koło 30% przyspieszenia. Blit przez maskę też jest znacząco szybszy.

Niestety są problemy z funkcjami blitującymi, zwłaszcza z zamiennikiem BltBitMap() czyli blitCopy(). Coś tam mocno kaszanię, co widać w moim rysowaniu fontów, przez co nie wszystkie literki dobrze się rysują.

Jeśli ktokolwiek chciałby pomóc podebugować, to jest na to łatwy sposób - wystarczy wkleić tę funkcję do swojego kodu i używać zamiennie zamiast BltBitMap() - powinno działać 1:1 oprócz wspomnianego tweaka z mintermem. No i na sztywno zamiast sBlitManager.pBlitterSetFn() wykorzystać kod z blitSetRegs().

Dla ciekawskich - w kodzie ACE'a jest mechanizm kolejkowania blitów, ale coraz bardziej jestem skłonny go wywalić, implementując w zamian możliwość podania funkcji, która miałaby być wykonywana w czasie czekania na blitter. Taka funkcja powinna możliwie często zwracać, by sprawdzanie stanu blittera odbywało się odpowiednio często. W takim rozwiązaniu można by przerzucić część logiki gry właśnie do tej funkcji, uzyskując lepszą współbieżność blittera i CPU. Ewentualną synchronizację można by zrobić maszyną stanów sterowaną globalami setowanymi po skończeniu danych blitów.

Dodatkowo, obecny build OpenFire ma problem z rysowaniem pocisków i eksplozji, który pojawił się w momencie przejścia na interleaved - widać też jest coś nie tak z funkcją blitCopyMask(). Tu to najlepiej testować bezpośrednio w mojej grze, jeśli komuś by się AŻ TAK nudziło.
[#20] Re: Debugowanie ACE'a

@teh_KaiN, post #19

Tematu blittera ciąg dalszy - stwierdziłem że przepiszę funkcję blitCopy na nowo z użyciem tylko ascending mode dla czytelności kodu.

Mam sobie obrazek z fontami (powiększenie 2x):


I powiedzmy że chcę wyblitować sobie 'a' (ascii 97) które leży sobie w drugim rządku alfabetu. Kanał "A" to maska, złożona z bltadat = 0xFFFF i odpowiednich bltafwm,bltalwm. Kanał "B" to bitmapa z fontem, "C" i "D" to docelowa 16x6.



Aby zblitować to docelowo na 0,0 maskę pierwszą mam 0011 1100 0000 0000 a drugą zera. Blit dwóch wordów z shiftem 14. Destination ustawiony jeden word wcześniej niż trzeba. Spodziewanym przeze mnie rezultatem powinno być:
- wyjazd litery "A" przez shift poza pierwszego worda w prawo, czyli same zera na "A" i nic się na pierwszym wodzie nie dzieje,
- drugi word shiftowany o 14 przez co wjeżdża litera "A" z poprzedniego worda i ląduje na pierwszym bicie.

Potem modulo cofa się na docelowej bitmapie o jeden word żeby wyblitować kolejną linię. Wszystko cacy, tak się dzieje, tylko że cokolwiek widać w ostatniej linii blita: wygląda to tak jakby jakimś cudem kolejne linie zamazywały poprzednie wbrew temu co mówi debugowe modulo A|C (0xFA).



Kod który to robi:
BYTE blitUnsafeCopy(
	tBitMap *pSrc, WORD wSrcX, WORD wSrcY,
	tBitMap *pDst, WORD wDstX, WORD wDstY, WORD wWidth, WORD wHeight,
	UBYTE ubMinterm, UBYTE ubMask
) {
	// Each dot/hash is 2 bits
	// CASE A:
	// srcX & 15 = dstX & 15
	// ####|#### ....|.... ....|....
	// ^===================v
	// ....|.... ....|.... ####|####
	//
	// CASE B:
	// srcX & 15 < dstX & 15
	// ..##|##.. ....|.... ....|....
	//   ^--v
	// ....|#### ....|.... ....|....
	// ^=========v
	// ....|.... ....|#### ....|....
	//
	// CASE C:
	// srcX & 15 > dstX & 15
	// ..##|#### ####|##.. ....|.... ....|....
	//   ^-------v
	// ....|.... ####|#### ####|#### ....|....
	// ^=========v
	// ....|.... ....|.... ####|#### ####|####

	logWrite(
		"Blitting from %hd,%hd to %hd,%hu, size: %hdx%hd\n",
		wSrcX, wSrcY, wDstX, wDstY, wWidth, wHeight
	);
	logWrite(
		"Bitmap sizes - src: %d,%d, dst: %d,%d\n",
		pSrc->BytesPerRow<<3, pSrc->Rows, pDst->BytesPerRow<<3, pDst->Rows
	);

	UBYTE ubSrcOffs = wSrcX & 0xF;
	UBYTE ubDstOffs = wDstX & 0xF;

	LONG lSrcSeg = pSrc->BytesPerRow * wSrcY + (wSrcX >> 3);
	LONG lDstSeg = pDst->BytesPerRow * wDstY + (wDstX >> 3);
	if(ubSrcOffs > ubDstOffs) {
		// Case C
		lDstSeg -= 2;
		ubDstOffs += 16;
		logWrite("Case 'C'\n");
	}
	UBYTE ubShift = ubDstOffs-ubSrcOffs;
	UWORD uwBlitWidth = (MAX(ubSrcOffs, ubDstOffs) + wWidth + 15) & 0xFFF0;
	UWORD uwBlitWords = uwBlitWidth >> 4;
	WORD wSrcModulo = pSrc->BytesPerRow - (uwBlitWords << 1);
	WORD wDstModulo = pDst->BytesPerRow - (uwBlitWords << 1);
	UWORD uwFirstMask = 0xFFFF >> ubSrcOffs;
	if(ubSrcOffs + wWidth < 16)
		uwFirstMask &= 0xFFFF << (16 - (ubSrcOffs + wWidth));
	UWORD uwLastMask = 0xFFFF << (uwBlitWidth - (ubSrcOffs + wWidth));

	logWrite("Offsets - src: %hhu, dst: %hhu\n", ubSrcOffs, ubDstOffs);
	logWrite("Segs - src: %d, dst: %d\n", lSrcSeg, lDstSeg);
	logWrite("Shift: %hhu\n", ubShift);
	logWrite("Blit width: %hu (%hu words)\n", uwBlitWidth, uwBlitWords);
	logWrite("Modulos - src: %hd, dst: %hd\n", wSrcModulo, wDstModulo);
	logWrite("Masks: %04X, %04X\n", uwFirstMask, uwLastMask);

	ubMask &= 0xFF >> (8- MIN(pSrc->Depth, pDst->Depth));
	UBYTE ubPlane = 0;
	OwnBlitter();
	custom.bltcon0 = (ubShift << ASHIFTSHIFT) | USEB|USEC|USED | ubMinterm;
	custom.bltcon1 = (ubShift << BSHIFTSHIFT);
	custom.bltafwm = uwFirstMask;
	custom.bltalwm = uwLastMask;
	// shift & mask before ptr & data
	custom.bltbmod = wSrcModulo;
	custom.bltcmod = wDstModulo;
	custom.bltdmod = wDstModulo;
	custom.bltadat = 0xFFFF;
	while(ubMask) {
		if(ubMask & 1) {
			WaitBlit();
			// This hell of a casting must stay here or else large offsets get bugged!
			custom.bltbpt  = (UBYTE*)(((ULONG)(pSrc->Planes[ubPlane])) + lSrcSeg);
			custom.bltcpt  = (UBYTE*)(((ULONG)(pDst->Planes[ubPlane])) + lDstSeg);
			custom.bltdpt  = (UBYTE*)(((ULONG)(pDst->Planes[ubPlane])) + lDstSeg);
			custom.bltsize = (wHeight << 6) | uwBlitWords;
		}

		ubMask >>= 1;
		++ubPlane;
	}
	DisownBlitter();
	return 1;
}


A log z tego:
Blitting from 146,0 to 0,0, size: 4x6
Bitmap sizes - src: 256,6, dst: 16,6
Case 'C'
Offsets - src: 2, dst: 16
Segs - src: 18, dst: -2
Shift: 14
Blit width: 32 (2 words)
Modulos - src: 28, dst: -2
Masks: 3C00, 0000


Bardzo bym prosił o pomoc z racji zbliżającego się RKLE a co najważniejsze - z blitterem walczę już tyle czasu że nie mam pomysłu - muszę popełniać jakiś błąd rzeczowy z tym jak on działa.

EDYT: A jak powiększę bitmapę docelową wszerz o 16px i dstX też zwiększę o 16 to poprawnie cały prostokąt litery się rysuje. Oczywiście przesunięty. wtf!

Blitting from 146,0 to 16,0, size: 4x6
Bitmap sizes - src: 256,6, dst: 32,6
Case 'C'
Offsets - src: 2, dst: 16
Segs - src: 18, dst: 0
Shift: 14
Blit width: 32 (2 words)
Modulos - src: 28, dst: 0
Masks: 3C00, 0000


Ostatnia aktualizacja: 07.10.2017 12:57:57 przez teh_KaiN
[#21] Re: Debugowanie ACE'a

@teh_KaiN, post #20

Strasznie to wszystko skomplikowane dla mnie. Zanim przejmiesz blitter za pomocą OwnBlitter to trzeba zrobić WaitBlitter, bo nie wiadomo czy ktoś przed Tobą właśnie w tym momencie z niego nie korzysta, nawet Ty sam możesz korzystać dwa razy i okaże się, że w trakcie poprzedniego blitu zaczynasz zmieniać rejestry blittera. Pomijam fakt, że tak na prawdzie to warto Own i Disown blitter przenieść gdzieś do jakiegoś początkowego initu gry. Ja przynajmniej nie widzę takiego sensu by co chwila robić own i disown. Dokładnie to ja nie wiem co ta funkcja ma robić w nazwie ma copy, czyli kopiowanie, a te uskuteczniamy za pomocą D=A czyli $9f0 w bltcon0 (jeśli nie ma przesunięć). Nie podałeś jakiej wielkości są te fonty i jak są ułożone w pamięci. Nic nie wiadomo na temat ekranu, jakie jest modulo. Jeśli AGA to czy fmode jest ustawione czy nie (wtedy też zmienia się modulo ekranu). Ja bym zaczął od własnie prostego skopiowania D=A.
[#22] Re: Debugowanie ACE'a

@asman, post #21

Kolejność OwnBlitter faktycznie zmienię, ale nie sądzę żeby tu miała istotny wpływ.

Twarde D=A działa tylko i wyłącznie jeśli nie masz shifta, w innym razie zerujesz sobie bity na szerokość shifta i tego, co zostaje za końcem wblitowywanego obszaru do końca zawierającego worda. Na pełnych wordach działa moja funkcja blitCopyAligned() i ona jest bezproblemowa. Żeby uniknąć problemów potrzebujesz maskę A, źródło danych B i istniejące dane C żeby niczego nie zniszczyć.

Ułożone są fonty jak widać, jest to 1bpp, które dałem w dwukrotnym powiększeniu. Jakoś wcześniej w tym wątku pisałem że rozmawiamy o OCS/ECS. Ekran też nie ma nic do tego, bo blituję do bitmapy 16x6 - sorry że poleciałem takim skrótem myślowym, ale myślałem że z loga funkcji będzie to dość jasne.

Ostatnia aktualizacja: 08.10.2017 23:07:06 przez teh_KaiN
[#23] Re: Debugowanie ACE'a

@teh_KaiN, post #22

Kolejność OwnBlitter faktycznie zmienię, ale nie sądzę żeby tu miała istotny wpływ.

Przejęcie Blittera w systemie jest zrealizowane w ten sposób, że po OwnBlitter() musi nastąpić WaitBlit(), po czym masz swobodny dostęp do Blittera. Przed DisownBlitter() za to WaitBlita() być nie musi.

Używanie Blittera kiedy wykonuje pracę może prowadzić do "nieokreślonych konsekwencji". Chyba warto odrzucić tę ewentualność przy testowaniu swojego kodu.
[#24] Re: Debugowanie ACE'a

@teh_KaiN, post #20

Jedna rzecz mi się nie zgadza. Blitujesz o szerokości 2 słów a destynacja ma tylko jedno słowo szerokości (16pix) to jakim cudem to ma zadziałać poprawnie ?
[#25] Re: Debugowanie ACE'a

@asman, post #24

Pionowe prowadnice pokazują początek i koniec pierwszego WORDa bitmapy źródłowej:



Maska 0011 1100 0000 0000 żeby wyciąć samo "A". Shift 14 zatem "A" się wyblituje dopiero w drugim WORDzie, pierwszy będzie miał maskę z samych zer. Destynacja jest ustawiana na WORD wcześniej, tak by pierwsze słowo z maską zerową zrobiło nic, a drugie wypaliło się faktycznie na bitmapie.

EDYT: oczywiście rzutowania bltxpt można uprościć do:
custom.bltbpt  = (UBYTE*)((ULONG)pSrc->Planes[ubPlane]) + lSrcSeg;
custom.bltcpt  = (UBYTE*)((ULONG)pDst->Planes[ubPlane]) + lDstSeg;
custom.bltdpt  = (UBYTE*)((ULONG)pDst->Planes[ubPlane]) + lDstSeg;


Ostatnia aktualizacja: 05.11.2017 10:40:49 przez teh_KaiN
[#26] Re: Czy opłaca się tworzyć gry?
Co do engine'u i wgryzania się w chipset to już coś takiego powstało. Siedzę nad tym ponad 5 lat, ale jakoś nikt nie jest skory do pomocy żeby to mi pomóc rozwijać. Latają na tym wszystkie moje gry amigowe.

Interesujace - rzucilem okiem na system.c i mam pare uwag:
- po LoadView powinny byc dwa wywolania WaitTOF
- vbr odczytasz w trybie supervisor tj. test czy procesor >68000 (AttnFlags w ExecBase), jesli tak, to vbr=Supervisor(GetVBR); GetVBR musi byc w assemblerze: movec.l VBR, d0 ; rte
- przerwania - uzylbym AddIntServer/RemIntSrver zamiast bezposrednio patchowac wektory, wtedy odczyt vbr nie bedzie potrzebny

Do obslugi joysticka/myszki itp uzylbym lowlevel.library - zdaje sie, ze powinna dzialac na starszych wersjach ks jak np. 2.0, przy okazji obsluguje tez przerwania
[#27] Re: Czy opłaca się tworzyć gry?

@docent, post #26

@Docent dzięki! Dwa WaitTOFy kiedyś tam były i z jakiegoś powodu został jeden, nie pamiętam czemu - wrócę do dwóch i jak znajdę powód czemu dałem jeden to napiszę w komentarzu w kodzie. ;) VBR dopiszę bo będę miał to niedługo jak testować na 020. Przerwania - byłem tam i okazuje się że OS dalej się wpieprza swoimi rzeczami w te przerwania, zjadając czas CPU. Dowód - AddIntServer narzuca format argumentów callbacków przerwania, więc nie jest to bezpośrednia podmiana wektorów na sprzęcie, tylko wciąż masz wrapper który oddaje kontrolę OSowi do robienia newralgicznych rzeczy.

W grach mierzę sobie czas tak jak mi Asman kiedyś zasugerował, żeby zmieniać kolor zerowy przed i po wykonaniu jakiegoś fragmentu głównej pętli, co w rezultacie da kolorowy pasek w tle proporcjonalnie gruby do czasu wykonania. Przed zawłaszczeniem HW kolorowe paski zaczynały się i kończyły w losowych miejscach, po zawłaszczeniu HW paski co klatkę pojawiają się w tym samym momencie i są zawsze takiej samej grubości.
[#28] Re: Czy opłaca się tworzyć gry?

@teh_KaiN, post #27

Przede wszystkim byłoby miło gdyby Administracja przeniosła posty dotyczące silnika teh_KaiN. To wtedy się wypowiem :)
[#29] Re: Czy opłaca się tworzyć gry?

@asman, post #28

Na przykład do wątku debugowanie ACE'a. Albo kontynuujmy sami dyskusję tam. ;)
[#30] Re: Debugowanie ACE'a

@teh_KaiN, post #1

Też przejrzałem system.c i też mam parę spostrzeżeń.
1. Przeniósłbym obsługę przerwań do modułu int lub coś podobnej nazwie (interrupts).
1a. W int2Handler nie jest sprawdzane czy to przerwanie jest 2 poziomu. Chodzi mi o to czy faktycznie itreqr ma w sobie INTF_PORTS.
1b. Na szybkich maszynach na przykład w A4000 przerwanie może wykonać się za szybko i jeden g_pCustom->intreq = INTF_PORTS; to za mało. Albo trzeba powtórzyć to coś bądź przetestować jakiś custom rejestr (na przykład tst.w intreqr(a0) ). We większości źródeł jakie widziałem to jest powtórzenie. Można też zrobić dodatkowo nop.

2 Jeśli chodzi o te WaitOFy to podejrzewam że powodem "dania WaitOF przed LoadView(0)" są krzaki które pojawiają się na chwilę przed załadowaniem 'pustego view'. Ja omijam ten problem wyłączając DMAF_RASTER w dmacon.
2a. Przed 'resetem display' to ja u siebie zawłaszczam blitter by ominąć copjmp nasty bug. I starm się nie używać copjmp. A tutaj (w system.c) nie widzę tego.

3. Przydałby się OsFlush po 'reset display' albo dioda we flopie będzie się cały czas świecić. Implementacje OsFlush mam w asmie jakby kto chciał.

4. Zatrzymujesz przerwania a potem czakasz na ramkę i wyłączasz dma. Hoho (tak świątecznie ) to tu sie może zdarzyć (po zatrzymaniu przerwań a przed wyłączeniem dma).Pomyśl przez chwilę o copperze który wciąż działa i może sobie zmienić a na przykład intenę.

5. Powtórzenia! i skomplikowany kod - jak dla mnie
6. Zakomentowany blok kodu - po co ? Po tygodniu tego nie będziesz pamiętał.
Na stronie www.PPA.pl, podobnie jak na wielu innych stronach internetowych, wykorzystywane są tzw. cookies (ciasteczka). Służą ona m.in. do tego, aby zalogować się na swoje konto, czy brać udział w ankietach. Ze względu na nowe regulacje prawne jesteśmy zobowiązani do poinformowania Cię o tym w wyraźniejszy niż dotychczas sposób. Dalsze korzystanie z naszej strony bez zmiany ustawień przeglądarki internetowej będzie oznaczać, że zgadzasz się na ich wykorzystywanie.
OK, rozumiem