iOS OpenGL 太慢

发布于 2024-12-28 10:06:41 字数 5493 浏览 2 评论 0原文

我是 Xcode 编程新手,我正在尝试使用 OpenGL 创建一款 iPhone 游戏,支持 60 FPS 的视网膜显示,但它运行速度太慢。我基于developer.apple 上的GLSprite 示例来构建它。我已经尽我所能对其进行了优化,但它仍然运行<模拟器上的 30 FPS(我还没有在真实设备上测试过 - 也许它更快?)。瓶颈似乎是绘制多边形 - 我使用了非常小的纹理(256x256 PNG)和像素格式(RGBA4444);我已经禁用了混合;我已将所有转换代码移至加载阶段,希望获得更好的性能;一切都没有成功。我保留一个顶点数组来存储该步骤的所有内容,然后使用 GL_TRIANGLES 和一个函数调用进行绘制 - 因为我认为它比调用多个 glDrawArray 更快。当我达到大约 120 个顶点(每个矩形精灵 6 个)时,它开始滞后,但在很多地方我读到 iPhone 甚至可以处理数百万个顶点。下面的代码有什么问题? OpenGL 是在 iPhone 上渲染图形最快的方法吗?如果没有,我还应该使用什么?

OpenGL 加载代码,在开始时仅调用一次:

glViewport(0, 0, backingWidth, backingHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);

glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glMatrixMode(GL_MODELVIEW);
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glBindTexture(GL_TEXTURE_2D,texture[0]); //Binds a texture loaded previously with the code given below

glVertexPointer(3, GL_FLOAT, 0, vertexes); //The array holding the vertexes
glEnableClientState(GL_VERTEX_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, uvCoord); //The array holding the uv coordinates
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

纹理加载方法:

- (void)loadSprite:(NSString*)filename intoPos:(int)pos { //Loads a texture within the bundle, at the given position in an array storing all textures (but I actually just use one at a time)
CGImageRef spriteImage;
CGContextRef spriteContext;
GLubyte *spriteData;
size_t  width, height;

// Sets up matrices and transforms for OpenGL ES
glViewport(0, 0, backingWidth, backingHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);

// Clears the view with black
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

// Sets up pointers and enables states needed for using vertex arrays and textures
glVertexPointer(2, GL_FLOAT, 0, spriteVertices);
glEnableClientState(GL_VERTEX_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, spriteTexcoords);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

// Creates a Core Graphics image from an image file
spriteImage = [UIImage imageNamed:filename].CGImage;
// Get the width and height of the image
width = CGImageGetWidth(spriteImage);
height = CGImageGetHeight(spriteImage);
textureWidth[pos]=width;
textureHeight[pos]=height;
NSLog(@"Width %lu; Height %lu",width,height);
// Texture dimensions must be a power of 2. If you write an application that allows users to supply an image,
// you'll want to add code that checks the dimensions and takes appropriate action if they are not a power of 2.

if(spriteImage) {
    // Allocated memory needed for the bitmap context
    spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
    // Uses the bitmap creation function provided by the Core Graphics framework. 
    spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    // After you create the context, you can draw the sprite image to the context.
    CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), spriteImage);
    // You don't need the context at this point, so you need to release it to avoid memory leaks.
    CGContextRelease(spriteContext);

    // Use OpenGL ES to generate a name for the texture.
    glGenTextures(1, &texture[pos]);
    // Bind the texture name.
    glBindTexture(GL_TEXTURE_2D, texture[pos]);
    curTexture=pos;

    if (1) { //This should convert pixel format
        NSLog(@"convert to 4444");
        void*                   tempData;
        unsigned int*           inPixel32;
        unsigned short*         outPixel16;

        tempData = malloc(height * width * 2);

        inPixel32 = (unsigned int*)spriteData;
        outPixel16 = (unsigned short*)tempData;
        NSUInteger i;
        for(i = 0; i < width * height; ++i, ++inPixel32)
            *outPixel16++ = ((((*inPixel32 >> 0) & 0xFF) >> 4) << 12) | ((((*inPixel32 >> 8) & 0xFF) >> 4) << 8) | ((((*inPixel32 >> 16) & 0xFF) >> 4) << 4) | ((((*inPixel32 >> 24) & 0xFF) >> 4) << 0);

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, tempData);
        free(tempData);
    } else {

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);           
    }

    // Set the texture parameters to use a minifying filter and a linear filer (weighted average)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Specify a 2D texture image, providing the a pointer to the image data in memory
    //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    // Release the image data
    free(spriteData);

    // Enable use of the texture
    glEnable(GL_TEXTURE_2D);
    // Set a blending function to use
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    // Enable blending
    glEnable(GL_BLEND);
}

