Dealing with Stage3D Error 3702 ~ July 23, 2014
The Update (2014-07-24)
Today, I updated my Starling to the latest on github, and discovered what I wish I’d known a week ago: this solution has been in place in Starling proper for a couple of months. Still, I went to the trouble of typing it all up and explaining the problem. This solution would still be useful for people using other Stage3D libs, so I’m going to leave it up. Just be aware that if you’re using Starling, update to the latest and you don’t need to worry about this issue!
The Problem
Stage3D
’s Error #3702 is the “Context3D not available” error that may occur if you try to initialize Stage3D
with an unsupported profile. In Starling, this error is typically handled by Starling itself, and results in a dark red warning across the stage with a message like “Context 3D not available! Possible reasons: wrong wmode or missing device support.” If you’re like me, this error never happens on your own machines or devices, but you start getting complaints from players that they can’t play your game, and you have no idea why.
I ended up adding an automated bug report to my web build when this particular case comes up to see just how many people are affected, and I was shocked by how quickly I landed a pile of bug reports. I don’t have specific player affected percentage numbers, but it turned out not to be the very rare case that I had previously assumed it is, so I promptly freaked out and flailed around for a few hours, trying to get that flood of reports down to a trickle. It was happening across all modern browsers, OSs and Flash versions; there seemed to be no rhyme or reason.
The Flailing
I did a lot of googling, and found that some people were encountering this problem, but did not find a good general case solution. A lot of the posts I found spread around the Internet showed a general lack of understanding and confusion about why some people were affected and not others. I finally came across a post on the Rebuild game forums with the same problem. Rebuild is a fantastic series of games built by the incredible Sarah Northway. The forum post looked like she’d probably found a solution. It wasn’t posted there, so I bugged her on twitter and she generously shared her erudite wisdom with me. The solution found here is hers, adapted a bit.
Context3D Profiles
The first thing to understand about this error is what the various Context3DProfiles are, and why you might want to use each one.
A quick (by no means comprehensive) breakdown:
Context3DProfile.BASELINE_CONSTRAINED
. This is the lowest common denoninator profile. If you can get all the functionality you want from this profile, you can just use it and ignore the rest of this article. If you want to use large (4096x4096) textures or Rectangular textures, you want to avoid this proile.Context3DProfile.BASELINE
. This is the middleground profile. The main advantage overBASELINE_CONSTRAINED
is that it supports rectangular (instead of only square) textures. Starling blurs this limitation by doing an expensive (time and memory)Bitmap
copy of your source art from the rectangle it came in to the appropriate square size if it must.Context3DProfile.BASELINE_EXTENDED
. If you want to use 4096x4096 textures, you must run in this profile. If you want to run on older devices, you’ll need to have multiple versions of your textures at different sizes so that your game can still run if it must degrade to a lower profile.
The Counterfeit Solution
Adobe recently added Stage3D.requestContext3DMatchingProfiles
which Starling added support for by passing a Vector.<String>
of profiles in for the profile parameter of the Starling constructor
. This was meant to automate this process, correctly degrading through profiles until one is found that will work on the current computer or device. Unfortunately, Adobe appears to have flubbed it; this method no longer appears to support Context3DRenderMode.AUTO
, which is the mechanism that lets Stage3D
init fall back to software rendering if it must. This can happen if the computer doesn’t have hardware acceleration support, or (more likely) the player has turned off hardware acceleration for the Flash Player.
The result is that if you try to use the new requestContext3DMatchingProfiles
method, you can end up landing a nasty Error #3702, even if you passed in all of the Context3D
profiles.
Code Time
The solution that Sarah (and now also Mosaic Medley) uses is to attempt intialization of each profile in order using the older Stage3D.requestContext3D
function, catching errors and moving on to the next until a working Context3DProfile
is found. With appropriate embed settings (wmode=direct
), and requiring a modern Flash version (12.0.0 is good enough for my uses), I have yet to receive an Error #3702 using this approach.
The crux is in how we initialize Stage3D:
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | /**
* Initializes the first available Stage3D with the best Context3D it can support. Will fall
* back to software rendering in Context3DProfile.BASELINE_CONSTRAINED if necessary.
*
* @return a Future that may succeed with the profile in use by the newly initialized
* Context3D.
*/
protected function initStage3D () :Future {
var stage3D :Stage3D = stage.stage3Ds[0];
// try all three profiles in descending order of complexity
var profiles :Vector.<String> = new <String>[ Context3DProfile.BASELINE_EXTENDED,
Context3DProfile.BASELINE, Context3DProfile.BASELINE_CONSTRAINED ];
var currentProfile :String;
stage3D.addEventListener(flash.events.Event.CONTEXT3D_CREATE, onCreated);
stage3D.addEventListener(ErrorEvent.ERROR, onError);
var promise :Promise = new Promise();
function requestNextProfile () :void {
// pull off the next profile and try to init Stage3D with it
currentProfile = profiles.shift();
try {
stage3D.requestContext3D(Context3DRenderMode.AUTO, currentProfile);
} catch (e :Error) {
if (profiles.length > 0) {
// try again next frame
setTimeout(requestNextProfile, 1);
} else throw e;
}
}
function onCreated (e :flash.events.Event) :void {
var isSoftwareMode :Boolean =
stage3D.context3D.driverInfo.toLowerCase().indexOf("software") >= 0;
if (isSoftwareMode && profiles.length > 0) {
// don't settle for software mode if there are more hardware profiles to try
setTimeout(requestNextProfile, 1);
return;
}
// We've found a working profile. Clean up and exit.
cleanup();
promise.succeed(currentProfile);
}
function onError (e :ErrorEvent) :void {
// if we have another profile to try, try it; else fail for good
if (profiles.length > 0) setTimeout(requestNextProfile, 1);
else {
cleanup();
promise.fail(e);
}
}
function cleanup () :void {
stage3D.removeEventListener(flash.events.Event.CONTEXT3D_CREATE, onCreated);
stage3D.removeEventListener(ErrorEvent.ERROR, onError);
}
// kick off the process by requesting the first profile.
requestNextProfile();
return promise;
}
|
Note that I’m using a Future
here. This is from the reactive programming library for actionscript, React, which I cannot recommend enough. Promise
is simply the concrete implementation of Future
.
The rest of the implementation is pretty straightforward. We start with a class that extends Sprite
, and start the Stage3D
init when we’re added to the stage:
18 19 20 21 22 23 24 25 26 27 28 | public class App extends flash.display.Sprite {
public function App () {
addEventListener(Event.ADDED_TO_STAGE, addedToStage);
}
protected function addedToStage (e :flash.events.Event) :void {
initStage3D()
.onFailure(function (e :ErrorEvent) :void {
trace("Stage 3D error [" + e.toString() + "]");
}).onSuccess(initStarling);
}
|
initStarling
is given the profile String
by the Future
that initStage3D
returns. I found that on some platforms, Stage3D.context3D.profile
was mysteriously missing. I didn’t find a reason for it, and didn’t dig further because I was able to just pass it around myself. Perhaps this problem is also why Starling requires that you pass the configured profile in to the constructor.
The last bit to understand is that when we pass the already initialized Stage3D
instance in to Starling, it will assume that it’s sharing that Stage3D
with another framework, which modifies its behavior in several ways. Since we’re not actually sharing the Stage3D
around, we can reset the shareContext
property on the Starling instance before continuing with init:
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | protected function initStarling (profile :String) :void {
var stage3D :Stage3D = stage.stage3Ds[0];
trace("Stage 3D init complete [" + profile + ", " + stage3D.context3D.driverInfo + "]");
var starlingInst :Starling = new Starling(starling.display.Sprite, stage, null, stage3D,
Context3DRenderMode.AUTO, profile);
// we're not actually sharing context, but because we passed Starling a stage3D that is
// already initialized, it thinks we are.
starlingInst.shareContext = false;
starlingInst.addEventListener(starling.events.Event.ROOT_CREATED, run);
starlingInst.start();
}
protected function run (e :starling.events.Event) :void {
var starlingInst :Starling = Starling(e.target);
starlingInst.removeEventListener(starling.events.Event.ROOT_CREATED, run);
// Starling is good to go, let the game building commence!
}
|
That’s it! Hopefully this can clear up some of the general Error #3702 confusion out there. If you have any questions, comments or find a problem with the code here, leave it in a comment below.
comments powered by Disqus