Loading a PNG into Memory and Displaying It as a Texture with OpenGL ES 2: Adding Support for iOS

In the previous post, we looked at loading in texture data from a PNG file and uploading it to an OpenGL texture, and then displaying that on the screen in Android. To do that, we used libpng and loaded in the data from our platform-independent C code.

In this post, we’ll add supporting files to our iOS project so we can do the same from there.

Prerequisites

To complete this lesson, you’ll need to have completed Loading a PNG into Memory and Displaying It as a Texture with OpenGL ES 2, Using (Almost) the Same Code on iOS, Android, and Emscripten. The previous iOS post, Calling OpenGL from C on iOS, Sharing Common Code with Android, covers setup of the Xcode project and environment.

You can also just download the completed project for this part of the series from GitHub and check out the code from there.

Adding the common platform code

The first thing we’ll do is add new supporting files to the common platform code, as we’ll need them for both iOS and emscripten. These new files should go in /airhockey/src/platform/common:

platform_file_utils.c

#include "platform_file_utils.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

FileData get_file_data(const char* path) {
	assert(path != NULL);
		
	FILE* stream = fopen(path, "r");
	assert (stream != NULL);

	fseek(stream, 0, SEEK_END);	
	long stream_size = ftell(stream);
	fseek(stream, 0, SEEK_SET);

	void* buffer = malloc(stream_size);
	fread(buffer, stream_size, 1, stream);

	assert(ferror(stream) == 0);
	fclose(stream);

	return (FileData) {stream_size, buffer, NULL};
}

void release_file_data(const FileData* file_data) {
	assert(file_data != NULL);	
	assert(file_data->data != NULL);

	free((void*)file_data->data);
}

We’ll use these two functions to read data from a file and return it in a memory buffer, and release that buffer when we no longer need to keep it around. For iOS & emscripten, our asset loading code will wrap these file loading functions.

platform_log.c

#include "platform_log.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>

#define LOG_VPRINTF(priority)	printf("(" priority ") %s: ", tag); \
								va_list arg_ptr; \
								va_start(arg_ptr, fmt); \
								vprintf(fmt, arg_ptr); \
								va_end(arg_ptr); \
								printf("\n");

void _debug_log_v(const char *tag, const char *fmt, ...) {
	LOG_VPRINTF("VERBOSE");
}

void _debug_log_d(const char *tag, const char *fmt, ...) {
	LOG_VPRINTF("DEBUG");
}

void _debug_log_w(const char *tag, const char *fmt, ...) {
	LOG_VPRINTF("WARN");
}

void _debug_log_e(const char *tag, const char *fmt, ...) {
	LOG_VPRINTF("ERROR");
}

For iOS and emscripten, our platform logging code just wraps around printf.

Updating the iOS code

There’s just one new file that we we need to add to the ios group in our Xcode project, platform_asset_utils.m:

#include "platform_asset_utils.h"
#include "platform_file_utils.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

FileData get_asset_data(const char* relative_path) {
	assert(relative_path != NULL);
    
    NSMutableString* adjusted_relative_path = 
        [[NSMutableString alloc] initWithString:@"/assets/"];
    [adjusted_relative_path appendString:
        [[NSString alloc] initWithCString:relative_path encoding:NSASCIIStringEncoding]];
    
    return get_file_data(
        [[[NSBundle mainBundle] pathForResource:adjusted_relative_path ofType:nil]
         cStringUsingEncoding:NSASCIIStringEncoding]);
}

void release_asset_data(const FileData* file_data) {
    assert(file_data != NULL);
	release_file_data(file_data);
}

To load in an asset that’s been bundled with the application, we first prefix the path with ‘/assets/’, and then we use the mainBundle of the application to get the path for the resource. Once we’ve done that, we can use the regular file reading code that we’ve defined in platform_file_utils.c.

iOS experts: When I was researching how to do this, I wasn’t sure if this was the best way or even the right way, but it does seem to work. I’d love to know if there’s another way to do this that is more appropriate, perhaps just by grabbing the path of the application and concatenating that with the relative path?

Aside from adding this new file, we just need to add some references to the project and then we’ll be able to compile & run the app.

Adding the libpng files

Right-click the project and select Add Files to “Air Hockey”…. Add the following C files from the libpng folder, and add them as a new folder group:

png.c pngerror.c pngget.c pngmem.c pngpread.c pngread.c pngrio.c pngrtran.c pngrutil.c pngset.c pngtrans.c pngwio.c pngwrite.c pngwtran.c pngwutil.c

Remove the common folder group that may be left there from the last lesson, and then add all of the files from the core folder as a new folder group. Do the same for all of the files in /platform/common. Finally, add the assets folder as a folder reference, not as a folder group. That will link the assets folder directly into the project and package those files with the application.

We’ll also need to link to libz.dylib. To do this, click on the ‘airhockey’ target, select Build Phases, expand Link Binary With Libraries, and add a reference to ‘libz.dylib’.

The Xcode Project Navigator should look similar to the below:

Xcode Project Navigator

Xcode Project Navigator

It might make more sense to link in the libpng sources as a static library somehow, but I found that this compiled very fast even from a clean build. Once you run the application in the simulator, it should look similar to the following image:

iOS Simulator, showing the texture on the screen

iOS Simulator, showing the texture on the screen

Now the same code that we used in Android is running on iOS to load in a texture, with very little work required to customize it for iOS! One of the advantages of this approach is that we can also take advantage of the vastly superior debugging and profiling capabilities of Xcode (as compared to what you get in Eclipse with the NDK!), and Xcode can also build the project far faster than the Android tools can, leading to quicker iteration times.

Exploring further

The full source code for this lesson can be found at the GitHub project. In the next post, we’ll also cover an Emscripten target, and we’ll see that it also won’t take much work to support. As always, let me know your feedback. :)

About the book

Android is booming like never before, with millions of devices shipping every day. In OpenGL ES 2 for Android: A Quick-Start Guide, you’ll learn all about shaders and the OpenGL pipeline, and discover the power of OpenGL ES 2.0, which is much more feature-rich than its predecessor.

It’s never been a better time to learn how to create your own 3D games and live wallpapers. If you can program in Java and you have a creative vision that you’d like to share with the world, then this is the book for you.

Share

4 thoughts on “Loading a PNG into Memory and Displaying It as a Texture with OpenGL ES 2: Adding Support for iOS”

Add Comment Register



Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>