Historia kart graficznych: od prostych akceleratorów 2D do mocy centrów danych AI

0
66
5/5 - (2 votes)

Nawigacja:

Od terminali tekstowych do pierwszych kontrolerów grafiki

Era terminali tekstowych i znaków półgraficznych

Historia kart graficznych nie zaczyna się od kolorowych pulpitów Windows ani nawet od VGA. Pierwszy etap to światy, w których „grafika” oznaczała wyłącznie tekst. Komputery pracowały z terminalami tekstowymi: monitor wyświetlał znaki z ustalonej tablicy (fontu sprzętowego), a procesor jedynie wpisywał kody znaków do pamięci ekranu. Żadnych pikseli, żadnych bitmap – tylko siatka kolumn i wierszy, np. 80×25 znaków.

Aby jakoś obejść ograniczenia, używano tzw. znaków półgraficznych: bloków, linii poziomych i pionowych, rogów ramek. Dzięki nim dało się „narysować” pseudo-okna, wykresy, proste interfejsy, ale nadal były to znaki z ustalonego zestawu. System operacyjny lub aplikacja operowały na poziomie znaków, a nie pojedynczych pikseli.

W tym modelu procesor główny (CPU) odpowiadał za całą logikę, a „karta graficzna” była w praktyce prostym kontrolerem wyświetlacza. Nie było pojęcia akceleracji, bo nie było prawdziwych operacji graficznych. CPU kopiował liczby do pamięci ekranu, a układ wideo tylko odświeżał sygnał na monitorze.

Pierwsze kontrolery wideo: CGA, Hercules, EGA

Na komputerach zgodnych z IBM PC pojawiły się pierwsze karty wideo, które znamy z nazw: CGA, Hercules, EGA. Z dzisiejszej perspektywy to prymitywne układy, ale wprowadziły kluczowe pojęcia: tryby graficzne, rozdzielczość, paletę kolorów.

CGA (Color Graphics Adapter) zapewniała zarówno tryb tekstowy, jak i graficzny. W trybie graficznym obsługiwała 320×200 pikseli w 4 kolorach z ustalonej palety lub 640×200 w dwóch kolorach. Jakość była niska, kolory wyblakłe, a artefakty na monitorach kompozytowych tworzą dziś mityczną „estetykę CGA”.

Hercules Graphics Card (HGC) poszła w inną stronę: wysoka rozdzielczość monochromatyczna 720×348 pikseli. Dla zastosowań biurowych, CAD czy edycji tekstu szczegółowość była ważniejsza niż kolor. W praktyce wiele osób z tamtych czasów pozytywnie wspomina ostrość obrazu Herculesa, mimo braku barw.

EGA (Enhanced Graphics Adapter) próbowała połączyć światy CGA i Herculesa. Oferowała do 16 kolorów z szerszej palety przy wyższej rozdzielczości, co było krokiem w stronę tego, co kojarzymy z „prawdziwą grafiką”, ale nadal było dalekie od współczesnych standardów.

Framebuffer, rozdzielczość i paleta barw w realiach lat 80.

Kluczowe pojęcie, które przewija się przez całą historię kart graficznych, to framebuffer – obszar pamięci, gdzie zapisany jest obraz do wyświetlenia. W prostym ujęciu: każdy piksel to informacje o kolorze, które muszą być przechowywane w pamięci i cyklicznie odczytywane do generowania sygnału wideo.

Nawet przy rozdzielczości rzędu 640×480 i 16 kolorach ilość potrzebnej pamięci była znacząca jak na ówczesne czasy. Typowe komputery miały po kilkadziesiąt – kilkaset kilobajtów RAM, a pamięć wideo konkurowała z pamięcią systemową o koszt i przepustowość. Dlatego stosowano tryby z małą liczbą kolorów i rozdzielczościami, które dziś wyglądają archaicznie.

