Тайловая графическая система /0x02

Если ваш компилятор не распознает некоторые типы данных, то вот несколько строчек кода:

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
}

Все части статьи.

Вы можите оставить комментарий, или поставить трэкбек со своего сайта.

Написать комментарий

XHTML: Вы можете использовать эти теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Bug Report
Локализовано: шаблоны Wordpress