Ten artykuł został oryginalnie opublikowany w numerze 4/2025 (119) (lipiec/sierpień 2025) magazynu Programista.
W artykule tym przejdziemy szybkim krokiem przez historię API graficznych takich jak DirectX, OpenGL, Vulkan oraz towarzyszącego im na przestrzeni lat rozwoju kart graficznych z jednej strony, a z drugiej – gier video. Nie będziemy się uczyli programowania w żadnym z tych API. Artykuł ten powinien być zrozumiały i zaciekawić może każdego, kto interesuje się grami czy grafiką albo przynajmniej w dzieciństwie grał w gry.
Siłą rzeczy w artykule przedstawiono tylko wybrane fakty i zastosowano pewne uproszczenia. To nie będzie kronika kolejnych dat i wydarzeń w historii rozwoju grafiki komputerowej. Chcę raczej zaprosić czytelnika w podróż, w trakcie której będziemy skakali po osi czasu (aż do czasów starożytnych!), aby jak najlepiej zrozumieć, skąd się wzięła sytuacja, jaka panuje obecnie w dziedzinie programowania kart graficznych, i jak może ona rozwijać się dalej w przyszłości.
Skupimy się na platformach, które służą do grania w gry komputerowe w domu, a więc przede wszystkim na pecetach z systemem Windows. Rozwój grafiki na Linuksie, na urządzeniach mobilnych, na jakichś profesjonalnych stacjach roboczych czy serwerach mógłby stanowić osobną historię.
Aby zrozumieć, w jaki sposób i w jakim celu powstały API graficzne, musimy cofnąć się do czasów, kiedy jeszcze nie istniały. Lata 80. to z punktu widzenia współczesnej informatyki prawdziwa prehistoria. Królowały wówczas komputery domowe, takie jak ZX Spectrum, Commodore 64, Atari czy Amiga. Miały one niewielkie możliwości sprzętowe. Na przykład 16-bitowe Atari ST wyposażone było w 256 KB do 1 MB pamięci RAM oraz procesor o częstotliwości 8 MHz.
Już na takie komputery powstawały jednak wciągające gry. Ich grafika była jeszcze dwuwymiarowa, o niskiej rozdzielczości i ograniczonej palecie kolorów. Już wtedy jednak komputery wyposażone były w sprzętowe przyspieszenie (akcelerację) wyświetlania grafiki w postaci rysowania tzw. duszków (ang. sprites). Dzięki nim można było wyświetlać poruszające się obiekty (jak postać bohatera czy potworów–przeciwników) dość szybko, aby gracz dostrzegał płynny ruch, co nie byłoby możliwe, gdyby powolny procesor główny musiał je rysować piksel po pikselu.
Programowanie tych komputerów odbywało się bardzo niskopoziomowo, często bezpośrednio w asemblerze. Wyświetlanie grafiki, jak również wszystkie inne funkcje sprzętowe realizowane były przez ustawianie odpowiednich rejestrów czy też zapisy pod określone adresy pamięci. Taki kod był oczywiście specyficzny dla danego modelu komputera, co nie było problemem dopóty, dopóki wszyscy użytkownicy mieli taki sam lub podobny sprzęt (rozszerzony ewentualnie o dodatkową pamięć itp.)
Aby zrozumieć ewolucję API graficznych, będziemy spoglądać na Rysunek 1 i do niego wracać. Opisaną powyżej sytuację ilustruje pierwsza kolumna, oznaczona jako (1). Widzimy w niej, że gra odwołuje się bezpośrednio do sprzętu, a konkretnie do układu graficznego (GPU).
Rysunek 1. Rozwój gier, silników i API graficznych

W drugiej połowie lat 80. i na początku lat 90. dominować zaczęły komputery osobiste typu IBM PC. Architektura ta podbiła rynek m.in. za sprawą swojej modularności i rozszerzalności. Do ustandaryzowanej obudowy i płyty głównej można było montować różnorodne komponenty, np. karty rozszerzeń, a do uniwersalnych portów wejścia–wyjścia podłączać różne urządzenia peryferyjne dostarczane przez wielu producentów.
Dawne pecety miały monitory monochromatyczne z kartami graficznymi takimi jak Hercules, ale później pojawiły się standardy kolorowe, jak VGA. Kto dodatkowo zainstalował w swoim komputerze kartę dźwiękową oraz napęd CD-ROM, mógł pochwalić się, że jego komputer jest „multimedialny”. Mógł on wtedy kupić i uruchomić zapisaną na płycie CD „encyklopedię multimedialną”, która zawierała nie tylko ogromny zbiór haseł z różnych dziedzin z opisem tekstowym, ale również dołączone do niektórych haseł obrazki, dźwięki, a nawet krótkie sekwencje filmowe, co wówczas robiło ogromne wrażenie.
Wracając jednak do pecetów najdawniejszych, systemem operacyjnym był tam MS-DOS, a programowanie aplikacji nadal odbywało się niskopoziomowo, przez bezpośredni dostęp do sprzętu. W modularnej architekturze PC rodziło to problemy z zapewnieniem kompatybilności z różnymi urządzeniami. Nie było wówczas architektury Plug&Play. Każda gra czy program musiał być osobno przystosowany do pracy z wieloma popularnymi modelami urządzeń. Na Rysunku 2 pokazano zrzuty ekranu z gry „Duke Nukem 3D”, w której ręcznie skonfigurować trzeba było posiadany model karty dźwiękowej i numer przerwania IRQ, aby w grze pojawił się dźwięk.
Rysunek 2. Konfiguracja dźwięku w grze Duke Nukem 3D

