Para||elix — платформа демосцены
19 subscribers
14 photos
7 links
Parallelix — demoscene platform for multithreaded native intros
Чат: @parallelix_chat
Автор: @jin_x
Download Telegram
Многопоточный код в прототипах теперь тоже работает 🔥
Прототип немного быстрее.

По линиям (1250 vs 1200 fps):

#include "config.h"
#include "api_impl.h"

extern "C" int __stdcall ParallelixMainExW(void*, const wchar_t*);

void __stdcall draw_pixel(uint32_t thread_index, uint32_t y, uint32_t x, uint32_t group_size, PixelPointer pixel_addr, uint32_t spec_value, ServiceData* service_data)
{
for (uint32_t limit = x + group_size; x < limit; ++x) {
*pixel_addr.pixel8bpp++ = static_cast<uint8_t>(((x ^ y) + spec_value) >> 4);
}
}

void __stdcall intro(ServiceData* service_data)
{
using namespace api;
exported::InitVideo({ InitVideoOptions::ColorMode::palette_8bpp, VideoMode::default_full, 0,
InitVideoOptions::use_video_mode, InitVideoOptions::LineAlignment::align_default });
for (int i = 0; ; ++i) {
// 15 = group by lines
exported::ParallelPixels({ 15, ParallelPixelsOptions::ImageSizeType::screen_resolution, true }, draw_pixel, {}, i);
exported::DisplayFrame({ true });
}
}

int main()
{
ParallelixMainExW(intro, L". -kc -fr");
}


По 1 пикселю (800 vs 780 fps):

#include "config.h"
#include "api_impl.h"

extern "C" int __stdcall ParallelixMainExW(void*, const wchar_t*);

void __stdcall draw_pixel(uint32_t thread_index, uint32_t y, uint32_t x, uint32_t group_size, PixelPointer pixel_addr, uint32_t spec_value, ServiceData* service_data)
{
*pixel_addr.pixel8bpp = static_cast<uint8_t>(((x ^ y) + spec_value) >> 4);
}

void __stdcall intro(ServiceData* service_data)
{
using namespace api;
exported::InitVideo({ InitVideoOptions::ColorMode::palette_8bpp, VideoMode::default_full, 0,
InitVideoOptions::use_video_mode, InitVideoOptions::LineAlignment::align_default });
for (int i = 0; ; ++i) {
// 0 = group by 1 pixel (0 << 1)
exported::ParallelPixels({ 0, ParallelPixelsOptions::ImageSizeType::screen_resolution, true }, draw_pixel, {}, i);
exported::DisplayFrame({ true });
}
}

int main()
{
ParallelixMainExW(intro, L". -kc -fr");
}


По 64 пикселя (1180 vs 1125 fps):

#include "config.h"
#include "api_impl.h"

extern "C" int __stdcall ParallelixMainExW(void*, const wchar_t*);

void __stdcall draw_pixel(uint32_t thread_index, uint32_t y, uint32_t x, uint32_t group_size, PixelPointer pixel_addr, uint32_t spec_value, ServiceData* service_data)
{
for (uint32_t limit = x + group_size; x < limit; ++x) {
*pixel_addr.pixel8bpp++ = static_cast<uint8_t>(((x ^ y) + spec_value) >> 4);
}
}

void __stdcall intro(ServiceData* service_data)
{
using namespace api;
exported::InitVideo({ InitVideoOptions::ColorMode::palette_8bpp, VideoMode::default_full, 0,
InitVideoOptions::use_video_mode, InitVideoOptions::LineAlignment::align_64b });
for (int i = 0; ; ++i) {
// 6 = group by 64 pixels (1 << 6)
exported::ParallelPixels({ 6, ParallelPixelsOptions::ImageSizeType::screen_resolution, true }, draw_pixel, {}, i);
exported::DisplayFrame({ true });
}
}

int main()
{
ParallelixMainExW(intro, L". -kc -fr");
}
Please open Telegram to view this post
VIEW IN TELEGRAM
Теперь можно загружать вот такие DLL-ки через командную строку (компилится в 2.5 КБ с /NODEFAULTLIB) ☺️

#include "config.h"
#include "api_impl.h"

