Oggi vi parlo di schede grafiche nel mondo GNU/Linux, in particolare delle schede AMD. Il motivo per cui mi concentro sull’AMD è semplice: è l’unica azienda che ha preso seriamente il settore Linux Desktop/Gaming. Purtroppo Intel non produce hardware sufficientemente potente per lo scopo, mentre NVIDIA è pensa solo a impedire agli utenti di eseguire un software diverso da quello che hanno deciso loro, ostacolando in tutti i modi il progetto nouveau. Non a caso il signor Linus Torvalds si è già espresso limpidamente a riguardo.

In questo articolo introdurrò i molti nomi che vengono continuamente citati quando si parla di driver open source, ma non fatevi intimorire.

Su Linux la gestione della scheda grafica o di altri processori accelerati, è, vista la complessità, organizzata in moduli e in livelli. Un certo numero di moduli che cooperano possono esporre ad un livello superiore un insieme di funzionalità tramite delle API (Application Programming Interface). Più che ci allontaniamo dal livello di partenza (l’hardware), maggiore è il livello di astrazione. Esaminiamo (superficialmente) i vari livelli per arrivare alle nostre finestre interattive con tanto di puntatore, icone colorate e animazioni così complesse che con meno potenza di calcolo ci sono andati sulla Luna :D.

Livello 0: hardware

Come ho già detto, l’hardware è il livello di partenza, senza il quale non si porrebbero tutti questi problemi… Ovviamente è costituito da almeno una CPU e, solitamente, almeno una GPU. Le varie unità comunicano tra di loro mediante un bus, un sistema di comunicazione ad alta velocità arbitrato. La CPU ha accesso alla memoria RAM principale, mentre la GPU può avere accesso ad una memoria ad essa riservata o condividere la RAM con la CPU, in base al tipo di GPU (discreta o integrata).

A tal proposito l’AMD, da qualche anno, propone l’HSA, un insieme di specifiche per l’integrazione di CPU e GPU sullo stesso bus, in modo che condividano la memoria e i lavori da eseguire, in modo da ridurre l’inevitabile latenza di comunicazione tra CPU e GPU.

La necessità di una GPU separata dalla CPU sorge dal fatto che i programmi grafici (specialmente quelli 3D), devono eseguire ripetutamente un certo insieme di operazioni (nel caso 3D calcoli di algebra lineare, come la moltiplicazione di matrici). Per rendere l’esecuzione più efficiente, la GPU è armata di hardware che esegue, in pochi cicli di clock, operazioni che altrimenti avrebbero richiesto numerose istruzioni macchina della CPU. In questo senso si parla di accelerazione grafica. Infatti esiste anche l’accelerazione crittografica che è inclusa in tutte le CPU moderne ed è fondamentale sui server. Esiste anche l’accelerazione audio fornita dalle schede audio per calcoli di DSP (Digital Signal Processing).

Livello 1: kernel

Il Kernel è il componente fondamentale di ogni sistema operativo ed il suo scopo principale è quello di arbitrare l’accesso alle risorse hardware da parte dei processi, garantendo un compromesso fra prestazioni e sicurezza.

I nostri bei programmi grafici colorati, per accendere tutti quei pixel, devono tenere in memoria il colore di ogni pixel su schermo. L’area di memoria, di solito della GPU, predisposta per questo scopo si chiama tradizionalmente framebuffer. Ad esempio se ho uno schermo con risoluzione 800×600, avrò bisogno di avere in memoria 800×600 = 480000 pixel. Ogni pixel di solito ha una profondità di colore di 24bit (RGB -> 1 byte per il rosso, 1 per il verde e 1 per il blu = 3 byte = 24 bit), quindi la dimensione del framebuffer in byte sarà 800 × 600 × 3 = 1440000 byte ≃ 1.4 MB. Uno schermo 4K ha bisogno di un framebuffer di 4096 × 2160 × 3 = 26542080 ≃ 25 MB.

