alleviating-your-obscure-frustrations-since-2013

This is part two in a series. If you don't see what you're looking for here, try part one.

These quirks were discovered in the process of developing Double Dynamo, a new memory and rhythm game coming to iOS later this summer. Want to hear more? Click that link, or hop over to the Facebook page and click Like.

Cocos2d-x continues to be a pretty good way to get things done in the world of mobile games, and I'm still glad I chose the framework for my current project. But it has its share of quirks. I went over a few of these quirks in an earlier post and I've run into a few more since then, so it seems like a good time for an update. I'm using Cocos2d-x version 2.1.2.

Bitmap Rendering

Cocos2d's solution for rendering to bitmaps, CCRenderTexture, generally does a pretty good job. One gotcha I mentioned previously is that it renders upside down. Another thing you may notice is that the resulting texture exhibits aliasing when it scales. For an improvement (suggested here) call setAntiAliasTexParameters() on the contained CCTexture2D:

CCRenderTexture *rt;
// ...
CCTexture2D *texture = rt->getSprite()->getTexture();
texture->setAntiAliasTexParameters();

I also noticed that some of my rendered nodes had weird borders or looked slightly cut off. It turns out that CCRenderTexture’s initializers take integer arguments but internally these arguments are multiplied by the content scale factor and rounded again — this can lead to two roundings! I recommend changing all int widths and heights to float, plus the following patch:

@@ -255,8 +255,8 @@ bool CCRenderTexture::initWithWidthAndHeight(int w, int h, CCTexture2DPixelForma
     void *data = NULL;
     do 
     {
-        w = (int)(w * CC_CONTENT_SCALE_FACTOR());
-        h = (int)(h * CC_CONTENT_SCALE_FACTOR());
+        w *= CC_CONTENT_SCALE_FACTOR();
+        h *= CC_CONTENT_SCALE_FACTOR();

         glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m;_nOldFBO);

@@ -271,8 +271,8 @@ bool CCRenderTexture::initWithWidthAndHeight(int w, int h, CCTexture2DPixelForma
         }
         else
         {
-            powW = ccNextPOT(w);
-            powH = ccNextPOT(h);
+            powW = ccNextPOT(ceilf(w));
+            powH = ccNextPOT(ceilf(h));
         }

         data = malloc((int)(powW * powH * 4));

You may also want to ensure the node(s) you're rendering are pixel-aligned.

More Rounding Errors

For some screen resolutions I noticed a 1-pixel gap at the bottom of the screen on nodes that were supposed to fit the entire window. I'm enough of a perfectionist to drill down and figure out what was going wrong rather than just patch the gap with a hack.

First I noticed that CCEGLViewProtocol::setViewPortInPoints() was casting floats to ints, which seemed blatantly wrong, so I threw in some rintf(3) calls:

void CCEGLViewProtocol::setViewPortInPoints(float x , float y , float w , float h)
 {
-    glViewport((GLint)(x * m_fScaleX + m_obViewPortRect.origin.x),
-               (GLint)(y * m_fScaleY + m_obViewPortRect.origin.y),
-               (GLsizei)(w * m_fScaleX),
-               (GLsizei)(h * m_fScaleY));
+    glViewport((GLint)rintf(x * m_fScaleX + m_obViewPortRect.origin.x),
+               (GLint)rintf(y * m_fScaleY + m_obViewPortRect.origin.y),
+               (GLsizei)rintf(w * m_fScaleX),
+               (GLsizei)rintf(h * m_fScaleY));
 }

This didn't quite fix things, but digging a little deeper it became clear that CCEGLViewProtocol::getVisibleOrigin() was returning the wrong coordinates for the kResolutionNoBorder policy. Instead I used the viewport rect that had already been calculated in CCEGLViewProtocol::setDesignResolutionSize():

@@ -124,8 +125,8 @@ CCPoint CCEGLViewProtocol::getVisibleOrigin() const
 {
     if (m_eResolutionPolicy == kResolutionNoBorder)
     {
-        return CCPointMake((m_obDesignResolutionSize.width - m_obScreenSize.width/m_fScaleX)/2, 
-                           (m_obDesignResolutionSize.height - m_obScreenSize.height/m_fScaleY)/2);
+        return CCPointMake(rintf(-m_obViewPortRect.origin.x) / m_fScaleX,
+                           rintf(-m_obViewPortRect.origin.y) / m_fScaleY);
     }
     else 
     {

Please note that the round happens before the divide, since we want an integral value in pixels, not necessarily in points.

Threads & Leaks

This one's iOS only. I'm using Apple's Audio Queue Services to play gapless background music, and most of that is happening in a separate thread. On one of my test devices I saw these error messages:

__NSCFString autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug

Turned out I was calling into CCFileUtils::fullPathForFilename() from a thread without an autorelease pool, and this line was leaking:

NSString *relPath = [NSString stringWithUTF8String:pszFileName];

I replaced the convenience method with alloc and initWithUTF8String, which does not call autorelease:

diff --git a/cocos2dx/platform/ios/CCFileUtils.mm b/cocos2dx/platform/ios/CCFileUtils.mm
--- a/cocos2dx/platform/ios/CCFileUtils.mm
+++ b/cocos2dx/platform/ios/CCFileUtils.mm
@@ -307,10 +307,12 @@ std::string CCFileUtils::fullPathForFilename(const char* pszFileName)
 {
     CCAssert(pszFileName != NULL, "CCFileUtils: Invalid path");

-    NSString *relPath = [NSString stringWithUTF8String:pszFileName];
+    NSString *relPath = [[NSString alloc] initWithUTF8String:pszFileName];
+    bool isAbsolutePath = [relPath isAbsolutePath];
+    [relPath release];

     // Return directly if it's an absolute path.
-    if ([relPath isAbsolutePath]) {
+    if (isAbsolutePath) {
         return pszFileName;
     }

Granted, I probably shouldn't be calling into CCFileUtils from another thread, but I assumed that just getting a filename path would be safe.

Next: Performance Optimizations

I hope this helps someone! I'm going to be super busy with my game launch for the next month or so, but at some point I'm planning to write a post about performance optimizations, in which I bring the performance of the app up to 60 fps on an iPad 1. Stay tuned, and in the meantime I'd love to hear your feedback.

comments powered by