extern "C" BOOL APIENTRY _DllMainCRTStartup(HMODULE hModule, DWORD fdwReason, LPVOID lpvReserved)
{
return TRUE;
}

/*
DLLEXPORT BOOL __stdcall ParallelixCheck(ServiceData* service_data)
{
return TRUE;
}
*/

void __stdcall draw_pixel(uint32_t thread_index, uint32_t y, uint32_t x, uint32_t group_size, PixelPointer pixel_addr, uint32_t spec_value, ServiceData* service_data)
{
*pixel_addr.pixel8bpp = static_cast<uint8_t>(((x ^ y) + spec_value) >> 4);
}

DLLEXPORT void __stdcall ParallelixStart(ServiceData* service_data)
{
using namespace api;
exported::InitVideo({ InitVideoOptions::ColorMode::palette_8bpp, VideoMode::default_full, 0,
InitVideoOptions::use_video_mode, InitVideoOptions::LineAlignment::align_default });
for (int i = 0; ; ++i) {
// 0 = group by 1 pixel (1 << 0)
exported::ParallelPixels({ 0, ParallelPixelsOptions::ImageSizeType::screen_resolution, true }, draw_pixel, {}, i);
exported::DisplayFrame({ true });
}
}


P.S. Функция ParallelixCheck специально закомменчена, т.к. является необязательной, и при её отсутствии считается, что всё ок.
Please open Telegram to view this post
VIEW IN TELEGRAM
Мысли вслух.

🔹 Хочу перевести код под компилятор Intel и сравнить производительность с Visual C++.

🔹 По части распараллеливания циклов думаю подрубить Intel TBB (давно уже хочу изучить его). И также сравнить с моей реализацией 🙂

Второе пока не горит, есть более приоритетные задачи.
А вот первое уже можно сделать, чтоб потом пришлось меньше исправлять код.
Please open Telegram to view this post
VIEW IN TELEGRAM
Задумался вот о чём.
Планируется, что Parallelix будет не только запускать интро, но и выполнять ряд других функций: проверять файл на совместимость, упаковывать и распаковывать, добавлять и удалять заголовок, проверять хэши, даже загружать файлы из интернета :)

Напомню, что сейчас платформа состоит из 2-х файлов (потом будет ещё документация и т.п.):
🔸 parallelix.dll — содержит парсер командной строки, загрузчик и среду исполнения, т.е. всё :)
🔸 parallelix.exe — загружает DLL и передаёт ей командную строку.

Зачем так сложно?

Платформа использует фиксированные линейные адреса для хранения кода API, загрузки кода интро, видеобуфера и пр. Так проще, поскольку не нужно делать интро позиционно-независимым.

✅️ Исторически было решено использовать довольно-таки малые адреса, которые при загрузке программы оказывались занятыми (т.к. код инициализации DLL эти адреса "прибирал к рукам"). Поэтому EXE-шник резервировал нужные адреса, а потом динамически загружал наш основной DLL. Сейчас я снова решил поменять адреса, поэтому в принципе, такой проблемы быть не должно.

✅️ Вторая причина — более веская: возможность создания прототипов, которые будут использовать API из DLL. Т.е. мы делаем интро либо в виде DLL, либо в виде EXE-шника, который использует функции из parallelix.dll. В первом случае мы передаём наш DLL с прототипом в качестве параметра для parallelix.exe (аналогично тому, как мы передаём имя сайз-закодированного файла с плоским кодом). Во втором случае мы имеем полноценно работающий EXE-шник (и тут parallelix.exe нам вообще не нужен).


Так вот, вернёмся к исходному вопросу. Есть несколько вариантов организовать возможность запускать разные функции.

Засунуть все эти функции в ту же DLL-ку. Да, она будет больше в размере, но глобально: 500 КБ или 5 МБ она будет весить — не особо-то и важно. И тут есть 2 варианта:

1️⃣ Использовать команды в, пардон, командной строке, т.е. формат parallelix command [files and options]:
parallelix run intro.plx -kc -fr
parallelix run https://github/vasya/scene/intro.plx --check-hash md5:d41d8cd98f00b204e9800998ecf8427e
parallelix pack intro.plx -m 5 -o intro_packed.plx
parallelix remove-header intro.plx
parallelix version


