amarao: (Default)

Ну чем не ZX-Spectrum? Это целиком весь main.rs, который мигает разноцветным экраном. Обратите внимание на отсутствие event loop'а и прочей ерунды.

use lib::EasyScreen;

fn main(){
    let screen = EasyScreen::new();
    let mut c = 0;
    loop{
        c+=1;
        screen.fill(c);
    }
}

https://github.com/amarao/equart/blob/switching_to_sdl/src/main.rs

Рисование я начну писать после того, как доделаю всякие put_pixel и т.д. Алсо, не реализовать ли мне спектрумовский circle, который умел рисовать муары при углах больше 3 радиан?

amarao: (Default)
Итог:
render fps: 2400 fps
draw fps: 60 fps
cpu use: 120%

или

render fps: 2100 fps
draw fps: 310 fps
cpu use: 200%

lockless rendering. Один тред считает в цикле (тупо fill новым цветом, 100% CPU в треде), второй это читает и выводит, либо asap (310 fps, 100% CPU в треде), либо по vsync (60 fps, 20% CPU в треде).

Сам код - я на питоне бы не смог написать короче. 55 строк вся библиотека (и то, я не уверен, что нужно руками clone делать). ... Уверен, вся библиотека 47 строк.

https://github.com/amarao/equart/tree/switching_to_sdl/src

Это просто счастье какое-то! Наконец-таки, у моего нового быстрого компьютера появился новый быстрый код!

Ближайший роадмп: сделать инверсию (вынести SDL в тред, в main сделать только рисование), чтобы остался только init_graphics()...

Заметим, этот же алгоритм идеально режется на параллельны треды. Каждому свой диапазон значений считать, а вывод - в общий буффер, и всё. Сравните это со старой версией (piston) - 1200+ строк, в которых ад и погибель с message'ами и копированиями буферов.

AtomicU32 вместе с Arc и Ordering::Relaxed звучит как читерство. Слишком просто, слишком волшебно, слишком легко.
amarao: (Default)

Итог трёх дней моей возни с проблемой... Грандиозный успех 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. Я думаю, она много кому понравится как библиотека, хотя рисковать делать библиотеку пока я не готов (вероятнее всего, задумаюсь, по результатам успеха).

Proof-of-concept: https://github.com/amarao/rust_dual/blob/arc/src/main.rs

amarao: (Default)
pub struct Screen{
    bytes: Vec<std::sync::atomic::AtomicU32>,
    width: u32,
    height: u32
}

impl Screen {
    pub fn copy_to_buffer<T>(&self, target: &mut [T]) -> Result<(), &'static str>
    where T: bytemuck::Pod{
        if self.bytes.len() != target.len(){
            return Err("Size of target should be the same as source.")
        }
        let target_u32: &mut [u32] = bytemuck::cast_slice_mut(target);
        for idx in 0..self.bytes.len(){
            target_u32[idx] = self.bytes[idx].load(Relaxed);
        }
        Ok(())
    }
}

Так сказать, "а что, так можно было"?

Если мне сейчас удастся малой кровью сделать его мутабельным (я верю в тебя, атомик) и пропихнуть между тредами, то это же просто праздник какой-то. У меня будет шаренная между тредами память с Relaxed моделью, в которой в одном треде я мирно делаю copy_to_buffer (для нужд рендеринга в текстуру), а в другом я в него делаю set_pixel/get_pixel, которые работают со скоростью mov для u32.

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

Бенчмарк показал, что запись в атомик в Relaxed занимает столько же времени, как и в обычный u32.

amarao: (Default)
Я потыкался-потыкался и обнаружил, что есть же sdl, и её биндинги для rust'а. А у него (неё?) есть window().into_canvas(), которая, в свою очередь, чистой воды 2D (то, что мне и нужно).

Сейчас попытаюсь отбенчмаркать, может ли оно выдержать честные 60FPS в фулскрине с поточечным рисованием.
Не выдерживает. 15 fps для FullHD, 8 fps для 2560x1440. Но, это worst case (draw_point для каждой точки экрана каждого кадра). Интересно, что в отличие от piston, оно супер-мега smooth, даже не смотря на эти 15.

Моя реальная задача-то - не рисовать безумно быстро, а обновлять экран по мере надобности, и не страдать при этом из-за тяжёлой математики.

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

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

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

Profile

amarao: (Default)
amarao

May 2026

S M T W T F S
     12
3 4 567 89
101112 13141516
17181920 2122 23
242526 27 282930
31      

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated May. 28th, 2026 02:00 pm
Powered by Dreamwidth Studios