Category: искусство

Category was added automatically. Read all entries about "искусство".

404

Shared memory crazy fast

Итог трёх дней моей возни с проблемой... Грандиозный успех safe and sound Rust'а.

Предистория: Я решил-таки сделать себе в программе рисования удобно, как было на zx-spectrum. То есть - программа рисования рисует себе в цикле, когда нужно точечки на экран ставит, точечки появляются на экране и остаются там пока программа не перезапишет. Никаких спецусилий, никаких event loop'ов и т.д.

Я хотел иметь то же самое в условиях оконного отображения. Оконное отображение - это event loop, надо реагировать на кнопочки и команды выхода. Если мы в цикле 10с считаем Солнышко Лиссажу, то мы 10с не реагируем на кнопки (aka зависли). Классические подходы: резать задачу на кусочки и перемежать их с ответом на event loop. Плюсы: относительно просто. Минусы - не ясно где резать, можно промахнуться с отправкой отрисованного и получить лаг на экране. Второй подход: соседний тред, который шлёт мелкие сообщения об обновлениях, основной тред рисует их на экране и отвечает за кнопки. Плюсы: если сообщения мелкие, практически гарантировано можно успеть ответить на event. Минусы: безумные накладные расходы на сообщения, проблема "очереди" (либо блокирующая, либо память течёт). В целом, если точки генерируются достаточно быстро, то снова та же проблема "когда прекратить принимать сообщения и отправлять кадр на рендеринг"?

Моя идея: shared область памяти, в которую рисующий (считающий) тред пишет непрерывно (как хочет), а отображающий заглядывает для обновления текстуры. Схватил текстуру, отобразил в буффер, дальше библиотека SDL (да и любая другая) с режимом vsync вернёт управление как только будет отрисован кадр на экране. Ляпота, да?

Только была пробелма: Rust не любит двойной доступ к памяти. Это тот самый danger quadrant, где конкуретный read и write.

В процессе я нырнул глубоко в unsafe, прочитал много про pointer'ы, в целом стал лучше понимать rust.

итог: код без атомиков (я себе таки его написал прямо сейчас) не работает от слова вообще. Один тред пишет, второй наслаждается кешем процессора.

Код с атомиками я сначала написал ужасно, с двойным указателем в один и тот же slice, отдаваемый в разные треды. unsafe, unsound and dangerous. Но он работал с офигенной скоростью в 13.5 ГБ/с (в режиме максимального congestion, когда один тред непрерывно меняет ячейку, а второй непрерывно её читает).

Мне сказали "переходи на arc". Для меня было открытием, что atomic позволяет сделать store для значения на non-mut ref, но я пошёл и написал. Стало порядка 9ГБ/с.

И тут мне сказали про Arc<[AtomicU32]>. Это было безумие! У меня скорость стала ещё выше - 17.5ГБ/с. Я подозреваю, что это подозрительно близко к скорости моей памяти (25.5ГБ/с в один канал), и, возможно, ограничена скоростью IPI (который как раз кеши процессорам и инвалидирует).

При этом в коде 0 unsafe!!! Я писаю кипятком.

А ещё я проверил насколько проверка индексов всё тормозит. Ответ: мало тормозит. около 0.2с (с 9.9s на 10 прогонов полного цикла по u32 до 9.7s), и оно того не стоит.

Фух. Сейчас я пойду приделывать это всё в свой основной проект. Подумав головой, я хочу сделать хитрое - засунуть рисовалку в дополнительный тред, а в основном оставить только код рисования.

Чтобы было так:

fn main(){
let mut screen = Screen::new();
for i in foo(){
screen.put_pixel(x, y);
}
screen.done();
}

А всякая SDL'ная фигня была в этом самом screen::new().

Это будет моя спектрумовская мечта. Берёшь и рисуешь, no thinking required. Я думаю, она много кому понравится как библиотека, хотя рисковать делать библиотеку пока я не готов (вероятнее всего, задумаюсь, по результатам успеха).

PS Вот proof-of-concept: https://github.com/amarao/rust_dual/blob/arc/src/main.rs
404

SDL demo

Получилось круче, чем я ожидал. fullscreen 2560x1440 fade-in/out с примесью шума шпарит на 60fps 2560x1440 с 62% утилизацией CPU и нулём спецусилий по оптимизации (кроме использования texture.update).

Всё остальное в SDL офигенно. Нужен fullscreen? Фигак. Нужно курсор убрать? Фигак. Нужно узнать разрешение этого самого fullscreen? Фигак. Всё что нужно и без доп. заморочек. Я пока не до конца понимаю необходимость разделения между window и canvas, но это мелочь.

Вот код демки: https://github.com/amarao/sdl_random

В целом я чувствую нереальное освобождение после боли с недокументированным piston'ом в котором "texture context" это мистическая фигня, которая что-то делает, но что именно не разобрать.