Здесь для облегчения жизни можно пойти на хитрость: разрешить опускать команду run. Т.о. следующие 2 команды будут работать одинаково:
parallelix intro.plx -kc -fr
parallelix run intro.plx -kc -fr


Для надёжности (вдруг кто-то решит назвать файл именем pack без расширения) все команды можно предварять каким-то символом, например, слешем:
parallelix intro.plx -kc -fr
parallelix /run intro.plx -kc -fr
parallelix /run https://github/vasya/scene/intro.plx --check-hash md5:d41d8cd98f00b204e9800998ecf8427e
parallelix /pack intro.plx -m 5 -o intro_packed.plx
parallelix /remove-header intro.plx
parallelix /version

2️⃣ В случае необходимости использовать "специальную" функцию (не запуск), использовать опции вместо команд:
parallelix intro.plx -kc -fr
parallelix https://github/vasya/scene/intro.plx --check-hash md5:d41d8cd98f00b204e9800998ecf8427e
parallelix --pack intro.plx -m 5 -o intro_packed.plx
parallelix intro.plx --pack -m 5 -o intro_packed.plx
parallelix --remove-header intro.plx
parallelix --version
Please open Telegram to view this post
VIEW IN TELEGRAM
Ну и ещё пара вариантов...

3️⃣ Сделать для "специальных" функций отдельную прогу (при этом и основной DLL будет весить меньше):
parallelix intro.plx -kc -fr
parallelix https://github/vasya/scene/intro.plx --check-hash md5:d41d8cd98f00b204e9800998ecf8427e
parallelix-tools pack intro.plx -m 5 -o intro_packed.plx
parallelix-tools remove-header intro.plx
parallelix --version
parallelix-tools --version


Или даже несколько прог:
parallelix intro.plx -kc -fr
parallelix https://github/vasya/scene/intro.plx --check-hash md5:d41d8cd98f00b204e9800998ecf8427e
parallelix-pack intro.plx -m 5 -o intro_packed.plx
parallelix-remove-header intro.plx
parallelix --version
parallelix-pack --version


4️⃣ Совместить 1-й (или 2-й) и 3-й варианты. EXE-шник содержит в себе парсер команды (при её наличии) и запускает тул в случае, если это не /run.
Т.е. следующие пары команд будут аналогичными:
parallelix intro.plx -kc -fr
parallelix /run intro.plx -kc -fr

parallelix /pack intro.plx -m 5 или(?) parallelix --pack intro.plx -m 5
parallelix-tools pack intro.plx -m 5


parallelix /remove-header intro.plx или(?) parallelix --remove-header intro.plx
parallelix-tools remove-header intro.plx


Как вариант, вместо parallelix-tools.exe можно наделать DLL-ок и использовать только команды, каждая из которых будет грузить свою DLL:
parallelix.exe — "менеджер" запуска всех команд
parallelix.dll — загрузчик кода, среда исполнения
parallelix-packer.dll — упаковщик-распаковщик
parallelix-header.dll — работа с заголовками
и т.д.
Ну или вообще оставить 2 DLL-ки: parallelix.dll и parallelix-tools.dll.
Заметьте, что в этих случаях все DLL будут загружаться динамически, т.е. если у вас нет какой-то DLL-ки, которая вам не нужна, система при запуске ругаться не будет.

————————————————————

Какой из вариантов вам кажется более удобным для использования? 😁
Хотелось бы мнений с обоснованием.
Please open Telegram to view this post
VIEW IN TELEGRAM
Загрузил немного предыдущими лонг-постом? 😁

Наверное, лучше сделать так:
🔹 parallelix.exe + parallelix.dll
🔹 parallelix-tools.exe

Форматы запуска:

🔸 parallelix [/command] [files, options...]
Если команда не указана (а она может быть только первым параметром), подразумевается /run, т.е. загрузка parallelix.dll и передача ему управления. Если указана другая команда (кроме некоторых специальных типа /help, /about, /version и т.п. — эти команды отрабатывает сам exe-шник), запускается parallelix-tools.exe, которому передаётся вся командная строка.

