Abi*_*bix 1 performance xcode opengl-es ios metal
我正在尝试将我的项目从 OpenGL 迁移到 iOS 上的 Metal。但我似乎遇到了性能墙。任务很简单...
我有一个大纹理(超过 3000x3000 像素)。我需要在每个 touchesMoved 事件上绘制几个(几百个)小纹理(比如 124x124)。这是在启用特定混合功能的同时。它基本上就像一个画笔。然后显示大纹理。任务大致就是这样。
在 OpenGL 上它运行得非常快。我得到大约 60fps。当我将相同的代码移植到 Metal 时,我只能设法获得 15fps。
我创建了两个最低限度的示例项目来演示这个问题。这是项目(OpenGL和Metal)...
https://drive.google.com/file/d/12MPt1nMzE2UL_s4oXEUoTCXYiTz42r4b/view?usp=sharing
这大致就是我在 OpenGL 中所做的......
- (void) renderBrush:(GLuint)brush on:(GLuint)fbo ofSize:(CGSize)size at:(CGPoint)point {
GLfloat brushCoordinates[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
GLfloat imageVertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f,
};
int brushSize = 124;
CGRect rect = CGRectMake(point.x - brushSize/2, point.y - brushSize/2, brushSize, brushSize);
rect.origin.x /= size.width;
rect.origin.y /= size.height;
rect.size.width /= size.width;
rect.size.height /= size.height;
[self convertImageVertices:imageVertices toProjectionRect:rect onImageOfSize:size];
int currentFBO;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤tFBO);
[_Program use];
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, (int)size.width, (int)size.height);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, brush);
glUniform1i(brushTextureLocation, 2);
glVertexAttribPointer(positionLocation, 2, GL_FLOAT, 0, 0, imageVertices);
glVertexAttribPointer(brushCoordinateLocation, 2, GL_FLOAT, 0, 0, brushCoordinates);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ONE);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_BLEND);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, currentFBO);
}
Run Code Online (Sandbox Code Playgroud)
我在每个触摸事件的循环中运行此代码(大约 200-500)。它运行得非常快。
这就是我将代码移植到 Metal 的方式...
- (void) renderBrush:(id<MTLTexture>)brush onTarget:(id<MTLTexture>)target at:(CGPoint)point withCommandBuffer:(id<MTLCommandBuffer>)commandBuffer {
int brushSize = 124;
CGRect rect = CGRectMake(point.x - brushSize/2, point.y - brushSize/2, brushSize, brushSize);
rect.origin.x /= target.width;
rect.origin.y /= target.height;
rect.size.width /= target.width;
rect.size.height /= target.height;
Float32 imageVertices[8];
// Calculate the vertices (basically the rectangle that we need to draw) on the target texture that we are going to draw
// We are not drawing on the entire target texture, only on a square around the point
[self composeImageVertices:imageVertices toProjectionRect:rect onImageOfSize:CGSizeMake(target.width, target.height)];
// We use different one vertexBuffer per pass. This is because this is run on a loop and the subsequent calls will overwrite
// The values. Other buffers also get overwritten but that is ok for now, we only need to demonstrate the performance.
id<MTLBuffer> vertexBuffer = [_vertexArray lastObject];
memcpy([vertexBuffer contents], imageVertices, 8 * sizeof(Float32));
id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:mRenderPassDescriptor];
commandEncoder.label = @"DrawCE";
[commandEncoder setRenderPipelineState:mPipelineState];
[commandEncoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[commandEncoder setVertexBuffer:mBrushTextureBuffer offset:0 atIndex:1];
[commandEncoder setFragmentTexture:brush atIndex:0];
[commandEncoder setFragmentSamplerState:mSampleState atIndex:0];
[commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
[commandEncoder endEncoding];
Run Code Online (Sandbox Code Playgroud)
}
然后在每个触摸事件中使用单个 MTLCommandBuffer 循环运行此代码,例如...
id<MTLCommandBuffer> commandBuffer = [MetalContext.defaultContext.commandQueue commandBuffer];
commandBuffer.label = @"DrawCB";
dispatch_semaphore_wait(_inFlightSemaphore, DISPATCH_TIME_FOREVER);
mRenderPassDescriptor.colorAttachments[0].texture = target;
__block dispatch_semaphore_t block_sema = _inFlightSemaphore;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
dispatch_semaphore_signal(block_sema);
}];
_vertexArray = [[NSMutableArray alloc] init];
for (int i = 0; i < strokes; i++) {
id<MTLBuffer> vertexBuffer = [MetalContext.defaultContext.device newBufferWithLength:8 * sizeof(Float32) options:0];
[_vertexArray addObject:vertexBuffer];
id<MTLTexture> brush = [_brushes objectAtIndex:rand()%_brushes.count];
[self renderBrush:brush onTarget:target at:CGPointMake(x, y) withCommandBuffer:commandBuffer];
x += deltaX;
y += deltaY;
}
[commandBuffer commit];
Run Code Online (Sandbox Code Playgroud)
在我附上的示例代码中,我用计时器循环替换了触摸事件以保持简单。
在 iPhone 7 Plus 上,我使用 OpenGL 获得 60fps,使用 Metal 获得 15fps。可能是我在这里做错了什么?
删除所有冗余:
-setVertexBufferOffset:atIndex:根据需要仅设置偏移量,而不更改缓冲区。composeImageVertices:...可以使用适当的转换直接写入顶点缓冲区,避免memcpy.composeImageVertices:...实际执行的操作以及 ifdeltaX和deltaY是常量,您可以一次设置顶点缓冲区,永远。顶点着色器可以根据需要变换顶点。您可以将适当的数据作为制服(目标点和渲染目标大小,甚至是变换矩阵)传入。mPipelineState,mBrushTextureBuffer以及mSampleState每一次。stroke单个四边形的实例。在顶点着色器中,根据实例 ID 变换位置。您必须作为统一数据传递deltaX和deltaY输入。画笔索引也可以位于传入的单个缓冲区中,着色器可以通过实例 ID 在其中查找画笔索引。| 归档时间: |
|
| 查看次数: |
1020 次 |
| 最近记录: |