Placa de vídeo
VBE, memória mapeada, cores, XPM, sprites e apresentação de frames.
O píxel (3,1) não tem necessariamente o mesmo endereço em dois modos gráficos. A largura da imagem, os bytes por píxel e o espaço entre linhas determinam onde escrever. O framebuffer guarda as cores; o controlador de vídeo lê essa memória para apresentar a imagem.
Modo e informação VBE
A VBE, Video BIOS Extension, oferece uma interface para consultar e selecionar modos gráficos sem programar os registos privados de cada GPU. O ambiente do laboratório usa a interface da BIOS INT 0x10, invocada através do apoio Minix.
| Função em AX | Operação |
|---|---|
0x4F00 | Informação do controlador VBE |
0x4F01 | Informação de um modo |
0x4F02 | Selecionar modo |
0x4F07 | Consultar ou alterar o início da imagem apresentada |
Para selecionar o modo 0x105 com framebuffer linear, BX recebe 0x105 | (1 << 14), ou 0x4105. O bit 14 pede o acesso linear, ou seja, não faz parte do identificador base do modo.
Uma função completa para o ambiente Minix/LCF, com o apoio de BIOS da imagem da cadeira, é:
#include <lcom/lcf.h>
#include <machine/int86.h>
#include <stdint.h>
int set_linear_mode(uint16_t mode) {
reg86_t registers = {0};
registers.intno = 0x10;
registers.ax = 0x4F02;
registers.bx = (uint16_t)(mode | (1u << 14));
int result = sys_int86(®isters);
if (result != OK) return result;
return registers.ax == 0x004F ? OK : 1;
}
O resultado esperado de set_linear_mode(0x105) é o modo selecionado, se suportado. A função verifica duas coisas: a chamada Minix foi executada e a VBE devolveu sucesso em AX. Não aloca nem mapeia o framebuffer. Não funciona num compilador C portátil nem substitui a inicialização completa de vídeo.
Antes de desenhar, consulta a informação do modo, porque não podes assumir que dois modos de 16 bpp têm os mesmos campos de cor. Conserva resolução horizontal e vertical, bits por píxel, bytes por linha, modelo de memória, posições e tamanhos dos campos RGB e endereço físico da VRAM.
A função VBE 0x4F01 escreve um bloco de informação em memória acessível pela BIOS, indicado por ES:DI no modelo de real mode. O apoio da cadeira trata dessa alocação e cópia para vbe_mode_info_t. Um apontador virtual arbitrário de C não é automaticamente um endereço válido para a BIOS.
Mapear memória física
PhysBasePtr é o endereço físico do framebuffer, por isso não o convertas simplesmente para um apontador e escrevas: o processo usa endereços virtuais. No Minix, a sequência é obter permissão para a região com sys_privctl, mapeá-la com vm_map_phys e conservar o apontador virtual devolvido.
Se cada linha ocupa pitch bytes e há height linhas, a região usada por uma imagem ocupa pitch * height bytes. Valida o produto num tipo capaz de representar o tamanho e confere falhas em todas as operações. Ao sair, desmonta o mapeamento e repõe o modo de texto através do apoio do ambiente. Falhar depois de selecionar o modo também exige reposição.
Calcular o endereço de um píxel
Num modo linear com píxeis ocupando um número inteiro de bytes:
é o número de bytes por píxel. pitch pode ser maior do que width * B, devido a alinhamento ou organização do modo. O intervalo visível exige e .
No modo de 1024 por 768 a 8 bpp com pitch = 1024, o píxel (200,150) começa em . Uma imagem ocupa 786432 bytes, ou 768 KiB. Isso não é necessariamente toda a VRAM disponível na placa.
Agora considera largura 4, altura 2, 24 bpp e pitch = 14. Cada linha tem 12 bytes visíveis mais dois bytes de padding. O píxel (3,1) começa em e ocupa os bytes 23, 24 e 25. O cálculo y * width * 3 + x * 3 começaria erradamente no byte 21.
Na figura, a segunda linha começa em 14, não em 12; os três bytes destacados são o píxel pedido e os dois últimos de cada linha ficam por usar.
Índices e cores diretas
Num modo indexado, o valor do píxel escolhe uma entrada da paleta. Com oito bits, há até 256 índices; isso não significa que cada índice seja um valor RGB de oito bits.
Num modo direct color, a palavra do píxel contém componentes. Para cada componente com bits a partir da posição , isola-a com:
Para compor a cor, limita cada componente ao seu campo e desloca-a para a posição correta. No formato RGB565, R tem cinco bits na posição 11, G seis na posição 5 e B cinco na posição 0. Para :
No x86 little-endian, os dois bytes em memória são 0F F8. O significado dos campos da palavra e a ordem dos bytes em memória são questões diferentes. Em 24 bpp, não existe um tipo uint24_t padrão e escrever um uint32_t pisaria o píxel seguinte.
Este programa portátil escreve um píxel de 24 bits numa imagem com padding e imprime 56 34 12, porque guarda os três bytes em ordem little-endian. Define explicitamente que os três bytes contêm os 24 bits inferiores:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
static bool pixel24(uint8_t *buffer, size_t size, size_t pitch,
size_t width, size_t height, size_t x, size_t y,
uint32_t color) {
if (x >= width || y >= height) return false;
size_t offset = y * pitch + x * 3;
if (offset > size || size - offset < 3) return false;
for (unsigned i = 0; i < 3; ++i)
buffer[offset + i] = (uint8_t)(color >> (8 * i));
return true;
}
int main(void) {
uint8_t image[28] = {0};
if (!pixel24(image, sizeof image, 14, 4, 2, 3, 1, 0x123456))
return 1;
printf("%02X %02X %02X\n", (unsigned)image[23],
(unsigned)image[24], (unsigned)image[25]);
return 0;
}
O exemplo guarda os 24 bits inferiores da palavra. Não converte componentes RGB para campos arbitrários de um modo VBE. As dimensões fixas são pequenas e os produtos não transbordam. Uma API que aceite dimensões arbitrárias deve validar também as multiplicações.
Linhas, retângulos e clipping
Uma linha horizontal escreve píxeis com X consecutivo e Y fixo. Um retângulo chama a mesma operação em várias linhas. Para largura e altura , desenha no intervalo semiaberto , que contém píxeis.
Escolhe o contrato: rejeitar figuras parcialmente fora do ecrã ou recortá-las, clipping. Para rejeitar sem overflow, depois de validar a origem, compara w <= screen_width - x e h <= screen_height - y. Calcular x + w primeiro num tipo pequeno pode dar a volta e aceitar uma figura inválida.
Para copiar uma linha de um sprite, respeita o passo entre linhas tanto na origem como no destino. Uma única cópia de sprite_width * sprite_height * B só é correta se a disposição dos dois retângulos for contígua de forma compatível. No ecrã, a próxima linha pode começar muito depois do fim da linha do sprite.
Padrão de cores por retângulos
Para desenhar uma grelha de n por n retângulos, escolhe n positivo, sem exceder a largura nem a altura do ecrã. Separa a posição na grelha da posição em píxeis. A coluna col determina X e a linha row determina Y: com largura de célula cw e altura ch, o retângulo (col, row) começa em (col * cw, row * ch). Se cada retângulo tiver largura width / n e altura height / n, as divisões inteiras podem deixar faixas no bordo; define se as conservas ou as distribuis pelo último retângulo.
Num modo indexado, um padrão pode usar:
O resto mantém o índice dentro da largura do píxel; a paleta decide a cor desse índice. Num modo de cor direta, varia cada componente no seu próprio intervalo. Por exemplo, num padrão RGB565 com componentes iniciais e step = 3, o retângulo da linha 1 e coluna 2 pode usar:
A cor composta é (4 << 11) | (1 << 5) | 6, ou 0x2026. O módulo de cada campo depende do seu número de bits, não dos 16 bits do píxel completo. Estas fórmulas definem este padrão; não são uma exigência da VBE.
XPM e sprites
Um XPM representa píxeis por símbolos de texto. O cabeçalho indica largura, altura, número de cores e número de caracteres por píxel. Seguem-se as definições de cores e as linhas da imagem. Um XPM de altura com cores contém, no formato simples usado no exemplo, strings.
xpm_load converte essa representação para um pixmap no formato pedido, como XPM_INDEXED ou XPM_8_8_8. A estrutura xpm_image_t guarda dimensões, tamanho e bytes. Verifica se a conversão devolve NULL. Carrega uma vez por recurso e reutiliza o resultado, em vez de voltar a interpretar o XPM em cada frame.
Um sprite reúne um pixmap e a informação necessária para o colocar na cena. A transparência significa não escrever certos píxeis do sprite. Não assumas que preto é sempre transparente: usa o valor e o formato definidos para o recurso. A ordem de desenho determina quais objetos ficam por cima de outros.
Uma animação pode escolher um de vários pixmaps. Se cada figura dura quatro frames e desenhas a 30 frames/s, cada figura dura s. Com três figuras, um ciclo completo dura s.
Para movimento, se speed = 3 significa três píxeis por frame, a 30 frames/s obtemos 90 píxeis/s. Na convenção de velocidade negativa usada em exercícios do laboratório, speed = -3 significa um píxel a cada três frames, ou 10 píxeis/s a 30 frames/s. Limita o último passo à distância que falta, para chegar à posição final sem passar além dela.
Double buffering e tearing
Desenhar diretamente na imagem visível pode mostrar um fundo apagado ou objetos incompletos. Com double buffering, compões o frame completo num back buffer e depois copias para o framebuffer. Isso reduz a exposição das fases intermédias de desenho, mas uma cópia ainda demora tempo.
Se o controlador ler o framebuffer durante a cópia, uma parte da imagem pode ser do frame antigo e outra do novo. Isso é tearing. Double buffering por cópia não garante que o ecrã veja apenas frames completos.
Cópia a meio do refrescamento
O controlador leu partes de dois frames. A fronteira é o tearing.
Troca no intervalo vertical
A página nova estava completa antes da troca e fica estável durante a leitura.
Com page flipping, ambos os buffers estão na VRAM e alteras qual é o apresentado, usando uma operação como VBE Set Display Start. Para evitar tearing, a mudança deve respeitar a sincronização com o refrescamento vertical. A frequência do timer da aplicação não garante que estejas sincronizado com o ecrã.
Uma cópia sincronizada também tem de caber no intervalo permitido pelo modo. Uma troca de página agendada para o refrescamento evita copiar todos os píxeis nesse intervalo, se a implementação suportar essa operação.
Triple buffering
Triple buffering acrescenta outro back buffer para preparar um novo frame enquanto um frame completo aguarda apresentação. Exige uma política sobre qual apresentar e o apoio da implementação. Distingue sempre tempo de simulação, ritmo de construção dos frames e ritmo de refrescamento do ecrã.
Exercícios
Calcular offset com padding
Uma imagem linear tem largura 8, altura 4, 24 bpp e pitch de 28 bytes. Qual é o offset inicial do píxel (3,2)?
Pista
Solução
Offset = 2 × 28 + 3 × 3 = 65 bytes. O píxel ocupa os bytes 65, 66 e 67; o início da linha seguinte continua a depender do pitch.
Erros frequentes
Usar a largura visível em vez de pitch ou somar x sem o multiplicar por bytes por píxel.
Interpretar velocidade negativa
Um sprite percorre 600 píxeis. Desenhas a 30 frames/s e speed = -3 significa um píxel a cada três frames. Quanto dura o movimento, ignorando a apresentação inicial?
Pista
Solução
São 1800 frames e 1800/30 = 60 segundos. A velocidade média é 10 píxeis/s. O sinal negativo representa frames por píxel nesta convenção, não movimento na direção contrária.
Erros frequentes
Tratar -3 como três píxeis por frame ou usar a frequência do timer quando a frequência de desenho é diferente.
Avaliar double buffering
Compões um frame inteiro num back buffer e copias para a VRAM sem sincronização. Isso garante ausência de tearing? Descreve um instante em que o ecrã ainda pode mostrar duas imagens.
Pista
Mais uma pista
O controlador pode ler uma zona já copiada e outra ainda antiga.
Solução
Não garante. Durante a cópia, o controlador pode apresentar linhas com o frame novo e linhas ainda do antigo. O back buffer evita mostrar as etapas do desenho, mas a apresentação precisa de sincronização adequada. Page flipping também pode rasgar a imagem se mudar a página a meio da leitura.
Confere a tua resposta:
- Distingui desenho do back buffer e cópia.
- Expliquei leitura simultânea pelo controlador.
- Relacionei apresentação com sincronização vertical.
Erros frequentes
Dizer que memcpy é instantâneo ou confundir frequência de timer com sincronização do refrescamento.
Isolar componentes de uma cor
No formato RGB565, a palavra de cor é 0xB7D4. Qual é a componente verde, entre 0 e 63?
Pista
Solução
(0xB7D4 >> 5) & 0x3F = 62. Os campos vermelho e azul dão 22 e 20. Com a máscara errada 0x1F obterias 30, não 62, por isso este exemplo distingue os seis bits do verde. A ordem little-endian dos bytes em memória não muda estas posições na palavra.
Erros frequentes
Usar máscara 0x1F perde o sexto bit verde; trocar os bytes antes de isolar um campo altera a palavra original.
Transparência e ordem dos sprites
Uma linha do fundo contém [1, 2, 3]. Os sprites A e B cobrem as mesmas
três posições: A contém [T, 0, 9] e B contém [T, 6, T].
T é o valor transparente do recurso; 0 é preto visível.
Dá a linha depois de desenhar A e depois B sobre o fundo. O que muda se desenhares B antes de A? Explica que píxeis saltas.
Pista
Solução
A conserva a primeira posição, escreve preto na segunda e 9 na terceira:
[1, 0, 9]. B conserva a primeira e a terceira, mas substitui a segunda
por 6. O resultado é [1, 6, 9].
Na ordem inversa, B produz [1, 6, 3] e A produz [1, 0, 9].
Só saltas T; saltar também o preto impediria A de ocultar o 6 na segunda
posição. A ordem de desenho determina o sprite visível onde ambos escrevem.
Confere a tua resposta:
- Obtive [1, 0, 9] depois de desenhar A.
- Obtive [1, 6, 9] depois de desenhar B.
- Conservei o preto visível e expliquei a diferença ao inverter a ordem.