Как правильно реализовать темы для рисования на холсте?
Я хочу создать пользовательский элемент управления, полученный из TCustomControl
который я буду переопределять Paint
метод и рисование таких вещей, как градиентный фон, графика и формы и т. д., а затем, наконец, сетка поверх всего этого.
Я знаю, что все это, вероятно, будет медленным, поэтому, чтобы оптимизировать все это, я думал об использовании потоков, например, одного потока для рисования фона, одного потока для рисования фигур и одного потока для рисования сетки, но я не слишком уверен в Понимание и реализация всего этого правильно.
Методом проб и ошибок и просмотром некоторых примеров потоков (хотя я никогда не мог найти хороших примеров рисования потоков), мне удалось придумать следующее, которое будет моим общим классом потоков:
type
TCanvasThread = class(TThread)
private
FOnThreadPaint: TNotifyEvent;
FCanvas: TCanvas;
protected
procedure Execute; override;
procedure Sync;
public
constructor Create(Canvas: TCanvas; OnPaint: TNotifyEvent);
destructor Destroy; override;
property Canvas: TCanvas read FCanvas;
end;
constructor TCanvasThread.Create(Canvas: TCanvas; OnPaint: TNotifyEvent);
begin
inherited Create(False);
FreeOnTerminate := True;
FCanvas := Canvas;
FOnThreadPaint := OnPaint;
end;
destructor TCanvasThread.Destroy;
begin
inherited Destroy;
end;
procedure TCanvasThread.Execute;
begin
if Assigned(FOnThreadPaint) then
Synchronize(Sync);
end;
procedure TCanvasThread.Sync;
begin
FOnThreadPaint(Self);
end;
И вышесказанное реализовано в пользовательском элементе управления следующим образом:
type
TMyControl = class(TCustomControl)
private
procedure OnClientPaint(Sender: TObject); // paint gradient
procedure OnShapesPaint(Sender: TObject); // paint shapes etc
protected
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
constructor TMyControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Width := 600;
Height := 400;
end;
destructor TMyControl.Destroy;
begin
inherited Destroy;
end;
procedure TMyControl.OnClientPaint(Sender: TObject);
begin
GradientFillCanvas(TCanvasThread(Sender).Canvas, clSilver, clWhite, ClientRect, gdVertical);
end;
procedure TMyControl.OnShapesPaint(Sender: TObject);
begin
TCanvasThread(Sender).Canvas.Rectangle(50, 50, 100, 100);
end;
procedure TMyControl.Paint;
begin
TCanvasThread.Create(Canvas, OnClientPaint);
TCanvasThread.Create(Canvas, OnShapesPaint);
// implement other paint threads etc..
// TCanvasThread.Create(Canvas, OnGridPaint);
// ...
// using regular canvas drawing here seems to be blocked too?
end;
С учетом вышесказанного я вижу, что градиент закрашен, и я вижу, что нарисована белая прямоугольная фигура, но при изменении размера элементов управления (например, при выравнивании по клиенту) возникает множество мерцаний, я думал о двойной буферизации с помощью растрового изображения, но если Можно было бы предпочесть использовать только холст. Я также больше не могу рисовать, используя обычные элементы управления canvas, как выделено закомментированной строкой в TMyControl.Paint
,
Неужели я неправильно понял что-то здесь основное, и я все это неправильно реализовал? Я читаю такие вещи, как критические секции, пулы потоков и т. Д., Но это несколько ошеломляет. Я экспериментировал с Canvas.Lock и Canvas.UnLock, но все мерцает независимо от изменения размера, и я не могу рисовать на обычном холсте после создания своих потоков в Paint
метод.
Итак, мой вопрос, как правильно реализовать темы для рисования на холсте? Код неправильный, и мне нужно начать заново и правильно его реализовать? Я действительно потерял сознание на этом этапе и нашел это довольно запутанным, я даже попытался перейти туда, где я создаю темы в Paint
метод перехвачен WM_SIZE
Метод сообщения, который несколько уменьшил мерцание, но не полностью, я боюсь, что мог пропустить что-то большее здесь, поэтому буду признателен за некоторые отзывы и рекомендации, пожалуйста.
Благодарю.
1 ответ
1) Вы не можете использовать VCL в многопоточном режиме. Он просто не предназначен для этого. Затраты на распространяющиеся защелки и блокировки для многопоточности были бы огромными, а выгода - неизмеримо бесконечно малой для 99% приложений.
2) И вы НЕ используете ваш Canvas в многопоточном режиме в любом случае. Посмотрите этот ваш код:
procedure TCanvasThread.Execute;
begin
if Assigned(FOnThreadPaint) then
Synchronize(Sync);
Что это значит? Это означает именно: "Я не хочу делать многопоточность, я хочу запускать всю свою работу в едином главном потоке VCL".
Что значит Synchronize
позвонить сделать? Прочитайте документацию, Synchronize
в его основной форме означает "временно остановить этот поток и выполнить его однопоточную работу". Теперь, если ВСЕ и КАЖДЫЙ из ваших фоновых рабочих потоков делают "останови меня и сделай мою работу в одном потоке" - тогда это то, что он говорит. Вы создаете потоки только для их немедленной остановки, но вместо этого вся работа передается в один основной поток VCL. Вы выделили только чертовски много ресурсов, которые вы не используете. Вы создали однопоточное приложение с огромным бременем, которое создавало дополнительные потоки, которые только для остановки. Вы дополнительно убили предсказуемость и создали - Google термин! - состояние гонки. Теперь у вас есть несколько "псевдо-фоновых потоков", но вы никогда не можете сказать, какой из них будет работать первым, а какой - вторым.
Так какие варианты тогда?
3) Прежде всего, используйте многопоточность только тогда, когда у вас нет другого выбора. И когда у вас есть блоки задач, которые на 100% изолированы, а не одна общая переменная.
procedure TMyControl.Paint;
begin
TCanvasThread.Create(Canvas, OnClientPaint);
TCanvasThread.Create(Canvas, OnShapesPaint);
Правило нарушено, вы берете одну и ту же переменную Canvas и помещаете ее в оба потока. Вы не должны. Если вы не можете разделить потоки, чтобы быть полностью изолированными, то, скорее всего, у вас нет задачи, которая является многопоточной.
Ладно, я слишком строг, есть задачи, в которых можно разделить небольшую сумму на переменные, при условии, что ЛЮБОЙ доступ к ним ЯВЛЯЕТСЯ СВОБОДНО АРБИТРИРОВАННЫМ, поэтому никогда 2+ потоки не будут делать это одновременно. Но для любого новичка правило большого пальца, как я сказал: 100% -ная изоляция или отсутствие многопоточности, даже 99%.
Таким образом, как правило, вы можете захотеть использовать один Canvas, а это значит, что вы можете сделать это только в одном потоке. Oookay. Попробуйте использовать более быстрый холст вместо стандартного VCL. Например, http://graphics32.org/. Были также TCanvas
реализации над Direct 2D
а также GDI+
- Я не знаю, быстрее они или нет. Или какую-нибудь другую библиотеку 2D-графики из http://torry.net/ и аналогичные каталоги.
В общем, прежде чем пытаться создать медленное многопоточное приложение, потратьте свое время и силы на создание быстрого однопоточного приложения.
4) Иногда вы действительно можете разделить свою картинку на слои, такие как слои Photoshop. На этот раз вы можете надеяться на многопоточность. Вы создаете НЕСКОЛЬКО разных растровых изображений, по одному на поток. Вы заполняете их прозрачным цветом. Затем вы заставляете свои потоки рисовать нужные части в свои собственные растровые изображения. Затем в основном потоке вы видите, когда ВСЕ потоки сделали свою работу, а затем в одном основном потоке вы сливаете эти много прозрачных растровых изображений один за другим в целевую форму. TPainBox
холст, и вы делаете это в правильном порядке. Но даже тогда вам лучше бросить их на склад TCanvas
а также TBitmap
и использовать более быстрые библиотеки. Как бы то ни было, у меня никогда не было надежной и быстрой работы на складе VCL TBitmap
с прозрачными изображениями они просто не были предназначены для истинной прозрачности. И это вновь и вновь проявляется в неожиданных недостатках и глюках.
5) Другое дело с этими нитями, кроме рас, вы просто не имеете права рисовать на окнах GDI за пределами WM_PAINT
событие или в VCL
условия, вы просто нарушаете договор, когда вы рисуете форму (или любой TWinControl
) вне его Paint
метод (или OnPaint
обработчик вызывается в базе Paint
метод). Это просто нарушение законов MS Windows. Вы можете перенести в фоновые потоки некоторые данные, заполняющие кеш, вычисляя или загружая некоторые невидимые данные. Возможно, в крайнем случае, даже рендеринг этих данных в эти монополизированные временные битовые карты по одному на поток. Но рендеринг самой формы, рисование на холсте - может быть сделано только в Paint
/ OnPaint
и НЕ может быть выгружен в любой объект, запущенный после выхода из метода Paint. Контроль потока рендеринга должен все внутри Paint
никогда не снаружи. Итак, потоки здесь не применимы: они выполняются вне Paint
Способ, которым они не имеют законного права трогать холст вашей формы. Вы должны прочитать некоторые учебные пособия по окнам и сообщениям GDI в MS Windows и узнать, как работают циклы аннулирования и восстановления.
6) и, наконец, найдите OmniThreadingLibrary и прочитайте все учебные пособия и объяснения, которые вы можете найти по этому поводу. Вы должны понять простую идею - многопоточность всегда стоит дорого (всегда работает менее эффективно, чем однопоточная программа, если вычислять на процессор), и только некоторая часть программы может быть извлечена в многопоточность, а не целую программу, и что только 100% изолированные части любой работы действительно многопоточны. Части работ, которые имеют какое-либо отношение друг к другу, не получают 100% многопоточности, независимо от того, что вы делаете. Другими словами, прочитайте как можно больше учебников и FAQ по OTL, чтобы понять простую идею: вы НЕ хотите многопоточности большую часть своей жизни. Многопоточность - исключение из нормы, которое стоит только в некоторых конкретных ситуациях. Когда вы сомневаетесь, нужна ли вам многопоточность или нет - тогда вам нужна однопоточность. Вы используете многопоточность только как последний шанс, когда не работает нормальное и законное средство. Это было полушутя, но только наполовину.