Paleta barw była ograniczona przez sposób kodowania koloru. CGA pozwalała na 4 kolory z zaledwie kilkunastu możliwych kombinacji. Dopiero później pojawiły się palety 256 kolorów wybieranych z przestrzeni 262 144 barw (tzw. 18-bitowy DAC), a jeszcze później – prawdziwy kolor 16,7 mln barw (24 bity na piksel). Dla programistów oznaczało to ciągłą konieczność oszczędzania: mniejsze sprite’y, prostsze tła, palety dobierane jak najrozsądniej.

Ograniczenia: przepustowość, brak akceleracji, wszystko na barkach CPU

Wczesne kontrolery wideo nie przyspieszały obliczeń. Procesor musiał samodzielnie liczyć każdy piksel, każdą linię, każdą literę. Karta tylko tę pamięć odczytywała i zamieniała na sygnał dla monitora. To podejście miało dwa poważne ograniczenia:

  • Przepustowość magistrali – CPU mógł zapisywać do pamięci wideo tylko z określoną prędkością. Przy wyższych rozdzielczościach i większej liczbie klatek na sekundę trzeba było kopiować ogromne ilości danych, co szybko blokowało system.
  • Zużycie czasu CPU – każda operacja graficzna (rysowanie linii, wypełnienie prostokąta) wymagała pętli w kodzie CPU. Im bardziej wyrafinowany interfejs, tym więcej czasu procesora poświęconego na grafikę, kosztem logiki aplikacji.

Stąd dylematy z końca lat 80. i początku 90.: czy lepiej mieć rozbudowany, graficzny interfejs, czy jednak pozostać przy tekstowym, ale szybkim systemie. Dopiero pojawienie się akceleratorów 2D zaczęło zmieniać reguły gry.

Mit idealizowanej „grafiki retro”

W opisach historycznych łatwo trafić na idealizowanie obrazu na Amidze, Atari ST czy pierwszych kartach VGA. Faktycznie, na tle terminali tekstowych przeskok był ogromny: kolorowe gry, animacje, interfejsy okienkowe. Jednak trzeba oddzielić nostalgię od realnych parametrów.

Wiele „pięknych” gier z tamtego okresu działało w bardzo niskich rozdzielczościach, często z prostą animacją i mocną kompresją grafiki. Monitory CRT maskowały piksele i rozmywały obraz, co pomagało w odbiorze. Z dzisiejszego punktu widzenia jakość obrazu była słaba, a możliwości kart graficznych – dramatycznie ograniczone w porównaniu z współczesnymi nawet zintegrowanymi układami.

Zbliżenie starej płytki drukowanej komputera na drewnianym tle
Źródło: Pexels | Autor: Nicolas Foster

Narodziny akceleratorów 2D i ery VGA

Przejście od „wyświetlaczy” do akceleratorów grafiki 2D

Kolejny krok w historii kart graficznych to transformacja z biernego urządzenia wyświetlającego obraz w aktywny akcelerator 2D. Wraz z popularyzacją systemów okienkowych (Windows, OS/2, X11) pojawiła się potrzeba szybkiego rysowania prostokątów, ikon, czcionek i przewijania okien. CPU nie nadążało przy rosnących rozdzielczościach i coraz bogatszych interfejsach.

Rozwiązaniem było przeniesienie części zadań na kartę graficzną. Pojawiły się układy z wbudowanym blitterem (Block Image Transfer) i logiką do wykonywania operacji graficznych bezpośrednio w pamięci wideo. Zamiast kopiować miliony bajtów, CPU wysyłał do karty krótkie komendy: „skopiuj ten prostokąt tu”, „wypełnij to tło”, „narysuj linię”.

To podejście nie tylko przyspieszało operacje, ale też odciążało magistralę systemową. Mniej danych musiało przepływać między pamięcią główną a pamięcią wideo, bo wiele operacji działo się lokalnie na karcie. Był to pierwszy krok w stronę myślenia o karcie graficznej jako o współprocesorze.

VGA i SVGA: standaryzacja rozdzielczości i kolorów

