Если ваш компилятор не распознает некоторые типы данных, то вот несколько строчек кода:
1 2 3 4 5 6 7 | typedef uint8_t boolean; typedef uint8_t unsigned char; #define bitRead(value, bit) (((value) >> (bit)) & 0x01) #define bitSet(value, bit) ((value) |= (1UL << (bit))) #define bitClear(value, bit) ((value) &= ~(1UL << (bit))) |
Что ж, выводить изображения мы умеем, теперь нужно сделать автоматизированный вывод нашей «карты» тайлов на экранный буфер.
В принципе, вывести слой талов — легко. Нам нужно лишь перебирать ячейки «карты» и выводить соответствующий тайл. Но у нас слоев несколько. Здесь появляется проблема.
Представим, что из экранного буфера изображение напрямую отправляется по проводам в монитор/тв/дисплей. Если выполнять этот код на микроконтроллере или на другой платформе, то при отрисовки нескольких слоев изображения — появится «мерцание». Дело в том, что программа может не успеть дорисовать изображение, в это время срабатывает прерывание и экранный буфер(видео-буфер) отправляется на экран, и пользователь видит «кривое» не дорисованное изображение. Есть несколько путей решения:
Двойная буферизация
Здесь нам нужно завести «вторую» видео-память такого же размера. Сначала рисуем изображение в первом буфере, затем быстро копируем во второй видео-буфер, который уже непосредственно выводится на экран.
Самый лучший вариант.
1 2 3 4 5 | РисоватьСлой(Слой1, Буфер2); РисоватьСлой(Слой2, Буфер2); РисоватьСлой(Слой3, Буфер2); БыстроПамятьКопировать(Буфер2, Буфер1); |
Буфер1 = VGA.buf; Буфер2 = VGA.prebuf. В функции drawBitmap — нужно будет поменять VGA.buf на VGA.prebuf.
Z-Буфер или Буфер приоритетов
Что же делать если по каким-то причинам у нас критически мало ОЗУ? Для начала сформируем дополнительный массив размером 256×208/64 по 8байт в памяти.
uint64_t zbuffer[256*208/64];
Итак. Принцип таков: перед началом рендеринга zbuffer заполняется нулями; при выводе чего-либо в видео-память, мы должны проверить ячейку zbuffer на наличие единиц: если такового нет, то пишем что хотели в видео-память и также заполняем ячейку zbuffer единицей.
И порядок вывода должен быть таким:
1 2 3 | РисоватьСлой(Слой3, Буфер1); РисоватьСлой(Слой2, Буфер1); РисоватьСлой(Слой1, Буфер1); |
Теперь слои и вовсе не будут пересекаться. Давайте внесем изменения в функцию drawBitmap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | inline void MoveLineOfImage(const uint8_t *Source, uint8_t *Dest, int16_t x, int16_t y, uint8_t Count) { uint16_t cv = y*256; for (uint8_t i=0; i<Count; ++i){ if ((*Source)!=0xE3 and !bitRead(zbuffer[(cv/64)+i+x], y%64)) { *Dest=*Source; bitSet(zbuffer[(cv/64)+i+x], y%64); } ++Source; ++Dest; }; } inline void drawBitmap(unsigned char tex, const uint8_t* b, int16_t posx, int16_t posy) { uint16_t texx = (tex*16*16); int8_t lsx; if ((posx>256) || (posy>208) || (posx+16<=0) || (posy+16<=0)) return; int16_t x1,y1,x2,y2; x1=y1=0; x2=16; y2=16; if (posx<0) x1=-posx; if (posy<0) y1=-posy; if (posx+16>256) x2=256-posx; if (posy+16>208) y2=208-posy; lsx=x2-x1; const uint8_t *p1=b+texx+y1*16+x1; int16_t i; long Addr=(posy+y1)*256+posx+x1; for (i=y1; i<y2; ++i) { MoveLineOfImage(p1, VGA.buf+Addr,posx+x1,posy+y1+i, lsx); p1+=16; Addr+=256; } } |
РисоватьСлой(void) функция
Мы немножко отошли от темы, перейдем в одну из основных функций. Итак, нам нужна подпрограмма, которая будет сканировать ячейки карты(массива) и выводить нужный тайл в нужном месте на экране(экранном буфере).
(Извинения за немного небрежный рисунок :))
Пример кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | inline void drawTiles(unsigned char (*tilen)[256/16], const uint8_t* bb, int16_t xcam, int16_t ycam, unsigned char xbb, unsigned char ybb, boolean zero) { int8_t yco = -1; //Вот так нежелательно делать, int8_t xco = -1; //...но это багфикс для будущего. unsigned char bugtile = 0; while((yco<VGA_H/16+1)) { while(xco<(VGA_W/16+1)) { bugtile = tilen[yco+ybb][xco+xbb]; if (bugtile>zero) { drawBitmap(bugtile-1-zero, bb, (xco*16)+xcam, (yco*16)+ycam); } ++xco; } xco=-1; ++yco; } yco=-1; xco=-1; } |
У этой функции несколько параметров.
- unsigned char** — это указатель на нашу карту(массив);
- const uint8_t* — указатель на набор тайлов(текстур);
- int16_t xcam — смещение всех тайлов по икс;
- int16_t ycam — смещение всех тайлов по игрек;
- u.char xbb, ybb — аналогично предыдущим, но смещение происходит по целым(16х16) блокам;
- boolean zero — начальная точка отсчета тайла из набора тайлов.
Использование:
1 | drawTiles(Map.tile1, TexturePack, 0,0, 0,0, 0); |
Map — переменная типа Map.
Рендер
Здесь представлено два варианта: для Двойной Буферизации и для ZБуфера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void render1() { memset(VGA.prebuf, 0, 256*208); //Очищаем второй буфер drawTiles(Map.tile1, TexturePack, 0,0, 0,0, 0); //Рисуем Слой1 drawTiles(Map.tile2, TexturePack, 0,0, 0,0, 0); //Рисуем Слой2 drawTiles(Map.tile3, TexturePack, 0,0, 0,0, 0); //Рисуем Слой3 memcpy(VGA.buf, VGA.prebuf, 256*208); //Копируем буфер2 в буфер1(видео-память) } void render2() { memset(zbuffer, 0, sizeof(zbuffer)); //Очищаем Zbuffer drawTiles(Map.tile3, TexturePack, 0,0, 0,0, 0); //Рисуем Слой3 drawTiles(Map.tile2, TexturePack, 0,0, 0,0, 0); //Рисуем Слой2 drawTiles(Map.tile1, TexturePack, 0,0, 0,0, 0); //Рисуем Слой1 } |