循环中单个像素的渲染速度
Render speed for individual pixels in loop
我正在努力将单个像素绘制到 UIView
以创建分形图像。我的问题是我的渲染速度。我目前正在 运行 循环此循环 260,000 次,但我想渲染更多像素。事实上,在我的 iPad Mini 上 运行 大约需要 5 秒。
我之前用的是UIBezierPath
,但是速度更慢(大约7秒)。我一直在寻找 NSBitMap
的东西,但我不确定这是否会加快速度或首先如何实现它。
我也在考虑尝试将循环中的像素存储到数组中,然后在循环后将它们全部绘制在一起。不过,我不太确定在数组中存储像素然后从数组中检索像素的最佳过程是什么。
任何有助于加快此过程的帮助都会很棒。
for (int i = 0; i < 260000; i++) {
float RN = drand48();
for (int i = 1; i < numBuckets; i++) {
if (RN < bucket[i]) {
col = i;
CGContextSetFillColor(context, CGColorGetComponents([UIColor colorWithRed:(colorSelector[i][0]) green:(colorSelector[i][1]) blue:(colorSelector[i][2]) alpha:(1)].CGColor));
break;
}
}
xT = myTextFieldArray[1][1][col]*x1 + myTextFieldArray[1][2][col]*y1 + myTextFieldArray[1][5][col];
yT = myTextFieldArray[1][3][col]*x1 + myTextFieldArray[1][4][col]*y1 + myTextFieldArray[1][6][col];
x1 = xT;
y1 = yT;
if (i > 10000) {
CGContextFillRect(context, CGRectMake(xOrigin+(xT-xMin)*sizeScalar,yOrigin-(yT-yMin)*sizeScalar,.5,.5));
}
else if (i < 10000) {
if (x1 < xMin) {
xMin = x1;
}
else if (x1 > xMax) {
xMax = x1;
}
if (y1 < yMin) {
yMin = y1;
}
else if (y1 > yMax) {
yMax = y1;
}
}
else if (i == 10000) {
if (xMax - xMin > yMax - yMin) {
sizeScalar = 960/(xMax - xMin);
yOrigin=1000-(1000-sizeScalar*(yMax-yMin))/2;
}
else {
sizeScalar = 960/(yMax - yMin);
xOrigin=(1000-sizeScalar*(xMax-xMin))/2;
}
}
}
编辑
我创建了一个多维数组来存储 UIColors,因此我可以使用位图来绘制图像。它明显更快,但我的颜色现在不能正常工作。
这是我将 UIColors 存储到数组中的位置:
int xPixel = xOrigin+(xT-xMin)*sizeScalar;
int yPixel = yOrigin-(yT-yMin)*sizeScalar;
pixelArray[1000-yPixel][xPixel] = customColors[col];
这是我画的东西:
CGDataProviderRef provider = CGDataProviderCreateWithData(nil, pixelArray, 1000000, nil);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGImageRef image = CGImageCreate(1000,
1000,
8,
32,
4000,
colorSpace,
kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast,
provider,
nil, //No decode
NO, //No interpolation
kCGRenderingIntentDefault); // Default rendering
CGContextDrawImage(context, self.bounds, image);
不仅颜色不对,而且每次渲染图像时,颜色都与上次完全不同。我一直在用颜色测试不同的东西,但我仍然不知道为什么颜色不对,而且我更困惑它们是如何不断变化的。
如果您想最大化渲染速度,我会推荐位图渲染。矢量光栅化要慢得多,并且 CGContext 绘图并不是真正用于高性能实时渲染。
我遇到了类似的技术挑战,发现 CVOpenGLESTextureCacheRef
是最快的。纹理缓存允许您将位图直接上传到图形内存中以进行快速渲染。渲染利用 OpenGL,但因为它只是 2D 全屏图像 - 您真的不需要了解 OpenGL 就可以使用它。
您可以在此处查看我写的使用纹理缓存的示例:https://github.com/justinmeiners/image-sequence-streaming
我原来的相关问题在这里:
How to directly update pixels - with CGImage and direct CGDataProvider
我的项目从文件渲染位图,所以它有点不同,但您可以查看 ISSequenceView.m 的示例,了解如何使用纹理缓存和设置 OpenGL 进行此类渲染。
您的渲染过程可能类似于:
1. Draw to buffer (raw bytes)
2. Lock texture cache
3. Copy buffer to texture cache
4. Unlock texture cache.
5. Draw fullscreen quad with texture
如果你真的想单独改变许多不同的像素,你最好的选择可能是分配一块内存(大小 width * height * bytes per pixel
),直接在内存中进行更改,然后转换整个东西使用 CGBitmapContextCreateWithData
立即转换为位图
可能有比这更快的方法(参见 Justin 的回答)。
逐像素绘图——对每个像素进行复杂的计算,如分形渲染——是你可以要求计算机完成的最困难的事情之一。这里的每个其他答案都涉及其困难的一个方面,但这还不是全部。 (幸运的是,现代硬件也针对这种渲染进行了优化,如果您知道要它做什么的话。我会讲到的。)
@jcaron 和@JustinMeiners 都注意到 CoreGraphics 中的矢量绘图操作(甚至是矩形填充)会对基于 CPU 的光栅化产生不利影响。操作位图数据缓冲区会更快,但不会快很多。
将该缓冲区放到屏幕上也需要时间,特别是如果您必须完成创建位图图像缓冲区然后在 CG 上下文中绘制它们的过程 — 这需要在CPU 和大量的内存带宽工作来复制该缓冲区。所以@JustinMeiners 是正确的,直接访问 GPU 纹理内存将是一个很大的帮助。
但是,如果您仍然在 CPU 代码中填充缓冲区,您仍然会受到两种成本的阻碍(充其量,如果您天真地这样做会更糟):
- 渲染每个像素的顺序工作
- 渲染时从纹理内存到帧缓冲区的内存传输成本
@JustinMeiners 的回答很适合他的用例——图像序列是预渲染的,所以他确切地知道每个像素将是什么,他只需要将它放入纹理内存中。但是您的用例需要大量的每像素计算。
幸运的是,每像素计算正是 GPU 的设计目标!欢迎来到像素着色器的世界。对于屏幕上的每个像素,您可以 运行 一个独立的计算来确定该点与分形集的关系,从而确定用什么颜色绘制它。可以 运行 并行计算一次处理多个像素,它的输出直接进入屏幕,因此没有内存开销可以将位图转储到帧缓冲区中。
在 iOS 上使用像素着色器的一种简单方法是 SpriteKit — 它可以为您处理大部分必要的 OpenGL/Metal 设置,因此您只需编写逐像素算法在 GLSL 中(实际上,GLSL 的一个子集在支持 Metal 的设备上自动转换为 Metal 着色器语言)。 Here's a good tutorial on that, and here's another on OpenGL ES pixel shaders for iOS in general.
我正在努力将单个像素绘制到 UIView
以创建分形图像。我的问题是我的渲染速度。我目前正在 运行 循环此循环 260,000 次,但我想渲染更多像素。事实上,在我的 iPad Mini 上 运行 大约需要 5 秒。
我之前用的是UIBezierPath
,但是速度更慢(大约7秒)。我一直在寻找 NSBitMap
的东西,但我不确定这是否会加快速度或首先如何实现它。
我也在考虑尝试将循环中的像素存储到数组中,然后在循环后将它们全部绘制在一起。不过,我不太确定在数组中存储像素然后从数组中检索像素的最佳过程是什么。
任何有助于加快此过程的帮助都会很棒。
for (int i = 0; i < 260000; i++) {
float RN = drand48();
for (int i = 1; i < numBuckets; i++) {
if (RN < bucket[i]) {
col = i;
CGContextSetFillColor(context, CGColorGetComponents([UIColor colorWithRed:(colorSelector[i][0]) green:(colorSelector[i][1]) blue:(colorSelector[i][2]) alpha:(1)].CGColor));
break;
}
}
xT = myTextFieldArray[1][1][col]*x1 + myTextFieldArray[1][2][col]*y1 + myTextFieldArray[1][5][col];
yT = myTextFieldArray[1][3][col]*x1 + myTextFieldArray[1][4][col]*y1 + myTextFieldArray[1][6][col];
x1 = xT;
y1 = yT;
if (i > 10000) {
CGContextFillRect(context, CGRectMake(xOrigin+(xT-xMin)*sizeScalar,yOrigin-(yT-yMin)*sizeScalar,.5,.5));
}
else if (i < 10000) {
if (x1 < xMin) {
xMin = x1;
}
else if (x1 > xMax) {
xMax = x1;
}
if (y1 < yMin) {
yMin = y1;
}
else if (y1 > yMax) {
yMax = y1;
}
}
else if (i == 10000) {
if (xMax - xMin > yMax - yMin) {
sizeScalar = 960/(xMax - xMin);
yOrigin=1000-(1000-sizeScalar*(yMax-yMin))/2;
}
else {
sizeScalar = 960/(yMax - yMin);
xOrigin=(1000-sizeScalar*(xMax-xMin))/2;
}
}
}
编辑
我创建了一个多维数组来存储 UIColors,因此我可以使用位图来绘制图像。它明显更快,但我的颜色现在不能正常工作。
这是我将 UIColors 存储到数组中的位置:
int xPixel = xOrigin+(xT-xMin)*sizeScalar;
int yPixel = yOrigin-(yT-yMin)*sizeScalar;
pixelArray[1000-yPixel][xPixel] = customColors[col];
这是我画的东西:
CGDataProviderRef provider = CGDataProviderCreateWithData(nil, pixelArray, 1000000, nil);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGImageRef image = CGImageCreate(1000,
1000,
8,
32,
4000,
colorSpace,
kCGBitmapByteOrder32Big | kCGImageAlphaNoneSkipLast,
provider,
nil, //No decode
NO, //No interpolation
kCGRenderingIntentDefault); // Default rendering
CGContextDrawImage(context, self.bounds, image);
不仅颜色不对,而且每次渲染图像时,颜色都与上次完全不同。我一直在用颜色测试不同的东西,但我仍然不知道为什么颜色不对,而且我更困惑它们是如何不断变化的。
如果您想最大化渲染速度,我会推荐位图渲染。矢量光栅化要慢得多,并且 CGContext 绘图并不是真正用于高性能实时渲染。
我遇到了类似的技术挑战,发现 CVOpenGLESTextureCacheRef
是最快的。纹理缓存允许您将位图直接上传到图形内存中以进行快速渲染。渲染利用 OpenGL,但因为它只是 2D 全屏图像 - 您真的不需要了解 OpenGL 就可以使用它。
您可以在此处查看我写的使用纹理缓存的示例:https://github.com/justinmeiners/image-sequence-streaming
我原来的相关问题在这里: How to directly update pixels - with CGImage and direct CGDataProvider
我的项目从文件渲染位图,所以它有点不同,但您可以查看 ISSequenceView.m 的示例,了解如何使用纹理缓存和设置 OpenGL 进行此类渲染。
您的渲染过程可能类似于:
1. Draw to buffer (raw bytes)
2. Lock texture cache
3. Copy buffer to texture cache
4. Unlock texture cache.
5. Draw fullscreen quad with texture
如果你真的想单独改变许多不同的像素,你最好的选择可能是分配一块内存(大小 width * height * bytes per pixel
),直接在内存中进行更改,然后转换整个东西使用 CGBitmapContextCreateWithData
可能有比这更快的方法(参见 Justin 的回答)。
逐像素绘图——对每个像素进行复杂的计算,如分形渲染——是你可以要求计算机完成的最困难的事情之一。这里的每个其他答案都涉及其困难的一个方面,但这还不是全部。 (幸运的是,现代硬件也针对这种渲染进行了优化,如果您知道要它做什么的话。我会讲到的。)
@jcaron 和@JustinMeiners 都注意到 CoreGraphics 中的矢量绘图操作(甚至是矩形填充)会对基于 CPU 的光栅化产生不利影响。操作位图数据缓冲区会更快,但不会快很多。
将该缓冲区放到屏幕上也需要时间,特别是如果您必须完成创建位图图像缓冲区然后在 CG 上下文中绘制它们的过程 — 这需要在CPU 和大量的内存带宽工作来复制该缓冲区。所以@JustinMeiners 是正确的,直接访问 GPU 纹理内存将是一个很大的帮助。
但是,如果您仍然在 CPU 代码中填充缓冲区,您仍然会受到两种成本的阻碍(充其量,如果您天真地这样做会更糟):
- 渲染每个像素的顺序工作
- 渲染时从纹理内存到帧缓冲区的内存传输成本
@JustinMeiners 的回答很适合他的用例——图像序列是预渲染的,所以他确切地知道每个像素将是什么,他只需要将它放入纹理内存中。但是您的用例需要大量的每像素计算。
幸运的是,每像素计算正是 GPU 的设计目标!欢迎来到像素着色器的世界。对于屏幕上的每个像素,您可以 运行 一个独立的计算来确定该点与分形集的关系,从而确定用什么颜色绘制它。可以 运行 并行计算一次处理多个像素,它的输出直接进入屏幕,因此没有内存开销可以将位图转储到帧缓冲区中。
在 iOS 上使用像素着色器的一种简单方法是 SpriteKit — 它可以为您处理大部分必要的 OpenGL/Metal 设置,因此您只需编写逐像素算法在 GLSL 中(实际上,GLSL 的一个子集在支持 Metal 的设备上自动转换为 Metal 着色器语言)。 Here's a good tutorial on that, and here's another on OpenGL ES pixel shaders for iOS in general.