Конференция "Начинающим" » Потоки
 
  • 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) ???
    разные чудеса бывают если использовать глобальные(внешние) переменные внутри потоков. даже если это необходимо, но криво продумано/сделано.

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

    > Даже не могу понять когда и где он вышел.
    потоки работают параллельно друг другу, им не обязательно выходить из одного чтобы в это же время работать другому.
 
Конференция "Начинающим" » Потоки
Есть новые Нет новых   [118608   +46][b:0][p:0.001]