• dmk © (09.02.17 15:47) [0]
    Подскажите пожалуйста, есть ли в WinApi возможность дождаться окончания вызова функции? Проблема в том, что в потоках рисуется буфер, который потом выводится на экран. Сразу после вызова BitBlt потоки успевают нарисовать в буфере обновленное содержимое, а отрисовка BitBlt еще не завершена. Получается наложение нового на старое. Можно конечно 2 буфера чередовать, но вдруг в WinApi что есть, чтобы дождаться окончания вывода?
  • NoUser © (09.02.17 17:02) [1]
    > дождаться окончания вывода?
    У тебя два потока, - один 'рисует' в hdcSrc, второй вызывает BitBlt ?

    Да, есть: EnterCriticalSection/LeaveCriticalSection.

    Можно также, для большей утилизации многоядерности: TryEnterCriticalSection с дополнительным буфером ...
  • Игорь Шевченко © (09.02.17 19:27) [2]
    BitBlt не подразумевает асинхронное выполнение
  • dmk © (09.02.17 19:41) [3]
    >Да, есть: EnterCriticalSection/LeaveCriticalSection.
    Так у меня через TThread.Synchronize процедура обновления. Т.е. там уже CriticalSection используется. Значит 2 буфера.

    >BitBlt не подразумевает асинхронное выполнение
    Так о том и пишу. Блок памяти в момент вывода досутпен для изменения :(
  • Игорь Шевченко © (09.02.17 21:39) [4]
    dmk ©   (09.02.17 19:41) [3]

    Значит надо перед вызовом BitBlt заблокировать буфер функцией EnterCriticalSection, а после вызова разблокировать LeaveCriticalSection
    А изменяющий код перед изменениями тоже должен вызвать EnterCriticalSection, а после изменения LeaveCriticalSection, вроде азы синхронизации.
  • NoUser © (10.02.17 00:36) [5]

    > Сразу после вызова BitBlt поток(и)! успевают нарисовать в буфере обновленное одержимое

    у тебя больше одного дополнительного потока рисуют одну картинку?

    Тогда Synchronize тебе не поможет - оно работает только между тем кто вызвал и основным потоком.
  • Pavia © (10.02.17 08:40) [6]

    > Получается наложение нового на старое. Можно конечно 2 буфера
    > чередовать, но вдруг в WinApi что есть, чтобы дождаться
    > окончания вывода?

    Нету. Предполагается, что по выходу данные забраны на вывод.  

    Но бага есть! Толи в самой винде, толи в драйверах. Скорее первое.


    > Так у меня через TThread.Synchronize процедура обновления.

    Это ничего не значит. Нужен код или схема.


    > Игорь Шевченко ©   (09.02.17 21:39) [4]

    Азы синхронизации говорят, что если надо заблокировать данные(буфер, блок данных), то надо использовать Mutex (исключительный доступ). Или замок к примеру Canvas.Lock.
  • Игорь Шевченко © (10.02.17 10:19) [7]
    Pavia ©   (10.02.17 08:40) [6]

    Я поражаюсь твоему апломбу вкупе с твоим невежеством.
  • Pavia © (10.02.17 11:33) [8]

    > Я поражаюсь твоему апломбу вкупе с твоим невежеством.

    Люблю правду и докапываюсь до истины. А самое главное я верю в то, что я говорю так как говорю правду. И вера моя основана на книгах, которые я прочитал и на экспериментах которые я поставил.
    Все мы люди и можем ошибаться.

    Так вот с BitBlt и синхронизацией я экспериментировал правда давненько и SDK в плоть до DDK прошерстил, а вот это было пару месяцев назад.

    Что касается критической секции и мьютекса. Так это всего лишь указание на правильное использование слов с учётом их семантики. Есть такая вещь называется метафора и аналогия, и ими следует пользоваться вместо того что-бы грубо нарушать качество передачи смысла.  

    А если тут найдётся умник который скажете, что Canvas.Lock основан на критических секциях. То уж извините это называется медвежьей услугой. Так как этот человек предлагает вам начать изобретать свой велосипед вместо того что-бы пользоваться готовым.

    Вообще есть у меня в планах написать статью или брошюрку с описанием параллельности. Как что и зачем. Вот только шаблон книги надо подобрать.  Может посоветовать, что за рыбу взять?
  • dmk © (10.02.17 13:36) [9]
    >Предполагается, что по выходу данные забраны на вывод.

    Вопрос решился простым циклом ожидания.
    Вот такой цикл отрисовки.

    procedure QThread.Execute;
    var
     cP: TFloatPoint;
     Params: TDrawParams;

    label
     StopDraw;

    begin
     //Параметры по умолчанию
     Params := FillParams;

     //Центр вращения
     cP.X := cX;
     cP.Y := cY;

     //Порядковый номер потока
     NumberID := GetFreeID(ThreadID);

     //Заполняем параметры
     Params.cX := cX;
     Params.cY := cY;

     //Изменяем только угол
     Params.Angle := gAngle;

     //Отсечение всего региона
     if bClip then
       Params.R := lmR else
       Params.R := Rgn;

     //Цикл отрисовки
     while (gDrawCounter < gNumDrawCycles) do
     begin
       //Превышение индекса объектов
       //учитывается в программе отрисовки
       gDrawProc(Params);

       //Обновляет буфер самый шустрый поток
       if (ThreadID = gFirstID) then
       begin
         //Обновляем буфер
         //Synchronize(UpdateThreads); //<- Работает медленнее
         UpdateThreads; //<- Работает бытсрее
         //Прогресс
         gProgress := Round(gDrawCounter / (gNumDrawCycles - 1) * 100);
         //Кол-во циклов отрисовки
         Inc(gDrawCounter);
         //Следующий угол
         gAngle := RorAngle(gAngle, gAngleInc);
         //Сброс кол-ва отрисованных объектов
         gObjectIndex := 0;
       end;//if (gNumEntered)

       //Устанавливаем флаг завершения отрисовки
       FPaints[NumberID] := True;

       //Остальные потоки ждут здесь
       //пока первый поток не отрисует буфер
       //или все потоки не соберутся здесь
       while (not PaintsFinished) do
       begin
         //Проверка на превышение циклов отрисовки
         if (gDrawCounter >= gNumDrawCycles) then goto StopDraw;
       end;

       //Проверка на ESC
       if gESC then goto StopDraw;
     end;//while

    StopDraw:

     //Устанавливаем флаг завершения отрисовки
     FPaints[NumberID] := True;

     //Ждем здесь пока остальные потоки не закончат отрисовку
     while (not PaintsFinished) do
     begin
     end;
    end;


    Вот такая процедура вывода

    procedure UpdateThreads;
    var
     dp: TDrawParams;

    begin
     DrawProgress(gProgress, false);
     BitBlt(pDC, Rgn.X, Rgn.Y, Rgn.W, Rgn.H, sBmp.MemDC, Rgn.X, Rgn.Y, SrcCopy);

     //Счетчик обновлений
     Inc(gNumBufferUpdates);
    end;


    В таком виде работает нормально, но есть асинхронное поведение ядер.
    Несмотря на InterlockedIncrement некоторые потоки успевают нарисовать быстрее.
    Синхронность отсутствует вообще. Видимо из-за разницы в ГГц-ах.
  • NoUser © (10.02.17 13:36) [10]
    > SDK в плоть до DDK ))


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

    ( 'Рыба': http://www.kurzenkov.com/Articles/multithreading1.html )
  • Игорь Шевченко © (10.02.17 13:54) [11]
    Pavia ©   (10.02.17 11:33) [8]


    > И вера моя основана на книгах, которые я прочитал и на экспериментах
    > которые я поставил.


    Если бы среди прочитанных книг был учебник по грамматике русского языка - было бы еще лучше. А так - просто смешно.
  • Inovet © (10.02.17 17:06) [12]
    > [8] Pavia ©   (10.02.17 11:33)
    > Вообще есть у меня в планах написать статью или брошюрку
    > с описанием параллельности. Как что и зачем. Вот только
    > шаблон книги надо подобрать.  Может посоветовать, что за
    > рыбу взять?

    Совет был дан кем-то из классиков: если можешь не писать - не пиши.
  • Eraser © (10.02.17 20:49) [13]

    > dmk ©   (09.02.17 15:47) 


    > Сразу после вызова BitBlt потоки успевают нарисовать в буфере
    > обновленное содержимое, а отрисовка BitBlt еще не завершена.
    >

    1. есть такое понятие, как двойная буферизация, погугли.

    2. если нужна скорость - выкинь GDI и используй Direct2D http://docwiki.embarcadero.com/RADStudio/Seattle/en/Using_the_Direct2D_Canvas

    3. Если вдруг надумал рисовать в потоках VCL оберткой над GDI, нужно везде использовать блокировки canvas, если интересно почему - см. FreeMemoryContexts из модуля Vcl.Graphics, там очень захватывающая история.
  • dmk © (10.02.17 23:19) [14]
    Eraser ©   (10.02.17 20:49) [13]

    Да не. Цель наоборот избавиться от привязки к чужим технологиям. От ОС нужен только вывод, а так у меня библиотека сама многое рисует. Суть мультипоточности в моем случае сделать отрисовку быстрее и сохранить порядок наложения рисуемых объектов. Чтобы результат был одинаковый в одном потоке или нескольких.

    Тут скриншот: http://hostingkartinok.com/show-image.php?id=347c22d0a4d969a5a9781c625d8f84b3
    Тут exe: https://cloud.mail.ru/public/5mkt/mK1uRZZLT

    Главное треугольник не трогать. Не допилен.
  • dmk © (10.02.17 23:20) [15]
    Под 32 бита наверно не пойдет.
  • Eraser © (11.02.17 02:16) [16]

    > dmk ©   (10.02.17 23:19) [14]

    видимо речь про многопоточный 3D рендер, а не вывод.
    мне кажется это весьма сложная тема, тут без серьезного углубления в теорию никак, с кондачка вряд ли выйдет.
  • dmk © (11.02.17 03:10) [17]
    Там и 3D и 2D. Типа своего директ икса.
  • Pavia © (11.02.17 10:12) [18]

    >  Суть мультипоточности в моем случае сделать отрисовку быстрее
    > и сохранить порядок наложения рисуемых объектов. Чтобы результат
    > был одинаковый в одном потоке или нескольких.

    Что-бы сохранить порядок нужно либо Z-буфер либо разбивать экран на разные квадраты каждый квадрат обрабатывается своим потоком.
    Либо как во всех движках на фрагменты. Все они помещаются в очередь и потоки по очереди берут из неё задание на рендеринг. Или по кадрам один кадр рисует один поток второй кадр второй поток.
  • han_malign © (20.04.17 15:12) [19]

    > Но бага есть! Толи в самой винде, толи в драйверах. Скорее первое.

    - это не бага
    https://msdn.microsoft.com/en-us/windows/hardware/drivers/display/asynchronous-rendering
    GdiFlush()
    https://msdn.microsoft.com/en-us/library/windows/desktop/dd144844(v=vs.85).aspx
  • MinGW (20.04.17 20:46) [20]
    У вас DIB-секция - грубо говоря просто массив байт в памяти. Каким же образом GDI обязана "блокировать" доступ к ней? И зачем ей это?

    Это вопрос не функций GDI, а синхронизации потоков.

    Вы что, не имеете в своей многопоточной системе событий/сигналов типа "отрисовка кадра началась", "отрисовка кадра закончилась", "пора начать рисовать новый кадр"? Молотите как попало на максимуме процессора? Это как бы нехорошо.
  • dmk © (21.04.17 12:42) [21]
    >пора начать рисовать новый кадр
    Суть потоков в том, что в совокупности они настолько быстрые, что после вызова BitBlt они начинают рисовать в буфере который еще выводится. Т.е. система не успевает дорисовать до конца буфер, а потоки уже успевают в нем нарисовать новое. Флага в системе что отрисовка окончена нет. В одном потоке нет проблем.
  • D7 (24.04.17 11:32) [22]
    Вообще вроде логично что надо дождаться пока кадр выведется, и тока потом продолжать рисование на этом кадре.
    Дак может сделаете свой?

    ...
    IsDrawing:=True;
    BitBlt(...);
    IsDrawing:=False;
    ...


    Как бы логически аналог Lock/Unlock из TCanvas.
  • dmk © (24.04.17 17:23) [23]
    Вы странный человек. Это потоки. У меня рисует только один поток с ID=0.
    Остальные ждут пока он отрисует. Именно такой флаг и стоит. Только когда делается вызов BitBlt, поток ID=0 сразу после вызова функции выходит и возвращается в цикл где ждут остальные потоки, чтобы продолжить отрисовку, но сама BitBlt еще не успевает дорисовать!!! Сама функция еще не выполнена!!! Может она драйверу дает отрисовывать, может еще кому. Факт в том, что она не успевает дорисовать, а потоки уже рисуют в буфере. 20 потоков (в моем случае) это очень быстро!!!! BitBlt просто не потокобезопасна. Она жизнеспособна только в одном потоке. У меня и Synchronize стоит и флаги. Результат один и тот же.

    Еще раз привожу код:


    //Это процедура вывода на экран
    procedure TTestForm.UpdateThreads;
    var
     dp: TDrawParams;

    begin
     if (not gThreadsUpdating) then
     begin
       gThreadsUpdating := true;

       if bZBuffer then sBmp.DrawZBuffer;

       //Информация и контролы
       sBmp.DrawSmallText(Rgn.X + 10, Rgn.Y + 10, Ansistring('Рисует ID: ' + HexToStr(Word(gFirstID)) + 'h'), 1, crWhite, 255, 64);
       dp.Angle := gAngle;
       if bInfo then TestForm.DrawCenterInfo(dp);
       RepaintControls;
       DrawProgress(gProgress, false);
       UpdateWindow; // <-Здесь находитя BitBlt

       if b3D then sBmp.ClearZBuffer;

       //Очищаем фон
       ClearBack;

       //Счетчик обновлений
       Inc(gNumBufferUpdates);

       gThreadsUpdating := false;
     end;
    end;

    //Это цикл отрисовки
    procedure QThread.Execute;
    var
     cP: TFloatPoint;
     Params: TDrawParams;

    label
     StopDraw;

    begin
     //Параметры по умолчанию
     Params := FillParams;

     //Центр вращения
     cP.fX := cX;
     cP.fY := cY;

     //Порядковый номер потока
     NumberID := GetFreeID(ThreadID);

     //Заполняем параметры
     Params.cX := cX;
     Params.cY := cY;
     Params.ThreadID := NumberID;
     Params.Angle := gAngle;

     //Отсечение всего региона
     if bClip then
       Params.R := lmR else
       Params.R := Rgn;

     //Цикл отрисовки
     while (gDrawCounter <= gNumDrawCycles) do
     begin
       //Превышение индекса объектов
       //учитывается в программе отрисовки
       //Рисуем ...
       gDrawProc(Params);

       //Обновляет буфер самый шустрый поток
       if (ThreadID = gFirstID) then
       begin
         //Обновляем буфер
         Synchronize(TestForm.UpdateThreads); //<-- Synchronize не обязательно, но может моргать!
         //TestForm.UpdateThreads;
         //Прогресс
         gProgress := Round(gDrawCounter / (gNumDrawCycles - 1) * 100);
         //Кол-во циклов отрисовки
         Inc(gDrawCounter);
         //Следующий угол
         gAngle := RorAngle(gAngle, gAngleInc);
         //Сброс кол-ва отрисованных объектов
         gObjectIndex := 0;
       end;//if (gNumEntered)

       //Устанавливаем флаг завершения отрисовки
       FPaints[NumberID] := True;

       //Остальные потоки ждут здесь
       //пока первый поток не отрисует буфер
       //или все потоки не соберутся здесь
       while (not PaintsFinished) do
       begin
         //Проверка на превышение циклов отрисовки
         if (gDrawCounter >= gNumDrawCycles) then goto StopDraw;
       end;

       //Проверка на ESC
       if gESC then goto StopDraw;
     end;//while

    StopDraw:

     //Устанавливаем флаг завершения отрисовки
     FPaints[NumberID] := True;

     //Ждем здесь пока остальные потоки не закончат отрисовку
     while (not PaintsFinished) do
     begin
     end;
    end;

  • dmk © (24.04.17 17:40) [24]
    Короче добавил CriticalSection в UpdateThreads - скорость снизилась, но глюки исчезли.
    Значит Игорь Шевченко ©   (09.02.17 21:39) [4] был прав.
Есть новые Нет новых   [134427   +34][b:0][p:0.001]