O ile DOS był systemem jednozadaniowym, pojawienie się systemu Windows zapewniło możliwość pracy z wieloma aplikacjami naraz. Wraz z nią dostępny stał się graficzny interfejs użytkownika (GUI) oparty na okienkach, które mogły wzajemnie się przysłaniać. Początkowo model współpracy między uruchomionymi programami był kooperatywny (aplikacja musiała oddać sterowanie następnej). To jednak sprawiało, że źle napisany program mógł łatwo zawiesić cały komputer. W nowszych wersjach Windowsa, jak 95, 98, a tym bardziej wersjach opartych na jądrze NT (Windows 2000, XP itd…), aplikacje nie mogły już tak łatwo zawiesić całego systemu czy uzyskać dostępu do pamięci innego procesu.
To wymagało także odizolowania aplikacji od bezpośredniego dostępu do sprzętu. Taka „wirtualizacja”, w połączeniu z wielozadaniowością, a także potrzebą zapewnienia kompatybilności różnych aplikacji z różnymi modelami klawiatur, myszek, drukarek, monitorów itd. spowodowała, że niezbędnym pośrednikiem stał się sterownik (ang. driver). Dostarczony przez producenta danego urządzenia, zainstalowany w systemie, zajmował się kontrolowaniem swojego urządzenia, podczas gdy aplikacje przeprowadzały wszelkie operacje (np. wyświetlania okienek, odtwarzania dźwięków, drukowania) poprzez API systemowe (WinAPI).
Taki stan był dobry dla aplikacji okienkowych, jak edytory tekstu, ale nie był idealny dla gier. Na Rysunku 3 pokazano zrzut ekranu z gry „SimCity 2000”, która w wersji na Windows 3.1 wyświetlała swoją grafikę w ramach zwykłego okienka, korzystając z systemowego menu i innych kontrolek. Jak nietrudno się domyślić, twórcy gier woleliby wyświetlać całą grafikę we własnym zakresie, przejmując kontrolę nad pełnym ekranem. To nie tylko pozwala grom wyglądać bardziej atrakcyjnie, ale też zapewnia lepszą wydajność, gdyż bezpośredni dostęp do bufora ramki omija komponowanie nakładających się okienek i związane z tym dodatkowe kopiowanie danych.
Rysunek 3. Gra SimCity 2000 w wersji na Windows 3.x

