Tuesday, July 13, 2010

OpenGL ES 2.0 Book Teaser

I've been making really good progress on the OpenGL ES 2.0 book for Prags, and I'm really happy with what I've done so far. The book isn't quite as hand-holding as Beginning iPhone 3 Development was, but it's probably more hand-holding than any graphics programming book I've ever read. If you've got prior experience with graphics programming, you may get frustrated with the pace of the book. I'm working on Chapter 8 now, and I haven't even gotten to lighting yet.

Those of you who have never worked with a programmable pipeline engine like OpenGL ES 2.0 or have tried and been frustrated by the books and resources available, will (I hope) appreciate the approach I'm taking. I'm trying to be very, very thorough. I'm trying not to leave any questions hanging in the air. I've found, over the years, graphics programming books to be frustrating in that they assume a certain level of prior knowledge that's not easy to obtain outside of college math classes. It's my goal to explain not only how to do things, but why, and give at least some high-level information about the underlying concepts and math. My goal is to make graphics programming approachable for people who don't necessarily have math knowledge beyond basic high school geometry and trig. I can't do that with everything. For example, with projection matrices, I simply didn't think it was worth trying to cover homogeneous coordinates, so I focused on how projection vectors worked and mostly skipped the why. But that's an exception. In most cases, I'm really focusing on why we do what we do.

Writing books like this is fun, to be honest. I'm learning OpenGL ES 2.0 at a much deeper level than I previously knew and I feel good about the progress of the book. From the discussions I've had, I think there's a lot of people out there who want to program in OpenGL ES 2.0 so they can do cool stuff on the iPhone and iPad, but who just don't really know where to start. It's like all the cool stuff is sitting on a shelf just out of reach. And that's frustrating.

I don't know yet when the book will be available. I'm hoping that it will get accepted into the beta books program when it's far enough along. If that happens, I'll make sure to post about it here, because then the book will be available for reading online before the official release.

Now, no part of the book is ready for public consumption yet, but I'm going to post some of my code from the book today. I know there's a shortage of good, clean, straightforward iOS OpenGL ES 2.0 code out there, so I though I'd post one of the projects from the chapter I'm working on now for anybody who wanted to try and figure out how it all fits together.

Although it's a a simple app, there's a fair amount going on. I take care of setting up a perspective projection and a model view matrix that both moves and rotates the object and also do texture mapping. There's a simple vertex shader and a fragment shader that take care of transforming the scene and doing the texture mapping. Now, in OpenGL ES 2.0, there are no built-in functions to handle any of these tasks, so it's all got to be done manually, primarily in the shaders. There are also some useful classes and functions you may be able to leverage in your own code. Much of what's in here is based on code I've posted in the past, but it's all been updated and tweaked for use in the OpenGL ES 2.0 programmable pipeline.

Screen shot 2010-07-13 at 11.24.55 PM.png
Yes, it's our old friend, the icosahedron, but all dressed up to look like a twenty-side die. Because, you know, what's more geeky than a twenty-sided die?

Now, this project hasn't been code reviewed. Heck, the chapter it's for hasn't even been written yet, so I'm sure there are mistakes and CBBs (could be betters). This is also not particularly efficient code. I'm putting off until later in the book a number of optimizations, including vectorizing the matrix and vector functions, interleaving per-vertex attributes, and using VBOs and VAOs. At this point, I'm much more focused on clarity and concepts than on performance, so just be aware of that before you decide to incorporate any of this code into a production project.

As always, there are no requirements placed on your use of this code. You don't have to give attribution, and you don't have to contribute back changes, though if you do fix or improve something, I'd be glad to hear about it.

You can find the Xcode project right here.

I've also made the original Blender and Pixelmator files I used to create the vertices and texture coordinates of the icosahedron as well as the texture. You can download those here.

I am sorry, but have to say that I won't be able to answer questions about the code. Between the book and client work right, most days I'm at my desk from 8:00 in the morning until 1:00 or 2:00 the next morning, seven days a week, so if you need explanations, you're going to have to wait for the book.

Friday, July 9, 2010

Thumb

I thought I had done a post on this at some point, but after Googling around, I guess I never did. There were a couple of Twitter discussions about the subject in the past few days, so I thought it was worth mentioning. You can get more in-depth detail about this subject by watching the two OpenGL ES videos from the 2009 Tech Talk World Tour Videos (iTunes link, requires logging in with iPhone SDK account).