In realtà la situazione è più complessa e, solitamente, si usano più di un framebuffer, ad esempio nella tecnica chiamata double buffering, si usano 2 framebuffer: uno contiene il frame che la GPU sta calcolando, l’altro contiene il frame precedentemente completato. Quando la GPU finisce di calcolare, i framebuffer vengono scambiati, così che il frame che viene inviato al monitor sia sempre completo, evitando il cosidetto “tearing” (lo schermo visualizza metà del vecchio frame e metà del nuovo).

Ora più processi che scrivono nel framebuffer, inevitabilmente causano problemi, ad esempio su schermo si vedrà chi arriva a sovrascrivere per ultimo. Per quanto riguarda il kernel Linux, inizialmente, c’erano solo le semplici API FBDEV (FrameBuffer DEVice), che consentivano a più processi di condividere un framebuffer virtuale nella RAM. Tuttavia tali API non erano state progettate per supportare l’accelerazione hardware. Il progetto DirectFB altro non era che la modifica di FBDEV per consentire l’accelerazione grafica, ma non ha avuto fortuna.

Successivamente (prima di DirectFB) fu introdotto in Linux il sottosistema DRM (Direct Rendering Manager), che espone delle API che permettono ai processi in spazio utente di mandare comandi direttamente alla GPU, gestendo i problemi di concorrenza tra processi.

DRM ha molti driver, cioè codice che dipende da quale dispositivo stiamo utilizzando, tra i quali: radeon, i915, nouveau, amdgpu, virtio, vc4, msm e altri. L’AMD ha 2 driver DRM: il vecchio radeon e il nuovo amdgpu, entrambi di ottima qualità. Anche il driver i915 è in ottima forma. Purtroppo ciò non si applica agli altri driver, il cui grado di qualità varia molto fino ad arrivare al pessimo, quasi inutilizzabile, visto che queste grandi aziende si tengono per se tutta la documentazione rilevante dei prodotti che vendono.

Un altro modulo importante è KMS (Kernel ModeSetting) e gestisce la comunicazione a basso livello con i monitor. Ad esempio si occupa di codificare il contenuto del framebuffer per inviarlo al monitor.

Livello 2: libdrm (DRM library)

Lo spazio utente, invece di interagire direttamente con le API di DRM, utilizza la libreria libdrm, che ne facilita l’accesso e permette di riutilizzare il codice, che altrimenti andrebbe ripetuto in ogni programma. Anche libdrm è composta da una parte comune e una parte che dipende dall’architettura della scheda grafica. Abbiamo, infatti i vari libdrm_radeon, libdrm_amdgpu, libdrm_intel, libdrm_nouveau e così via, che potenzialmente implementano funzionalità aggiuntive, non presenti nella parte comune.

Livello 3: Mesa3D

Per quanto detto fin’ora, uno sviluppatore che vuole pubblicare una applicazione grafica dovrebbe scriversi del codice specifico per ogni piattaforma: dovrebbe usare le API di Linux, Windows, Android e così via, nel suo programma.

A questo punto entra in gioco Mesa3D, nata come implementazione open source delle API OpenGL. Mesa interagisce con libdrm per fornire le sue API più astratte e indipendenti dal tipo di acceleratore grafico. OpenGL è ben nota perché è l’API multipiattaforma per eccellenza ed è supportata da tutti i sistemi operativi principali (Linux, Mac, Windows e pure i BSD). Col passare degli anni, Mesa ha aggiunto il supporto a molte altre API, quali: OpenGL ES, EGL, OpenCL e Vulkan.

Sulle distribuzioni GNU/Linux viene distribuita nell’installazione di base per desktop. Lo sviluppo di Mesa è cresciuto esponenzialmente nel corso degli anni ed ormai ha poco a cui invidiare ad altre soluzioni proprietarie, specialmente quando si parla di schede grafiche AMD o Intel (ribadisco: NVIDIA no).

Per mettere in comunicazione le applicazioni grafiche con l’hardware accelerato, è stato introdotto in Mesa e nel kernel (parte di DRM) l’infrastruttura DRI.

Come libdrm, anche Mesa ha del codice specifico ad ogni acceleratore:

Driver Schede video
r600 AMD fino a TeraScale
radeonsi AMD architettura GCN
i965 Intel
nv50 NVIDIA fino a Tesla
nvc0 NVIDIA da Fermi
freedreno Qualcomm
softpipe software
llvmpipe software
swr software

