Как правильно реализовать темы для рисования на холсте?

Я хочу создать пользовательский элемент управления, полученный из 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, чтобы понять простую идею: вы НЕ хотите многопоточности большую часть своей жизни. Многопоточность - исключение из нормы, которое стоит только в некоторых конкретных ситуациях. Когда вы сомневаетесь, нужна ли вам многопоточность или нет - тогда вам нужна однопоточность. Вы используете многопоточность только как последний шанс, когда не работает нормальное и законное средство. Это было полушутя, но только наполовину.

Другие вопросы по тегам