The ARM architecture has something called thumb mode (or just thumb). Now, I'm not a hardware engineer so the following may not be 100% technically accurate, but my understanding is that basically thumb mode uses a subset of the processor's available operations and passes two 16-bit operations in the space of a single 32-bit operation, allowing commands to be sent to the CPU twice as fast. For most applications, this is great, and leads to an improvement in overall performance.

However, with the ARMv6-based chips in the original iPhone, the iPhone 3g, and the first generation iPod touch¹, thumb mode didn't have access to the vector processors, so floating point operations forced the processor to convert the two 16-bit operations back into two 32-bit operations, perform the floating point math, and then convert back to the thumb operations, meaning you not only didn't see a performance increase, you often saw a dramatic decrease in performance with thumb on when writing heavy floating-point code, such as you would for an OpenGL ES application.

The version of thumb in ARMv7 which is used by the chip in the iPhone 3Gs and which is also used by Apple's A4 chip and therefore available on the iPad and iPhone 4, does have full access to the vector processors, so you can get the benefit of thumb while doing large amounts of floating point operations, so you want thumb on for these processors.

Therefore, if you're writing an OpenGL ES application, or anything else that does a lot of floating point operations, you want to use conditional build settings in Xcode to turn "Compile for Thumb" ON for ARMv7 and OFF for ARMv6.

You can add conditional build settings by selecting the build setting in Xcode and using the little gear button in the lower left corner of the Build Settings window. If you click it, it will popup a menu, and one of the options will be "Add Conditional Build Setting", which will add a new subrow to that setting. Select "ARMv6" on the left column and use the right column to turn it off. Once you do that, your application will build as a fat binary with an ARMv6 version that doesn't use thumb, and an ARMv7 version of the binary that does. Generally, the increase in application size is relatively minor compared to application's image and sound resources, and the performance gains can be substantial.

Friday, July 2, 2010

Pressure Sensitive iPad

One thing that I've wished the iPad had from the beginning was the ability to detect different levels of pressure, similar to a Wacom tablet. That would make it much more useful for things like sketching. I've heard from a few people that the hardware supports it, but I've been skeptical of those claims. How could a capacitative touch device detect pressure? But a few people I talked to at WWDC insisted it was possible with the hardware.

Turns out they were right. This is really cool. I don't know anything about the technology - whether they're really using pressure (I tend to doubt it) or just the relative size of the area being touched, but the results in the video look promising. I also hope the final version can be accomplished with only public APIs somehow.

Thursday, July 1, 2010

Synthesize by Default

So, in my previous post, I told you I was excited about the "synthesize by default" functionality that's now available in LLVM 1.5. And I am, very much so. But it turns out there's a caveat that tempers my excitement at the moment.

With synthesized iVars, we've had direct access to the underlying synthesized variable for a while now, so if you created a class like this:

#import <UIKit/UIKit.h>

@interface MyViewController : UIViewController
{
}

@property (nonatomic, retain) NSString *foo;
@end


You're able to access the NSString variable foo that backs the property of the same name within the scope of your class. So, in your dealloc method, you can do this, to give one common example:

- (void)dealloc
{
[foo release], foo = nil;
[super dealloc];
}

However, if you enable the "synthesize by default" feature in LLVM 1.5 and use it, meaning you don't actually have a @synthesize statement for the foo property, you lose this direct access to the underlying variables, leaving you with a bit of a conundrum - how do you do your memory cleanup without unintentionally triggering unwanted functionality, such as those in a lazy accessor or some more involved mutators.

Earlier today, I tweeted a question asking about the correct way to deal with this situation. After a bit of a Who's on First-like back and forth with many smart people, it finally came out that this is a bug in LLVM, not an intentional design decision. Hopefully it will be fixed before too long, but in the meantime, the best answer is probably to just keep using @synthesize.

Thanks to a whole bunch of people on Twitter, many of whom I'm sure I missed!

Wednesday, June 30, 2010

Modern ABI & No More @Synthesize

