Перейти к содержанию

Многопоточный GDI


Рекомендуемые сообщения


Здравствуйте. Кто может подсказать по поводу многопоточной отрисовки графики посредством
GDI ?
Суть в следующем - экран разбит на 5 столбцов шириной примерно 200 пикселей. В каждый столбец
рисую несколько графиков используя Canvas.Pixels либо Canvas.MoveTo,LineTo. С целью ускорения вывода
сделал отрисовку каждого столбца в своём потоке, а после отрисовки всех столбцов многопоточно,далее 
в основном потоке делаю вывод на экран посредством Canvas.Draw. Для каждого столбца создаётся 
своя bitmap на собственном CompatibleDC.Однако скорость не увеличивается, а наоборот уменьшается и 
довольно сильно. Путём отладки выяснил, что в момент выполнения Canvas.Pixels поток лочится и другие 
потоки не работают.

Ещё раз повторю для каждого потока создаётся собственная bitmap привязанная к совственному CompatibleDC.
т.е. при выполнении Thread(N).Bitmap.Canvas.Pixels[x,y] := RandomColor все остальные Thread лочатся
до завершения операции в Thread(N). И не просто до завершения, а лок висит ещё довольно долго, что
приводит к увеличению времени отрисовки по срвнению с однопоточным режимом. Реальные цифры таковы

в однопоточном режиме заполнение одного столбца 400X100 случайными пикселями происходит примерно 
за 16 мсек. Двух столбцов 32 мсек.

В многопоточном режиме - один столбец,один поток (блокировок потоков не возникает) 16мсек.
два столбца, два потока 210 мсек. (чем больше столбцов тем дольше рисует по сравнению с однопотоком)

выход пока видится только в создании массивов плоскостей пикселей не привязанных ни к какому DC
и отрисовка на этих плоскостях графических примитивой с помощью например алгоритмов Брезенхема.
Но хотелось бы DrawText со всем шрифтовым богатством Windows.

Вопрос в следующем - рисовал ли кто в многопотоке функциями GDI и каким образм ?
ps: WinXp / Delphi7

Изменено пользователем kaktus
дополнение
Ссылка на комментарий
Поделиться на другие сайты

В 17.08.2020 в 19:56, kaktus сказал:

Thread(N).Bitmap.Canvas.Pixels[x,y]

Загляни в реализцию этого метода. Я не уверен, что он использует чистый GDI. Вполне возможно, внутри ставится какой-то лок, который общий для всего окна.

Ссылка на комментарий
Поделиться на другие сайты

Thread(N).Bitmap.Canvas.Pixels[x,y] наверняка лочится, однако я отрабатывал множество разных вариантов, в том числе и вариант API SetPixel(DC,X,Y,Color)  - увы он тоже лочит потоки.

Ссылка на комментарий
Поделиться на другие сайты

13 часов назад, kaktus сказал:

увы он тоже лочит потоки.

Скорее всего, лочится не поток, а контекст. А поток ожидает его освобождения. Попробуй в потоке создавать контекст в памяти, а потом отображать его на нужный контекст блиттингом.

Ссылка на комментарий
Поделиться на другие сайты

Я так и делал, вот код потока 

Спойлер

  mainDC := GetDC(vHndl);

  Try

   fDC     := CreateCompatibleDC (mainDC);
   fBitmap := CreateCompatibleBitmap (mainDC,400,100);

   oldBitmap := SelectObject (fDC,fBitmap);

   For iy := 0 To 100 - 1 Do
    For ix := 0 To 200 - 1 Do
     SetPixel(fDC,ix,iy,RGB (random(255),random(255),random(255)));
     
  SelectObject (fDC,oldBitmap);
  DeleteObject(fBitmap);
  DeleteDC(fDC);

  ReleaseDC(vHndl,mainDC);

  Finally
   InterlockedDecrement(DV^);
  End;

 

Это было для проверки времени выполнения и до вывода результата даже не дошло. Если закомментить  SetPixel - то блокировок не возникает на любом количестве потоков. Однако любая графическая операция тут же начинает блокировать поток. Потоки начинают выполнятся последовательно. Я даже делал так - создавал и удалял CompatibleDC в основном потоке (использовал массив) а потокам раздавал уже готовый bitmap ( думал что может GetDC блокировало) - но нет, не помогло.