W odpowiedzi na te potrzeby Microsoft stworzył interfejs DirectX. To nowe API, przeznaczone do gier i innych aplikacji multimedialnych, zapewniało wydajniejszy dostęp do funkcji sprzętowych, jak na przykład wejście w tryb pełnoekranowy (ang. exclusive fullscreen). Słowo „direct” nie oznacza tu jednak bezpośredniego dostępu do sprzętu w sensie tak dosłownym, jak to było w dawnych komputerach. W systemie nadal działał sterownik, a także inne aplikacje, tak że wciśnięcie Alt+Tab powodowało opuszczenie trybu pełnoekranowego.
Litera „X” w nazwie DirectX oznaczała z kolei „coś”, czym mógł być jeden z wielu komponentów wchodzących w skład tego API. Stare wersje DirectX dostarczały bowiem osobną część służącą do wyświetlania grafiki 2D (DirectDraw), grafiki 3D (Direct3D), odtwarzania dźwięku (DirectSound), muzyki (DirectMusic), obsługi klawiatur, myszek i innych kontrolerów (DirectInput), odtwarzania video (DirectShow) i innych. Do naszych czasów przetrwał jedynie Direct3D. Pozostałe biblioteki, jeśli wciąż działają dla zapewnienia kompatybilności wstecznej, są już jednak niezalecane, gdyż zostały wyparte przez inne, nowocześniejsze. Obecnie określenie DirectX jest więc synonimem Direct3D.
Jeszcze inną ciekawostką może być geneza nazwy konsoli Xbox. Oznacza ona bowiem nic innego, jak „DirectX Box”, czyli pudełko z DirectXem. Microsoft, tworząc swoją konsolę, zdecydował się bowiem „upakować” tam komputer ze specjalną wersją systemu Windows i właśnie DirectXem jako API przeznaczonym do obsługi grafiki i wszelkich multimediów przez gry tworzone na tę platformę. Tak jest do dzisiaj, gdzie tworzenie gier na nowe generacje Xbox wymaga używania DirectXa (ze specyficznymi dla konsoli rozszerzeniami).
W tym miejscu warto doprecyzować, co mamy na myśli, pisząc API. Skrót ten oznacza „Application Programming Interface”. Interfejs to ogólnie pewien łącznik między czymś a czymś. Tak jak graficzny interfejs użytkownika (GUI) jest łącznikiem pozwalającym użytkownikowi sterować aplikacją, tak API stanowi dla programisty protokół komunikacji między częściami oprogramowania, np. między pisanym programem a jakąś biblioteką. Tak naprawdę API to definicja funkcji, struktur, stałych i innych elementów kodu, zapisana np. w postaci dokumentacji lub pliku nagłówkowego .h dla języka C.
API graficzne są natomiast interfejsem pomiędzy aplikacją (najczęściej grą) a sterownikiem graficznym. API takie jak Direct3D stanowi standardowy protokół komunikacji pozwalający wydawać komendy rysowania grafiki na ekranie, podczas gdy sterownik zajmuje się ich tłumaczeniem na niskopoziomowe komendy specyficzne dla danego modelu GPU (dyskretnej karty graficznej bądź też układu graficznego zintegrowanego z procesorem). Sytuację tą ilustruje kolumna (2) na Rysunku 1.
Pojawienie się grafiki trójwymiarowej w grach nie było wydarzeniem nagłym. Wskazać można wiele ogniw tej ewolucji. Większość z nich związana jest z rozwojem gier z gatunku strzelanek z perspektywy pierwszej osoby (ang. FPS – First Person Shooter). Jedną z pierwszych popularnych gier tego typu był „Wolfenstein 3D” firmy id Software. O ile trójwymiarowe ściany z nałożonymi teksturami robiły wrażenie, o tyle – ze względu na sposób renderowania grafiki w tej grze – podłogi i sufity musiały pozostać płaskie i jednokolorowe. Dopiero następna strzelanka tej firmy – „Doom” – zniosła to ograniczenie, pozwalając uzyskać na mapie schody.
Wskazać można kilka firm developerskich i ich tytułów, które stanowiły kamienie milowe w historii rozwoju gier 3D, w tym m.in. id Software („Wolfenstein 3D”, „Doom”, „Quake”), Epic MegaGames („Unreal”) czy 3D Realms („Duke Nukem 3D”). Historię rozwoju gier FPS świetnie opowiada 4,5-godzinny film „FPS: First Person Shooter Documentary” (2023).
Współzałożyciele tych firm, jak John Carmack w id Software i Tim Sweeney w Epicu, stali się postaciami kultowymi w środowisku programistów grafiki i gier. Ówcześnie sami byli głównymi programistami swoich gier. Obecnie każdy z nich jest raczej pełnym sukcesu przedsiębiorcą, choć obaj pozostają blisko związani z technologią. Tim Sweeney nadal zarządza firmą Epic, nadzorując rozwój silnika Unreal Engine i gry „Fortnite”, podczas gdy John Carmack skupił się na tematyce sztucznej inteligencji.
Sprzętowa akceleracja grafiki 3D też nie pojawiła się natychmiast. Pierwsze gry 3D renderowane były w pełni programowo – przez procesor główny CPU. Pewnym „brakującym ogniwem ewolucji” była karta 3dfx Voodoo, która stanowiła dodatkową kartę rozszerzeń przeznaczoną wyłącznie do grafiki 3D, która musiała być zamontowana w pececie obok zwykłej karty graficznej. Do jej programowania służyło własne API o nazwie Glide. Dopiero potem zwykłe karty graficzne w pecetach zintegrowały sprzętowe wsparcie dla rasteryzacji trójkątów, które stało się standardowym sposobem na przedstawianie obiektów 3D i takim pozostaje do dzisiaj.
Tymczasem gry z gatunku strzelanek pierwszoosobowych nadal wyznaczają nowe ścieżki w jakości grafiki, fotorealizmie i wykorzystaniu nowych, zaawansowanych technik graficznych. Razem z nimi w parze często idą wysokie wymagania sprzętowe, które niejednego gracza skłaniają do zakupu nowej konsoli, nowego komputera czy przynajmniej nowszej karty graficznej, aby zagrać w nowo wydany tytuł. Takimi grami wyznaczającymi nowy standard jakości były swego czasu m.in. „Crysis”, „Killzone 2”, „STALKER”, a także wychodzące regularnie nowe gry z serii „Call of Duty”.
Uwagę dotychczas poświęciliśmy interfejsowi DirectX, a tymczasem opisując API graficzne, wypada wspomnieć też o jego konkurencie: OpenGL. Można wskazać wiele różnic między nimi, jak też i wiele podobieństw. Oba rozwijały się w ciągu wielu dekad, dając dostęp do wydajnego renderowania grafiki 3D. Oba dostępne są do użycia w systemie Windows i choć większość gier korzysta z DirectX, wydanych zostało też wiele dużych gier opartych na OpenGL (w tym gry firmy id Software).
Trudno powiedzieć z całą pewnością, by jedno z tych API było obiektywnie lepsze od drugiego, dostarczało większych możliwości czy działało z większą wydajnością. Można oczywiście rozważać wiele różnic i niuansów, jednak koniec końców to są tylko interfejsy zapewniające dostęp do tych samych funkcji sprzętowych karty graficznej.
Najważniejsza różnica polega na tym, kto jest twórcą danego API, a za tym, jakie platformy ono wspiera. DirectX został stworzony i wciąż rozwijany jest przez Microsoft, a więc jego natywnym środowiskiem jest system Windows oraz konsola Xbox. OpenGL rozwijany jest przez grupę Khronos, która stanowi konsorcjum zrzeszające wiele firm zainteresowanych rozwojem grafiki komputerowej.
Wśród członków Khronosa znaleźć możemy wszystkich producentów układów graficznych (AMD, Intel, Nvidia, Apple, a także układów mobilnych – Samsung, Arm, Qualcomm, Imagination), firmy tworzące istotne oprogramowanie graficzne i platformy gier (np. Valve, Epic, Unity, Adobe), ale też wiele firm, których byśmy się tam nie spodziewali, jak… Ikea (znana z produkcji mebli) czy Continental (znany z produkcji opon). Wszystkie te firmy z takich czy innych powodów zainteresowane są bowiem rozwojem standardów graficznych. Listę członków Khronosa można znaleźć na stronie Khronos Members. Jak wiele z tych firm rozpoznajesz?
Taka sytuacja sprawia, że OpenGL stanowi standard wspierany na wielu platformach, czy to pecety z systemem Windows, Linux, komputery MacOS, smartfony z systemem Android, iOS, czy jeszcze wiele innych, bardziej egzotycznych urządzeń. OGL ma też system rozszerzeń, który pozwala producentom danego GPU dodawać nowe funkcje do API.
To sprawia, że wybór API graficznego do użycia w danym programie podyktowany jest przede wszystkim zbiorem platform, na jakich ten program ma działać. Aplikacje przenośne na wiele różnych systemów częściej wybierają OpenGL. Tymczasem dla twórców „dużych” gier racjonalnym wyborem jest DirectX, który zapewni obsługę dwóch spośród trzech platform przeznaczonych na takie gry: PC/Windows i Xbox. PlayStation i tak bowiem ma własne, niestandardowe API.
Kolejną różnicą, oprócz listy wspieranych platform, jest formuła samego API. DirectX zaprojektowany został w technologii COM, a więc jest interfejsem obiektowym polegającym na używaniu klas (a dokładniej – interfejsów) i ich metod. OpenGL z kolei zdefiniowany został w języku C, a więc używa wyłącznie funkcji globalnych, struktur, stałych, enumów i w oryginale opierał się na swego rodzaju „maszynie stanów”, gdzie zawsze operujemy na obiekcie ustawionym aktualnie jako bieżący. Oto przykładowy kod:
glClearColor(0.0f, 0.0f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(shader_program); int color_location = glGetUniformLocation(shader_program, "Color"); glUniform4f(color_location, v0, v1, v2, v3); glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, 3); glfwSwapBuffers(window);
„Rządy silnej ręki” Microsoftu sprawiły, że firma ta nie bała się niejednokrotnie przedefiniować całe API, zrywając kompatybilność wsteczną i unowocześniając je zgodnie z tym, jak postępował rozwój możliwości sprzętowych GPU. Wydany w 2004 roku DirectX 9.0c był wersją przez wiele lat dominującą wśród twórców gier. Po niej przyszedł mocno przeprojektowany DirectX 10, a następnie wydany niedługo po nim w 2009 roku DX11, który ponownie na wiele lat stał się podstawą większości pecetowych gier.
Tymczasem OpenGL zachowywał kompatybilność wsteczną do swoich pierwszych wersji sięgających lat 90. Zaletą takiego podejścia była niewątpliwie łatwość w rozwijaniu i utrzymywaniu starego kodu, obojętnie czy chodzi o silniki gier, czy o inne aplikacje graficzne, np. z dziedziny CAD. Wadą natomiast – skomplikowanie API, a co za tym idzie trudność w jego implementacji po stronie sterownika graficznego, związany z tym narzut wydajnościowy, a także duża liczba czających się w nim błędów i „przypadków szczególnych”. Na przykład czasami jakieś dwie funkcje nie współpracują ze sobą w taki sposób, jak na innej karcie graficznej bądź jak to jest zapisane w specyfikacji.
Możemy też rozważać szybkość, z jaką nowe funkcje sprzętowe pojawiające się w GPU zostają udostępnione przez API. Wydawać by się mogło, że OpenGL i jego następca – Vulkan – przez swoją otwartość powinny rozwijać się szybciej. Okazuje się jednak, że to DirectX częściej oferuje nowości wcześniej, zanim zrobi to konkurent. Dzieje się tak, ponieważ Microsoft ma pełnię władzy nad swoim API, a jego rozwój musi konsultować tylko z producentami GPU używanych na Windowsie, których jest niewielu (AMD, Intel, Nvidia i od niedawna Qualcomm). Tymczasem w grupie Khronos uczestniczy bardzo wiele firm, a więc rozwój technologii opiera się na dyskusjach i głosowaniach, w których zapewne liczy się nie tylko aspekt techniczny, ale i polityka – interesy poszczególnych firm.
Zanim nowe funkcje sprzętowe trafią do standardu API, nieraz dodawane są jako niestandardowe rozszerzenie oferowane przez producenta danego GPU. W OpenGLu i Vulkanie rozszerzenia są wspierane natywnie, jednak dostawcy układów graficznych znaleźli sposób, aby takie rozszerzenia dodawać również do DirectX-a, oferując je w postaci swoich bibliotek: AMD GPU Services (AGS) oraz NVAPI.
Na koniec rozważań o OpenGLu warto pochylić się nad częścią „Open” z jego nazwy. Wiele osób twierdzi bowiem, że OpenGL jest „otwarty”, a DirectX – „zamknięty”. To może być prawdą w pewnym sensie, ale zależy od tego, jaki aspekt rozważamy. Tak, jak mówiliśmy wcześniej, API to tylko definicja interfejsu programistycznego. Dokumentacja obu API – DX i OGL – dostępna jest publicznie, legalnie i za darmo w Internecie (czego nie można powiedzieć o dokumentacji konsol – Xbox, PlayStation czy Switch).
Nic nie stoi więc na przeszkodzie, aby wystarczająco zdeterminowany i posiadający odpowiednie zasoby podmiot nie mógł dostarczyć nowej implementacji takiego API. Dawny spór sądowy między Oracle a Google dotyczący interfejsu Java w Stanach Zjednoczonych dostarczył precedensowego rozstrzygnięcia, według którego API nie można zastrzec w sposób, który prawnie uniemożliwiałby innym dostarczenie alternatywnej implementacji. Dla DirectXa powstały zresztą takie implementacje – biblioteki DXVK (dla DX10/11) i vkd3d-proton (dla DX12), które realizują funkcje DirectXa z użyciem Vulkana. To dzięki nim gry stworzone na Windows działają na SteamDecku, który oparty jest przecież na Linuksie.
Z kolei ktoś, kto ma ambitny plan zaprojektować i sprzedawać swoje własne, nowe GPU, do reklamowania go jako wspierający OpenGL lub Vulkan musi przejść zbiór testów dostarczonych przez Khronosa (nazywane CTS), podobnie jak Microsoft dostarcza testy do DirectXa (nazywane WHQL). Jak widać więc, mówienie z całą pewnością, że „OpenGL jest otwarty, a DirectX zamknięty”, nie jest do końca prawidłowe. W takim stwierdzeniu chodzi raczej o to, że ten pierwszy tworzony rozwijany jest przez konsorcjum wiele firm, a ten drugi – przez jedną firmę.
Z biegiem lat rozwijały się nie tylko karty graficzne, ich możliwości sprzętowe i towarzyszące im nowe funkcje dodawane do API, ale też coraz bardziej rozbudowane stawały się gry. Coraz bardziej szczegółowa i realistyczna grafika, animacja, dźwięk i muzyka, sztuczna inteligencja, mechanika gameplayu i inne aspekty… To wszystko sprawiało, że tworzenie gier było i wciąż staje się coraz trudniejsze i coraz bardziej kosztowne.
W tym w ferworze walki, aby ukończyć i wydać grę w terminie wyznaczonym przez wydawcę, z możliwie małą liczbą błędów, taką, która spodoba się recenzentom i graczom, stosunkowo dawno zrodziło się pojęcie „silnika gry” (ang. engine). Koncepcja ponownego użycia kodu w nowym projekcie w postaci wydzielonej biblioteki klas czy funkcji znana jest każdemu programiście. Silnik gry to nic innego, jak taka biblioteka czy też framework stanowiący podstawę do budowy kodu konkretnej gry.
Rozwój silnika jako osobnego komponentu rozpoczął się od ponownego używania kodu w ramach jednej firmy. Wracając do naszego przykładu gier typu FPS, id Software stworzył i wciąż rozwija silnik o nazwie id Tech, na którym oparte są ich gry, podczas gdy Epic rozwija swój Unreal Engine. Ciekawostką jest, że Epic w 1998 roku wydał strzelankę FPS o nazwie „Unreal” konkurującą o serca graczy z Quake’iem. Dziś kojarzymy tę nazwę raczej tylko z silnikiem.
Z czasem wyłonił się model biznesowy, który pozwolił jednym firmom licencjonować od innych gotowy silnik, aby móc skupić się wyłącznie na tworzeniu swojej gry zamiast na rozwijaniu technologii od zera. Początkowo nie było jasne, czy możliwe jest stworzenie silnika wspierającego różne gatunki gier. Mówiło się o silnikach dedykowanych do strzelanek FPS czy strategii RTS.
Udało się jednak wypracować pewne abstrakcje, dzięki którym dzisiejsze silniki (jak Unreal Engine, Unity) są uniwersalne i sprawdzają się wśród twórców gier wszelkiego rodzaju. Taki silnik zapewnia obsługę podstawowych aspektów gry, jak wyświetlanie grafiki, odtwarzanie dźwięków, symulację fizyki, obsługę kontrolerów i wiele innych.
W aspekcie graficznym możemy mówić o przeniesieniu poziomu abstrakcji na wyższy poziom. Używając bezpośrednio API graficznego, musimy posługiwać się obiektami takimi jak „urządzenie”, „bufor”, „tekstura”, „shader”. Tymczasem silnik ukrywa tę złożoność, oferując obiekty wyższego poziomu, z których łatwiej jest komponować kod gry, jak „scena” i stojący na niej „aktorzy”, „kamera”, „światło” czy „materiał”. Warto zauważyć, że nie są to jakieś nowe byty, ale pojęcia znane choćby z kina, a niektóre z nich także z teatru, którego początki sięgają starożytności (jak scena, a na niej aktorzy ubrani w stroje wykonane z różnych materiałów).
Tak więc kod współczesnej gry stanowi logikę wyższego poziomu, która skupia się na implementacji konkretnego tytułu. Używa przy tym funkcji danego silnika, a ten dopiero korzysta z API graficznego do renderingu grafiki. Sytuację tę ilustruje kolumna (3) na Rysunku 1. We współczesnym gamedevie ten, kto programuje w C++ i zajmuje się silnikami, może być określony jako programista „niskopoziomowy”, na co mogliby się oburzyć programiści systemów embedded piszący w C lub w asemblerze i sterujący bezpośrednio sprzętem.
W pewnym momencie wśród programistów gier dało się odczuć niezadowolenie z powodu ówczesnego stanu API graficznych. Nowe gry wychodzące na dostępne wówczas konsole (Xbox One i PlayStation 4) wyglądały rewelacyjnie. Tymczasem gracze pecetowi musieli wciąż kupować coraz to nowsze karty graficzne lub obniżać rozdzielczość i poziom detali w ustawieniach gry, aby uzyskać wystarczającą wydajność. Podczas gdy zwykli użytkownicy mogli winić leniwych developerów i niedbale zrobione porty konsolowe, programiści pewną dozą winy obarczali API graficzne.
Te bowiem, przypadku konsol dawały bardziej bezpośredni, niskopoziomowy dostęp do danego GPU. Tam jest to oczywiście prostsze, ponieważ sprzęt jest zawsze taki sam – nie ma potrzeby wspierania szerokiej gamy różnych modeli od różnych producentów, jak na PC. Jednakże PlayStation zawsze oferował własne, niskopoziomowe API, a nawet interfejs DirectX dostępny na Xboksie wzbogacony był o dodatki specyficzne dla konsoli. Tymczasem na PC królujące wówczas DirectX 11 i OpenGL coraz bardziej oddalały się od tego, jak nowe GPU są zbudowane, i miały abstrakcję wymuszającą na sterowniku dużo dodatkowej pracy, co owocowało większym narzutem na CPU, a więc i niższą wydajnością.
Pewne nadzieje pokładano w nowej wersji OpenGL 3.0. Wielu programistów liczyło na „nowy start” i zerwanie z kompatybilnością wsteczną (tak jak to nieraz zrobił DirectX), aby pozostawić tylko nowoczesne funkcje i zapewnić wydajny dostęp do funkcji współczesnych GPU. Tak się jednak nie stało. Wydany w 2008 roku nowy OGL 3.0 rozczarował „progresywnych” programistów, zachowując kompatybilność wsteczną. Zadowoleni mogli być ci „konserwatywni”, którzy nie musieli przepisywać ogromnych ilości starego kodu aplikacji graficznych (np. CADowskich). Na niewiele zdało się zdefiniowanie przy tym osobnego core profile dla nowoczesnych funkcji i compatibility profile dla zapewnienia kompatybilności ze starymi funkcjami. Sterowniki nadal musiały bowiem obsługiwać je wszystkie.
W 2013 roku firma AMD wydała własne API graficzne o nazwie Mantle. Było ono niskopoziomowe i dobrze dostosowane do architektury kart graficznych tej firmy. Nie zdobyło ono jednak większej popularności. Ukazało się wprawdzie kilka gier z opcją obsługi tego API (m.in. „Battlefield 4”, „Ashes of the Singularity”), jednak trudno było nakłonić developerów, aby poświęcili swój czas na implementowanie API specyficznego dla tylko jednego producenta kart graficznych.
Istotnym wydarzeniem był wykład „Approaching Zero Driver Overhead in OpenGL” wygłoszony na Game Developers Conference (GDC) w 2014 r., a przygotowany wspólnie przez konkurujących ze sobą na co dzień producentów GPU: AMD, Intela i Nvidię. Pokazywali oni, których funkcji OpenGL można używać, a których należy unikać, aby sterownik mógł rozwinąć skrzydła jak najwyższej wydajności i w pełni wykorzystać moc obliczeniową GPU, a nie musieć zajmować się zapewnieniem kompatybilności ze starymi funkcjami nieprzystającymi do dzisiejszych czasów.
Wreszcie nadeszła dawno wyczekiwana rewolucja – pojawiły się nowe API graficzne, mocno przeprojektowane i zrywające z kompatybilnością wsteczną. Jako pierwszy, w lipcu 2015 roku, ukazał się DirectX 12. Microsoft zachował jego obiektową naturę opartą o COM, ale zdefiniował całe API na nowo i wprowadził wiele nowych rodzajów obiektów.
Później, w lutym 2016 r., Khronos wydał specyfikację swojego zupełnie nowego API o nazwie Vulkan. To z kolei oparte jest – podobnie jak wcześniej OpenGL – na nagłówkach C, a więc składa się z funkcji globalnych i struktur. Jednak ze względu na zdefiniowanie „nieprzezroczystych” wskaźników na różne rodzaje obiektów, stanowi de facto interfejs obiektowy. Wspiera także rozszerzenia poprzez sprytny pomysł na dołączanie łańcucha nowych rodzajów struktur przez listę łączoną.
Oba te nowe API cechuje bardziej niskopoziomowy dostęp do funkcji sprzętowych karty graficznej. Wiąże się z tym wiele konsekwencji, bardziej lub mniej przyjemnych. Pojawiły się nowe rodzaje obiektów, którymi programista musi zarządzać. Obok znanego wcześniej „urządzenia”, „tekstury” czy „bufora” dochodzi teraz m.in. „bufor komend”, „deskryptor” i „bariera”. Tak naprawdę istniały one w kartach graficznych od dawna, ale wcześniej sterownik ukrywał je pod wyższym poziomem abstrakcji API. Teraz, używając nowych API, programista silnika musi je wykorzystywać bezpośrednio, co z jednej strony pozwala uzyskać lepszą wydajność, ale z drugiej – stanowi dodatkowe wyzwanie i może prowadzić do różnorodnych błędów, z crashem całej gry włącznie. Innymi słowy, to silnik musi teraz implementować więcej logiki, która wcześniej realizowana była przez sterownik graficzny. Sytuację tę ilustruje kolumna (4) na Rysunku 1.
Warto dodać, że użycie tych nowych API nie musi samo w sobie oznaczać większej wartości na liczniku FPS. Wciąż chodzi przecież o dostęp do tych samych funkcji sprzętowych GPU. Rysowanie tych samych trójkątów cieniowanych tym samym shaderem na tym samym komputerze powinno w teorii wykonać się w takim samym czasie. Proste przeportowanie silnika z DX11 czy OGL na nowe API może wręcz przynieść utratę wydajności, jeżeli programiści nie poświęcą uwagi prawidłowemu i wydajnemu używaniu tego API oraz optymalizacji.
Tym, co te nowe API zmieniają, jest przede wszystkim „cieńszy”, a więc także prostszy i szybszy sterownik graficzny, a co za tym idzie mniejszy narzut po stronie CPU (np. podczas wyświetlania dużej ilości obiektów). Dopiero dodatkowy wysiłek włożony w dostosowanie silnika do DX12 czy Vulkana może zaowocować lepszym wykorzystaniem mocy obliczeniowej karty graficznej. Przykładem może być możliwość wykonania jakichś innych obliczeń równolegle do renderowania grafiki, co znane jest jako asynchronous compute. Także po stronie CPU nie musimy już renderować wszystkiego na wątku głównym czy też wyznaczać jednego „wątku renderującego”, ale możemy równolegle na wielu wątkach CPU wypełniać wiele command bufferów, tym samym lepiej wykorzystując coraz większą liczbę rdzeni obecnych w nowych procesorach.
Także nowe funkcje dodawane do GPU w ostatnich latach uzyskały wsparcie już tylko w tych nowych API (DX12 i Vulkan), a nie w starych (DX11, OGL). Należą do nich przede wszystkim ray tracing, ale również mesh shaders, variable rate shading czy najnowszy „wynalazek” – GPU work graphs. Chcąc skorzystać z tych funkcji, jesteśmy więc skazani na używanie nowych API.
Powstaje pytanie, które z tych nowych API działają na których platformach. Nie ma tutaj niespodzianek. DirectX, jako stworzony przez Microsoft, otrzymał wsparcie na Windows, a także na konsolach Xbox One X|S i Xbox Series X|S. Początkowo dostępny był tylko na nowo wydanym Windowsie 10. Było to w pewnym stopniu uzasadnione względami technicznymi (nowa wersja Windows Display Driver Model – WDDM). Przez graczy postrzegane było to jednak jako próba zmuszenia ich do przesiadki na nowy system operacyjny. Po pewnym czasie Microsoft dodał więc częściowe wsparcie dla DX12 także na Windowsie 7.
Vulkan z kolei, tworzony przez grupę Khronos, podobnie jak OpenGL został standardem wspieranym szeroko przez wiele platform sprzętowych i programowych. Dostępny jest na Windowsie, Linuksie, a nawet FreeBSD czy Raspberry Pi. Na platformach mobilnych wsparcie nie było tak oczywiste. Google w pewnym okresie planował opracowanie własnego API graficznego nowej generacji. Ostatecznie jednak wrócił do „stołu negocjacji” i dziś telefony z Androidem wspierają Vulkana. Taki zwrot nie nastąpił w Apple, skutkiem czego macOS, jak i mobilny iOS nie wspiera Vulkana natywnie. Zamiast tego firma z jabłkiem w logo opracowała własne niskopoziomowe API o nazwie Metal, a Vulkana można używać na tych platformach jedynie pośrednio – przez bibliotekę MoltenVK.
Warto podkreślić, że Vulkan jest w pewnym sensie pierwszym API graficznym, który pod wspólnym interfejsem oferuje dostęp do GPU zarówno tych „dużych” (jak PC), jak i „małych” (jak smartfony). Nawet OpenGL taki nie był, gdyż w systemach mobilnych dostępna była specjalna wersja OpenGL ES. W zamian za to Vulkan musiał wprowadzić do API pewne elementy potrzebne dla GPU mobilnych, które działają w inny sposób, niż te potężniejsze. Pojawiły się m.in. render passy, na które programiści pecetowi mogą narzekać jako kłopotliwe w użyciu, a zupełnie niepotrzebne.
Czy te nowe API (DirectX 12 i Vulkan) odniosły spodziewany sukces? Można powiedzieć, że tak. Choć ich adopcja na początku była powolna, obecnie duży procent wychodzących gier zaimplementowana jest z ich użyciem. Te, które chcą korzystać z ray tracingu, nie mają zresztą innego wyjścia. Najważniejsze silniki (Unreal Engine, Unity, Godot) również oferują już możliwość korzystania z tych API. Po latach obecności na rynku widać, że pod względem popularności wśród gier dla Windowsa ponownie zwyciężyło API Microsoftu, ale Vulkan też działa dobrze na tym systemie i bywa używany w niektórych grach.
Czy te API spełniły pokładane w nich nadzieje? Tu można mieć wątpliwości. Niski poziom sprawia, że trudniej jest się nauczyć i używać takiego API. Niemal legendarna, a przy tym w pełni prawdziwa jest historia o tym, jak potrzeba napisać kilkaset linii kodu, aby wyrenderować pierwszy trójkąt. Wcześniej należy bowiem utworzyć i zainicjalizować wiele potrzebnych obiektów i podać ich wiele parametrów. Oto przykładowy kod wykonujący jedną barierę w Vulkanie:
VkImageMemoryBarrier2 image_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT,
.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1
}
};
VkDependencyInfo dependency_info{
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.dependencyFlags = 0,
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &image_barrier
};
vkCmdPipelineBarrier2(command_buffer, &dependency_info);
Równocześnie zalety, jakie miało przynieść przeniesienie API na niższy poziom, okazały się tylko połowiczne. Tworzenie jednego dużego obiektu pipeline state object zawierającego zapisaną na stałe konfigurację całego potoku graficznego miało wyeliminować potrzebę przekompilowania shaderów przez sterownik i zapobiec przycinaniu się gier (ang. hitching). Tymczasem zamiast tego wymusza ono utworzenie nowego obiektu stanu dla każdej potrzebnej kombinacji parametrów, co owocuje albo długim czasem ładowania gry, albo znowu przycinaniem się w czasie rozgrywki, tyle że już wywoływanym przez samą grę, nie przez sterownik.
DX12 i Vulkan wciąż nie są niskopoziomowe na tyle, aby zapewniać bezpośrednią kontrolę nad GPU i przewidywalną wydajność. Wciąż istnieją szybkie i wolne „ścieżki”. Alokując pamięć video, nie mamy kontroli nad tym, kiedy ta pamięć się wyczerpie oraz co się stanie potem, np. które tekstury system przeniesie do pamięci RAM, co pogarsza wydajność dostępu do nich. Wykonując barierę, nie możemy być pewni, która będzie błyskawicznie szybka, a która wyjątkowo wolna, bo wewnętrznie musi wykonać przekodowanie całej tekstury. Takie przykłady można mnożyć.
Jakby tego było mało, te nowe API mają dużo problemów ze stabilnością. Mając API tak niskopoziomowe, sterownik prostszy, a więcej odpowiedzialności spoczywającej na silniku, winą za crashe można zwykle obarczyć programistów gry lub silnika. Nie zmienia to jednak faktu, że takie problemy występują częściej i są trudniejsze w debugowaniu. Co więcej, nieraz pewien kod działa prawidłowo na GPU jednego producenta, a wyświetla artefakty na innym GPU i to najczęściej jest wciąż wina programisty, a nie sterownika tej drugiej karty graficznej. Nic dziwnego więc, że wiele nawet najnowszych gier wciąż oferuje do wyboru użycie DirectX 11 jako opcja zapewniająca lepszą kompatybilność i stabilność.
Jeżeli czytasz ten artykuł, aby dowiedzieć się czegoś praktycznego i przydatnego do twojej praktyki programisty, to mam nadzieję, że ten i następny rozdział nareszcie spełni twoje oczekiwania. Być może zastanawiasz się, które API wybrać do nauki. W dzisiejszych czasach powiedziałbym przekornie: najlepiej żadne.
Obecnie gry stały się tak skomplikowane, a ich tworzenie tak odległe od bezpośredniego używania API graficznych czy innych bibliotek, że najlepiej jest opanować jeden z istniejących, popularnych silników: Unreal Engine, Unity lub Godot i skupić się na tworzeniu swojej gry. Dotyczy to większości programistów zainteresowanych tworzeniem gier, od tych chcących uczyć się i dobrze bawić, tworząc swoje amatorskie produkcje, poprzez zespoły tworzące grę w 48h w ramach konkursu game jam, niezależnych twórców i małe studia mające pomysł na grę i chcące ją jak najszybciej ukończyć i wydać, po średnie i całkiem duże studia developerskie.
Programowanie grafiki bezpośrednio z użyciem API graficznego i towarzyszące mu tworzenie silnika to bowiem w dzisiejszych czasach wyzwanie karkołomne. Kilka silników, jak te trzy wspomniane wyżej, zdominowało rynek, a utworzenie nowego od zera wydaje się dziś projektem na miarę stworzenia nowego systemu operacyjnego. Tego również praktycznie nikt nie robi od zera. Nawet Google – firma bogata i potężna – użyła Linuksa jako podstawy do stworzenia Androida.
Inna potężna firma – Amazon – chciała w pewnym momencie stworzyć swój silnik gier o nazwie Lumberyard. Oni także nie zaczęli od zera, ale oparli go na istniejącym silniku CryEngine. Lumberyard nie zdobył jednak większej popularności i został anulowany. Równocześnie inna firma Jeffa Bezosa – Blue Origin – z sukcesem wystrzeliła rakietę, która wyniosła go w kosmos. Można więc powiedzieć, że tworzenie silników to nie rocket science – to coś jeszcze trudniejszego 🙂
Wróćmy jednak do decyzji, której technologi się uczyć. Być może, podobnie jak autora tego artykułu, bardziej od tworzenia gier interesuje cię samo programowanie grafiki w swoich bardziej technicznych aspektach. Wówczas też do wyboru mamy obecnie wiele gotowych narzędzi, które wspierają taki tech art. Sam Unreal Engine pozwala na zaawansowaną zabawę materiałami i wieloma aspektami renderingu. Przydatne mogą być też zewnętrzne narzędzia, takie jak Blender czy Houdini. Do zabawy z grafiką i proceduralną generacją w czasie rzeczywistym można z kolei wykorzystać np. Resolume, ArKaos, Modul8 czy vvvv.
Być może zobaczyłeś/aś kiedyś, jak niezwykłe efekty można uzyskać, pisząc same shadery i generując cały obraz proceduralnie. Od prostych gradientów, przez efektowne fraktale, po całe zapierające dech w piersiach sceny 3D z kształtami powtarzającymi się w nieskończoność… Wiele osób dzieli się takimi efektami na platformie X. Na demoscenie organizowane są nawet konkursy w pisaniu takich efektów z pamięci i w ograniczonym czasie. Warto poszukać choćby zapisów video z konkursu „shader showdown” z demoparty Revision. Pisanie takich shaderów można jednak ćwiczyć za pomocą narzędzia online opartego na WebGL: ShaderToy, bez potrzeby pisania choćby jednej linijki kodu C++.
Z drugiej strony trzeba jednak przyznać, że silniki gier nadal istnieją i ktoś musi się zajmować ich pisaniem. Nie dotyczy to tylko pracowników firmy Epic i Unity. Okazuje się bowiem, że jeśli popatrzymy na topowe gry AAA, to wiele z nich wciąż opartych jest na własnym silniku rozwijanym od lat przez ich twórców. Należą do nich choćby gry z serii „Horizon”, „Call of Duty”, „Assassin’s Creed”, „GTA”, „Red Dead Redemption”, „Cyberpunk 2077” czy „The Last of Us”. Można też podać przykłady mniejszych, niezależnych gier 3D, które udało się stworzyć i wydać w oparciu o autorski silnik, jak polska produkcja „Jupiter Hell”.
Jeżeli dotrwałeś/aś do tego miejsca artykułu i nadal rozważasz naukę jednego z API graficznych, gratuluję! Nie wszyscy marzą o jak najszybszym stworzeniu i wydaniu swojej wymarzonej gry. Niektórych – włączając autora tego artykułu – bardziej interesuje implementacja tych niższych warstw kodu, uzyskanie jakichś ciekawych efektów graficznych lub choćby sama świadomość, że kod renderingu jest dobrze napisany i działa wydajnie. Wówczas powstaje pytanie, którego API graficznego warto się uczyć.
Odpowiedź zależy przede wszystkim od tego, na jakich platformach ma działać nasz kod. Dla kogoś związanego z ekosystemem Apple naturalnym wyborem będzie Metal. Dla miłośników Linuksa dobrym wyborem będzie Vulkan. Osobom zainteresowanym tworzeniem gier na PC/Windows i ewentualnie na konsole (po zatrudnieniu się w jakimś studio developerskim, które ma dostęp do odpowiednich devkitów i SDK, bo te nie są dostępne publicznie dla programistów–amatorów) poleciłbym DirectX 12.
Nie trzeba jednak wybierać pomiędzy tymi API niskopoziomowymi, tak trudnymi do opanowania i używania, a nauką istniejącego silnika. Istnieją też biblioteki, które oferują wyższy poziom abstrakcji. Wśród nich wymienić można bgfx, WebGPU (którego nazwa jest myląca – to nie jest biblioteka wyłącznie przeglądarkowa) i SDL3 (jego część oznaczona jako GPU). Pamiętajmy jednak, że nie są to prawdziwe API graficzne w takim sensie, jak opisane w tym artykule – obsługiwane bezpośrednio przez sterownik graficzny, a jedynie nadbudowane nad nie biblioteki. Zawsze można też zacząć naukę od starego dobrego DX11. Naukę OpenGLa, natomiast trudno w obecnych czasach polecać z powodu licznych wad wymienionych już wyżej oraz faktu, że dziś coraz rzadziej bywa już używany.
Na koniec warto nadmienić jeszcze o API dających dostęp do mocy obliczeniowej GPU, ale nie oferujących renderingu grafiki, a jakieś innego rodzaju obliczenia (co bywa nazywane GPGPU – General-Purpose Computing on GPU), jak te związane z machine learning. Do takich API należy OpenCL oraz CUDA.
Trudno przewidzieć, jak dokładnie potoczy się rozwój API graficznych, GPU i całej grafiki komputerowej. Póki co na horyzoncie nie rysuje się żadna rewolucyjna zmiana na miarę wprowadzenia DX12 i Vulkana. Istniejące API są regularnie rozbudowywane o nowe funkcje, a twórcy gier i silników wciąż uczą się tak konstruować swój kod, aby jak najlepiej je wykorzystywać. Swoimi doświadczeniami dzielą się oni na konferencjach takich, jak wspomniane już Game Developers Conference odbywające się w San Francisco czy też konferencja online dedykowana wyłącznie silnikom: Rendering Engine Architecture Conference (REAC).
Rasteryzacja trójkątów od dawna jest i wciąż pozostaje podstawowym sposobem reprezentowania i wyświetlania grafiki 3D. Coraz większą popularność zdobywa jednak ray tracing. Można sobie wyobrazić przyszłość, kiedy za ileś lat, na nowych generacjach GPU moc obliczeniowa będzie wystarczająca, aby ta technika mogła posłużyć do generowania całego obrazu w czasie rzeczywistym. Zrezygnowanie z rasteryzacji uprościłoby potok graficzny i pozwoliłoby uniknąć stosowania wielu „sztuczek”, które są obecnie używane w grafice. Pewne zastosowania znajdują też inne reprezentacje grafiki, jak reprezentacja wokselowa, signed distance fields (SDF) czy gaussian splats.
Jeśli chodzi o sam kształt API, możliwe jest wiele scenariuszy. Jeśli więcej osób doszłoby do wniosków podobnych, jak przedstawione wyżej na temat obecnych niskopoziomowych API, możliwy byłby powrót do API na wyższym poziomie. Być może udałoby się znaleźć jakieś lepsze abstrakcje, które zapewniłyby dostęp do nowoczesnych funkcji (jak ray tracing czy wykorzystanie wielowątkowości), a równocześnie zapewniłyby domyślnie stabilność (mało crashy) i poprawność (unikanie błędów niezależnie od użytego GPU), a maksymalna wydajność i niski narzut po stronie CPU byłyby możliwe do uzyskania po włożeniu odpowiedniego wysiłku – odwrotnie niż to jest teraz.
Można sobie też wyobrazić scenariusz odwrotny, w którym API graficzne staną się jeszcze bardziej niskopoziomowe, a może nawet specyficzne dla danego producenta GPU. O ile kiedyś trudno by było to zaakceptować (co pokazało niepowodzenie Mantle), o tyle teraz, kiedy bezpośrednim użyciem API zajmuje się już tylko niewielka grupa programistów grafiki rozwijających silniki gier, już nie nierealnym wydaje się skłonienie ich, aby użyli osobnego API dla GPU od AMD, Intela, Nvidii itd. Wszystko to jednak są dywagacje autora niepoparte żadnymi informacjami o tym, że rozwój miałby podążać właśnie w tę stronę.
Adam SawickiZastrzeżenie: Ten content był po raz pierwszy zaprezentowany jako wykład na Kole Naukowym Twórców Gier „Polygon” na Politechnice Warszawskiej w marcu 2024, a później, w lipcu 2025, opublikowany w postaci artykułu w magazynie „Programista”. Zdaję sobie sprawę z tego, że wiele informacji zostało tu pominiętych lub uproszczonych celem pokazania ciekawej i spójnej historii. Adresatem są bowiem programiści niekoniecznie zaznajomieni ze szczegółami programowania grafiki. Jeżeli jesteś programistą zaawansowanym w temacie renderingu czy programowania gier, powinieneś raczej przeczytać artykuł No Graphics API od Sebastian Aaltonen. Zapraszam jednak do zgłaszania uwag w komentarzach poniżej.