tag:blogger.com,1999:blog-48824080341980033502024-02-19T14:08:04.614+09:00Q-BlogA, hopefully regular, update on what's happening with <a href="http://www.qythyx.com">Qythyx</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.comBlogger31125tag:blogger.com,1999:blog-4882408034198003350.post-32673169701519755892020-03-09T14:25:00.002+09:002020-03-09T14:34:10.208+09:00Unit test coverage in Visual Studio for MacI've recently started working on a new project using <a href="https://visualstudio.microsoft.com/vs/mac/">Visual Studio for Mac</a> to write apps for iOS and Android in C#. I'll try to write about this new project in the future, but for now I wanted to talk about measuring and viewing code coverage for my unit tests.<br />
<br />
Although Visual Studio for Windows does have code coverage measurements and reporting <a href="https://docs.microsoft.com/en-us/visualstudio/test/using-code-coverage-to-determine-how-much-code-is-being-tested?view=vs-2019">built-in</a>, VS for Mac does not. It does support running unit tests, via <a href="https://nunit.org/">NUnit</a> or <a href="https://xunit.net/">xUnit</a>, but only shows information about tests passing or failing.<br />
<br />
I did a bit of searching about found the <a href="https://github.com/ademanuele/VSMac-CodeCoverage">VSMac-CodeCoverage</a> extension for VS, but when I tried it install it failed. It looks like there has been a new release which may solve that, but I haven't had a chance to try yet. [<b>Update: that extension has been fixed and does work now.]</b><br />
<br />
Because the above extension didn't work for me when I tried, I can up with a different usable solution. It isn't perfect, but it does allow me to run my tests, generate a code coverage report, and view it as HTML with a single menu option. Here's what I did:<br />
<br />
<ol>
<li>In your unit tests project add the following two nuget packages:</li>
<ol>
<li><a href="https://github.com/tonerdo/coverlet">coverlet</a></li>
<li><a href="https://github.com/danielpalme/ReportGenerator">ReportGenerator</a></li>
</ol>
<li>Verify that you can run tests from the command line by running this in the folder of your unit tests project:<div class="code" style="background-color: #1e1e1e; color: #d4d4d4; font-family: "Fira Code", Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height: 21px; white-space: pre;">
dotnet <span style="color: #dcdcaa;">test</span> --collect:<span style="color: #ce9178;">"XPlat Code Coverage"</span></div>
</li>
<li>Verify that you can generate reports from the command line:<div class="code" style="background-color: #1e1e1e; color: #d4d4d4; font-family: "Fira Code", Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height: 21px; white-space: pre;">
~/.dotnet/tools/reportgenerator -reports:TestResults/0cf0a5da-b2a9-46b1-9fe8-78ae83743fd7/coverage.cobertura.xml -targetdir:CoverageReport</div>
The long ID will be different for each test run.<br />
Then view the generated report:<div class="code" style="background-color: #1e1e1e; color: #d4d4d4; font-family: "Fira Code", Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height: 21px; white-space: pre;">
open CodeCoverage/index.htm</div>
</li>
<li>If the above worked, then you just need to script to semi-automate this. Here's my script:<div class="code" style="background-color: #1e1e1e; color: #d4d4d4; font-family: "Fira Code", Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height: 21px; white-space: pre;">
<div>
<span style="color: #6a9955;">#!/bin/zsh</span></div>
<div>
dotnet <span style="color: #dcdcaa;">test</span> --collect:<span style="color: #ce9178;">"XPlat Code Coverage"</span></div>
<div>
~/.dotnet/tools/reportgenerator -reports:TestResults/<span style="color: #ce9178;">`ls -1t TestResults </span>|<span style="color: #ce9178;"> head -1`</span>/coverage.cobertura.xml -targetdir:./CodeCoverage</div>
<div>
open CodeCoverage/index.htm</div>
</div>
</li>
<li>Finally, add a custom tool to the Tools menu: Tools → Edit Custom Tools...<br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1md4YAKQTWMy1QiFIBE12Y7XGFn6I482cnKa9FICwaLNcSQN5mjOCWdvPUApFc4EdvKz9nNBnSqOArW3lIUsZgmzQy05ElIbCV8pUjn8kVvXkExBYCqRMCsZQOv-M00MUY8UZlkmK9xyv/s1600/EditCustomTool.png" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="1131" data-original-width="1540" height="292" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1md4YAKQTWMy1QiFIBE12Y7XGFn6I482cnKa9FICwaLNcSQN5mjOCWdvPUApFc4EdvKz9nNBnSqOArW3lIUsZgmzQy05ElIbCV8pUjn8kVvXkExBYCqRMCsZQOv-M00MUY8UZlkmK9xyv/s400/EditCustomTool.png" width="400" /></a></li>
</ol>
<div>
You can then run the tests to generate coverage data, generate the coverage report, and open it in your browser just by running this new command from the Tools menu. Note that you must run the command when one of your unit test files is opened because it uses the <b><span style="font-family: "courier new" , "courier" , monospace;">${ProjectDir}</span></b> as the working directory and the unit test runner must run in a unit test project.</div>
Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-35376870004010902362015-03-01T16:55:00.001+09:002015-03-01T17:04:05.882+09:00Facebook Integration<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNGXmwOPhavh91Vve7vx5MCZwVToTJrXCuSVLKKw9MMiJGmMU7Jf8R8RUpWd8wSagE55XcfPFWKHRChYiVHTyyn6x0eU3J9ZgjMtryT86HJ-lhj8aKsaWFzZPCLGWuQqFp97Y0THSe-Hbp/s1600/IMG_5015.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNGXmwOPhavh91Vve7vx5MCZwVToTJrXCuSVLKKw9MMiJGmMU7Jf8R8RUpWd8wSagE55XcfPFWKHRChYiVHTyyn6x0eU3J9ZgjMtryT86HJ-lhj8aKsaWFzZPCLGWuQqFp97Y0THSe-Hbp/s1600/IMG_5015.jpg" height="400" width="225" /></a>I've spent the past few weeks adding Facebook integration to Zoing. This took much longer than I expected, but it is working now and I only need to finish testing and release. I didn't carefully document why this took longer than expected, but I still want to write a little about my experience in case it might help others.<br />
<br />
You can see what this looks like (for iOS) in the picture to the right. Below that is how it ends up in your Facebook timeline.<br />
<br />
First, as I've written before, I'm using <a href="http://www.xamarin.com/">Xamarin</a> to develop on both iOS and Android in C#. This allows me to work in a language I'm proficient in, and reuse a lot of the code. This works really well for areas of the code that are platform agnostic, such as the core logic for my game. It also works quite well for areas that have a good common library, such as using <a href="http://www.opentk.com/">OpenTK</a>, which is a C# wrapper for OpenGL. But, sharing code becomes less useful when a library is not consistent across platforms, which is the case for Facebook's SDK for <a href="https://developers.facebook.com/docs/ios">iOS</a> and <a href="https://developers.facebook.com/docs/android">Android</a>.<br />
<br />
I can't think of any good reason that Facebook's SDK is so different for these two platforms. Sure there are differences in the way that most Apple libraries work and Android libraries work. Some of that is due to best practices for Objective C versus those for Java. But, I don't see the differences in Facebook's library is being beneficial in any way. It means that anyone who wants to integrate Facebook into their apps essentially needs to learn how to use the library twice.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjin-70nxdmj93ueIOOwFdUDEZjoIPNOKKeZy8hDtyWBaqnZ4e-1XM7cxHu2ywewNryiLi3mRQkErDPW4_U593uC5J1bKJipIGQRkAYrX6F6LEqJRWdjJIk9SaW5gwUjpzeia4ePeDJai1Y/s1600/Screen+Shot+2015-03-01+at+5.00.48+PM.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjin-70nxdmj93ueIOOwFdUDEZjoIPNOKKeZy8hDtyWBaqnZ4e-1XM7cxHu2ywewNryiLi3mRQkErDPW4_U593uC5J1bKJipIGQRkAYrX6F6LEqJRWdjJIk9SaW5gwUjpzeia4ePeDJai1Y/s1600/Screen+Shot+2015-03-01+at+5.00.48+PM.png" height="210" width="320" /></a>Making this even worse is that the documentation from Facebook is not very good. It is quite extensive, but just not good. There are very few examples, and they are hard to find. The one I found that ended up helping me the most was the sample game they have, FriendSmash. They have a complete sample project for both <a href="https://github.com/fbsamples/ios-friend-smash-v2">iOS</a> and <a href="https://github.com/fbsamples/android-friend-smash-v2">Android</a>.<br />
<br />
I should mention that this problem is made even more complex for me because I'm also using Xamarin's Facebook SDK wrapper, which is available in separate versions for <a href="https://components.xamarin.com/view/facebookios">iOS</a> and <a href="http://components.xamarin.com/view/facebookandroid">Android</a>. From my perspective Xamarin would ideally provide a single library for Facebook integration that has a consistent API, and that there are platform specific layers to have that library communicate with Facebook's iOS and Android libraries.<br />
<br />
<h2>
One Library to Rule Them All</h2>
To make my life easier, this is exactly what I set out to create. I'm currently only using a small subset of all Facebook functionality. Basically I only need to: login and logout, retrieve the user's profile image, and post a <a href="https://developers.facebook.com/docs/opengraph">custom story</a>. There were two key reasons I wanted this single cross-platform library. One was because I also plan to integrate other social networks, at least Twitter and maybe others. Ideally I can interface with these via a consistent API. The second was because most of my code is platform agnostic and can't call any platform specific functions. Creating this library allows me to perform these Facebook actions from within my code.<br />
<br />
To support this I created the following abstract class:<br />
<pre class="prettyprint scrollable">using System;
namespace Qythyx.Infrastructure.SocialNetwork
{
/// <summary>Provides functionality related to a social network platform.</summary>
public abstract class SocialNetworkConnector
{
private bool _isLoggedIn;
/// <summary>Initializes a new instance of <see cref="SocialNetworkConnector"/>.</summary>
protected SocialNetworkConnector()
{
Login(false, _ => {});
}
/// <summary>Gets a value indicating whether this <see cref="SocialNetworkConnector"/> is displaying dialog.</summary>
/// <value><c>true</c> if displaying a dialog; otherwise, <c>false</c>.</value>
public bool DisplayingDialog { get; private set; }
/// <summary>Logs in to the social network and then calls the specified callback.</summary>
/// <param name="showUI">Determines if the login UI should be shown or not.</param>
/// <param name="callback">An action that is called after the login completes
/// with either <c>true</c> if the login was successful; otherwise <c>false</c>.</param>
public void Login(bool showUI, Action<bool> callback)
{
DisplayingDialog = showUI ? true : false;
PerformLogin(
showUI,
success =>
{
if(!success)
{
Logout();
}
IsLoggedIn = success;
callback(success);
DisplayingDialog = false;
}
);
}
/// <summary>Logs in to the social network and then calls the specified callback.</summary>
/// <param name="showUI">Determines if the login UI should be shown or not.</param>
/// <param name="callback">An action that is called after the login completes
/// with either <c>true</c> if the login was successful; otherwise <c>false</c>.</param>
protected abstract void PerformLogin(bool showUI, Action<bool> callback);
/// <summary>Logs out of the social network.</summary>
public void Logout()
{
PerformLogout();
IsLoggedIn = false;
}
/// <summary>Logs out of the social network.</summary>
protected abstract void PerformLogout();
/// <summary>Gets or sets a value indicating whether the user is logged in to the social network.</summary>
/// <value><c>true</c> if logged in; otherwise, <c>false</c>.</value>
public bool IsLoggedIn
{
get
{
return _isLoggedIn;
}
set
{
_isLoggedIn = value;
if(value)
{
GetUserImage(
(success, userImage) =>
{
UserImage = userImage;
if(LoggedInStateChangeEventHandler != null)
{
LoggedInStateChangeEventHandler(true, UserImage);
}
}
);
}
else
{
if(LoggedInStateChangeEventHandler != null)
{
LoggedInStateChangeEventHandler(false, null);
}
}
}
}
/// <summary>The user's image.</summary>
public ShareDetails UserImage { get; private set; }
/// <summary>Occurs when logged in state changes.
/// The <c>bool</c> determines if the user is logged in.
/// In general if the user is logged in then the <see cref="ShareDetails"/> should hold the user's image,
/// but if there was a problem retrieving it then this will be <c>null</c>.
/// </summary>
public event Action<bool, ShareDetails> LoggedInStateChangeEventHandler;
/// <summary>Attempts to login if necessary and, if logged in, performs the given action.</summary>
/// <param name="action">The action to perform if login is successful.</param>
public void ConfirmLoginAndDoAction(Action action)
{
if(!IsLoggedIn)
{
Login(true, success => { if(success) action(); });
}
else
{
action();
}
}
/// <summary>Retrieves the user's profile image from the social network and, once it is retrieved, passes it to the callback.
/// The callback is passed a boolean denoting if retrieval was successful and the image information. The image format is RGBA.
/// If retrieval fails the image data will be <c>null</c>.</summary>
/// <param name="callback">A callback that is called after retrieval completes.</param>
protected abstract void GetUserImage(Action<bool, ShareDetails> callback);
/// <summary>Shares the details of completing a level to the social network.</summary>
/// <param name="details">The <see cref="ShareDetails"/> describing the accomplishment.</param>
public abstract void ShareLevelCompletion(ShareDetails details);
}
}</pre>
This defines all of the functionality I need for Facebook. Unfortunately, to implement this for iOS and Android takes quite a bit of code, and as I said above, it's quite different for each platform. Also, unfortunately, to hook in the functionality for each platform requires bits and pieces of code or configuration in multiple files, which is kind of a pain. I'll try to explain what I did to get this working on both platforms.<br />
<h2>
iOS</h2>
<h3>
AppDelegate.cs</h3>
You need to initialize the Facebook library during you app startup and the recommended place to do that is in the <code class="prettyprint">AppDelegate.FinishedLaunching</code> method by adding the following. You'll also need to add <code class="prettyprint">using MonoTouch.FacebookConnect;</code> to this file.
<br />
<pre class="prettyprint scrollable">FBSettings.DefaultAppID = "<u><i>your Facebook app id</i></u>";
FBSettings.DefaultDisplayName = "<u><i>your Facebook app display name</i></u>";</pre>
So, where does that app id come from? Well, that's another important piece of setting up Facebook integration. This is explained in Facebook's "getting started" documentation for <a href="https://developers.facebook.com/docs/ios/getting-started/#appid">iOS</a> and <a href="https://developers.facebook.com/docs/android/getting-started">Android</a>, although reading through the instructions left me with a lot of questions that took a while to finally resolve.<br />
<br />
<h3>
Info.plist</h3>
Next you need to add your app ID to your <code class="prettyprint">Info.plist</code> file. This is also explained in Facebook's <a href="https://developers.facebook.com/docs/ios/getting-started#configurePlist">documentation</a>, although it also could be clearer.<br />
<br />
<h3>
FacebookConnector.cs</h3>
After this I needed to implement the iOS version of my <code class="prettyprint">SocialNetworkConnector</code>:
<br />
<pre class="prettyprint scrollable">using CoreGraphics;
using Foundation;
using MonoTouch.FacebookConnect;
using System;
using System.Collections.Generic;
using System.Globalization;
using UIKit;
using Qythyx.Infrastructure.Extensions;
using Qythyx.Infrastructure.SocialNetwork;
namespace Qythyx.ZoingiOS
{
/// <summary>Provides functionality related to a Facebook.</summary>
public sealed class FacebookConnector : SocialNetworkConnector
{
private static readonly string[] _ExtendedPermissions = new [] { "user_about_me", "publish_actions" };
/// <summary>Initializes a new instance of <see cref="FacebookConnector"/>.</summary>
public FacebookConnector()
: base()
{
}
/// <summary>Logs in to the social network and then calls the specified callback.</summary>
/// <param name="showUI">Determines if the login UI should be shown or not.</param>
/// <param name="callback">An action that is called after the login completes
/// with either <c>true</c> if the login was successful; otherwise <c>false</c>.</param>
protected override void PerformLogin(bool showUI, Action<bool> callback)
{
FBSession.OpenActiveSession(
_ExtendedPermissions,
showUI,
new SessionStateHandler(callback).FBSessionStateHandler
);
}
/// <summary>Logs out of the social network.</summary>
protected override void PerformLogout()
{
FBSession.ActiveSession.CloseAndClearTokenInformation();
}
/// <summary>Retrieves the user's profile image from the social network and, once it is retrieved, passes it to the callback.
/// The callback is passed a boolean denoting if retrieval was successful and the image information. The image format is RGBA.
/// If retrieval fails the image data will be <c>null</c>.</summary>
/// <param name="callback">A callback that is called after retrieval completes.</param>
protected override void GetUserImage(Action<bool, ShareDetails> callback)
{
FBRequest.ForMe.Start(
new RequestHandler(
(success, result) =>
{
ShareDetails details = null;
if(success)
{
FBGraphObject userInfo = (FBGraphObject)result;
string imageUrl = String.Format(
CultureInfo.InvariantCulture,
"https://graph.facebook.com/{0}/picture?width={1}&height={1}",
userInfo["id"].ToString(),
Zoing.Constants.SocialUserImageSize
);
details = GetFacebookUserImage(imageUrl);
}
callback(success, details);
}
).FBRequestHandler
);
}
private static ShareDetails GetFacebookUserImage(string imageUrl)
{
try
{
NSData data = NSData.FromUrl(new NSUrl(imageUrl));
using(CGImage image = UIImage.LoadFromData(data).CGImage)
{
int scaledWidth = ((Int32)image.Width).NearestSmallerPowerOf2();
int scaledHeight = ((Int32)image.Height).NearestSmallerPowerOf2();
byte[] pixels = new byte[scaledWidth * scaledHeight * 4];
using(CGBitmapContext context = new CGBitmapContext(pixels, scaledWidth, scaledHeight, 8, 4 * scaledWidth, CGColorSpace.CreateDeviceRGB(), CGBitmapFlags.NoneSkipLast))
{
context.DrawImage(new CGRect(0, 0, scaledWidth, scaledHeight), image);
return new ShareDetails(String.Empty, pixels, scaledWidth, scaledHeight);
}
}
}
catch(Exception)
{
return null;
}
}
private static void StageImage(UIImage image, Action<bool, string> completionAction)
{
// Using FBRequestConnection.UploadStagingResource fails with a cast exception, but the following works.
FBRequest request = FBRequest.ForUploadStagingResource(image);
request.Start(
new RequestHandler(
(success, result) => completionAction(success, success ? ((FBGraphObject)result)["uri"].ToString() : null)
).FBRequestHandler
);
}
/// <summary>Shares the details of completing a level to the social network.</summary>
/// <param name="details">The <see cref="ShareDetails"/> describing the accomplishment.</param>
public override void ShareLevelCompletion(ShareDetails details)
{
ObjCRuntime.Class.ThrowOnInitFailure = false;
UIImage image = CreateUIImage(details);
StageImage(
image,
(success, url) =>
{
if(success)
{
ShareLevelCompletion(details.Message, image, url);
}
}
);
}
private static UIImage CreateUIImage(ShareDetails details)
{
const int facebookSize = Qythyx.Zoing.Constants.FacebookImageSize;
CGBitmapContext context = new CGBitmapContext(null, facebookSize, facebookSize, 8, 0, CGColorSpace.CreateDeviceRGB(), CGBitmapFlags.NoneSkipLast);
using(
CGImage image = new CGImage(
details.Width,
details.Height,
8,
32,
details.Width * 4,
CGColorSpace.CreateDeviceRGB(),
CGBitmapFlags.None,
new CGDataProvider(details.Image, 0, details.Image.Length),
null,
false,
CGColorRenderingIntent.Default
)
)
{
double scale = Math.Max((double)details.Width / facebookSize, (double)details.Height / facebookSize);
int scaledWidth = (int)(details.Width / scale);
int scaledHeight = (int)(details.Height / scale);
int left = (facebookSize - scaledWidth) / 2;
int top = (facebookSize - scaledHeight) / 2;
context.InterpolationQuality = CGInterpolationQuality.High;
context.DrawImage(new CGRect(left, top, scaledWidth, scaledHeight), image);
}
return new UIImage(context.ToImage());
}
private static void ShareLevelCompletion(string title, UIImage image, string imageUrl)
{
// From https://developers.facebook.com/docs/applinks/hosting-api
const string url = "https://fb.me/_____your app's URL, see above link____";
var fbOpenGraphObject = FBGraphObject.OpenGraphObjectForPost(
"zoing-game:game_level",
title,
new NSString(imageUrl),
new NSString(url),
Qythyx.Zoing.Constants.ZoingDescription
);
FBGraphObject graphObject = (FBGraphObject)ObjCRuntime.Runtime.GetNSObject(fbOpenGraphObject.Handle);
var graphAction = FBGraphObject.OpenGraphAction;
graphAction.SetObject(new NSString("game_level"), "preview property");
graphAction.SetObject(graphObject, "game_level");
graphAction.SetObject(new NSString("true"), "fb:explicitly_shared");
graphAction.SetObject(image, "image");
FBOpenGraphActionParams fbParams = new FBOpenGraphActionParams(graphAction, "me/zoing-game:complete", "game_level");
if(FBDialogs.CanPresentShareDialog(fbParams))
{
FBDialogs.PresentShareDialog(
graphAction,
"zoing-game:complete",
"game_level",
(call, results, error) =>
{
// do nothing, either the share was successful or not, nothing to do about it
}
);
}
else
{
Dictionary<string, object> webParameters = new Dictionary<string, object>() {
{ "name", title },
{ "caption", "Zoing" },
{ "description", Qythyx.Zoing.Constants.ZoingDescription },
{ "link", url },
{ "picture", "https://sites.google.com/a/qythyx.com/www/Zoing%20Icon%20180x180.png" },
};
var s = GetNSDictionary(webParameters)["picture"];
// FBWebDialogs.PresentDialogModally failed with an exception, so using this instead.
FBWebDialogs.PresentFeedDialogModally(
FBSession.ActiveSession,
GetNSDictionary(webParameters),
(result, resultsUrl, error) => { /* do nothing */ }
);
}
}
private static NSDictionary GetNSDictionary(Dictionary<string,object> dictionary)
{
NSMutableDictionary nsd = new NSMutableDictionary();
foreach(var entry in dictionary)
{
nsd.Add(new NSString(entry.Key), NSObject.FromObject(entry.Value));
}
return nsd;
}
private class RequestHandler
{
private readonly Action<bool, NSObject> _completionHandler;
public RequestHandler(Action<bool, NSObject> completionHandler)
{
_completionHandler = completionHandler;
}
public void FBRequestHandler(FBRequestConnection connection, NSObject result, NSError error)
{
#if DEBUG
if(error != null)
{
Console.WriteLine("Facebook Request Error: " + error.ToString());
if(connection.UrlResponse != null)
{
Console.WriteLine("Facebook URLResponse: " + connection.UrlResponse.ToString());
}
}
#endif
_completionHandler(error == null, result);
}
}
private sealed class SessionStateHandler
{
private readonly Action<bool> _action;
public SessionStateHandler(Action<bool> action)
{
_action = action;
}
public void FBSessionStateHandler(FBSession session, FBSessionState state, NSError error)
{
#if DEBUG
if(error != null)
{
Console.WriteLine("Facebook Request Error: " + error.ToString());
}
#endif
_action(SessionStateIsLoggedIn(state));
}
}
private static bool SessionStateIsLoggedIn(FBSessionState state)
{
return state != FBSessionState.ClosedLoginFailed && state != FBSessionState.Closed && state != FBSessionState.CreatedOpening;
}
}
}</pre>
<h3>
Hooking It All Together</h3>
Obviously there's a bit more to it than the above. For example, attaching something to the base class's <code class="prettyprint">Info.plist</code>LoggedInStateChangeEventHandler to actually use the user's image that is returned. All of that stuff will be different and specific to each app and I can't document exactly how everyone should use this.<br />
<br />
Anyway, I hope this helps someone else. I'll try to document the Android version soon. It's a bit more complex, but does implement my consistent abstract class, and after hooking everything up correctly, works pretty well.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-91798285717204458002015-02-01T19:15:00.001+09:002015-02-01T19:15:38.202+09:00Building for ARM64<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgACozmzS4gX5xHBtwDMYTYi0Ic6Ibj03lvB8KYcDxYSZzZQpy3IJm9ph5Cx1ufnexTT5ugWXZCRpMyQgvGBR4sOXg3VSUoXr3AhtsjhB1LqWGmt8tfCFzyR8BnCwl6Tx4IOEdADf4Ci5wT/s1600/Zoing+ARM64+Benchmark.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgACozmzS4gX5xHBtwDMYTYi0Ic6Ibj03lvB8KYcDxYSZzZQpy3IJm9ph5Cx1ufnexTT5ugWXZCRpMyQgvGBR4sOXg3VSUoXr3AhtsjhB1LqWGmt8tfCFzyR8BnCwl6Tx4IOEdADf4Ci5wT/s1600/Zoing+ARM64+Benchmark.png" height="206" width="400" /></a></div>
Now that I <a href="http://blog.qythyx.com/2015/02/finally-android-is-published.html">finally released the Android version</a> of Zoing and Zoing Zero to the Google Play Store, I now can finally start working on some new features. But, before doing that I need to provide 64 bit support, as <a href="https://developer.apple.com/news/?id=10202014a">described by Apple</a>. Fortunately, <a href="http://xamarin.com/">Xamarin</a> makes this <a href="http://developer.xamarin.com/guides/cross-platform/macios/unified/">pretty easy</a>. I recently updated my game to the Unified API, which includes support for ARM 64, and was entirely painless.<br />
<br />
Aside from the requirement to do this, I also wondered how much of a performance improvement it might give me. I benchmarked Zoing built for ARM7, ARM7s, and ARM64. All of these runs were done on my iPhone 6 Plus.<br />
<br />
Clearly the build that includes ARM64 does provide a huge performance improvement (average FPS of 622 compared to a best of 482 for previous builds). I find it interesting that the ARMv7s does not provide any benefit of ARMv7, but again this was testing on a iPhone 6 Plus, which contains the ARM64. Perhaps if I tested on an iPhone 5S I would have seen something different.<br />
<br />
I should also explain these FPS numbers. iOS actually limits OpenGL animations to a maximum of 60 FPS. For the purposes of this benchmark I run my game's logic loop as fast as I can, but show the screen only once every 1,000 logic-loop cycles. This allows me to benchmark my code's efficiency as well as these CPU changes.<br />
<br />
I will try to blog a little more often going forward, but I've said that before too. ;-)Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com2tag:blogger.com,1999:blog-4882408034198003350.post-46827219825293672762015-02-01T19:14:00.002+09:002015-03-02T19:04:22.950+09:00Finally, Android Is Published!<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWS2RpMhgYYxP0uxmNjR_IEa8eF_b_fuK0VfiSGAubU_YjT7bj9nOvQ4WRjafjCvItOisM9ENVhx8mHPkTmMOrU-Vk38fiNZziABDuyQm67xN-SEncmye4jcPXoUI-mzhYJosEiQVh2S5g/s1600/Android+Phone.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWS2RpMhgYYxP0uxmNjR_IEa8eF_b_fuK0VfiSGAubU_YjT7bj9nOvQ4WRjafjCvItOisM9ENVhx8mHPkTmMOrU-Vk38fiNZziABDuyQm67xN-SEncmye4jcPXoUI-mzhYJosEiQVh2S5g/s1600/Android+Phone.png" height="400" width="225" /></a></div>
Sorry for the long hiatus in writing, all I can say is that I've been busy. The good news is that I have nice results from that busyness. I've finally released the Android version of Zoing. There are two options: <a href="https://play.google.com/store/apps/details?id=com.qythyx.zoing">Zoing for $0.99</a> or <a href="https://play.google.com/store/apps/details?id=com.qythyx.zoingzero">Zoing Zero for free with ads</a>.<br />
<br />
Now that I've finished the Android version I hope to be able to iterate on adding to features to both iOS and Android pretty quickly. Keep watching this blog or the new <a href="https://www.facebook.com/ZoingGame">Zoing page on Facebook</a> for news. I'm also tweeting under <a href="https://twitter.com/ZoingGame">ZoingGame</a>, so feel free to follow me there as well.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-3645873989498319342014-06-01T22:40:00.002+09:002014-06-01T22:40:30.083+09:00Back to OptimizingIt's been a while since <a href="http://0.0.7.222/04/andmy-first-appstore-update.html">I lost wrote, </a>but I have a good excuse, I've been focusing on creating the Android version of my game. The good news is that everything is working now. It's running on both Android 2.3+ and a slightly improved version for Android 4.4+ (it shows in full-screen). What I'm working on now is optimizing it to get the performance I want. I've written <a href="http://0.0.7.219/01/more-optimizations.html">articles about optimization in the past</a>, so I'll keep this short and to the point of what I've done so far.<br />
<h2>
Measure, Measure, Measure</h2>
I've written about this in the past, but I've found that when you're trying to optimize something it is extremely important to measure often and enough. Also, optimizing for a focused measurement doesn't always result in improvements in the real environment.<br />
<br />
To try to do a better job at measuring I've been using histograms of the speed of what I'm measuring to get a better picture than just an average speed. I'm also using the profiler functionality in Visual Studio, but I can only use that for the PC version of my game, not directly against the Android binary. Still this led to useful insight.<br />
<h2>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCG1HxcgWqViYvo3tHDKPFqQ2OuTluJhWgMhBWn7bAidodOBXx3UbVssSr9uelNR5aevFQegqF0lD4rSYOc9GEwsKmdyhDJzO0G0Ia47yOJLq7_Ja4dLkhmhgv0fkv2luwNnyl37RRaMXP/s1600/image+(6).png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCG1HxcgWqViYvo3tHDKPFqQ2OuTluJhWgMhBWn7bAidodOBXx3UbVssSr9uelNR5aevFQegqF0lD4rSYOc9GEwsKmdyhDJzO0G0Ia47yOJLq7_Ja4dLkhmhgv0fkv2luwNnyl37RRaMXP/s1600/image+(6).png" height="246" width="400" /></a></div>
Two Optimizations</h2>
When I profiled my code I found two areas I thought I could improve relatively easily. The first was an unnecessary calculation when I manipulate (translate or rotate) quads. I was recalculating the center of the quads even though for these operations that wasn't necessary. This was pretty straight-forward to improve and resulted in a significant speedup. You can see this in the picture to the right: PC-L-SS is the original version, and PC-L-SS-Opt Mutable is this first optimization.<br />
<br />
After the above I ran the profiler again and determined that a significant amount of time was being spent on calculating sine and cosine operations. I did some Google searching and found an <a href="http://gamedev.stackexchange.com/questions/4779/is-there-a-faster-sine-function">interesting discussion</a> that describes a faster way to calculate sine (the <a href="http://web.archive.org/web/20111104163237/http://www.devmaster.net/forums/showthread.php?t=5784">source of this information</a> contains more details). So, I implemented this alternative sine calculation and measured again. This resulted in the PC-L-SS-FastSin numbers in the histogram.<br />
<br />
The average FPS numbers for the above are: original = 1,114, first optimization = 1,380, and second optimization = 1,411. Note that this is just running my game logic code, but not actually rendering via OpenGL. I did this in order to focus as much as possible on the area I was optimizing.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigzcVuwuhVixvqEwUrEV4cy-on4Cy6Et6qqlUtWYbk0GGYbUarf0dKjm3vngwm1NabCEG4mAKMMjsO36MgmbNo7M2tPfidAXr1iAJCra3blfuiEmjHqmZFd7amc3RgJwG3R19sbLb11uhn/s1600/image+(7).png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigzcVuwuhVixvqEwUrEV4cy-on4Cy6Et6qqlUtWYbk0GGYbUarf0dKjm3vngwm1NabCEG4mAKMMjsO36MgmbNo7M2tPfidAXr1iAJCra3blfuiEmjHqmZFd7amc3RgJwG3R19sbLb11uhn/s1600/image+(7).png" height="246" width="400" /></a></div>
<h2>
But What About Android?</h2>
So, the above is interesting and all, but I'm trying to optimize Android, not my PC version. So, since I can't use the profiler, I can just run the same measurements. This resulted in the histogram to the right.<br />
<br />
Clearly from this both the first optimization (Opt Mutable) and the second additional optimization (Opt Sine) are significantly faster than the original. To summarize this even more, the average FPS for each are: original = 37.29, first optimization = 42.95, second optimization = 43.45. In this case the numbers are both the game logic and the actual rendering. Because I'm trying to evaluate the true game performance, in my attempt to get to 60 FPS, I thought this was more useful.<br />
<h2>
Still More Work to Do</h2>
While I'm pretty happy with the results of this initial optimization, it's still not performing as well as I want. The Android measurements above were done on a Nexus 4, and I'd really like it to run at 60 FPS with some headroom for slower devices. My profiler investigations have given me some ideas for more optimizations, but I expect them to be more difficult than the above.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-62499320791236576502014-04-16T14:27:00.001+09:002014-04-16T14:27:18.208+09:00And...my first AppStore update<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglMR2ZmyypEajsOrwZ-NCxaCtrKuwlAIwKN7lS75I99Yysh50_TfFOPwHlQ64vbw75QaydUpmrzuK0Vwzmn85S8SD58WgbKKMaGZJj-d2J3lln-C8kE_RYfjJ7B4RSiti-Xyxm7Ms9P9Di/s1600/2014-04-16+14.18.46.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglMR2ZmyypEajsOrwZ-NCxaCtrKuwlAIwKN7lS75I99Yysh50_TfFOPwHlQ64vbw75QaydUpmrzuK0Vwzmn85S8SD58WgbKKMaGZJj-d2J3lln-C8kE_RYfjJ7B4RSiti-Xyxm7Ms9P9Di/s1600/2014-04-16+14.18.46.png" height="320" width="180" /></a>
<p>I pushed a minor update to Zoing to the AppStore on Saturday and it just went live. It's not particularly interesting in terms of changes to the game, but it's exciting for me given this is my first one ever.</p>
<p>The actual update just rearranges the icons on the start screen to try to make it easier to use. Now I'm working on Android support, so I don't expect any new iOS updates until after the Android version is released. Although, before then, I expect the ad-supported free version for iOS to become available.</p>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-39514647784464394662014-04-14T22:01:00.001+09:002014-04-16T14:38:19.785+09:00Released!<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZMLztRT15hhhZkIEiwZEv3HU6A3K0snHvMXtidkaXXwcadzb6yGVCLLSYzu6o8K2Hg_SRwgTaA5vQ7dZkr2eHGlz-7OYVz7CHHC8kJakJ2lZ5f5DljtM11E7eDo-05YnsbaigCxHCcRHS/s1600/640x1136+copy.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZMLztRT15hhhZkIEiwZEv3HU6A3K0snHvMXtidkaXXwcadzb6yGVCLLSYzu6o8K2Hg_SRwgTaA5vQ7dZkr2eHGlz-7OYVz7CHHC8kJakJ2lZ5f5DljtM11E7eDo-05YnsbaigCxHCcRHS/s1600/640x1136+copy.jpg" height="320" width="180" /></a></div>
<span style="font-family: inherit;">This post is a little delayed, but I finally released <a href="https://itunes.apple.com/ca/app/zoing/id847181024?mt=8">Zoing to the App Store</a> about two weeks ago! It's available in all countries. Please check it out and write a nice review, 5 stars would also be great.</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">I also just submitted the free, ad-supported version to the App Store two days ago. Apple typically takes about 10 days to review new apps, so I'll announce it here when that's available. It's exactly the same game, but shows ads on some of the screens, although not on the main game screens.</span>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-18125806898751268212014-02-10T15:00:00.000+09:002014-02-10T15:01:26.606+09:00Who stole my pixels?<p>Moving away from the overly detailed <a href="/2014/02/alpha-layering.html">discussion on alpha layering from my last post</a>, today I want to briefly discuss developing for iOS and taking advantage of the full resolution of retina devices. Although I knew some care was needed in order to use the full high-resolution retina display, for some reason I assumed this was already happening. Recently, however, I realized that that wasn't true. A little Googling found lot's of info on this and it all comes down to scaling between points and pixels.</p>
<h2>Scaling the heights</h2>
<p>There are many discussions on this topic, so I won't go into too much detail, but I did want to mention how I addressed the problem. Briefly, for those who don't know, when Apple released their first high-resolution retina devices they did it in a careful way that allowed all existing apps to still run and look good. Basically they added a new property of the screen that defines what it's scale is. The official Apple documentation for <a href="https://developer.apple.com/library/ios/documentation/uikit/reference/UIScreen_Class/Reference/UIScreen.html#//apple_ref/occ/instp/UIScreen/scale">UIScreen.Scale</a> says:</p>
<blockquote>
This value reflects the scale factor needed to convert from the default logical coordinate space into the device coordinate space of this screen. The default logical coordinate space is measured using points. For standard-resolution displays, the scale factor is 1.0 and one point equals one pixel. For Retina displays, the scale factor is 2.0 and one point is represented by four pixels.
</blockquote>
<p>To use this scale I needed to do three things:</p>
<ol>
<li>Set the <code class="prettyprint">ContentScaleFactor</code> of my <code class="prettyprint">EAGLView</code>, which is a subclass of <code class="prettyprint">iPhoneOSGameView</code> to the same scale so that my rendering uses the entire high-resolution screen</li>
<li>Tell my game the full high-resolution size of the screen</li>
<li>Multiply all of my coordinates by the defined scale factor</li>
</ol>
<p>For the first item I both get and remember the scale factor and set the <code class="prettyprint">ContentScaleFactor</code> to it in the initialization of my <code class="prettyprint">EAGLView</code>. Because I want my game to run on older devices with older versions of iOS I need to first check that the new <code class="prettyprint">scale</code> property exists and then only reference it if it does. The purpose of <code class="prettyprint">ContentScaleFactor</code> is defined by Apple as:</p>
<div class="widget" style="float: right; width: 15%; margin: 0px; padding: 0 15px 0 15px;"><p>Hmm, reading over this now I just noticed that it says I shouldn't have to adjust the value of <code class="prettyprint">ContentScaleFactor</code>. I'll need to go back and look at my code again.</p></div>
<blockquote>
<p>The scale factor determines how content in the view is mapped from the logical coordinate space (measured in points) to the device coordinate space (measured in pixels). This value is typically either <code>1.0</code> or <code>2.0</code>. Higher scale factors indicate that each point in the view is represented by more than one pixel in the underlying layer. For example, if the scale factor is <code>2.0</code> and the view frame size is 50 x 50 points, the size of the bitmap used to present that content is 100 x 100 pixels.</p><p>The default value for this property is the scale factor associated with the screen currently displaying the view. If your custom view implements a custom <code><a href="https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/uiview/uiview.html#//apple_ref/occ/instm/UIView/drawRect:">drawRect:</a></code> method and is associated with a window, or if you use the <code><a href="https://developer.apple.com/library/ios/documentation/GLkit/Reference/GLKView_ClassReference/Reference/Reference.html#//apple_ref/occ/cl/GLKView">GLKView</a></code> class to draw OpenGL ES content, your view draws at the full resolution of the screen. For system views, the value of this property may be <code>1.0</code> even on high resolution screens.</p><p>In general, you should not need to modify the value in this property. However, if your application draws using OpenGL ES, you may want to change the scale factor to trade image quality for rendering performance. For more information on how to adjust your OpenGL ES rendering environment, see <a href="https://developer.apple.com/library/ios/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/ImplementingaMultitasking-awareOpenGLESApplication/ImplementingaMultitasking-awareOpenGLESApplication.html#//apple_ref/doc/uid/TP40008793-CH5-SW6">“Supporting High-Resolution Displays”</a> in <em><a href="https://developer.apple.com/library/ios/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008793">OpenGL ES Programming Guide for iOS</a></em>.</p>
</blockquote>
<p>Here's the code that does that:</p>
<pre class="prettyprint scrollable">
_screenScale = 1;
if(UIScreen.MainScreen.RespondsToSelector(new Selector("scale"))
&& RespondsToSelector(new Selector("contentScaleFactor")))
{
_screenScale = ContentScaleFactor = UIScreen.MainScreen.Scale;
}
</pre>
<p>For the second item I simply multiply the screen size by the scale I determined above via this code snippet: <code class="prettyprint scrollable">new Size(Frame.Size.Width, Frame.Size.Height) * _screenScale</code>.</p>
<p>Finally, for the third item, I perform another multiplication of the scale when I handle touch events.</p>
<pre class="prettyprint scrollable">
/// <summary>Called when touch events end.</summary>
/// <param name="touches">Touches.</param>
/// <param name="e">The touch event.</param>
public override void TouchesEnded(NSSet touches, UIEvent e)
{
base.TouchesEnded(touches, e);
UITouch touch = (UITouch)touches.AnyObject;
_lastTouch = touch.LocationInView(this);
_screenManager.Screen.HandleTouch(new Point(_firstTouch.X, _firstTouch.Y) * _screenScale, new Point(_lastTouch.X, _lastTouch.Y) * _screenScale, true);
}
/// <summary>Called when a touch event moves.</summary>
/// <param name="touches">Touches.</param>
/// <param name="e">The touch event.</param>
public override void TouchesMoved(NSSet touches, UIEvent e)
{
UITouch touch = (UITouch)touches.AnyObject;
_lastTouch = touch.LocationInView(this);
_screenManager.Screen.HandleTouch(new Point(_firstTouch.X, _firstTouch.Y) * _screenScale, new Point(_lastTouch.X, _lastTouch.Y) * _screenScale, false);
}
</pre>
<p>All of the above code happens in my iOS specific <code class="prettyprint">EAGLView</code> class and the rest of my code needed no adjustments.</p>
<p>Next: coming soon...</p>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-63415616916053732352014-02-07T17:26:00.001+09:002014-02-18T23:19:59.770+09:00Alpha layering<script src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" type="text/javascript"></script>
<p>In a <a href="/2014/01/premultiplied-alpha.html">recent post I discussed premultiplied alpha in textures</a>. After that I thought I had my alphas under control, but it turns out I was wrong.</p>
<h2>Keeping score</h2>
<p>Early in my game development I came up with a slightly interesting way to render text. In the game, text is used only to show the game score and is generally quite large on the screen. The score advances every frame, or 1/60<sup>th</sup> of a second. Because that advancement would actually change too quickly for the eye to see I actually truncate the last digit and only render the remaining ones. So if the score advances frame-by-frame as <code class="prettyprint">137 → 138 → 139 → 140 → 141</code>, I would actually render it as <code class="prettyprint">13 → 13 → 13 → 14 → 14</code>. Even doing this the rendered score visibly advances every 10 frames, or every 1/6<sup>th</sup> of a second. This is still pretty rapid, and I wanted to visually smooth that quickly changing score when it's rendered on the screen.</p>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhudOTofya4s1sjtbLm3rV7ISC3T9iyBidQOflB8OyD97VIuitI-YZSdBSFEvrD0kvkuwctxB1QtnxGG2HmYLu7PuEZTGX4I8k8zT2rJzdaiF45MnK5GtJO2uUXg2YCz4PeLxvrmyVEZiUi/s1600/Alpha+Text.png" imageanchor="1" style="float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhudOTofya4s1sjtbLm3rV7ISC3T9iyBidQOflB8OyD97VIuitI-YZSdBSFEvrD0kvkuwctxB1QtnxGG2HmYLu7PuEZTGX4I8k8zT2rJzdaiF45MnK5GtJO2uUXg2YCz4PeLxvrmyVEZiUi/s400/Alpha+Text.png" /></a>
<h2>50 shades of grey</h2>
<p>The idea I settled on was to keep track of the last N instances of the score each time a frame in rendered. In the above example, if N was 5, that would be the list of numbers shown. One frame later the list would change to <code class="prettyprint">13 → 13 → 14 → 14 → 14</code>, and 7 frames later it would become <code class="prettyprint">14 → 14 → 14 → 14 → 15</code>. After keeping track of these last N scores I then actually rendered them all with the oldest score being very transparent and each successive score being more and more opaque. This gives an effect like the one shown in the image to the right. Actually that image is showing the score rendered as it is also being rotated in order to make the separate layers easier to see, in most cases the score is not rotating, so each layer is stacked directly on top of the previous one.</p>
<p>This layered transparency effect worked out well and I've had it in place since quite early in the game development. The way I actually implemented this was to take my desired alpha and figure out how many fractional shades of that I would need such that when I layered them all together I would get my desired alpha. For example, for the score when it is shown during a game it is dimmed so as not to distract from game play and has an intended alpha of 0.25. After the game when showing the final score it is brightened to full opacity, or alpha 1.0. If I have 5 layers then I decided to break the desired alpha into 15 fractions <code class="prettyprint">1 + 2 + 3 + 4 + 5</code>. This is also \( \frac{N \times (N+1)}{2} \) or \( \frac{N^2 + N}{2} \). So, for the dimmed alpha of 0.25 I calculated the fractional alpha as \( \frac{0.25}{15} = 0.0167 \). Then I rendered the oldest score layer with an alpha of 0.0167, the second oldest as \( 0.0167 \times 2 = 0.0333 \), etc, up to the newest score as \( 0.0167 \times 5 = 0.0833 \). When all of these were layered on top of one another it produced the desired 0.25 alpha...or so I thought.</p>
<p>One problem I periodically ran into was that if I adjusted a parameter related to the transparency, like making the text slightly lighter or darker, or make the number of layers more or fewer, then the resulting aggregated color often ended up being not what I expected. Recently, and after improving my understanding of alpha blending as described in my previous post, I finally spent the time to dig into this and understand this properly. The problem was that my simple math was incorrect and does not represent how layered alphas work.</p>
<p>In the case of my text, I'm layering a pure white opaque texture onto some existing background. For the texture, being pure opaque white, each white pixel is <code class="prettyprint">(1, 1, 1, 1)</code>. Because I want to dim this, as described above, I adjust the alpha 0.0167 to get <code class="prettyprint">(1, 1, 1, 0.0167)</code>. To figure out how this blends with the background I return to the alpha formula I described in my previous post, which said regarding <code class="prettyprint">GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);</code>: for the source color use it as is; and for the destination color take the alpha component of the source, subtract that from one, and multiply that by each component of the destination.</p>
<h2>Diminishing returns</h2>
<p>Let's do the math. The source is <code class="prettyprint">(1, 1, 1, 0.0167)</code> and to keep things simple the destination is black, or <code class="prettyprint">(0, 0, 1, 1)</code>. So, <i>use the source component as is</i> means just keep <code class="prettyprint">(1, 1, 1, 0.0167)</code>. Next, <i>for the destination color, take the alpha component of the source, subtract that from one, and multiply that by each component of the destination</i> means we take the source alpha, 0.0167, subtract that from 1, \( 1 - 0.0167 = 0.9833 \), and multiply that by the destination, which gives us <code class="prettyprint">(0, 0, 0, 0.9833)</code>. Now, the actual screen doesn't have an alpha component, it just shows colors, so to get a real color we multiply the alpha by each component and then add the results, or: <code class="prettyprint">(1, 1, 1, 0.0167)</code> becomes <code class="prettyprint">(0.0167, 0.0167, 0.0167)</code> and <code class="prettyprint">(0, 0, 0, 0.98333)</code> becomes <code class="prettyprint">(0, 0, 0)</code> and adding those two gives us <code class="prettyprint">(0.0167, 0.0167, 0.0167)</code> or <span style="background: #040404;"> X </span>. That's very close to black, but that's ok, it's just 1 of 15 shades we're applying. Let's do another.</p>
<p>The source is the same, <code class="prettyprint">(1, 1, 1, 0.0167)</code>, or multiplied out as <code class="prettyprint">(0.0167, 0.0167, 0.0167)</code>. The destination is different this time, since we layering on top of what we just calculated, which is <code class="prettyprint">(0.0167, 0.0167, 0.0167, 0.9833)</code>, or multiplied out as <code class="prettyprint">(0.0164, 0.0164, 0.0164)</code>. Adding these gives us <code class="prettyprint">(0.0331, 0.0331, 0.0331)</code> or <span style="background: #080808;"> X </span>. Hmm, that still looks awfully black, but it's just two of 15 shades.</p>
<p>Now, for those of you paying attention, this actually isn't the shade we'd have at this point. I'm supposed to take the fractional alpha for the oldest text plus two of that fraction for the next oldest, which means at this point I should have three shades layered on one another, which would be a tad brighter black. But, actually, even that isn't true, and this is where we start getting to the heart of my problem. You do not get to the same color if you apply the alpha layer three times in succession compared to doing it once and then a second layer at twice the alpha. Let me explain.<br />
If I continued the above calculations for one more layer at the same fractional alpha I would end with a final color of <code class="prettyprint">(0.0492, 0.0492, 0.0492)</code>. On the other hand, if I took my first layer, <code class="prettyprint">(0.0167, 0.0167, 0.0167)</code>, and layered on top of it a double fractional shade <code class="prettyprint">(1, 1, 1, 0.0334)</code>, then I would result with <code class="prettyprint">(0.0497, 0.0497, 0.0497)</code>.</p>
<p>Now, 0.04918 and 0.04973 are not that different, but if I did this for all 15 layers the difference increases. Here's the complete table:</p>
<table border="0" cellpadding="2" cellspacing="0" style="border-collapse:collapse; border: solid 1px black;">
<colgroup>
<col style="border-right: solid 1px black;"></col>
<col style="border-right: solid 1px #dddddd;"></col>
<col style="border-right: solid 1px black;"></col>
<col style="border-right: solid 1px #dddddd;"></col>
<col style="border-right: solid 1px black;"></col>
<col style="border-right: solid 1px #dddddd;"></col>
<col></col>
</colgroup>
<tbody>
<tr>
<td></td>
<td colspan="2" style="text-align: center; border-bottom: solid 1px #dddddd;">Shade Applied</td>
<td colspan="2" style="text-align: center; border-bottom: solid 1px #dddddd;">Cumulative Total</td>
<td colspan="2" style="text-align: center; border-bottom: solid 1px #dddddd;">Calculated Color</td>
</tr>
<tr style="border-bottom: solid 1px black;">
<td style="text-align: center;">Layer</td>
<td style="text-align: center;">Single Layers</td>
<td style="text-align: center;">Increasing Shades</td>
<td style="text-align: center;">Single Layers</td>
<td style="text-align: center;">Increasing Shades</td>
<td style="text-align: center;">Single Layers</td>
<td style="text-align: center;">Increasing Shades</td>
</tr>
<tr>
<td align="right">1</td>
<td align="right">0.0167</td>
<td align="right">0.0167</td>
<td align="right">0.0167</td>
<td align="right">0.0167</td>
<td align="right">0.0167</td>
<td align="right">0.0167</td>
</tr>
<tr>
<td align="right">2</td>
<td align="right">0.0167</td>
<td align="right">0.0333</td>
<td align="right">0.0333</td>
<td align="right">0.0500</td>
<td align="right">0.0331</td>
<td align="right">0.0494</td>
</tr>
<tr>
<td align="right">3</td>
<td align="right">0.0167</td>
<td align="right">0.0500</td>
<td align="right">0.0500</td>
<td align="right">0.1000</td>
<td align="right">0.0492</td>
<td align="right">0.0970</td>
</tr>
<tr>
<td align="right">4</td>
<td align="right">0.0167</td>
<td align="right">0.0667</td>
<td align="right">0.0667</td>
<td align="right">0.1667</td>
<td align="right">0.0650</td>
<td align="right">0.1572</td>
</tr>
<tr>
<td align="right">5</td>
<td align="right">0.0167</td>
<td align="right">0.0833</td>
<td align="right">0.0833</td>
<td align="right" style="background: #ffffaa; font-weight: bold;">0.2500</td>
<td align="right">0.0806</td>
<td align="right" style="background: #ffffaa; font-weight: bold;">0.2274</td>
</tr>
<tr>
<td align="right">6</td>
<td align="right">0.0167</td>
<td></td>
<td align="right">0.1000</td>
<td></td>
<td align="right">0.0959</td>
<td></td>
</tr>
<tr>
<td align="right">7</td>
<td align="right">0.0167</td>
<td></td>
<td align="right">0.1167</td>
<td></td>
<td align="right">0.1110</td>
<td></td>
</tr>
<tr>
<td align="right">8</td>
<td align="right">0.0167</td>
<td></td>
<td align="right">0.1333</td>
<td></td>
<td align="right">0.1258</td>
<td></td>
</tr>
<tr>
<td align="right">9</td>
<td align="right">0.0167</td>
<td></td>
<td align="right">0.1500</td>
<td></td>
<td align="right">0.1404</td>
<td></td>
</tr>
<tr>
<td align="right">10</td>
<td align="right">0.0167</td>
<td></td>
<td align="right">0.1667</td>
<td></td>
<td align="right">0.1547</td>
<td></td>
</tr>
<tr>
<td align="right">11</td>
<td align="right">0.0167</td>
<td></td>
<td align="right">0.1833</td>
<td></td>
<td align="right">0.1688</td>
<td></td>
</tr>
<tr>
<td align="right">12</td>
<td align="right">0.0167</td>
<td></td>
<td align="right">0.2000</td>
<td></td>
<td align="right">0.1826</td>
<td></td>
</tr>
<tr>
<td align="right">13</td>
<td align="right">0.0167</td>
<td></td>
<td align="right">0.2167</td>
<td></td>
<td align="right">0.1963</td>
<td></td>
</tr>
<tr>
<td align="right">14</td>
<td align="right">0.0167</td>
<td></td>
<td align="right">0.2333</td>
<td></td>
<td align="right">0.2097</td>
<td></td>
</tr>
<tr>
<td align="right">15</td>
<td align="right">0.0167</td>
<td></td>
<td align="right" style="background: #ffffaa; font-weight: bold;">0.2500</td>
<td></td>
<td align="right" style="background: #ffffaa; font-weight: bold;">0.2228</td>
<td></td>
</tr>
</tbody>
</table>
<p>This shows two methods of adding the layers. Single Layers means layering the fractional alpha multiple times. Increasing Shades means doing that, but increasing the fraction for each layer. I've highlighted the interesting numbers. The Cumulative Total colums just show that in both methods the resulting simple-math total is what we're trying to get to, 0.25. The Calculated Color column shows what we actually end up with, which in both cases is less than our target, or 0.2228 in the case of Single Layers or 0.2274 in the case of Increasing Shades.</p>
<p>The reason both numbers don't add up to what we want is that each successive layer causes less of a change than the previous one. This is a case of diminishing returns. Because of this we need to do some math to calculate the fractional shade such that it takes into account these diminishing returns.</p>
<div style="float: right">
<table border="0" cellpadding="2" cellspacing="0" style="border-collapse:collapse; border: solid 1px black;">
<colgroup>
<col style="border-right: solid 1px black;"></col>
<col style="border-right: solid 1px #dddddd;"></col>
<col style="border-right: solid 1px black;"></col>
<col></col>
</colgroup>
<tbody>
<tr>
<td></td>
<td colspan="2" style="text-align: center; border-bottom: solid 1px #dddddd;">Calculated Color</td>
</tr>
<tr style="border-bottom: solid 1px black;">
<td>Layer</td>
<td>Single Layers</td>
<td>Increasing Shades</td>
</tr>
<td height="15" align="right" style="height:20px;">1</td>
<td align="right">0.0667</td>
<td align="right">0.0667</td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">2</td>
<td align="right">0.1289</td>
<td align="right">0.1911</td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">3</td>
<td align="right">0.1870</td>
<td align="right">0.3529</td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">4</td>
<td align="right">0.2412</td>
<td align="right">0.5255</td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">5</td>
<td align="right">0.2918</td>
<td align="right" style="background: #ffffaa; font-weight: bold;">0.6836</td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">6</td>
<td align="right">0.3390</td>
<td></td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">7</td>
<td align="right">0.3830</td>
<td></td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">8</td>
<td align="right">0.4242</td>
<td></td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">9</td>
<td align="right">0.4626</td>
<td></td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">10</td>
<td align="right">0.4984</td>
<td></td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">11</td>
<td align="right">0.5318</td>
<td></td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">12</td>
<td align="right">0.5630</td>
<td></td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">13</td>
<td align="right">0.5922</td>
<td></td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">14</td>
<td align="right">0.6194</td>
<td></td>
</tr>
<tr>
<td height="15" align="right" style="height:20px;">15</td>
<td align="right" style="background: #ffffaa; font-weight: bold;">0.6447</td>
<td></td>
</tr>
</tbody>
</table>
</div>
<h2>Warning! Math ahead!</h2>
<p>I'm going to shift our target to 1 instead of 0.25. This will make the diminishing returns problem more clear. Doing this, and following the same flawed layering strategy described above, the Calculated Color from the above table becomes as shown on the right. You can now see the totals are quite far for our target of 1. In fact, because of diminishing returns, no matter how many layers we add we'll never actually get to 100% opaque. If I extended the above to 100 layers the Single Layers color would be 0.999.</p>
<p>So, what I needed to do was to determine a different fractional alpha to use when layering that would add up to my desired target. To do this I converted my Single Layers iterative algorithm into a formula. First, the iterative version of this is below, where <i>fractional</i> is the fraction of my intended alpha, or \( \frac{1}{15} = 0.067 \).</p>
$$ f(n) = (1 - fractional) \times f(n-1) + fractional $$
<p>This calculates the final color for <i>n</i> where <i>n</i> is the number of layers. To convert this to a formula that does not reference itself I started by listing the successive values of this formula in <a href="http://spreadsheets.google.com">Google Spreadsheets</a>, just like the above tables, and then I played with it until I came up with the following equivalent formula.</p>
$$ color = 1 - (1 - fractional)^{layers} $$
<p>We can check this by plugging in our fractional alpha of \( \frac{1}{15} = 0.067 \) and our 15 layers as \( 1 - (1 - 0.067)^{15} = 0.645 \), which gives us the same value. So far so good, but what we want is a formula into which we supply the color and the number of layers and it gives us the fractional alpha. That means we want to get the <i>fractional</i> part of the formula on one side by itself. Time to remember how do to formula manipulation. First, get the exponent expression on a side by itself.</p>
$$ 1 - color = (1 - fractional)^{layers} $$
<p>Next, reverse the exponential equation.</p>
$$ (1 - color)^{ \frac{1}{layers} } = 1 - fractional $$
<p>Finally, get <i>fractional</i> by itself and positive.</p>
$$ fractional = 1 - (1 - color)^{ \frac{1}{layers} } $$
<p>Great, now we just plug in our target <i>color</i>, which was fully opaque white or 1, and keeping the <i>layers</i> as 15, we get...oops...hmm, \( 1 - 1 = 0 \) and \( 0^{ \frac{1}{15} } = 0 \), and \( 1 - 0 = 1 \), so the <i>fractional</i> is 1. Ack, we have a problem. This actually makes sense. Remember when I said that with the diminishing returns problem we can add the fractional alpha as many times as we want and never actually get to 100% opaque, well this is telling us the same thing. Actually, if the fractional alpha starts out as 1 then we'll get to 100% opaque, but then we don't get the successive alpha blending effect that we want.</p>
<p>Ok, time to cheat, although I want the final alpha to be 1 I'll settle with it being a single shade off of 1. Since there are 256 possible alpha shades I'll settle with \( \frac{255}{256} = 0.996 \). And plugging that into the formula I get \( 1 - (1 - 0.996)^{ \frac{1}{15} } = 0.308 \). If I layer that 15 times I get this succession of colors: <code class="prettyprint">0.308 → 0.521 → 0.669 → 0.771 → 0.841 → 0.890 → 0.924 → 0.947 → 0.964 → 0.975 → 0.983 → 0.988 → 0.992 → 0.994 → 0.996</code>.</p>
<p>This works and I've implemented this in code now, but it's not actually exactly what I want. This is the Single Layers algorithm, where really I want the Increasing Shades algorithm. The formula for that is more complex, though:</p>
$$ f(n) = \left[ \left(1 - \frac{ n^2 + n }{2} \times fractional \right) \times f(n-1) \right] + \frac{ n^2 + n }{2} \times fractional $$
<p>I've tried to convert this into a single formula that does not reference \( f(n-1) \), but have not been successful yet. For now I'll either stick with the previous formula or use the one above and calculate it recursively.</p>
<p>Next: <a href="/2014/02/who-stole-my-pixels.html">Who stole my pixels?</a></p>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-35131483729396747072014-01-28T13:47:00.002+09:002014-02-07T17:27:30.052+09:00A Tutorial; Step-by-step<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTloshoHREt5rtL5R70lTyk_jf3Ii6ZnQoUH_Ry8jCZfFIzHSrgBMVRM8nqpvODyVaZc_6XKdkafcVkmTfyc32IPJ6Bt4NrTtPSF4CtEzmuG3_lhywxmD0BiDvtQNHK2PIbBdkgm09cKY3/s1600/Finder.png" imageanchor="1" style="float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTloshoHREt5rtL5R70lTyk_jf3Ii6ZnQoUH_Ry8jCZfFIzHSrgBMVRM8nqpvODyVaZc_6XKdkafcVkmTfyc32IPJ6Bt4NrTtPSF4CtEzmuG3_lhywxmD0BiDvtQNHK2PIbBdkgm09cKY3/s320/Finder.png" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLVsWWucBEZGT8ONgduNrGo-1nfgTIejBMIu83mZKGIqZd17EwIXcsJdpi4booL73M8Sma3x_pc0F3U3Yk_gnx7p4T8GFMxExnX26CFr-489y4E3QGNNYGMJp4y16_FR-ZjZ8fGb7I_TZY/s1600/Touching.png" imageanchor="1" style="float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLVsWWucBEZGT8ONgduNrGo-1nfgTIejBMIu83mZKGIqZd17EwIXcsJdpi4booL73M8Sma3x_pc0F3U3Yk_gnx7p4T8GFMxExnX26CFr-489y4E3QGNNYGMJp4y16_FR-ZjZ8fGb7I_TZY/s320/Touching.png" /></a>
<p>I've been planning to add a tutorial to my game from the beginning, and finally got around to really working on it a few weeks ago. Similar to <a href="/2014/01/simple-solutions.html">my previous post about simple solutions</a>, I found that once I tinkered with ideas enough and came up with a clear idea of how to implement it, my tutorial code just fell into place quite smoothly and fit satisfyingly into the existing architecture.<p>
<h2>Making a point</h2>
<p>I decided that the best way to communicate how to play the game was to have an image of a finger moving on the screen indicating touch actions. You can see an example of this in the image to the right. I created the hand image by taking a photo of a hand (my eldest daughter's) and then manipulating it a bit to flatten the colors and such. In the tutorial it is opaque when touching the screen (the left image), or mostly transparent when not touching the screen (the right image).</p>
<p>After creating the finger I needed a way to move it around the screen easily and also initiate the touch events. For this I created a <code class="prettyprint">Finger</code> class that extends <code class="prettyprint">Symbol</code> (see <a href="/2014/01/simple-solutions.html">my previous post</a> for a discussion on this) and holds the image of the finger. This class also has new animation functionality mostly implemented in the methods shown below.</p>
<p><code class="prettyprint">BeginAnimation</code> is called once for each single animation step (e.g., moving from point A to point B with the finger touching the screen or not, as indicated). This animation is then handled as part of the normal <code class="prettyprint">Animate</code> method, which is called once for each <code class="prettyprint">Widget</code> during the main animation loop, by calling <code class="prettyprint">DoFingerAnimation</code>. As you can see it mostly just updates the finger's position and, once complete, calls the <code class="prettyprint">_fingerCompletionAction</code>.</p>
<pre class="prettyprint scrollable">
public void BeginAnimation(Point start, Point finish, bool isTouchingScreen, TimeSpan duration, Func<bool> completionChecker, Action completionAction)
{
_fingerMoveStartTime = DateTime.Now;
_fingerMoveDuration = duration;
AnimationStartPosition = start;
AnimationFinishPosition = finish;
IsTouchingScreen = isTouchingScreen;
_fingerCompletionChecker = completionChecker;
_fingerCompletionAction = completionAction;
}
private void DoFingerAnimation()
{
TimeSpan elapsed = DateTime.Now - _fingerMoveStartTime;
if(elapsed < _fingerMoveDuration)
{
Position = Animator.Interpolate(
AnimationStartPosition,
AnimationFinishPosition,
_fingerMoveDuration.TotalSeconds,
elapsed.TotalSeconds,
Animator.Curve.Linear
);
AnimationCurrentPosition = Position;
}
else if(IsBeingAnimated && _fingerCompletionChecker())
{
_fingerMoveStartTime = DateTime.MinValue;
_fingerCompletionAction();
}
}
</pre>
<h2>Stepping it up</h2>
<p>So, now that I can perform a single step of an animation controlling the finger, I need to string these together into multiple steps that show a complete tutorial lesson. To do this I created a couple of data holders within my <code class="prettyprint">Tutorial</code> class. The first, <code class="prettyprint">Step</code>, represents a single step of the tutorial and performs a single finger animation movement. The second, <code class="prettyprint">Lesson</code>, holds all of the data for a single tutorial lesson including the game elements to show on the screen and the sequence of steps.</p>
<p>One thing to note, there is a slightly confusing use of the term "completion checker" here, since it is used twice. It basically serves the same purpose for two different levels of the lesson. Inside <code class="prettyprint">Step</code> it is used to determine if that step should end. Of course the step has a set duration, but even after that duration there can be other conditions that must be met (see the actual lesson examples later). Similarly, in <code class="prettyprint">Lesson</code> this is used to determine if the lesson is complete.</p>
<pre class="prettyprint scrollable">
private struct Step
{
public Step(double destinationX, double destinationY, bool touchScreen, double duration, Func<Tutorial, bool> completionChecker)
{
Finish = new Point((float)destinationX, (float)destinationY);
TouchScreen = touchScreen;
Duration = TimeSpan.FromSeconds(duration);
CompletionChecker = completionChecker ?? (foo => { return true; });
}
public Point Finish;
public bool TouchScreen;
public TimeSpan Duration;
public Func<Tutorial, bool> CompletionChecker;
}
private struct Lesson
{
public int Width;
public int Height;
public IEnumerable<Point> Goals;
public IEnumerable<ShipInfo> Ships;
public IEnumerable<Tuple<int, int, WallLocation>> Walls;
public Func<Tutorial, bool> CompletionChecker;
public IEnumerable<Step> Steps;
}
</pre>
<h2>A lesson plan</h2>
<p>Fortunately I was able to use a combination of the <code class="prettyprint">yield return</code> technique for enumerations and C#'s <a href="http://msdn.microsoft.com/en-us/library/bb397680.aspx">object initializers</a> to compactly define individual lessons. I do this statically and populate an array to hold them all.</p>
<pre class="prettyprint scrollable">
private static Lesson[] All = Tutorials.ToArray();
private static IEnumerable<Lesson> Tutorials
{
get
{
int width = 4;
int height = 6;
// Create one simple diagonal.
yield return new Lesson
{
Width = width,
Height = height,
Goals = new Point[] { new Point(width - 2, height - 2) },
Ships = new ShipInfo[] { new ShipInfo(ShipType.Hero, new Point(0.5f, 0.5f), Direction.East, 0.025f) },
Walls = new Tuple<int, int, WallLocation>[0],
CompletionChecker = tutorial => { return tutorial.AchievedAllGoals; },
Steps = new Step[] {
new Step(width * 0.6, height * 0.4, false, 3, null),
new Step(width - 2, 0, false, 2.5, null),
new Step(width - 1, 1, true, 1.5, tutorial => { return tutorial.Ships.First().Position.X < width - 2.5; }),
new Step(width - 0.5, 1.5, false, 1.5, null)
}
};
.
.
.
</pre>
<p>Pulling apart this first <code class="prettyprint">Lesson</code>, the interesting part is the 3rd step that has the non-null completion check. This check ensures that the <code class="prettyprint">Ship</code> is far enough to the left before taking the finger off of the screen, and therefore completing the diagonal. Without doing this the ship could end up on the wrong side of the diagonal and not bounce to where it is supposed to.</p>
<p>There are a number of interim lessons I'm not including here, but one interesting one (shown below) is the lesson showing how to pause the game, which is done by swiping all the way across the screen horizontally in either direction. The interesting part here is that I needed to show the pause symbol, and then the continue symbol. To do this I cheated a little in two ways. First, in the step before I want the appropriate symbol to be visible, I used the completion check to create the needed symbol, although it's alpha was initially set to 100% transparent. This is done via the <code class="prettyprint">CreatePauseSymbol</code> and <code class="prettyprint">CreateContinueSymbol</code> methods, not shown here. Second, also not shown here, I adjust the transparency of the pause symbol to become more opaque as the finger completes its animation. This was a little hackish, but worked out pretty well.</p>
<pre class="prettyprint scrollable">
.
.
.
// How to pause.
yield return new Lesson
{
Width = width,
Height = height,
Goals = new Point[] { new Point(width - 2, height - 2) },
Ships = new ShipInfo[] { new ShipInfo(ShipType.Hero, new Point(0.5f, 0.5f), Direction.South, 0.045f) },
Walls = new Tuple<int, int, WallLocation>[0],
CompletionChecker = tutorial => { return true; },
Steps = new Step[] {
new Step(width * 0.6, height * 0.8, false, 2, null),
new Step(0, height * 0.45, false, 1, tutorial => tutorial.CreatePauseSymbol()),
new Step(width, height * 0.55, true, 3, tutorial => tutorial.CreateContinueSymbol()),
new Step(width - 1.5f, height - 1.5f, false, 1.5, null),
new Step(width * 0.5, height * 0.5, false, 1.5, null),
new Step(width * 0.5, height * 0.5, true, 0.05, tutorial => tutorial.RemoveContinueSymbol()),
new Step(width * 0.65, height * 0.65, false, 0.5, null),
new Step(0, height - 2, false, 0.75, null),
new Step(1, height - 1, true, 0.75, tutorial => { return tutorial.Ships.First().Position.Y < height - 2; }),
new Step(width * 0.75, height * 0.55, false, 1, null),
}
};
}
}
</pre>
<h2>Linking them together</h2>
<p>Finally, now that the lessons are defined, I need to do two more things: make the finger's touch actually behave like a normal touch in a normal game, and queue up the steps so they play in order. The first was pretty easy by just calling the existing touch handlers with the appropriate touch points. The second also turned out particularly well because I used the built in <code class="prettyprint">onComplete</code> action in the finger animations to recursively call a method that dequeues each successive step.</p>
<pre class="prettyprint scrollable">
private void DoTutorialSteps(Queue<Step> queue)
{
if(queue.Count > 0)
{
Step step = queue.Dequeue();
Action onComplete = step.TouchScreen
? new Action(() =>
{
HandleTouch(BoardToScreen(_finger.AnimationStartPosition), BoardToScreen(_finger.AnimationCurrentPosition), true);
base.HandleBoardTouch(_finger.AnimationStartPosition, _finger.AnimationCurrentPosition, true);
DoTutorialSteps(queue);
})
: new Action(() => { DoTutorialSteps(queue); });
_finger.BeginAnimation(_finger.Position, step.Finish, step.TouchScreen, step.Duration, () => step.CompletionCheck(this), onComplete);
}
}
</pre>
<p>I'm now almost done with the tutorial and have only one more lesson to add.</p>
<p>Next time: <a href="/2014/02/alpha-layering.html">Alpha layering</a>.</p>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-23397857800173959192014-01-27T12:52:00.001+09:002014-01-28T13:48:58.083+09:00Simple Solutions<p>Unlike the in depth investigation and learning required to understand premultiplied alpha, as discussed in my <a href="http://blog.qythyx.com/2014/01/premultiplied-alpha.html">previous post</a>, today's topic is simple and satisfying.</p>
<h2>Collections of collections</h2>
<p>I've structured the graphical elements of my game into a number of logical layers (not display layers). I won't go into all of them, but to give you an idea, here are some of the key parts, from the most fundamental to the most complex:</p>
<ol>
<li>The most basic layer is a <code class="prettyprint">Texture</code>, which is an abstract class that represents a picture that is loaded from a file.</li>
<li>Above that is an <code class="prettyprint">OpenGLES20Texture</code>, which is an implementation of <code class="prettyprint">Texture</code> specifically for OpenGL ES 2.0.</li>
<li>Above that is a <code class="prettyprint">Graphic</code>, which includes information about the <code class="prettyprint">Texture</code>, plus information about its orientation, size, and color.</li>
<li>The next layer up is <code class="prettyprint">Widget</code>, which is an abstract class that has a 2D position, an abstract <code class="prettyprint">Animate</code> method, and an abstract <code class="prettyprint">Render</code> method.</li>
<li>One layer above that is <code class="prettyprint">Symbol</code>, which implements <code class="prettyprint">Widget</code> and provides a number of concrete features, including references to one or more <code class="prettyprint">Graphic</code> instances. I use this class for all of the interactive controls in the game, like the checkmark to start a game, the question mark icon for starting the tutorial, etc.</li>
<li>Another layer above <code class="prettyprint">Widget</code> are all of the game UI elements, like <code class="prettyprint">Ship</code>, which is the white ball that moves around and bounces off of walls. This also implements <code class="prettyprint">Widget</code> and contains the logic to make the ship do what it is supposed to. I have similar classes for the other game UI elements like <code class="prettyprint">Wall</code>, <code class="prettyprint">Diagonal</code>, etc.</li>
</ol>
<p>Given the above structure, this means that all graphical elements that I need to render inherit from Widget. In the various game screens I keep track of these in collections that inherit from the following:</p>
<pre class="prettyprint scrollable">
public interface IWidgetEnumeration
{
/// <summary>Returns the <see cref="Widget"/>s in the collection.</summary>
IEnumerable<Widget> Widgets { get; }
}
</pre>
<p>For example, the game screen parent class collects all of these multiple collections via the following:</p>
<pre class="prettyprint scrollable">
protected sealed override IEnumerable<IWidgetEnumeration> WidgetsToRender
{
get
{
foreach(IWidgetEnumeration collection in LowerWidgetsToRender)
{
yield return collection;
}
yield return WallsHorizontal;
yield return WallsVertical;
yield return Goals;
yield return Diagonals;
yield return Poles;
yield return _diagonalMarkers;
yield return Ships;
yield return Collisions;
foreach(IWidgetEnumeration collection in UpperWidgetsToRender)
{
yield return collection;
}
yield return _pauseSymbols;
}
}
</pre>
<p>What's a little interesting here is that this accessor is an <code class="prettyprint">IEnumerable<IWidgetEnumeration></code>, or an enumeration of collections. This allows subclasses to override <code class="prettyprint">LowerWidgetsToRender</code> and <code class="prettyprint">UpperWidgetsToRender</code> to add additional widgets as necessary. What's been slightly annoying to me for a while, and what finally gets to the point of this blog entry, is that there have been a number of instances when I needed to only add a single new graphical element in a sub-class. But, since I need to return an <code class="prettyprint">IWidgetEnumeration</code> I kept needing to create a collection to contain that single graphical element. This made the override of <code class="prettyprint">LowerWidgetsToRender</code> look something like this:</p>
<pre class="prettyprint scrollable">
protected override IEnumerable<IWidgetEnumeration> LowerWidgetsToRender
{
get { yield return _lowerWidgets; }
}
</pre>
<p>Where <code class="prettyprint">_lowerWidgets</code> is an <code class="prettyprint">IWidgetEnumeration</code> that contains just one <code class="prettyprint">Widget</code>. I couldn't just return the <code class="prettyprint">Widget</code> directly, because I must return an <code class="prettyprint">IWidgetEnumeration</code>. This seems inefficient to create this collection just to contain one element. But wait, <code class="prettyprint">IWidgetEnumeration</code> is an interface. What's to stop me from implementing that directly on <code class="prettyprint">Widget</code> so I can just return that directly? Well, that's exactly what I did. I made <code class="prettyprint">Widget</code> implement <code class="prettyprint">IWidgetEnumeration</code> and added the following simple bit of code to it.</p>
<pre class="prettyprint scrollable">
public IEnumerable<Widget> Widgets
{
get { yield return this; }
}
</pre>
<p>This allowed me to change the above <code class="prettyprint">LowerWidgetsToRender</code> accessor into the following, where <code class="prettyprint">_tutorialCounter</code> is the single <code class="prettyprint">Widget</code> that was inside the previous collection.</p>
<pre class="prettyprint scrollable">
protected override IEnumerable<IWidgetEnumeration> LowerWidgetsToRender
{
get { yield return _tutorialCounter; }
}
</pre>
<p>I don't think this refactor improves the performance of the code in any measurable way, but it does make things a bit more straight forward and easier to understand. It's obviously not particularly clever or exciting, but I was happy when I thought of the solution and do feel the code is better because of it.</p>
<p>Next: <a href="/2014/01/a-tutorial-step-by-step.html">A Tutorial; Step-by-step</a>.</p>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-1815596107787620972014-01-23T17:08:00.000+09:002014-02-06T11:20:32.977+09:00Premultiplied alpha<h2>Sorry I've been gone</h2>
<p>It's been a while since my <a href="/2013/10/opengl-and-opentk-fun.html">last post about OpenGL and OpenTK Fun</a>. This is because the challenge I described in that article, and ultimately resolving that issue, unstuck me from making real progress on my development and I've been putting much more effort into my game since then and am now much further along. That's all good news, but now I need to both make progress on the game and try to keep this blog going too.</p>
<p>I'll start today with a discussion about OpenGL premultiplied alpha textures, but before that I want to send a quick thank you out to a new <a href="http://alexgorbatchev.com/wiki/SyntaxHighlighter">code syntax highlighter</a> by Alex Gorbatchev. It's JavaScript based, so I no longer need to manipulate my code entries before posting them in these articles. Also a thank you to Carter Cole and his <a href="http://blog.cartercole.com/2009/10/awesome-syntax-highlighting-made-easy.html">blog entry</a> describing how to easily setup the syntax highlighting in Blogger.</p>
<h2>So, what is "premultiplied alpha" and why do I care?</h2>
<p>As for what it is, there are plenty of pages out there that can describe it much better that me, so I'm not even going to try. I found a good succinct description on <a href="http://blog.rarepebble.com/111/premultiplied-alpha-in-opengl/">Martin Stone's blog</a>, which points to a more detailed description on <a href="http://home.comcast.net/~tom_forsyth/blog.wiki.html#[[Premultiplied%20alpha]]">Tom Forsyth's blog</a>. Please check those pages out if you want to learn more.</p>
<p>As for why I care, that I can describe in more detail. Initially I knew almost nothing about premultiplied alpha. I had seen the term a few times, for example when compiling my app there is some log message mentioning that it is precompiling my PNG textures, but I never tried to understand that more since everything was working fine. The reason I started to care more, and look into it more, are due to a couple things.</p>
<p>First, there was always something bothering me about a portion of my OpenGL code. From the beginning when I got the code working on both iOS and in Windows I found that I had to have a platform specific check for texture blending:</p>
<pre class="prettyprint scrollable">
// on iPhone this is needed to apply the color adjustment (on PC this is our normal setting)
if(colorAdjustment.Alpha != 1)
{
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
}
#if MONOTOUCH
else
{
GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);
}
#endif
</pre>
<p>I put this code into place after trial and error. The <code class="prettyprint">#if MONOTOUCH</code> section only compiles and runs on the MonoTouch framework, which means only on iOS. What I didn't understand was, given that OpenGL is supposed to be a consistent interface across platforms, why did I need to have this condition depending on the platform? All other code and image assets related to OpenGL textures and blending was the same between the two platforms, so why was this needed?</p>
<p>Well, the answer goes back to what I mentioned above about where I had previously heard about premultiplied alpha; the iOS compilation log message. What that message means is that my assumption that I'm using the same image assets (PNG images in my case) is not true. Although I have the same PNGs referenced in both the iOS project and Windows project, when the iOS version gets built the PNGs are adjusted to have alpha premultiplied.</p>
<p>So, why does that require the adjustment in <code class="prettyprint">GL.BlendFunc</code>? Well, first we need to know what <code class="prettyprint">GL.BlendFunc</code> does. The first parameter is how to use the incoming (source) colors when blending them with the existing (destination) pixels. The second parameter is how to adjust those destination pixels. There is ample documentation about this on many sites, so I won't go into all of the parameter options, but I will discuss the two that I was using. The first version, <code class="prettyprint">GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);</code> says two things:
<ol>
<li>For the source color, take the alpha (or transparency) component and multiply it by each other component. For example, if the RGB color is <code class="prettyprint">(1, 0.8, 0.5)</code> <span style="background: #FFCC7F"> </span> then after the multiplication it would become <code class="prettyprint">(0.5, 0.4, 0.25)</code> <span style="background: #7F663F"> </span>. This is the same color, but darkened.</li>
<li>For the destination color, take the alpha component of the source, subtract that from one, and multiply that by each component of the destination. In this case the alpha is 0.5, so subtracting that from 1 is also 0.5, which would be multiplied by the destination color. For example, if the existing destination RGB color was <code class="prettyprint">(0.2, 1, 1)</code> <span style="background: #33FFFF"> </span> then after multiplying it would become <code class="prettyprint">(0.1, 0.5, 0.5)</code> <span style="background: #197F7F"> </span>. Again, the same color, but darkened.</li>
</ol>
<p>After completing the above calculations on the source and destination colors then are blended by adding them together. That is <code class="prettyprint">(0.5, 0.4, 0.25) + (0.1, 0.5, 0.5) = (0.6, 0.9, 0.75)</code> <span style="background: #99E5BF"> </span>. Looking at the two original colors you can see that this works and the resulting blended color is correct.</p>
<p>Ok then, what's up with the second version: <code class="prettyprint">GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);</code>? How does this change things? I won't go through all the steps in detail, but starting with the same two first colors you would get a final function of <code class="prettyprint">(1, 0.8, 0.5) + (0.1, 0.5, 0.5) = (1.1, 1.3, 1.0)</code> or pure white, since each component exceeds the maximum value of one and is therefore limited to one. Clearly that is too bright and doesn't work work. So, why would you want to do that? Well, that's where premultiplied alpha comes in. The first version used <code class="prettyprint">BlendingFactorSrc.SrcAlpha</code>, which multiplies the alpha by the color. And what do you think <b>pre</b>multiplied alpha does? It did that exact same calculation when the texture asset was created (or built, in this case). This means that we don't need to do it again now while blending. Instead we use the color as is, which is what <code class="prettyprint">BlendingFactorSrc.One</code> does.</p>
<p>So, final question, why do this premultiplied alpha in the first place? I'll quote Tom Forsyth from his blog post (referenced above) for a pretty good explanation. For a much more in depth discussion please read his whole post.</p>
<blockquote>"Normal" alpha-blending munges together two physically separate effects - the amount of light this layer of rendering lets through from behind, and the amount of light this layer adds to the image. Instead, it keeps the two related - you can't have a surface that adds a bunch of light to a scene without it also masking off what is behind it. Physically, this makes no sense - just because you add a bunch of photons into a scene, doesn't mean all the other photons are blocked. Premultiplied alpha fixes this by keeping the two concepts separate - the blocking is done by the alpha channel, the addition of light is done by the colour channel. This is not just a neat concept, it's really useful in practice.</blockquote>
<h2>Doing premultiplied alpha on Windows</h2>
<p>Ok, so now I understand what's going on, but how do I fix it for Windows since I don't have a build step modifying my PNG files? I did some research and it seems there are some tools that will convert a normal PNG to a premultiplied alpha PNG, specifically it seems <a href="http://www.gimp.org/">GIMP</a> will do this, although I didn't try it myself. I didn't really want to have to convert my existing assets each time I modified them, or complicate my Windows build process by using addition tools, so I made a code change to do the alpha multiplication at the time I load my PNGs. That's a somewhat wasteful operation, but it only happens once, and considering I don't have very many textures I felt it was a good and simple solution.</p>
<p>Below is the code that does this. You'll notice some strange BGRA to RGBA conversion as well. This is due to using the <code class="prettyprint">Bitmap</code> class and it's <a href="http://stackoverflow.com/questions/580666/why-do-my-images-seem-to-be-in-the-format-of-bgra-instead-of-argb">how Windows works</a>.</p>
<pre class="prettyprint scrollable">
public static Texture LoadTexture(string fileName, Func<byte[], int, int, Texture> textureCreator)
{
if(textureCreator == null)
{
throw new ArgumentNullException("textureCreator");
}
using(Bitmap image = new Bitmap(fileName))
{
BitmapData bitmapData = image.LockBits(
new Rectangle(0, 0, image.Width, image.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb
);
image.UnlockBits(bitmapData);
IntPtr rawData = bitmapData.Scan0;
Texture texture;
unsafe
{
int length = image.Width * image.Height * 4;
byte[] data = new byte[length];
Marshal.Copy(rawData, data, 0, length);
for(int i = 0; i < length; i += 4)
{
float alpha = (float)data[i + 3] / 255;
byte r = data[i + 2];
byte g = data[i + 1];
byte b = data[i + 0];
data[i + 0] = MultiplyByAlpha(alpha, r);
data[i + 1] = MultiplyByAlpha(alpha, g);
data[i + 2] = MultiplyByAlpha(alpha, b);
data[i + 3] = (byte)(alpha * 255);
}
texture = textureCreator(data, image.Width, image.Height);
}
return texture;
}
}
private static byte MultiplyByAlpha(float alpha, byte channel)
{
return (byte)(channel * alpha);
}
</pre>
<p>After putting this code in place, so that my textures turn into premultiplied alpha data as I load them, I was able to simplify my original platform dependent code into just this:</p>
<pre class="prettyprint scrollable">
GL.BlendFunc(colorAdjustment.Alpha != 1 ? BlendingFactorSrc.SrcAlpha : BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);
</pre>
<p>Removing that platform specific code is a very minor improvement, but more than that I'm happy to understand all of this quite a bit more.</p>
<p>Next: <a href="/2014/01/simple-solutions.html">Simple Solutions</a>.</p>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com3tag:blogger.com,1999:blog-4882408034198003350.post-30235713069214133142013-10-05T17:27:00.000+09:002014-01-23T17:11:48.500+09:00OpenGL and OpenTK FunI've been working on updating my OpenGL code to use OpenGL ES 2.0 (instead of 1.1). I've run into a number of problems doing this, many of which are exasperated because I don't if my problems are related to my OpenGL code or the underlying libraries I'm using (<a href="http://www.opentk.org/">OpenTK</a> and the <a href="http://www.imgtec.com/PowerVR/insider/sdkdownloads/index.asp">PowerVR OpenGL ES</a> implementation for Windows).<br />
<br />
To try to diagnose the problems I tried to start with a simple OpenGL ES 2.0 example. Unfortunately, all of the examples I found were either for native OpenGL ES (not via .NET) or specifically from <a href="http://www.xamarin.com/">Xamarin</a> for iOS or Android, where I wanted a simple OpenTK example. The OpenTK distribution actually includes one very simple example (just showing an empty window with a colored fill), but as I mentioned in my previous post, the latest official OpenTK release is from 2010 and has some issues.<br />
<br />
I searched around and found what appears to be the most active development branch of OpenTK on GitHub at <a href="https://github.com/andykorth/opentk">https://github.com/andykorth/opentk</a>. I downloaded that branch and tried to run the same example, but continued to run into problems.<br />
<br />
At first I got a <code>System.PlatformNotSupportedException</code>.<br />
<pre style="overflow: auto; word-wrap: normal;">System.PlatformNotSupportedException at OpenTK.Platform.Factory.UnsupportedPlatform.CreateGLContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext, Boolean directRendering, Int32 major, Int32 minor, GraphicsContextFlags flags) in opentk\Source\OpenTK\Platform\Factory.cs:line 171
at OpenTK.Graphics.GraphicsContext..ctor(GraphicsMode mode, IWindowInfo window, Int32 major, Int32 minor, GraphicsContextFlags flags) in opentk\Source\OpenTK\Graphics\GraphicsContext.cs:line 134
at OpenTK.GameWindow..ctor(Int32 width, Int32 height, GraphicsMode mode, String title, GameWindowFlags options, DisplayDevice device, Int32 major, Int32 minor, GraphicsContextFlags flags, IGraphicsContext sharedContext) in opentk\Source\OpenTK\GameWindow.cs:line 222
at OpenTK.GameWindow..ctor(Int32 width, Int32 height, GraphicsMode mode, String title, GameWindowFlags options, DisplayDevice device, Int32 major, Int32 minor, GraphicsContextFlags flags) in opentk\Source\OpenTK\GameWindow.cs:line 180
at Examples.Tutorial.SimpleES20Window..ctor(GraphicsContextFlags flags) in opentk\Source\Examples\OpenGLES\2.0\SimpleWindow20.cs:line 26
at Examples.Tutorial.SimpleES20Window.Main() in opentk\Source\Examples\OpenGLES\2.0\SimpleWindow20.cs:line 112
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()</pre>
<p>This led me down a little side road of trying to confirm that I had the PowerVR OpenGL ES library properly installed. After confirming it was, as far as I could tell, I stepped through the code in the debugger and got to the Factory constructor in <code>opentk\Source\OpenTK\Platform\Factory.cs</code>.</p>
<script type="syntaxhighlighter" class="brush: csharp"><![CDATA[
static Factory()
{
if (Configuration.Sdl2Supported) Default = new SDL2.Sdl2Factory();
else if (Configuration.RunningOnWindows) Default = new Windows.WinFactory();
else if (Configuration.RunningOnMacOS) Default = new MacOS.MacOSFactory();
else if (Configuration.RunningOnX11) Default = new X11.X11Factory();
else Default = new UnsupportedPlatform();
if (Configuration.Sdl2Supported)
{
Embedded = new Egl.EglSdl2PlatformFactory();
}
else if (Egl.Egl.IsSupported)
{
if (Configuration.RunningOnWindows) Embedded = new Egl.EglWinPlatformFactory();
else if (Configuration.RunningOnMacOS) Embedded = new Egl.EglMacPlatformFactory();
else if (Configuration.RunningOnX11) Embedded = new Egl.EglX11PlatformFactory();
else Embedded = new UnsupportedPlatform();
}
else
{
Embedded = new UnsupportedPlatform();
}
if (Default is UnsupportedPlatform && !(Embedded is UnsupportedPlatform))
Default = Embedded;
}]]></script>
<p>While stepping through I kept finding that <code>Egl.Egl.IsSupported</code> was false. That code for IsSupported is below.</p>
<script type="syntaxhighlighter" class="brush: csharp"><![CDATA[
// Returns true if Egl drivers exist on the system.
public static bool IsSupported
{
get
{
try { GetCurrentContext(); }
catch (Exception) { return false; }
return true;
}
}
]]></script>
<p>Oh great, a swallowed exception, maybe that would tell me something helpful. Stepping through the code again and examining the exception showed me a <code>System.BadImageFormatException</code>.</p>
<pre style="overflow: auto; word-wrap: normal;">System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)
at OpenTK.Platform.Egl.Egl.GetCurrentContext()
at OpenTK.Platform.Egl.Egl.get_IsSupported() in opentk\Source\OpenTK\Platform\Egl\Egl.cs:line 316
</pre>
<p>I'm sure some web searching would find it, but I've run into this problem in the past and remembered it had to do with incompatible binary formats. For example, if you have a .NET program compiled for x64 and try to load an 32bit x86 native DLL. I checked the OpenTK solution's build configuration, and indeed, it was set to "AnyCPU". I changed it to x86 and tried again and the above exception went away, but it still didn't work.</p>
<p>Next I got an entirely unhelpful <code>System.Collections.Generic.KeyNotFoundException</code>. With the below stack-trace.</p>
<pre style="overflow: auto; word-wrap: normal;">System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at OpenTK.Graphics.GraphicsContext.get_CurrentContext() in opentk\Source\OpenTK\Graphics\GraphicsContext.cs:line 344
at OpenTK.Graphics.GraphicsContext.LoadAll() in opentk\Source\OpenTK\Graphics\GraphicsContext.cs:line 512
at OpenTK.GameWindow..ctor(Int32 width, Int32 height, GraphicsMode mode, String title, GameWindowFlags options, DisplayDevice device, Int32 major, Int32 minor, GraphicsContextFlags flags, IGraphicsContext sharedContext) in opentk\Source\OpenTK\GameWindow.cs:line 220
at OpenTK.GameWindow..ctor(Int32 width, Int32 height, GraphicsMode mode, String title, GameWindowFlags options, DisplayDevice device, Int32 major, Int32 minor, GraphicsContextFlags flags) in opentk\Source\OpenTK\GameWindow.cs:line 180
at Examples.Tutorial.SimpleES20Window..ctor(GraphicsContextFlags flags) in opentk\Source\Examples\OpenGLES\2.0\SimpleWindow20.cs:line 26
at Examples.Tutorial.SimpleES20Window.Main() in opentk\Source\Examples\OpenGLES\2.0\SimpleWindow20.cs:line 112
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()</pre>
<p>Well, for a pretty low-level media platform this isn't very encouraging as it doesn't describe anything about the real problem and is clearly happening deep in the library. After a lot of debugging I finally found the below bit of code in <code>opentk\Source\OpenTK\Graphics\GraphicsContext.cs</code>.</p>
<script type="syntaxhighlighter" class="brush: csharp"><![CDATA[
/// <summary>
/// Gets the GraphicsContext that is current in the calling thread.
/// </summary>
/// <remarks>
/// Note: this property will not function correctly when both desktop and EGL contexts are
/// available in the same process. This scenario is very unlikely to appear in practice.
/// </remarks>
public static IGraphicsContext CurrentContext
{
get
{
lock (SyncRoot)
{
if (available_contexts.Count > 0)
{
ContextHandle handle = GetCurrentContext();
if (handle.Handle != IntPtr.Zero)
return (GraphicsContext)available_contexts[handle].Target;
}
return null;
}
}
}
]]></script>
<p>I missed it the first few times I was stepping through the code, but the comment says it all: Note: this property <b>will not function correctly when both desktop and EGL</b> contexts are available in the same process. This scenario is very unlikely to appear in practice. In my case, since I'm running on Windows, I do actually have both desktop and EGL available. I didn't feel like digging into the code to fix this so that both contexts could actually exist simultaneously, but I remembered the Factory class that's I'd looked into above and made an easier fix. I did this in <code>opentk\Source\OpenTK\Platform\Factory.cs</code> in the constructor. My updated version is below.</p>
<script type="syntaxhighlighter" class="brush: csharp"><![CDATA[
static Factory()
{
Default = new UnsupportedPlatform();
if (Configuration.Sdl2Supported)
{
Embedded = new Egl.EglSdl2PlatformFactory();
}
else if (Egl.Egl.IsSupported)
{
if (Configuration.RunningOnWindows) Embedded = new Egl.EglWinPlatformFactory();
else if (Configuration.RunningOnMacOS) Embedded = new Egl.EglMacPlatformFactory();
else if (Configuration.RunningOnX11) Embedded = new Egl.EglX11PlatformFactory();
else Embedded = new UnsupportedPlatform();
}
else
{
Embedded = new UnsupportedPlatform();
if (Configuration.Sdl2Supported) Default = new SDL2.Sdl2Factory();
else if (Configuration.RunningOnWindows) Default = new Windows.WinFactory();
else if (Configuration.RunningOnMacOS) Default = new MacOS.MacOSFactory();
else if (Configuration.RunningOnX11) Default = new X11.X11Factory();
}
if (Default is UnsupportedPlatform && !(Embedded is UnsupportedPlatform))
Default = Embedded;
}
]]></script>
<p>You can compare to the original version above, but basically I made it such that the library first tries to see if EGL is available, and if so, uses it. If it is not available then the desktop version is instantiated. You can see that the Default context isn't instantiated unless the EGL contexts don't work.</p>
<p>After all of this I could run the sample program and was rewarded with a 800x600 blue window. Woohoo! Ok, it's not that exciting, but at least it was running and I can now try to get my OpenGL ES 2.0 code working.</p>
<p>Next: <a href="http://blog.qythyx.com/2014/01/premultiplied-alpha.html">Premultiplied alpha</a></p>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-22381394127279794522013-09-25T21:21:00.001+09:002014-01-23T13:19:54.303+09:00I'm Back<p>After more than 2.5 years I'm back. I'm going to try hard to get back into spending regular time on my game and actually get it published. So far I've spent about 15 hours just getting it back to the point it was when I last left it. A lot has changed since then, which is why it took so long to just get it working at all. Here're a few things that have changed and some details about what I've had to do to fix them.</p>
<ol>
<li>I've changed my home computer. I used to have a tower Windows PC and a Macbook Air for doing the iOS-specific development portions. I now have just a Macbook Pro Retina, which I run VMWare Fusion and Windows 8. I made this migration a while ago, but finally getting my development environment working well took some work.</li>
<li>OpenTK seems to have made some changes, which I don't fully understand since their release date is 2010-10-06 and should have been before I took a hiatus. See <a href="http://www.blogger.com/blogger.g?blogID=4882408034198003350#OpenTK_Issues">more details</a> below.</li>
<li>In the interim 2.5 years both XCode and Xamarin MonoDevelop have evolved and new versions have been released that required more updates. Xamarin's product has been renamed to Xamarin Studio and now has some interesting integration with Visual Studio, which hopefully will make development a bit easier. See more detail below.</li>
</ol>
<h2>OpenTK Issues</h2><a name="OpenTK_Issues"></a>
<p>When I tried to run my game on Windows using OpenTK I got a <code>PInvokeStackImbalance</code> exception saying: <code>This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.</code> Since I was running against the release build of OpenTK, which doesn't contain debug symbols this error occurred in my <code>GLView</code> class' constructor. It is an implementation of OpenTK's <code>GameWindow</code>. After compiling a debug build of OpenTK, I eventually traced this to the function <code>GetProcAddress</code> in <code>Egl.cs</code>, which is a PInvoke call.</p>
<script type="syntaxhighlighter" class="brush: csharp"><![CDATA[
DllImportAttribute("libEGL.dll", EntryPoint = "eglCopyBuffers")]
public static extern IntPtr GetProcAddress(string funcname);
]]></script>
<p>The problem is the EntryPoint should be <code>eglGetProcAddress</code>, not <code>eglCopyBuffers</code>. After changing this and rebuilding OpenTK it worked fine. Doing some searching on the net shows this has been fixed in GitHub, but no new official build containing the fix has been released. It was an easy fix, but a bit disappointing that OpenTK's current official release has such a basic bug in it.</p>
<p>Beyond this, it seems that the latest official OpenTK release differs from what is released with MonoTouch. I'm still not sure of the extend of these differences, but I may write more on this later once I get a better understanding.</p>
<h2>Xamarin and Visual Studio</h2>
<p>As I discussed in a <a href="http://www.blogger.com/2010/09/monodevelop-isnt-bad-but-i-really.html">previous post</a>, I've been using Visual Studio as my primary development environment and only switching to <s>MonoDevelop</s> Xamarin Studio when I wanted to actually deploy my code to my iPhone. Apparently others also are interested in doing this because Xamarin has integrated their product with Visual Studio; you can read more about this on <a hred="http://xamarin.com/visual-studio" href="http://www.blogger.com/blogger.g?blogID=4882408034198003350">their site</a>. Because they're now providing .NET DLLs that can be used in Visual Studio to compile iOS .NET apps, it means I can simplify one of the issues I had with maintaining and building my code.</p>
<p>I have my game broken into 4 projects, each of which builds into a separate binary. One of these, which I call "Launcher", is the most device dependent, and references device specific libraries. This project can't actually be shared across platforms because the code is too different, so I have both a Launcher.iOS project and a Launcher.PC (in the future I hope to add Launcher.Android and Launcher.WinPhone). For the other three projects I also need separate project files, but 100% of the code is shared. This leaves me with this folder hierarchy:</p>
<style>
.project_hierarchy ul {margin: 0 !important;}
.project_hierarchy ul li {padding: 0 !important;}
</style>
<ul class="project_hierarchy">
<li>Zoing</li>
<ul>
<li>Launcher.iOS
<ul>
<li>Launcher.iOS.csproj (project file that references iOS specific libraries)</li>
<li>...iOS specific code files...</li>
</ul>
</li>
<li>Launcher.PC
<ul>
<li>Launcher.PC.csproj (project file that references PC specific libraries)</li>
<li>...PC specific code files...</li>
</ul>
</li>
<li>MediaFramework
<ul>
<li>MediaFramework.iOS.csproj (project files that references MonoTouch .NET libraries)</li>
<li>MediaFramework.PC.csproj (project files that reference Windows .NET libraries)</li>
<li>...device independent C# files included in both project files...</li>
</ul>
</li>
<li>OpenTKConnector
<ul>
<li>OpenTKConnector.iOS.csproj (project files that references MonoTouch .NET libraries)</li>
<li>OpenTKConnector.PC.csproj (project files that reference Windows .NET libraries)</li>
<li>...device independent C# files included in both project files...</li>
</ul>
</li>
<li>Zoing
<ul>
<li>Zoing.iOS.csproj (project files that references MonoTouch .NET libraries)</li>
<li>Zoing.PC.csproj (project files that reference Windows .NET libraries)</li>
<li>...device independent C# files included in both project files...</li>
</ul>
</li>
</ul>
</ul>
<p>MediaFramework is a set of device and framework independent classes that define the building blocks for my game. This includes things like: <code>Audio/AudioSample</code>, <code>Device/Orientation</code>, <code>Graphics/Texture</code>, <code>Graphics/TextureArea</code>, <code>Graphics/MutableShape</code>, <code>Graphics/Shape</code>, <code>Graphics/Size</code>, etc.</p>
<p>OpenTKConnector is a concrete implementation of the MediaFramework abstract classes using OpenTK (OpenAL and OpenGL). For example there is <code>OpenGLES11/OpenGLES11Texture</code> and <code>OpenGLES20/OpenGLES20Texture</code>, both of which implement <code>MediaFramework/Graphics/Texture</code>. Similarly there is <code>OpenAL/OpenALAudioSample</code>, which implements <code>MediaFramework/Audio/AudioSample</code>.</p>
<p>The benefit of having this separation is that my Zoing game just needs to know about the abstract classes in MediaFramework and complete ignores the platform details. For example, AudioSample has a <code>Play</code> method, which plays the sample. That is common for any platforms I will eventually support.</p>
</br>
<p>Anyway, restructuring my solution to have the project files arranged as above took a bit of time. I had to recreate the iOS project files to reference the MonoTouch library properly. I also had to move these files around, which is made a little more difficult with version control. Some of this meant examining and tweaking the project files' XML by hand.</p>
<p>This reminds me of one small bug in Visual Studio 2010 that caused me some confusion until I understood it. When you unload a project, then edit the project's <code>.csproj</code> XML file, then reload the project, it seems that Visual Studio doesn't recognize the manual changes you made. To fix this simple unload it again, then reload it again. This is a bit annoying, but once you know about it the workaround is simple.</p>
<p>Next: <a href="http://blog.qythyx.com/2013/10/opengl-and-opentk-fun.html">OpenGL and OpenTK Fun</a></p>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-58761228106934087412011-01-06T21:48:00.000+09:002014-01-23T13:17:09.569+09:00More OptimizationsIn my <a href="http://blog.qythyx.com/2010/12/phunky-physics.html">previous post</a> I discussed some challenges and interesting compromises I had to make to simulate physics in my game as much as I needed given how I was trying to make the game behave in ways that conflict with the real world. In this post I revisit optimization and discuss some recent work I've put into that area.<br />
<br />
<h4>
Introduction</h4>
I'll start with a brief explanation of why it has been so long since my last entry. Partly this is because I've been busy with other things and the recent holidays have also taken up some of my time, but the main reason is that this round of optimizations has been quite a bit more complex than I anticipated. Interestingly, it hasn't been the actual code changes that have been challenging, it has been my efforts to carefully analyze my code's slow areas and fully understand the optimizations I've made.<br />
<br />
In some ways this is related to my <a href="http://blog.qythyx.com/2010/10/optimizing.html">previous post on optimizations</a>. In that post I left with a conclusion that you need to be careful when optimizing to make sure you understand if and how the changes are really improving performance. To that end I tried to be more diligent in this round of optimizations.<br />
<br />
My initial goal in this round was to optimize my game's calculations when advancing the game state frame by frame. I'm currently targeting 30 FPS, but would like that to be as efficient as possible in order to preserve battery life. Also, even though the iPhone can do graphics up to 60 FPS, I examined what my game does and found that there is very little perceivable visual improvement moving from 30 FPS to 60 FPS, which is why I decided to target the former.<br />
<br />
To be able to properly analyze the performance of my code's calculations, as opposed to time taken by the actual graphics chip's rendering, I created a benchmark mode for my game that does all calculations as normal, but doesn't do any of the OpenGL API calls. I also do this outside of the top-level game loop, since the OpenTK framework imposes its own FPS limitations in this loop.<br />
<br />
<h4>
Sample Size</h4>
I should mention that all of this optimization work was done on my PC in Visual Studio. Since my goal is to optimize my algorithms, I'm working on the assumption that the improvements will also be reflected in code when running on an actual iPhone.<br />
<br />
When I started testing this benchmark mode I tried running for a few thousand frames. I got results around 1200 FPS. I initially assumed this number of frames was enough to produce useful results and started the process of running under various conditions both for generating performance statistics to measure improvements and for running within VS' performance analysis tools to see what areas of the code were slowest and the best target for optimization efforts.<br />
<br />
The problem I found was that the performance differed wildly from run to run. I ended up wasting quite a lot of time doing analysis with these too-short runs that produced inconsistent results. I finally found that the only real way to get more consistent results was to simply increase the number of frames to render and therefore the test time. Eventually I settled on running for 100,000 frames, or about one minute. At this number of frames I found that I could get multiple runs to finish within a few milliseconds of one another, which, over 1 minute, is quite consistent.<br />
<br />
<h4>
First Optimization - Math</h4>
Using this sample size, my baseline analysis produced a run in <span style="color: yellow;">78.05 seconds at 1281.23 FPS</span>. I then ran this through the VS performance analysis tools. This revealed a lot of information, but I decided to start with one particular method I though I could quickly optimize.<br />
<br />
I have an object called <c>Shape</c>. This represents a set of vertices that are connected to define a 2D shape. So far I'm using it for triangles or quads only, but it is flexible up to any number of points. This object also supports some transform operations like moving it in the 2D plane, resizing it, and rotating it (both around it's own center point as well as some other point). The way I've implemented these transform operations is to, at the time they're called, only remember what the operations are, but not to actually do the transform calculations until needed. This is to optimize when multiple transforms occur at the same time, such as an offset, resize, and rotation.<br />
<br />
When the final transformed shape is needed I have a method called <c>MakeClean</c> that, if needed, performs the calculations. When I did my first performance analysis I found that this method was taking <span style="color: yellow;">20.8%</span> of total time with these details:<br />
<pre class="csharpcode"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMokjYiXtGUmYYLD7-62DBiYYLJCX6YkpQswMWhO2KLEtA5Omu4dbWpUsoPt2xdnbVu2QO0EmTM-cFioCqFYsi8tKGQNpnD_91YqWLNU-e4ji6nCVBLouHJMYSzXp_Fv5uPCHnEphkuYSq/s1600/MakeClean+old.png" /></pre>
<br />
I rewrote this to remove temporary object creation (actually <c>Vector2</c> is a struct, not an object, but same idea), reduce repeated property accesses, combine multiple calculations, etc. This reduced the time to <span style="color: yellow;">9.8%</span> and produced these details:<br />
<pre class="csharpcode"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnFlQmFqswuUtGOLerW56M089aFX5Css6Zdsj5gzBvmPwIOqp1v-o8ynBh1NknQ7tcVgdEZj8ZzJyuPLNICq6jT7yW12hySszWWduR-d30z9eyMdMCCv0MTbkSA61FQZbD0lJr8f7Uumi8/s1600/MakeClean+new.png" /></pre>
<br />
The result of this was the 100,000 frames finished in <span style="color: yellow;">68.9 seconds at 1451.38 FPS</span>. That's 88% of the original time, and 113% of the original FPS.<br />
<br />
<h4>
Second Optimization - LINQ</h4>
After the above optimization I did another performance analysis in VS and received this summary report:<br />
<pre class="csharpcode"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYCUJ7ol88eK4uuWPQU2IGZ5MAjOhzQ9p0V0ogFBmtu8ocmCg64CBcXT67vn9llKNlnW1ccHmgH0jc2bKrqAW2zaOP5xzmfFm9fVALKY2KfymSBGANfXlq5JdByB-4m8MG55hXDU9NZLgT/s1600/LINQ.png" /></pre>
<p>I was not surprised to see the LINQ entry there since when I was originally writing that area of the code I knew it wasn't particularly efficient. At the time, however, I wrote it as simply as possible and decided I would optimize it if necessary later. Well, now it's later and time to optimize.</p>
<p>For this area, I can't do any simple localized calculation optimizations, however. That LINQ code is all in the .NET framework and out of my control. In the VS performance analyzer, if I dig into that method, no source code is shown at all, since it is part of the core libraries. In fact, trying to determine where this code is called from is slightly complicated because VS shows this only indirectly. It shows the caller of this LINQ as:</p>
<script type="syntaxhighlighter" class="brush: csharp"><![CDATA[
foreach(GameElement element in ElementsToRender.SelectMany(collection => collection.GameElements))
{
element.Render();
}
]]></script>
<p>But, in this there's no <c>ConcatIterator</c> to be seen anywhere. Fortunately, it isn't too hard to guess that it is somewhere in <c>ElementsToRender</c>. The code for that initially was:</p>
<script type="syntaxhighlighter" class="brush: csharp"><![CDATA[
protected override IEnumerable<GameElement> ElementsToRender
{
get
{
// TODO: Concat is not very efficient for this, especially since the collections are not changing often.
// It would be better to have a single collection that has a copy of the once-casted GameElements.
// This would have to detect when the collections do change and recreate the single collection.
return WallsHorizontal.Values.Cast<GameElement>()
.Concat(WallsVertical.Values.Cast<GameElement>())
.Concat(Goals.Values.Cast<GameElement>())
.Concat(Diagonals.Values.Cast<GameElement>())
.Concat(Poles.Cast<GameElement>())
.Concat(new GameElement[] { DiagonalStart, DiagonalFinish }.Where(marker => marker != null))
.Concat(Ships.Cast<GameElement>())
.Concat(Collisions.Cast<GameElement>());
}
}
]]></script>
<p>
Hah, see, I told you I wasn't surprised to see this as taking a lot of time. I even wrote a comment to myself about it. One thing the VS performance analysis doesn't show very well is why this is slow. Although this code clearly has a lot of stringed-together <c>Concat</c> calls, I suspect that the embedded <c>Cast</c> operations are also quite a performance hit.</p>
<p>The reason my original code was like this is because I'm developing in .NET 3.5, which does not support covariance and contravariance and therefore doesn't allow casting collections of subclasses into collections of common supperclasses. This is supported in .NET 4.0, however.</p>
<p>My solution to this was to create simple collection classes that hold my <c>GameElement</c> objects, and that can provide those objects both in their subclass typed state as well as their superclass form, and do this efficiently. The basic way I do this is to have have the collections detect when they are modified (added to, removed from, etc) and mark themselves as dirty in that case. Then, when I request the collection in the superclass form, that list is generated and cached, if necessary. This allows me to reduce the above code to this:</p>
<script type="syntaxhighlighter" class="brush: csharp"><![CDATA[
protected override IEnumerable<IGameElementCollection> ElementsToRender
{
get
{
yield return WallsHorizontal;
yield return WallsVertical;
yield return Goals;
yield return Diagonals;
yield return Poles;
yield return _diagonalMarkers;
yield return Ships;
yield return Collisions;
}
}
]]></script>
<p>At a different area in the code I do a <c>SelectMany</c> to combine all of these collections together. The result of this was the 100,000 frames finished in <span style="color: yellow;">55.59 seconds at 1799.04 FPS</span>. That's 81% of the previous time, and 124% of the original FPS.</p>
<h4>Third Optimization</h4>
<p>I also spent a bit of time on a third optimization. The end result of that was no performance improvement at all, in fact, it was 3% slower that the previous version. I won't describe this optimization in detail now, and I'm actually keeping the slower version for now. The reason is that I think it lays a good foundation for possibly making a more significant optimization in the future, and in some ways is cleaner that the earlier version. If I do such an optimization later I'll then describe it more.</p>
<h4>Summary</h4>
<p>The end result of all of the above was that my original performance of <span class="highlight">78.05 seconds at 1281.23 FPS</span> was improved to <span class="highlight">57.08 seconds at 1752.04 FPS</span>. This is 73% of the original time and 137% of the original FPS.</p>
<p>All in all I'm pretty happy with that result. I'm now able to continue feature development and will hopefully be able to blog more regularly again.</p>
<p>Next: <a href="http://blog.qythyx.com/2013/09/im-back.html">I'm Back</a></p>Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-53205539907246863942010-12-08T14:19:00.001+09:002011-01-06T21:49:23.078+09:00Phunky PhysicsIn my <a href="http://blog.qythyx.com/2010/11/more-monotouch-gotchas.html">previous post</a> I discussed some issues I ran into running my .NET code in the MonoTouch environment. In this post I'm switching gears a bit and will discuss an issue I've been struggling with for a while related physics simulation in my game.<br />
<br />
<div class="image"><img src="http://www.qythyx.com/zoing/CollisionBeforeDiagonal.jpg" /><br />
Before Diagonal</div><div class="image"><img src="http://www.qythyx.com/zoing/CollisionAfterDiagonal.jpg" /><br />
After Diagonal</div><h4 style="font-size: 120%;">90° in the Shade</h4>The issue came up because I was trying to make some of my physics interactions behave different from how they do in reality. You would think that bending reality or completely changing how physics works should be possible in a computer simulation's implementation, but I found out that changing how things work quickly leads to unintended problems. I wonder if this says something about the necessity of how physics actually works in real life.<br />
<br />
If you look at the "Before Diagonal" image on the right, you see a white ball heading towards a collision with a blue diagonal. Then, in "After Diagonal", you can see the white ball after it has bounced off of the blue diagonal. For the purposes of my game the white ball must always be exactly halfway between a row or column of purple stars. Before the bounce it is travelling horizontally, and is halfway vertically in a row. After it bounces it is travelling vertically, and is halfway horizontally in a column. The problem is that in an actual 90° bounce like this, with a ball that has a non-zero radius, it would not bounce from halfway-vertical-in-row to halfway-horizontal-in-column like this. The lower part of leading edge of the ball would hit the diagonal first, before the halfway point, and then the ball would bounce upwards, but to the left of the halfway-horizontal-in-column point.<br />
<br />
I initially tried to resolve this problem by simply <i>teleporting</i> the ball from the one halfway point to the next halfway point when the bounce occurred. This looked fine, and seemed to work ok, until I added interactions between multiple balls. The problem was that if there was already another ball in the place to where the first ball is teleported, then they would become overlapped and not properly bounce off of one another.<br />
<br />
I solved this problem by not doing the teleportation and having the ball bounce as if it has a zero radius. This works fine for my purposes, but has another small problem. For the skin shown in the images to the right, the ball is fuzzy and the diagonal is lightning-like, so having the ball overlap the diagonal when it bounces looks fine. For an alternative skin, though, where the ball is completely solid and the diagonal is more physical-looking, it will not look ok. My plan to solve this is to have the interaction calculation done as if the radius is zero, but have the rendering adjust the ball's visible position so it doesn't overlap the diagonal.<br />
<br />
<div class="image"><img src="http://www.qythyx.com/zoing/CollisionShipBefore.jpg" /><br />
Before Collision</div><div class="image"><img src="http://www.qythyx.com/zoing/CollisionShipAfter.jpg" /><br />
After Collision</div><div class="image"><img src="http://www.qythyx.com/zoing/CollisionShipEvent.jpg" /><br />
Collision!</div><h4 style="font-size: 120%;">Billiards this is Not</h4>After resolving the above issue I then started working on ball-to-ball collisions. When the two balls are on the same row or column this is easy. The issue is when they are moving perpendicular to one another.<br />
<br />
If you look an the "Before Collision" image to the right you can see that the tow balls are heading towards one another and will collide in the lower-left area of the image. If these were actual balls, like billiard balls, then the result of the collision is that the one moving down would head left and the one moving left would head down. The angle each ball changes to would depend on their speed and mass. My problem with implementing this type of collision is the same as that I described above related to bouncing off of diagonals. Basically, I want the balls to remain exactly in the halfway point between a row or a column. In this scenario, though, that would not happen.<br />
<br />
For ball-to-ball collisions I could not use the same strategy I used above for diagonal collisions; calculating as if the ball had a zero radius. I actually initially tried this, and it worked ok for two balls, but once more than two were involved it became possible that all of the balls come to be in exactly the same position and could never resolve their collisions. Also, adjusting the rendering of the overlapped balls so that the don't appear to be overlapping is more difficult.<br />
<br />
The solution I eventually came up with was to have the balls simply bounce and reverse directions entirely (see "After Collision" image). As I said, in real physics, that would not happen, and I wanted to make it look reasonable. To attempt do that I added a graphic element that is shown when the collision occurs, and implies that something more than just a normal real-life, ball-to-ball collision is happening. Currently I'm using the graphic shown in the "Collision!" image, but this is tentative, and also can be customized for each skin. Even though the physics of this collision isn't even close to real physics, it actually works pretty well in the game.<br />
<br />
<h4 style="font-size: 120%;">Summary</h4>Resolving both of the problems described above took quite a while. I experimented with a number of different solutions before settling on the current one. I also tried to get things working in the early stages when I had the balls teleporting during a diagonal bounce. This seemed we work for some cases, but was never reliable. I find it interesting that even the small adjustment to physical rules I was trying to make in having the balls bounce and maintain their position halfway in a row or column caused problems that were basically impossible to solve. Of course the other side if this is that even my final solution goes against how physics works in real life.<br />
<br />
One other thing that makes me feel pretty comfortable with my current solution is that it ended up in much cleaner code. In other strategies I always had a number of special cases, that made the code messy. Once I changed to my current solution they went away.<br />
<br />
Next time: <a href="http://blog.qythyx.com/2011/01/more-optimizations.html">More Optimizations</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com1tag:blogger.com,1999:blog-4882408034198003350.post-56059607405927686952010-11-22T08:29:00.002+09:002010-12-08T14:22:11.181+09:00More MonoTouch GotchasIn my <a href="http://blog.qythyx.com/2010/11/fighting-with-blogger.html">previous post</a> I took a break about discussing my adventure developing for the iPhone with .NET and discussed some issues I've had blogging with Blogger. In this post I get back to the main of subject and describe an odd issue I've encountered with MonoTouch.<br />
<br />
The issue is due to the way that MonoTouch compiles to native iPhone binaries. It does not support JIT compilation, which a "normal" .NET environment does support. In most cases this is fine, but sometimes the compiler cannot fully detect the types that are needed and therefore doesn't compile them into the binary. Then, during runtime, when it detects it needs a type that was not compiled it crashes.<br />
<br />
The code below is a pretty simple example of this issue:<br />
<pre class="csharpcode"><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><summary></span><span style="color: green; font-family: Consolas; font-size: 12pt;">Initializes a new instance of the </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><see cref="Shape"/></span><span style="color: green; font-family: Consolas; font-size: 12pt;"> class.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></summary></span><span style="font-family: Consolas; font-size: 12pt;"><o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><param name="points"></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The points.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></param></span><span style="font-family: Consolas; font-size: 12pt;"><o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="color: blue; font-family: Consolas; font-size: 12pt;">public</span><span style="font-family: Consolas; font-size: 12pt;"> Shape(<span style="color: #2b91af;">Vector2d</span>[] points)<o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="font-family: Consolas; font-size: 12pt;"> : <span style="color: blue;">this</span>(points.Select(point => <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>((<span style="color: blue;">float</span>)point.X, (<span style="color: blue;">float</span>)point.Y)).ToArray())<o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="font-family: Consolas; font-size: 12pt;">{<o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="font-family: Consolas; font-size: 12pt;">}<o:p></o:p></span></div></pre><br />
This is a very simple alternate constructor for my <c>Shape</c> class. It is a convenience constructor to allow passing in <c>Vector2d</c> values (these are <c>double</c> based vectors) instead of <c>Vector2</c> values (these are <c>float</c> based). The LINQ expression just enumerates over the array and converts the values.<br />
<br />
So, why doesn't this work? Well, the error I get is something like:<br />
<pre>Attempting to JIT compile method
'System.Linq.Enumerable/PredicateOf`1<OpenTK.Vector2d>:.cctor
()' while running with --aot-only.
</pre><br />
I believe what this is saying is that the constructor for <c>System.Linq.Enumerable/PredicateOf`1<OpenTK.Vector2d></c> is not found, and since JIT isn't supported it can't compile it at runtime. The reason it isn't compiled initially is because the compiler doesn't recognize that the LINQ expression will ultimately need this constructor. The way around this is it reference the needed type explicitly in code. Unfortunately, I didn't figure out a way to do that. Fortunately, I found a different workaround, although it does include a little bit of unnecessary overhead:<br />
<br />
<div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><summary></span><span style="color: green; font-family: Consolas; font-size: 12pt;">Initializes a new instance of the </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><see cref="Shape"/></span><span style="color: green; font-family: Consolas; font-size: 12pt;"> class.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></summary></span><span style="font-family: Consolas; font-size: 12pt;"><o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><param name="points"></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The points.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></param></span><span style="font-family: Consolas; font-size: 12pt;"><o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="color: blue; font-family: Consolas; font-size: 12pt;">public</span><span style="font-family: Consolas; font-size: 12pt;"> Shape(<span style="color: #2b91af;">Vector2d</span>[] points)<o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="font-family: Consolas; font-size: 12pt;"> : <span style="color: blue;">this</span>(<span style="color: blue;">new</span> <span style="color: #2b91af;">List</span><<span style="color: #2b91af;">Vector2d</span>>(points).Select(point => <span style="color: blue;">new</span> <span style="color: #2b91af;">Vector2</span>((<span style="color: blue;">float</span>)point.X, (<span style="color: blue;">float</span>)point.Y)).ToArray())<o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="font-family: Consolas; font-size: 12pt;">{<o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"><span style="font-family: Consolas; font-size: 12pt;">}<o:p></o:p></span></div><div class="MsoNormal" style="line-height: normal; margin-bottom: .0001pt; margin-bottom: 0cm; mso-layout-grid-align: none; text-autospace: none;"></div><br />
In this case, the LINQ expression is over the <c><span style="font-family: Consolas; font-size: 12pt;"><span style="color: #2b91af;">List</span><<span style="color: #2b91af;">Vector2d</span>></span></c>, which works fine. I believe the reason this works is because <c><span style="font-family: Consolas; font-size: 12pt;"><span style="color: #2b91af;">List</span><<span style="color: #2b91af;">Vector2d</span>></span></c> explicitly implements <c>System.Linq.Enumerable/PredicateOf`1<OpenTK.Vector2d></c>, where the array does not.<br />
<br />
I need to play around with this a bit more to see how I can fix it without the <c>List</c> wrapper. The problem is this only shows up when I deploy to the iPhone, so the build/run/test cycle is a bit slower. For now my workaround works ok, so improving this is not a high priority.<br />
<br />
Next time: <a href="http://blog.qythyx.com/2010/12/phunky-physics.html">Phunky Physics</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-87041952805064665822010-11-18T08:57:00.003+09:002014-01-23T13:03:38.576+09:00Fighting with BloggerIn my <a href="http://blog.qythyx.com/2010/10/optimizing.html">previous post</a> I described some work I spent optimizing my .NET code to make it perform well on an iPhone. I expect I'll have a follow up post when I do more optimizations in the future, but next I want to take a slight detour from coding to discuss a small battle I had with getting Blogger to work well with the type of info I'm posting.<br />
<br />
Before getting into that discussion I want briefly give an update on the game. These blog posts are lagging actual development by a couple months. Basically it takes me a while to finish a blog entry, so I try to queue them up and revise them when I have time. So, in reality the game is coming along pretty well. It is still slow going because I can only put in a few hours here and there, but it is progressing nicely.<br />
<br />
Now, on to fighting with Blogger...<br />
<br />
So, as mentioned above, I wanted to be able to post source code that looks good and has syntax highlighting. Initially I tried a simple solution that I know worked to some degrees from my previous experience, simply copy the code from Visual Studio (VS) and paste into Blogger. Oops, no, that doesn't quite work. If you do that no formatting is included. But, if you copy and paste into Word then it is included. Hmm, ok, how about copying and pasting into Word, then re-copying from Word and pasting into Blogger? Yep, that works, and gives output like below:<br />
<pre><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><summary></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> Binds the texture.</span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></summary></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><param name="textureData"></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The texture data.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></param></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><param name="width"></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The width.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></param></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><param name="height"></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The height.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></param></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><returns></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The texture's bound id.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></returns></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: blue; font-family: Consolas; font-size: 12pt;">private</span><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: blue;">static</span> <span style="color: blue;">uint</span> BindTexture(<span style="color: blue;">byte</span>[] textureData, <span style="color: blue;">int</span> width, <span style="color: blue;">int</span> height)</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;">{</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: blue;">uint</span> textureId = 0;</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.GenTextures(1, <span style="color: blue;">ref</span> textureId);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.BindTexture(<span style="color: #2b91af;">All</span>.Texture2D, textureId);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">All</span> format = (System.<span style="color: #2b91af;">Environment</span>.OSVersion.Platform == <span style="color: #2b91af;">PlatformID</span>.Win32NT) ? <span style="color: #2b91af;">All</span>.Bgra : <span style="color: #2b91af;">All</span>.Rgba;</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"> //</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> useless comment placed here just to make the line very long</span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.TexImage2D(<span style="color: #2b91af;">All</span>.Texture2D, 0, (<span style="color: blue;">int</span>)format, width, height, 0, format, <span style="color: #2b91af;">All</span>.UnsignedByte, textureData);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> textureData = <span style="color: blue;">null</span>;</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.TexParameter(<span style="color: #2b91af;">All</span>.Texture2D, <span style="color: #2b91af;">All</span>.TextureMinFilter, (<span style="color: blue;">float</span>)<span style="color: #2b91af;">All</span>.Linear);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.TexParameter(<span style="color: #2b91af;">All</span>.Texture2D, <span style="color: #2b91af;">All</span>.TextureMagFilter, (<span style="color: blue;">float</span>)<span style="color: #2b91af;">All</span>.Linear);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.Enable(<span style="color: #2b91af;">All</span>.Texture2D);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: blue;">return</span> textureId;</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;">}</span></div></pre><br />
Actually, the above is wrapped in a <c>pre</c>, which I have styled to make the grey background and such. But, there's still a problem, the long lines wrap and make it harder to read. So, what I really want is for the code area to scroll horizontally when the lines are too long. Fortunately, a bit of CSS can do that:<br />
<br />
<pre>.csharpcode
{
font-family: Consolas, "Courier New", Courier, Monospace;
margin: 0em;
padding: 0.5em;
border: solid 0.1em #000000;
background: #222222;
white-space: pre;
white-space: nowrap;
overflow: auto;
overflow-y: hidden;
}
</pre><br />
So, if I change the simple <c>pre</c> to <c>pre class="csharpcode"</c> then I get the following instead. Notice the horizontal scrollbar and the long lines no longer wrap.<br />
<pre class="csharpcode"><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><summary></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> Binds the texture.</span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></summary></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><param name="textureData"></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The texture data.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></param></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><param name="width"></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The width.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></param></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><param name="height"></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The height.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></param></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;"><returns></span><span style="color: green; font-family: Consolas; font-size: 12pt;">The texture's bound id.</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"></returns></span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: blue; font-family: Consolas; font-size: 12pt;">private</span><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: blue;">static</span> <span style="color: blue;">uint</span> BindTexture(<span style="color: blue;">byte</span>[] textureData, <span style="color: blue;">int</span> width, <span style="color: blue;">int</span> height)</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;">{</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: blue;">uint</span> textureId = 0;</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.GenTextures(1, <span style="color: blue;">ref</span> textureId);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.BindTexture(<span style="color: #2b91af;">All</span>.Texture2D, textureId);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">All</span> format = (System.<span style="color: #2b91af;">Environment</span>.OSVersion.Platform == <span style="color: #2b91af;">PlatformID</span>.Win32NT) ? <span style="color: #2b91af;">All</span>.Bgra : <span style="color: #2b91af;">All</span>.Rgba;</span><span style="color: grey; font-family: Consolas; font-size: 12pt;"> //</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> useless comment placed here just to make the line very long</span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.TexImage2D(<span style="color: #2b91af;">All</span>.Texture2D, 0, (<span style="color: blue;">int</span>)format, width, height, 0, format, <span style="color: #2b91af;">All</span>.UnsignedByte, textureData);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> textureData = <span style="color: blue;">null</span>;</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.TexParameter(<span style="color: #2b91af;">All</span>.Texture2D, <span style="color: #2b91af;">All</span>.TextureMinFilter, (<span style="color: blue;">float</span>)<span style="color: #2b91af;">All</span>.Linear);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.TexParameter(<span style="color: #2b91af;">All</span>.Texture2D, <span style="color: #2b91af;">All</span>.TextureMagFilter, (<span style="color: blue;">float</span>)<span style="color: #2b91af;">All</span>.Linear);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: #2b91af;">GL</span>.Enable(<span style="color: #2b91af;">All</span>.Texture2D);</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;"> <span style="color: blue;">return</span> textureId;</span></div><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="font-family: Consolas; font-size: 12pt;">}</span></div></pre><br />
The only remaining problem is that this HTML that comes from VS via Word is <b>very</b> verbose. Just the very first line, a comment, contains all of this HTML:<br />
<pre><div class="MsoNormal" style="line-height: normal; margin: 0in 0in 0pt; mso-layout-grid-align: none;"><span style="color: grey; font-family: Consolas; font-size: 12pt;">///</span><span style="color: green; font-family: Consolas; font-size: 12pt;"> </span><span style="color: grey; font-family: Consolas; font-size: 12pt;">&lt;summary&gt;</span><span style="font-family: Consolas; font-size: 12pt;"></span></div>
</pre><br />
So, the entire code block ends up being quite a lot of HTML.<br />
<br />
To address this issue I actually jumped through a number of other hoops to create less verbose HTML. In fact, the earlier pages in this blog use that simpler HTML. But, going forward, I've decided to use the above method. It is easier and creates more nicely formatted output. It is a bit more verbose, but then if you look at all of the other CSS that Blogger includes the pages are pretty heavy to start with.<br />
<br />
I actually might go back and reformat the previous pages at some point, so the above statement might become inaccurate.<br />
<br />
Anyway, I guess my fight with Blogger is a lot less interesting that I thought. As I was figuring out how to do all of the above it was annoyingly painful. But not that I have a solution it is pretty simple.<br />
<br />
Next time: <a href="http://blog.qythyx.com/2010/11/more-monotouch-gotchas.html">More MonoTouch Gotchas</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com1tag:blogger.com,1999:blog-4882408034198003350.post-86055076092307507572010-10-24T21:53:00.002+09:002010-11-18T08:57:48.340+09:00Optimizing .NET on the iPhone<div class="image"><img src="http://www.qythyx.com/zoing/Splashsmall.png" /><br />
Early Splash Screen</div><br />
After working exclusively on my PC for a while adding functionality I decided to deploy to my iPhone to make sure everything still works well. When I did that I was happy to see that it did run correctly, but not so happy to see pretty poor performance.<br />
<br />
In the game's splash screen I was only getting about 10 FPS, even though I'm shooting for 30. The splash screen is ironically more computationally intensive than a real game screen because of the grid resolution. For a real game screen the board is around 10x15 squares, depending on the level. For the splash screen, currently, it is 20x30 squares. That a difference from 176 Poles on the game screen to 651 on the splash screen. Clearly I needed to either optimize a lot or reduce the complexity of the splash screen. On the other hand, if I could get the splash screen performing well, then that would be a good baseline such that if it worked well on a particular device then the real game screens should be just fine.<br />
<br />
Because of my platform independent abstraction efforts I described in an <a href="http://blog.qythyx.com/2010/09/divide-and-conquer.html">earlier post</a>, I have access to the sophisticated profiling tools in Visual Studio (VS) and can quickly find the areas of code that are taking the most time. I realize that Xcode on Mac includes the Instruments tool, but so far I haven't been able to get it to work well with my MonoTouch generated code so that no symbol names are displayed. Only function addresses are shown, so it is nearly useless for this type of investigation. I've reached out to the MonoTouch community and hopefully there is a way to get symbols displayed, but for now I'll rely on VS.<br />
<br />
<h4 style="font-size: 120%;">Too Many Objects</h4>One area I suspected might become a problem even when I was first creating it was my <c>Triangle</c> object. It contained 3 <c>Vector2</c> objects, one for each vertex of the triangle. It also has a number of manipulation methods (rotate, scale, etc) which calculate new vertices and replace the old ones with the new ones.<br />
<br />
The problem is that the game does these triangle manipulations for almost every single graphic element for every frame. In the case of the splash screen thats <c>651 x 3 x 30 = 58590</c> new <c>Vertex2</c> objects created every second, and that's just for the poles on the screen, there are many other elements as well. Although the .NET memory management system is quite efficient, this is still a lot of overhead that is completely unnecessary if I change how my <c>Triangle</c> works. Which is exactly what I did...<br />
<br />
Instead of using <c>Vertex2</c> objects for my triangle's points, I changed them to 6 <c>float</c> values. The 6 values are the <c>X</c> and <x>Y</x> values for each of the 3 vertices. I used <c>float</c> because that is what I later pass to OpenGL and want to avoid the overhead of casting.<br />
<br />
Unfortunately, the result of this rewrite was that there was not measurable performance gain in either CPU usage or memory allocation. How can that be? Well, it turns out that <c>Vector2</c> is a struct, not an object. This means that it is not allocated and garbage collected in the same way and creating new instances of it is quite light weight. Although I should have checked that <c>Vertex2</c> is a class before embarking on this optimization it didn't take that much time and I learned a bit.<br />
<br />
<h4 style="font-size: 120%;">Array Bounds Checking</h4>OpenGL requires an array of <c>float</c> values in <c>GL.VertexPointer</c> and <c>GL.TexCoordPointer</c>. To populate this array I go through each of my graphic elements and add its vertices to the end of the array. In .NET there is automatic bounds checking performed on arrays to ensure you do not access memory outside of the array. This bounds checking is great for safety, but there is a performance penalty associated with it.<br />
<br />
To see if it was significant for me, I just wrapped the area of code that populates the array with an <c>unsafe { ... }</c> block. After doing this that function went from about 26% total CPU usage down to about 23%. It's only a 3% improvement, but it's a very simple one. Since I carefully calculate my array index in this routine I'm confident the lack of bounds checking will be ok in this case.<br />
<br />
<h4 style="font-size: 120%;">Too Many Function Calls</h4>Initially when I populated the <c>float</c> arrays for OpenGL I was calling a property for each vertex in my triangle one at a time. I changed this so that I pass in an array and starting index into the triangle and it populates it. This reduced the number of property accesses by 6 times.<br />
<br />
Unfortunately I didn't profile this change carefully so I can't say how much of an affect it had, but I imagine it would have been measurable reducing more than 60,000 property calls to closer to 10,000 method calls.<br />
<br />
<h4 style="font-size: 120%;">Too Many Calculations</h4>My triangle object remembers its original state (position, rotation, scale, etc) so that I can manipulate it based on that state. A side affect of this is that when I want to get the actual vertices I need to calculate them based on its original state and the current transformations. Initially I was doing this every time I needed the vertices, even if its state or transformations hadn't changed.<br />
<br />
To fix this I added a boolean flag, <c>isDirty</c> that I use to keep track of it state. If its state changes then I set the flag to <c>true</c> and then next time I need the vertices I calculate them only if that flag is set to <c>true</c> and then set it to <c>false</c>.<br />
<br />
This optimization reduced these calculations from about 29% of CPU time to 11%. Now, finally, a nice gain.<br />
<br />
<h4 style="font-size: 120%;">Too Many Draws</h4>After all of the above investigations and optimizations, it turned out the biggest improvement I made was from a stupid mistake.<br />
<br />
I refactored the class that acts as the main view manager in order to better support different views; a game view and a splash screen view in this case. In doing that refactor I reduced the two-phase update-then-draw functionality into one-phase update-and-draw. In my case there's no real benefit to either case, and the single phase seemed a bit simpler. When I did this, however, the main render loop, which is one of the platform-dependent classes, still called both phases. As part of the refactor the update method became renamed to the draw one. The consequence of this was that on the iPhone version of the render loop, the draw method was called twice, once when it used to be for update and then again for the draw. Since I did the refactor on my PC I tested it there and must have fixed this problem for the PC version of the render loop. When I went to test the iPhone version, however, it was still doing this double work.<br />
<br />
Anyway, when I found the problem and removed the redundancy there was a significant performance improvement. That, along with my other optimizations, means the game is back to running my targeted 30 FPS, even in the more complex splash screen. When it's doing that it's eating about 70% CPU, and I'd really like to bring that down for battery life and to better support older devices. I do have one more significant idea based on data from the VS profiler, but I haven't gotten to it. If it works well I'm hoping for about a 10% gain in efficiency. If and when I do another pass as optimizations I'll write about them here.<br />
<br />
Next time: <a href="http://blog.qythyx.com/2010/11/fighting-with-blogger.html">Fighting with Blogger</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com1tag:blogger.com,1999:blog-4882408034198003350.post-3452961881293020692010-10-21T23:46:00.001+09:002014-01-23T10:29:16.452+09:00Problems Deploying to iPhoneSorry for the brief hiatus, I was on a business trip to Beijing for 1 week, but I'm back now and hope to get back into the swing of developing.<br />
<br />
In my <a href="http://blog.qythyx.com/2010/10/openal-on-windows.html">last post</a> I discussed how I got OpenAL working on Windows. After that I wanted to confirm that all my platform independent abstractions worked correctly on the iPhone but going back to my Mac and building and deploying there. Unfortunately, when I did that, I ran into a few issues. This post discusses that experience.<br />
<br />
The first error I received was <code>mtouch failed with no output (1)</code>. After a bit of searching on the web I found a few hints about not allowing spaces in the output assembly name. I changed some settings to correct this, but the problem persisted.<br />
<br />
I looked at the command being from by MonoTouch and noticed other areas in the command were not escaping or quoting some paths. Initially I was accessing my project over a Samba share directly to my Windows Documents directory. This meant that I was opening the project on my Mac from <code>Documents\visual studio 2010\Projects\Zoing</code>. Notice the spaces in "visual studio 2010".<br />
<br />
To fix this I changed how I mounted the Samba share to directly mount the <code>Zoing</code> directory, thus skipping the areas with spaces. After doing this I could deploy to my iPhone. Joy!<br />
<br />
Well, no, actually not. As soon as I ran the program I got this:<br />
<pre><div class="hanging">Tue Sep 28 23:46:25 unknown kernel[0] <Debug>: launchd[9744] Builtin profile: container (sandbox) Tue Sep 28 23:46:25 unknown kernel[0] <Debug>: launchd[9744] Container: /private/var/mobile/Applications/478863D8-A953-4D5A-84FD-AF8C096DC363 [69] (sandbox) Tue Sep 28 23:46:27 unknown UIKitApplication:launcher[0x114c][9744] <Notice>: Unhandled Exception: System.TypeInitializationException: <span class="highlight">An exception was thrown by the type initializer for System.Collections.Generic.EqualityComparer`1</span> ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ExecutionEngineException: <span class="highlight">Attempting to JIT compile method 'System.Collections.Generic.GenericEqualityComparer`1<OpenTK.Vector2>:.ctor</span> ()' while running with --aot-only.</div><div class="hanging">Tue Sep 28 23:46:27 unknown UIKitApplication:launcher[0x114c][9744] <Notice>: at System.Reflection.MonoCMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in <filename unknown>:0 </div><div class="hanging">Tue Sep 28 23:46:27 unknown UIKitApplication:launcher[0x114c][9744] <Notice>: --- End of inner exception stack trace ---</div></pre><br />
The highlighted areas point to the important part. Basically, for some reason, MonoTouch, on the actual iPhone (it works fine in the simulator) does not support default <code>EqualityComparers</code> properly. Fortunately, I can pass in a <code>IEqualityComparer<T></code> to the <code>Dictionary</code> constructor, like so:<br />
<pre class="csharpcode">_diagonals = <span class="kwrd">new</span> Dictionary<Vector2, Diagonal>(VectorComparer.Singleton);</pre><br />
And, my <code>VectorComparer</code> implementation is:<br />
<pre class="csharpcode"><div><span class="kwrd">private</span> <span class="kwrd">class</span> VectorComparer : IEqualityComparer<Vector2></div><div>{</div><div> <span class="kwrd">private</span> <span class="kwrd">static</span> VectorComparer _singleton = <span class="kwrd">new</span> VectorComparer();</div><div></div><div> <span class="kwrd">public</span> <span class="kwrd">static</span> VectorComparer Singleton</div><div> {</div><div> get</div><div> {</div><div> <span class="kwrd">return</span> _singleton;</div><div> }</div><div> }</div><div></div><div> <span class="kwrd">public</span> <span class="kwrd">bool</span> Equals(Vector2 x, Vector2 y)</div><div> {</div><div> <span class="kwrd">return</span> x.Equals(y);</div><div> }</div><div></div><div> <span class="kwrd">public</span> <span class="kwrd">int</span> GetHashCode(Vector2 obj)</div><div> {</div><div> <span class="kwrd">return</span> obj.GetHashCode();</div><div> }</div><div>}</div></pre><br />
Currently, for my purposes, this implementation is fine because I know I'm only putting in <code>Vector2</code> objects with values that are precise as a <code>float</code>. If I expect to want to compare <code>Vector2</code> objects where, because of math rounding, the values may not be so precise I could adjust my comparisons to have some level of necessary precision to consider the values equal.<br />
<br />
Next time: <a href="http://blog.qythyx.com/2010/10/optimizing.html">Optimizing .NET on the iPhone</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-6105566857074809672010-10-07T22:23:00.001+09:002010-10-21T23:46:35.528+09:00OpenAL on WindowsIn my <a href="http://blog.qythyx.com/2010/10/opengl-es-11-on-windows.html">previous post</a> I discussed the graphics related word I had to do to get my iPhone targeted game running on my PC. This was relatively easy to do and has improved my productivity significantly. Of course, my game contains sound as well, and it is important to be able to develop those aspects of it on my PC as well. This post discusses that.<br />
<br />
From the beginning I decided to use <a href="http://connect.creativelabs.com/openal">OpenAL</a> for sound in my game. The main reason is it provides relatively simple access to 3D positioned audio, which is something I wanted (actually, I really just am working 2D space). Getting this working on the iPhone initially in native Objective-C was pretty straight forward, but, as with OpenGL, OpenAL is not natively available on Windows. The OpenTK library that MonoTouch relies on has built in support for OpenAL, but it still needs an underlying library.<br />
<br />
I searched around a bit and quickly found <a href="http://connect.creativelabs.com/openal/Downloads/Forms/AllItems.aspx">Creative Labs' OpenAL library for Windows</a>. This installed easily, but I still needed to implement the layer to reads WAV files and converts them into a format that OpenAL understands.<br />
<br />
In the iPhone version I do this to initialize OpenAL: <br />
<pre class="csharpcode"><div><span class="rem">/// <summary></span></div><div><span class="rem">/// As per the SDK:</span></div><div><span class="rem">/// <br/></span></div><div><span class="rem">/// Your application must call this function before making any other Audio Session Services calls.</span></div><div><span class="rem">/// You may activate and deactivate your audio session as needed (see AudioSessionSetActive),</span></div><div><span class="rem">/// but should initialize it only once.</span></div><div><span class="rem">/// </summary></span></div><div><span class="kwrd">public</span> SoundManager()</div><div>{</div><div> <span class="rem">// setup our audio session</span></div><div> AudioSession.Initialize();</div><div> AudioSession.Category = AudioSessionCategory.AmbientSound;</div><div> AudioSession.SetActive(<span class="kwrd">true</span>);</div><div> AudioSession.Interrupted += HandleAudioSessionInterrupted;</div><div> AudioSession.Resumed += HandleAudioSessionResumed;</div><div></div><div> <span class="rem">// TODO: Check if BGM is playing</span></div><div> <span class="rem">// UInt32 size = sizeof(iPodIsPlaying);</span></div><div> <span class="rem">// result = AudioSessionGetProperty(kAudioSessionProperty_OtherAudioIsPlaying, &size, &iPodIsPlaying);</span></div><div></div><div> <span class="rem">// if the iPod is playing, use the ambient category to mix with it</span></div><div> <span class="rem">// otherwise, use solo ambient to get the hardware for playing the app background track</span></div><div> <span class="rem">// UInt32 category = (iPodIsPlaying) ? kAudioSessionCategory_AmbientSound : kAudioSessionCategory_SoloAmbientSound;</span></div><div></div><div> OpenALManager = <span class="kwrd">new</span> OpenALManager();</div><div>}</div></pre><br />
And then this to load CAF sound samples: <br />
<pre class="csharpcode"><div><span class="kwrd">private</span> <span class="kwrd">static</span> AudioFile GetAudioFile(<span class="kwrd">string</span> filename)</div><div>{</div><div> <span class="kwrd">if</span>(!File.Exists(filename)) {</div><div> <span class="kwrd">throw</span> <span class="kwrd">new</span> FileNotFoundException(<span class="str">"Could not find sound file."</span>, filename);</div><div> }</div><div></div><div> AudioFileType fileType;</div><div> <span class="kwrd">switch</span>(Path.GetExtension(filename).ToUpper()) {</div><div> <span class="kwrd">case</span> <span class="str">".CAF"</span>:</div><div> fileType = AudioFileType.CAF;</div><div> <span class="kwrd">break</span>;</div><div> <span class="kwrd">default</span>:</div><div> <span class="kwrd">throw</span> <span class="kwrd">new</span> NotSupportedException(<span class="str">"Audio files of type "</span> + Path.GetExtension(filename) + <span class="str">" are not supported."</span>);</div><div> }</div><div></div><div> <span class="kwrd">using</span>(CFUrl cfUrl = CFUrl.FromFile(filename)) {</div><div> <span class="kwrd">return</span> AudioFile.Open(cfUrl, AudioFilePermission.Read, fileType);</div><div> }</div><div>}</div><div></div><div><span class="kwrd">private</span> <span class="kwrd">static</span> AudioData GetAudioData(AudioFile audioFile)</div><div>{</div><div> <span class="rem">// Set the client format to 16 bit signed integer (native-endian) data</span></div><div> <span class="rem">// Maintain the channel count and sample rate of the original source format</span></div><div> AudioStreamBasicDescription outputFormat = <span class="kwrd">new</span> AudioStreamBasicDescription();</div><div> outputFormat.SampleRate = audioFile.StreamBasicDescription.SampleRate;</div><div> outputFormat.ChannelsPerFrame = audioFile.StreamBasicDescription.ChannelsPerFrame;</div><div></div><div> outputFormat.Format = AudioFormatType.LinearPCM;</div><div> outputFormat.BytesPerPacket = 2 * audioFile.StreamBasicDescription.ChannelsPerFrame;</div><div> outputFormat.FramesPerPacket = 1;</div><div> outputFormat.BytesPerFrame = 2 * audioFile.StreamBasicDescription.ChannelsPerFrame;</div><div> outputFormat.BitsPerChannel = 16;</div><div> outputFormat.FormatFlags = AudioFormatFlags.IsPacked | AudioFormatFlags.IsSignedInteger;</div><div></div><div> <span class="rem">// Set the desired client (output) data format</span></div><div> <span class="kwrd">bool</span> hadError = audioFile.SetProperty(AudioFileProperty.DataFormat, Marshal.SizeOf(outputFormat), GCHandle.ToIntPtr(GCHandle.Alloc(outputFormat)));</div><div> <span class="kwrd">if</span>(hadError) {</div><div> <span class="kwrd">throw</span> <span class="kwrd">new</span> InvalidOperationException(<span class="str">"Could not set output format."</span>);</div><div> }</div><div></div><div> <span class="kwrd">byte</span>[] data = <span class="kwrd">new</span> <span class="kwrd">byte</span>[audioFile.Length];</div><div> audioFile.Read(0, data, 0, (<span class="kwrd">int</span>)audioFile.Length, <span class="kwrd">false</span>);</div><div> AudioData audioData = <span class="kwrd">new</span> AudioData(data, outputFormat.ChannelsPerFrame == 1 ? ALFormat.Mono16 : ALFormat.Stereo16, outputFormat.SampleRate);</div><div></div><div> <span class="kwrd">return</span> audioData;</div><div>}</div></pre><br />
For the PC I had to do something similar, except for WAV files, not CAF:<br />
<br />
<pre class="csharpcode"><div><span class="kwrd">using</span> System;</div><div><span class="kwrd">using</span> System.IO;</div><div><span class="kwrd">using</span> OpenTK.Audio;</div><div><span class="kwrd">using</span> OpenTK.Audio.OpenAL;</div><div><span class="kwrd">using</span> Qythyx.OpenTKTools.Sound;</div><div></div><div><span class="kwrd">namespace</span> Qythyx.Launcher_PC</div><div>{</div><div> <span class="kwrd">internal</span> <span class="kwrd">sealed</span> <span class="kwrd">class</span> WaveReader</div><div> {</div><div> <span class="kwrd">private</span> <span class="kwrd">static</span> ALFormat GetALFormat(WaveData data)</div><div> {</div><div> <span class="kwrd">switch</span>(data.Channels)</div><div> {</div><div> <span class="kwrd">case</span> 1:</div><div> <span class="kwrd">if</span>(data.BitsPerSample == 8)</div><div> {</div><div> <span class="kwrd">return</span> ALFormat.Mono8;</div><div> }</div><div> <span class="kwrd">else</span> <span class="kwrd">if</span>(data.BitsPerSample == 16)</div><div> {</div><div> <span class="kwrd">return</span> ALFormat.Mono16;</div><div> }</div><div> <span class="kwrd">break</span>;</div><div> <span class="kwrd">case</span> 2:</div><div> <span class="kwrd">if</span>(data.BitsPerSample == 8)</div><div> {</div><div> <span class="kwrd">return</span> ALFormat.Stereo8;</div><div> }</div><div> <span class="kwrd">else</span> <span class="kwrd">if</span>(data.BitsPerSample == 16)</div><div> {</div><div> <span class="kwrd">return</span> ALFormat.Stereo16;</div><div> }</div><div> <span class="kwrd">break</span>;</div><div> }</div><div></div><div> <span class="kwrd">throw</span> <span class="kwrd">new</span> AudioException(<span class="str">"Unsupported audio format. Channels = "</span> + data.Channels + <span class="str">", Bits per Sample = "</span> + data.BitsPerSample);</div><div> }</div><div></div><div> <span class="rem">/// <summary>Reads and decodes the sound file.</summary></span></div><div> <span class="rem">/// <param name="filename">The WAVE filename.</param></span></div><div> <span class="rem">/// <returns>An <see cref="AudioData"/> object that contains the decoded data.</returns></span></div><div> <span class="kwrd">public</span> <span class="kwrd">static</span> AudioData ReadWave(<span class="kwrd">string</span> filename)</div><div> {</div><div> WaveData data = ReadWaveData(filename);</div><div> <span class="kwrd">return</span> <span class="kwrd">new</span> AudioData(data.Data, GetALFormat(data), data.SampleRate);</div><div> }</div><div></div><div> <span class="kwrd">private</span> <span class="kwrd">struct</span> WaveData</div><div> {</div><div> <span class="kwrd">public</span> <span class="kwrd">int</span> RiffChunckSize;</div><div> <span class="kwrd">public</span> <span class="kwrd">int</span> FormatChunkSize;</div><div> <span class="kwrd">public</span> <span class="kwrd">short</span> AudioFormat;</div><div> <span class="kwrd">public</span> <span class="kwrd">short</span> Channels;</div><div> <span class="kwrd">public</span> <span class="kwrd">int</span> SampleRate;</div><div> <span class="kwrd">public</span> <span class="kwrd">int</span> ByteRate;</div><div> <span class="kwrd">public</span> <span class="kwrd">short</span> BlockAlign;</div><div> <span class="kwrd">public</span> <span class="kwrd">short</span> BitsPerSample;</div><div> <span class="kwrd">public</span> <span class="kwrd">int</span> DataChunkSize;</div><div> <span class="kwrd">public</span> <span class="kwrd">byte</span>[] Data;</div><div> }</div><div></div><div> <span class="rem">// Read the WAVE/RIFF headers.</span></div><div> <span class="kwrd">private</span> <span class="kwrd">static</span> WaveData ReadWaveData(<span class="kwrd">string</span> filename)</div><div> {</div><div> <span class="kwrd">using</span>(Stream stream = <span class="kwrd">new</span> FileStream(filename, FileMode.Open, FileAccess.Read))</div><div> {</div><div> <span class="kwrd">using</span>(BinaryReader reader = <span class="kwrd">new</span> BinaryReader(stream))</div><div> {</div><div> WaveData data = <span class="kwrd">new</span> WaveData();</div><div></div><div> <span class="rem">// RIFF header</span></div><div> <span class="kwrd">if</span>(<span class="kwrd">new</span> <span class="kwrd">string</span>(reader.ReadChars(4))!= <span class="str">"RIFF"</span>)</div><div> {</div><div> <span class="kwrd">throw</span> <span class="kwrd">new</span> FormatException(<span class="str">"File is not recognized as valid WAVE format. Can't find RIFF signature."</span>);</div><div> }</div><div></div><div> data.RiffChunckSize = reader.ReadInt32();</div><div></div><div> <span class="kwrd">if</span>(<span class="kwrd">new</span> <span class="kwrd">string</span>(reader.ReadChars(4)) != <span class="str">"WAVE"</span>)</div><div> {</div><div> <span class="kwrd">throw</span> <span class="kwrd">new</span> FormatException(<span class="str">"File is not recognized as valid WAVE format. Can't find expected WAVE format."</span>);</div><div> }</div><div></div><div> <span class="rem">// WAVE header</span></div><div> <span class="kwrd">if</span>(<span class="kwrd">new</span> <span class="kwrd">string</span>(reader.ReadChars(4)) != <span class="str">"fmt "</span>)</div><div> {</div><div> <span class="kwrd">throw</span> <span class="kwrd">new</span> FormatException(<span class="str">"File is not recognized as valid WAVE format. Can't find 'fmt' marker."</span>);</div><div> }</div><div></div><div> data.FormatChunkSize = reader.ReadInt32();</div><div> data.AudioFormat = reader.ReadInt16();</div><div> data.Channels = reader.ReadInt16();</div><div> data.SampleRate = reader.ReadInt32();</div><div> data.ByteRate = reader.ReadInt32();</div><div> data.BlockAlign = reader.ReadInt16();</div><div> data.BitsPerSample = reader.ReadInt16();</div><div></div><div> <span class="kwrd">while</span>(reader.PeekChar() == 0)</div><div> {</div><div> reader.Read();</div><div> }</div><div> <span class="kwrd">if</span>(<span class="kwrd">new</span> <span class="kwrd">string</span>(reader.ReadChars(4)) != <span class="str">"data"</span>)</div><div> {</div><div> <span class="kwrd">throw</span> <span class="kwrd">new</span> FormatException(<span class="str">"File is not recognized as valid WAVE format. Can't find data marker."</span>);</div><div> }</div><div></div><div> data.DataChunkSize = reader.ReadInt32();</div><div></div><div> data.Data = reader.ReadBytes((<span class="kwrd">int</span>)stream.Length);</div><div></div><div> <span class="kwrd">return</span> data;</div><div> }</div><div> }</div><div> }</div><div> }</div><div>}</div></pre><br />
Well, after this OpenAL was happy on my PC and I've now got sound and graphics and I can develop happily in Visual Studio. Woohoo!<br />
<br />
Next time: <a href="http://blog.qythyx.com/2010/10/problems-deploying-to-iphone.html">Problems deploying to iPhone</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com3tag:blogger.com,1999:blog-4882408034198003350.post-85493725548101199052010-10-04T09:46:00.010+09:002014-01-23T10:27:51.059+09:00OpenGL ES 1.1 on Windows<div class="image"><img src="http://www.qythyx.com/zoing/WrongTextures%28small%29.png"/><br />
Wrong Colors</div><div class="image"><img src="http://www.qythyx.com/zoing/CorrectTextures%28small%29.png"/><br />
Correct Colors</div>In my <a href="http://blog.qythyx.com/2010/09/divide-and-conquer.html">previous post</a> I discussed how I separated my .NET solution into multiple projects with the goal of having the bulk of my code 100% platform independent. The initial goal of this was to allow me to easily develop with in Visual Studio (VS), but a second and important benefit is it sets me up well to port my game to other .NET platforms like Android and Windows Mobile 7. I also mentioned that the graphics framework I'm targeting is OpenGL ES version 1.1. Although iPhone 4 supports OpenGL ES 2.0, older iPhones do not and I want to support them as well.<br />
<br />
To get the full benefits of developing in VS I not only want to be able to compile, but also run my game. To do that I need to be able to render using Open GL ES 1.1. The problem is that this is a version of OpenGL that is for mobile devices. Fortunately, there are a few emulators available for the PC.<br />
<br />
After some web searching I found an <a href="http://www.malideveloper.com/developer-resources/tools/software-development/opengl-es-11-emulator.php">emulator by from Mali</a>, but when I tried to use it I kept getting this error: <code>[Error] Failed to create EGL window surface, error 12293</code>. I did a lot of searching and investigation to fix it, but was never able to. I did see one person describe that they were successful, but their suggestions weren't working for me, so I eventually gave up and tried to find another emulator.<br />
<br />
Next I found <a href="http://www.imgtec.com/PowerVR/insider/sdk/KhronosOpenGLES1xMBX.asp">this one from Khronos</a>. It still had a few minor issues, but when I followed the steps described in <a href="http://www.opentk.com/node/1643">this thread</a> I got it to work. The thread said that they also managed to get the Mali emulator working, and I don't know why it didn't work for me.<br />
<br />
One interesting thing I found with this emulator was that it isn't 100% compatible with the OpenGL ES 1.1 that runs on the iPhone. Specifically the <code>internalFormat</code> argument passed to <code>GL.TexImage2D</code> seems different. In my original iPhone version I passed <code>All.Rgba</code> for this, but when I ran this on the PC my textures' colors were strange. After a lot of investigation I finally discovered that changing this to <code>All.Bgra</code> fixes the problem. I currently have a hack to set this depending on the OS.<br />
<br />
<pre class="csharpcode"><div><span class="kwrd">bool</span> isWindows = System.Environment.OSVersion.Platform == PlatformID.Win32NT;</div><div>All <span class="highlight">format = isWindows ? All.Bgra : All.Rgba;</span></div><div>GL.TexImage2D(All.Texture2D, 0, <span class="highlight">(<span class="kwrd">int</span>)format</span>, width, height, 0, format, All.UnsignedByte, textureData);</div></pre><br />
I'm not enough of a OpenGL expert to know which is actually correct. It will also be interesting to see how other platforms like Android and Windows Mobile 7 handle this. For now I'm just pretty happy that this was the only platform-dependent adjustment I've had to make.<br />
<br />
Next time: <a href="http://blog.qythyx.com/2010/10/openal-on-windows.html">OpenAL on Windows</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-82801932215092290132010-09-30T06:10:00.014+09:002010-10-04T09:48:42.950+09:00Divide and ConquerIn my <a href="http://blog.qythyx.com/2010/09/monodevelop-isnt-bad-but-i-really.html">previous post</a> I discussed how I found a way to edit my iOS <a href="http://monodevelop.com/">MonoDevelop</a> (MD) projects in Visual Studio (VS) on my PC, but that I could not compile there and wanted to fix that. In this entry to discuss my solution.<br />
<br />
At it's heart the problem is that by default iOS projects reference the <a href="http://monotouch.net/">MonoTouch</a> (MT) library that wraps the standard iOS libraries (Cocoa, Core*, etc). These projects also have some special configuration options for iOS devices that VS doesn't like. Furthermore, if you are creating a typical iOS application then you probably need to interact with the MT libraries a lot. On the other hand, if you can divide your project into we separated layers, then all but the device specific functionality should be able to exist in standard .NET code and be fully compilable in any platform. That is exactly what I did.<br />
<br />
Initially I had a single .NET project with 3 main components:<br />
<ol style="margin-top: 0px;"><li>OpenGL setup - A modified version of the default OpenGL related classes provided when you create a new MT iOS OpenGL solution. The modifications are relatively minor, although they do include also setting up OpenAL.</li>
<li>Graphics and Sound utilities - Classes I'd written to provide some more object oriented constructs for base graphics and sound functionality. For example, I created a simple <code>Texture</code> class that was simply a set of coordinates that defined a triangle in the OpenGL world along with its matching triangular coordinates of the texture to show there. Another example is an <code>AudioSample</code> class that encapsulates an OpenAL audio sample that can be played, stopped, etc.</li>
<li>Game logic - The actual game related components of my game.</li>
</ol><br />
So, what I did was to separate these 3 components into separate projects with only the first one referring to the MT library. In addition I created a new version of the first component that was compatible with the PC. If you're paying attention you might wonder about how I could create a PC project that relies on OpenGL ES (ES is the version of OpenGL that is for mobile devices) and on OpenAL, neither of which are standard functionality in Windows. I'll go into some details of this in future posts, for now I'll continue the discussion of the project reorganization.<br />
<br />
I should mention that this project separation includes the unfortunate requirement of having parallel project (<code>.csproj</code>) files, one for MD and one for VS. This means that anytime I add a new file to my VS project I need to eventually add it to the MD one as well. Fortunately these new files quickly show up as missing when I compile, so it hasn't been too much of a burden yet. I do hope that MT supports VS better in the future, though.<br />
<br />
On the coding side, one of the challenges I faced was that I didn't want to have anything in my first 2 projects that was specific to the game I'm writing. All of that should be only in the 3rd project. At the same time, the loading of data into my <code>Texture</code> and <code>AudioSample</code> classes is platform specific. For example, on the iPhone it is recommended that you have you audio files in CAF format. On the PC WAV is more typical. Also, UI interaction is platform dependant. Specifically the iPhone provides multi-touch events and the PC mouse events. Fortunately, it turned out to be relatively easy to handle all of this.<br />
<br />
For the Texture data loading I simply pass a <code><span style="color: #2b91af;">Func</span><<span style="color: blue;">string</span>, <span style="color: #2b91af;">Texture</span>></code> to my main game class' constructor. This takes a string (the filename of the texture data) and returns a fully instantiated, platform independent <code>Texture</code> object.<br />
<br />
Similarly, for the <code>AudioSample</code> I pass a <code><span style="color: #2b91af;">Func</span><<span style="color: blue;">string</span>, <span style="color: #2b91af;">AudioData</span>></code> that does that same but returns a platform independent <code>AudioData</code> object.<br />
<br />
Finally, for the UI interaction I just have few methods on my main game object that are called for touches/mouse event. At this point I don't have any interaction that is iOS specific (i.e., multi-touch) that is not possible on the PC. If I get to the point that this is required then I'll have to come up with a work-around for the PC side.<br />
<br />
One thing that's implied in all of this is that I did need to implement the texture loader and sample loader twice, once for each platform. This is true, but was relatively simple. One other huge advantages of dividing my projects in this way now is that I'm setting myself up well to support Android and Windows Mobile 7 devices later. Although, I should mention, I am still writing my game classes with a certain amount of reliance on OpenGL, so porting this to another graphics framework would probably be a bit more work (Does Windows Mobile 7 have OpenGL or some mobile DirectX?).<br />
<br />
Next time: <a href="http://blog.qythyx.com/2010/10/opengl-es-11-on-windows.html">OpenGL ES 1.1 on Windows</a>.<br />
<br />
P.S. One other note on this...<br />
My initial version written in MD on the Mac used standard .NET <code>System.Drawing.PointF</code> structs to keep track of coordinates. Unfortunately, I ran into a problem with this when I separated my projects. Apparently MT does not include a full implementation of <code>System.Drawing</code> and only bundles a few parts inside the main MT library. This means that when I separated my platform independent project for which I did not want a reference to MT they also lot their reference to <code>PointF</code>. My solution was to change to use OpenGL's <code>Vector2</code> struct, which is basically the same, although it is one of the reliances on OpenGL that I mentioned above that I would prefer not to have.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com1tag:blogger.com,1999:blog-4882408034198003350.post-37287161387869181002010-09-29T08:45:00.003+09:002010-09-30T06:11:30.175+09:00MonoDevelop isn't bad, but I really prefer VisualStudioIn my <a href="http://blog.qythyx.com/2010/09/iphone-does-net.html">last entry</a> I described how I decided to do my iOS development on <a href="http://monotouch.net/">MonoTouch</a>. Now I discuss how despite the increased efficiency of a familiar language (C#), I still struggled with the <a href="http://monodevelop.com/">MonoDevelop</a> IDE.<br />
<br />
First, let me say that my dissatisfaction with MonoDevelop (MD) is in due to comparing it to Visual Studio (VS). According to <a href="http://en.wikipedia.org/wiki/Visual_studio#Version_history">Wikipedia</a>, VS started in 1997. Clearly it isn't fair to compare the maturity and depth of features of a product that has been under development for more than 13 years to one that is only a few years old. Also, I'm sure the size of the development teams are greatly unequal. Despite that, the reality for me was that I found MD falling short of what I was used to wanted.<br />
<br />
I noticed on the <a href="http://monotouch.net/Roadmap">MonoTouch Roadmap page</a> that they plan to extend <a href="http://www.go-mono.com/monotools/">Mono for Visual Studio</a> to "allow developers to use Visual Studio to develop iPhone applications", but since no timeline is given I have no idea when this will happen. So, I tried to do something on my own.<br />
MD saves projects in the same format as VS, so my first thought was to just open the project there and see what happens. I tried this, but VS complained of some error. I did a search and found a <a href="http://manniat.pp-p.net/blog/post/2009/11/18/MonoTouch-in-Visual-Studio.aspx">page suggesting a solution</a> called <a href="http://manniat.pp-p.net/Documents/VSMTouch1.0.msi.zip">VSMTouch</a>.<br />
<br />
The solution was to convert the MD projects so that VS can open them. Although the format of the files are the same, the MD projects refer to certain iPhone specific libraries, which don't exist on a Windows PC, of course. What this means is that even using VSMTouch will create a project that can be open in VS, but can't be compiled.<br />
<br />
Well, although that allows me to edit in my familiar environment, I still was not satisfied. VS has some great features that require the ability to compile (e.g., <a href="http://msdn.microsoft.com/en-us/library/bb429476(VS.80).aspx">FxCop</a> and built-in unit test framework). Also, I knew that I would want the ability to run my game relatively often as I develop it to test new things. Additionally, in the long term, MT plans to support Android, and Windows Mobile 7 will support .NET out of the box, so there are strong incentives for me to be able to develop more fully across platfroms.<br />
<br />
Next time: <a href="http://blog.qythyx.com/2010/09/divide-and-conquer.html">Divide and Conquer</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0tag:blogger.com,1999:blog-4882408034198003350.post-64637160294844481252010-09-26T16:44:00.000+09:002010-09-29T09:01:44.265+09:00iPhone Does .NETIn my <a href="http://blog.qythyx.com/2010/09/zoing-beginning.html">last entry</a> I described how I began the process of developing a game for iOS. Now, I'm going to continue that story with what's changed since 1 and ½ years ago when I started.<br />
<br />
When I was developing initially I was reading a number of news feeds related to iPhone development to try to learn as I went. I remember at that time <a href="http://www.novell.com/home/">Novell</a> announce a <a href="http://www.mono-project.com/Main_Page">Mono</a> based platform for iPhone development. This was very intriguing to me at the time, since Mono is an opensource implementation of the .NET framework, with which I have a lot of experience; far more than C, C++, and Objective-C, which are the default languages for iPhone development. I didn't really look into it at the time as it was pre-release and, if I remember correctly, advertised as being around $1000 for a license.<br />
<br />
When I recently returned to developing my game, however, I again quickly became frustrated by the Xcode development environment and the learning curve involved for me. Around this time Apple announced greatly <a href="http://www.apple.com/pr/library/2010/09/09statement.html">relaxed development restrictions</a> and I remembered the Novell announcement and decided to see how the maturity of the platform was now. As it turns out, <a href="http://monotouch.net/">MonoTouch</a> (the name of the Novell platform) is still undergoing rapid development and is at the point where it is quite usable. Also, the price either changed or I misremembered and is down to $399 for individuals.<br />
<br />
<div style="margin: 0px;">Novell has a free version of MonoTouch that has only the limitation that you can run your program only in the iPhone emulator and not on an actual device, so I downloaded that and started porting my game over from the old C & C++ & Objective-C version I'd previously worked on. I should mention that MonoTouch includes a version of <a href="http://monodevelop.com/">MonoDevelop</a>, an opensource .NET <a href="http://en.wikipedia.org/wiki/Integrated_development_environment">IDE</a>, that integrates well for developing for iPhone.</div><br />
After a bit of work I got enough of my game running to see that it would work on MonoTouch and that it seemed to run fast enough on .NET on the emulator on my Mac. But, I was a bit worried how well it would run on an actual iPhone.<br />
<br />
I debated for a while, but then noticed that Novell was having a sale for MonoTouch of 15% off in celebration of 1 year of MonoTouch, so I jumped in and signed up. I then deployed to my iPhone and was pleasantly surprised at the performance. Everything looked fine.<br />
<br />
Admittedly $399 is still a bit of an investment, but I'd already paid for a used iMac (about $300 or $400, I don't remember) and 2 iOS Developer licenses ($99 each year), so I decided to double-down and improve the chances I actually finish the game this time.<br />
<br />
Next time: <a href="http://blog.qythyx.com/2010/09/monodevelop-isnt-bad-but-i-really.html">MonoDevelop isn't bad, but I really prefer VisualStudio</a>.Rainerhttp://www.blogger.com/profile/11647667806922261038noreply@blogger.com0