13 декабря 2011 в 22:27

Создание 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 запакованных байта.

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

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

Ничего не остаётся, придётся лезть в ассемблер.
Но об этом как-нибудь в следующий раз.
провод @w23
карма
107,5
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

Комментарии (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
      • +1
        Не хочу превращать информативную статью в саморекламу.
    • +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
    Буду показывать эту статью в ответ на вопрос «что такое хабр?».

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