在iPhone上绘制屏幕缓冲区的最快方法

10
我有一个“软件渲染器”,现在正在将其从PC移植到iPhone。在iPhone上手动更新像素缓冲区的屏幕的最快方法是什么?例如,在Windows中,我找到的最快函数是SetDIBitsToDevice。
我对iPhone或库不太了解,并且似乎有许多层和不同类型的UI元素,因此我可能需要很多解释...
目前,我只会不断更新opengl中的纹理并将其渲染到屏幕上,我非常怀疑这不是最好的方法。
更新:
我尝试过OpenGL屏幕大小的纹理方法:
我得到了17fps...
我使用了512x512的纹理(因为它需要是2的幂)
只是调用
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,512,512,GL_RGBA,GL_UNSIGNED_BYTE, baseWindowGUI->GetBuffer());

似乎主要是由于这个代码导致了所有的缓慢。

注释掉这段代码,保留所有软件渲染GUI代码以及现在不更新纹理的渲染,结果是60fps,30%的渲染器使用率,并且没有从CPU中出现明显的峰值。

请注意,GetBuffer()只是返回GUI系统的软件后备缓冲区的指针,在任何情况下都不会重新调整或调整缓冲区的大小,它已经被正确地调整和格式化为纹理,所以我相当确定减速与软件渲染器无关,这是好消息,看起来如果我能找到一种方法以60帧更新屏幕,则软件渲染应该可以使用。

我尝试使用512,320而不是512,512进行更新纹理调用,这更奇怪的是更慢......以10fps运行,同时还显示呈现利用率仅约为5%,所有时间都浪费在openGLES中的Untwiddle32bpp调用中。

如果更直接的blit将产生更好的效果,我可以将我的软件渲染器更改为原生渲染任何像素格式。

提供信息,测试是在2.2.1版iPod Touch G2上进行的(因此像是一个类固醇的iPhone 3G)

更新2:

我刚刚完成了CoreAnimation/ Graphics方法的编写,看起来不错,但我有点担心它如何每帧更新屏幕,基本上放弃旧的CGImage,创建一个全新的...在下面的“一些随机函数”中请查看:

这是更新图像的最快方式吗?任何帮助将不胜感激。

//
//  catestAppDelegate.m
//  catest
//
//  Created by User on 3/14/10.
//  Copyright __MyCompanyName__ 2010. All rights reserved.
//




#import "catestAppDelegate.h"
#import "catestViewController.h"
#import "QuartzCore/QuartzCore.h"

const void* GetBytePointer(void* info)
{
    // this is currently only called once
    return info; // info is a pointer to the buffer
}

void ReleaseBytePointer(void*info, const void* pointer)
{
    // don't care, just using the one static buffer at the moment
}


size_t GetBytesAtPosition(void* info, void* buffer, off_t position, size_t count)
{
    // I don't think this ever gets called
    memcpy(buffer, ((char*)info) + position, count);
    return count;
}

CGDataProviderDirectCallbacks providerCallbacks =
{ 0, GetBytePointer, ReleaseBytePointer, GetBytesAtPosition, 0 };


static CGImageRef cgIm;

static CGDataProviderRef dataProvider;
unsigned char* imageData;
 const size_t imageDataSize = 320 * 480 * 4;
NSTimer *animationTimer;
NSTimeInterval animationInterval= 1.0f/60.0f;


@implementation catestAppDelegate

@synthesize window;
@synthesize viewController;


- (void)applicationDidFinishLaunching:(UIApplication *)application {    


    [window makeKeyAndVisible];


    const size_t byteRowSize = 320 * 4;
    imageData = malloc(imageDataSize);

    for(int i=0;i<imageDataSize/4;i++)
            ((unsigned int*)imageData)[i] = 0xFFFF00FF; // just set it to some random init color, currently yellow


    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    dataProvider =
    CGDataProviderCreateDirect(imageData, imageDataSize,
                               &providerCallbacks);  // currently global

    cgIm = CGImageCreate
    (320, 480,
     8, 32, 320*4, colorSpace,
     kCGImageAlphaNone | kCGBitmapByteOrder32Little,
     dataProvider, 0, false, kCGRenderingIntentDefault);  // also global, probably doesn't need to be

    self.window.layer.contents = cgIm; // set the UIWindow's CALayer's contents to the image, yay works!

   // CGImageRelease(cgIm);  // we should do this at some stage...
   // CGDataProviderRelease(dataProvider);

    animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(someRandomFunction) userInfo:nil repeats:YES];
    // set up a timer in the attempt to update the image

}
float col = 0;

-(void)someRandomFunction
{
    // update the original buffer
    for(int i=0;i<imageDataSize;i++)
        imageData[i] = (unsigned char)(int)col;

    col+=256.0f/60.0f;

    // and currently the only way I know how to apply that buffer update to the screen is to
    // create a new image and bind it to the layer...???
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    cgIm = CGImageCreate
    (320, 480,
     8, 32, 320*4, colorSpace,
     kCGImageAlphaNone | kCGBitmapByteOrder32Little,
     dataProvider, 0, false, kCGRenderingIntentDefault);

    CGColorSpaceRelease(colorSpace);

    self.window.layer.contents = cgIm;

    // and that currently works, updating the screen, but i don't know how well it runs...
}


