Перейти к содержанию
Авторизация  
kaktus

Многопоточный 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. Я маньяк (волосы торчат, глаза красные - пора спать)

 

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

Поделиться сообщением


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

Хотел предложить попробовать DIB секции, но ты уже сам до этого варианта дошел )) Молодец ) Я рад, что у тебя получилось ) 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
В 20.08.2020 в 21:41, Xipho сказал:

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

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
2 часа назад, kaktus сказал:

Ты предложил - и я услышал

А, старею ) Забыл, что все-таки успел это предложить ранее ) 

Поделиться сообщением


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

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

  • Предпросмотр
Авторизация  

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

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

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