Toggling iOS Render Debug Options Without a Computer

Have you ever been away from your computer, playing around with a feature on your app, and spot a performance issue that you want to get some insights into right at that moment?

Some of the tools that we depend on for debugging these issues, like Instruments, are sufficiently powerful that I don't blame them for requiring a separate machine to use.

Others, though, don't have that same excuse — like the incredibly useful Render Debug Options (such as Color Blended Layers or Color Offscreen-Rendered). Though these are enabled through Xcode, they're essentially just toggling a flag on the device itself, so it seems reasonable that we can cut Xcode out of the picture entirely.

Note: The timing of this post might seem a bit off, as being away from a computer is less viable now than usual.

As a counterpoint, I'd like to mention that WWDC is in less than a week away, and I feel that posting this now slightly increases the chance that by sheer bad luck, this becomes officially supported and this post is made redundant — which would still be a fine outcome overall.

CARenderServerSetDebugOption

When you toggle one of the render debug options in Xcode, the end result is a call to CARenderServerSetDebugOption on your device or simulator. This function takes in three parameters:

CARenderServerSetDebugOption(
    0,    /* render server mach port */
    0x02, /* debug option (Color Blended Layers) */
    1     /* new value */
)

CARenderServerSetDebugOption works by communicating with iOS’ Render Server, a separate process that handles… well, rendering.

The first parameter here is a mach port that allows other processes to communicate with that server. You can get the appropriate port yourself by first calling CARenderServerGetServerPort, but you can also pass 0 to have CARenderServerSetDebugOption look it up for you.

The remaining two parameters are fairly straightforward — we pass an integer value that represents the option we want to update, and then its new value (either 0 or 1).

Here are the options that Xcode allows you to toggle:

Color Blended Layers:              0x02
Color Hits Green and Misses Red:   0x13
Color Copied Images:               0x01
Color Layer Formats:               0x14
Color Immediately:                 0x03
Color Misaligned Images:           0x0E
Color Offscreen-Rendered Yellow:   0x11
Color Compositing Fast-Path Blue:  0x12
Flash Updated Regions:             0x00

Setting Options from a Simulator

Since CARenderServerSetDebugOption is not a public function, we need to look it up at runtime. We can do this using dlsym, which allows us to find the address of a symbol within a given dynamic library (in this case, QuartzCore):

// The location of the QuartzCore
// framework on iOS
let quartzCorePath =
    "/System/Library/Frameworks/" +
    "QuartzCore.framework/QuartzCore"

// Use `dlopen` to get a handle
// for the QuartzCore framework
let quartzCoreHandle = dlopen(
    quartzCorePath,
    RTLD_NOW)

// Store the address of our function
let functionAddress = dlsym(
    quartzCoreHandle,
    "CARenderServerSetDebugOption")

// Create a typealias representing our
// function's param types / return type
typealias functionType = @convention(c) (
    CInt, CInt, CInt
) -> Void

// Cast the address to the
// above function type
let CARenderServerSetDebugOption = unsafeBitCast(
    functionAddress,
    to: functionType.self)

// Call the function!
CARenderServerSetDebugOption(0, 0x2, 1)

If you call the above code in an app running on the Simulator, you'll see the Color Blended Layers option take effect, with your app now painted in a sea of red & green.

Replacing the second parameter in the call to CARenderServerSetDebugOption with other values from the list above allows you to toggle other options instead — including ones that are usually only exposed for devices rather than simulators (though not all work perfectly).

Setting Options from a Device

Entitlements

The above code will run without any errors or logs on a physical device — but it won't actually have any effect. That's because the binary calling this function must include the com.apple.QuartzCore.debug entitlement.

It's easy to add this entitlement for use on a jailbroken device using ldid or jtool), but unfortunately, this does limit our ability to use this function on standard devices.

Note: It might also be possible to use an entitlement bypass here, but I haven't had much luck with this particular entitlement. Part of the issue might be that it's still unclear to me precisely where this entitlement is actually checked — a question for a different day.

Might As Well Build A Preference Bundle

Since we're essentially limited to jailbroken devices here, we might as well switch to controlling this via Settings, since that feels a bit more natural than keeping some random app around to toggle render options with.

PreferenceLoader provides an easy way to do just that; it allows us to create our own preference pane with full control over the view controller it displays, which is perfect for our use case.

We can start by creating a subclass of PSListController, provided by iOS’ Preferences.framework. This provides a lot of conveniences for us, including the ability to easily create new switch cells using PSSpecifier instances, each of which describes an individual preference.

As an example, the below setup will create a preference pane with a single switch, along with the ability to run arbitrary code when that switch is toggled:

#import <Preferences/PSListController.h>
#import <Preferences/PSSpecifier.h>

// Create a `PSListController` subclass
@interface ExampleListController: PSListController

@end

@implementation ExampleListController

// Override `viewDidLoad` to setup our specifiers,
// which describe each preference we support
- (void)viewDidLoad {
    [super viewDidLoad];

    // Specifier for a switch labeled "Basic Switch"
    // that calls our setter below when toggled
    PSSpecifier *specifier =
        [PSSpecifier preferenceSpecifierNamed:@"Basic Switch"
                                       target:self
                                          set:@selector(valueChanged:forSpecifier:)
                                          get:nil
                                       detail:Nil
                                         cell:PSSwitchCell
                                         edit:Nil];

    // Update our controller's specifiers list
    self.specifiers = [NSMutableArray arrayWithObject:specifier];
}

// Setter called when our switch is toggled
- (void)valueChanged:(NSNumber *)value forSpecifier:(PSSpecifier *)specifier {
    // Logs "Specifier 'Basic Switch' changed to 1" (or 0)
    NSLog(@"Specifier '%@' changed to %i",
        specifier.name,
        value.boolValue);
}

@end

From here, we can easily create one PSSpecifier for each render debugging option that we want to expose, and then call CARenderServerSetDebugOption in our setter to enable the option. We can even add a getter that uses CARenderServerGetDebugOption to ensure our preference pane always reflects the current state when relaunched.

The End Result

With this setup, we can build our full preference pane, and I'm fairly happy with the end result:

If you're interested in the resulting code, or want to try it on your own device, you can find the project on GitHub.

An idea for a Better End Result

One thing I wanted to try earlier on was creating an application that provided info & examples for each of the render debug options.

For example, you could have a detail view controller with a paragraph or two about offscreen rendering, and then a scrollable list of examples where it might come into play — like two side-by-side rectangles with a shadow applied, but one using shadowPath to avoid offscreen rendering — and a short blub about the difference. When running on a simulator or jailbroken device, you could add switches to enable or disable the effect and see the difference for yourself.

I thought that would be a fun app; both for educational purposes, and to have an easy reference for some common performance issues; but I realized that not only do I not know enough details about some of the newer options (like “Color Layer Formats”), I am also unable to find any good sources for learning about them!

If you're aware of a good reference for these options, or know enough about them yourself and are willing to share — please let me know!


Say Hello!