VGA (Video Graphics Array) wprowadziła standard, który na długie lata ustawił minimalne oczekiwania co do grafiki na PC. Tryb 640×480 w 16 kolorach dla zastosowań biurowych i 320×200 w 256 kolorach dla gier stał się de facto normą. To właśnie „256 kolorów” pozwoliło na zupełnie inną estetykę gier: płynne gradienty, bogate tła, szczegółowe sprite’y.

Wkrótce potem przyszły rozszerzenia określane ogólnie jako SVGA (Super VGA). Różni producenci dodawali własne tryby graficzne: 800×600, 1024×768 i wyższe, często wciąż z ograniczeniem do 256 kolorów. Aby skorzystać z nich w DOS, gry ładowały specjalne sterowniki lub korzystały ze standardu VESA BIOS Extensions (VBE).

W praktyce użytkownik rzadko miał pełny komfort. To, co było „standardem” na papierze, często różniło się w detalach implementacyjnych między producentami, wymagało specyficznych sterowników, a wydajność bywała nierówna. Marketing obiecywał „wysoką rozdzielczość SVGA”, a rzeczywistość potrafiła sprowadzić wszystko do kompromisów.

Najważniejsi gracze: S3, Cirrus Logic, Tseng Labs, ATI, Matrox

W epoce akceleratorów 2D rynek kart graficznych był znacznie bardziej zróżnicowany niż dzisiaj. Kilku producentów zyskało rozpoznawalność dzięki różnym priorytetom projektowym:

  • S3 – bardzo popularne układy, często wybierane przez producentów komputerów OEM. Słynne serie Trio i Virge (początkowo 2D) oferowały dobrą kompatybilność i przyzwoitą wydajność 2D przy umiarkowanym koszcie.
  • Cirrus Logic – układy znane z tanich, poprawnych kart 2D, często spotykane w laptopach i tanich komputerach biurowych. Mocno wykorzystywane w środowiskach DOS i Windows 3.x.
  • Tseng Labs – cenione za wysoką wydajność w DOS, szczególnie w grach i aplikacjach tekstowych. Użytkownicy zwracali uwagę na szybkość przewijania i ogólną responsywność.
  • ATI – początkowo również producent układów 2D, budował markę na dobrej jakości obrazu i sukcesywnie rozwijał funkcje multimedialne (akceleracja MPEG, wyjścia TV).
  • Matrox – słynął z wyśmienitej jakości obrazu 2D, szczególnie w wysokich rozdzielczościach i na monitorach CRT. Dla grafików DTP Matrox był często pierwszym wyborem.

Te różnice widać do dziś w opiniach „starej gwardii”: jedni chwalą szybkość Tseng Labs, inni jakość obrazu Matroxa, jeszcze inni wspominają S3 jako „złoty środek”. W praktyce były to nyuanse, ale pokazują, że już w epoce 2D istniały odmienne filozofie projektowania kart graficznych.

Typowe zadania akceleratorów 2D

Akceleratory 2D koncentrowały się na kilku powtarzalnych operacjach, które dominowały w systemach okienkowych i graficznych interfejsach użytkownika. Najważniejsze z nich to:

  • BitBLT (Bit Block Transfer) – szybkie kopiowanie bloków pamięci (prostokątów pikseli) w obrębie VRAM, np. podczas przewijania okna lub przesuwania widgetu.
  • Rysowanie linii i kształtów – sprzętowe wsparcie dla kresek, prostokątów, czasem z wypełnieniem, co przyspieszało rysowanie ramek, menu, przycisków.
  • Operacje na czcionkach – przyspieszanie rysowania tekstu przez kopiowanie gotowych bitmap znaków bezpośrednio do bufora ekranu.
  • Operacje logiczne na pikselach – proste funkcje (AND, OR, XOR) przy kopiowaniu, umożliwiające np. implementację kursora myszy bez nadpisywania tła.

Dla użytkownika końcowego objawiało się to płynniejszym przewijaniem dokumentów, szybciej reagującymi oknami i mniejszą ilością „migotania” interfejsu. Przy pracy biurowej różnice były wyraźnie odczuwalne, choć mało kto rozumiał, że stoi za tym akceleracja 2D.