Примеры:
parallelix intro.plx -kc -fr
parallelix /run https://github/vasya/scene/intro.plx --check-hash md5:d41d8cd98f00b204e9800998ecf8427e
parallelix /pack intro.plx -m 5 -o intro_packed.plx
parallelix /remove-header intro.plx
parallelix /version


🔸 parallelix-tools /command [files, options...]
Здесь команда обязательна, и она тоже должна быть первым параметром. Если указана /run, запускается parallelix.exe, которому передаётся вся командная строка.

Примеры:
parallelix-tools /run intro.plx -kc -fr
parallelix-tools /run https://github/vasya/scene/intro.plx --check-hash md5:d41d8cd98f00b204e9800998ecf8427e
parallelix-tools /pack intro.plx -m 5 -o intro_packed.plx
parallelix-tools /remove-header intro.plx
parallelix-tools /version


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

Ставьте лайк, если норм 👍
Работа пока стоит, а идеи в голову лезут 😄

1️⃣ Многослойность!

Фреймбуфер у меня большой (256 МБ) — это позволит хранить несколько кадров, несколько слоёв либо использовать масштабирование (зум).

С помощью специальной API-функции можно будет накладывать на область итогового слоя (по смещению 0; либо по любому другому смещению) блок из другой области памяти. При этом можно указать цвет прозрачности (опционально), а также размер области источника и приёмника. Вот только х/з что там с GDI-функциями на эту тему. Хорошо было бы вообще иметь возможность использовать 8 лишних бит в 32-битном режиме цветности как альфа-канал. Ну и, разумеется, указывать способы наложения (включая AND, OR, XOR и пр).

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

Пока писал, вспомнил свою интру DEEP TRIP (как пример, где можно было бы такое использовать), в которой вместо чёрного цвета фрактала Жюлиа рисуется пламя.
Ещё один пример — игра Тетрис. Не нужно рисовать большие квадраты, достаточно 1 пикселя.
На итоговый слой копируется фон, далее игровое поле (с масштабированием, без размытия). Ну и потом можно вывести счёт и т.п.

2️⃣ Спрайты!

Это почти то же самое (в целом можно даже совместить это всё в одной функции), только здесь появляются дополнительные плюшки. Например, глубина цвета спрайта может быть меньше глубины цвета видеорежима. Можно, скажем, использовать 1 бит на пиксель и дополнительно указать 1 или 2 цвета (в зависимости от необходимости прозрачности).

3️⃣ Шрифты!

О шрифтах я думаю уже давно. Их должно быть несколько: моноширинный, без засечек, с засечками и какой-нибудь интересный. Возможно, какой-то из них будет иметь вариант курсива, либо курсовом будет просто смещение верхней и нижней частей. И каждый из них должен иметь 2-4 размера (скорее всего, 4 размера моно и по 2-3 остальных).

Масштабировать их можно будет, но в естественном размере они, конечно, будут выглядеть лучше. Не знаю только насчёт полутонов (видеорежимы же могут быть и с палитрой, так что скорее нет, чем да). Разумеется, с указанием цвета текста и фона (либо прозрачный фон/текст). С возможностью поворота на угол, кратный 90 градусам.

Большой вопрос стоит относительно набора символов. Их должно быть 256, но туда нужно уместить и русские буквы, и буквы разных европейских народов (без иероглифов, арабских, грузинских, еврейских букв и т.п... греческие можно, если хватит места, но вряд ли его хватит). Пожалуй, стоит ввести диакритические знаки, которые будут накладываться поверх предыдущего символа. Но хорошо ли это будет выглядеть на немоноширинных шрифтах (хотя, Unicode же как-то приспособили)?

Ну вот как-то так...
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Салют!
Друзья, я снова сел за проект (наконец-то), уже кое-что перелопатил, подробнее чуть позже.

А пока важно вот что.
Я делаю область констант (чтобы не засорять код интро часто используемыми числами). Есть возможность разместить 64+15 = 79 чисел. По факту их будет гораздо больше, но уже за пределами области «удобной» адресации. Хотя можно разместить и 256 чисел (с «относительно» удобной адресацией) 😁