Ссылка на комментарий
Поделиться на другие сайты

Ну все правильно. Ты основной контекст получил, но не отпустил его. Зачем ты его держишь во время операций с контекстом из памяти? Получил - создал совместимый контекст - отпустил основной. Поработал с совместимым, блиттингом загнал его на основной и все. Может быть, этом причина. Но это не точно. Проверь, если не поможет, попробуй переключиться на DIB секции. У меня в уроке по созданию трейнера на плюсах есть работа с диб секциями. Но основной контекст не держи, мой тебе совет. А вообще, не видя весь код, сложно что-либо советовать.

Ссылка на комментарий
Поделиться на другие сайты

Я так делал : Создавал несколько CompatibleDC/Compatible bitmap в основном потоке и отпускал DC. Затем запускал потоки и после

их завершения освобождал Compatible ресурсы. Увы - тоже не взлетело.

А как прикрепить файл - rar архив,? Я не нашёл.

ps: Мне почему то кажется, что сама операция SetPixel содержит код который включает механизм блокировки, (например SetPixel вызывает SendMessage) я в гугле нашёл множество аналогичных вопросов на эту проблему, но не нашёл ни одного решения - кроме отсылок к msdn и уверений, что это возможно "мамой клянусь". Я больше склоняюсь к мысли что это невозможно и придётся работать с массивами и низкоуровневыми алгоритмами отрисовки.

Ссылка на комментарий
Поделиться на другие сайты

Про SendMessage в SetPixel я конечно погорячился :) но мне простительно, это я от отчаяния. (Что конечно не отрицает возможности наличия других типов блокировок в графических функциях GDI) Привожу максимально упрощённый код для тестирования многопоточной отрисовки. К качеству прошу не придираться - код для демонстрации.

https://fex.net/ru/s/lxdcmrp
не смог найти как прикрепить файл к форуму, через некоторое время ссылка протухнет и для потомков ничего не останется.

ps: С++ я тоже понимаю со словарём :) Поэтому если кто-нибудь решится привести рабочий пример многопоточной отрисовки на C++ это тоже будет считаться благородным поступком и зачтётся плюсиком в карму.

 

Изменено пользователем kaktus
дополнение
Ссылка на комментарий
Поделиться на другие сайты

Не знаю как работает Bitmap.Canvas.Pixels[x,y], но мне кажется это не просто доступ к массиву, если бы у тебя сразу bitmap обрабатывалась как массив, тогда бы у тебя и один поток наверное всё бы успевал, SetPixel это вообще супер медленный метод! А несколько потоков простых, например созданных через CreateThread и работающих со своими bitmap вообще никак не могут друг друга заблокировать, они при такой проблеме скорей бы вызвали ошибку, это всё язык на котором ты пишешь и его навороты, что он там сотворяет внутри.

Ссылка на комментарий
Поделиться на другие сайты

Цитата

... SetPixel это вообще супер медленный метод! ...

Да это вобщем-то неважно, речь идёт о том что параллельная работа с GDI невозможна. Не нравится SetPixel - попробуй TextOut раз 500 на поток. Окажется что в однопотоке это всё выполняется быстрее чем в многопотоке. А если попытаться проследить выполнение кода - окажется, что потоки встают в очередь и выполняются последовательно.

 

Цитата

... А несколько потоков простых, например созданных через CreateThread и работающих со своими bitmap вообще никак не могут друг друга заблокировать...

- Угу, я тоже так думал. Попробуй на C++ это сотворить - сильно удивишся.

Блокировка идёт не на уровне bitmap (я про тот который HBITMAP потому что tBimap считается потокобезопасным, а значит однозначно набит Locka-ами/UnLock-ами )

и блокировка идёт даже не на уровне bitmap а скорее либо на уровне драйвера видеокарты (и CompatibleDC тут не спасает) либо сама Windows не позволяет выполнять графические функции API в нескольких потоках.

 

Цитата

...это всё язык на котором ты пишешь и его навороты...

Та там практически чистый API. Всякие удобные обёртки типа tThread к проблеме отношения не имеют. Посмотри код который я приложил - он хорошо документирован. Попробуй его выполнить на C++ тогда поймёшь масштаб беды :)

 