I haven't talked about one of my favorite new features of Xcode 4 and LLVM, because I wasn't sure of the NDA status. However, the cat's out of the bag now, so I feel like it's safe to mention. The iPhone Simulator in Xcode 4 now uses the Modern ABI (yet it's still correctly uses 32-bit datatypes!), so we can use synthesized instance variables in our iPhone applications and use the simulator. On top of that, if you choose to use LLVM instead of GCC, one of the (many) benefits you'll get is that @synthesize declaration is now assumed. If you don't need to specify anything on the @synthesize declaration (like a different setter or getter name) and your property isn't @dynamic, you can justleave it off. This feature isn't turned on by default (yet), but see Pilky's blog post for instructions on enabling this feature.

Less code to write? Boo-yeah! Count me in!

I believe (though I haven't confirmed this yet) that you can write code this way and still target older releases back to 3.0. If anyone knows otherwise, please let me know and I'll spread the word, though.

Monday, June 28, 2010

Code As If…

Sorry for how slow things have been here lately; I'm still suffering from WWDC work backlog, plus I've been spending a lot of time on the new book. This is the first book I've tried to write while also doing full-time client work and it's taking a bit of a toll on me.

I've had a blog post floating around my head since writing Beginning iPhone Development that has never become completely formed. It's about how the process of writing about code has changed the way I write code. I think the thoughts are finally ready to congeal into solid form. So, here goes.

The other day, I saw somebody wearing a T-shirt that said "Dance Like Nobody's Watching". I'm not much of a dancer, but I like the sentiment. However, dancing is not coding. The worst thing you can do is code like nobody's watching. You should code like everybody's watching you.

One thing I noticed after I'd been writing code for public consumption for a while is that I've become much more critical of my own code than I was before I started putting it out there for the world to see. A lot of that comes from pure vanity - I want to minimize the number of stupid mistakes that people are going to call me out on. There are a lot of eyes on the code I write for books and for this blog, and some of those eyes belong to people who are, quite frankly, smarter than me.

It's impossible to always put out perfect code, but I do like to avoid giving the impression of being a sloppy coder or, worse, of being a complete hack. The process I go through with code that goes into a book or blog post goes something like this:
  1. Make it work
  2. Make it work well
  3. Make it read well
The process isn't quite as linear as that makes it look, but that works as a very basic statement of the process. The last step includes the idea of refactoring, but it includes more than that. It's proofreading my code after I'm happy with the way it functions in order to look for ways the code could be written better. I'm not talking about performance here, that goes under #2. This is purely about maintainability and readability. Are my methods and variables using logical names? Is my code easy to read? Is the formatting consistent? Was I being lazy anywhere and doing quick copy and paste coding. Step 3 is about trying to be my own worst critic. You wouldn't send a proposal or other written document out without proofreading it. Your code shouldn't be any different.

I thought that I would revert back to my old ways when I wasn't writing code for public consumption, but it turns out that once acquired, this habit is hard to shake. Frankly, like most developers, I had always assumed that the third step, or at least the more picayune aspects of it, were a waste of time when doing production work.

That hasn't turned out to be true in my experience. In fact, it's been quite the opposite. In almost every case, I spend less time writing code because my code is now more readable, more precise, and generally quite a bit shorter than the way I used to write code. Every time I have to change, fix, or extend existing code, I spend less time doing it, and those little bits add up over the course of even a short job. In the log run, all those little things pay huge dividends.

Here's an example I was working on this weekend for OpenGL ES 2.0 for iOS 4. I'm putting together an Objective-C class to make creating and managing multiple programs and shaders in OpenGL easier. One piece of functionality I wanted to include was the ability to get the program and shader logs out of OpenGL. Without that information, it's awfully hard to identify why a shader won't compile or a program won't link. The end result of my first two steps ("Make it work", "Make it work well"), was this:

- (NSString *)vertexShaderLog
{
GLint logLength = 0, charsWritten = 0;

glGetShaderiv(vertShader, GL_INFO_LOG_LENGTH, &logLength);

if (logLength > 0)
{
char *log = malloc(logLength);
glGetShaderInfoLog(vertShader, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding
]
;
free(log);
return ret;
}


return @"No Log Data";
}

- (NSString *)fragmentShaderLog
{
GLint logLength = 0, charsWritten = 0;

glGetShaderiv(fragShader, GL_INFO_LOG_LENGTH, &logLength);

if (logLength > 0)
{
char *log = malloc(logLength);
glGetShaderInfoLog(fragShader, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding
]
;
free(log);
return ret;
}


return @"No Log Data";
}

- (NSString *)programLog
{
GLint logLength = 0, charsWritten = 0;

glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);

if (logLength > 0)
{
char *log = malloc(logLength);
glGetProgramInfoLog(program, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding
]
;
free(log);
return ret;
}


return @"No Log Data";
}

It was obvious, even as I was writing this code that I wasn't going to leave it like this. You can get away with this kind of unnecessary duplication of code in most projects because it functions fine and most clients and many development managers don't have the technical chops to read your code, but it violates the DRY principle something fierce. This is hardly the worse example of copy-and-paste coding I've ever seen, but code like this shouldn't ever go into production.

I tested this code to make sure the methods worked, then I immediately refactored the two shader log methods by adding a private method declared in an extension. I then changed the two existing methods to simply call the private method, so I was then using the same code for both scenarios.

- (NSString *)shaderLogForShader:(GLuint)shader
{
GLint logLength = 0, charsWritten = 0;

glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);

if (logLength > 0)
{
char *log = malloc(logLength);
glGetShaderInfoLog(shader, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding
]
;
free(log);
return ret;
}


return @"No Log Data";
}

