«на русской сцене мы удивляем друг друга тем, что вообще что-то делаем», © manwe
(из статуса SCRIMERS на demoscene.ru/forum)
Пятиминутка мета: в этом тексте вам, котятки, предстоит прочитать о том, как потратить свое время совершенно неэффективно с точки зрения отношения размера полученного продукта к потраченным времени и усилиям.
Предположим, что мы хотим сделать что-нибудь эдакое, например, интру размером до 4кб, но мы нищеброды, и у нас нет денег на виндовс и видеокарту с шейдерами, поддерживающими ветвления. Или мы просто не хотим брать стандартный набор из apack/crinkler/sonant/4klang/боже-что-там-еще-есть, делать очередную «смотрите все! я тоже умею рэймарчинг дистанс филдс!» и теряться среди десятков-сотен таких же. Ну или же мы просто любим выпендриваться как попало в надежде, что девочки на нас наконец-то обратят внимание.
В общем, не важно. Пусть у нас просто есть какой-нибудь линукс со слабой видеокартой и вся юность впереди. Попробуем со всем этим теперь создать запускаемый файл размером не более, скажем, 1024 байт, который при запуске умудрялся бы каким-нибудь образом создавать и показывать пользователю что-нибудь (эдакое).

Что для этого мы можем задействовать? Прежде всего, у нас есть X11 и OpenGL. Как их можно проинициализировать наименьшей кровью:
Последний вариант, несомненно, самый тру, но он слишком сложен для меня пока. Поэтому будем делать предпоследний.
Давайте расчехлим gcc и попробуем почувствовать всё то, с чем нам предстоит иметь дело. Начнем со скелета нашей интры — инициализируем OpenGL-контекст, укажем viewport и запустим простенький eventloop, завершающий приложение при нажатии любой клавиши:
(Внимательный читатель здесь может обнаружить следующее:
Скомпилируем его:
(Примечание1: для пользователей бинарных дистрибутивов: вам потребуются компилятор (gcc), заголовочные файлы для OpenGL (обычно лежат в mesa) и версия SDL для разработчиков (libsdl-dev, или что-то около того).)
(Примечание2: флаг -m32 нужен только для обладателей 64-битных дистрибутивов)
И ужаснемся тому, что такая простенькая программа занимает почти 7кб!
Чешем репу, понимаем, что нам на фиг не сдался crt и прочие буржуйские излишества, поэтому вырезаем их.
Нужно:
Кроме этого, надо указать gcc параметр -nostartfiles.
И заодно сразу стрипнем бинарник:
Получаем ~4.7 килобайта, что на ~30% лучше, но все еще очень много.
В этой программе полезного кода ещё кот наплакал, и практически все место занимают различные elf-заголовки. Для выпиливания бесполезностей из заголовков существует утилита sstrip из набора elfkickers (http://www.muppetlabs.com/~breadbox/software/elfkickers.html):
Это дает нам ещё примерно 600 байт и опасно приближает размер бинарника к 4кб. И это он ещё ничего полезного-то не делает.
В очередной раз расстраиваемся, смотрим в хекс-редакторе на наш бинарник и обнаруживаем нули. Много их. А кто лучше всех сражается с нулями и прочими одинаковостями?
Правильно:
(Примечание1-очевидность: нужно поставить пакет p7zip. Почему p7zip и такая сложность? Потому что: (а) gzip и прочие сжимают на несколько процентов хуже, (б) надо убрать gz-заголовок с лишними метаданными, вроде имени файла)
(Примечание2: почему вообще gzip, а не, скажем, bzip2? потому что эмпирически p7zip -tGZip дает лучший результат из всего того, что я перепробовал, и распаковщики чего есть более-менее везде)
intro.gz занимает уже около 650 байт! Осталось только сделать его запускаемым. Создадим файл unpack_header со следующим содержанием:
и прилепим этот заголовок к нашему архиву:
Сделаем файл запускаемым и посмотрим на то, что у нас получилось:
Убедимся в том, что эти 700 с копейками байт действительно создают пустое окно.
То, что мы сейчас сделали, называется gzip-дроппингом, и корни его уходят в популярный в свое время (до эры crinkler и ребят) метод cab-dropping, который делал ровно то же самое, но под виндой.
Давайте посмотрим, а много ли можно захерачить в оставшиеся 300 байт. Традиционно делаем следующее: выводим на весь экран glRect, который отрисовывается специальными шейдерами вида color = f(x,y):
Код довольно прозрачный, кроме, пожалуй, момента с varying vec4 p, которая служит для протаскивания нормализованных экранных координат из вершинного шейдера во фрагментный, и делает нас независимыми от физических размеров окна.
Итак, попробуем собрать:
Обнаруживаем, что такой мелочью мы уже хапнули лишних 50 байт, и вылезли за дозволенный один килобайт. «Ты у меня еще попляшешь, попляшешь!», думаем мы и достаем из-за пазухи последний трюк: ручная загрузка всех необходимых функций из динамических библиотек:
Ух! Наконец-то становится более-менее мясисто и запутанно!
Что здесь происходит: мы храним нужные нам библиотеки и функции из них одной строкой через нули-разделители, вместо того, чтобы все эти данные хранить в весьма рыхлых и плохо пакующихся структурах внутри elf-заголовка. Функции из строки загружаются в просто массив указателей, из которого они уже в нужный момент вызываются через макросы. Стоит отметить, что конкретные типы параметров в разумных пределах не имеют значения — они все равно будут выровнены на 4 байта в стеке. А возвращаемое значение так и вообще можно игнорировать, если оно не нужно — все равно вызывающая функция, по соглашениям, не рассчитывает на то, что eax будет сохранен. И тем более не надо проверять на ошибки — на ошибки проверяют только слюнтяи и тряпки, которым никто не даёт.
В команде сборки разденем уж бинарник совсем до гола, оставив ему зависимости только от ld-linux.so (интерпретатор, позволяющий динамическую линковку вообще), и libld.so, в которой лежат функции dlopen и dlsym:
899 байт! Ещё целых сто с жирком байт можно забить Творчеством™!
Что же туда можно уместить? Например, старинный эффект туннеля, от которого всех уже тошнит (да-да, тот самый из скриншота наверху).
Для начала нам нужно протащить время в шейдеры. Мы не будем делать это как ботаны через glUniform, а поступим по-простому, как дворовые пацаны:
Для того, чтобы время не терялось в интерполяции, нужна небольшая помощь со стороны вершинного шейдера:
Всё, теперь в компоненте p.z у нас лежит текущее время. Осталось только прифигачить его к туннелю.
Туннель делается просто — у нас есть угол a=atan(p.x,p.y) и перспектива по-деревенски: z=1./length(p.xy), остается только сгенерировать какую-нибудь текстуру color=f(a,z,t). В сами координаты время, конечно же, тоже можно подмешать. Да и вообще все можно делать, например, бросить читать этот бред нафиг и пойти гулять — там же такая офигенская ясная погода, большие чистые сугробы и сосновый бор в двух шагах от дома!

В общем, методом научно-итерационного тыка получаем такое:
Эта няшка собирается моим gcc-4.5.3 в ровно 1024 запакованных байта.
Окей, думаем мы, вряд ли здесь можно сделать что-нибудь ещё лучше и сложнее. Почти довольные собой лезем смотреть на то, что делают другие ребята в данной категории…
И впадаем в уныние.
Ничего не остаётся, придётся лезть в ассемблер.
Но об этом как-нибудь в следующий раз.
(из статуса SCRIMERS на demoscene.ru/forum)
Пятиминутка мета: в этом тексте вам, котятки, предстоит прочитать о том, как потратить свое время совершенно неэффективно с точки зрения отношения размера полученного продукта к потраченным времени и усилиям.
Предположим, что мы хотим сделать что-нибудь эдакое, например, интру размером до 4кб, но мы нищеброды, и у нас нет денег на виндовс и видеокарту с шейдерами, поддерживающими ветвления. Или мы просто не хотим брать стандартный набор из apack/crinkler/sonant/4klang/боже-что-там-еще-есть, делать очередную «смотрите все! я тоже умею рэймарчинг дистанс филдс!» и теряться среди десятков-сотен таких же. Ну или же мы просто любим выпендриваться как попало в надежде, что девочки на нас наконец-то обратят внимание.
В общем, не важно. Пусть у нас просто есть какой-нибудь линукс со слабой видеокартой и вся юность впереди. Попробуем со всем этим теперь создать запускаемый файл размером не более, скажем, 1024 байт, который при запуске умудрялся бы каким-нибудь образом создавать и показывать пользователю что-нибудь (эдакое).

Что для этого мы можем задействовать? Прежде всего, у нас есть X11 и OpenGL. Как их можно проинициализировать наименьшей кровью:
- Напрмую через Xlib, GLX
+: гаранитированно есть в системе;
-: 11 вызовов только для того, чтобы поднять GL-контекст - glut
+: 4-5 функций для GL-контекста;
-: есть далеко не везде - SDL
+: 2 вызова для контекста, де-факто стандарт, есть практически везде;
-: может где-то вдруг не быть, или в каком-нибудь будущем потенциально поползти совместимость с новыми версиями - забить на библиотеки и делать все руками
+: можно выкинуть динамическую линковку и получить, по слухам (viznut?), около 300 байт для инициализации GL-контекста
-: можно себе всё сломать, пока разберёшься
Последний вариант, несомненно, самый тру, но он слишком сложен для меня пока. Поэтому будем делать предпоследний.
Давайте расчехлим gcc и попробуем почувствовать всё то, с чем нам предстоит иметь дело. Начнем со скелета нашей интры — инициализируем OpenGL-контекст, укажем viewport и запустим простенький eventloop, завершающий приложение при нажатии любой клавиши:
#include <SDL.h>
#define W 1280
#define H 720
#define FULLSCREEN 0//SDL_FULLSCREEN
int main(int argc, char* argv)
{
SDL_Init(SDL_INIT_VIDEO);
SDL_SetVideoMode(W, H, 32, SDL_OPENGL | FULLSCREEN);
glViewport(0, 0, W, H);
SDL_Event e;
for(;;)
{
SDL_PollEvent(&e);
if (e.type == SDL_KEYDOWN) break;
// что-нибудь нарисуем здесь потом
SDL_GL_SwapBuffers();
}
SDL_Quit();
return 0;
}
(Внимательный читатель здесь может обнаружить следующее:
- оптимизм, затмевающий солнце — полное отсутствие проверок на ошибки
- отсутствие проверки на e.type == SDL_QUIT (обработка закрытия окна пользователем), что будет слегка нервировать любителей закрывать приложения кликом на крестик, а не нажатием произвольной клавиши
Скомпилируем его:
(Примечание1: для пользователей бинарных дистрибутивов: вам потребуются компилятор (gcc), заголовочные файлы для OpenGL (обычно лежат в mesa) и версия SDL для разработчиков (libsdl-dev, или что-то около того).)
(Примечание2: флаг -m32 нужен только для обладателей 64-битных дистрибутивов)
cc -m32 -o intro intro.c `pkg-config --libs --cflags sdl` -lGL
И ужаснемся тому, что такая простенькая программа занимает почти 7кб!
Чешем репу, понимаем, что нам на фиг не сдался crt и прочие буржуйские излишества, поэтому вырезаем их.
Нужно:
- Вместо main() объявить фнукцию _start() без аргументов и возвращаемого значения (ее можно переименовать, но зачем?).
- Вместо простого выхода из этой функции сделать системный вызов выхода из приложения (eax=1, ebx=exit_code).
#include <SDL.h>
#define W 1280
#define H 720
#define FULLSCREEN 0//SDL_FULLSCREEN
void _start()
{
SDL_Init(SDL_INIT_VIDEO);
SDL_SetVideoMode(W, H, 32, SDL_OPENGL | FULLSCREEN);
glViewport(0, 0, W, H);
SDL_Event e;
for(;;)
{
SDL_PollEvent(&e);
if (e.type == SDL_KEYDOWN) break;
// что-нибудь нарисуем здесь потом
SDL_GL_SwapBuffers();
}
SDL_Quit();
asm ( \
"xor %eax,%eax\n" \
"inc %eax\n" \
"int $0x80\n" \
);
}
Кроме этого, надо указать gcc параметр -nostartfiles.
И заодно сразу стрипнем бинарник:
cc -m32 -o intro intro.c `pkg-config --libs --cflags sdl` -lGL -Os -s -nostartfiles
Получаем ~4.7 килобайта, что на ~30% лучше, но все еще очень много.
В этой программе полезного кода ещё кот наплакал, и практически все место занимают различные elf-заголовки. Для выпиливания бесполезностей из заголовков существует утилита sstrip из набора elfkickers (http://www.muppetlabs.com/~breadbox/software/elfkickers.html):
sstrip intro
Это дает нам ещё примерно 600 байт и опасно приближает размер бинарника к 4кб. И это он ещё ничего полезного-то не делает.
В очередной раз расстраиваемся, смотрим в хекс-редакторе на наш бинарник и обнаруживаем нули. Много их. А кто лучше всех сражается с нулями и прочими одинаковостями?
Правильно:
cat intro | 7z a dummy -tGZip -mx=9 -si -so > intro.gz
(Примечание1-очевидность: нужно поставить пакет p7zip. Почему p7zip и такая сложность? Потому что: (а) gzip и прочие сжимают на несколько процентов хуже, (б) надо убрать gz-заголовок с лишними метаданными, вроде имени файла)
(Примечание2: почему вообще gzip, а не, скажем, bzip2? потому что эмпирически p7zip -tGZip дает лучший результат из всего того, что я перепробовал, и распаковщики чего есть более-менее везде)
intro.gz занимает уже около 650 байт! Осталось только сделать его запускаемым. Создадим файл unpack_header со следующим содержанием:
T=/tmp/i;tail -n+2 $0|zcat>$T;chmod +x $T;$T;rm $T;exit
и прилепим этот заголовок к нашему архиву:
cat unpack_header intro.gz > intro.sh
Сделаем файл запускаемым и посмотрим на то, что у нас получилось:
chmod +x intro.sh
./intro.sh
Убедимся в том, что эти 700 с копейками байт действительно создают пустое окно.
То, что мы сейчас сделали, называется gzip-дроппингом, и корни его уходят в популярный в свое время (до эры crinkler и ребят) метод cab-dropping, который делал ровно то же самое, но под виндой.
Давайте посмотрим, а много ли можно захерачить в оставшиеся 300 байт. Традиционно делаем следующее: выводим на весь экран glRect, который отрисовывается специальными шейдерами вида color = f(x,y):
#include <SDL.h>
#include <GL/gl.h>
#define W 1280
#define H 720
#define FULLSCREEN 0//SDL_FULLSCREEN
char* shader_vtx[] = {
"varying vec4 p;"
"void main(){gl_Position=p=gl_Vertex;}"
};
char* shader_frg[] = {
"varying vec4 p;"
"void main(){"
"gl_FragColor=p;"
"}"
};
void shader(char** src, int type, int p)
{
int s = glCreateShader(type);
glShaderSource(s, 1, src, 0);
glCompileShader(s);
glAttachShader(p, s);
}
void _start()
{
SDL_Init(SDL_INIT_VIDEO);
SDL_SetVideoMode(W, H, 32, SDL_OPENGL | FULLSCREEN);
glViewport(0, 0, W, H);
int p = glCreateProgram();
shader(shader_vtx, GL_VERTEX_SHADER, p);
shader(shader_frg, GL_FRAGMENT_SHADER, p);
glLinkProgram(p);
glUseProgram(p);
SDL_Event e;
for(;;)
{
SDL_PollEvent(&e);
if (e.type == SDL_KEYDOWN) break;
glRecti(-1,-1,1,1);
SDL_GL_SwapBuffers();
}
SDL_Quit();
asm ( \
"xor %eax,%eax\n" \
"inc %eax\n" \
"int $0x80\n" \
);
}
Код довольно прозрачный, кроме, пожалуй, момента с varying vec4 p, которая служит для протаскивания нормализованных экранных координат из вершинного шейдера во фрагментный, и делает нас независимыми от физических размеров окна.
Итак, попробуем собрать:
cc -m32 -o intro intro.c `pkg-config --libs --cflags sdl` -lGL -Os -s -nostartfiles && \
sstrip intro && \
cat intro | 7z a dummy -tGZip -mx=9 -si -so > intro.gz && \
cat unpack_header intro.gz > intro.sh && chmod +x intro.sh && \
wc -c intro.sh && \
./intro.sh
Обнаруживаем, что такой мелочью мы уже хапнули лишних 50 байт, и вылезли за дозволенный один килобайт. «Ты у меня еще попляшешь, попляшешь!», думаем мы и достаем из-за пазухи последний трюк: ручная загрузка всех необходимых функций из динамических библиотек:
#include <dlfcn.h>
#include <SDL.h>
#include <GL/gl.h>
#define W 1280
#define H 720
#define FULLSCREEN 0//SDL_FULLSCREEN
const char* shader_vtx[] = {
"varying vec4 p;"
"void main(){gl_Position=p=gl_Vertex;}"
};
const char* shader_frg[] = {
"varying vec4 p;"
"void main(){"
"gl_FragColor=p;"
"}"
};
const char dl_nm[] =
"libSDL-1.2.so.0\0"
"SDL_Init\0"
"SDL_SetVideoMode\0"
"SDL_PollEvent\0"
"SDL_GL_SwapBuffers\0"
"SDL_Quit\0"
"\0"
"libGL.so.1\0"
"glViewport\0"
"glCreateProgram\0"
"glLinkProgram\0"
"glUseProgram\0"
"glCreateShader\0"
"glShaderSource\0"
"glCompileShader\0"
"glAttachShader\0"
"glRecti\0\0\0";
// удобство:
#define _SDL_Init ((void(*)(int))dl_ptr[0])
#define _SDL_SetVideoMode ((void(*)(int,int,int,int))dl_ptr[1])
#define _SDL_PollEvent ((void(*)(void*))dl_ptr[2])
#define _SDL_GL_SwapBuffers ((void(*)())dl_ptr[3])
#define _SDL_Quit ((void(*)())dl_ptr[4])
#define _glViewport ((void(*)(int,int,int,int))dl_ptr[5])
#define _glCreateProgram ((int(*)())dl_ptr[6])
#define _glLinkProgram ((void(*)(int))dl_ptr[7])
#define _glUseProgram ((void(*)(int))dl_ptr[8])
#define _glCreateShader ((int(*)(int))dl_ptr[9])
#define _glShaderSource ((void(*)(int,int,const char**,int))dl_ptr[10])
#define _glCompileShader ((void(*)(int))dl_ptr[11])
#define _glAttachShader ((void(*)(int,int))dl_ptr[12])
#define _glRecti ((void(*)(int,int,int,int))dl_ptr[13])
void* dl_ptr[14];
// функция квази-ручной загрузки динамических библиотек
void dl()
{
const char* pn = dl_nm;
void** pp = dl_ptr;
for(;;) // для всех библиотек
{
void* f = dlopen(pn, RTLD_LAZY); // откроем
for(;;) // для всех ее precious функций
{
while(*(pn++) != 0); // пропускаем все байты до следующего за нулем байта
if (*pn == 0) break; // если и он ноль, то это конец текущей so'шки
*pp++ = dlsym(f, pn);
}
// закончили с текущей библиотекой
if (*++pn == 0) break; // если за нулем-разделителем тоже ноль, то все, это конец
}
}
void shader(const char** src, int type, int p)
{
int s = _glCreateShader(type);
_glShaderSource(s, 1, src, 0);
_glCompileShader(s);
_glAttachShader(p, s);
}
void _start()
{
dl();
_SDL_Init(SDL_INIT_VIDEO);
_SDL_SetVideoMode(W, H, 32, SDL_OPENGL | FULLSCREEN);
_glViewport(0, 0, W, H);
int p = _glCreateProgram();
shader(shader_vtx, GL_VERTEX_SHADER, p);
shader(shader_frg, GL_FRAGMENT_SHADER, p);
_glLinkProgram(p);
_glUseProgram(p);
SDL_Event e;
for(;;)
{
_SDL_PollEvent(&e);
if (e.type == SDL_KEYDOWN) break;
_glRecti(-1,-1,1,1);
_SDL_GL_SwapBuffers();
}
_SDL_Quit();
asm ( \
"xor %eax,%eax\n" \
"inc %eax\n" \
"int $0x80\n" \
);
}
Ух! Наконец-то становится более-менее мясисто и запутанно!
Что здесь происходит: мы храним нужные нам библиотеки и функции из них одной строкой через нули-разделители, вместо того, чтобы все эти данные хранить в весьма рыхлых и плохо пакующихся структурах внутри elf-заголовка. Функции из строки загружаются в просто массив указателей, из которого они уже в нужный момент вызываются через макросы. Стоит отметить, что конкретные типы параметров в разумных пределах не имеют значения — они все равно будут выровнены на 4 байта в стеке. А возвращаемое значение так и вообще можно игнорировать, если оно не нужно — все равно вызывающая функция, по соглашениям, не рассчитывает на то, что eax будет сохранен. И тем более не надо проверять на ошибки — на ошибки проверяют только слюнтяи и тряпки, которым никто не даёт.
В команде сборки разденем уж бинарник совсем до гола, оставив ему зависимости только от ld-linux.so (интерпретатор, позволяющий динамическую линковку вообще), и libld.so, в которой лежат функции dlopen и dlsym:
cc -Wall -m32 -c intro.c `pkg-config --cflags sdl` -Os -nostartfiles && \
ld -melf_i386 -dynamic-linker /lib32/ld-linux.so.2 -ldl intro.o -o intro && \
sstrip intro && \
cat intro | 7z a dummy -tGZip -mx=9 -si -so > intro.gz && \
cat unpack_header intro.gz > intro.sh && chmod +x intro.sh && \
wc -c intro.sh && \
./intro.sh
899 байт! Ещё целых сто с жирком байт можно забить Творчеством™!
Что же туда можно уместить? Например, старинный эффект туннеля, от которого всех уже тошнит (да-да, тот самый из скриншота наверху).
Для начала нам нужно протащить время в шейдеры. Мы не будем делать это как ботаны через glUniform, а поступим по-простому, как дворовые пацаны:
float t = _SDL_GetTicks() / 1000. + 1.;
_glRectf(-t, -t, t, t);
Для того, чтобы время не терялось в интерполяции, нужна небольшая помощь со стороны вершинного шейдера:
const char* shader_vtx[] = {
"varying vec4 p;"
"void main(){gl_Position=p=gl_Vertex;p.z=length(p.xy);}"
};
Всё, теперь в компоненте p.z у нас лежит текущее время. Осталось только прифигачить его к туннелю.
Туннель делается просто — у нас есть угол a=atan(p.x,p.y) и перспектива по-деревенски: z=1./length(p.xy), остается только сгенерировать какую-нибудь текстуру color=f(a,z,t). В сами координаты время, конечно же, тоже можно подмешать. Да и вообще все можно делать, например, бросить читать этот бред нафиг и пойти гулять — там же такая офигенская ясная погода, большие чистые сугробы и сосновый бор в двух шагах от дома!

В общем, методом научно-итерационного тыка получаем такое:
#include <dlfcn.h>
#include <SDL.h>
#include <GL/gl.h>
#define W 1280
#define H 720
#define FULLSCREEN 0//SDL_FULLSCREEN
const char* shader_vtx[] = {
"varying vec4 p;"
"void main(){gl_Position=p=gl_Vertex;p.z=length(p.xy);}"
};
const char* shader_frg[] = {
"varying vec4 p;"
"void main(){"
"float "
"z=1./length(p.xy),"
"a=atan(p.x,p.y)+sin(p.z+z);"
"gl_FragColor="
"2.*abs(.2*sin(p.z*3.+z*3.)+sin(p.z+a*4.)*p.xyxx*sin(vec4(z,a,a,a)))+(z-1.)*.1;"
"}"
};
const char dl_nm[] =
"libSDL-1.2.so.0\0"
"SDL_Init\0"
"SDL_SetVideoMode\0"
"SDL_PollEvent\0"
"SDL_GL_SwapBuffers\0"
"SDL_GetTicks\0"
"SDL_Quit\0"
"\0"
"libGL.so.1\0"
"glViewport\0"
"glCreateProgram\0"
"glLinkProgram\0"
"glUseProgram\0"
"glCreateShader\0"
"glShaderSource\0"
"glCompileShader\0"
"glAttachShader\0"
"glRectf\0\0\0";
// удобство:
#define _SDL_Init ((void(*)(int))dl_ptr[0])
#define _SDL_SetVideoMode ((void(*)(int,int,int,int))dl_ptr[1])
#define _SDL_PollEvent ((void(*)(void*))dl_ptr[2])
#define _SDL_GL_SwapBuffers ((void(*)())dl_ptr[3])
#define _SDL_GetTicks ((unsigned(*)())dl_ptr[4])
#define _SDL_Quit ((void(*)())dl_ptr[5])
#define _glViewport ((void(*)(int,int,int,int))dl_ptr[6])
#define _glCreateProgram ((int(*)())dl_ptr[7])
#define _glLinkProgram ((void(*)(int))dl_ptr[8])
#define _glUseProgram ((void(*)(int))dl_ptr[9])
#define _glCreateShader ((int(*)(int))dl_ptr[10])
#define _glShaderSource ((void(*)(int,int,const char**,int))dl_ptr[11])
#define _glCompileShader ((void(*)(int))dl_ptr[12])
#define _glAttachShader ((void(*)(int,int))dl_ptr[13])
#define _glRectf ((void(*)(float,float,float,float))dl_ptr[14])
static void* dl_ptr[15];
// функция квази-ручной загрузки динамических библиотек
static void dl() __attribute__((always_inline));
static void dl()
{
const char* pn = dl_nm;
void** pp = dl_ptr;
for(;;) // для всех библиотек
{
void* f = dlopen(pn, RTLD_LAZY); // откроем
for(;;) // для всех ее precious функций
{
while(*(pn++) != 0); // пропускаем все байты до следующего за нулем байта
if (*pn == 0) break; // если и он ноль, то это конец текущей so'шки
*pp++ = dlsym(f, pn);
}
// закончили с текущей библиотекой
if (*++pn == 0) break; // если за нулем-разделителем тоже ноль, то все, это конец
}
}
static void shader(const char** src, int type, int p) __attribute__((always_inline));
static void shader(const char** src, int type, int p)
{
int s = _glCreateShader(type);
_glShaderSource(s, 1, src, 0);
_glCompileShader(s);
_glAttachShader(p, s);
}
void _start()
{
dl();
_SDL_Init(SDL_INIT_VIDEO);
_SDL_SetVideoMode(W, H, 32, SDL_OPENGL | FULLSCREEN);
_glViewport(0, 0, W, H);
int p = _glCreateProgram();
shader(shader_vtx, GL_VERTEX_SHADER, p);
shader(shader_frg, GL_FRAGMENT_SHADER, p);
_glLinkProgram(p);
_glUseProgram(p);
SDL_Event e;
for(;;)
{
_SDL_PollEvent(&e);
if (e.type == SDL_KEYDOWN) break;
float t = _SDL_GetTicks() / 400. + 1.;
_glRectf(-t, -t, t, t);
_SDL_GL_SwapBuffers();
}
_SDL_Quit();
asm ( \
"xor %eax,%eax\n" \
"inc %eax\n" \
"int $0x80\n" \
);
}
Эта няшка собирается моим gcc-4.5.3 в ровно 1024 запакованных байта.
Окей, думаем мы, вряд ли здесь можно сделать что-нибудь ещё лучше и сложнее. Почти довольные собой лезем смотреть на то, что делают другие ребята в данной категории…
И впадаем в уныние.
Ничего не остаётся, придётся лезть в ассемблер.
Но об этом как-нибудь в следующий раз.