- (void)dealloc {
    [viewController release];
    [window release];
    [super dealloc];
}


@end

嘿,我也遇到了这个问题,难道每帧都创建和销毁CGImage是唯一的解决方案吗?(我尝试过了,比glTexsubimage慢)。直接提供程序的整个重点似乎在于它应该指向该缓冲区,因此如果我更改它,则图像会更新。对此有什么帮助吗? - Justin Meiners
我认为使用CGDataProviderCreateDirect的目的是直接访问像素缓冲区,使用CADisplayLink刷新。不幸的是,这种方式非常慢。有什么解决方法吗? - aoakenfo
5个回答

11

实现仅使用CPU的2D图形最快的App Store批准方法是使用CGDataProviderCreateDirect创建支持缓冲区的CGImage,并将其分配给CALayercontents属性。

为了获得最佳效果,请使用kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32LittlekCGImageAlphaNone | kCGBitmapByteOrder32Little位图类型,并进行双缓冲处理,以使显示始终处于一致状态。

编辑:理论上,这应该比绘制到OpenGL纹理更快,但始终要进行性能剖析以确保。

编辑2:CADisplayLink是一个有用的类,无论你使用哪种合成方法。


这个速度足够处理24帧每秒的视频吗? - Stefan Arentz
@St3fan:我希望它足够快,至少可以做到60fps!我开始有一种感觉,即iPhone的API和操作系统比桌面Windows还要臃肿!@rpetrich,谢谢,我会试试的。我对iPhone开发几乎一无所知,比如GUI等方面,所以可能需要一些时间,你能推荐一个好的模板来入门吗?我只用过OpenGL。 - matt
Matt,相比Win32,iPhone的API非常好用。有些事情只能通过更高级别的Cocoa代码实现。如果您认为这需要一个更好的Cocoa API,请向Apple提交错误报告。 - Stefan Arentz
1
@Brad Larson:在iPhone上,所有内容最终都会进入一个QuartzCore场景图中,并由GPU渲染。对于OpenGL内容,场景会由应用程序中的GPU渲染到CALayer,然后该层与图形的其余部分一起合成到SpringBoard的帧缓冲区中。OpenGL路径:CPU渲染-> OGL纹理-> OGL CALayer-> 帧缓冲区。CALayer路径:CPU渲染->标准CALayer->帧缓冲区。 - rpetrich
@Brad Larson:绘制CALayer的内容总是很慢,因为这涉及到一次复制(在CPU上执行,速度较慢)。另一方面,如果新内容已经在视频内存中,则设置内容可能会很快。由于iPhone使用统一内存模型,所有内存都是视频内存。我想OS X的行为会有很大不同(并且需要使用IOSurface)。 - rpetrich
显示剩余9条评论

3
最快的方式是使用私有框架IOFrameBuffer/IOSurface。
因此,OpenGL似乎是AppStore应用程序唯一可能的方式。

我已经更新了问题,并附上了OpenGL方法的结果,看起来不太好。 - matt

3
为了回复@rpetrich的答案,我想说在我进行的测试中,我发现OpenGL是最快的方法。我实现了一个简单的对象(UIView子类),名为EEPixelViewer,可以以尽可能高效的方式使用OpenGL将各种格式的像素(24bpp RGB、32位RGBA和多种YpCbCr格式)推送到屏幕上。这个解决方案对于大多数人来说都应该可行,并且在几乎所有iOS设备上,包括老设备,在大多数像素格式下都能达到60fps。使用非常简单,不需要OpenGL知识。
pixelViewer.pixelFormat = kCVPixelFormatType_32RGBA;
pixelViewer.sourceImageSize = CGSizeMake(1024, 768);
EEPixelViewerPlane plane;
plane.width = 1024;
plane.height = 768;
plane.data = pixelBuffer;
plane.rowBytes = plane.width * 4;
[pixelViewer displayPixelBufferPlanes: &plane count: 1 withCompletion:nil];

重复调用displayPixelBufferPlanes函数以为每一帧加载像素缓冲区到GPU(使用glTexImage2D),基本上就是这样。代码很智能,它会尝试使用GPU进行任何简单处理,例如颜色通道置换,将YpCbCr转换成RGB等。

此外,还有相当多的逻辑用于尊重使用UIView的contentMode属性进行缩放,因此UIViewContentModeScaleToFit / Fill等都能按预期工作。


1
也许你可以将软件渲染器中使用的方法抽象成GPU着色器...这样可能会获得更好的性能。你需要将编码后的“视频”数据作为纹理发送。

这是一个好主意,但不幸的是不适用于我的情况,但对其他人来说绝对是有用的。 - matt

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接