Dlaczego akceleracja 2D okazała się ślepą uliczką

W połowie lat 90. akceleracja 2D wydawała się naturalnym kierunkiem rozwoju. Jednak wiele z tych wyspecjalizowanych funkcji okazało się trudnych do dalszej rozbudowy, gdy na scenę zaczęła wchodzić grafika 3D. Twardo zakodowane jednostki do rysowania linii i prostokątów nie nadawały się do nowych zadań, takich jak rasteryzacja trójkątów czy zaawansowane teksturowanie.

Przy przejściu do środowiska Windows i API graficznego GDI wiele operacji mogło pozostać „po stronie CPU”, korzystając z coraz szybszych procesorów. Wraz z nadejściem sprzętowo akcelerowanej grafiki 3D i odciążeniem CPU w tym obszarze zapotrzebowanie na ekstremalnie wydajne 2D stopniowo malało.

W efekcie wiele specjalistycznych funkcji 2D albo zostało uproszczonych, albo w nowszych kartach po prostu zanikało. Jednocześnie jednak doświadczenia z akceleracją 2D – zarządzanie pamięcią, komendy wysokiego poziomu, integracja ze sterownikami systemu – przygotowały grunt pod znacznie bardziej złożone akceleratory 3D.

Pierwsze podejścia do grafiki 3D na PC

Grafika 3D realizowana programowo: Doom, Quake i spółka

Zanim na rynek trafiły karty pokroju 3Dfx Voodoo, grafika 3D na PC była realizowana niemal wyłącznie programowo, czyli przez CPU. Gry takie jak Wolfenstein 3D i Doom stosowały sprytne sztuczki (tzw. „silnik pseudo-3D”), aby budować wrażenie trójwymiaru przy relatywnie niewielkim koszcie obliczeniowym. Prawdziwe 3D z pełną swobodą kamery zaczęło się rozpowszechniać dopiero z Quake i kolejnymi tytułami.

Ograniczenia renderowania programowego

Renderowanie 3D wyłącznie na CPU szybko natrafiło na twarde limity. Nawet bardzo sprytne silniki, takie jak ten użyty w Doomie, opierały się na szeregu założeń upraszczających scenę: brak pełnej swobody patrzenia w górę i dół, ograniczenia w geometrii (brak „pokoju nad pokojem”), ruchome obiekty traktowane często jak billboardy 2D. Dzięki temu liczba operacji na klatkę pozostawała względnie przewidywalna.

Gdy twórcy gier zaczęli iść w stronę prawdziwego 3D, pojawiły się kosztowne etapy: transformacja współrzędnych wierzchołków, obliczanie oświetlenia, cieniowanie pikseli, teksturowanie, a do tego zarządzanie złożonymi scenami. CPU musiało dzielić te zadania z logiką gry, fizyką, AI, dźwiękiem. Efekt był prosty: aby zachować płynność, trzeba było ciąć jakość grafiki lub komplikację świata.

W tym czasie powstawały też wyspecjalizowane biblioteki programowe, jak 3D-API od Rendition czy software’owe renderery wielu studiów. Pozwalały osiągać niezłe efekty na ówczesnym sprzęcie, ale każde dodatkowe źródło światła, większa tekstura czy wyższa rozdzielczość drastycznie obciążały CPU. Doszło do momentu, gdy kolejne generacje procesorów poprawiały sytuację tylko częściowo, a oczekiwania graczy rosły szybciej niż moc pojedynczego rdzenia.

DOS, sterowniki i chaos wczesnych API 3D

Początek akceleracji 3D na PC to okres sporego zamieszania. Nie istniał jednolity standard, a producenci kart próbowali forsować własne rozwiązania. Gry pod DOS często komunikowały się z kartą bezpośrednio, obchodząc system operacyjny i sterowniki systemowe. Każda karta mogła wymagać osobnego pliku .DLL lub zestawu bibliotek dostarczonych na dyskietce czy płycie CD.

