---== linux demomaking ==---

   В этой статье я расскажу о том как написать движок для демки под linux.
   Стоит заметить,  что на это  сподвигла меня  дока madcr'a из x25zine#2.
   Я расскажу  только основы,  конкретно как  выводить картинку  на экран,
   крутить музыку и т.д.


   Начнём  как  водится  с  главного,  тобишь с графы.  Первое  что  стоит
   рассказать, так это то, как выводить на экран изображение без всяких Х.
   В линухах (и наверное не только в них)  есть такой интерфейс для работы
   с видеокартой как Frame Buffer. Точнее даже не совсем интерфейс, скорее
   девайс /dev/fbX. X на большинстве  машин == 0,  если конечно  на  твоей
   машине не стоит  более одной  видео-карты.  Для начала,  чтобы работать
   с frame_buffer'ом необходимо загрузить линух собственно  в  графическом
   режиме, с поддержкой fb. Скорее всего так уже и есть (если в  консоли у
   тебя  разрешение 800x600,  1024x768 или больше  то всё ок).  Если нет -
   ковыряй конфиг lilo.


   Допустим,  у нас всё готово.  Теперь о том как работать с /dev/fb:  это
   виртуальный  деавйс  символьного  типа,   в  котором  хранится  текущее
   ``содержимое'' экрана. Вот небольшой пример:

   $ cat /dev/fb0 > scrshot

   Таким образом  мы сохраняем  текущее содержимое  экрана ака  скриншот в
   файл scrshot. Соответсвенно, если писать в этот девайс, мы можем менять
   текущую  картинку,  проще  говоря писать  на дисплее.  Теперь  ещё одна
   проблема: а как узнать текущее разрешение и глубину цвета ``на лету'' ?
   Вот для этого мы можем воспользоваться ioctl()-ом.  Советую заглянуть в
   файл /usr/include/linux/fb.h. Там описаны нужные нам структуры:

 struct fb_var_screeninfo {
        __u32 xres;                     /* горизонтальная развёртка     экрана */
        __u32 yres;   /* вертикальная развёртка экрана */
        __u32 xres_virtual;             /* аналогично, но уже виртуальная развёртка     */
        __u32 yres_virtual;
        __u32 xoffset;                      /* offset from virtual to visible */
        __u32 yoffset;                      /* resolution                       */

        __u32 bits_per_pixel;           /* бит на пиксел, ака color depth       */
        __u32 grayscale;                     /* если не 0, цвета переводятся в градации серого */

        struct fb_bitfield red;           /* bitfield in fb mem if true color, */
        struct fb_bitfield green;        /* else only length is significant */
        struct fb_bitfield blue;
        struct fb_bitfield transp;      /* прозрачность */

         __u32 nonstd;                   /* != 0 Non standard pixel format */

         __u32 activate;                /* see FB_ACTIVATE_*            */

         __u32 height;                   /* высота картинки в мм */
         __u32 width;                     /* ширина картинка в мм */

        __u32 accel_flags;              /* acceleration flags (hints)   */

        /* Timing: All values in pixclocks, except pixclock (of course) */
        __u32 pixclock;                 /* pixel clock in ps (pico seconds) */
        __u32 left_margin;              /* time from sync to picture    */
        __u32 right_margin;             /* time from picture to sync    */
        __u32 upper_margin;             /* time from sync to picture    */
        __u32 lower_margin;
        __u32 hsync_len;                /* length of horizontal sync    */
        __u32 vsync_len;                /* length of vertical sync      */
        __u32 sync;                     /* see FB_SYNC_*                */
        __u32 vmode;                    /* see FB_VMODE_*               */
        __u32 reserved[6];              /* Reserved for future compatibility */
 };


 struct fb_fix_screeninfo {
        char id[16];                    /* identification string eg "TT Builtin" */
        unsigned long smem_start;       /* Start of frame buffer mem */
                                        /* (physical address) */
        __u32 smem_len;                 /* Length of frame buffer mem */
        __u32 type;                     /* see FB_TYPE_*                */
        __u32 type_aux;                 /* Interleave for interleaved Planes */
        __u32 visual;                   /* see FB_VISUAL_*              */
        __u16 xpanstep;                 /* zero if no hardware panning  */
        __u16 ypanstep;                 /* zero if no hardware panning  */
        __u16 ywrapstep;                /* zero if no hardware ywrap    */
        __u32 line_length;              /* length of a line in bytes    */
        unsigned long mmio_start;       /* Start of Memory Mapped I/O   */
                                        /* (physical address) */
        __u32 mmio_len;                 /* Length of Memory Mapped I/O  */
        __u32 accel;                    /* Type of acceleration available */
        __u16 reserved[3];              /* Reserved for future compatibility */
 };


   На самом деле самое ценное в структуре  fb_fix_screeninfo  это значение
   line_length, хотя конечно его можно расчитать по простой формуле:

 line_len = fb_var_struct.xres * fb_var_struct.bits_per_pixel / 8;

   Вот небольшой пример того, как получить значения этих структур:

 #includzz

 int main(){
   int fd;
   struct fb_var_screeninfo inf0;
   strcut fb_fix_screeninfo inf1;

   fd = open("/dev/fb0", O_RDWR);

   ioctl(fd, FBIOGET_VSCREENINFO, &inf0);
   ioctl(fd, FBIOGET_FSCREENINFO, &inf1);

   /* теперь мы можем ковырять полученные значения */

   printf("screen resolution: %d x %d (%d bits)\n", inf0.xres, inf0.yres,
   inf0.bits_per_pixel);

   /* выставляем новые значения */

   inf0.xoffset = 0;
   inf0.yoffset = 0;
   ioctl(fd, FBIOPUT_VSCREENINFO, &inf0);

   close(fd);
   return;
 }

   Тестим:

 $ gcc -o test test.c
 $ ./test
 screen resolution: 1024 x 768 (16 bits)
 $


   Отлично, мы можем узнать всю необходимую инфу о настройках fb,а это как
   раз то что нужно. Конечно можно теперь маллочить спейс объёмом
   xres * yres * bits_per_pixel / 8,   прорисовывать  там  каждый  кадр  и
   выводить это дело на экран.  Но есть техника получше. В unix существует
   такой системный вызов,  как mmap().  Для его использования в  сях нужно
   подключить файл <sys/mman.h>.Теперь объявляем, скажем войдовый, pointer
   (назовём  его  scr_ptr).  С  помошью  mmap() мы  можем  отобразить файл
   /dev/fb0 в памяти и уже работать с ним как с локальным массивом,  адрес
   которого после вызова ядро поместит в наш указатель scr_ptr. mmap() сам
   будет обновлять файл по мере изменения его образа в памяти, и наоборот.
   Вот небольшой пример:

   scr_ptr = mmap(0, scr_size, PROT_READ | PROT_WRITE, MAP_SHARED,
   dev_fb0_descriptor, 0);

   Теперь scr_ptr указывает на область памяти,в которой хранится наш кадр.
   Изменяя его мы увидим как mmap() синхронизирует кадры. Пример:

   memset(scr_ptr, 0, scr_size);

   После выполнения данной функции весь экран зальёт белым цветом.Ещё один
   простой пример:

   memset(scr_ptr + 2048*3, 0xff, 2048);

   Четвёртый ряд пикселей с верху станет чёрным. Ну и так далее.  Рулясь с
   оффсетами мы можем гулять по всему массиву. Скажем нам нужно записать в
   точку с координатами (13,37) значение 0xc0 (началом координат считается
   левый верхний угол экрана):

   memset(scr_ptr + 2048*38 + 13, 0xc0, 2);

   Хорошо, мы умеем рисовать на экране.  Кстати,  возможно кого-то смущает
   откуда взялось 2048? Это значение, количество байт на строку, не трудно
   подсчитать,  зная количество бит на пиксел и разрешение,  или взять  из
   структуры fb_fix_screeninfo.


   Естественно, было бы глупо вырисовывать здоровые цветные картинки таким
   допотопным способом (по-умному он называется векторное построение). Для
   этого стоит считывать инфу из файлов и затем, преобразовав её, писать в
   кадр.  Тут есть  некоторые трудности  -  линуксовый Vesafb не даёт  нам
   менять  глубину  цвета без  перезагрузки  системы,  так что  стоит  для
   удобства всю графику хранить в 16-ти битном формате (конкртенее, в unix
   отлично  отрисовывается  палитра в  формате  "WinNT 16bits (565)",  для
   работы с  которым  в  win9x  нужно слить  dll-ки [1])  или ``на  лету''
   конвертировать в него,  что ужасно громоздко. С 16-битным  кодированием
   снова получаем гимор - 16 бит это 2 байта на пиксел,так что, к примеру,
   хранить картинку размером 1024x768 очень громоздко. Можно конечно юзать
   разные  алгоритмы  сжатия,  но  имхо  проще  всю  демку зажать скажем в
   .tar.bz,  чем линковать i.e.  zlib и  распаковывать все  ресурсы наружу
   поотдельности. Скажем, 16-ти битовая растровая картинка размером ~800кб
   после  сжатия  уменьшается до  ~150 kb,  что весьма неплохо.  Ещё  одна
   интересная техника умеьшения размера картинки - использовать zoom перед
   выводом на экран.  Например, имага хранится в 512x384, а при  выводе на
   экран мы дублируем каждый второй пиксел по горизонтали и  каждую вторую
   строку  по  вертикали.   Вообще-то,   качество   будет   дерьмовое  (по
   горизонтали особенно), я лично использую только вертикальный зум,  т.к.
   при нём кадр смотрится вполне сносно. Зато размер файла можно уменьшить
   в целых четыре раза!! Кстати, если исходная  ``маленькая''  картинка не
   изобилирует  кучей  мелких фрагментов,  можно  спокойно зумить  её и по
   горизонтали.


   Ещё пара советов на последок о графике:

     - при создании рисунка,  который хочешь затолкать в демку,  ставь его
      длину и ширину кратными 8, т.к. это сэкономит много нервов и времени
      при обработке (zoom/resize/crop) и выводе такой имаги.


     - растровые картинки (.bmp) если кто не читал доку из прошлого номера
      записываются в  обратном порядке.  Тоесть,  файл надо читать с конца
      блоками размером, равным длине экранной строки в байтах. Пример:


      lseek(fb_device, -2048, SEEK_END); // читаем 1-ю строку
      read(fb_device, scr_ptr, 2048);
      lseek(fb_device, -2048*2, SEEK_END); // читаем 2-ю строку
      read(fb_device, scr_ptr, 2048);
      ...



   Теперь поговорим  о звуке.  В линухе мы можем прямо в консоли запустить
   играть файл формата .au, а последние лет пять и PCM'овые .wav:

 $ cat sound.au > /dev/audio
 $ cat sound.wav > /dev/dsp

   Рекомендуется использовать формат .au, т.к. он старее. Вообще,  судя по
   инклудам /dev/audio как и данный  формат перекочевал со спарков.  Юзать
   mp3 очень гиморно, к тому же .au вполне неплохо  держит качество звука,
   а размер и вообще не может не радовать.


   Но не всё так просто  -  как правило при  выключении/ребуте системы все
   звуковые настройки микшера звуковой карты  (если он есть)  ``слетают''.
   Так что для совместимости стоит написать  функцию, ``включающую'' звук.
   Для этого нам понадобится глянуть в файл <linux/soundcard.h>:

  /*********************************************
   * IOCTL commands for /dev/mixer
   */
     
  /*
   * Mixer devices
   *
   * There can be up to 20 different analog mixer channels. The
   * SOUND_MIXER_NRDEVICES gives the currently supported maximum.
   * The SOUND_MIXER_READ_DEVMASK returns a bitmask which tells
   * the devices supported by the particular mixer.
   */

 #define SOUND_MIXER_NRDEVICES  25
 #define SOUND_MIXER_VOLUME     0
 #define SOUND_MIXER_BASS       1
 #define SOUND_MIXER_TREBLE     2
 #define SOUND_MIXER_SYNTH      3
 #define SOUND_MIXER_PCM                4
 #define SOUND_MIXER_SPEAKER    5
 #define SOUND_MIXER_LINE       6
 #define SOUND_MIXER_MIC                7
 #define SOUND_MIXER_CD         8
 #define SOUND_MIXER_IMIX       9       /*  Recording monitor  */
 #define SOUND_MIXER_ALTPCM     10
 #define SOUND_MIXER_RECLEV     11      /* Recording level */
 #define SOUND_MIXER_IGAIN      12      /* Input gain */
 #define SOUND_MIXER_OGAIN      13      /* Output gain */
 /*
  * The AD1848 codec and compatibles have three line level inputs
  * (line, aux1 and aux2). Since each card manufacturer have assigned
  * different meanings to these inputs, it's inpractical to assign
  * specific meanings (line, cd, synth etc.) to them.
  */
 #define SOUND_MIXER_LINE1      14      /* Input source 1  (aux1) */
 #define SOUND_MIXER_LINE2      15      /* Input source 2  (aux2) */
 #define SOUND_MIXER_LINE3      16      /* Input source 3  (line) */
 #define SOUND_MIXER_DIGITAL1   17      /* Digital (input) 1 */
 #define SOUND_MIXER_DIGITAL2   18      /* Digital (input) 2 */
 #define SOUND_MIXER_DIGITAL3   19      /* Digital (input) 3 */
 #define SOUND_MIXER_PHONEIN    20      /* Phone input */
 #define SOUND_MIXER_PHONEOUT   21      /* Phone output */
 #define SOUND_MIXER_VIDEO      22      /* Video/TV (audio) in */
 #define SOUND_MIXER_RADIO      23      /* Radio in */
 #define SOUND_MIXER_MONITOR    24      /* Monitor (usually mic) volume */

   И немного макросов:

 #define MIXER_READ(dev)                _SIOR('M', dev, int)
 #define SOUND_MIXER_READ_VOLUME                MIXER_READ(SOUND_MIXER_VOLUME)
 #define SOUND_MIXER_READ_BASS          MIXER_READ(SOUND_MIXER_BASS)
 #define SOUND_MIXER_READ_TREBLE                MIXER_READ(SOUND_MIXER_TREBLE)
 #define SOUND_MIXER_READ_SYNTH         MIXER_READ(SOUND_MIXER_SYNTH)
 #define SOUND_MIXER_READ_PCM           MIXER_READ(SOUND_MIXER_PCM)
 #define SOUND_MIXER_READ_SPEAKER       MIXER_READ(SOUND_MIXER_SPEAKER)
 #define SOUND_MIXER_READ_LINE          MIXER_READ(SOUND_MIXER_LINE)
 #define SOUND_MIXER_READ_MIC           MIXER_READ(SOUND_MIXER_MIC)
 #define SOUND_MIXER_READ_CD            MIXER_READ(SOUND_MIXER_CD)
 #define SOUND_MIXER_READ_IMIX          MIXER_READ(SOUND_MIXER_IMIX)
 #define SOUND_MIXER_READ_ALTPCM                MIXER_READ(SOUND_MIXER_ALTPCM)
 #define SOUND_MIXER_READ_RECLEV                MIXER_READ(SOUND_MIXER_RECLEV)
 #define SOUND_MIXER_READ_IGAIN         MIXER_READ(SOUND_MIXER_IGAIN)
 #define SOUND_MIXER_READ_OGAIN         MIXER_READ(SOUND_MIXER_OGAIN)
 #define SOUND_MIXER_READ_LINE1         MIXER_READ(SOUND_MIXER_LINE1)
 #define SOUND_MIXER_READ_LINE2         MIXER_READ(SOUND_MIXER_LINE2)
 #define SOUND_MIXER_READ_LINE3         MIXER_READ(SOUND_MIXER_LINE3)

 /* Obsolete macros */
 #define SOUND_MIXER_READ_MUTE          MIXER_READ(SOUND_MIXER_MUTE)
 #define SOUND_MIXER_READ_ENHANCE       MIXER_READ(SOUND_MIXER_ENHANCE)
 #define SOUND_MIXER_READ_LOUD          MIXER_READ(SOUND_MIXER_LOUD)

 #define SOUND_MIXER_READ_RECSRC                MIXER_READ(SOUND_MIXER_RECSRC)
 #define SOUND_MIXER_READ_DEVMASK       MIXER_READ(SOUND_MIXER_DEVMASK)
 #define SOUND_MIXER_READ_RECMASK       MIXER_READ(SOUND_MIXER_RECMASK)
 #define SOUND_MIXER_READ_STEREODEVS    MIXER_READ(SOUND_MIXER_STEREODEVS)
 #define SOUND_MIXER_READ_CAPS          MIXER_READ(SOUND_MIXER_CAPS)

 #define MIXER_WRITE(dev)               _SIOWR('M', dev, int)
 #define SOUND_MIXER_WRITE_VOLUME       MIXER_WRITE(SOUND_MIXER_VOLUME)
 #define SOUND_MIXER_WRITE_BASS         MIXER_WRITE(SOUND_MIXER_BASS)
 #define SOUND_MIXER_WRITE_TREBLE       MIXER_WRITE(SOUND_MIXER_TREBLE)
 #define SOUND_MIXER_WRITE_SYNTH                MIXER_WRITE(SOUND_MIXER_SYNTH)
 #define SOUND_MIXER_WRITE_PCM          MIXER_WRITE(SOUND_MIXER_PCM)
 #define SOUND_MIXER_WRITE_SPEAKER      MIXER_WRITE(SOUND_MIXER_SPEAKER)
 #define SOUND_MIXER_WRITE_LINE         MIXER_WRITE(SOUND_MIXER_LINE)
 #define SOUND_MIXER_WRITE_MIC          MIXER_WRITE(SOUND_MIXER_MIC)
 #define SOUND_MIXER_WRITE_CD           MIXER_WRITE(SOUND_MIXER_CD)
 #define SOUND_MIXER_WRITE_IMIX         MIXER_WRITE(SOUND_MIXER_IMIX)
 #define SOUND_MIXER_WRITE_ALTPCM       MIXER_WRITE(SOUND_MIXER_ALTPCM)
 #define SOUND_MIXER_WRITE_RECLEV       MIXER_WRITE(SOUND_MIXER_RECLEV)
 #define SOUND_MIXER_WRITE_IGAIN                MIXER_WRITE(SOUND_MIXER_IGAIN)
 #define SOUND_MIXER_WRITE_OGAIN                MIXER_WRITE(SOUND_MIXER_OGAIN)
 #define SOUND_MIXER_WRITE_LINE1                MIXER_WRITE(SOUND_MIXER_LINE1)
 #define SOUND_MIXER_WRITE_LINE2                MIXER_WRITE(SOUND_MIXER_LINE2)
 #define SOUND_MIXER_WRITE_LINE3                MIXER_WRITE(SOUND_MIXER_LINE3)

 /* Obsolete macros */
 #define SOUND_MIXER_WRITE_MUTE         MIXER_WRITE(SOUND_MIXER_MUTE)
 #define SOUND_MIXER_WRITE_ENHANCE      MIXER_WRITE(SOUND_MIXER_ENHANCE)
 #define SOUND_MIXER_WRITE_LOUD         MIXER_WRITE(SOUND_MIXER_LOUD)

 #define SOUND_MIXER_WRITE_RECSRC       MIXER_WRITE(SOUND_MIXER_RECSRC)


   Нас будет  интересовать собственно  общая громкость и  громкость канала
   PCM. Вот как мы можем выставить их значения на максимум:


 int mixer, volume = 0xffffffff;

 mixer = open("/dev/mixer", O_RDWR);

 ioctl(mixer, SOUND_MIXER_WRITE_VOLUME, &volume);
 ioctl(mixer, SOUND_MIXER_WRITE_PCM, &volume);

 close(mixer);


   Уровень громкости  для каждого из каналов находится в значении  volume.
   Т.к. мне было дико лениво смотреть формат этой самой громкости я ставлю
   максимум на сколько позволяет размер аргумента, тоесть 4 байта ).

   На последок немного о работе с клавой.  Существует два  режима вывода в
   консоли  -  графический и текстовый.  Текстовый используется  когда мы,
   скажем просто работаем  в консоли,  при  этом  мы  сразу видим  то  что
   вводим, так называемое эхо. А вот в графическом моде эхо отключено. Вот
   небольшой пример:

 vtty = open("/dev/tty0", O_RDWR);  // /dev/tty0 ссылается на текущий tty

 ioctl(vtty, KDSETMODE, KD_GRAPHICS);  // switch on graph mode
 ...
 ioctl(vtty, KDSETMODE, KD_TEXT);      // back to text mode

   Как видно, всё довольно просто.  Но этого конечно мало,  также мы можем
   выбрать режим чтения с клавиатуры:

  - K_XLATE (дефолтовый), транслирует последовательности нажимаемых клавиш
    через keymap и собственно выдаёт символы.

  - K_UNICODE делает тоже, что и предидущий,  но выдаёт не ASCII,  а UTF-8
    коды.

  - K_RAW, самый илитный режим!!! Вообще никакой трансляции.  Ядро  выдаёт
    не коды сиволов, а коды клавиш.  Причём отсылаются  они не  только при
    нажатии, но и при отпускании кнопы. Очень мощная штука,  вот небольшой
    список кодов клавиш:

       key          push_code          release_code
   +------------------------+---------------------+
    escape                 1                   129
    f1-f10            59..68              187..196
    scroll_lock           70                   198
    backspace             14                   142

   Ну и так далее,  полные списки можно  найти в инклудах (linux/input.h).
   Примеры:

 ioctl (vtty, KDGKBMODE, &mode); // get current keyboard-mode

        ioctl (vtty, KDSKBMODE, K_RAW); // set raw keyboard mode

   Вот собственно и всё,что нужно для написания движка демки. А ``поверх''
   него можно уже навешать всяких эффектов, etc.

 Links:

 [1] dll-ки для работы с 16-битными изображениями в win9x:

  http://fly.to/mwgfx/ - ищите mwgfxdll.exe

 [2] мой пример демки со всеми ресурсами (исходник в инклудах)

  http://defaced.w6.ru/demo/