通过共享资源内存来结合CoreGraphics和Metal的功能

Metal API很棒,它为移动设备和台式设备打开了很多可能性。 但是, GPU有时可能有点笨拙,尤其是在诸如字体渲染或曲线绘制之类的高精度项目中,因此有时您确实会错过旧的CPU优秀项目。 我经常碰巧会想使用CoreGraphics的一些功能,但是保持我的GPU管道快速高效,而没有多余的转换CPU / GPU同步中断

启发我写这篇文章的情况是,使用户能够绘制遮罩覆盖图,以识别希望显示某些效果的区域。 当然可以用Metal来实现绘图,但这需要大量工作:保持路径数据,三角剖分和渲染逻辑。 对于快速原型,仅使用CoreGraphics会容易得多,但是最直接的方法是从UI画布创建CGImage ,然后转换为MTLTexture以将其作为遮罩提供给某些着色器。

多维门户

但是,如果我们希望避免不必要的转换并将CoreGraphics路径直接绘制到MTLTexture中怎么 ? 这似乎完全有可能!

为了实现这一点,我们必须在某些CGContextMTLTexture之间共享一个内存缓冲区。 Metal可以从MTLBuffer对象创建纹理,而MTLBuffer则有机会指向已分配的内存。

因此,我们需要执行以下操作:

  1. CGContext分配内存
  2. 创建指向相同内存的MTLBuffer
  3. MTLBuffer创建MTLTexture
  4. 瞧!

实施

免责声明#1:在此演示中,我使用Alloy ,这是我在Apple平台上所有GPU项目中使用的Metal库。 它在香草金属上提供了一个很小的Swifty API

免责声明2:我既不在演示中也不做任何分解,也没有错误处理。 一切都在视图控制器中完成,无论是强制展开还是致命错误。 本文用作参考片段。

当为MTLBuffer分配无副本内存时,事情有些棘手。 为了使其快乐,它必须完美对齐 ,通常为4096字节。 在创建MTLBuffer时, MTLTextures也很麻烦 :我们必须对齐每个纹理行 ,并且对齐方式取决于我们要使用的像素格式 。 幸运的是,这两个任务都是可行的!

为了演示起见,我们将为MTKView上的可绘制单通道蒙版分配共享资源。

每当我们的MTKView更改大小时,我们都会重新分配掩码。 当然不是必须的,但这是最简单的方法。

首先,我们需要从系统中了解RAM页面大小,因此,我们将使用getpagesize()函数。 现在我们必须计算每行对齐的字节数和总分配大小。 为了分配对齐的内存,我们将使用posix_memalign(_,_,_)系统函数。

请注意,从这一点来看,我们负责分配此内存,但是稍后我们将对其进行处理

现在我们准备从此内存创建CGContext ,您可能已经完成了很多次,因此这应该非常简单:

我在这里使用CGImageAlphaInfo.no​​ne表示这是灰度图像,但是您也可以使用.alphaOnly

使用完全相同的内存,我们现在可以创建一个无副本MTLBuffer ,使其负责释放内存。 但是,您当然可以通过将nil传递给最后一个参数来自行管理。

现在,最后一步:我们正在从此缓冲区创建纹理。 纹理的存储模式必须与缓冲区相同。 您可以在此处阅读针对不同平台/操作系统的其他要求。

由于我们最初以Metal应该满意的格式分配内存,因此一切都应该顺利进行。

演示版

好的,我们都准备好了! 将CGContextMTLTexture缓存到某个位置后,您可以对它们进行任何操作。

我们将创建一个简单的应用程序,使您可以“刮擦”模糊的纹理,最终结果可以在视频中看到:

让我们开始寻找一个可爱的小狗图像 ,将其加载到MTLTexture中 ,然后使用Metal Performance Shaders进行模糊处理。

Simple Metal片段着色器将处理原始纹理,覆盖纹理和蒙版纹理的混合:

然后,只要MTKView要重绘自身,我们就只需调度它。

您应该考虑CPUGPU之间发生的内存同步。 您不应创建CoreGraphicsMetal可以同时访问此缓冲区的情况,尤其是在macOS上

为防止本文过分膨胀,我故意不包括使用UIKit touch API 创建渲染管道状态片段顶点着色器CoreGraphics绘图代码,您可以在此处找到完整的源代码。

结论

混合使用CoreGraphicsMetal确实非常有用,并且可以同时使用两种方法:从CoreTextMetal绘制文本,或将3d网格渲染到CGContext并以某种方式在CPU上处理 。 剩下的就是你的想象力!

Interesting Posts