• dmk © (28.01.18 02:28) [0]
    Привет. Есть процедура отрисовки в потоках.
    Возможно ли сделать так, чтобы в процедуру ZStars[d].DrawObject(ThreadID)
    заходили все потоки? У меня получается, что на 1 объект - 1 поток,
    а надо чтобы на 1 объект все потоки.

    procedure QThread.Execute;
    begin

       for s := 0 to High(ZStars) do
       begin
         d := InterlockedIncrement(gObjectIndex) - 1;
         if (d > N) then Break;

         ZStars[d].DrawObject(ThreadID); //<- Сюда проходит только один поток
       end;
    end;

  • anetE10 © (28.01.18 10:29) [1]
    потоки сами никуда не ходят и не заходят.

    но процедуры (любые) работают в том потоке, из которых они были вызваны.

    так что твоя DrawObject может выполняться в любом потоке,
    в том числе во всех потоках которые уже созданы
    и всех потоках что еще будут созданы потом
  • dmk © (28.01.18 15:17) [2]
    Да понял уже. Придется DrawObject разворачивать прямо в Execute.
  • sniknik © (29.01.18 00:47) [3]
  • kilkennycat © (29.01.18 04:35) [4]

    > sniknik ©   (29.01.18 00:47) [3]

    ;)
  • dmk © (29.01.18 07:57) [5]
    Ну а чего смешного? Уже сделал многопоточную отрисовку.

    Сплошная заливка:
    Было 2560×1600×20 fps
    Стало 2560×1600×120 fps
    Ускорение в 6 раз.

    Было 1280×800×64 fps
    Стало 1280×800×480 fps
    Ускорение в 7.5 раз.

    Векторная заливка:
    Было 1280×800×64 fps
    Стало 1280×800×375 fps
    Ускорение в 5.85 раза.

    Вектор немного тормозит из-за сложности просчетов.
    Вот скриншот:
    https://hostingkartinok.com/show-image.php?id=4091ac6f814e12e345c8201a3fa171f2

    Пропускная способность памяти низкая у CPU. На пределе фактически считает.
    В моем случае 14 Гб/с.

    Так что ;) Вам.
  • dmk © (29.01.18 08:15) [6]
    Или вот последние данные:

    3 млн полигонов:
    1 из 10 ядер: ~5 fps
    9 из 10 ядер: ~44 fps

    Ускорение в 8,8 раза.
  • sniknik © (29.01.18 10:16) [7]
    > Ну а чего смешного? Уже сделал многопоточную отрисовку.
    из той ветки
    > dmk ©   (22.11.17 22:00) [19]
    > Synchronize(BitBlt)
    это смешно, серьезно. ты тут на словах пытаешься решить вопрос, которого не бывает, в принципе, как коня в вакууме. а то что приводишь в той ветке показывает, что и ты этого не делаешь, т.е. делаешь что-то, от чего приводишь тесты, но не то что пишешь словами.

    это ну типа вопрос "как долететь на машине до луны?", ответ "машины не летают! (ну, по еще)", а ты такой "нет, на луне уже были, у меня вот тут замеры разных скорости машин ...". в общем вещи вообще друг с другом не связанные, но да, кто-то к ракете на машине подъезжал, и если смотреть в общем зачете машина(много машин, поток сотрудников для запуска)+ракета даже мог ускорить прилет на луну.
  • sniknik © (29.01.18 10:19) [8]
    > ну, по еще
    ну, пока еще
  • dmk © (29.01.18 15:25) [9]
    Ваще не в точку. Там я только начал вопрос изучать, а на сей момент я его изучил.
    Асинхронный многопоточный расчет полигонов - сделал. Вот и все.
    А вы ракета - машина - луна. Я даже «частоту вашей волны» не понял.
    Зачем троллить? Или это у вас просто привычка?
  • sniknik © (29.01.18 17:30) [10]
    > Асинхронный многопоточный расчет полигонов - сделал.
    так расчет или отрисовка? без точности в терминах описать что-то словами невозможно. что бы ты там не думал себе про троллинг.
  • dmk © (29.01.18 18:26) [11]
    >так расчет или отрисовка?
    И расчет и отрисовка. Расчет с помощью CPU.
    Запись в память разве не считается отрисовкой? На мой взгляд считается.
    Потом вывод средствами системы: BitBlt.
    Вот кусочек расчетов на асм:

     //На входе в XMM0 должен быть fZ (тип Single)
    asm
     comiss xmm0, dword ptr [r10] //Читаем Z-Buffer
     jbe @Bequal
     dec X1
     jnz @X
     ret

    @Bequal:
     movd dword ptr [r10], xmm0 //Пишем fZ
     shufps xmm0, xmm0, 00b //Размножаем fZ для умножения

     //Цвет по вектору
     //На входе в XMM0 находится single-вектор точки
     //в барецинтрических координатах

     //Размножаем X,Y,Z для умножения
     pshufd xmm1, xmm4, 0
     pshufd xmm2, xmm5, 0
     pshufd xmm3, xmm6, 0

     mulps xmm1, xmm7 //Result.X := (V[0] * CV0.X) + (V[1] * CV1.X) + (V[2] * CV2.X);
     mulps xmm2, xmm8 //Result.Y := (V[0] * CV0.Y) + (V[1] * CV1.Y) + (V[2] * CV2.Y);
     mulps xmm3, xmm9 //Result.Z := (V[0] * CV0.Z) + (V[1] * CV1.Z) + (V[2] * CV2.Z);
     addps xmm1, xmm2
     addps xmm1, xmm3

     mulps xmm0, xmm1 //Результат остается в XMM0
     movd xmm1, r12d //Множитель 255.0. Перед циклом загрузить в R12d
     shufps xmm1, xmm1, 00b
     mulps xmm0, xmm1 //Умножаем сразу 4 значения
     cvtps2dq xmm0, xmm0 //Округляем сразу 4 значения
     movd xmm2, r15d //Маска сборки пиксела (Перед циклом загрузить в r15d)
     pshufb xmm0, xmm2 //Собираем пиксел
     movd dword ptr [r11], xmm0 //Результат пишем по адресу пиксела

     dec X
     jnz @X
    end;
  • sniknik © (29.01.18 18:58) [12]
    > Потом вывод средствами системы: BitBlt.
    http://pda.delphimaster.net/?id=1486644466&n=5

    > Игорь Шевченко ©   (09.02.17 19:27) [2]
    > BitBlt не подразумевает асинхронное выполнение

    также и много-поточного, почему и нужны синхронизации/критические секции...
    т.к. "приемник"
    HDC hdcDest, // дескриптор целевого DC
    синхронный. одно-поточное устройство типа COM порта...

    ты бы свои же вопросы почитал, разобрался... как вижу много раз тебе одно и то же отвечают.
  • dmk © (29.01.18 20:08) [13]
    У меня был затык в плане распараллеливания отрисовки по полигонам.
    Перенес отрисовку в класс TThread и сделал рендер в нем. Все распараллеливается,
    но GdiFlush видимо не помогает при выводе BitBlt. Все равно пока идет вывод буфера
    через BitBlt - потоки успевают чуть-чуть нарисовать в нем. Визуально похоже не глюки (горизонтальные полоски). Буду бороться с помощью двух буферов. SwapBuffers.

    procedure RenderThread.Execute;
    var
     Params: TDrawParams;
     H: integer;

    begin
     //Заполняем параметры
     Params.ThreadID := FNumberID;

     H := High(ZStars);

     //Цикл отрисовки
     while (gDrawCounter <= gNumDrawCycles) do
     begin
       //Флаг - отрисовка начата
       ThreadFinished[FNumberID] := False;

       //Трансформация
       TransformObjects(ZStars);

       //Отрисовка
       DrawObjects(ZStars);

       //Флаг - отрисовка завершена
       ThreadFinished[FNumberID] := True;

       //Ждем конца отрисовки всеми потоками
       repeat until ThreadsFinished;

      //<- Сюда хочу поместить SwapBuffers

       //Обновляет буфер главный VCL-поток
       if (ThreadID = gMainThreadID) then
       begin
         //...
         //gCS.Enter;

         //Загрузка процессора
         if (gDrawCounter mod 10) = 0 then gUsage := GetCPUUsage(gCPUTimes);

         //Обновляем буфер
         Synchronize(TestForm.UpdateThreads);

         //...
         //gCS.Leave;
       end;//if (gNumEntered)

       //Проверка на ESC
       if gESC then Break;
     end;//while
    end;
  • Eraser © (29.01.18 22:25) [14]

    > dmk ©   (29.01.18 20:08) [13]


    > dmk ©   (29.01.18 18:26) [11]

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


    > procedure RenderThread.Execute;

    у тебя ложное представление о том, как устроен класс TThread.

    >   Synchronize(TestForm.UpdateThreads);

    вот ЭТО сведет на нет в несколько порядков все твои подуги с asm.
  • dmk © (30.01.18 01:46) [15]
    Все потуги сводит на нет скорость памяти. В этом и весь затык. Процессор обсчитывает около 160 Гб/с, а пропускная способность памяти всего 14 Гб/с. Одну сцену, например, 5 ядер обсчитывают также как и 10 ядер. Так что, все эти ядра имеют смысл, если данные хранить в регистрах или параметры передавать через регистры. Тогда будет хороший выигрыш по производительности. Но переменных намного больше чем самих регистров, поэтому все через регистры не передашь. Например, матрица занимает 64 байта. На сцене из 300 000 полигонов - отрисовка занимает на одной матрице 18.31 Мб за проход.
    Скорость рендеринга около 110 fps. Это значит, что за секунду 1,96 Гб съедается на одной матрице при умножении с выравниванием не говоря уже о загрузке адресов в переменные. Таких матриц у меня 2 - это уже 3,92 Гб на одних только матрицах по шине полетело из моих 14-и общих. Ну и т.д. Каждая нормаль проходит обсчет в проекцию камеры. Это 16 байт. На одних нормалях сжирается 251 Мб в секунду. Ну и т.д. и т.п. В итоге от моих 14 Гб/с остается 5-6 Гб/с на результат, т.е. обсчитанную графику. В итоге вместо расчетных теоретических ~900 fps я имею 110-120 fps, т.к. нужно еще синхронизировать потоки перед отрисовкой следующего кадра. Что для софтрендера совсем неплохо. Без моего асма скорость упадет вообще до 20-25 fps. Компилятор Delphi не умеет по регистрам все распихивать. Приходится ручками.
  • dmk © (30.01.18 02:00) [16]
    У моей видюхи например пропускная способность памяти 336.5 GB/s. Если шейдеры не цеплять, то 8-9 тысяч fps получается просто рендеринг с отсечением и без источников света.
  • dmk © (30.01.18 03:31) [17]
    Меня вообще немного другое напрягает. Потоки ведут себя необъяснимо.
    В Execute есть такой код, который чистит фреймбуферы:


    procedure RenderThread.ClearBuffers;
    var
     AW, BW: QWord;
     zA, dA: QWord;
     Len: QWord;
     i, k: integer;
     NewTarget: PBitmap64;

    begin
     gNumEntered := InterlockedIncrement(gNumEntered);

     //Смена Framebuffer
     if (gDrawCounter and 1) = 0 then
       NewTarget := @BmpB else NewTarget := @BmpA;

     //Очистка Z-Buffer и FrameBuffer
     AW := SingleAsQWord(ZFar);
     BW := SingleAsQWord(0.0);

     Len := NewTarget^.ScLen shr 1;

     repeat
       i := InterlockedIncrement(gLineIndex) - 1;
       if (i > NewTarget^.Height) then Break;

       zA := NewTarget^.ZAddress32(0, i);
       dA := NewTarget^.PAddress32(0, i);

       StoreQWords(zA, Len, AW);
       StoreQWords(dA, Len, BW);

     until (i >= NewTarget^.Height);

     gNumLeaved := InterlockedIncrement(gNumLeaved);

     //repeat until (gNumLeaved = gNumEntered);

     if (gNumLeaved = gNumEntered) then
     begin
       gLineIndex := 0;
       gNumEntered := 0;
       gNumLeaved := 0;
     end;

     //Смена буфера
     RenderTarget := NewTarget;
    end;


    Спрашивается, а почему (gNumEntered <> gNumLeaved) ???
    Поток полюбому должен был пройти этот участок:
    gNumLeaved := InterlockedIncrement(gNumLeaved);
    Но он каким то волшебным образом вылетел из процедуры и «тусит» в другом цикле.
    Вот что за напасть? Даже не могу понять когда и где он вышел.
  • Игорь Шевченко © (30.01.18 09:51) [18]

    >  В итоге вместо расчетных теоретических ~900 fps я имею
    > 110-120 fps, т.к. нужно еще синхронизировать потоки перед
    > отрисовкой следующего кадра. Что для софтрендера совсем
    > неплохо. Без моего асма скорость упадет вообще до 20-25
    > fps.


    А как, я извиняюсь, другие рендеры работают, тоже со скоростью памяти мучаются ?
    (Просто интересно)
  • sniknik © (30.01.18 10:09) [19]
    > Спрашивается, а почему (gNumEntered <> gNumLeaved) ???
    разные чудеса бывают если использовать глобальные(внешние) переменные внутри потоков. даже если это необходимо, но криво продумано/сделано.

    > Но он каким то волшебным образом вылетел из процедуры и «тусит» в другом цикле.
    у тебя что потоки в цикле, и ты ждешь последовательного выполнения?

    > Даже не могу понять когда и где он вышел.
    потоки работают параллельно друг другу, им не обязательно выходить из одного чтобы в это же время работать другому.
  • dmk © (30.01.18 12:14) [20]
    >А как, я извиняюсь, другие рендеры работают, тоже со скоростью памяти мучаются ?
    Именно так. В 3Ds max софтверные драйвера медленнее моих функций в несколько раз.
  • dmk © (30.01.18 12:32) [21]
    А вообще софтвер особо не в почете, когда есть видюхи.
    Железный рендер быстрее в любом случае. Мне просто хотелось иметь свой софтверный
    вариант. Оказалось процессоры под это не особо заточены.
  • dmk © (30.01.18 14:55) [22]
    >В 3Ds max софтверные драйвера медленнее моих функций в несколько раз.
    У меня на 1 ядре ~500к полигонов с освещением по ламберту выдает 13-15 fps.
    В 3ds max на 10 ядрах - 9-10 fps. У меня на 10 ядрах ~45-50 fps. Мелочь, а приятно :)

    Вот скрин:
    https://cdn1.radikalno.ru/uploads/2018/1/30/56bf85bf6fd189af421f397e711fc325-full.jpg
  • dmk © (30.01.18 14:58) [23]
  • dmk © (30.01.18 15:08) [24]
    >разные чудеса бывают если использовать глобальные(внешние) переменные внутри
    >потоков. даже если это необходимо, но криво продумано/сделано.

    Подскажите как правильно? Во всех примерах, которые я нашел
    используются глобальные переменные с InterlockedIncrement.
    Как мне их заставить рисовать последовательно?
    Создавать на каждое действие свой класс потоков и вызывать из основного цикла?
    Типа так?
    1. Resume ClearBufferThreads
    2. Очистка буферов (Класс потоков - ClearBufferThreads)
    3. Suspend ClearBufferThreads
    4. Resume ModifyObjectsThreads
    5. Трансформация объектов (Класс потоков - ModifyObjectsThreads)
    6. Suspend ModifyObjectsThreads
    7. Resume RenderObjectsThreads
    8. Рендер объектов (Класс потоков - RenderObjectsThreads)
    9. Suspend RenderObjectsThreads
    10. Вывод просчитанного в основном VCL-потоке.

    В нынешнем варианте потоки очень много простаивают в циклах.
    Серьезная потеря производительности. Если запустить просчет без ожидания потоков,
    то скорость отрисовки возрастает до ~350 fps, что очень хотелось бы.
  • Eraser © (30.01.18 16:44) [25]

    > dmk ©   (30.01.18 14:55) [22]
    > >В 3Ds max софтверные драйвера медленнее моих функций в
    > несколько раз.
    > У меня на 1 ядре ~500к полигонов с освещением по ламберту
    > выдает 13-15 fps.
    > В 3ds max на 10 ядрах - 9-10 fps. У меня на 10 ядрах ~45-
    > 50 fps. Мелочь, а приятно :)

    не то чтобы я не доверяю, но картинки только от твоего рендера, откуда взяты данные 3ds max - не ясно, также, ну и самое главное, при сравнении fps нужно сравнивать и качество картинки )

    ну и небольшое отступление - модная нынче на форуме рубрика "минутка рекламы облаков". сколько можно выкладывать фотки на всякий помойках да еще и в невменяемом качестве?
    https://disk.yandex.ru/
    https://www.dropbox.com/
    и т.д.
    там не обязательно даже устанавливать что-то, за глаза хватит и веб-версии для хостинга картинок.

    мне не совсем понятно, какой все таки этап ты хочешь распараллелить? вот статья для чайников https://habrahabr.ru/post/158983/ ткни пальцем, что именно ты пытаешься обрабатывать в потоках?


    > Но он каким то волшебным образом вылетел из процедуры и
    > «тусит» в другом цикле.

    это ты как понял, через отладчик? так это, скорее всего, глюк отладчика, такой эффект бывает, при отладке многопоточного кода. 100% даст только логирование, в этом случае.
  • dmk © (30.01.18 20:18) [26]
    >не то чтобы я не доверяю
    В 3ds max работаю по основной профессии. Не то чтобы круто, но лицензия стоит.

    Вот видос моего редактора. Делаю в свободное время.
    https://yadi.sk/i/QagOQ1em3Rvfjs

    Пока делаю рендер. В основном VCL-потоке маленькие модели крутятся шустро,
    а мне надо сделать поддержку многопоточности для крупных моделей.
    В самом начале видоса - гоблин. 500к. Средняя модель. Моя версия многопоточности
    видимо не очень хороша, т.к. медленно. 50-55 fps всего. Надо параллелить лучше.
    Рендер у меня софтверный - свой. Свои примитивы, свои шейдеры и т.п. Свой mini-API.
    OpenGL пока не подключен, ибо пойдет потом навесом.

    Unity мне не надо. У меня стоит.

    Вот я и думаю, а как лучше реализовать многопоточность, чтобы в цикле соблюдалась последовательность рендеринга?
    У меня 5 этапов:
    1. Очистка буферов
    2. Трансформация
    3. Рендеринг объектов
    4. Постобработка.
    5. Вывод на экран

    >это ты как понял, через отладчик?
    Ага. Вот это и напрягает. Delphi видимо плохо отлажена. У меня XE6 Prof.
    Хотя при запуске все 19 потокв вошли в процедуру, а вышли из нее только 15.
    4 испарилось где то посередине, хотя выхода там нигде нет.
  • Eraser © (30.01.18 23:41) [27]

    > dmk ©   (30.01.18 20:18) [26]



    > Надо параллелить лучше.


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

    http://rouse.drkb.ru/books.php#rihter там в самом низу. Рихтер.
    Есть раздел про объекты синхронизации и т.д.
    Интуитивно догадываюсь, что тебя в итоге спасет что-то вроде смеси семафоров и waitformultipleobjects.
  • sniknik © (31.01.18 10:13) [28]
    > Моя версия многопоточности
    у тебя ее похоже и нет... и не нужна, судя по оговоркам о циклах потоков, ожидании завершения для начала работы другого.
    у тебя есть куча лишнего, путающего тебя же, кода делающего то же что вызов процедур в простом цикле... в общем есть кривая и запутанная обработка линейной логики.
    выкинь все лишнее связанное с потоками, упрости и все ускорится, пусть не намного (цикл или цепочка последовательных потоков равны, если +- секунды считать). ну или логику продумай под много поточность. сначала нужно конечно понять потоки, а не спрашивать "как правильно", правильно под разные случаи по разному.
  • Игорь Шевченко © (31.01.18 10:32) [29]

    > Delphi видимо плохо отлажена


    Очевидно же
  • dmk © (31.01.18 16:10) [30]
    >Очевидно же
    Для 64 битов при передаче более 3-х параметров компилятор иногда помещает
    4-й и более параметр в RSP+OFFSET вместо регистров или RBP+OFFSET. Что приводит к AV.
    Я же его не заставляю этого делать. А у меня вообще .NOFRAME стоит.
    Align16 не работает ни при каких обстоятельствах. Еще некоторые директивы не работают.
    Глюки есть. Это чтобы не слыть пустозвоном. В EDN все вопросы подняты, но стоят без ответов.
  • dmk © (31.01.18 16:17) [31]
    sniknik ©   (31.01.18 10:13) [28]
    >у тебя ее похоже и нет...

    Ну почему же нет. Вот мой цикл Execute.
    Из вызываемых процедур в цикле только одну приведу, т.к. другие очень большие.
    Работает, но асинхронная передача данных приводит к визуальным глюкам.
    А так довольно шустро все распараллеливает.
    В данном случае вместо 35 fps в одном потоке - ~175-177 fps.


    procedure RenderThread.ClearBufferLines;
    var
     zA, pA: QWord;

    begin
     if (not gAllClear) then
     begin
       gLineIndex := InterlockedIncrement(gLineIndex) - 1;
       if (gLineIndex < RenderTarget^.BitmapRegion.H) then
       begin
         zA := RenderTarget^.ZAddress32(0, gLineIndex);
         pA := RenderTarget^.PAddress32(0, gLineIndex);

         StoreQWords(zA, TargetLen, ZQ);
         StoreQWords(pA, TargetLen, 0);

         gNumClearLines := InterlockedIncrement(gNumClearLines);
       end;
     end;

     gAllClear := (gNumClearLines >= RenderTarget^.BitmapRegion.H);
    end;

    procedure RenderThread.Execute;
    begin
     //Цикл отрисовки
     while (gDrawCounter <= gNumDrawCycles) do
     begin
       repeat
         ClearBufferLines;
       until gAllClear;

       //Трансформация
       repeat
         TransformObjects(ZStars);
       until gAllTransformed;

       //Отрисовка
       repeat
         DrawObjects(ZStars);
       until gAllPainted;

       //Ждем конца отрисовки всеми потоками
       repeat until (gAllClear and gAllTransformed and gAllPainted);

       //Флаг завершения обновления
       gUpdatingFinished := False;

       //Обновляет буфер один поток
       if (FNumberID = 0) then
       begin
         //Загрузка процессора
         if (gDrawCounter mod 10) = 0 then gUsage := GetCPUUsage(gCPUTimes);

         //Обновляем буфер
         Synchronize(TestForm.UpdateThreads);
         //TestForm.UpdateThreads;

         //Флаг завершения обновления
         gUpdatingFinished := True;
       end;//if (gNumEntered)

       //Ждем конца обновления
       repeat until gUpdatingFinished;

       //Проверка на ESC
       if gESC then Break;
     end;//while
    end;
  • dmk © (31.01.18 16:36) [32]
    В данном случае мне удалось заставить потоки делать все шаги последовательно.
    Этот участок даже не нужен. Я его пока для отладки оставил.
    repeat until (gAllClear and gAllTransformed and gAllPainted);

    Вот что получается (видео 12 Мб):
    https://yadi.sk/i/-rurTadp3RxJMD
    Полоски в некоторых местах и иногда пробивает «нечто».
    На самом деле это не дочищенный Buffer.
  • sniknik © (31.01.18 18:43) [33]
    > Ну почему же нет. Вот мой цикл Execute.
    вот поэтому -
    > В данном случае мне удалось заставить потоки делать все шаги последовательно.
    потоки работают параллельно, независимо, на разных процессорах (если их несколько), если же они работают(/их заставляют) последовательно то это просто одно поточный цикл for реализованный сложным косвенным методом.
  • dmk © (31.01.18 20:49) [34]
    >последовательно то это просто одно поточный цикл for
    Ну да, только многопоточный (усиленный). Этого и добиваюсь.
  • dmk © (03.02.18 07:04) [35]
    В общем сделал. Вот тут видос: https://yadi.sk/i/JOWjny6z3S3tP8
    Распараллеливание записано c 7:40 до 9:25. Перед этим все на одном ядре работает.
    Ускорение от 4,5 до 8 раз. Все упирается в скорость памяти к сожалению.
    Даже одно ядро забивает почти всю пропускную способность памяти.
    Но в целом 1 млн полигонов ~35 fps. Норм для софтрендера.
Есть новые Нет новых   [118240   +20][b:0][p:0.002]