每个游戏循环调用的实际绘制代码:

glDrawArrays(GL_TRIANGLES, 0, vertexIndex); //vertexIndex is the maximum number of vertexes at this loop

glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];

I'm new to Xcode programming and I'm trying to create an iPhone game using OpenGL with support for retina display at 60 FPS, but it runs way too slow. I based it on the GLSprite example at developer.apple. I've already optimized it the best I could, but it keeps running < 30 FPS on the Simulator (I haven't tested it on a real device yet - maybe it's faster?). The bottleneck appears to be drawing the polygons - I've used really small textures (256x256 PNG) and pixel formats (RGBA4444); I've disabled blending; I've moved all transformation code to the load phase hoping for better performance; everything to no success. I'm keeping a vertex array that stores everything for that step, then draws using GL_TRIANGLES with one function call - because I think it's faster than calling multiple glDrawArrays. It starts lagging when I reach about 120 vertexes (6 for each rectangular sprite), but in many places I've read the iPhone can handle even millions of vertexes. What's wrong with the code below? Is OpenGL the fastest way to render graphics on the iPhone? If not, what else should I use?

OpenGL loading code, called just once, at the beginning:

glViewport(0, 0, backingWidth, backingHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);

glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glMatrixMode(GL_MODELVIEW);
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glBindTexture(GL_TEXTURE_2D,texture[0]); //Binds a texture loaded previously with the code given below

glVertexPointer(3, GL_FLOAT, 0, vertexes); //The array holding the vertexes
glEnableClientState(GL_VERTEX_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, uvCoord); //The array holding the uv coordinates
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

The texture loading method:

- (void)loadSprite:(NSString*)filename intoPos:(int)pos { //Loads a texture within the bundle, at the given position in an array storing all textures (but I actually just use one at a time)
CGImageRef spriteImage;
CGContextRef spriteContext;
GLubyte *spriteData;
size_t  width, height;

// Sets up matrices and transforms for OpenGL ES
glViewport(0, 0, backingWidth, backingHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);

// Clears the view with black
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

// Sets up pointers and enables states needed for using vertex arrays and textures
glVertexPointer(2, GL_FLOAT, 0, spriteVertices);
glEnableClientState(GL_VERTEX_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, spriteTexcoords);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

// Creates a Core Graphics image from an image file
spriteImage = [UIImage imageNamed:filename].CGImage;
// Get the width and height of the image
width = CGImageGetWidth(spriteImage);
height = CGImageGetHeight(spriteImage);
textureWidth[pos]=width;
textureHeight[pos]=height;
NSLog(@"Width %lu; Height %lu",width,height);
// Texture dimensions must be a power of 2. If you write an application that allows users to supply an image,
// you'll want to add code that checks the dimensions and takes appropriate action if they are not a power of 2.

if(spriteImage) {
    // Allocated memory needed for the bitmap context
    spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
    // Uses the bitmap creation function provided by the Core Graphics framework. 
    spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    // After you create the context, you can draw the sprite image to the context.
    CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), spriteImage);
    // You don't need the context at this point, so you need to release it to avoid memory leaks.
    CGContextRelease(spriteContext);

    // Use OpenGL ES to generate a name for the texture.
    glGenTextures(1, &texture[pos]);
    // Bind the texture name.
    glBindTexture(GL_TEXTURE_2D, texture[pos]);
    curTexture=pos;

    if (1) { //This should convert pixel format
        NSLog(@"convert to 4444");
        void*                   tempData;
        unsigned int*           inPixel32;
        unsigned short*         outPixel16;

        tempData = malloc(height * width * 2);

        inPixel32 = (unsigned int*)spriteData;
        outPixel16 = (unsigned short*)tempData;
        NSUInteger i;
        for(i = 0; i < width * height; ++i, ++inPixel32)
            *outPixel16++ = ((((*inPixel32 >> 0) & 0xFF) >> 4) << 12) | ((((*inPixel32 >> 8) & 0xFF) >> 4) << 8) | ((((*inPixel32 >> 16) & 0xFF) >> 4) << 4) | ((((*inPixel32 >> 24) & 0xFF) >> 4) << 0);

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, tempData);
        free(tempData);
    } else {

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);           
    }

    // Set the texture parameters to use a minifying filter and a linear filer (weighted average)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Specify a 2D texture image, providing the a pointer to the image data in memory
    //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    // Release the image data
    free(spriteData);

    // Enable use of the texture
    glEnable(GL_TEXTURE_2D);
    // Set a blending function to use
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    // Enable blending
    glEnable(GL_BLEND);
}