- (NSString *)vertexShaderLog
{
return [self shaderLogForShader:vertShader];
}

- (NSString *)fragmentShaderLog
{
return [self shaderLogForShader:fragShader];
}

- (NSString *)programLog
{
GLint logLength = 0, charsWritten = 0;

glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);

if (logLength > 0)
{
char *log = malloc(logLength);
glGetProgramInfoLog(program, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding
]
;
free(log);
return ret;
}


return @"No Log Data";
}

That's definitely better than the original version. I got rid of the obvious duplication between the two nearly identical shader log methods. The only difference between the original two methods was the GLuint (unsigned integer) value passed into the two OpenGL ES function calls.

This code still bothered me, though. Look how similar the shader and program log functionality is. It's identical except for the two function calls and, I realized, those two function calls take exactly the same parameters. It had been a while since I had used them, so it took my brain a few seconds to drudge up how C function pointer worked, but once it did, the path for refactoring this code became obvious. A single method could be used for all three of these cases if I took function pointers to the two OpenGL API calls as method parameters.

Now, this second refactoring is one a lot of people would probably say isn't worth the hassle. It's too much. It doesn't pass a "cost-benefit analysis". The result is not going to be that much shorter, so how can you justify the time? I used to think exactly the same way before spending so much time writing code for public consumption, but now I've become convinced that refactoring like this does pass the cost-benefit analysis. I wasn't putting enough weight on the value of good code when doing that analysis. When you come back to code weeks, months, or years later, you won't remember all the little details. With well-written code, you won't have to.

The truth (in my experience, at least) is that refactored code is enough easier to maintain that it's worth taking the time to do it. Like everything, the more you do it, the better you get at it, and the faster you can do it. Eventually, it becomes second nature. You end up writing better code in roughly the same amount of time that you used to write sloppy-but-functional code. Plus, every time you visit that code in the future, you have a little less code to search through. Whenever you have to debug that code, you have a little less code to step through. Every time you fix the code, you have less code to fix.

If I had an error in the original version of these log functions, I would almost certainly have the same exact error in three places. When I discovered such an error, do you think I would remember to fix it in all three places? Maybe, if I wrote the code recently enough to remember. If somebody else were fixing my code, do you think they'd know they had to fix it in three places? Maybe… but more likely they wouldn't.

Here's the final version of the method I settled on for the book¹:

typedef void (*GLInfoFunction)(GLuint program, GLenum pname, GLint* params);
typedef void (*GLLogFunction) (GLuint program, GLsizei bufsize, GLsizei* length, GLchar* infolog);

- (NSString *)logForOpenGLObject:(GLuint)object
infoCallback:(GLInfoFunction)infoFunc
logFunc:(GLLogFunction)logFunc
{
GLint logLength = 0, charsWritten = 0;

infoFunc(object, GL_INFO_LOG_LENGTH, &logLength);

if (logLength > 0)
{
char *log = malloc(logLength);
logFunc(object, logLength, &charsWritten, log);
NSString *ret = [[NSString alloc] initWithBytes:log
length:logLength
encoding:NSUTF8StringEncoding
]
;
free(log);
return ret;
}


return @"No Log Data";
}

