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

July 2025

S M T W T F S
  1234 5
678 9101112
13141516171819
20212223242526
2728293031  

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jul. 31st, 2025 10:53 pm
Powered by Dreamwidth Studios