Gallium3D

Con l’eccezione del driver i965 dell’Intel, che viene considerato un driver “classico” o DRI-style, tutti i driver condividono una parte del codice, utilizzando un’infrastruttura comune chiamata Gallium3D, progettata per facilitare la scrittura di nuovi driver Mesa. Il driver i965 è stato scritto prima che venisse introdotto Gallium3D e per vari motivi non è stato portato.

Gallium3D vs DRI

I driver software (softpipe, llvmpipe e swr) implementano OpenGL senza accelerazione da parte della GPU.

Softpipe è un driver non molto performante, quindi è stato “potenziato” utilizzando il compilatore [LLVM], ottenendo llvmpipe. Nel caso in cui si utilizzi un pc senza scheda grafica, di default Mesa renderizza usando llvmpipe. swr, invece, è più recente ed è riservato a CPU Intel che hanno particolari istruzioni macchina accelerate.

Gallium3D fornisce inoltre una comoda HUD che permette di plottare su schermo vari grafici relativi alle performance, al carico, alla temperatura e velocità delle ventole. Basta settare la variabile di ambiente GALLIUM_HUD e lanciare l’applicazione grafica che vogliamo monitorare. Ad esempio GALLIUM_HUD='fps' glxgears lancia glxgears mostrando un grafico degli fps.

È possibile avere una panoramica grafica dello stato dei vari driver Mesa dal sito mesamatrix.net.

Livello 4: X, Wayland o Mir

Su questo livello troviamo ben 3 alternative: il tradizionale X, l’emergente compositore Wayland e Mir, server creato da Canonical per Ubuntu, ma non andato in porto. Tutte le distribuzioni, compreso Ubuntu, stanno attualmente completando la transizione da X a Wayland. Il server X è un software che ha origini molto antiche, per quanto riguarda l’informatica. La sua architettura, seppur rivista più volte, è piuttosto datata ed è fonte di numerosi problemi di sicurezza e prestazioni per i desktop Linux.

Il server X ha nuovamente una parte DIX (Device Independent X) e una parte DDX (Device Dependent X). Questa dipendenza dal dispositivo è rimasta per ragioni storiche. Prima dell’avvento di DRM, infatti, era la parte DDX a dover effettuare il mode setting, infatti si parlava di UMS (User Mode Setting) e il server X andava eseguito con i privilegi di root! Oggi è disponibile il DDX xf86-video-modesetting, che è generico in quanto accelera il 2D usando GLAMOR che traduce le primitive 2D in operazioni OpenGL, rendendo obsoleto il concetto di DDX.

Wayland

Le idee chieve dietro al nuovo sistema Wayland sono:

  • specifica del protocollo Wayland
  • libwayland-server, implementazione in C lato server (detto anche compositore, ad esempio: Weston o GNOME Shell) del protocollo
  • libwayland-client, implementazione lato client (ad esempio GTK o Qt) in C del protocollo
  • il compositore compone i buffer delle finestre delle varie applicazioni
  • ogni finestra ha un suo buffer e non può vedere i buffer delle altre finestre, a meno che non lo richieda al compositore (che potrebbe negare la richiesta per motivi di sicurezza)
  • input delle periferiche gestito con libinput (utente) e evdev (kernel)
  • accelerazione grafica diretta tramite EGL sia per il compositore che per le finestre dei client (non sono necessari i DDX)

Livello 5: Toolkit grafico (GTK, Qt, SDL, …)

UI toolkit

Quando si vuole scrivere un nuovo programma con interfaccia grafica, solitamente si utilizzano delle API che semplificano notevolmente lo sviluppo e garantiscono l’uniformità a livello sia visivo che operativo dell’applicazione con tutte le altre applicazioni installate sul sistema. L’annoso compito viene svolto da un toolkit grafico. I più conosciuti sono GTK, Qt e SDL, tutte librerie multipiattaforma.

GTK, sorprendentemente, sta per GIMP ToolKit, infatti inizialmente faceva parte di GIMP, il noto programma di manipolazione di immagini! Solo successivamente è stato separato ed è diventato il toolkit usato da GNOME, il famoso ambiente grafico.