Pierwsze próby unifikacji przyniosły interfejsy takie jak 3Dfx Glide, wczesne wersje Direct3D, różne dialekty OpenGL oraz firmowe API mniej znanych producentów. Efekt dla użytkownika bywał kuriozalny: jedna gra działała świetnie na Voodoo, ale kiepsko na Riva 128, inna wymagała osobnego „patcha OpenGL” od producenta karty, jeszcze inna miała własny, wbudowany renderer dla kilku wybranych układów.

Rynek w naturalny sposób dążył do standaryzacji. Deweloperzy nie chcieli utrzymywać kilku równoległych ścieżek kodu. Gracze też szybko tracili cierpliwość do ręcznego wybierania sterowników i patchy. W dłuższej perspektywie przetrwały tylko te API, które zapewniały względnie stabilny, wspólny mianownik: hardware’owe OpenGL w zastosowaniach profesjonalnych i Direct3D w grach na Windows.

Narodzinom akceleratorów 3D towarzyszyły liczne „półśrodki”

W przejściowym okresie pojawiały się układy nazywane marketingowo „3D”, które w praktyce niewiele miały wspólnego z pełnowymiarową kartą 3D. Część z nich przyspieszała tylko wybrane etapy renderingu: mapowanie tekstur, zeta-bufor, filtrowanie. Inne oferowały „tryby 3D”, ale działały wyłącznie w kilku konkretnych rozdzielczościach, bez pełnego wsparcia dla wszystkich efektów stosowanych przez deweloperów gier.

Niektóre konstrukcje potrafiły przyspieszyć starsze tytuły lub dema technologiczne, ale w zderzeniu z następną falą gier traciły sens. Częścią problemu było też to, że twórcy gier musieli programować „pod konkretny chip”, zamiast pod abstrakcyjną warstwę API. Jeśli układ implementował pewne funkcje sprzętowo, ale API nie miało ich standardowego odpowiednika, pełnia możliwości pozostawała niewykorzystana.

Komputer Apple II w muzeum jako przykład wczesnej historii grafiki komputerowej
Źródło: Pexels | Autor: Ruben Boekeloo

Era „wojen GPU”: 3Dfx, Nvidia, ATI/AMD i reszta

3Dfx Voodoo: pierwszy „kultowy” akcelerator 3D

Za symboliczny początek ery masowego 3D na PC często uznaje się 3Dfx Voodoo. Nie był to pierwszy układ 3D w ogóle, ale trafił w rzadkie połączenie: sensowna moc obliczeniowa, dobrze zaprojektowane API Glide, rozsądny model sterowników i ścisła współpraca z twórcami gier. Co ważne, Voodoo był z reguły dodatkową kartą, pracującą obok istniejącego akceleratora 2D i łączoną z nim specjalnym kabelkiem pass-through.

Ograniczenie do określonych rozdzielczości (najczęściej 640×480) i brak obsługi 2D upraszczały projekt układu. Cała krzemowa powierzchnia mogła zostać poświęcona na funkcje istotne z punktu widzenia gier: rasteryzację trójkątów, filtrowanie tekstur, z-bufor, proste efekty przezroczystości. W praktyce różnica między wersją gry z obsługą Voodoo a czysto programową bywała tak duża, że gracze traktowali brak akceleratora jako poważną przeszkodę.

Wejście Nvidii i upadek 3Dfx

Nvidia w pierwszych latach nie była oczywistym liderem. Wczesne układy, jak NV1, eksperymentowały choćby z geometrią kwadratową zamiast trójkątów, co utrudniało adaptację. Przełom nastąpił z rodziną RIVA (128, TNT, TNT2), gdy firma postawiła na standardowe API (Direct3D, OpenGL) i pełną kartę 2D/3D w jednym. Z punktu widzenia użytkownika oznaczało to koniec kabli przełączających między osobnymi kartami i prostszy model sterowników.

3Dfx z kolei próbowało utrzymać przewagę wydajności, rozwijając kolejne generacje Voodoo i forsując Glide. Przez pewien czas przewaga jakości obrazu i płynności była realna, ale rynek coraz mocniej przesuwał się w stronę otwartych API. Ostatecznie 3Dfx przegrało z kombinacją agresywnej strategii produktowej Nvidii, rosnącej konkurencji cenowej i własnych błędów biznesowych. Finałem było wykupienie części aktywów 3Dfx przez Nvidię.

