Кстати, кто знает, каков диапазон базового адреса загрузки PE-образа при ASLR ?
Если он может вылетать за пределы
Если он может вылетать за пределы
0x01000000
, думаю, стоит отключить его, чтобы он не порушил всю идею организации памяти 🤔Сделано резервирование и выделение памяти подо всё необходимое (код и данные, видеопамять, API). А также загрузка кода по адресу
По сути, осталось сделать самое сложное и важное — API😁
Но каркас для API (адреса, о которых я писал в прошлый раз и заглушки функций в виде инструкций
0x10000000
и его запуск. SEH активирован, результат работы которого вы можете видеть в MessageBox'е (после деления на ноль в intro, код которого также прикрепил). Парсинг параметров командной строки допилил ещё вчера.По сути, осталось сделать самое сложное и важное — API
Но каркас для API (адреса, о которых я писал в прошлый раз и заглушки функций в виде инструкций
ret
) уже готов.Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Сейчас у меня большая загрузка разными делами, и времени на проект очень мало.
Но я успел выделить код intro в отдельный поток, сделать подготовку вспомогательных потоков, цикл обработки сообщений и проброс исключений из любого потока в основной поток (вернее, в обработчик сообщений). Также добавил экстренное закрытие intro по горячей клавише (пока без настройки, только Win+Ctrl+X) и ещё несколько опций.
Да, чуть не забыл. Упаковал 64-мегабайтный каркас API в чуть менее 600-байтный код (простым, RLE-подобным алгоритмом) и сделал распаковщик в память. Всё работает. Т.е. прямо сейчас можно запустить код такого вида (fasm):
Все API пока содержат по-прежнему только
____________________
Я решил вместе с версиями давать программе кодовые имена: виды птиц🕊
P.S. Кстати, вы знали, что волнистый попугайчик может развивать скорость до 100-120 км/ч? Прямо как морская чайка. Это примерно в 3 раза быстрее воробья 🦜
А крошечная колибри, летающая обычно со скоростью 48-85 км/ч, во время коротких перелётов может достигать скорости аж до 150 км/ч, делая при этом 50-80 взмахов крыльями в секунду (а во время ухаживания — до 200 взмахов, прямо как пчёлы) 🐝
Но я успел выделить код intro в отдельный поток, сделать подготовку вспомогательных потоков, цикл обработки сообщений и проброс исключений из любого потока в основной поток (вернее, в обработчик сообщений). Также добавил экстренное закрытие intro по горячей клавише (пока без настройки, только Win+Ctrl+X) и ещё несколько опций.
Да, чуть не забыл. Упаковал 64-мегабайтный каркас API в чуть менее 600-байтный код (простым, RLE-подобным алгоритмом) и сделал распаковщик в память. Всё работает. Т.е. прямо сейчас можно запустить код такого вида (fasm):
format binary as 'll'
use32
call ebp
call dword [ebp-128]
call dword [ebp+eax] ; eax при запуске = 0
jmp $
Все API пока содержат по-прежнему только
ret
, а выйти из jmp $
можно клавишей Win+Ctrl+X.____________________
Я решил вместе с версиями давать программе кодовые имена: виды птиц
P.S. Кстати, вы знали, что волнистый попугайчик может развивать скорость до 100-120 км/ч? Прямо как морская чайка. Это примерно в 3 раза быстрее воробья 🦜
А крошечная колибри, летающая обычно со скоростью 48-85 км/ч, во время коротких перелётов может достигать скорости аж до 150 км/ч, делая при этом 50-80 взмахов крыльями в секунду (а во время ухаживания — до 200 взмахов, прямо как пчёлы) 🐝
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Как насчёт текстур?
Почему бы среде исполнения не сгенерировать случайные байты для intro? А шум Перлина? Или даже позволить пользователю (например, организатору пати) загружать свои текстуры (BMP/бинари, да хоть саундтрек!).
Тем более, у нас есть 4 незадействованные области примерно по 15МБ в зоне API (а в расширенном API их будет 5), чего им простаивать? 😁
Но это, скорее всего, будет реализовано позже, не в первой версии. Сейчас это не первостепенное.
Почему бы среде исполнения не сгенерировать случайные байты для intro? А шум Перлина? Или даже позволить пользователю (например, организатору пати) загружать свои текстуры (BMP/бинари, да хоть саундтрек!).
Тем более, у нас есть 4 незадействованные области примерно по 15МБ в зоне API (а в расширенном API их будет 5), чего им простаивать? 😁
Но это, скорее всего, будет реализовано позже, не в первой версии. Сейчас это не первостепенное.
Продумываю API.
Пока условно разбил основные функции на несколько групп:
🔸 0…-0x0F — control and miscellaneous functions;
🔸 -0x10…-0x2F — threading functions;
🔸 -0x30…-0x3F — video functions;
🔸 -0x40…-0x4F — sound functions;
🔸 -0x50…-0x5F — time functions;
🔸 -0x60…-0x6F — keyboard/mouse functions;
🔸 -0x70…-0x7F — file functions.
Может, чего-то забыл?
API FUNCTION CALLS
API functions are called by
For the best comfort use
Examples:
🔘
🔘
🔘
🔘
CONTROL AND MISCELLANEOUS FUNCTIONS
🔹 0 (
🔹 -1 (
🔹 -2 (
🔹 -4 (
Пока условно разбил основные функции на несколько групп:
🔸 0…-0x0F — control and miscellaneous functions;
🔸 -0x10…-0x2F — threading functions;
🔸 -0x30…-0x3F — video functions;
🔸 -0x40…-0x4F — sound functions;
🔸 -0x50…-0x5F — time functions;
🔸 -0x60…-0x6F — keyboard/mouse functions;
🔸 -0x70…-0x7F — file functions.
Может, чего-то забыл?
API FUNCTION CALLS
API functions are called by
call dword [ebp+fn]
(3-byte instruction, where fn is function number) or by call ebp
(2-byte instruction) with function number in ah
. Value in ebp
is set on intro start and it's not recommended to modify it (however, it is not prohibited). If you have modified value of ebp
you can use respectively call dword [API_TABLE_BASE+fn]
(6-byte instruction) or call API_TABLE_BASE
(call API_HANDLER
is alias). Parameters are specified in al
, edx
and ecx
registers. Result is returned in edx
register or/and carry flag (CF
). All other registers and flags are not modified.For the best comfort use
call_api
macro with function number/name or ah
as argument to call API function via ebp
register. Use call_api_direct
macro with function number/name or ah
in parameter to call API function via API_TABLE_BASE
. Function number will be placed in 32-bit register and this register can be specified as macro argument.Examples:
call_api @CALL_IN_THREAD
will be translated to call dword [ebp-0x10]
(3 bytes).call_api ah
will be translated to call ebp
(2 bytes).call_api_direct ebx
will be translated to call dword [API_TABLE_BASE+ebx]
(6 bytes).call_api_direct ah
will be translated to call API_TABLE_BASE
(5 bytes).CONTROL AND MISCELLANEOUS FUNCTIONS
🔹 0 (
@EXIT
) — exit intro. Never returns.🔹 -1 (
@ABORT
) — abort intro execution with no code. Never returns.🔹 -2 (
@ABORT_EX
) — abort intro execution with code (ABORTCODE_*
) specified in al
. Never returns.🔹 -4 (
@SET_EXCEPTION_MODE
) — set exception mode on wrong API usage: (al
= 0 — disabled, return error result; 1 — enabled, generate exception). Default is disabled.Please open Telegram to view this post
VIEW IN TELEGRAM
THREADING FUNCTIONS
🔹 -0x10 (
🔹 -0x11 (
🔹 -0x12 (
🔹 -0x13 (
🔹 -0x14 (
🔹 -0x15 (
🔹 -0x16 (
🔹 -0x17 (
🔹 -0x18 (
🔹 -0x19 (
🔹 -0x1A (
🔹 -0x1B (
🔹 -0x1C (
🔹 -0x1D (
🔹 -0x10 (
@CALL_IN_THREAD
) — call function in a separate thread (edx
= function address). Returns CF
: 0 — success (edx
= thread index), 1 — total number of threads is too large.🔹 -0x11 (
@TERMINATE_THREAD
) — terminate thread (edx
= thread index or 0 for all threads except main). Returns CF
: 0 — success; 1 — thread is not running. Function with edx = 0
should be called only from the main thread (else exception is generated if exception mode is enabled)! It's not recommended to use this function!🔹 -0x12 (
@MULTI_CALL
) — call function in many threads and synchronize (edx
= function address; al
= number of threads (0 — all free threads including current)).🔹 -0x13 (
@MULTI_CALL_NOSYNC
) — call function in many threads and continue (edx
= function address; al
= number of threads (0 — all free threads)). Returns CF
: 0 — success; 1 — no free threads (if al
= 0 on call) or total number of threads is too large.🔹 -0x14 (
@MULTI_LOOP
) — run loop, call function in many threads and synchronize (edx
= function address; ecx
= number of iterations).🔹 -0x15 (
@MULTI_LOOP_NOSYNC
) — run loop, call function in many threads and continue (edx
= function address; ecx
= number of iterations). Returns CF
: 0 — success; 1 — no free threads.🔹 -0x16 (
@MULTI_PIXELS
) — call function for every pixel, pixel group or line of video memory in many threads and synchronize (al
= number of pixels in group (must be 0 for line or 1, 2, 4, 8, 16, 32, 64 for pixel group); edx
= function address). Returns CF
: 0 — success; 1 — wrong al
value or video memory is not initialized. This function shouldn't be called if video memory is not initialized (video mode is not set; exception is generated if exception mode is enabled)!🔹 -0x17 (
@MULTI_PIXELS_NOSYNC
) — call function for every pixel, pixel group or line of video memory in many threads and continue (al
= number of pixels in group (must be 0 for line or 1, 2, 4, 8, 16, 32, 64 for pixel group); edx
= function address). Returns CF
: 0 — success; 1 — no free threads, wrong al
value or video memory is not initialized. This function shouldn't be called if video memory is not initialized (video mode is not set; exception is generated if exception mode is enabled)!🔹 -0x18 (
@WAIT_THREAD
) — wait for thread function to finish execution (edx
= thread index except 0). Returns CF
: 0 — success; 1 — thread is not running, time is out or wrong edx
value on call.🔹 -0x19 (
@WAIT_THREAD_TIMEOUT
) — wait (with timeout) for thread function to finish execution (edx
= thread index except 0; ecx
= timeout in milliseconds). Returns CF
: 0 — success; 1 — thread is not running or wrong edx
value on call.🔹 -0x1A (
@WAIT_ALL_THREADS
) — wait for all thread functions to finish execution. Returns CF
: 0 — success; 1 — no threads are running or called not from the main thread. This function should be called only from the main thread (else exception is generated if exception mode is enabled)!🔹 -0x1B (
@WAIT_ALL_THREADS_TIMEOUT
) — wait (with timeout) for all thread functions to finish execution (ecx
= timeout in milliseconds). Returns CF
: 0 — success; 1 — no threads are running, time is out or called not from the main thread. This function should be called only from the main thread (else exception is generated if exception mode is enabled)!🔹 -0x1C (
@SYNC_THREADS
) — synchronize all threads (wait when all other threads call the same function or function @SYNC_THREADS_TIMEOUT
).🔹 -0x1D (
@SYNC_THREADS_TIMEOUT
) — synchronize all threads with timeout (wait when all other threads call the same function or function @SYNC_THREADS
) or time will be out (ecx
= timeout in milliseconds). Returns CF
: 0 — successfully synchronized; 1 — time is out.🔹 -0x1E (
🔹 -0x1F (
🔹 -0x20 (
🔹 -0x21 (
🔹 -0x22 (
🔹 -0x23 (
🔹 -0x24 (
🔹 -0x25 (
🔹 -0x26 (
🔹 -0x27 (
🔹 -0x28 (
🔹 -0x29 (
🔹 -0x2A (
🔹 -0x2B (
🔹 -0x2C (
Total number of threads (including the main thread) can't be greater than 256 (in this API version). The main thread has index 0.
Mutex / condition variable id must be in range from 0 to 63 (in this API version).
Once flag must be in range from 0 to 63 (in this API version).
Конечно, в первую очередь я буду реализовывать другие группы функций ("control and miscellaneous functions" и "video functions"), а потом уже эту. Но описал пока "threading functions".
Ваши мысли, господа!🙂
@SYNC_LOCK
) — lock mutex exclusively / enter critical section for read and write access (al
= mutex id). Returns CF
: 0 — success; 1 — wrong al
value on call.🔹 -0x1F (
@SYNC_LOCK_TIMEOUT
) — lock mutex exclusively / enter critical section for read and write access with timeout (al
= mutex id; ecx
= timeout in milliseconds). Returns CF
: 0 — successfully locked; 1 — time is out or wrong al
value on call.🔹 -0x20 (
@TRY_SYNC_LOCK
) — try to lock mutex exclusively / try to enter critical section for read and write access (al
= mutex id). Returns CF
: 0 — successfully locked; 1 — mutex is locked by another thread or wrong al
value on call.🔹 -0x21 (
@SYNC_UNLOCK
) — unlock exclusively locked mutex / leave critical section with read and write access (al
= mutex id). Returns CF
: 0 — success; 1 — wrong al
value on call.🔹 -0x22 (
@SYNC_LOCK_SHARED
) — lock mutex for shared ownership / enter critical section for read only access (al
= mutex id). Returns CF
: 0 — success; 1 — wrong al
value on call.🔹 -0x23 (
@SYNC_LOCK_SHARED_TIMEOUT
) — lock mutex for shared ownership / enter critical section for read only access with timeout (al
= mutex id; ecx
= timeout in milliseconds). Returns CF
: 0 — successfully locked; 1 — time is out or wrong al
value on call.🔹 -0x24 (
@TRY_SYNC_LOCK_SHARED
) — try to lock mutex for shared ownership / try to enter critical section for read only access (al
= mutex id). Returns CF
: 0 — successfully locked; 1 — mutex is locked by another thread or wrong al
value on call.🔹 -0x25 (
@SYNC_UNLOCK_SHARED
) — unlock mutex locked with shared ownership / leave critical section with read only access (al
= mutex id). Returns CF
: 0 — success; 1 — wrong al
value on call.🔹 -0x26 (
@WAIT_CONDVAR
) — wait until condition variable is awakened, lock mutex exclusively (al
= mutex / condition variable id (if high bit is set then mutex is already locked)). Returns CF
: 0 — success; 1 — wrong al
value on call.🔹 -0x27 (
@WAIT_CONDVAR_TIMEOUT
) — wait (with timeout) until condition variable is awakened, lock mutex exclusively (al
= mutex / condition variable id (if high bit is set then mutex is already locked); ecx
= timeout in milliseconds). Returns CF
: 0 — success; 1 — time is out or wrong al
value on call.🔹 -0x28 (
@WAIT_CONDVAR_SHARED
) — wait until condition variable is awakened, lock mutex for shared ownership (al
= mutex / condition variable id (if high bit is set then mutex is already locked)). Returns CF
: 0 — success; 1 — wrong al
value on call.🔹 -0x29 (
@WAIT_CONDVAR_SHARED_TIMEOUT
) — wait (with timeout) until condition variable is awakened, lock mutex for shared ownership (al
= mutex / condition variable id (if high bit is set then mutex is already locked); ecx
= timeout in milliseconds). Returns CF
: 0 — success; 1 — time is out or wrong al
value on call.🔹 -0x2A (
@NOTIFY_ONE
) — notify one waiting thread (al
= mutex / condition variable id). Returns CF
: 0 — success; 1 — wrong al
value on call.🔹 -0x2B (
@NOTIFY_ALL
) — notify all waiting threads (al
= mutex / condition variable id). Returns CF
: 0 — success; 1 — wrong al
value on call.🔹 -0x2C (
@CALL_ONCE
) — call function once even if other threads try to call it (al
= once flag id; edx
= function address). Returns CF
: 0 — success; 1 — wrong al
value on call.Total number of threads (including the main thread) can't be greater than 256 (in this API version). The main thread has index 0.
Mutex / condition variable id must be in range from 0 to 63 (in this API version).
Once flag must be in range from 0 to 63 (in this API version).
Конечно, в первую очередь я буду реализовывать другие группы функций ("control and miscellaneous functions" и "video functions"), а потом уже эту. Но описал пока "threading functions".
Ваши мысли, господа!
Please open Telegram to view this post
VIEW IN TELEGRAM
Вчера и позавчера исправлял механизм запуска потоков и блок отлова исключений (до этого был лишний re-throw в обработчике SEH, теперь всё чётко). Ну и перелопатил функции для работы с потоками (что выше присылал). Теперь хочу это список ещё укоротить (объединив некоторые функции), чтобы они влезли в один блок из 16 функций.
В связи с этим задумался вот о чём. Как удобнее передавать параметры: через регистры или через стек? Эволюция calling convention пришла к регистрам (в x86 был стёк, в x64 стали регистры для первых параметров), но хорошо ли это для интр?
С одной стороны, регистры — это скорость, нет лишних обращений к памяти (стеку), да и регистр можно переиспользовать, и если в нём уже есть нужное значение, не нужно делать лишний
Эта мысль влечёт за собой следующую. Так как у меня 2 способа вызова API-функций: прямой вызов конкретной функции (
Идём ещё дальше: а как насчёт результатов? Стоит ли возвращать его в регистре (
Что думаете об этом?
Сейчас создам опрос :))
update: Опрос создал в чате (нажмите на комментарий и проголосуйте, пожалуйста, вдумчиво) 😉
В связи с этим задумался вот о чём. Как удобнее передавать параметры: через регистры или через стек? Эволюция calling convention пришла к регистрам (в x86 был стёк, в x64 стали регистры для первых параметров), но хорошо ли это для интр?
С одной стороны, регистры — это скорость, нет лишних обращений к памяти (стеку), да и регистр можно переиспользовать, и если в нём уже есть нужное значение, не нужно делать лишний
push
. С другой — для интры привязка к регистрам означает неудобство, поскольку каждый регистр на счёту, и привязка к регистрам сковывает.Эта мысль влечёт за собой следующую. Так как у меня 2 способа вызова API-функций: прямой вызов конкретной функции (
call dword [ebp+fn]
) и вызов по номеру в ah
(call ebp
), может, и номер функции для второго случая передавать через стек?Идём ещё дальше: а как насчёт результатов? Стоит ли возвращать его в регистре (
edx
)? Может, и результат оставлять в стеке? И далее его можно будет pop
-нуть в нужный регистр, не затирая значение в edx
.Что думаете об этом?
Сейчас создам опрос :))
update: Опрос создал в чате (нажмите на комментарий и проголосуйте, пожалуйста, вдумчиво) 😉
С новым 2️⃣0️⃣2️⃣3️⃣ годом, друзья! 🎄
Пусть в этом году сбудутся те мечты и достигнутся те цели, которые созвучны вашей душе!🔥
Всем успехов в творчестве и в других сферах жизни!❤️
Здоровья, бодрости, энергии и вдохновения!🤩
Ура!🥂 ✨
P.S. У каждого куранты бьют в разные моменты, поэтому отправляю по своему местному времени🙂
Пусть в этом году сбудутся те мечты и достигнутся те цели, которые созвучны вашей душе!
Всем успехов в творчестве и в других сферах жизни!
Здоровья, бодрости, энергии и вдохновения!
Ура!
P.S. У каждого куранты бьют в разные моменты, поэтому отправляю по своему местному времени
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
Работа идёт, но пока медленно... В целом же, если реализовать буквально 2-3 API-функции (✌️
————————————————————
Я тут иногда подумываю о названии проекта. Изначально была идея назвать его SEXI (что-нибудь типа Startup Environment for eXtra-speed Intros) или EROS (Environment of Runtime for Overclocked Scenes). Мне казалось это интересным, такой fun. Но многие восприняли такие названия с иронией. Не хотелось бы, чтобы люди относились к проекту несерьёзно.
Текущее название — Para\\e/
Хорошее, но какое-то… избитое что ли? Изюминки как будто не хватает. Но руки \e/ мне нравятся🙂
Другие неплохие варианты:
🔸 F!RE (Fast Intro Runtime Environment).
🔸 loom (ткацкий станок). Потоки = треды = нити; генератор нитей = ткацкий станок.
Так себе варианты:
🔹 SPEED (Saucy Parallel Execution Environment for Demos).
🔹 FLIP (Fast Low-level Intro Platform).
🔹 PURE (Parallel Unified Runtime Environment). Чистый код — в смысле нативный, плоский.
Хочется отразить в первую очередь многопоточность (скорость), во вторую — нативность, низкоуровневость. И какую-то изюминку / перчинку добавить. Элемент креативности, озорство (но не клоунство🤡 ).
Давайте подумаем! Подкиньте идей, друзья😉
P.S. Всех с Рождеством!🎄
@INIT_VIDEO
, @DISPLAY_IMAGE
, ну и @PARALLEL_PIXELS
), то можно уже пробовать делать интры и сравнивать скорость с другими платформами ————————————————————
Я тут иногда подумываю о названии проекта. Изначально была идея назвать его SEXI (что-нибудь типа Startup Environment for eXtra-speed Intros) или EROS (Environment of Runtime for Overclocked Scenes). Мне казалось это интересным, такой fun. Но многие восприняли такие названия с иронией. Не хотелось бы, чтобы люди относились к проекту несерьёзно.
Текущее название — Para\\e/
Хорошее, но какое-то… избитое что ли? Изюминки как будто не хватает. Но руки \e/ мне нравятся
Другие неплохие варианты:
🔸 F!RE (Fast Intro Runtime Environment).
🔸 loom (ткацкий станок). Потоки = треды = нити; генератор нитей = ткацкий станок.
Так себе варианты:
🔹 SPEED (Saucy Parallel Execution Environment for Demos).
🔹 FLIP (Fast Low-level Intro Platform).
🔹 PURE (Parallel Unified Runtime Environment). Чистый код — в смысле нативный, плоский.
Хочется отразить в первую очередь многопоточность (скорость), во вторую — нативность, низкоуровневость. И какую-то изюминку / перчинку добавить. Элемент креативности, озорство (но не клоунство
Давайте подумаем! Подкиньте идей, друзья
P.S. Всех с Рождеством!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
Итак, друзья! Первые видимые результаты: я реализовал функции
Про скорость пока могу сказать только следующее: вывод xor-шаблона в 480х270 с масштабированием на FullHD даёт у меня порядка 1500 fps. Вывод в 1920x1080 — в 2 раза медленнее.
Многопоточность ещё не задействовал.
@InitVideo
и @DisplayFrame
. Всё работает, картинка выводится 😁Про скорость пока могу сказать только следующее: вывод xor-шаблона в 480х270 с масштабированием на FullHD даёт у меня порядка 1500 fps. Вывод в 1920x1080 — в 2 раза медленнее.
Многопоточность ещё не задействовал.
👍2🎉2
Сгенерировал палитру по умолчанию для 8-битных режимов. Провозился целых 3 дня, но сделал вариант, который, вполне нравится.
На всякий случай опишу 😁
1️⃣ Первый ряд — ч/б градиент от чистого чёрного до чистого белого.
2️⃣ Далее 12 цветных рядов от самого тёмного до почти белого. Чистый цвет на 11-й позиции каждой строки (считая с 0). Соответственно, слева 11 затемнённых, справа 4 осветлённых цвета. Палитра начинается с голубого как более близкого к белому.
3️⃣ Следующий ряд — 12 радужных цветов со значительной примесью серого (более мягкие, не такие кислотные, как те, что выше) + 4 цвета с лёгкой примесью серого.
4️⃣ Последние 2 ряда — цвета палитры SWEETIE16, расширенные до 32 штук интерполяцией (могут использоваться циклически).
Все цвета сначала генерируются линейно, после чего производится гамма-коррекция с коэффициентом 1/1.2 (т.е. все значения во float в диапазоне [0; 1] возводятся в степень 1.2). Число выбрано эмпирически как более-менее оптимальное: меньшее значение (1.0) — слишком много ярких тонов, большее (1.333) — слишком много тёмных. Понятно, что многое зависит от настроек монитора и индивидуальных особенностей восприятия, но я решил сделать так.
В целом я доволен. Немного смущает вырвиглазность цветов (за исключением 3-х нижних рядов). Но потом я планирую сделать альтернативную (может, даже не одну) палитру с более мягкими цветами. Либо взяв за основу эту, либо просто большой градиент от чёрного в белый, переходящий в радугу, и в конце снова уходящий в чёрный.
К тому же, я собираюсь сделать функции генерации палитры :)
На всякий случай опишу 😁
1️⃣ Первый ряд — ч/б градиент от чистого чёрного до чистого белого.
2️⃣ Далее 12 цветных рядов от самого тёмного до почти белого. Чистый цвет на 11-й позиции каждой строки (считая с 0). Соответственно, слева 11 затемнённых, справа 4 осветлённых цвета. Палитра начинается с голубого как более близкого к белому.
3️⃣ Следующий ряд — 12 радужных цветов со значительной примесью серого (более мягкие, не такие кислотные, как те, что выше) + 4 цвета с лёгкой примесью серого.
4️⃣ Последние 2 ряда — цвета палитры SWEETIE16, расширенные до 32 штук интерполяцией (могут использоваться циклически).
Все цвета сначала генерируются линейно, после чего производится гамма-коррекция с коэффициентом 1/1.2 (т.е. все значения во float в диапазоне [0; 1] возводятся в степень 1.2). Число выбрано эмпирически как более-менее оптимальное: меньшее значение (1.0) — слишком много ярких тонов, большее (1.333) — слишком много тёмных. Понятно, что многое зависит от настроек монитора и индивидуальных особенностей восприятия, но я решил сделать так.
В целом я доволен. Немного смущает вырвиглазность цветов (за исключением 3-х нижних рядов). Но потом я планирую сделать альтернативную (может, даже не одну) палитру с более мягкими цветами. Либо взяв за основу эту, либо просто большой градиент от чёрного в белый, переходящий в радугу, и в конце снова уходящий в чёрный.
К тому же, я собираюсь сделать функции генерации палитры :)
Кстати, этот рисунок выводит 20-байтовая программа вот с таким исходником:
Кстати, с прошлого поста я исправил несколько страшных багов, сейчас всё работает чётко😎
Так что, теперь можно перейти к функциям работы с многопоточностью.
include 'parallel_fasm_sdk.inc'
CodeStart!
apicall @InitVideo, (16-1) + (16-1)*ivHeightMult + ivPalette8bpp
@@: stosb
inc al
jnz B
apicall @DisplayFrame, dfNoSync
jmp $
Несмотря на то, что программа заканчивается инструкцией jmp $
, из неё легко выйти двойным нажатием на Esc (можно и одинарным, но выход будет не сразу, а через секунду — это время даётся программе для отмены или корректного завершения). Либо комбинацией "быстро убить интро" Win+Ctrl+X.Кстати, с прошлого поста я исправил несколько страшных багов, сейчас всё работает чётко
Так что, теперь можно перейти к функциям работы с многопоточностью.
Please open Telegram to view this post
VIEW IN TELEGRAM
Друзья! Хорошие новости! 🔥 Я реализовал параллелизм: API для вызова пользовательских функций в многопоточном режиме 🤩 . Т.е. параллельный for, одномерный и двумерный. В частности, API будет вызывать вашу функцию для каждой строки экрана, для группы из нескольких пикселей (например, для 64-х) или для каждого пикселя в отдельности.
Самый медленный вариант (как можно догадаться) — использование попиксельных вызовов, т.к. он связан в большим кол-вом накладных расходов на вычисления и вызов функции. На моём компьютере такой режим позволяет достигать 1537 fps для FullHD (против 1112 fps для однопоточного режима) без учёта вывода на экран. Но это для отрисовки банального XOR-паттерна. Для таких простых операций использовать попиксельный режим неэффективно. Построчный или 64-пиксельный работает гораздо быстрее (до 6521 fps). Однако, если добавить в код холостой цикл на 1000 итераций, разница в попиксельном режиме будет куда существеннее: 32,34 fps в многопоточном коде против 2,21 fps в однопоточном (ускорение в 14,639 раз). Т.е. для сложных алгоритмов (реймаршинг, фракталы) такой режим можно использовать.
Первый алгоритм for, который я сделал 2 дня назад (в таблице он обозначен "первая реализация"), работал ощутимо медленнее, т.к. был сделан совсем по-простому. Сегодня я его оптимизировал в 2.7-100 раз для однопиксельных режимов и до 4.5 раз для 64-пиксельных режимов работы. Но это, конечно, далеко не финальная оптимизация, там есть ещё над чем работать😎
Так что друзья, можно сказать, что идея себя оправдала, параллелизм позволяет добиться существенного ускорения (у меня вышло до 16,7 раз для 20 потоков, из которых реальных ядер 12, остальные 8 — гипертреды)🥳
Больше подробностей о тестровании скорости — в таблице (смотрите верхнюю половину, т.к. он учитывает только вычислительную часть, без вывода на экран, который вносит существенную лепту в скорость работы).
Самый медленный вариант (как можно догадаться) — использование попиксельных вызовов, т.к. он связан в большим кол-вом накладных расходов на вычисления и вызов функции. На моём компьютере такой режим позволяет достигать 1537 fps для FullHD (против 1112 fps для однопоточного режима) без учёта вывода на экран. Но это для отрисовки банального XOR-паттерна. Для таких простых операций использовать попиксельный режим неэффективно. Построчный или 64-пиксельный работает гораздо быстрее (до 6521 fps). Однако, если добавить в код холостой цикл на 1000 итераций, разница в попиксельном режиме будет куда существеннее: 32,34 fps в многопоточном коде против 2,21 fps в однопоточном (ускорение в 14,639 раз). Т.е. для сложных алгоритмов (реймаршинг, фракталы) такой режим можно использовать.
Первый алгоритм for, который я сделал 2 дня назад (в таблице он обозначен "первая реализация"), работал ощутимо медленнее, т.к. был сделан совсем по-простому. Сегодня я его оптимизировал в 2.7-100 раз для однопиксельных режимов и до 4.5 раз для 64-пиксельных режимов работы. Но это, конечно, далеко не финальная оптимизация, там есть ещё над чем работать
Так что друзья, можно сказать, что идея себя оправдала, параллелизм позволяет добиться существенного ускорения (у меня вышло до 16,7 раз для 20 потоков, из которых реальных ядер 12, остальные 8 — гипертреды)
Больше подробностей о тестровании скорости — в таблице (смотрите верхнюю половину, т.к. он учитывает только вычислительную часть, без вывода на экран, который вносит существенную лепту в скорость работы).
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Ребята, кто может протестить скорость на своих машинах? Я пришлю файлы, нужно будет запустить BAT-ник и подождать несколько минут. А потом прислать мне результат (TXT-шники).
Поскольку многопоточность работает (и как мы видим по тестам, существенно ускоряет код), уже вполне можно писать интры, причём в хайрез и с использованием SIMD 😉
А я продолжаю думать о названии.
1️⃣ Помимо Para\\e/ и...
2️⃣ loom (главное, чтоб не было стойких ассоциаций с Java) появились ещё идеи.
3️⃣ artefact (что-то типа Advanced RunTime Environment For Art Code Threads, хорошо шарящие в инглише поправьте, плиз, если звучит странно).
4️⃣ Идея о F!RE трансформировалась в firebird (тем более, что я хочу называть версии видами птиц). Перевод с элементами фана: Fast Intro Runtime Environment (Brilliant Idea of Russian Developer)😄 . Смущают только возможные ассоциации с БД и, не дай бог, торпедой или ещё чем-то.
5️⃣ Ну и ещё интересное, на мой взгляд, имя — Para||ex (Parallel Environment of eXecution(?)). На Parallax аббревиатуру не придумал, но Parallex более оригинальное название.
6️⃣ Банальное: remi (Runtime Environment for Multithreaded Intros).
Что думаете? Об этих названиях. Ну и может, тоже идей подкинете?🤔
А я продолжаю думать о названии.
1️⃣ Помимо Para\\e/ и...
2️⃣ loom (главное, чтоб не было стойких ассоциаций с Java) появились ещё идеи.
3️⃣ artefact (что-то типа Advanced RunTime Environment For Art Code Threads, хорошо шарящие в инглише поправьте, плиз, если звучит странно).
4️⃣ Идея о F!RE трансформировалась в firebird (тем более, что я хочу называть версии видами птиц). Перевод с элементами фана: Fast Intro Runtime Environment (Brilliant Idea of Russian Developer)
5️⃣ Ну и ещё интересное, на мой взгляд, имя — Para||ex (Parallel Environment of eXecution(?)). На Parallax аббревиатуру не придумал, но Parallex более оригинальное название.
6️⃣ Банальное: remi (Runtime Environment for Multithreaded Intros).
Что думаете? Об этих названиях. Ну и может, тоже идей подкинете?
Please open Telegram to view this post
VIEW IN TELEGRAM
⏸ Название определено (думаю, все уже заметили), больше мучить этим вас не буду 😃
Ссылки в телеге изменены, но старые тоже работают в режиме переадресации.
⏸ Хотел написать интру для Lovebyte 2023 под Para||elix, но не успел. Жаль, конечно. Но с другой стороны, есть возможность доработать и потом показать более полноценную версию.
⏸ Заметил, что legacy FPU работает пипец как медленно в сравнении со скалярным SSE/AVX (с векторным, думаю, будет такая же картина). По крайней мере, на Alder Lake. На Sandy Bridge замечал разницу, но не настолько большую. Вообще, странно, Instruction Tables (Agner Fog) показывает незначительные различия. В общем, чтобы говорить об этом более предметно, нужно потестить более обстоятельно. И на AMD в том числе.
Плюс вылезли вопросы сохранения FPU и SIMD-регистров (ибо некоторые API типа
⏸ Также задумался о 64-битной версии (на будущее). Раньше, конечно, тоже думал, но эта мысль казалось не столь интересной. По сути, интры получаются не шибко больше в размере, зато кол-во регистров удваивается (как общего назначения, так и SIMD). Но сложнее организовать API-вызовы по похожей схеме (т.е.
⏸ Исправил баг: при использовании многопоточных функций интра висла через несколько секунд или минут. Причина была неочевидна, но чисто интуитивно решил положить
⏸ При старте интры, а также при вызове функций потоков (к примеру, для каждого пикселя) в регистр
Но, наверное, я расширю эту область до 64КБ (18МБ для 256+32 потоков — не так уж и много). Может, урежу стек (для каждого потока) до 512КБ (вместо дефолтного 1МБ) или даже до 256КБ (к чему такой большой?)🤔
⏸ Поменял значение регистра😀
Объясняю суть. При таком значении
✅ Первые 2 варианта (1x, 2x) можно зарезервировать для API (таким образом, кол-во функций увеличивается с 255 до 511 — с учётом возможных расширений, пусть будет).
✅ Следующий (3x) — для сервисных данных (инфа о процессоре, событиях (юзер нажал Esc), опциях, настройках видео и аудио, коэффициенты для приведения координат к диапазону 0..1, FP-координаты центра и т.д.). Причём, если раньше под них выделялось 128 байт, то сейчас можно 256 (128 оказалось мало).
✅ Ещё один (5x) — для хранения констант (0, 0.5, 1, π, 2π, e, √2 и т.д., включая векторные по 16 float'ов для SIMD вплоть до AVX-512).
✅ И последний
Ссылки в телеге изменены, но старые тоже работают в режиме переадресации.
⏸ Хотел написать интру для Lovebyte 2023 под Para||elix, но не успел. Жаль, конечно. Но с другой стороны, есть возможность доработать и потом показать более полноценную версию.
⏸ Заметил, что legacy FPU работает пипец как медленно в сравнении со скалярным SSE/AVX (с векторным, думаю, будет такая же картина). По крайней мере, на Alder Lake. На Sandy Bridge замечал разницу, но не настолько большую. Вообще, странно, Instruction Tables (Agner Fog) показывает незначительные различия. В общем, чтобы говорить об этом более предметно, нужно потестить более обстоятельно. И на AMD в том числе.
Плюс вылезли вопросы сохранения FPU и SIMD-регистров (ибо некоторые API типа
@GetDurationFloat
— получение времени с момента старта, могут их портить).⏸ Также задумался о 64-битной версии (на будущее). Раньше, конечно, тоже думал, но эта мысль казалось не столь интересной. По сути, интры получаются не шибко больше в размере, зато кол-во регистров удваивается (как общего назначения, так и SIMD). Но сложнее организовать API-вызовы по похожей схеме (т.е.
call [ebp+fn]
, где fn — это любое число, сделать уже не получится). В общем, поживём — увидим. До этого ещё далеко.⏸ Исправил баг: при использовании многопоточных функций интра висла через несколько секунд или минут. Причина была неочевидна, но чисто интуитивно решил положить
notify_all
внутрь залоченного мьютекса (раньше он был снаружи) — баг пропал :)⏸ При старте интры, а также при вызове функций потоков (к примеру, для каждого пикселя) в регистр
ebx
теперь записывается выровненный по границе 256 байт адрес 4КБ буфера в TLS, т.е. потоко-локальной области. Это оказалось офигенно удобно. Т.е. вы можете хранить локальные переменные не только в стеке, но и в TLS, используя адресацию вида [ebx+n]
. Потокобезопасно! Скажу по секрету, что можно адресовать не только в плюс, но и в минус (до 128 байт), т.е. у вас есть не 4КБ, а даже 4096+128 байт :)Но, наверное, я расширю эту область до 64КБ (18МБ для 256+32 потоков — не так уж и много). Может, урежу стек (для каждого потока) до 512КБ (вместо дефолтного 1МБ) или даже до 256КБ (к чему такой большой?)
⏸ Поменял значение регистра
ebp
при старте интры. На нём завязана таблица API, а также зона сервисных данных. Раньше было ebp = 0x08000000
(напомню, что код грузится по адресу 0x10000000
(ebp*2
), фрейм буфер — 0x40000000
(ebp*8
)). Сейчас сделал ebp = 0x01C71C80
. Это же гораздо лучше, согласны? Объясняю суть. При таком значении
ebp
можно использовать не только [ebp]
, но и [ebp+ebp]
, [ebp+ebp*2]
, [ebp+ebp*4]
, и [ebp+ebp*8]
, т.е. целых 5 вариантов почти за ту же цену (но первый всё же дешевле на байт) :)[ebp+ebp*8]
(9x) — это адресация 0x10000080
(теперь понятно почему 0x01C71C80
?) Т.е. добавляя байтовую константу (-128..127) можно иметь доступ к первым 256 байтам кода (а сделав, скажем, lea edx,[ebp+ebp*8+127]
— к смещениям 127..382 через edx
). Тоже удобно.Please open Telegram to view this post
VIEW IN TELEGRAM