- (NSString *)vertexShaderLog
{
return [self logForOpenGLObject:vertShader
infoCallback:(GLInfoFunction)&glGetShaderiv
logFunc:(GLLogFunction)&glGetShaderInfoLog
]
;

}

- (NSString *)fragmentShaderLog
{
return [self logForOpenGLObject:fragShader
infoCallback:(GLInfoFunction)&glGetShaderiv
logFunc:(GLLogFunction)&glGetShaderInfoLog
]
;
}

- (NSString *)programLog
{
return [self logForOpenGLObject:program
infoCallback:(GLInfoFunction)&glGetProgramiv
logFunc:(GLLogFunction)&glGetProgramInfoLog
]
;
}


Now, if you work for somebody else, especially if you work in a large corporation, you may be measured on a series of metrics including something like "Lines of Code Written" (LOC). While I understand the need for accountability, most corporate metrics are (to be blunt) a bullshit abuse of statistics. The absolute worst metric ever devised is LOC. If you're working somewhere that's still in the coding dark ages and measuring you by LOC, run like hell, or at least lobby for a change in that policy. Measuring performance based on LOC encourages even good engineers to write sloppy, horrible code.

In my example here, I spent some of my time improving my code by removing 25 lines of code. I spent time taking it from 64 lines of code down to 39 lines of code. As an developer, I shouldn't be punished for doing that!

In real-life situations, we often convince ourselves that there's not time for refactoring while we code. After all, we have to make that deadline, and there's always a deadline! We can always come back and refactor the code later when we have more time, right?

Only you won't. You won't come back until you absolutely need to, and by that point, it'd probably be faster to rewrite it. If you work for somebody else, they're unlikely to let you go back and spend time "fixing" something that works perfectly well. That's especially true if you have a non-technical manager. Besides, you'll probably be onto your next deadline already and feeling time pressure all over again.

In the long run, good code is worth the time it takes. Often, it's worth it in the short run as well.

Make time to refactor, and not after the fact make it part of your development process. Do it up front, before it goes to test, because you won't come back. Proofread your code before you ever send out a build.

Or, in other words, code as if everyone was able to see and understand your code.

Addendum


There's another point I want to make. Not only should you code as if there were lots of eyes on your code, you should actually get lots of eyes on your code if you can. You will miss stuff even if you make proofreading and refactoring part of your development process. You will sometimes even miss stupid stuff. Case in point, I got an e-mail shortly after posting this from Brent Simmons of NetNewsWire fame. Brent very kindly took the time to proofread the code and pointed out one bug and several improvements that could be made.

Here are his suggestions:
  1. Return early if logLength < 1, so that the main functionality doesn't have to be indented.
  2. Return an autoreleased string, per Apple's coding conventions
  3. Return nil in the case of no log data, rather than a string that would have to be compared to determine if there was log data or not.
  4. Change the name of the char * and NSString variables to something slightly more meaningful. (Since the method is logForSomething, it returns a variable named log.)