В целом, в equart примерно треть накрученного кода была связана с тем, что я не мог просто и дёшего копировать байты в текстуры. Можно было 'draw', что было крайне далеко от "вот тебе массив u8, это новое содержимое текстуры". (Возможно, тут ещё разница между старым процом и новым...), в SDL это очень просто.

Сейчас я засяду за многопоточный диспетчер "рисования", и мне придётся снова решить как правильно рисовать. Видимо, всё-таки слать changes.

О чём речь?

У меня есть тред, задача которого рисовать и обрабатывать события (ресайз, потенциально "стрелочки" по картинке и т.д.). Я хочу, чтобы этот тред был 100% отзывчивым.

Ещё есть то, что я рисую - это график уравнения или функции. Функция может считаться очень медленно и совершенно нормально, если часть точек будет появляться со временем (вплоть до минут). Задача рассчёта полностью и легко параллелится на треды, так что можно утилизовать все ядра.

Возникает два подхода к тому, как эти треды сообщают о нарисованном:

Они раз в N времени пересылают в основной тред свой кусок битмапа целиком, все зависимости от того, что было посчитанно.

Либо, они шлют поток команд на рисование (т.е. список точек и их цветов).

... задача ресайза требует "группировки" данных от разных тредов.

Возможно, мне удастся засунуть в один тред процесс "рисования" по данным, а треды будут просто эти данные считать. Я глубоко впечатлён seqlock'ами в ядре (кто не знает - lockless метод доступа к шареным между тредами данным, позволяющий читать и писать одновременно, при условии, что запись происходит не сильно часто), так что можно с ними поиграть тоже.

UPD: Ещё, вся моя демка после компиляции - 350к (после strip). Это, конечно, не 64к com-файлы, но и не многие мегабайты.

... продолжаю разглядывать ассемблер...

if frames % 2 == 0 {

test al, 1
jne .LBB24_53
404

рисование из Rust

Полировка QuadTree идёт, в принципе, люди с форума предложили использовать переменные сборки для управления кодом (сейчас у меня важные константы производительности просто константами в коде, но для некоторых значений может быть специфичный, более быстрый код).

Но параллельно меня продолжает смущать глубокая некрасивость в районе piston'а. Я добился на нём некоторой скорости рисования, но очень дорогой ценой. У меня идёт двойное копирование (кто-то это может назвать "двойной буфферизацией"), и мне оно не нравится. Ещё там шути что в районе текстур, и вообще, я чувствую себя слегка заброшенным, потому что документации по сущностям piston почти нет, там сквозят нюансы opengl, но они не полностью применимы.

Так что я начинаю думать о переезде на что-то другое. wayland пока что звучит слишком радикально (я домашнюю машину ещё не перевёл), а вот vulkan можно считать состоявшимся и готовым к использованию. Мне хочется найти какой-то компромисс между понятностью того, как оно всё работает, и минимальным количеством низкоуровневых деталей с которыми надо возиться. Ну и быстро рисовать. Раньше на компьютере 10-летней давности у меня ещё были excuses по производительности; на новом их нет. Должно быть 2560x1440@60FPS без лагов и с плавной визуализацией прогресса (пока считаются медленные формулы).
404

рисование

Меж тем, мои эксперименты в 2D графике уже дали мне 25 мегапикселов в секунду. Это не просто 25 мегапикеслов, а put_pixel, плюс 6 рандомов на точку, плюс регенерация текстур, плюс IPC через каналы. Оно всё ещё страшно далеко от того, что мне хочется (для моих ожиданий, мне надо примерно 220 Mpix/s, что в минимальном виде, 1.8ГБ/с, и я напомню, что моя машинка — это Core 2 quad на 2.5ГГц, с bus speed 1333, на которой bandwidth показывает аж 19016 МБ/с).

Мои 25 mpx/s — это примерно 200МБ/с (даже если удвоить на запись/чтение, то 400, но там ещё текстурогенератор — так что утроить — 600).

Но запас явно есть — 1.8ГБ/с это меньше 10% от предела памяти. 

... Упс, пока писал, бенчмарк выполз за пределы кеша. 4.8Гб/с. При моих 1.8 мне надо быть очень аккуратным в копированиях.

В любом случае, моё самолюбие приятно почёсывается рассуждениями про то, что мои десятки миллионов пикселов в секунду недостаточны, и надо больше.

Сам проект (без цели, просто отладка 2D в rust) — тут

Сейчас я буду пробовать вместо DrawCommand просто пересылать буффера с полным апдейтом. Надеюсь, память выдержит.

404

Цилиндрическое (5)

Итак, с рисованием цилиндра разобрались. Я поэкспериментировал в набросках — большая ось 2D эллипса всегда перпендикулярна 3D-линии центральной симметрии цилиндра. Вероятнее всего, она же — 2D линия симметрии, но это надо проверять.

А вот дальше я поймал себя на большой ошибке. Я начал делать долгий рисунок — и чуть-чуть не совершил её.

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

Вот так вот выглядит наивная ошибка:

Цилиндр — Blender'овский рендер, тень — честно подрисованные штрихи в Inkscape. Основа наивной ошибки — тень начинается от самой нижней точки "поверхности". На переднем плане — это низ эллипса, на заднем — точка перелома с боковой стороны на задний эллипс.

Эта ошибка становится более режущей глаз, если у нас усиливается проекция и ракурс.

Collapse )
404

