Translucent Software's Blog

Tuesday, March 4, 2014

Update: Apparently something has changed in Google maps api since this original post and now it appears that the content window of the iframe has to explicitly be checked in the onload function, otherwise the function will incorrectly load the map when it has not in fact properly loaded. This has been updated in the code example below.During a diversion with some website work, I came across an interesting issue with using Google's Map Engine Lite to embed a custom map on a website. For those of you who are unfamiliar with the Map Engine Lite, as I was, it provides a nice graphical front-end to create a Google map with custom markers and features and then easily share the map with others by just embedding the link to the map in an iframe on a website. All went as expected; create a nice custom map, embed the map on the website and done, simple and straight forward with basically no coding required.
So now the moment has come to show others the great map that has been created and BOOM!Nothing! There is just a big blank, empty space where the map should be. With a perplexed look you initially decide this must just be an isolated incident and investigate the browser console to discover that an error of "'X-Frame-Options''DENY'"is what is being reported as the reason the map is not being rendered. This really doesn't give any reassurance of what is going on to cause this big blank space on the website. With such a nondescript message you just defer investigating this issue, as it must just be just this person and go to show someone else the map and BOOM!Nothing!

Now you realize, there is a major issue going on here that is causing the map to not render and leave a big blank space smack dab in the middle of the website. Of course the blame, right or wrong, will fall directly on the website owner/builder and they will either think your incompetent or just plain inept to allow such a bad design to be on your website. Something must be done to fix this as it is not just simply an isolated incident and apparently is going to happen and needs to "just work" all the time regardless!
Ironically, the same place that is discovered to be causing the root of the problem provide access to the answer of what is causing this problem. A little Googling around and it is discovered that this issue is apparently cropping up on other peoples websites, so some relief that it's not just your website but that still doesn't make this problem any less irritating. But at the very least it might just have a solution floating around since this is not as isolated as first thought.
Some deep digging reveals that the core of the issue is that the user is "partially" logged into Google and before being allowed to see the map, Google is requiring the user to log in again, even though the map is specified as public without a log in required. Furthermore, the reason we have a blank map is because of the iframe not allowing redirection to another server; the map is hosted at 'mapsengine.google.com' and the required log on is being redirected to the server 'accounts.google.com', so it just refuses to render anything because of the redirection.
The interesting thing about this problem is that no one seems to have an answer about how to fix this. Actually this was really surprising since the problem seems to happen quite a bit, there seems to be reporting of this issue and the issue appears to have even been reported to Google. Regardless, it is completely unacceptable to have a blank space on a website with no explanation of what is going on, so a fix needed to be created since it could not be found.
Oddly enough the solution to this problem is actually quite easy and straight forward with a little use of javascript. The iframe prevents any fallback behavior if the content does not render but it does generate an 'onload' event when the frame loads. Hopefully you will see that now fixing this issue is trivial. All that is needed is to use an image as a placeholder that looks like the map (a screen shot works nicely) with a link to the map on Google initially and when the iframe is loaded in the background, swap it with the map placeholder image. Amazingly straight forward but apparently not a solution that anyone has posted that I could find.
Now onto some code to demonstrate how to fix this issue. The javascript is pretty straight forward, this can simply be placed in the head section like below:<html><head><script type="text/javascript">/* Create a function to create the maps iframe */function setupIframe() {var iframe = document.createElement("iframe");iframe.setAttribute("src", "https://mapsengine.google.com/map/embed?mid=(your_id_here)");iframe.setAttribute("id", "_map_iframe");iframe.style.width = 800+"px";iframe.style.height = 375+"px";iframe.style.visibility="hidden";iframe.style.boxShadow="10px 10px 5px #888888";document.getElementById("map_div").appendChild(iframe);iframe.onload=function() {/* UPDATE: Apparently we need to check and make sure that we have a valid content window */if(this.contentWindow != null) {

/* Access the placeholder image, set it hidden and remove it from the DOM */

var placeholder = document.getElementById("_maps_placeholder");

placeholder.style.visibility="hidden";

placeholder.parentNode.removeChild(placeholder);

/* Set the iframe to visible */

this.style.visibility="visible";}

};}/* Make sure to run the setup code when the window loads */window.onload=setupIframe;/* Set a timeout function to remove the iframe if it does not load (if desired) */window.setTimeout(function () {var mapsFrame = document.getElementById("_map_iframe"); if(mapsFrame.style.visibility != "visible") mapsFrame.parentNode.removeChild(mapsFrame);}, 10000);</script></head><body>…………...</body></html>
Hopefully this should be straight forward in what is happening. We create an iframe element in javascript with our map source, set its visibility to hidden, tweak a few of the style settings and add it to the document where it should inevitably be placed. Then in the iframe "onload" function, hide our map placeholder image, remove the placeholder and show the actual Google map, thus swapping out the image placeholder when/if the map loads. Next we need to add the setup function to the window to run when it loads the document content and here also setup a timeout function that can be added, if desired, to remove the iframe after a predetermined amount of time, 10 seconds in this example, to give up trying to load the map.
The html code for the actual place holder in the document would look something like this:<div id="map_div"><a id="_maps_placeholder"href="https://mapsengine.google.com/map/embed?mid=(your_id_here)"><img class="BorderedImg"src="images/Map_Placeholder.jpg"alt="Google Map"width="800"height="375"/></a></div>
All we have here is a div that holds a map placeholder image with a link to the Google map and where the Google map is being place via the Javascript function. Now if you noticed the actual anchor tag is what we are removing in the javascript; it can be the anchor tag or the image, it doesn't really matter which you remove, I just wanted all of the code removed. So no matter what we have "something" occupying the possibly blank space if the Google requires a log in, which the user will have an opportunity to do if the user follows the link to Google and everything will "just work" as it should.
The css used for the image, which most closely matches the styling that the final map uses is:.BorderedImg {border:2px solid #999;border-bottom-color:#EEE;border-right-color:#EEE;box-shadow: 10px 10px 5px #888888;}
That's it! A fairly simple solution that will provide some robust fallback behavior for the embedded map. I feel that this better matches the all around experience desired as there will always be something occupying the space for the map, even if the user has disabled javascript in their browser. It would have been much nicer, in my opinion, to be able place the anchor tag and image content inside of the <iframe></iframe> tags and have the iframe render that content if it would not render its own source. Short of that, here is a solution that accomplishes the same goal.

Monday, February 17, 2014

Translucent uses CAOpenGLLayer as one of its primary rendering facilities and the code for rendering needed to be updated to support the 3.2 OpenGL Core Profile and automatic switching between GPUs but there was no directly applicable, clear, concise information out there about how to specifically accomplish both of these things for CAOpenGLLayer. The only information out there really focused on using the NSOpenGLView. I didn't, couldn't and wouldn't use NSOpenGLView. So a solution was needed for CAOpenGLLayer to use the new Core Profile. This became especially important since the legacy style was deprecated in OpenGL a while ago and all future version use the new Core Profile style, Apple was just a little behind in pushing this through.
I will mention that using the new Core Profile requires the use of shaders and that now the programmer being responsible for all of the math that the library use to handle for matrix manipulation. I won't go into depth on those topics, they are quite lengthy themselves and there are plenty of resources available online. If your targeting 10.8 or later you can use GLKit, if your using 10.7 you can roll your own by using GLKit as a reference. Apple also has many pieces of example code with shader linking, compiling and use if your in need of that code as well.
For those of you who are upgrading your code it is worth mentioning that all rendering for the 3.2 OpenGL context needs to be done through the use of vertex arrays to send the drawing calls. You simply need to use glGenVertexArrays and glBindVertexArray to create a vertex array when specifying your vertex buffers and bind the vertex array first when setting up rendering. Without the vertex array, nothing will render. This can be subtle and easily overlooked as you can draw with vertex buffers and without vertex arrays in legacy mode but the Core Profile requires the use of both.
Now to get the Core Profile running on the layer was actually rather easy after a plunge through the docs of CGL (Core OpenGL), which is the lowest-level programming interface for OpenGL. The main thing that has to be done is to build the pixel format that you want the renderer to support, which is done in copyCGLPixelFormatForDisplayMask method of CAOpenGLLayer. Usually in this method you'd just return the super class implementation but to get the new Core Profile you have to opt in and specify the pixel format yourself. It should look something like this:

- (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask

{

CGLPixelFormatAttribute attribs[] = {

kCGLPFADisplayMask, 0,

kCGLPFAColorSize, 24,

kCGLPFAAlphaSize, 8,

kCGLPFAAccelerated,

kCGLPFADoubleBuffer,

kCGLPFAAllowOfflineRenderers,

NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,

0

};

attribs[1] = mask;

CGLPixelFormatObj pixFormatObj = NULL;

GLint numPixFormats = 0;

CGLChoosePixelFormat(attribs, &pixFormatObj, &numPixFormats);

return pixFormatObj;

}

The important things to notice is that the 3.2 profile is specified for the OpenGL profile to use and that allow offline renders is specified, offline renders means GPUs that are not directly/or currently connected to a display i.e. the integrated GPU.
Now one more important thing that has to be done to support the use of offline renders. In the applications Info.plist you need to add the key NSSupportsAutomaticGraphicsSwitching as a boolean and set it to YES. Both of these things are required to enable use of the switching between graphics card, otherwise it will only be allowed to run on the discrete card, which uses much more battery power and causes mobile users to not want to run the application with battery power.
Apple has a much more in depth Tech Note that addresses much more of the intricacies of using multiple GPUs with OpenGL. If your doing simple rendering you probably don't have to worry too much about switching GPUs but its always best to test it thoroughly.
Another hook into the context setup that is a good thing to override is copyCGLContextForPixelFormat, as this is where the context is created and it is a really good place to setup any of your OpenGL state required for rendering. It would look something like this:

//Setup any OpenGL state, make sure to set the context before invoking OpenGL

CGLContextObj currContext = CGLGetCurrentContext();

CGLSetCurrentContext(context);

//Issue any calls that require the context here.

CGLSetCurrentContext(currContext);

}

return context;

}

