< Controladoras de vídeo: 2D >
O interessante é que os usuários mais recentes, ao ouvir esta afirmação, não percebem claramente a diferença. Isto porque eles estão acostumados com suas "interfaces gráficas" (GUIs, ou "Graphics User Interfaces") onde tudo o que se vê na tela são gráficos, inclusive os caracteres, pois as novas interfaces nada mais fazem que "desenhar" na tela. E quando se lê um texto em uma tela de Windows, do Mac ou de qualquer distribuição Linux que use interface gráfica (ou seja, ícones e coisas que tais), o que na verdade se está enxergando são caracteres desenhados na tela.
Nos tempos da interface de caracteres a coisa era diferente. A tela era dividida em pequenos retângulos em cujo interior cabia um único caractere. Em geral uma tela era formada por 25 linhas com 80 destes retângulos cada (a famosa tela 80x25; havia outros formatos, mas eram raros). Quando um programa precisava exibir uma nova linha no vídeo, encaminhava ao sistema operacional os códigos dos caracteres a serem exibidos. O SO, por sua vez, criava em um trecho da memória – denominado "memória de vídeo" – a tela a ser exibida.
Mas note que a memória de vídeo (cujos chips normalmente residiam na própria controladora de vídeo, como hoje) não armazenava o "desenho" da tela. Guardava apenas uma tabela na qual cada posição correspondente a um dos pequenos retângulos da tabela (ou matriz) de 80x25 era preenchida com o código do caractere que deveria ser mostrado naquela posição e certos detalhes, como cor do caractere e do fundo (no máximo dezesseis) e atributos do caractere: sublinhado, piscante, enfatizado e reverso (trocando as cores do caractere e do fundo; se não entendeu, espere um pouco que logo ficará claro). Os circuitos da controladora, então, liam a matriz, preenchiam cada posição da tela com a imagem do caractere (copiada daquela que estava gravada em uma memória permanente também residente na controladora) conforme seus atributos e enviavam, agora sim, a imagem da tela completa para o vídeo sessenta vezes por segundo.
Note que, ao sistema, não era possível mover o cursor para cima e "encaixar" uma linha entre as demais. Tudo o que fosse escrito na tela do sistema tinha que ser na última linha livre. O usuário poderia, no máximo, emitir o comando "cls" (de "clear screen", limpar tela) e apagar tudo, começando novamente da primeira linha (é claro que, durante a execução de um programa, seria possível mover o cursor e inserir linhas, mas para isso o programa precisava ter acesso direto à memória de vídeo e cabia ao programador fazer todo o trabalho braçal exigido).
Figura 1: Tela do Macro Assembler da MS
Se você usa Windows e quer ter uma ideia aproximada de com que isto se parecia, abra o menu Iniciar, clique em "Todos os Programas", "Acessórios" e abra um Prompt de Comando. E se desejar usufruir a experiência de trabalhar em telas de caracteres, da próxima vez que ligar seu computador, acesse o "setup". Verá então como é trabalhar com telas com número fixo de linhas e colunas e com uma "fonte" gravada em ROM que não se pode alterar. Dureza...
As interfaces gráficas
E então vieram as interfaces gráficas. A primeira a ter alguma aceitação comercial foi a do Apple MacIntosh, lançado em 1984, mas restrita a um nicho de mercado. O sucesso veio em 1990 com Windows 3 (as versões anteriores, assim como outras tentativas de uso de interfaces gráficas não "vingaram"). E então as coisas mudaram. Pois todas as telas tornaram-se gráficas, inclusive a do sistema operacional.
Desculpe insistir, mas quem jamais usou uma tela de caracteres, como a maioria dos leitores desta coluna, talvez não consiga perceber a diferença. Então vou tentar explicar.
Enquanto uma tela de caracteres era uma matriz de retângulos nos quais cabia um caractere em cada, uma tela gráfica, mesmo nos tempos de antanho (entenda-se por antanho a era do IBM XT, primeira metade da década de oitenta do século passado) era constituída por uma matriz de pontos. E como os pontos formam a imagem, recebem o nome de "célula de imagem", ou "Picture Cell", em inglês, expressão contraída para "pixel".
Quantos pixels e quantas cores cada tela gráfica pode exibir depende do padrão de vídeo. Uma das mais simples é a que se vê na Figura 2, mostrando o King´s Quest, um dos primeiros jogos para PC, usando o padrão CGA (Color Graphics Array) que exibia 200 linhas de 320 pixels cada, que podiam assumir uma entre dezesseis diferentes cores.
Figura 2: tela gráfica padrão CGA
Sim, eu sei, comparada com as telas dos jogos modernos a da Figura 2 há de provocar gargalhadas. Mas não se iluda: para a época era um progresso e tanto (embora ainda não o suficiente para ser usada em uma interface gráfica). Mas comparada com uma tela de caracteres, era (literalmente) outra coisa...
Para começar, a tela gráfica é formada por pontos coloridos, não por retângulos contendo informações. E o número de pontos é muito maior. Assim como, dependendo do número de cores, também é maior o número de informações nela contidas. Complicou? Pois expliquemos.
Tomemos uma tela de caracteres 80x25. Ela é formada por 2.000 elementos (retângulos). Cada elemento contém um código de caractere (de acordo com a tabela ASCII então usada para caracteres, cada um deles ocupa um byte), que pode assumir uma entre dezesseis cores (e para exprimir 16 números diferentes bastam quatro bits, ou meio byte) e pode ter quatro atributos, cada um deles "ligado" ou "desligado" (mais quatro bits para armazenar o estado dos atributos). Portanto, cada elemento pode ser inteiramente caracterizado por dois bytes (um para o código de caractere, meio para a cor e meio para o atributo). Uma tela inteira pode ser armazenada em um total de 2.000 elementos x 2 bytes = 4.000 bytes
E como saber a posição do elemento na tela? Ora, basta lembrar que os elementos devem ser lidos da esquerda para a direita e de cima para baixo, de dois em dois bytes, linha após linha (ou seja: ao chegar ao final de uma linha, salta-se para a primeira posição da de baixo). Portanto a ordem do conjunto de bytes que constituem uma "tela" na memória de vídeo é suficiente para identificar a posição de cada elemento.
A tela gráfica obedece ao mesmo princípio. Imagine uma tela um pouco mais sofisticada que a da Figura 2, a tela CGA de 256 cores (na qual cada pixel pode assumir uma dentre 256 diferentes cores que, portanto, podem ser expressas por números de zero a 255 que ocupam um byte). Neste caso, para exprimir um pixel, basta sua cor (pois a posição, como vimos, é desnecessária já que, como os elementos da tela de caracteres, são "desenhados" na tela um a um, da esquerda para a direita e de cima para baixo, linha a linha).
Então, que espaço da memória de vídeo é ocupado por uma tela gráfica CGA 320x200 de 256 cores? Ora, 320 x 200 x 1 = 64.000 bytes (um para cada pixel).
Agora, pense. Para exibir a tela de caracteres, a UCP tem que montá-la na memória de vídeo sessenta vezes por segundo, ou seja, enviar 60 vezes 4.000 bytes = 240.000 bytes/s para a controladora de vídeo (que abriga a memória de vídeo).
Já para exibir uma tela gráfica é preciso enviar 60 x 64.000 = 3.840.000 bytes/s. E isto para a mais simples das telas.
Quando as interfaces gráficas começaram a se popularizar, o padrão de tela dominante era a VGA (Video Graphics Array) de 640 x 480 pixels e 65.536 cores (ou 64K cores, que exigia 16 bits – ou 2 bytes – para armazenar cada cor). Uma tela inteira ocupava então na memória de vídeo um total de 640 x 480 x 2 = 614.400 bytes. Que, para ser renovada 60 vezes por segundo, exigia que a UCP compusesse cada uma das telas e as enviasse para a controladora de vídeo em uma taxa de 60 x 614,400 = 36.864.000 bytes/s.
Gastando memória e engarrafando o barramento
Tínhamos aí dois problemas.
O primeiro era a carga de trabalho sobre a UCP, ou microprocessador. Que, além de todas as demais tarefas exigidas pelos programas, ainda tinha que empregar grande parte de sua capacidade de processamento "desenhando" telas.
O segundo era a possibilidade de transportar tantos bytes em um intervalo de tempo tão curto entre UCP e controladora de vídeo usando o lento barramento de Entrada/Saída de então (barramento é o nome que se dá aos condutores elétricos e seus circuitos de controle que comunicam os elementos internos da placa-mãe; os que ligam a UCP à controladora de vídeo, que fica "espetada" em um dos "slots", constituem o barramento de E/S).
E isto para o padrão VGA de 64K cores. Porque logo depois apareceram padrões de vídeo com maior resolução (número de pixels) e número de cores. Só para que se tenha uma ideia, logo depois de aparecerem as interfaces gráficas o padrão XVGA (eXtended VGA) começou a se popularizar com suas 768 linhas de 1024 pixels cada, capaz de exibir o inacreditável número de 16 milhões de cores (ou seja: eram precisos 4 bytes para armazenar um único pixel). O resultado disto é que apenas uma tela ocupava mais de 3 MB de memória (faça as contas) e, só para renovar as telas, o tráfego sobre o barramento de E/S era da ordem de 180 MB/s.
Para suportar o aumento do tráfego de dados sobre o barramento a solução foi criar novos padrões de barramento. A primeira tentativa foi o VESA, depois veio o PCI seguido do AGP até, finalmente, chegarmos ao PCI-E (PCI eXpress) usado atualmente pelas controladoras gráficas de alto desempenho, que na versão atual (PCI-E 2.1) é capaz de suportar a extraordinária taxa de transferência de 16 GB/s (Gigabytes por segundo).
E para dar conta do armazenamento das telas, com todos os seus pontos e cores exigidos pelos novos padrões de vídeo (o padrão WQXGA, ou Wide Quad eXtended Graphics Array exibe uma tela de 1600 linhas de 2650 pixels, cada uma ocupando 4,1 MB; e não é o de maior resolução possível: veja a relação no tópico < http://en.wikipedia.org/wiki/Graphic_display_resolutions > "Graphic display resolutions" da Wikipedia) foi preciso aumentar a capacidade de memória das controladoras de vídeo, que passou dos 16 KB (para quem estranhou, confirmando por extenso: dezesseis quilobytes) das primeiras controladoras CGA para a espantosa casa dos GB (GigaBytes !!!) de memória instalada nas placas modernas.
Talvez um dia falemos sobre uma coisa ou outra. Mas hoje, o que nos interessa é descobrir como os fabricantes resolveram o terceiro aspecto do problema: reduzir a imensa carga de processamento sobre a UCP representada pela necessidade de "desenhar" as telas gráficas das novas interfaces.
Pois foi da maneira mais simples e direta possível: se a UCP não dá conta do trabalho, então vamos arranjar quem a ajude, ou seja, faça o "trabalho braçal" de desenhar as telas em seu lugar, liberando-a para tarefas mais nobres.
E foi assim que surgiram as primeiras "placas aceleradoras".
As placas aceleradoras
As placas aceleradoras começaram a aparecer no início dos anos noventa e não pararam de se desenvolver.
Uma "placa aceleradora de vídeo" é uma controladora que, além da memória de vídeo e dos circuitos necessários para codificar as imagens e enviá-las ao monitor (analógica ou digitalmente, conforme o monitor), contém ainda um chip muito especial, na verdade um processador auxiliar capaz de "desenhar" as telas baseando-se apenas em comandos genéricos enviados pela UCP. Este chip é o coprocessador gráfico ou Unidade Gráfica de Processamento (UGP), em inglês GPU (Graphics Processor Unit).
A UGP é o componente mais importante de toda a circuitaria dedicada a vídeo. Como toda sua capacidade de processamento visa gerar imagens, ela o faz muito mais depressa, o que justifica o nome de "aceleradora" dado às controladoras que dispõem de UGP.
Neste ponto convém notar que o aumento da rapidez na exibição de imagens alcançado pelas placas aceleradoras não se restringe ao fato delas disporem de uma UGP dedicada exclusivamente à geração de imagens (embora este seja um fator relevante). O desempenho melhora também porque reduz significativamente o tráfego sobre o barramento, já que a UCP não mais precisa enviar para a controladora cada ponto das telas, mas apenas instruções genéricas sobre como e onde desenhá-los.
Seu funcionamento se baseia no fato de que, como as UGPs "sabem" gerar imagens, basta que recebam da UCP alguns comandos gráficos de alto nível para cumprir sua tarefa. Estes comandos solicitam à UGP que desenhem polígonos e figuras curvas bidimensionais, além de executar operações de transferência de grandes blocos de dados de um ponto para outro da tela (operação denominada Bit-BLT, de "bit block transfer").
Por exemplo: sem a placa aceleradora, para desenhar um círculo vermelho em uma determinada região da tela, a UCP precisa identificar um ponto pertencente ao círculo, localizar sua posição na memória de vídeo e lá armazenar um valor de quatro bytes (nos padrões de 16 milhões de cores) representando o tom adequado de vermelho. E repetir a tarefa para cada um os pontos do círculo. Mas se a placa de vídeo contém uma UGP, tudo o que a UCP tem que fazer é enviar as coordenadas do centro da circunferência, seu diâmetro e cor do círculo que ela limita e voltar a seus afazeres. Daí para frente o problema é da UGP, que vai descobrir onde fica cada pixel na memória de vídeo e "colori-lo" de acordo.
Mas como funciona efetivamente uma UGP? Bem, se você for "fuçar" direitinho vai descobrir que ela é na verdade uma poderosíssima unidade de processamento matemático especializada na execução de operações do tipo "ponto flutuante" (ou seja, com operandos fracionários), posto que desenhar imagens na tela nada mais é que decompô-las em polígonos elementares (e quanto maior o número de polígonos, mas realista é a imagem) e calcular não somente as coordenadas de cada um de seus vértices como também a cor do polígono. E armazenar os dados correspondentes na memória de vídeo. E tudo isto nada mais é que um intrincado encadeamento de operações matemáticas.
Figura 3: Decomposição de uma imagem em polígonos
Veja, na Figura 3, um exemplo simples porém ilustrativo de como uma imagem (neste caso, obtida no < http://cgbrandon.wordpress.com/ > blog de Brandon Huang) pode ser decomposta em polígonos.
Este processo pode continuar sendo mais e mais detalhado aumentando o número de polígonos e calculando suas cores. Evidentemente, quanto maior o número de polígonos e menor seu tamanho, mais realista será a imagem. E, mais evidentemente ainda, o cálculo das coordenadas de cada um dos vértices de todos os polígonos consumiria um bocado de capacidade de processamento adicional. Daí a importância da UGP.
Investir tanto no processamento de gráficos parece exagero. Mas não é. Pois, por incrível que pareça, em sistemas com interface gráfica este é um dos fatores determinantes do desempenho de todo o sistema. E a principal razão para que as interfaces gráficas tenham demorado tanto a se disseminarem foi justamente a inexistência, na época em que surgiram, de hardware com capacidade de gerar gráficos com a necessária rapidez.
E a razão é simples: como tudo nas interfaces gráficas é composto por desenhos na tela, quanto maior a resolução e o número de cores, mais os usuários se sentiam compelidos a usá-las (ou a rejeitá-las quando não satisfatórias). E quanto maior a resolução e o número de cores, maior a capacidade de processamento drenada da UCP nas máquinas sem placas aceleradoras. O resultado disto é que as deficiências não se faziam sentir apenas no que toca à geração de gráficos, mas sim a tudo o que dizia respeito ao processamento. Simplificando: quem se metesse a usar uma interface gráfica em telas de alta resolução de máquinas sem "placas aceleradoras de vídeo" acabaria descobrindo que seu sistema havia ficado insuportavelmente lento, e não apenas no que toca aos gráficos. A máquina parecia funcionar em câmara lenta.
E olhe que estamos falando apenas de imagens bidimensionais.
Quando começaram a aparecer os aplicativos (principalmente jogos) que usam e abusam da tridimensionalidade, a questão mudou completamente de figura.