Qt invece viene utilizzato da KDE, altro ben noto ambiente grafico in competizione con GNOME.

SDL (Simple DirectMedia Layer) è una libreria multipiattaforma che consente l’accesso a basso livello a mouse, tastiera, audio e scheda grafica tramite OpenGL o DirectX e viene utilizzata dalla maggioranza dei giochi.

I programmi che fanno uso di questi toolkit non devono preoccuparsi se vengono eseguiti su ambiente Wayland o X o su un altro sistema operativo, perché è proprio il compito del toolkit quello di astrarre da tutto ciò che sta al di sotto.

Fine

Incredibilmente, dopo tutti questi passaggi, la nostra applicazione GTK/OpenGL (inserisci qui una API supportata), può tranquillamente renderizzare in 3D sia in finestra che a schermo intero a 60FPS (se non di più)!

Appendice 1: driver schede AMD

Eh si stavolta ho pure l’appendice! Ecco una tabella che vi mostra quali driver state utilizzando se avete schede AMD:

Architettura DRM(Kernel) OpenGL(Mesa) X.org
TeraScale e precedenti radeon r600 xf86-video-ati
GCN 1.0 e 1.1 radeon radeonsi xf86-video-ati
GCN 1.0 e 1.1 amdgpu radeonsi xf86-video-amdgpu
GCN 1.2 e successive amdgpu radeonsi xf86-video-amdgpu

Nel caso GCN 1.0 e 1.1 ci sono due alternative: il vecchio driver radeon che è ancora il default, o il nuovo amdgpu (ormai conviene attivarlo). Se si ha una scheda GCN 1.0 basta passare al kernel i parametri amdgpu.si_support=1 radeon.si_support=0, mentre se è GCN 1.1 amdgpu.cik_support=1 radeon.cik_support=0 (testato su Linux 4.14).

Questa pagina di Wikipedia è utile per capirci qualcosa sul casino che ha fatto l’AMD con nomi visto che ogni scheda ha più di un nome commerciale e un solo nome in codice.

Appendice 2: compilare Mesa3D con meson

Come si compila Mesa? Con meson!… eh dai non l’hanno fatto apposta…

# Il sorgente git pesa qualche centinaio di MB
git clone git://anongit.freedesktop.org/mesa/mesa
cd mesa

# Configurazione di mesa da riempire con i driver che si desiderano
# ad esempio -Dgallium-drivers=r600 o -Dgallium-drivers=radeonsi
meson build -Dbuildtype=release -Dprefix=/opt/mesa-master \
-Ddri-drivers= -Dgallium-drivers= -Dvulkan-drivers= \
-Ddri3=true -Degl=true -Dllvm=true -Dlmsensors=true -Dshared-glapi=true

# Controllare che la configurazione soddisfi i propri requisiti
meson configure build
# Se si vuole modificare un parametro usare meson configure build -Dparametro=valore

# Lanciamo la compilazione
ninja -Cbuild

# Installazione nella cartella prefix configurata precedentemente
sudo ninja -Cbuild install

A questo punto ci serve uno script (che chiamo mesa-master) che ci permetta di avviare un programma usando Mesa compilato e non quello di sistema:

#!/bin/bash

#export LIBGL_DEBUG=verbose
#export MESA_DEBUG=1
export LD_LIBRARY_PATH="/opt/mesa-master/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
export LIBGL_DRIVERS_PATH=/opt/mesa-master/lib/x86_64-linux-gnu/dri
export EGL_DRIVERS_PATH=/opt/mesa-master/lib/x86_64-linux-gnu/dri
#export MESA_GLSL_CACHE_DISABLE=1
export MESA_GLSL_CACHE_DIR=~/.cache/mesa-master
#export MESA_GL_VERSION_OVERRIDE=4.2
#export MESA_GLSL_VERSION_OVERRIDE=420

glxinfo | grep "OpenGL version string"

"$@"

Potete testare lo script lanciando mesa-master glxgears. Dovreste vedere i famosi ingranaggi che girano.

Commenti