You can use CAOpenGLLayer as it is, but more than likely you did not go to all the work of setting up an OpenGL renderer instead of creating a CALayer with and image for the reason you want to animate the rendering of the content. So for a complete example of CAOpenGLLayer I'll go over how you would setup the layer for custom property animation and how you would use that to render the animation.
This is just like any other custom animatable property in that you specify your properties as dynamic, create no implementations for them and override a few methods. The first two methods here to override are needsDisplayForKey and actionForKey. These are used to tell the layer animation system the properties that need to be animated and the actual animation to run on property change. The third method here, initWithLayer, is used to copy your layers instance variables. This would be an example of animating a color:

Notice here is that the model layer is being accessed for the fromValue in actionForKey and we create and return an animation object. With initWithLayer we only copy variables that are not part of the dynamic properties but part of the layer state, i.e. shaders, vao, vbo, etc. Now the actual rendering of this is done in drawInCGLContext. Here is an snippet of what it would look like.

- (void)drawInCGLContext:(CGLContextObj)ctx

pixelFormat:(CGLPixelFormatObj)pf

forLayerTime:(CFTimeInterval)t

displayTime:(constCVTimeStamp *)ts

{

NSColor *currentFill = [[self.presentationLayerfillColor]

colorUsingColorSpaceName:NSCalibratedRGBColorSpace];

CGFloat red = 0.0f, blue = 0.0f, green = 0.0f, alpha = 0.0f;

[currentFill getRed:&red green:&green blue:&blue alpha:&alpha];

//In the real world we would first have to enable the shader with glUseProgram

//before we could make any calls to set the shader variables

glUniform4f(colorPos, (GLfloat)red, (GLfloat)green, (GLfloat)blue, (GLfloat)alpha); //when we are all finished we need to call the super implementation to flush[superdrawInCGLContext:ctx pixelFormat:pf forLayerTime:t displayTime:ts];

}

Notice here that you need to make sure to access the presentation layer for current animated display value. Also to note with colors, they need to be in NSCalibratedRGBColorSpace to get the red, green and blue values with: getRed:green:blue:alpha:, otherwise they will create a wonderful exception and crash if the color space of the NSColor object is anything else, as it will be with something like [NSColor blackColor]. With the red,green,blue and alpha in hand you would then simply pass it down to the shader with something like glUniform4f or put it in your vertex buffer.
Hopefully this has been a helpful reference to get the Core Profile up and running in CAOpenGLLayer. I wanted this information out there because when I went through this process there really was not really any information on exactly how to support using the Core Profile on CAOpenGLLayer.
Also I hope that someone else will benefit from the consolidating of information here to enable switching of graphics chipsets, since it was not overly clear at the time that it is a "two step process" because it is split between seemingly unrelated tech notes. For full reference here is the Tech Note for setting the Info.plist variable.