ATI/AMD: od „goniącego peletonu” do równorzędnego konkurenta

ATI początkowo kojarzono bardziej z solidnym 2D i funkcjami multimedialnymi niż z „twardym” 3D. Seria Rage oferowała akcelerację 3D, ale często z kompromisami: brak pełnego wsparcia dla wszystkich trybów teksturowania, problemy sterowników, nie zawsze idealna zgodność z rosnącą bazą gier Direct3D.

Zmiana nastąpiła z debiutem serii Radeon. Architektura nastawiona na szeroką przepustowość, rozbudowane jednostki shaderów (jak na ówczesne standardy) i agresywna walka cenowa postawiły ATI w roli realnego rywala Nvidii. Przez kolejne lata rynkowy obraz często wyglądał tak: Nvidia dominowała w absolutnej wydajności topowych modeli, natomiast ATI (a później AMD po przejęciu) oferowało atrakcyjny stosunek cena/wydajność w średnim segmencie.

Inni uczestnicy wyścigu: Matrox, S3, PowerVR

W „złotych latach” wojen GPU poza wielką trójką pojawiało się wielu graczy, którzy próbowali swoich sił w 3D. Część z nich znikała po jednej czy dwóch generacjach, innym udało się znaleźć wąskie nisze.

  • Matrox – słynął z jakości obrazu i bardzo dobrego 2D, ale w 3D zwykle zostawał krok z tyłu za Nvidią i ATI. Karty takie jak G400 miały grupę oddanych fanów, ale brak agresywnego rozwoju 3D i ostrożna polityka produktowa sprawiły, że firma stopniowo wycofała się z rynku masowego.
  • S3 – po sukcesach w 2D próbowało sił z rodziną Virge i kolejnymi układami 3D, lecz rynek szybko ocenił je jako „3D-decelerators” z powodu ograniczonej wydajności. W późniejszym okresie technologia S3 trafiła raczej do zintegrowanych układów niż do topowych kart.
  • PowerVR – eksperymentował z tzw. renderowaniem kafelkowym (tile-based deferred rendering). Na desktopie rozwiązania te nie zdobyły masowej popularności, ale ta filozofia architektury powróciła z sukcesem w urządzeniach mobilnych, gdzie oszczędność energii stała się kluczowa.

W efekcie długofalowo utrzymał się model duopolu Nvidia–ATI/AMD w kartach konsumenckich, podczas gdy inni gracze znaleźli miejsce w GPU zintegrowanych, kartach profesjonalnych lub systemach wbudowanych.

Rośnie znaczenie sterowników i ekosystemu

Same parametry sprzętu coraz rzadziej wystarczały do oceny karty. Coraz większą rolę odgrywały sterowniki: ich stabilność, wydajność w konkretnych grach, tempo wydawania aktualizacji pod nowe tytuły. Zdarzały się przypadki, gdy teoretycznie słabszy układ z dopracowanymi sterownikami radził sobie lepiej niż konkurent z mocniejszymi parametrami „na papierze”.

Równolegle rosło znaczenie narzędzi deweloperskich: profilerów GPU, bibliotek do zarządzania efektami (np. pakiety typu „GameWorks”), dokumentacji i wsparcia technicznego. Producenci GPU zaczęli inwestować w relacje ze studiami, współtworząc dema technologiczne, pomagając optymalizować silniki i wprowadzając rozwiązania „pod konkretną architekturę”. W dłuższym okresie to właśnie ekosystem – a nie pojedyncze cechy krzemu – coraz mocniej różnicował oferty.

Retro komputery Apple z klawiaturami w witrynie sklepu w Tokio
Źródło: Pexels | Autor: Derek Xing

Architektura GPU – inne spojrzenie na obliczenia

Dlaczego GPU nie jest po prostu „szybszym CPU”

