From 4da9fe24fced02a6b3e61dd86896057505a43ade Mon Sep 17 00:00:00 2001 From: Pawel Rzonca Date: Wed, 12 Nov 2025 14:31:34 +0100 Subject: [PATCH 1/4] adding readme translation --- README.es.md | 3 ++- README.fr.md | 3 ++- README.md | 3 ++- README.pl.md | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 README.pl.md diff --git a/README.es.md b/README.es.md index 7c9a91a..8071098 100644 --- a/README.es.md +++ b/README.es.md @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB **Traducciones** * [English](./README.md) -* [Français](./README.fr.md) \ No newline at end of file +* [Français](./README.fr.md) +* [Polish](./README.pl.md) diff --git a/README.fr.md b/README.fr.md index b9b1fa8..6a7a103 100644 --- a/README.fr.md +++ b/README.fr.md @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB **Traductions** * [English](./README.md) -* [Spanish](./README.es.md) \ No newline at end of file +* [Spanish](./README.es.md) +* [Polish](./README.pl.md) diff --git a/README.md b/README.md index 67c4007..6469005 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,5 @@ https://discord.com/invite/Ks5MhUhqfB **Translations** * [Français](./README.fr.md) -* [Spanish](./README.es.md) \ No newline at end of file +* [Spanish](./README.es.md) +* [Polish](./README.pl.md) diff --git a/README.pl.md b/README.pl.md new file mode 100644 index 0000000..8371944 --- /dev/null +++ b/README.pl.md @@ -0,0 +1,19 @@ +Witajcie w Szkole Języka Asemblera FFmpeg. Zrobiliście pierwszy krok na najbardziej interesującej, wymagającej i satysfakcjonującej ścieżce w programowaniu. Te lekcje zapewnią wam fundamenty wiedzy o tym, jak asembler jest wykorzystywany w FFmpeg i otworzą wam oczy na to, co naprawdę dzieje się w waszym komputerze. + +**Wymagana wiedza** + +* Znajomość języka C, w szczególności wskaźników. Jeśli nie znasz C, przestudiuj książkę [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) +* Matematyka na poziomie szkoły średniej (skalary vs wektory, dodawanie, mnożenie itp.) + +**Lekcje** + +W tym repozytorium Git znajdziesz lekcje i przypisane do nich zadania (jeszcze nie dodane). Po ukończeniu tych lekcji będziesz w stanie wnieść wkład do FFmpeg. + +Dostępny jest serwer Discord, na którym odpowiadamy na pytania: +https://discord.com/invite/Ks5MhUhqfB + +**Tłumaczenia** + +* [English](./README.md) +* [Français](./README.fr.md) +* [Spanish](./README.es.md) From ebdf5e9b3b889b7e8ac718eee87792a0e77d9c64 Mon Sep 17 00:00:00 2001 From: Pawel Rzonca Date: Mon, 17 Nov 2025 23:00:42 +0100 Subject: [PATCH 2/4] add translation of lesson 1 --- README.pl.md | 2 +- lesson_01/index.pl.md | 218 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 lesson_01/index.pl.md diff --git a/README.pl.md b/README.pl.md index 8371944..91ca09f 100644 --- a/README.pl.md +++ b/README.pl.md @@ -1,4 +1,4 @@ -Witajcie w Szkole Języka Asemblera FFmpeg. Zrobiliście pierwszy krok na najbardziej interesującej, wymagającej i satysfakcjonującej ścieżce w programowaniu. Te lekcje zapewnią wam fundamenty wiedzy o tym, jak asembler jest wykorzystywany w FFmpeg i otworzą wam oczy na to, co naprawdę dzieje się w waszym komputerze. +Witaj w Szkole Języka Asemblera FFmpeg. Zrobiłeś pierwszy krok na najbardziej interesującej, wymagającej i satysfakcjonującej ścieżce w programowaniu. Te lekcje zapewnią Ci fundamenty wiedzy o tym, jak asembler jest wykorzystywany w FFmpeg i otworzą oczy na to, co naprawdę dzieje się w waszym komputerze. **Wymagana wiedza** diff --git a/lesson_01/index.pl.md b/lesson_01/index.pl.md new file mode 100644 index 0000000..0f7cc42 --- /dev/null +++ b/lesson_01/index.pl.md @@ -0,0 +1,218 @@ +**Lekcja Pierwsza Języka Asemblera FFmpeg** + +**Wprowadzenie** + +Witaj w Szkole Języka Asemblera FFmpeg. Zrobiłeś pierwszy krok na najbardziej interesującej, wymagającej i satysfakcjonującej ścieżce w programowaniu. Te lekcje zapewnią Ci fundamenty wiedzy o tym, jak asembler jest wykorzystywany w FFmpeg i otworzą oczy na to, co naprawdę dzieje się w waszym komputerze. + +**Wymagana wiedza** + +* Znajomość języka C, w szczególności wskaźników. Jeśli nie znasz C, przestudiuj książkę [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) +* Matematyka na poziomie szkoły średniej (skalary vs wektory, dodawanie, mnożenie itp.) + +**Czym jest język asemblera?** + +Język asemblera to język programowania, w którym tworzy się kod odpowiadający bezpośrednio instrukcjom przetwarzanym przez procesor (CPU). Czytelny dla człowieka kod asemblera jest, jak sama nazwa wskazuje, *asemblowany* (przekładany) na dane binarne, znane jako *kod maszynowy*, które są zrozumiałe dla procesora. Kod języka asemblera bywa określany mianem „assembly” lub, w skrócie, „asm”. + +Zdecydowana większość kodu asemblera w FFmpeg to tzw. SIMD (ang. Single Instruction Multiple Data – pojedyncza instrukcja, wiele danych). SIMD bywa czasem nazywane programowaniem wektorowym. Oznacza to, że dana instrukcja wykonuje operacje na wielu elementach danych jednocześnie. Większość języków programowania operuje na jednym elemencie danych naraz, co określa się mianem programowania skalarnego. + +Jak można się domyślić, SIMD doskonale sprawdza się w przetwarzaniu obrazów, wideo i dźwięku, które składają się z dużej ilości danych ułożonych w pamięci sekwencyjnie. W procesorze dostępne są specjalistyczne instrukcje, które pomagają w przetwarzaniu takich danych sekwencyjnych. + +W FFmpeg terminy „assembly function” (funkcja asemblera), „SIMD” i „vector(ise)” (wektor(yzacja)) są używane zamiennie. Wszystkie odnoszą się do tego samego: pisania funkcji w języku asemblera ręcznie, aby przetwarzać wiele elementów danych jednocześnie. Niektóre projekty mogą również określać je jako „assembly kernels” (jądra asemblera). + +Wszystko to może brzmieć skomplikowanie, ale ważne jest, aby pamiętać, że w FFmpeg kod asemblera pisały osoby w wieku szkolnym. Jak w przypadku wszystkiego, nauka to w 50% żargon, a w 50% rzeczywista wiedza. + +**Dlaczego piszemy w języku asemblera?** +Aby przyspieszyć przetwarzanie multimediów. Często można uzyskać 10-krotną lub większą poprawę wydajności dzięki pisaniu kodu asemblera, co jest szczególnie ważne, gdy chcemy odtwarzać filmy w czasie rzeczywistym bez zacinania się. Oszczędza to również energię i wydłuża żywotność baterii. Warto zauważyć, że funkcje kodowania i dekodowania wideo są jednymi z najczęściej używanych funkcji na świecie, zarówno przez użytkowników końcowych, jak i przez duże firmy w ich centrach danych. Nawet niewielka poprawa szybkości sumuje się szybko. + +Często można zobaczyć, online, ludzi używających *intrinsics*, które są funkcjami podobnymi do C, które mapują się na instrukcje asemblera, aby umożliwić szybszy dewelopemnt. W FFmpeg nie używamy intrinsics, ale zamiast tego piszemy kod asemblera ręcznie. Jest to obszar kontrowersji, ale intrinsics są zazwyczaj o około 10-15% wolniejsze niż ręcznie napisany asembler (zwolennicy intrinsics nie zgadzają się), w zależności od kompilatora. Dla FFmpeg każdy kawałek dodatkowej wydajności pomaga, dlatego piszemy bezpośrednio w kodzie asemblera. Istnieje również argument, że intrinsics są trudne do odczytania z powodu ich użycia „[Notacji Węgierskiej (Hungarian Notation)](https://pl.wikipedia.org/wiki/Notacja_w%C4%99gierska)”. + +Możesz również zobaczyć *inline assembly* (wstawka asemblera, tj. nie używając intrinsics) w kilku miejscach w FFmpeg ze względów historycznych lub w projektach takich jak jądro Linuksa ze względu na bardzo specyficzne przypadki użycia. Jest to sytuacja, w której kod asemblera nie znajduje się w osobnym pliku, ale jest napisany wraz z kodem C. W projektach takich jak FFmpeg panuje powszechna opinia, że kod ten jest nieczytelny, słabo wspierany przez kompilatory i trudny w utrzymaniu. + +I wreszcie – w internecie spotkasz wielu samozwańczych ekspertów twierdzących, że to wszystko jest zbędne, a kompilator może przeprowadzić całą tę „wektoryzację” za ciebie. Przynajmniej na etapie nauki – zignoruj ich. Niedawne testy, np. [projekcie dav1d](https://www.videolan.org/projects/dav1d.html), wykazały około dwukrotne przyspieszenie dzięki automatycznej wektoryzacji, podczas gdy wersje napisane ręcznie osiągały przyspieszenie rzędu 8x. + +**Warianty języka asemblera** +Te lekcje skupią się na języku asemblera x86 64-bitowym. Jest on również znany jako amd64, chociaż działa także na procesorach firmy Intel. Istnieją inne rodzaje asemblera dla innych procesorów, takich jak ARM czy RISC-V, i być może w przyszłości lekcje te zostaną o nie rozszerzone. + +W internecie spotkasz dwa warianty składni asemblera x86: AT&T oraz Intel. Składnia AT&T jest starsza i trudniejsza w czytaniu w porównaniu do składni Intela. Dlatego będziemy używać składni Intela. + +**Materiały pomocnicze** +Możesz być zaskoczony informacją, że książki czy zasoby internetowe, takie jak Stack Overflow, nie są w tym przypadku zbyt pomocnymi źródłami wiedzy. Wynika to częściowo z naszego wyboru ręcznego pisania asemblera w składni Intela. Ale również dlatego, że większość zasobów internetowych koncentruje się na programowaniu systemów operacyjnych lub hardware'u, zazwyczaj wykorzystując kod inny niż SIMD. Asembler w FFmpeg skupia się w szczególności na wysokowydajnym przetwarzaniu obrazu i – jak sam zobaczysz – jest to wyjątkowo specyficzne podejście do programowania w tym języku. Niemniej jednak, po ukończeniu tych lekcji bez trudu zrozumiesz także inne zastosowania asemblera. + +Wiele książek zagłębia się w szczegóły architektury komputerów, zanim w ogóle przejdzie do nauki asemblera. Jest to w porządku, jeśli to właśnie chcesz zgłębić, ale z naszego punktu widzenia przypomina to studiowanie budowy silnika przed nauką prowadzenia samochodu. + +Warto jednak dodać, że diagramy w dalszych rozdziałach książki „The Art of 64-bit assembly”, ukazujące instrukcje SIMD i ich działanie w formie wizualnej, są bardzo pomocne: https://artofasm.randallhyde.com/ + +Dostępny jest serwer Discord, na którym odpowiadamy na pytania: +[https://discord.com/invite/Ks5MhUhqfB](https://discord.com/invite/Ks5MhUhqfB) + +**Rejestry** +Registers are areas in the CPU where data can be processed. CPUs don’t operate on memory directly, but instead data is loaded into registers, processed, and written back to memory. In assembly language, generally, you cannot directly copy data from one memory location to another without first passing that data through a register. + +Rejestry to obszary wewnątrz procesora (CPU), w których mogą być przetwarzane dane. Procesory nie wykonują operacji bezpośrednio na pamięci. Zamiast tego dane są wczytywane do rejestrów, przetwarzane, a następnie zapisywane z powrotem do pamięci. +W języku asemblera generalnie nie można skopiować danych bezpośrednio z jednego miejsca w pamięci do drugiego bez uprzedniego przepuszczenia ich przez rejestr. + +**Rejestry ogólnego przeznaczenia (GPR)** +Pierwszym typem rejestru jest tzw. Rejestr Ogólnego Przeznaczenia (ang. General Purpose Register – GPR). Określa się je mianem „ogólnego przeznaczenia”, ponieważ mogą one zawierać zarówno dane, w tym przypadku wartości do 64 bitów, jak i adresy pamięci (wskaźniki). Wartość znajdująca się w rejestrze GPR może być przetwarzana za pomocą operacji takich jak dodawanie, mnożenie, przesunięcia bitowe itp. + +W większości książek poświęconych asemblerowi całe rozdziały dedykowane są niuansom rejestrów GPR, ich tłu historycznemu itd. Wynika to z faktu, że GPR-y są kluczowe w programowaniu systemów operacyjnych czy inżynierii wstecznej. Jednak w kodzie asemblera pisanym dla FFmpeg, rejestry GPR pełnią raczej rolę rusztowania, a przez większość czasu ich zawiłości nie są potrzebne i pozostają ukryte. + +**Rejestry wektorowe (SIMD)** +Rejestry wektorowe (SIMD), jak sama nazwa wskazuje, zawierają wiele elementów danych. Istnieją różne typy rejestrów wektorowych: + +* rejestry mm – rejestry MMX, rozmiar 64-bitowy, historyczne i obecnie rzadko używane. +* rejestry xmm – rejestry XMM, rozmiar 128-bitowy, szeroko dostępne. +* rejestry ymm – rejestry YMM, rozmiar 256-bitowy, pewne komplikacje przy ich używaniu. +* rejestry zmm – rejestry ZMM, rozmiar 512-bitowy, ograniczona dostępność. + +Większość obliczeń w kompresji i dekompresji wideo opiera się na liczbach całkowitych (ang. integer-based), więc przy tym pozostaniemy. Oto przykład 16 bajtów (bytes) w rejestrze xmm: + +| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +Ale może to być również osiem słów (words) (16-bitowe liczby całkowite): + +| a | b | c | d | e | f | g | h | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +Lub cztery podwójne słowa (doublewords) (32-bitowe liczby całkowite): + +| a | b | c | d | +| :---- | :---- | :---- | :---- | + +Lub dwa poczwórne słowa (quadwords) (64-bitowe liczby całkowite): + +| a | b | +| :---- | :---- | + +Podsumowując: + + +* **b**ytes - 8-bitowe dane +* **w**ords - 16-bitowe dane +* **d**oublewords - 32-bitowe dane +* **q**uadwords - 64-bitowe dane +* **d**ouble **q**uadwords - 128-bitowe dane + +Pogrubione znaki będą ważne później. + +**Dołaczanie x86inc.asm** + +W wielu przykładach zauważysz, że dołączamy plik x86inc.asm. Jest to lekka warstwa abstrakcji używana w projektach FFmpeg, x264 oraz dav1d, mająca na celu ułatwienie życia programiście asemblera. Pomaga ona na wiele sposobów, ale na początek warto wspomnieć o jednej z jej przydatnych funkcji: nadaje ona GPR-om etykiety r0, r1, r2. Oznacza to, że nie musisz pamiętać nazw rejestrów. Jak wspomniano wcześniej, rejestry GPR są zazwyczaj tylko „rusztowaniem”, więc takie uproszczenie znacznie ułatwia pracę. + +**Prosty fragment skalarnego asm** + +Przyjrzyjmy się prostemu (i mocno sztucznemu) fragmentowi asemblera skalarnego (kodu, który w ramach jednej instrukcji operuje na pojedynczych elementach danych, jeden po drugim), aby zobaczyć, o co w tym chodzi: + +```assembly +mov r0q, 3 +inc r0q +dec r0q +imul r0q, 5 +``` + +W pierwszej linii *immediate value (wartość natychmiastowa)* 3 (wartość zapisana bezpośrednio w samym kodzie asemblera, w przeciwieństwie do wartości pobranej z pamięci) jest zapisywana w rejestrze r0 jako poczwórne słowo (quadword). Zauważ, że w składni Intela operand źródłowy (wartość lub lokalizacja dostarczająca dane, znajdująca się po prawej) jest przenoszony do operandu docelowego (lokalizacja odbierająca dane, po lewej), co bardzo przypomina działanie funkcji memcpy. Możesz to również czytać jako „r0q = 3”, ponieważ kolejność jest taka sama. Sufiks „q” przy r0 oznacza, że rejestr ten jest używany jako poczwórne słowo. inc inkrementuje (zwiększa o 1) wartość, więc r0q zawiera 4.dec dekrementuje (zmniejsza o 1) wartość z powrotem do 3. imul mnoży wartość przez 5.Zatem na końcu r0q zawiera 15. + +Pamiętaj, że czytelne dla człowieka instrukcje, takie jak mov i inc, które są zamieniane przez asembler na kod maszynowy, znane są jako *mnemonics* (mnemoniki). W internecie i książkach możesz spotkać mnemoniki zapisane wielkimi literami, np. MOV i INC, ale oznaczają one to samo, co wersje małymi literami. W FFmpeg używamy mnemoników pisanych małymi literami, a wielkie litery rezerwujemy dla makr. + +**Zrozumienie podstawowej funkcji wektorowej** + +Oto nasza pierwsza funkcja SIMD: + +```assembly +%include "x86inc.asm" + +SECTION .text + +;static void add_values(uint8_t *src, const uint8_t *src2) +INIT_XMM sse2 +cglobal add_values, 2, 2, 2, src, src2 + movu m0, [srcq] + movu m1, [src2q] + + paddb m0, m1 + + movu [srcq], m0 + + RET +``` + +Przejdźmy przez nią linia po linii: + +```assembly +%include "x86inc.asm" +``` + +To jest "header" (nagłówek) opracowany w społecznościach x264, FFmpeg i dav1d, który dostarcza pomocnicze funkcje, predefiniowane nazwy i makra (takie jak cglobal poniżej), aby uprościć pisanie asemblera. + +```assembly +SECTION .text +``` + +To oznacza sekcję, w której umieszczany jest kod, który ma być wykonywany. Jest to w przeciwieństwie do sekcji .data, w której można umieścić dane stałe. + +```assembly +;static void add_values(uint8_t *src, const uint8_t *src2) +INIT_XMM sse2 +``` + +Pierwsza linia to komentarz (średnik „;” w asm jest jak „//” w C) pokazujący, jak wygląda argument funkcji w C. Druga linia pokazuje, jak inicjalizujemy funkcję do użycia rejestrów XMM, używając zestawu instrukcji sse2. Dzieje się tak, ponieważ paddb jest instrukcją sse2. Omówimy sse2 bardziej szczegółowo w następnej lekcji. + +```assembly +cglobal add_values, 2, 2, 2, src, src2 +``` + +To jest ważna linia, ponieważ definiuje funkcję C o nazwie „add_values”. + +Przejdźmy przez każdy element po kolei: + +* pierwszy parametr po cglobal to nazwa funkcji. +* następny parametr pokazuje, że ma dwa argumenty funkcji. +* parametr po nim pokazuje, że w tej funkcji użyjemy dwóch rejestrów GPR, w tym argumentów. W niektórych przypadkach możemy chcieć użyć więcej rejestrów GPR, więc musimy powiedzieć x86util, że potrzebujemy więcej. +* parametr po tym mówi x86util, ile rejestrów XMM zamierzamy użyć. +* dwa kolejne parametry to etykiety dla argumentów funkcji. + +Warto zauważyć, że starszy kod może nie mieć etykiet dla argumentów funkcji, ale zamiast tego bezpośrednio adresować rejestry GPR, używając r0, r1 itd. + +```assembly + movu m0, [srcq] + movu m1, [src2q] +``` + +movi jest skrótem od movdqu (move double quad unaligned). Alignement (wyrównanie) zostanie omówione w innej lekcji, ale na razie movu można traktować jako 128-bitowe przeniesienie z [srcq]. W przypadku mov nawiasy oznaczają, że adres w [srcq] jest dereferencjonowany, co jest równoważne z **src w C.* Jest to to, co nazywane jest załadowaniem (load). Należy zauważyć, że przyrostek „q” odnosi się do rozmiaru wskaźnika *(*tj. w C reprezentuje *sizeof(*src) == 8 na systemach 64-bitowych, a x86asm jest na tyle inteligentny, że używa 32-bitowego na systemach 32-bitowych), ale podstawowe ładowanie jest 128-bitowe. + +Zwróć uwagę, że nie odnosimy się do rejestrów wektorowych ich pełną nazwą, w tym przypadku xmm0, ale jako m0, przez formę abstrakcyjną. W przyszłych lekcjach zobaczysz, jak oznacza to, że możesz napisać napisać kod raz, w taki sposób, że będzie działał na wielu rozmiarach rejestrów SIMD. + +```assembly +paddb m0, m1 +``` + +paddb (czytaj to w myślach jako *p-add-b (p-dodaj-b*)) dodaje każdy bajt w każdym rejestrze, jak pokazano poniżej. Przedrostek „p” oznacza „packed” (spakowany) i służy do identyfikacji instrukcji wektorowych w przeciwieństwie do skalarnych. Przyrostek „b” pokazuje, że jest to bytewise addition (dodawanie bajtowe). + +| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +\+ + +| q | r | s | t | u | v | w | x | y | z | aa | ab | ac | ad | ae | af | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +\= + +| a+q | b+r | c+s | d+t | e+u | f+v | g+w | h+x | i+y | j+z | k+aa | l+ab | m+ac | n+ad | o+ae | p+af | +| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | + +```assembly +movu [srcq], m0 +``` + +Jest to tak zwana operacja store (zapisu). Dane są zapisywane z powrotem pod adres wskazany przez wskaźnik srcq. + +```assembly +RET +``` + +Jest to makro oznaczające powrót z funkcji. Praktycznie wszystkie funkcje asemblerowe w FFmpeg modyfikują dane w argumentach (w miejscu), zamiast zwracać konkretną wartość. + +Jak zobaczysz w zadaniu, tworzymy wskaźniki na funkcje asemblerowe i wykorzystujemy je tam, gdzie są one dostępne. + +[Następna lekcja](../lesson_02/index.pl.md) From 26b0f0f5b4068e71b987e98bac55b0968969582f Mon Sep 17 00:00:00 2001 From: Pawel Rzonca Date: Tue, 27 Jan 2026 15:45:12 +0100 Subject: [PATCH 3/4] adding translation of lesson 2 --- lesson_02/index.pl.md | 167 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 lesson_02/index.pl.md diff --git a/lesson_02/index.pl.md b/lesson_02/index.pl.md new file mode 100644 index 0000000..0379e74 --- /dev/null +++ b/lesson_02/index.pl.md @@ -0,0 +1,167 @@ +**Lekcja Druga Języka Asemblera FFmpeg** + +Teraz, gdy napisałeś swoją pierwszą funkcję w języku asemblera, wprowadzimy instrukcje skoków i pętle. + +Zaczniemy od wprowadzenia idei etykiet i skoków. W sztucznym przykładzie poniżej instrukcja jmp przenosi instrukcję kodu za „.loop:”. „.loop:” jest znane jako *etykieta* (ang. *label*), a kropka poprzedzająca etykietę oznacza, że jest to *lokalna etykieta*, co skutecznie pozwala na ponowne użycie tej samej nazwy etykiety w różnych funkcjach. Ten przykład, oczywiście, pokazuje nieskończoną pętlę, ale rozszerzymy to później na coś bardziej realistycznego. + +```assembly +mov r0q, 3 +.loop: + dec r0q + jmp .loop +``` + +Zanim stworzymy realistyczną pętlę, musimy wprowadzić rejestr *FLAGS* (z ang. FLAGI). Nie będziemy zbytnio zagłębiać się w zawiłości *FLAGS* (ponownie, ponieważ operacje na GPR są głównie szkieletem), ale istnieje kilka flag, takich jak Flaga Zerowa (Zero-Flag), Flaga Znaku (Sign-Flag) i Flaga Przepełnienia (Overflow-Flag), które są ustawiane na podstawie wyniku większości instrukcji innych niż mov na danych skalarnych, takich jak operacje arytmetyczne i przesunięcia. + +Tutaj jest przykład, w którym licznik pętli odlicza do zera, a jg (skok, jeśli większe niż zero) jest warunkiem pętli. dec r0q ustawia *FLAGS* na podstawie wartości r0q po instrukcji i można na ich podstawie wykonać skok. + +```assembly +mov r0q, 3 +.loop: + ; wnętrze pętli + dec r0q + jg .loop ; skok jeśli większe niż zero +``` +Jest to równoważne z następującym kodem w języku C: + +```c +int i = 3; +do +{ + // wnętrze pętli + i--; +} while(i > 0) +``` + +Powyższy kod w języku C jest nieco nietypowy. Zazwyczaj pętla w języku C jest zapisywana w ten sposób: + +```c +int i; +for(i = 0; i < 3; i++) { + // wnętrze pętli +} +``` + +Jest to mniej więcej równoważne z (nie ma prostego sposobu na dopasowanie tej pętli ```for```): + +```assembly +xor r0q, r0q +.loop: + ; wnętrze pętli + inc r0q + cmp r0q, 3 + jl .loop ; skok jeśli (r0q - 3) < 0, tzn. (r0q < 3) +``` + +W tym fragmencie jest kilka rzeczy, na które warto zwrócić uwagę. Po pierwsze, ```xor r0q, r0q``` jest powszechnym sposobem ustawiania rejestru na zero, który na niektórych systemach jest szybszy niż ```mov r0q, 0```, ponieważ, mówiąc prosto, nie dochodzi do faktycznego ładowania. Może być również używany na rejestrach SIMD z ```pxor m0, m0```, aby wyzerować cały rejestr. Następna rzecz to użycie cmp. cmp efektywnie odejmuje drugi rejestr od pierwszego (bez przechowywania wartości gdziekolwiek) i ustawia *FLAGS*, ale jak wynika z komentarza, można go czytać razem ze skokiem (jl = skok jeśli mniej niż zero), aby wykonać skok, jeśli ```r0q < 3```. + +Zauważ, że w tym fragmencie jest jedna dodatkowa instrukcja (cmp). Ogólnie rzecz biorąc, mniej instrukcji oznacza szybszy kod, dlatego preferowany jest wcześniejszy fragment. Jak zobaczysz w przyszłych lekcjach, istnieją kolejne sztuczki używane do unikania tej dodatkowej instrukcji i ustawiania *FLAGS* przez operacje arytmetyczne lub inne. Zauważ, że nie piszemy asemblera, aby dokładnie dopasować pętle w języku C, piszemy pętle tak, aby były jak najszybsze w asemblerze. + +Poniżej znajdują się niektóre popularne mnemotechniki skoków, których będziesz używać (flagi są podane dla kompletności, ale nie musisz znać szczegółów, aby pisać pętle): + +| Mnemonika | Opis | FLAGS | +| :---- | :---- | :---- | +| JE/JZ | Jump if Equal/Zero (skok jeśli równe/zero) | ZF = 1 | +| JNE/JNZ | Jump if Not Equal/Not Zero (skok jeśli nierówne/nie zero) | ZF = 0 | +| JG/JNLE | Jump if Greater/Not Less or Equal (signed) (skok jeśli większe/nie mniejsze lub równe, znakowane) | ZF = 0 and SF = OF | +| JGE/JNL | Jump if Greater or Equal/Not Less (signed) (skok jeśli większe lub równe/nie mniejsze, znakowane) | SF = OF | +| JL/JNGE | Jump if Less/Not Greater or Equal (signed) (skok jeśli mniejsze/nie większe lub równe, znakowane) | SF ≠ OF | +| JLE/JNG | Jump if Less or Equal/Not Greater (signed) (skok jeśli mniejsze lub równe/nie większe, znakowane) | ZF = 1 or SF ≠ OF | + +**Stałe (ang. Constants)** + +Spójrzmy na kilka przykładów pokazujących, jak używać stałych: + +```assembly +SECTION_RODATA + +constants_1: db 1,2,3,4 +constants_2: times 2 dw 4,3,2,1 +``` + +* SECTION_RODATA określa, że jest to sekcja danych tylko do odczytu. (Jest to makro, ponieważ różne formaty plików wyjściowych używane przez systemy operacyjne deklarują to inaczej) +* constants_1: Etykieta constants_1 jest zdefiniowana jako ```db``` (declare byte - deklaruj bajt) - tzn. równoważne z uint8_t constants_1[4] = {1, 2, 3, 4}; +* constants_2: To używa makra ```times 2```, aby powtórzyć zadeklarowane słowa - tzn. równoważne z uint16_t constants_2[8] = {4, 3, 2, 1, 4, 3, 2, 1}; + +Etykiety te, które asembler konwertuje na adres pamięci, mogą być następnie używane w operacjach ładowania (ale nie zapisywania, ponieważ są tylko do odczytu). Niektóre instrukcje przyjmują adres pamięci jako operand, więc mogą być używane bezpośrednio bez jawnego ładowania do rejestru (ma to swoje zalety i wady). + +**Offsety (ang. Offsets)** + +Offsety są odległością (w bajtach) między kolejnymi elementami w pamięci. Offset jest określany przez **rozmiar każdego elementu** w strukturze danych. + +Teraz, gdy potrafimy pisać pętle, czas pobrać dane. Ale istnieją pewne różnice w porównaniu z językiem C. Spójrzmy na następującą pętlę w języku C: + +```c +uint32_t data[3]; +int i; +for(i = 0; i < 3; i++) { + data[i]; +} +``` + +Offset 4-bajtowy między elementami danych jest wstępnie obliczany przez kompilator języka C. Jednak podczas ręcznego pisania asemblera musisz samodzielnie obliczać te offsety. + +Zobaczmy składnię obliczeń adresów pamięci. Ma to zastosowanie do wszystkich typów adresów pamięci: + +```assembly +[base + scale*index + disp] +``` + +* base - baza, jest to GPR (zazwyczaj wskaźnik z argumentu funkcji C) +* scale - skala, może być równa 1, 2, 4, 8. Domyślnie jest to 1 +* index - indeks, jest to GPR (zazwyczaj licznik pętli) +* disp - przesunięcie jest liczbą całkowitą (do 32-bitów). Przesunięcie jest offsetem do danych + +x86asm zapewnia stałą mmsize, która informuje o rozmiarze rejestru SIMD, z którym pracujesz. + +Tutaj jest prosty (i bezsensowny) przykład ilustrujący ładowanie z niestandardowych offsetów: + +```assembly +;static void simple_loop(const uint8_t *src) +INIT_XMM sse2 +cglobal simple_loop, 1, 2, 2, src + movq r1q, 3 +.loop: + movu m0, [srcq] + movu m1, [srcq+2*r1q+3+mmsize] + + ; tutaj można coś zrobić + + add srcq, mmsize +dec r1q +jg .loop + +RET +``` + +Zauważ, jak w ```movu m1, [srcq+2*r1q+3+mmsize]``` asembler wstępnie obliczy odpowiednią stałą przesunięcia do użycia. W następnej lekcji pokażemy Ci sztuczkę, aby uniknąć konieczności wykonywania add i dec w pętli, zastępując je pojedynczym add. + +**LEA** + +Teraz, gdy rozumiesz offsety, możesz używać lea (Load Effective Address - załaduj efektywny adres). Pozwala to na wykonywanie mnożenia i dodawania za pomocą jednej instrukcji, co będzie szybsze niż używanie wielu instrukcji. Oczywiście istnieją ograniczenia dotyczące tego, przez co można mnożyć i co dodawać, ale nie przeszkadza to w byciu potężną instrukcją. + +```assembly +lea r0q, [base + scale*index + disp] +``` + +Pomimo swojej nazwy, LEA może być używane zarówno do normalnej arytmetyki, jak i do obliczeń adresów. Możesz zrobić coś tak skomplikowanego jak: + +```assembly +lea r0q, [r1q + 8*r2q + 5] +``` + +Zauważ, że nie wpływa to na zawartość r1q i r2q. Nie wpływa to również na *FLAGS* (więc nie możesz wykonywać skoków na podstawie wyniku). Użycie LEA unika wszystkich tych instrukcji i tymczasowych rejestrów (ten kod nie jest równoważny, ponieważ add zmienia *FLAGS*): + +```assembly +movq r0q, r1q +movq r3q, r2q +sal r3q, 3 ; shift arithmetic left 3 - przesunięcie w lewo o 3 = * 8 +add r3q, 5 +add r0q, r3q +``` + +Zobaczysz, że lea jest często używane do ustawiania adresów przed pętlami lub wykonywania obliczeń jak powyżej. Oczywiście pamiętaj, że nie możesz wykonywać wszystkich rodzajów mnożenia i dodawania, ale mnożenia przez 1, 2, 4, 8 oraz dodawanie stałego przesunięcia jest powszechne. + +W następnym zadaniu będziesz musiał załadować stałą i dodać wartości do wektora SIMD w pętli. + +[Następna lekcja](../lesson_03/index.pl.md) From 5a403076e4cd260816d4db7f16a1785fa25c5376 Mon Sep 17 00:00:00 2001 From: Pawel Rzonca Date: Fri, 30 Jan 2026 21:52:27 +0100 Subject: [PATCH 4/4] adding lesson 3 --- README.es.md | 2 +- README.fr.md | 2 +- README.md | 2 +- lesson_03/index.pl.md | 200 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 lesson_03/index.pl.md diff --git a/README.es.md b/README.es.md index 8071098..eefe363 100644 --- a/README.es.md +++ b/README.es.md @@ -16,4 +16,4 @@ https://discord.com/invite/Ks5MhUhqfB * [English](./README.md) * [Français](./README.fr.md) -* [Polish](./README.pl.md) +* [Polski](./README.pl.md) diff --git a/README.fr.md b/README.fr.md index 6a7a103..b692acb 100644 --- a/README.fr.md +++ b/README.fr.md @@ -16,4 +16,4 @@ https://discord.com/invite/Ks5MhUhqfB * [English](./README.md) * [Spanish](./README.es.md) -* [Polish](./README.pl.md) +* [Polski](./README.pl.md) diff --git a/README.md b/README.md index 6469005..2b6b28e 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,4 @@ https://discord.com/invite/Ks5MhUhqfB * [Français](./README.fr.md) * [Spanish](./README.es.md) -* [Polish](./README.pl.md) +* [Polski](./README.pl.md) diff --git a/lesson_03/index.pl.md b/lesson_03/index.pl.md new file mode 100644 index 0000000..eccefca --- /dev/null +++ b/lesson_03/index.pl.md @@ -0,0 +1,200 @@ +**Lekcja Trzecia Języka Asemblera FFmpeg** + +Wyjaśnimy teraz trochę więcej żargonu i przedstawimy krótką lekcję historii. + +**Zestawy instrukcji** + +Być może zauważyłeś w poprzedniej lekcji, że mówiliśmy o SSE2, które jest zestawem instrukcji SIMD. Gdy wydawana jest nowa generacja procesorów, może ona zawierać nowe instrukcje, a czasami większe rozmiary rejestrów. Historia zestawu instrukcji x86 jest bardzo skomplikowana, więc oto uproszczona historia (istnieje o wiele więcej podkategorii): + +* MMX - Wydany w 1997 roku, pierwsze SIMD w procesorach Intela, 64-bitowe rejestry, historyczny +* SSE (Streaming SIMD Extensions) - Wydany w 1999 roku, 128-bitowe rejestry +* SSE2 - Wydany w 2000 roku, wiele nowych instrukcji +* SSE3 - Wydany w 2004 roku, pierwsze instrukcje poziome +* SSSE3 (Supplemental SSE3) - Wydany w 2006 roku, nowe instrukcje, ale co najważniejsze instrukcja pshufb shuffle, prawdopodobnie najważniejsza instrukcja w przetwarzaniu wideo +* SSE4 - Wydany w 2008 roku, wiele nowych instrukcji, w tym spakowane minimum i maksimum. +* AVX - Wydany w 2011 roku, 256-bitowe rejestry (tylko float) i nowa składnia trzech operandów +* AVX2 - Wydany w 2013 roku, 256-bitowe rejestry dla instrukcji całkowitoliczbowych +* AVX512 - Wydany w 2017 roku, 512-bitowe rejestry, nowa funkcja maski operacji. Miały ograniczone zastosowanie w FFmpeg ze względu na obniżanie częstotliwości procesora podczas używania nowych instrukcji. Pełne 512-bitowe tasowanie (permute) z vpermb. +* AVX512ICL - Wydany w 2019 roku, brak obniżania częstotliwości zegara. +* AVX10 - Nadchodzący + +Warto zauważyć, że zestawy instrukcji mogą być również usuwane z procesorów, a nie tylko dodawane. Na przykład AVX512 został [usunięty](https://www.igorslab.de/en/intel-deactivated-avx-512-on-alder-lake-but-fully-questionable-interpretation-of-efficiency-news-editorial/), kontrowersyjnie, w 12. generacji procesorów Intela. To właśnie z tego powodu FFmpeg wykonuje wykrywanie CPU w czasie działania. FFmpeg wykrywa możliwości procesora, na którym jest uruchamiany. + +Jak widziałeś w zadaniu, wskaźniki do funkcji są domyślnie w C i są zastępowane wariantem zestawu instrukcji. Oznacza to, że wykrywanie jest wykonywane raz i potem nie musi być już wykonywane ponownie. Jest to w przeciwieństwie do wielu własnościowych aplikacji, które na stałe kodują konkretny zestaw instrukcji, czyniąc w pełni funkcjonalny komputer przestarzałym. Pozwala to również na włączanie/wyłączanie zoptymalizowanych funkcji w czasie działania. To jest jedna z dużych zalet otwartego oprogramowania. + +Programy takie jak FFmpeg są używane na miliardach urządzeń na całym świecie, z których niektóre mogą być bardzo stare. FFmpeg technicznie wspiera maszyny obsługujące tylko SSE, które mają 25 lat! Na szczęście x86inc.asm potrafi powiedzieć, jeśli używasz instrukcji, która nie jest dostępna w danym zestawie instrukcji. +Aby dać Ci wyobrażenie o rzeczywistych możliwościach, oto dostępność zestawów instrukcji z [Steam Survey](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam) na listopad 2024 (oczywiście jest to stronnicze wobec graczy): + +| Zestaw instrukcji | Dostępność | +| :---- | :---- | +| SSE2 | 100% | +| SSE3 | 100% | +| SSSE3 | 99.86% | +| SSE4.1 | 99.80% | +| AVX | 97.39% | +| AVX2 | 94.44% | +| AVX512 (Steam does not separate between AVX512 and AVX512ICL) | 14.09% | + +Dla aplikacji takiej jak FFmpeg, która ma miliardy użytkowników, nawet 0,1% to bardzo duża liczba użytkowników i zgłoszeń błędów, jeśli coś przestanie działać. FFmpeg posiada rozbudowaną infrastrukturę testową do testowania wariantów CPU/OS/Compiler w naszym [FATE testsuite](https://fate.ffmpeg.org/?query=subarch:x86_64%2F%2F). Każdy pojedynczy commit jest uruchamiany na setkach maszyn, aby upewnić się, że nic nie przestaje działać. + +Intel zapewnia szczegółowy podręcznik zestawu instrukcji tutaj: [https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) + +Przeszukiwanie pliku PDF może być uciążliwe, dlatego istnieje nieoficjalna alternatywa internetowa tutaj: [https://www.felixcloutier.com/x86/](https://www.felixcloutier.com/x86/) + +Istnieje również wizualna reprezentacja instrukcji SIMD dostępna tutaj: +[https://www.officedaytime.com/simd512e/](https://www.officedaytime.com/simd512e/) + +Częścią wyzwania asemblera x86 jest znalezienie odpowiedniej instrukcji do swoich potrzeb. W niektórych przypadkach instrukcje mogą być używane w sposób, do którego pierwotnie nie były przeznaczone. + +**Sztuczki z przesunięciem wskaźnika** + +Wróćmy do naszej oryginalnej funkcji z Lekcji 1, ale dodajmy argument szerokości (ang. width) do funkcji C. + +Zamiast używać int dla zmiennej width, używamy ptrdiff_t, aby upewnić się, że górne 32 bity 64-bitowego argumentu są zerowe. Gdybyśmy bezpośrednio przekazali int width w sygnaturze funkcji, a następnie próbowali użyć go jako quad do arytmetyki wskaźników (np. używając `widthq`), górne 32 bity rejestru mogą być wypełnione dowolnymi wartościami. Moglibyśmy to naprawić przez rozszerzenie znaku width za pomocą `movsxd` (zobacz także makro `movsxdifnidn` w x86inc.asm), ale to jest łatwiejszy sposób. + +Poniższa funkcja zawiera sztuczki z przesunięciem wskaźnika: + +```assembly +;static void add_values(uint8_t *src, const uint8_t *src2, ptrdiff_t width) +INIT_XMM sse2 +cglobal add_values, 3, 3, 2, src, src2, width + add srcq, widthq + add src2q, widthq + neg widthq + +.loop + movu m0, [srcq+widthq] + movu m1, [src2q+widthq] + + paddb m0, m1 + + movu [srcq+widthq], m0 + add widthq, mmsize + jl .loop + + RET +``` + +Przejdźmy przez to krok po kroku, ponieważ może to być mylące: + +```assembly + add srcq, widthq + add src2q, widthq + neg widthq +``` + +Szerokość jest dodawana do każdego wskaźnika, tak że każdy wskaźnik teraz wskazuje na koniec bufora do przetworzenia. Następnie szerokość jest negowana. + +```assembly + movu m0, [srcq+widthq] + movu m1, [src2q+widthq] +``` + +Ładowania są następnie wykonywane z widthq będącym ujemnym. Tak więc w pierwszej iteracji [srcq+widthq] wskazuje na oryginalny adres srcq, czyli wskazuje z powrotem na początek bufora. + +```assembly + add widthq, mmsize + jl .loop +``` + +mmsize jest dodawany do ujemnego widthq, co przybliża go do zera. Warunek pętli to teraz jl (skok, jeśli mniejszy niż zero). Ta sztuczka oznacza, że widthq jest używany jednocześnie jako przesunięcie wskaźnika **oraz** jako licznik pętli, co oszczędza instrukcję cmp. Pozwala to również na użycie przesunięcia wskaźnika w wielu operacjach ładowania i zapisu, a także na użycie wielokrotności przesunięć wskaźnika, jeśli to konieczne (pamiętaj o tym przy zadaniu). + +**Wyrównanie** + +We wszystkich naszych przykładach używaliśmy movu, aby uniknąć tematu wyrównania. Wiele procesorów może ładować i zapisywać dane szybciej, jeśli dane są wyrównane, tzn. jeśli adres pamięci jest podzielny przez rozmiar rejestru SIMD. Tam, gdzie to możliwe, staramy się używać wyrównanych ładowań i zapisów w FFmpeg za pomocą mova. + +In FFmpeg, av_malloc is able to provide aligned memory on the heap and the DECLARE_ALIGNED C preprocessor directive can provide aligned memory on the stack. If mova is used with an unaligned address, it will cause a segmentation fault and the application will crash. It’s also important to be sure that the alignment value corresponds to the SIMD register size, i.e 16 with xmm, 32 for ymm and 64 for zmm. + +Oto jak wyrównać początek sekcji RODATA do 64 bajtów: + +```assembly +SECTION_RODATA 64 +``` + +Zauważ, że to tylko wyrównuje początek RODATA. Może być potrzebne dodanie bajtów wypełniających, aby upewnić się, że następna etykieta pozostaje na granicy 64 bajtów. + +**Rozszerzanie zakresu** + +Kolejnym tematem, którego do tej pory unikaliśmy, jest przepełnienie. Dzieje się tak na przykład, gdy wartość bajtu przekracza 255 po operacji takiej jak dodawanie lub mnożenie. Możemy chcieć wykonać operację, w której potrzebujemy wartości pośredniej większej niż bajt (np. słowa), lub potencjalnie chcemy pozostawić dane w tym większym rozmiarze pośrednim. + +Dla bajtów bez znaku przychodzą z pomocą instrukcje punpcklbw (packed unpack low bytes to words) i punpckhbw (packed unpack high bytes to words). + +Zobaczmy, jak działa punpcklbw. Składnia dla wersji SSE2 z podręcznika Intela jest następująca: + +| PUNPCKLBW xmm1, xmm2/m128 | +| :---- | + +Oznacza to, że jego źródło (prawa strona) może być rejestrem xmm lub adresem pamięci (m128 oznacza adres pamięci ze standardową składnią [base + scale*index + disp]), a miejsce docelowe to rejestr xmm. + +Strona officedaytime.com powyżej ma dobry diagram pokazujący, co się dzieje: + +![Co to jest](image1.png) + +Widać, że bajty są przeplatane z dolnej połowy każdego rejestru odpowiednio. Ale co to ma wspólnego z rozszerzaniem zakresu? Jeśli rejestr src jest wypełniony zerami, to przeplata bajty w dst z zerami. To jest to, co nazywa się *zerowym rozszerzeniem*, ponieważ bajty są bez znaku. punpckhbw może być użyty do zrobienia tego samego dla wysokich bajtów. + +Oto fragment pokazujący, jak to się robi: + +```assembly +pxor m2, m2 ; zero out m2 + +movu m0, [srcq] +movu m1, m0 ; make a copy of m0 in m1 +punpcklbw m0, m2 +punpckhbw m1, m2 +``` + +```m0``` oraz ```m1``` teraz zawierają oryginalne bajty rozszerzone zerowo do słów. W następnej lekcji zobaczysz, jak instrukcje trzy-operandowe w AVX sprawiają, że drugi movu jest niepotrzebny. + +**Rozszerzanie znaku** + +Dane ze znakiem są nieco bardziej skomplikowane. Aby rozszerzyć zakres liczby całkowitej ze znakiem, musimy użyć procesu znanego jako [rozszerzanie znaku](https://pl.wikipedia.org/wiki/Rozszerzenie_znaku_liczby_binarnej). Polega to na wypełnieniu najbardziej znaczących bitów bitem znaku. Na przykład: -2 w int8_t to 0b11111110. Aby rozszerzyć znak do int16_t, MSB o wartości 1 jest powtarzany, tworząc 0b1111111111111110. +```pcmpgtb``` (packed compare greater than byte) może być użyty do rozszerzania znaku. Poprzez wykonanie porównania (0 > bajt), wszystkie bity w docelowym bajcie są ustawiane na 1, jeśli bajt jest ujemny, w przeciwnym razie bity w docelowym bajcie są ustawiane na 0. punpckX może być użyty jak powyżej do wykonania rozszerzenia znaku. Jeśli bajt jest ujemny, odpowiadający bajt to 0b11111111, w przeciwnym razie to 0x00000000. Przeplatanie wartości bajtu z wynikiem pcmpgtb wykonuje rozszerzenie znaku do słowa jako wynik. + +```assembly +pxor m2, m2 ; wyzeruj m2 + +movu m0, [srcq] +movu m1, m0 ; zrób kopię m0 w m1 + +pcmpgtb m2, m0 +punpcklbw m0, m2 +punpckhbw m1, m2 +``` + +Jak widać, jest dodatkowa instrukcja w porównaniu do przypadku bez znaku. + +**Pakowanie (ang. packing)** + +packuswb (pack unsigned word to byte) oraz packsswb pozwalają przejść od słowa do bajtu. Pozwalają one przeplatać dwa rejestry SIMD zawierające słowa w jeden rejestr SIMD z bajtem. Należy zauważyć, że jeśli wartości przekraczają zakres bajtu, zostaną one nasycone (tzn. ograniczone do największej wartości). + +**Tasowanie (ang. shuffle)** + +Tasowanie, znane również jako permutacje, są prawdopodobnie najważniejszą instrukcją w przetwarzaniu wideo, a pshufb (packed shuffle bytes), dostępne w SSSE3, jest najważniejszym wariantem. + +Dla każdego bajtu odpowiadający bajt źródłowy jest używany jako indeks rejestru docelowego, z wyjątkiem sytuacji, gdy ustawiony jest MSB, wtedy bajt docelowy jest zerowany. Jest to analogiczne do następującego kodu w C (chociaż w SIMD wszystkie 16 iteracji pętli odbywają się równolegle): + +```c +for(int i = 0; i < 16; i++) { + if(src[i] & 0x80) + dst[i] = 0; + else + dst[i] = dst[src[i]] +} +``` +Tutaj znajduje się prosty przykład w asemblerze: + +```assembly +SECTION_DATA 64 + +shuffle_mask: db 4, 3, 1, 2, -1, 2, 3, 7, 5, 4, 3, 8, 12, 13, 15, -1 + +section .text + +movu m0, [srcq] +movu m1, [shuffle_mask] +pshufb m0, m1 ; Tasowanie m0 na podstawie m1 +``` + +Zauważ, że użycie -1 dla czytelności jako indeksu tasowania (shuffle) zeruje bajt wyjściowy: -1 jako bajt to 0b11111111 (uzupełnienie do dwóch), więc ustawiony jest bit MSB (0x80). + +[image1]: \ No newline at end of file