Сейчас планирую разместить там:
🟡 Последовательности 0...15 типа float и int32 (нужны в т.ч. для групповой обработки пикселей через SSE/AVX/AVX-512, чтобы добавлять их к номерам пикселей) [32 шт].
🟡 Обратные величины чисел 1...10 [10 шт].
🟡 Числа -4, -2, -1, 1.25, 1.5, 2.5, 7.5, 12.5, 16, 25, 32, 50, 64, 128, 256, 100, 125, 250, 500, 1000 (кроме нуля) и их обратные величины (кроме -1) [это 39 шт].
🟡 Маску знака (0x80000000), π, 2π, π/2, π/3, π/4, π/180, 180/π, sqrt(2), sqrt(3), 1/sqrt(2), 1/sqrt(3), sqrt(3)/2, 2/sqrt(3) (значения sin, cos, tg, cosec, sec, ctg стандартных углов), e, φ (золотое сечение) [14 шт].
🟡 Значения log2(e), log2(10), ln(2), ln(10), lg(2), lg(e) [6 шт].

Итого пока получается 101 шт. Можно ещё добавить степени 3, 5, степени 2 минус 1, больше степеней 2, 10, больше отрицательных чисел и их обратные величины. И т.д. Короче, добить «чем-нибудь» до 256 и (гораздо) более я смогу.

Главный вопрос...
Какие константы ещё могут быть полезны для генерации изображений, анимации, трёхмерных объектов, векторных и матричных вычислений?
🟡 Можно добавить постоянные Эйлера (γ), Каталана (G), Фейгенбаума (α, β) и пр. Но нужны ли они?
🟡 Таблицы sin, cos, tg, cosec, sec, ctg и arc* я сделаю, но по «более дальним» адресам.
🟡 Коэффициенты для вычисления функций через ряды Маклорена (Тейлора) будут, тоже «туда дальше».

Кто шарит во всех этих математических делах, гляньте, плиз, вот это список.
Если что-то вы сочтёте полезным, напишите в чат названия констант.
Может, есть что-то, что я не знаю / не пришло в голову? Отдельные числа, таблицы, ряды...
В общем, надеюсь на вашу активность на эту тему 🤝
Please open Telegram to view this post
VIEW IN TELEGRAM
Я портировал Space Fungus на Parallelix с использованием AVX (вычисляется 8 точек за раз).
Мой комп выдаёт 450 fps в FullHD (1920x1080) / TrueColor на 20-потоковым процессоре 😁
P.S. Расчётом занимается только CPU, GPU не задействован!

Шейдер-прототип выглядит так:
#define ITERATIONS 20

vec3 kaliset(vec3 p, vec3 u){
vec3 c=p;
for(int i=0;i<ITERATIONS;i++){
float len=length(p);
p=abs(p)/(len*len)-u; // abs(p)/dot(p,p)-u
c+=p;
}
return c/float(ITERATIONS);
}

void mainImage(out vec4 c, in vec2 xy)
{
vec2 uv=vec2(xy.x/iResolution.x-0.5,(xy.y-iResolution.y*0.5)/iResolution.x);
float m=iTime/60.0;
vec3 p=vec3(uv*iTime,0.1);
vec3 u=vec3(1.0,1.0,0.1)*m;
c.xyz=kaliset(p,u);
}
Please open Telegram to view this post
VIEW IN TELEGRAM
Друзья, две новости.

Новость 1️⃣ (об этом я частично писал в чате).
Платформа Para||elix будет выпускаться в трёх редакциях: «A», «B» и «C».

🔤SCETIC EDITION — версия с сокращёнными возможностями. Уменьшенное кол-во API-функций (нет поддержки спрайтов, генераторов палитры, функций рисования и пр.), малый набор палитр, один-два шрифта, отсутствуют области констант, встроенные текстуры.
Для тех, кто ценит простоту и любит делать всё своими руками.

🔤ASIC EDITION — стандартная версия. Будут добавлены слои и спрайты, несколько стандартных палитр + простые генераторы палитры, больше шрифтов. Доступ к ограниченному кол-ву констант (что особенно удобно при работе с SIMD). Стандартные текстуры (рандом, шум Перлина). Сжатия кода. Конфигуратор.
Для тех, кто любит удобства, но без перегибов.