Na poziomie marketingu często pojawia się porównanie: „karta ma kilka tysięcy rdzeni, CPU ma tylko kilka”. Tego typu zestawienia są mylące, bo rdzeń CPU i „rdzeń” GPU to zupełnie inne jednostki. CPU jest projektowane jako uniwersalny procesor ogólnego przeznaczenia: rozbudowana logika sterująca, głęboka hierarchia cache’y, silne jednostki do pojedynczych wątków, skomplikowane mechanizmy przewidywania skoków i spekulacji.

GPU idzie w przeciwnym kierunku. Większość tranzystorów rezerwuje na proste, powielone wiele razy jednostki arytmetyczne, zdolne wykonywać te same operacje na dużych porcjach danych. Logika sterująca jest możliwie uproszczona, a zakłada się, że kod będzie mocno zrównoleglony i relatywnie jednolity (te same instrukcje na wielu elementach danych). Dlatego GPU świetnie radzi sobie z zadaniami typu „oblicz to samo dla milionów pikseli czy wierzchołków”, a gorzej w scenariuszach silnie rozgałęzionych z nieprzewidywalnym przepływem sterowania.

Model SIMD/SIMT i bloki wątków

Większość współczesnych GPU stosuje wariant modelu SIMD (Single Instruction, Multiple Data) albo SIMT (Single Instruction, Multiple Threads). W uproszczeniu grupa wątków – np. 32 lub 64 – wykonywana jest w „lock-stepie”: w danym cyklu wszystkie wykonują tę samą instrukcję, choć na różnych danych. Gdy część wątków w grupie wejdzie w inny warunek (if), pozostałe muszą czekać lub wykonywać „puste” operacje, co powoduje zjawisko określane jako divergency.

Ten model jest znakomity dla zadań takich jak rasteryzacja trójkątów, cieniowanie pikseli, filtrowanie tekstur – tam każdy fragment obrazu wymaga podobnego ciągu obliczeń. Natomiast algorytmy z dużą liczbą rozgałęzień, nieregularnym dostępem do pamięci czy zależnościami między krokami trudniej dopasować do architektury GPU, co często prowadzi do rozczarowujących wyników mimo „tysięcy rdzeni na karcie”.

Hierarchia pamięci: przepustowość zamiast niskich opóźnień

CPU jest projektowane tak, by skrócić opóźnienie dostępu do danych: wielopoziomowe cache’e, zaawansowane mechanizmy prefetchingu, spekulacja. GPU stawia przede wszystkim na przepustowość: szeroka magistrala do pamięci VRAM, często pamięci typu GDDR lub HBM o bardzo wysokiej szybkości transferu, ale wyższych opóźnieniach niż typowy DRAM w systemie.

Aby efektywnie wykorzystać tę przepustowość, GPU potrzebuje tysięcy aktywnych wątków, które mogą być przełączane sprzętowo, gdy jedne czekają na dane, inne wykonują obliczenia. Jeśli algorytm nie jest w stanie wystarczająco „napełnić” procesora pracą (zbyt mało równoległości, za dużo synchronizacji), duża część teoretycznej mocy pozostaje niewykorzystana. To jedna z głównych pułapek przy przenoszeniu kodu z CPU na GPU.

Stałofunkcyjne bloki GPU

Wbrew popularnemu uproszczeniu, GPU nie składa się wyłącznie z „programowalnych shaderów”. Istotną część układu wciąż stanowią stałofunkcyjne bloki, wykonujące wybrane zadania znacznie efektywniej energetycznie niż ogólne jednostki arytmetyczne. Przykłady to:

  • jednostki rasteryzacji trójkątów i generowania pikseli (ROP-y),
  • specjalizowane jednostki filtrowania tekstur,
  • układy kodowania/dekodowania wideo (NVENC, VCE, UVD itd.),
  • akceleratory ray tracingu (RT cores, Ray Accelerators).

To one zapewniają wysoką wydajność w typowych scenariuszach graficznych oraz multimedialnych, ale równocześnie ograniczają elastyczność: jeśli zadanie wykracza poza przewidziane przez projektantów ścieżki, musi wrócić do jednostek programowalnych, co bywa kosztowne.