The actual drawing code that is called every game loop:

glDrawArrays(GL_TRIANGLES, 0, vertexIndex); //vertexIndex is the maximum number of vertexes at this loop

glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

栖竹 2025-01-04 10:06:41

根据 iOS 的 OpenGL 编程指南:

重要模拟器中OpenGL ES的渲染性能与实际设备上的OpenGL ES性能无关。
模拟器提供了一个优化的软件光栅器,
充分利用 Macintosh 的矢量处理功能
电脑。因此,您的 OpenGL ES 代码可能会运行得更快或更慢
iOS模拟器(取决于您的电脑和您要绘制的内容)
比在实际设备上。始终分析和优化您的绘图
在真实设备上编写代码,并且永远不要假设模拟器反映了
真实世界的表现。

该模拟器对于 OpenGL 应用程序的性能分析并不可靠。您需要在真实硬件上运行/分析。

当我达到大约 120 个顶点(每个顶点 6 个)时,它开始滞后
矩形精灵),但在很多地方我读过 iPhone 可以
甚至可以处理数百万个顶点。

详细说明一下您的评论:顶点数量并不是影响 OpenGL 性能的唯一变量。例如,仅使用一个三角形(3 个顶点),您就可以在整个屏幕上绘制像素。这显然比绘制仅覆盖几个像素的小三角形需要更多的计算。表示绘制许多像素的能力的指标称为填充率。

如果您的顶点在屏幕上代表大三角形,则填充率可能是您的性能瓶颈,而不是顶点变换。由于 iOS 模拟器确实使用了软件光栅器,尽管已经过优化,但它可能比实际的专用硬件慢。

在优化之前,您应该分析您的应用程序以了解实际的性能瓶颈是什么; 本文档可以帮助您。

According to the OpenGL programming guide for iOS :

Important Rendering performance of OpenGL ES in Simulator has no relation to the performance of OpenGL ES on an actual device.
Simulator provides an optimized software rasterizer that takes
advantage of the vector processing capabilities of your Macintosh
computer. As a result, your OpenGL ES code may run faster or slower in
iOS simulator (depending on your computer and what you are drawing)
than on an actual device. Always profile and optimize your drawing
code on a real device and never assume that Simulator reflects
real-world performance.

The simulator is not reliable to profile performance of OpenGL applications. You'll need to run/profile on the real hardware.

It starts lagging when I reach about 120 vertexes (6 for each
rectangular sprite), but in many places I've read the iPhone can
handle even millions of vertexes.

To elaborate a bit on this comment of yours : the number of vertices is not the only variable impacting OpenGL performance.For example, with only a single triangle (3 vertices), you can draw pixels on the whole screen. This obviously needs more computation than drawing a small triangle covering only a few pixels. The metric representing the capacity of drawing many pixels is known as fill-rate.

If your vertices represent large triangles on screen, it is probable that fill-rate is your performance bottleneck, and not vertex transform. As the iOS simulator does use a software rasterizer, albeit being optimized, it is probably slower that actual specialized hardware.

You should profile your application to know what is your actual performance bottleneck before optimizing ; this document can help you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文