I absolutely agree with every single one of these suggestions and am a little embarrassed about leaking the string, to be perfectly honest. Here's Brent's rewrite of my function that I plan to use in the book (with Brent's permission):

- (NSString *)logForOpenGLObject:(GLuint)object 
infoCallback:(GLInfoFunction)infoFunc
logFunc:(GLLogFunction)logFunc
{
GLint logLength = 0, charsWritten = 0;

infoFunc(object, GL_INFO_LOG_LENGTH, &logLength);
if (logLength < 1)
return nil;

char *logBytes = malloc(logLength);
logFunc(object, logLength, &charsWritten, logBytes);
NSString *log = [[[NSString alloc] initWithBytes:logBytes
length:logLength
encoding:NSUTF8StringEncoding
]
autorelease]
;
free(logBytes);
return log;
}


Thanks, Brent!


1 This may, of course, change if my tech reviewer finds an issue with it

Saturday, June 19, 2010

WWDC 2010 Post Mortem

WWDC. The Dubdub. Christmas in June.

If you've followed this blog for any length of time, you know Apple's annual developer conference is my absolute favorite week of the year, and it just seems to get better every year. For the days leading up to leaving for San Francisco, I'm like a kid on Christmas eve. I can't sleep from excitement and the time passes way too slowly.

Every year, there are less "must-see" technical sessions for me, personally. That was especially true this year because something like 60% of the attendees were first timers who haven't shipped their first app yet and the session schedule was designed accordingly, with quite a few beginner and intermediate level sessions. But WWDC is so much more than the sessions (and those come out on video anyway). Your first year or two attending, it's all about the sessions, but by this point for me, it's about the sessions that focus on new technologies, labs and, most importantly, a chance to catch up with people I only see once or twice a year. Well, it wasn't just new technology sessions. For obvious reasons, I also attended all of the OpenGL ES sessions this year, and they were great. They were much deeper technically than last year, and were much more focused on the OpenGL ES 2.0 programmable pipeline.

Even thought it was the same number of attendees as the last two years (5,200 attendees plus the various engineers and other staff), this year felt way busier than past years. Lines to get into sessions were often very long, and many sessions filled up. Some even filled up an overflow room. Although we've seen long lines for sessions going back to the introduction of the iPhone, it's never been like this year. Last year, for example, for the State of the Union addresses, I meandered into the overflow room ten minutes late and found a seat in one of several empty rows. This year, I sat on the floor of the overflow room for the overflow rooms. I guess that's good - people were serious about learning this year.

One negative aspect of the massive influx of newbs this year was a certain loss of etiquette. I've always been super impressed by the way my fellow WWDC geeks treat the staff and the facilities. I've never seen garbage left around or more than isolated cases of people being rude to the catering or cleaning staff. This year, unfortunately, that respect was somewhat lacking. During the keynote line, hundreds of people just left their litter laying around on the floor. It was really disgusting and I was embarrassed at that moment to be part of the group. And it didn't end with the keynote line, either, unfortunately. I saw many examples of people not bothering to pick up after themselves, or being rude to the staff. Even a few instance of people being rude to Apple engineers who were trying to help them with problems. I almost feel like I need to add a few items to my annual first timer's guide with things I had assumed any decent person would already know, like throw out your garbage, treat people with respect, and be nice to people who are trying to help you.

Let's be better next year, okay? Almost everybody I met or talked to seemed like super people, so I'm hoping this was just a one-time anomaly. I really, really would like it to be an isolated occurrence. Enough said on that topic.

Although the new iPhone 4 was the belle of the keynote ball, the real buzz at WWDC this year, as you probably know by now, was Xcode 4. For the first time in history, Apple has released the session videos to all registered developers for free, so if you haven't done so yet, you should go watch the Developer Tools State of the Union and the handful of Xcode 4 sessions. Apple's Developer Tools teams have been working really hard for quite a long time on this upcoming release, and even in the early preview state it's in, I already wish I could use it full-time. Fortunately, the Xcode team foresaw this and they made the project file format 100% backwards and forwards compatible between Xcode 3 and Xcode 4, so I can work in Xcode 4 then switch to Xcode 3 to do my ad hoc and release builds.

Honestly, one of my favorite parts of this particular WWDC was having the opportunity to buy a few rounds of drinks for some of the engineers who worked on Xcode 4. I'm not sure if those engineers have forgiven me yet, so I'm not going to call them out by name, but it's important to me during WWDC to show my appreciation to as many as I can of the people who make all the cool stuff I work with everyday. Steve Jobs gets a lot of credit, and rightly so, but he doesn't do it alone. It was really nice to see him call out some of the people who worked on iPhone 4 and iOS 4 during the keynote, but there are a lot of unsung heroes working at Apple, and most of them don't get a lot of recognition for teh awesome they bring. WWDC is the one week a year where we get to show our appreciation in person.

The User Experience lab appeared to be the biggest hit among the labs this year. Each morning, within minutes of the Moscone West doors opening up, there was a long line extending around the corner waiting for the UX lab to open. People waited in line literally for hours to get a UX review. I guess word got out this year about how good those reviews were. I know I saw several people raving about them last year. In general, I think people have really started to grok the fact that the labs represent an incredible opportunity to get questions answered by the people who really know the answers. If you're having a problem or an issue with a certain part of the system, likely you can find someone who actually works on that part of the system to answer your questions.

As for MartianCraft, we gave out all of our away team shirts pretty quickly this year. Sorry to those who wanted one but didn't get one. We way underestimated demand for the shirts and just didn't have enough with us to give them to everyone who wanted one. We'll be better prepared next year, and we're looking into making the shirts available online for anybody who's interested, but that probably won't happen until we've dug ourselves out from the hole that got created as a result of all three of us not working for a week.