Создание 1k/4k intro для Linux, часть 1

    «на русской сцене мы удивляем друг друга тем, что вообще что-то делаем», © manwe
    (из статуса SCRIMERS на demoscene.ru/forum)

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

    В общем, не важно. Пусть у нас просто есть какой-нибудь линукс со слабой видеокартой и вся юность впереди. Попробуем со всем этим теперь создать запускаемый файл размером не более, скажем, 1024 байт, который при запуске умудрялся бы каким-нибудь образом создавать и показывать пользователю что-нибудь (эдакое).





    Что для этого мы можем задействовать? Прежде всего, у нас есть X11 и OpenGL. Как их можно проинициализировать наименьшей кровью:
    1. Напрмую через Xlib, GLX
      +: гаранитированно есть в системе;
      -: 11 вызовов только для того, чтобы поднять GL-контекст
    2. glut
      +: 4-5 функций для GL-контекста;
      -: есть далеко не везде
    3. SDL
      +: 2 вызова для контекста, де-факто стандарт, есть практически везде;
      -: может где-то вдруг не быть, или в каком-нибудь будущем потенциально поползти совместимость с новыми версиями
    4. забить на библиотеки и делать все руками
      +: можно выкинуть динамическую линковку и получить, по слухам (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 запакованных байта.

    Окей, думаем мы, вряд ли здесь можно сделать что-нибудь ещё лучше и сложнее. Почти довольные собой лезем смотреть на то, что делают другие ребята в данной категории…

    И впадаем в уныние.

    Ничего не остаётся, придётся лезть в ассемблер.
    Но об этом как-нибудь в следующий раз.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 54
    • +26
      Зачетный стиль изложения!
      • 0
        Однозначно! :) респект автору
      • НЛО прилетело и опубликовало эту надпись здесь
      • +3
        Прочитал — захотел даже написать. Ух!
        • +3
          Автор статьи — злой гений. Вот нужно готовиться к зачёту, а я залез в код. Всё правильно делается!
        • +5
          Помню в сырцах демки для спектрума (какой не помню) был коммент
          ; KOLBASING Text (don't try to understand this function name)

          Вообще люди делающие такие вещи (особенно для слабых процев) порой гении в прямом смысле…
          • 0
            ооо, тема!
            когда то под DOSом была у меня директория, в которой были собраны сотни таких демок, там были и плазмы, и огонь, и молнии… и эти эффекты выглядели очень правдоподобно, даже без каких либо библиотек… даже на старых SVGA мониторах… ностальгия… ммм…
            А меня почему то всегда интересовала математика таких эффектов…
            Спасибо за статью, жду вторую часть. хочется увидеть немного демо(сцена)математики :)
            • +3
              А сейчас халява, сэр — расчеты перекидывают на шейдеры…
              • +1
                Эта халява, с одной стороны, достаётся совсем не бесплатно — у неё есть свои особенности и ограничения, и, с другой стороны, она делает возможным то, что раньше таковым не являлось. Так что баланс примерно там же остался :D.
                • 0
                  Всё-таки, накладных расходов на 3д эффекты гораздо меньше.
                  • +1
                    Ну что значит меньше? Меньше для чего? Есть что ли какой то стандарт на то как надо делать графику и что там должно быть?

                    Есть разные номинации и разные весовые категории. Скажем, энтузиасты до сих пор пишут демки под DOS, не прибегая ни к каким ухищрениям. Есть и другие, где наоборот, задается только верхняя планка размера файла. Вот тебе 512 байт и делай что хочешь — у кого круче, тот и выиграл. Единственное ограничение — нельзя выносить логику во внешние библиотеки. То есть SDL и OpenGL можно, ибо это библиотеки общего назначения. А вот вынести 10 мб своего кода в либу и вызвать ее из исполняемого файла в 40 байт это уже, извините, не труъ.

                    Если же говорить о собственно накладных расходах, то и тут не все однозначно. Простейший рейтрейсер можно уложить в сотню байт, тогда как GL код зачастую получается куда более жирным из-за всей этой чехарды с инициализацией. Опять же, шейдеры нельзя в класть бинари.

                    Вы же предлагаете запретить велогонки, оставив только марафон, ибо велосипед — это неспортивно и накладные расходы на перемещение гораздо меньше.
                    • 0
                      Я ничего не предлагаю. Я просто олдфаг по mode X
                      • 0
                        К слову, смачные демки топовые и 1к и 4к и 64к я люблю. и не только олдскульные :)
              • +5
                Хабр торт!
                • 0
                  У меня чего-то не работает(sstrip не было в дистрибутиве, пришлось использовать strip):
                  $ ./intro.sh
                  i915_program_error: Unsupported opcode: IF

                  Показывает только чёрную картинку. Попробовал погуглить, но пока безрезультатно.

                  Да и в размер как-то превышает 1кб, у меня 1456 байт занимает intro.sh(Собирал gcc 4.4.5)

                  А нормально что исполняемый файл intro с таким количеством библиотек связан?
                  • 0
                    >> i915_program_error: Unsupported opcode: IF
                    предполагаю, ему не нравится шейдер.

                    >> исполняемый файл intro с таким количеством библиотек связан
                    а можно вывод ldd?
                    • 0
                      Сейчас нет, попробую собрать на рабочем компе, а когда домой приду — выложу вывод ldd
                    • +1
                      sstrip можно взять здесь: www.muppetlabs.com/~breadbox/software/elfkickers.html
                      Простого strip не хватит — он довольно дурацкий и не вырезает, например, секции, которые для работы программы нафиг не нужны, только для отладки и всякой расширенной информации
                      (между нами, sstrip тоже не идеальный — он вырезает только то, что находится после всех-всех полезных частей программы, совершенно не трогая мусор, лежащий где-то посередине. В принципе, зная формат elf, можно наколбасить полноценный ssstrip, который перемешивал бы содержимое по-взрослому, но так лень, так лень).

                      >> i915_program_error: Unsupported opcode: IF
                      Похоже, что i915 не все функции из там написанных понимает нативно и пытается развернуть их во что-то более сложное, с ветвлениями и прочим неподдерживаемым. Можно поэкспериментировать — аккуратно повыключать «сложные» функции — и понять, в чем именно дело. Но дальше я не подскажу.
                    • 0
                      нужно встряхнуть стариной и покопаться в доках про ELF, посмотреть, откуда берется лишний жир.

                      вполне возможно, что в бинарник вставляется stdlib — я пока не проверил, но явных указаний линкеру не делать этого не вижу.
                      можно поиграться с linker scripts — попробовать выиграть размера бинарника.
                      можно посмотреть в исходники dlsym и попробовать заменить импорт по имени импортом по хешу — древняя вирмейкерская техника, не знаю, правда, заработает ли под линукс.

                      короче, я знаю, чем займусь сегодня вечером, спасибо за напоминание. =)
                      • +1
                        Ровно про это — как наколбасить elf-заголовок ручками на ассемблере — и будет следующая статья. Забегая вперед скажу, что там удается отжать у gcc/ld еще примерно 200-300 байт, что в масштабах 1кб, мягко скажем, очень много.

                        Да, импорт по хешу лучше не делать, он довольно нестабилен оказывается почему-то — то ли между версиями/сборками libc какие-то различия есть, то ли еще что. В любом случае это выливается в то, что старые интры не работают больше — падают в коде подгрузки функций по хешу.
                      • 0
                        Лол, ты ее таки дописал =) Провод, ты будешь писать про фреймбуфер? Или сначала просто про асм?
                        • 0
                          Ну и это, выложил бы ссылки на творения. Или боишься что засмеют? :D
                        • +2
                          Лол, она у меня уже три недели как дописанная почти была, всё руки не доходили перечитать, исправить шероховатости и выложить.
                          Следующая будет про ассемблер и elf. Про фреймбуфер потом. Можешь, в принципе, сам написать, если хочешь.
                          • 0
                            Ну может и напишу, если ноги дойдут. В принципе там особенно и нечего писать, если будет отдельная статья про elf. В общем, подумаю.
                        • 0
                          WeirdWire как всегда на высоте.
                          Изложение доставляет как и много лет назад… :)
                          • 0
                            Ху из weirdwire?
                            Сайт у них интересный)))
                          • 0
                            Помнится, когда читал про интро для Windows там говорили, что, помимо прочего, минимальны зависимости, то есть интро запускается даже при наличии у пользователя kernel.dll и либо opengl32.dll либо d3d9.dll/d3d8.dll.
                            Тут же еще тянется в зависимостях SDL и, возможно, с ним еще много разных пакетов, которые, вероятно, следует так же учитывать при определении размера интро.
                            Получается, что не совсем то интро, что под Windows, или я не прав ???
                            • 0
                              По этому вопросу нет единого мнения. С одной стороны, SDL не является частью системы де-юре — оно не входит ни в какой LSB, есть системы, где оно не поставляется и пр. С другой стороны, де-факто он есть почти везде, относительно стабильный и очень удобный — я даже для больших программ им часто пользуюсь. И в нем нет ничего криминального — он всего-лишь инициализирует GL-контекст и дает минимальный mainloop с обработкой сообщений. Никаких функций, вроде make_me_a_demo(), как в d3dx (шутка), там нет.
                              Ну и люди, делающие интры под линукс, SDL'ем пользуются и, насколько я знаю, еще не получали отказов от организаторов демопати.
                            • 0
                              Там, просто упомяну, что сейчас в мире Linux стандартным архиватором для высокого сжатия считается xz. Не уверен даст ли выигрыш в Вашем случае, но попробовать можно.
                              • 0
                                Под 64 бита собирается, но Segmentation fault :(
                                • 0
                                  Падает при вызове glLinkProgram…
                                  • +1
                                    Боюсь, что тут нужно уже писать полноценное приложение с проверкой на ошибки, логами и прочим — похоже, что что-то по пути валится (не открываются библиотеки, не создается gl-контекст, не компилируются шейдерные программы и т.п.), но интра продолжает смело шагать по трупам как ни в чем не бывало, и в итоге травится трупными ядами.
                                    • 0
                                      В общем, падает из-за самих шейдеров. Если в версию без динамической подгрузки адресов функций вставить последний шейдерный код — падает :( А если в версию с динамической подгрузкой старые шейдеры — не падает. Более того дело в строчке

                                      «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;»

                                      если заменить её на

                                      «p;»

                                      То не падает :)
                                      • 0
                                        Наверное какая-то проблема с дровами.
                                • 0
                                  Спасибо, обязательно попробую, как дойдут руки.
                                • 0
                                  И да, strip --remove-section=.comment немного убирает ненужного. И по идее .shstrtab тоже можно убрать, но не знаю для чего оно нужно.
                                  • 0
                                    Ага, .shstrtab содержит имена секций, её убрать не получится. А жаль :)
                                    • 0
                                      Можно делать сразу strip -s и, по идее, оно вырежет сразу всё, что считает безопасным. Но sstrip всё равно идёт дальше.

                                      Ещё раз забегая вперёд во вторую часть — на самом секции вообще не нужны для запуска программы, и их все можно вымести поганой метлой. И даже от program headers можно безболезненно откусить ощутимый кусок.
                                      • 0
                                        Нет, strip -s не убирает .comments. А насчёт sstrip был невнимателен (не прочитал, что это отдельная программа), подумал опечятка :)
                                  • 0
                                    Про музыку-звук будут статьи?
                                    • 0
                                      Да, конечно. Но, боюсь, в часть 2 не влезет уже и пойдёт только в третью.
                                      • 0
                                        А когда стоит ждать новые части?

                                        /me собрался осуществить мечту детства и выбраться на Assembly хотя бы в этом году :)
                                        • 0
                                          Если на меня ничего не свалится (например, автобус), то планирую каждые две-три недели выкладывать что-нибудь по теме, пока мои знания не закончатся.

                                          Мечта хорошая, но на Assembly только в наступающем году теперь. А в этом один tUM остался из крупных: www.demoparty.net/
                                          • 0
                                            Ну я и имел ввиду следующее лето ;)
                                    • 0
                                      Эх, вспомнил старый-добрый вирус LSD :)
                                      www.youtube.com/watch?v=wM_PDC05Y4o
                                      • +1
                                        Это довольно толстая реализация плазмы :D — судя по видео, её com-файл весит более 1кб. Конечно, для полнценного заражающего вируса это даже неплохо, но для только эффекта — очень много.
                                        Можно посмотреть, что ребята умудряются запихивать в 256 байт (например, puls by Rrrola), или даже меньше — в последнее время сжигает напалмом некто fsqrt (возможно, наш соотечественник). Очень рекомендую к просмотру у него буквально всё. Для этого, правда, потребуется DOSBox (или же машина с досом, но кто их сейчас держит).
                                        • +2
                                          Привет!
                                          Спасибо!
                                          Лучше всего — машина с Windows XP, в ней делаю финальное тестирование и всё идёт быстро.
                                          Многое тормозит в DosBOX, особенно в случаях чтения памяти (особенно нижних сегментов) и операциях fpu. Он в некоторых случаях может быть тормознее XP в десятки раз даже с максимальными настройками на скорость.
                                          Насчёт плазмы — неплохую можно сделать в 54 байта или даже поменьше.
                                          Так что пишите всё на асме и не жалейте времени на оптимизацию =)

                                          ПС Осваиваю псевдо-воксели, сегодня вечером выложу кое-что на 128б.

                                          • 0
                                            Привет!
                                            Windows XP да, тоже хороший вариант. Но, к сожалению, у меня, например, его нет — всё линуксы, да семёрки с макосями.

                                            По асму и оптимизации скажу честно: я завидую :D. Потому что когда писал свои 256 для линукса, обнаружил, что совершенно не хватает ни фантазии, ни знания ассемблера для того, чтобы сделать что-нибудь интересное. Ни времени, чтобы за обозримые неделю-месяц их выработать.
                                            Ну и еще там есть проблемы с производительностью и a/v sync, которые тоже требуют исследований. Но, я думаю, оно стоит того — там могут быть свои плюшки от, скажем, честного защищенного 32-битного режима.
                                            Но, возможно, правда следует попробовать для начала заколбасить любопытное под дос, чтобы научиться и прочувстсвовать.
                                      • +1
                                        я кончил.
                                        ничего из кода практически не понял (не совсем чтобы моя область), но ради такого стиля изложения готов даже перечитать ещё раз.

                                        и даже не думай останавливаться!
                                        • +2
                                          Буду показывать эту статью в ответ на вопрос «что такое хабр?».

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.