Я конечно могу на С++  пример приложить, но на это уйдёт крайне много времени т.к. мне придётся начинать с установки среды программирования. И программу на C++ мне придётся писать чуть ли не с букварём. Я надеюсь что на форуме есть люди которые умеют и C++ и ObjectPascal.

Изменено пользователем Xipho
Для цитирования нужно выделять часть текста собеседника, которую хочешь процитировать, и кликнуть по появившемуся "цитировать выделенное". В следующий раз будет пред.
Ссылка на комментарий
Поделиться на другие сайты

9 часов назад, kaktus сказал:

Я надеюсь что на форуме есть люди которые умеют и C++ и ObjectPascal.

Есть такие люди у нас. И imaginary умеет в плюсы. Возможно, она сможет вопроизвести твою проблему. Я тоже попробую это сделать, но у меня со  свободным временем очень туго, увы.

Ссылка на комментарий
Поделиться на другие сайты

Хм... Всё страньше и страньше ...

Напоминаю - программа заполняет две области 400x100 случайными пикселями.

Мучая несчастный GDI заметил такую странность. (Я сейчас исключительно про однопоточный режим. Вся работа происходит в основном потоке программы). Время работы программы меняется непредсказуемым образом, но принимает всего три значения - 32 мсек. 86мсек и 130мсек. ну плюс/минус конечно.

Измеритель у меня точный - QueryPerfomanceCounter, не какой-нибудь там GetTickCount :). Комп не занят, 4ре ядра... Ещё страньше то, что API SetPixel выполняется дольше чем Canvas.SetPixels[x,y] Хотя  Canvas.SetPixels обёртка вокруг API SetPixel. Чудеса вобщем. Ну да ладно - это чисто теоретический интерес, пока отложим.

Приятная новость заключается в том, что под Win7 время заполнения на нескольких потоках по крайней мере не больше чем на одном потоке. Уже радует. Ложка дёгтя тут заключается в том, что есть некая технологическая программа, которая работает исключительно на winXP (HASP, там ... все дела). И на таких компах ускорения отрисовки не получится. Хотя возможно и Windows7 не взлетит, проверю - напишу результат.

Далее по плану помучать DIBsection. Есть подозрение что CompatibleDC это всё таки про видеокарту и соответственно не shared ресурс, а DIB это про ОЗУ и возможно windows не будет разделять доступ к таким ресурсам.

ps: Если я пишу где-то глупость, то не стесняйтесь поправляйте меня. Я полез Куда Макар телят не гонял в несвойственные мне дебри и естественно буду косячить,

Ссылка на комментарий
Поделиться на другие сайты

Ура - DIB работает !!! И все странности со временем выполнения пропали. Скорость отрисовки тоже повысилась. 1 столбец - 10мсек. 2 столбца 14 мсек. Я так-же очень доволен тем фактом что нужные мне графические функции API типа TextOut поддерживают полноценную работу с DIB. Так-же

приятный бонус, что теперь можно писать напрямую в массив памяти и несомненно это будет быстрее чем SetPixel 

(.... не выдержал, проверил - 2 мсек. Более чем 10и кратное ускорение, если учитывать накладные расходы на обслуживание потоков..)

 

Предварительные выводы -

1.CreateCompatibleBitmap создаёт BITMAP привязанный к дайверу видеокарты (возможно к памяти видеокарты)

2. Параллелить вывод (если позволяет логика приложения) можно используя DIB секции. А судя по странностям с задержками вывода на основной DC так вообще предпочтительнее выводить сначала в DIB а потом BitBlt в основной DC. Всё равно народ в основном через двойной буфер выводит, так и пусть этот буфер будет DIB типа.

3. DIB секции поддерживают графические операции API и прямой доступ к плоскости изображения и скорее всего поддерживают все графические функции API (например кривые Безье)

4. Я маньяк (волосы торчат, глаза красные - пора спать)

 

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

  • Плюс 1
Ссылка на комментарий
Поделиться на другие сайты

В 20.08.2020 в 21:41, Xipho сказал:

Проверь, если не поможет, попробуй переключиться на DIB секции. У меня в уроке по созданию трейнера на плюсах есть работа с диб секциями

 

Ты предложил - и я услышал. Тестирование DIB секций я начал с твоей подачи. Тебе большое человеческое спасибо. :)

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.