Elastyczność programowalna kontra złożoność sterowania

Od czasów pierwszych shaderów programowalnych GPU przesunęły się od ściśle zdefiniowanego potoku grafiki 3D do coraz szerszego modelu programowalnego. Możliwość pisania własnych shaderów w HLSL/GLSL, a później CUDA, OpenCL czy Metal, otworzyła drogę nie tylko do nowych efektów graficznych, ale też do zupełnie innych klas zadań. Ten zysk elastyczności nie jest jednak „darmowy”.

Każdy dodatkowy stopień swobody wymaga bardziej złożonej logiki sterującej, większych rejestrów, bardziej skomplikowanego harmonogramowania wątków. Z jednej strony rośnie liczba możliwych zastosowań, z drugiej – łatwiej o sytuacje, w których programista przypadkiem ustawia GPU w niekorzystnej konfiguracji (np. zbyt małe grupy wątków, źle dopasowane wzorce dostępu do pamięci). To dlatego dwie aplikacje korzystające z tej samej karty potrafią różnić się wydajnością o rząd wielkości – nie przez „magiczne sterowniki”, lecz przez jakość dopasowania kodu do faktycznej architektury.

W praktyce prowadzi to do ciekawego paradoksu: nowoczesne GPU są bardzo ogólne w teorii, ale w codziennej pracy są najbardziej efektywne wtedy, gdy trzymamy się kilku dobrze znanych, „ulubionych” przez architekturę wzorców obliczeń. Wyjście poza nie jest możliwe, lecz pociąga za sobą koszt, który nie zawsze widać w marketingowych slajdach.

Znaczenie przepływu danych w projektowaniu algorytmów GPU

W klasycznych opisach wydajności często eksponuje się liczbę operacji zmiennoprzecinkowych na sekundę (FLOPS). Dla GPU ten parametr szybko okazuje się wtórny względem organizacji przepływu danych. Samo przeniesienie algorytmu z CPU „jeden do jednego” rzadko ma sens – zwykle trzeba go rozłożyć i ułożyć od nowa pod kątem:

  • koaleskowanego dostępu do pamięci globalnej,
  • maksymalnego ponownego wykorzystania danych w pamięci współdzielonej,
  • minimalizacji synchronizacji między blokami wątków.

Typowy błąd wygląda tak: zespół przepisuje kod pętli z CPU do kernela GPU, licząc na „100× szybciej”, a otrzymuje przyspieszenie symboliczne lub wręcz spowolnienie. Powód: każda iteracja pętli powoduje nieregularne skoki po pamięci, a wątków jest za mało, by ukryć opóźnienia. Dopiero przeprojektowanie struktury danych – np. przejście z „tablicy struktur” (AoS) na „strukturę tablic” (SoA) – pozwala GPU rozwinąć skrzydła.

Nie jest to drobiazg implementacyjny, lecz konsekwencja samego fundamentu architektonicznego. GPU zakłada, że dane przychodzą dużymi, niemal strumieniowymi porcjami. Jeśli aplikacja wciąż myśli kategoriami „losowych, małych odczytów”, korzysta z GPU tylko powierzchownie.

Od grafiki do obliczeń ogólnego przeznaczenia: narodziny GPGPU

Pierwsze „hacki” na shaderach

Zanim pojawiły się oficjalne frameworki typu CUDA czy OpenCL, badacze i inżynierowie zaczęli wykorzystywać programowalne shadery do obliczeń, które z grafiką miały wspólny tylko formalny model danych. Dane numeryczne pakowano do tekstur, obliczenia implementowano jako programy cieniujące, a wynik odczytywano z bufora ramki lub tekstur zwrotnych.

Takie podejście miało szereg ograniczeń: ograniczoną precyzję, brak wygodnego wsparcia dla warunków i pętli, konieczność „udawania” operacji ogólnego przeznaczenia za pomocą prymitywów graficznych. Mimo to już wtedy pojawiły się p