Цилиндрическое (2)

Утверждение о том, что стенка цилиндра и большая ось эллипса образуют прямой угол работает только в случае ортогональной проекции (т.е. отсутствии перспективы).


Вот цилиндр в ортогональной проекции. Если приложить уголок, будет понятно, что да, угол прямой:

Collapse )
404

Страшная правда про цилиндры

Занялся я тут обстоятельным рисованием цилиндра в ракурсе. И обнаружил, что "всё не так". Пришлось расчехлить блендер, inkscape и заняться поиском правды.

Итак, начнём с простого: оси эллипса не совпадают с диагоналями описывающего параллелепипеда.

Картинка (большая):

Collapse )
404

рисовальное - рассуждения о ДД

Фотографы знают боль с динамическим диапазоном (ДД). Боль в основном от того, что они часто не могут контролировать ДД картинки и делать его близким к ДД медиума.

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

Я специально сейчас оставляю техники расширения ДД (передача полутонов шрихом) за рассмотрением - это аналогично диттерингу у цифровых носителей.

Итак, у нас есть 4 тона для передачи всего (пять - с учётом "не рисовать вообще"). Достаточно ли этого для средней картинки? Ну, давайте считать. Света - один тон. Тени - один тон. Полутона - 2 тона. Плюс "не рисовать для бликов". Бедненько? Бедненько. Можно таким рисовать? Более чем.

Как только художник разложил картинку на света-полутона-тени, так эта картинка ожила.

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

Но даже эти 10 тонов не достаточны для того, чтобы нарисовать яблоко "просто так". Почему? Потому что если обычное яблоко разобрать по тонам, то оно всё будет "примерно в полутонах". Там нет ничего чёрного, нет ничего особо белого. Как быть?

В этот момент как раз и должно произойти разложение картинки на света и тени. Света и тени - это не абсолютные категории, а относительные. Если что-то находится в очень яркой тени (например, белая кружка на белом столе в белой комнате с лампой, которая светит в потолок), то разница между "тенью" и "светом" будет в обычной RGB модели, ну... в диапазоне от 200 до 220, например. Если мы это напрямую мапнем на доступные тона карандаша, то получим, что оно находится где-то на границе между светами полутонами. Причём очень близко. И с некоторыми матюгами мы можем выдавить из себя полутон между ними, и рисовать в этих двух тонах. Будет очень неудобно, много ошибок, а результат будет... Я попытался найти пример среди своих рисунков, но я обычно старательно вытягиваю их потом в редакторе, так что в цифровом виде не нашёл. Просто поверьте, очень бледно. Едва что-то заметное карандашом порисовано.

Соответственно, нужно делать то, что делают фотографы с ДД - tone remapping. Мы говорим, что самый тёмный тон в тени - это тень. А тень мы рисуем... Да, жирно и тёмно. А самый светлый - почти невидимо.

Обычная ошибка при этом идёт в районе выбора градаций, потому что если кружка реально светлая, то её основная часть будет в самых светлых тонах. Но деление тень/свет - оно при всей своей условности - единственный метод сохранить рисунок. Если тень - она тёмная. Темнее, чем все полутона. Если свет - светлая. Светлее полутонов.

А если на рисунке нет всех оттенков (может быть, кроме самого жирного, который с металлическим блеском), то это означает, что какая-то часть рисунка, которая могла быть нарисована, либо:

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

Tl;dr; видишь тень - мазюкай жёстче.
404

Рисовальное

Я совсем его не забросил.



100+ линейного рисунка, ~10 часов тонового. За время рисунка я делал несколько раз реальные прорывы в рисунке, когда обнаруживал, что спустя некоторое время, я таки понял форму и отношения в пространстве. По линейному рисунку - я сделал настолько хорошо, насколько мог. No slack at all.

Тон я делал постольку-поскольку, ибо линейный рисунок меня волнует больше.

Остаётся вопрос открытым - что лучше, 1 тщательный рисунок за 110 часов или 550 20-минутных набросков?

Алсо, ясное свидетельство, что любой человек без таланта и каких-либо задатков может начать рисовать. (Как - вопрос другой, но достаточно, чтобы таки рисовать). Что в свою очередь, подтверждает тезис, что рисунок - навык, а не искусство. Его можно использовать для искусства, но само по себе - это просто навык, в котором грайнд и многократные повторения значат больше, чем тонкая душевная жизнь.
404

рисовальное

Спустя несколько месяцев рисования карандашом попробовал набросать что-то в качестве аргумента на вакоме. Это же ужас и смерть! Как с таким жить-то? У меня ощущение, что руки из задницы 100%, причём там, где на бумаге я не испытываю ни малейших проблем.

Отдельные тренировки?