🔤OMPLETE EDITION — всё по максимуму. Функции рисования (GDI, OpenGL), байт-коды FPU/SIMD, bytebeat/floatbeat с компиляцией, генератор нот. Генераторы сложных палитр, математические функции. Доступ к WinAPI. Большой массив констант. Конфигуратор.
Для тех, кто хочет выжать максимум из платформы. Удобно для создания демо.

Версии платформы будут называться в честь великих художников, музыкантов, математиков и пр.
Например, Aivazovsky, Mandelbrot, Dali, Fibonacci, Mozart, Van Gogh и т.д.

——————————

Новость 2️⃣.
Я снова изменил значение регистра EBP — базового регистра для доступа к API и структурам.
А также адрес загрузки интро. Думаю, на этот раз окончательно 😁. Плюс ко всему, на старте EDX = EBP + 0x100, что будет облегчать доступ к дальним адресам структур (палитре, константами, коду).

Расклад такой:
ebp   = 0x038E1C80 (API table)
ebp*2 = 0x071C3900 (API table 2)
epb*3 = 0x0AAA5580 (constant area)
epb*4 = 0x0E387200 (reserved)
ebp*5 = 0x11C68E80 (service data)
ebp*8 = 0x1C70E400 (reserved)
epb*9 = 0x1FFF0080 (code start + 0x80)


Структура памяти (без учёта API, констант, сервисных данных):
User textures        : 0x12000000 (6*16 MB)
Internal textures 1 : 0x18000000 (4*16 MB) — extra textures
Internal textures 2 : 0x1D000000 (2*16 MB) — random / Perlin noise
Internal fonts : 0x1F000000 (15.875 MB)
Extra variable area : 0x1FFE0000 (64 KB)
Code start address : 0x1FFF0000 (64 KB)
Large user data area : 0x20000000 (512 MB)
Frame buffer address : 0x40000000 (256 MB)
Second frame buffer : 0x50000000 (256 MB) — not fixed sometimes
Huge memory heap : 0x80000000 (2046 MB)


Код записывается по адресу 0x1FFF0000. Если его размер не превышает 64 КБ, он не перекрывает область пользовательской памяти для данных в 512 МБ. Кроме того, такой адрес позволяет использовать [ebp+ebp*8] (адрес 0x1FFF0080) для доступа к первым 256 байтам кода, а также [edx+ebp*8] для доступа к следующим 256 байтам (ну и [ebp+edx*8], [edp+edx*8]). Экономия в среднем 2 байта (иногда 1, иногда даже 3-4) при доступе к данным. Т.к. на старте ESI = code start, 128 байт перед кодом также будут в лёгком доступе (а вообще там 64 КБ для дополнительных переменных, опять же не пересекающихся с 512 МБ). Кстати, кому мало 512 МБ, те могут вызвать API-функцию а-ля malloc, с помощью которой можно выделить почти 2 ГБ дополнительной памяти (при наличии 64-битной винды).

Раньше я думал о том, чтобы загружать код по адресу 0x10000000 либо 0x20000000. Значение 0x10000080 тоже делится на 9 (при старте с позиции 0x10000000), но там могут возникнуть проблемы при резервировании младших адресов (начиная с EBP). А вот 0x20000080 уже на 9 не делится. Но в целом адрес старта 0x1FFF0000 даже лучше, т.к. как я уже сказал, в этом случае код до 64 КБ не перекрывает большой массив для данных. Использовать для фрейм буфера адреса 0x60000000 и выше тоже не стоит, т.к. это может конфликтовать с DLL (в т.ч. системными) — проверено.

Сервисные данные адресуются через [ebp+ebp*4] и [edx+ebp*4]. После 256 байт основных данных идут дополнительные 128, и там же начинается палитра, первые 32 цвета (128 байт) будут в лёгком доступе.

Но самая красота — доступ к константам: [ebp+ebp*2], [edx+ebp*2], [ebp+edx*2], [edx+edx*2], получается целый килобайт без дыр!

По-моему, получилось красиво 😉
Please open Telegram to view this post
VIEW IN TELEGRAM