【问题标题】:Blocking Canvas阻塞画布
【发布时间】:2023-03-26 10:01:01
【问题描述】:

我有一个我的应用程序的表单,它可以有多达 1000 个可视组件,我在其中使用位图的 Canvas 绘制每个组件,并保存每个组件的位图(一种双缓冲),因为每个操作需要 20 毫秒。

我正在使用线程来绘制位图并将带有此位图的通知发送到 MainThread,以刷新可视化组件 UI。

理论上,当组件的位图被绘制在线程中时,它必须具有流体形式以显示组件,但实际上它不是流体。我决定看一下 delphi 的 TCanvas,我发现了一些惊人的东西:

class var // <<<<<<<<<<<<<<<<<<<<<<<<<<<< class var
  FLock: TObject;

function TCanvas.BeginScene(AClipRects: PClipRects = nil; AContextHandle: THandle = 0): Boolean;
begin
  Lock;
  ...
end;

procedure TCanvas.EndScene;
begin
  ...
  Unlock;
end;

class procedure TCanvas.Lock;
begin
  TMonitor.Enter(FLock);
end;

class procedure TCanvas.Unlock;
begin
  TMonitor.Exit(FLock);
end;

这绝对看起来不对。为什么 embarcadero 无法在不同线程中同时使用 TCanvas?创建 10 个线程来绘制位图是没有用的,因为每次都会处理 1 个线程...

  1. 为什么会存在?
  2. 有什么解决方法吗?如果我制作我的版本会发生什么 每个 TCanvas 仅具有本地监视器的 FMX.Graphics?
  3. 是否有任何第三方库拥有自己的 TCanvas?

我知道很多人会建议我使用原生类,Android 中的 JCanvas 和 iOS 中的 CGContextRef,但我想要一个 TCanvas 的解决方案,因为它的工作是封装所有平台的绘图功能,并且易于使用使用。

============= @EDIT =============

我将 FMX.Graphics 单元中 TCanvas 的 Lock 和 Unlock 更改为使用本地而不是全局监视器,以及 FMX.Types3D 单元中 TContext3D 的 BeginScene 和 EndScene。 我对这种变化感到非常担忧,但显然应用程序运行正常,最大的工作是重新编译整个 FMX。

【问题讨论】:

  • 做好失望的准备。此外,成千上万的视觉组件是一个不好的迹象。这不可能是最好的方法。我敢打赌,这个问题可以很容易地解决,而无需使用线程。
  • 特别是在移动平台的Firemonkey中,拥有太多控件是非常糟糕的设计。它变得太重了。相反,您应该 a) 尽可能重复使用控件,b) 为单独的主题制作单独的表单,并且只在第一次需要时创建它们,以及 c) 在不再需要时销毁控件。
  • @JerryDodge 我正在展示产品卡片,它位于 vertscrollbox 内。我已经减少了控件的数量,只制作了“显示控件”,它的位图缓冲区有很多产品卡......不幸的是,我不能只创建一个“大显示控件”,因为画布对每个设备都有大小限制(TCanvasAttribute.MaxBitmapSize)。但这不是问题。所有主要的移动应用程序都使用许多线程来解码 jpeg、绘制缓冲区等,只是为了节省 MainThread 并允许快速打开表单,并且不影响最低 60 fps 以使屏幕流畅。
  • 用户能否一次显示所有这些产品卡片?如果没有,就没有理由画它们——如果我理解正确,你画了所有的? - 一次全部,但确定哪些是可见的,然后绘制这些。也许甚至有概览的缩略图,而在详细视图中只有完整的产品图像?

标签: delphi canvas bitmap firemonkey


【解决方案1】:

Tbitmap 并不是真正的多线程。它是在 Delphi Tokyo 中作为多线程制作的,但设计非常糟糕(当您在后台线程中使用 Tbitmap 时,它们仍然有很多错误,例如 Tbitmap 仍然使用根本不是多线程的消息通知,因此可能导致随机异常)。在东京做得不错的是使 OpenGL 上下文多线程(在 android/ios 下),并且工作得很好(但不是仍然绑定到消息传递的 TTexture,但您可以轻松更新 ttexture 的源代码以更正(你可以看Alcinoe的源码知道怎么做)。

您想要实现的唯一解决方法是:

  1. 不要使用TBitmap,而是使用Texture(因为openGL完全多线程没有任何锁)
  2. 使用原生 OS 功能在后台线程中构建纹理(Android 中的 JCanvas 和 iOS 中的 CGContextRef)
  3. 避免使用如此多的控件,而是在正确的位置为自己绘制从主线程(因此在 onpaint 事件中)准备好的和可见的所有纹理

是的,我知道这很痛苦!

【讨论】:

  • 是的,我已经使用了你的库,alcinoe,我真的很喜欢它,而且我看到你使用了本机函数。这是太多的工作,但它工作得很好。但我相信创建一个从头开始编写的自定义位图并不是很困难,以正确的方式完成,使用自定义 Canvas,它是本机函数的包装器,多平台,多线程,具有本地关键部分,并且具有纹理属性...然后为控件的 TCanvas 创建一个助手绘制此自定义位图...
  • "避免使用这么多控件" 是的,我已经尽可能使用最少的控件,而且性能非常好,问题只是第一次绘制,这就是为什么我不得不画进去后台线程。
  • @vfbb 您可以尝试使用 Tcanvas(直接在 openGL 上工作),但最后您会发现 embarcadero 团队无法与成千上万的 android 开发人员所做的工作竞争Jcanvas 或 ios 开发人员在其图形功能中。如果速度和质量(是的,质量也是一个因素)不是很重要,那么尝试使用 Tbitmap(但不是在多线程中)
猜你喜欢
  • 2019-07-27
  • 2019-01-27
  • 2019-02-18
  • 2012-10-08
  • 1970-01-01
  • 2012-02-20
  • 1970-01-01
  • 2017-08-26
  • 2016-07-06
相关资源
最近更新 更多