Android: how to handle reloads of background-loaded textures

Discussion of issues specific to mobile platforms such as iOS, Android, Symbian and Meego.
Post Reply
robert82h
Gnoblar
Posts: 23
Joined: Thu Jun 13, 2013 5:41 pm
x 7

Android: how to handle reloads of background-loaded textures

Post by robert82h »

Hi, I'm developing an Android application that renders a Google Maps style 3D map. Most things went pretty smooth so far, but I'm stuck with one peculiar problem. Here's some background on what I'm using and doing:
- Latest Ogre version from 1.9 branch, compiled with Boost and Threading support (OGRE_THREAD_SUPPORT 2)
- Based on OgreJNI, i.e., the activity is created in Java
- I subclassed Ogre::Archive to handle requests for HTTP-URLs by going through Android's URLConnection class (the idea was to get a persistent download cache for free)

In order to keep rendering while downloads are in progress, I use Ogre's background loading feature:

Code: Select all

Ogre::TexturePtr tex = static_cast<Ogre::TexturePtr>(
				Ogre::TextureManager::getSingleton().create(url, group) );
tex->setBackgroundLoaded(true);

Ogre::ResourceBackgroundQueue* rbq = Ogre::ResourceBackgroundQueue::getSingletonPtr();
Ogre::BackgroundProcessTicket ticket = rbq->load("Texture", url, group, false, 0, 0, &g_texDownloadCB);

// create or clone material, prepare texture units, set texture name, leave information for operationCompleted(), etc.
In the operationCompleted() callback, switching the material to the newly created one is sufficient to make the texture replace the dummy material:

Code: Select all

pSceneMgr->getEntity(tex_download_tickets_[ticket].node)->setMaterialName(tex_download_tickets_[ticket].material);
So far everything works :-) When I start the application the textures are loaded in the background and appear as downloads complete.

However, when I switch to the Home screen and then back to my app's activity, only the textures that are not loaded in the background are re-initialized, all background-loaded textures stay black. From my archive class I see that no requests are made for these.

Code: Select all

JNIEXPORT void JNICALL Java_com_example_maps_MapsJNI_initWindow(JNIEnv * env, jobject obj,  jobject surface)
{
	if(surface)
	{
		ANativeWindow* nativeWnd = ANativeWindow_fromSurface(env, surface);
		if (nativeWnd && gRoot)
		{
			if (!gRenderWnd)
			{
				// Create scene when starting activity
			}  
			else
			{
				// Resume activity. This does *not* trigger reloads of background-loaded textures :(
				static_cast<Ogre::AndroidEGLWindow*>(gRenderWnd)->_createInternalResources(nativeWnd, NULL);
			}
		}
	}
}
What is the preferred way of reloading these background textures?

Unfortunately, obvious (to Ogre beginners like me) solutions like reloadAll() don't work and every idea I tried so far didn't work or caused other problems.

For example, somewhere inside _createInternalResources(), textures are reloaded by calling Ogre::Resource::reload(), which in turn calls unload() and load(). However, reload() does not have a background flag like load() does, so all background-loaded textures are unloaded, but not reloaded. Calling reload() on such a resource basically just unloads it (and sets the load state to "unloaded"). As far as I understand, this behaviour means that the engine loses track of which textures should be loaded and I'd have to keep track of them manually.

So I tried that and created a task that I placed into the work queue for background processing:

Code: Select all

Ogre::WorkQueue* wq = Ogre::Root::getSingleton().getWorkQueue();
Ogre::uint16 channel = wq->getChannel("Custom/ResourceReload");
wq->addRequest(channel, 42, Ogre::Any(reload_list));
In the corresponding request handler, I would unload and load(true) the textures:

Code: Select all

for(size_t i = 0; i < list->size(); i++)
{
	list->at(i)->unload();
	//list->at(i)->load(true);      <-- not safe in background (different EGL context)
	list->at(i)->prepare(true);

	// How to trigger loading texture? Do I have to notify someone?
}
This makes the textures load, i.e., my archive reports a request and I see Ogre has created the texture:

Code: Select all

I/OGRE    (26052): Texture: http://192.168.0.15/test.png: Loading 1 faces(PF_R8G8B8,2048x2048x1) with 11 hardware generated mipmaps from Image. Internal format is PF_R8G8B8,2048x2048x1.
However the texture still renders black.

As all of this got quite complicated, I'm wondering if there isn't a really obvious thing that I'm missing that will make the background-loaded textures reload automatically on the background thread. Any hints, pointers or clarifications are more than welcome!

Thanks,
Robert
robert82h
Gnoblar
Posts: 23
Joined: Thu Jun 13, 2013 5:41 pm
x 7

Re: Android: how to handle reloads of background-loaded text

Post by robert82h »

Found a solution: before _createInternalResources() is called, I post BackgroundProcessTickets for all the textures:

Code: Select all

	Ogre::TextureManager& m = Ogre::TextureManager::getSingleton();

	{ /* locked */
		boost::recursive_mutex::scoped_lock mgr_lock(m.mutex);

		Ogre::TextureManager::ResourceMapIterator rmi = m.getResourceIterator();
		for(Ogre::TextureManager::ResourceHandleMap::const_iterator i = rmi.begin(); i != rmi.end(); ++i)
		{
			boost::recursive_mutex::scoped_lock tex_lock(i->second->mutex);

			if(i->second->isReloadable() &&
			   i->second->isBackgroundLoaded() &&
			   i->second->getLoadingState() == Ogre::Texture::LOADSTATE_LOADED)
			{
				Ogre::ResourceBackgroundQueue* rbq = Ogre::ResourceBackgroundQueue::getSingletonPtr();
				Ogre::String group = Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME;
				// TODO somehow, these requests are no longer done on the background thread
				Ogre::BackgroundProcessTicket ticket = rbq->load("Texture", i->second->getName(), group, false, 0, 0, &tex_download_cb_);
			}
		}
	}

	static_cast<Ogre::AndroidEGLWindow*>(render_wnd_)->_createInternalResources(
					reinterpret_cast<ANativeWindow*>(native_wnd), NULL);
The callback text_download_cb_ is a no-op in this case, there's nothing else I need to do. This approach works, however in contrast to the other places where I use background loading, the requests here are *not* run on the background thread. This is something I still have to look into.
Post Reply