Reloading images after switching to fullscreen

Monkey Targets Forums/Desktop/Reloading images after switching to fullscreen

Karja(Posted 2012) [#1]
I've been messing around with creating external functions to switch between windowed and fullscreen mode in GLFW, and I ran into a problem. If I close the window, create a new one and attach the callbacks again everything runs fine...except for the fact that the images are lost.

Is there some way to force a reload of the images? I'm using Diddy, and I've tried different things like:

* Running "game.images.Clear()" and then reload all the images. No change.
* Patched mojo.app so that I could run "SetGraphicsContext New GraphicsContext( GraphicsDevice() )" as well, but that crashes the game.
* Tried to simply create a new instance of my DiddyApp to refresh everything, but that also crashes.

Instead of trying different longshots I might as well check if anyone has any hints... At the moment I can do a switch to fullscreen/windowed before I have loaded the images, so I can do a "Please restart the game for this change to take effect" at least.

The c++ code for the functions:

	static void setWindowed(int w, int h)
	{
		GLFWvidmode desktopMode;
		glfwGetDesktopMode( &desktopMode );

		glfwCloseWindow();

		glfwOpenWindowHint( GLFW_WINDOW_NO_RESIZE, GL_TRUE );
		glfwOpenWindow( w, h, 0, 0, 0, 0, 0, 0, GLFW_WINDOW );
		glfwSetWindowPos( (desktopMode.Width-w)/2,(desktopMode.Height-h)/2 );

		glfwEnable( GLFW_KEY_REPEAT );
		glfwDisable( GLFW_AUTO_POLL_EVENTS );

		glfwSetKeyCallback( gxtkApp::OnKey );
		glfwSetCharCallback( gxtkApp::OnChar );
		glfwSetWindowSizeCallback( gxtkApp::OnWindowSize );
		glfwSetWindowRefreshCallback( gxtkApp::OnWindowRefresh );
		glfwSetMouseButtonCallback( gxtkApp::OnMouseButton );
	}

	static void setFullscreen()
	{
		GLFWvidmode desktopMode;
		glfwGetDesktopMode( &desktopMode );

		glfwCloseWindow();

		glfwOpenWindowHint( GLFW_WINDOW_NO_RESIZE, GL_TRUE );
		glfwOpenWindow( desktopMode.Width, desktopMode.Height, 0, 0, 0, 0, 0, 0, GLFW_FULLSCREEN );
		glfwSetWindowPos( 0, 0 );

		glfwEnable( GLFW_KEY_REPEAT );
		glfwDisable( GLFW_AUTO_POLL_EVENTS );

		glfwSetKeyCallback( gxtkApp::OnKey );
		glfwSetCharCallback( gxtkApp::OnChar );
		glfwSetWindowSizeCallback( gxtkApp::OnWindowSize );
		glfwSetWindowRefreshCallback( gxtkApp::OnWindowRefresh );
		glfwSetMouseButtonCallback( gxtkApp::OnMouseButton );

		glfwEnable( GLFW_MOUSE_CURSOR );
	}



AdamRedwoods(Posted 2012) [#2]
You have to reload the images. Mojo dumps the data after uploading into video memory and if you're creating a new window, then you'll get a new opengl context.

You might need to use a flag (reload_images=true) to do the actual loading of images within OnRender(). I know I had to do this in android.


Karja(Posted 2012) [#3]
Yeah, I suspected as much - but how do I do that with diddy? I cleared the "game.images" list and re-loaded all the data with "game.images.Load()" etc. But there's no change - the image data is still gone.

I also tried explicitly clearing each image before reloading, but the Discard() call just crashes the app:

Local g:GameImage = game.images.Get( StripAll( i.name.ToUpper() ) )
If( g <> Null )
g.image.Discard()
game.images.Remove( StripAll( i.name ) )
Endif

Edit: Oh, right - maybe the context hasn't been initialized yet at the time when I try to reload the images. I'll try your example, with the reload flag that does it on OnRender instead. Thanks!


therevills(Posted 2012) [#4]
Cool! I'll have to set this :)

Do you mind if I add this to Diddy?

When loading resources into the banks (images/sounds), we could keep a list of the loaded resources, so the user doesnt have to reload them themselves after going to fullscreen... maybe ;)


Karja(Posted 2012) [#5]
That sounds like a great idea! And please, just go ahead and use this. Maybe I could help with modifying the banks as well, if time permits.

(I'm getting a spontaneous thought that it might be a good idea to let the user trigger the reloading at will, to have time to show a loading screen before locking up everything for a while.)


therevills(Posted 2012) [#6]
Hmmm, I've been messing around with this for a few hours... I think the memory address is totally lost when we are changing context.

For example:
background = game.images.Find("background")


Points to background, but when we change context background is no longer there... even if we reload background, it might not use the same memory address.

I've had my game crash with "Memory access violation" and alot of white squares...

So how do we save the memory address to reload the images into the same address?


Karja(Posted 2012) [#7]
Okay, I'm getting a bit lost here. I went for the approach to try to keep the GameImage structures intact, and simply reload the image after switching to windowed/fullscreen. So I modified Diddy's framework.monkey:

Class ImageBank Extends StringMap<GameImage>

	Method Reload:GameImage(name:String, nameoverride:String = "", midhandle:Bool=True, ignoreCache:Bool=False)
		Local storeKey:String = nameoverride.ToUpper()
		If storeKey = "" Then storeKey = StripAll(name.ToUpper())
		If Self.Contains(storeKey)
			Local i:GameImage = Self.Get(storeKey)
			i.Load(path + name, midhandle)
			Print "loaded " + name
			Return i
		End
		Return Null
	End


In my loading function I did a check to see whether or not the GameImage structure was still present:

	g = game.images.Get( StripAll( i.name.ToUpper() ) )
	If( g <> Null )
		game.images.Reload( i.name, "", i.mid )
	Else
		game.images.Load( i.name, "", i.mid )
	Endif


(Note: for me the game.images bank still seems to retain the GameImage data after I've switched to fullscreen... It's just the image data itself that needs to be reloaded and uploaded to video memory (like AdamRedwoods said above).)

The result of this is that Load is called the first time, and on successive calls Reload is called instead, and i.Load() in turn is called inside the ImageBank. But it still won't work - I get white squares despite that...

I tried reloading everything immediately after switching resolution, but I also tried to add a "reload stuff" flag so that it happened in my Render() method instead... But I still won't get the image data.

Tracing the functions forward gives:

GameImage::Load() -> LoadBitmap() -> LoadImage() -> Image::Load() -> gxtkGraphics::LoadSurface()

And I don't really see anything that should be a problem there. Weird. :/


therevills(Posted 2012) [#8]
Yep, I tried multiple things like this... I even had it working for the first time I switch resolution but after that I got white squares...

I might try and look in the BlitzMax's source to see how that does it, but I'm not that good with this low level coding...

Functions.monkey:
...
		Function SetNativeGraphicsSize:Void(w:Int, h:Int, fullScreen:Int) = "diddy::setGraphics"
...

Global reloadResources:Bool = False


Function SetGraphics:Void(w:Int, h:Int, fullScreen:Int = 0)
	SetNativeGraphicsSize(w, h, fullScreen)
	DEVICE_WIDTH = w
	DEVICE_HEIGHT = h
	SCREEN_HEIGHT = h
	SCREEN_WIDTH = w
	SCREEN_WIDTH2 = SCREEN_WIDTH / 2
	SCREEN_HEIGHT2 = SCREEN_HEIGHT / 2

	reloadResources = True
	'ReLoadResources()
End

Function ReLoadResources:Void()
Print "reload"
	Local fnt:Image = LoadImage("mojo_font.png", 96, Image.XPadding)

	SetFont(fnt)
		
	For Local key:String = Eachin game.images.Keys()
	'	Print "key = "+key
		game.images.Get(key).image.Discard()
	Next

	
	game.images.Clear()' = New ImageBank
	Local rList:List<Resource> = New List<Resource>
	For Local r:Resource = Eachin resourcesList
		rList.AddLast(r)
	Next
	resourcesList.Clear()
	Local tmpImage:Image

	For Local r:Resource = Eachin rList
		Select r.type
			Case "image"
				Print "IMAGE..."
				Print "filename = "+r.fileName
				Print "override = "+r.overrideName
				
				If r.midhandle
					Print "midhangle= true"
				Else
					Print "midhangle= false"

				Endif
				
				game.images.Load(r.fileName, r.overrideName, r.midhandle)
			Case "animimage"
				Print "ANIMIMAGE..."+r.fileName +" "+r.type
				game.images.LoadAnim(r.fileName, r.width, r.height, r.frames, tmpImage, r.midhandle, False, r.overrideName)
		End
	Next
	For Local key:String = Eachin game.images.Keys()
		Print "new key = "+key
		'game.images.Get(key).image.Discard()
	Next
End



framework.monkey:
	Method OnRender:Int()

		If reloadResources
			#if TARGET="glfw"
			ReLoadResources()
			#end
			reloadResources = False
			Return 0
		End
		
		FPSCounter.Update()
...


diddy.glfw.cpp

	static void setGraphics(int w, int h, int fullScreen)
	{
		if (fullScreen == 0) {
			GLFWvidmode desktopMode;
			glfwGetDesktopMode( &desktopMode );

			glfwCloseWindow();

			glfwOpenWindowHint( GLFW_WINDOW_NO_RESIZE, GL_TRUE );
			glfwOpenWindow( w, h, 0, 0, 0, 0, 0, 0, GLFW_WINDOW );
			glfwSetWindowPos( (desktopMode.Width-w)/2,(desktopMode.Height-h)/2 );

			glfwEnable( GLFW_KEY_REPEAT );
			glfwDisable( GLFW_AUTO_POLL_EVENTS );

			glfwSetKeyCallback( gxtkApp::OnKey );
			glfwSetCharCallback( gxtkApp::OnChar );
			glfwSetWindowSizeCallback( gxtkApp::OnWindowSize );
			glfwSetWindowRefreshCallback( gxtkApp::OnWindowRefresh );
			glfwSetMouseButtonCallback( gxtkApp::OnMouseButton );
		} else {
			GLFWvidmode desktopMode;
			glfwGetDesktopMode( &desktopMode );

			glfwCloseWindow();

			glfwOpenWindowHint( GLFW_WINDOW_NO_RESIZE, GL_TRUE );
			glfwOpenWindow( w, h, 0, 0, 0, 0, 0, 0, GLFW_FULLSCREEN );
			glfwSetWindowPos( 0, 0 );

			glfwEnable( GLFW_KEY_REPEAT );
			glfwDisable( GLFW_AUTO_POLL_EVENTS );

			glfwSetKeyCallback( gxtkApp::OnKey );
			glfwSetCharCallback( gxtkApp::OnChar );
			glfwSetWindowSizeCallback( gxtkApp::OnWindowSize );
			glfwSetWindowRefreshCallback( gxtkApp::OnWindowRefresh );
			glfwSetMouseButtonCallback( gxtkApp::OnMouseButton );

			glfwEnable( GLFW_MOUSE_CURSOR );
		}


Sorry for the messy code... ;)


Karja(Posted 2012) [#9]
Thanks for the code update - I'll continue experimenting with this tomorrow, and maybe I can find something... Hah, this is pretty fun detective work. :)


Karja(Posted 2012) [#10]
I have tried some different things such as adding glfwTerminate and Init when switching mode:

		glfwCloseWindow();
		glfwTerminate();

		if( !glfwInit() ){
			puts( "glfwInit failed" );
			exit( -1 );
		}

		glfwOpenWindowHint( GLFW_WINDOW_NO_RESIZE, GL_TRUE );


No change. Tried adding a "glEnable( GL_TEXTURE_2D );" after opening the new window as well. Double-checked that there's no OpenGL error after glTexSubImage2D() in gxtkSurface *gxtkGraphics::LoadSurface():

	glTexSubImage2D( GL_TEXTURE_2D,0,0,0,width,height,fmt,GL_UNSIGNED_BYTE,data );
	if( glGetError()!=GL_NO_ERROR ){
		glDeleteTextures( 1,&texture );
		unloadImage( data );
		return 0;
	}


...And in general checked that there are no errors in Image::Load, and that the width and height of the reloaded are correct. I'm starting to think that the loading of the data is going just fine, but something in OpenGL is buggy. E.g. that glTexImage2D() or glTexSubImage2D() is not working as expected, on the second time that a GL context is created.

I also found some hints that some people with SDL have the same problem - but no solution:
[url]http://www.gamedev.net/topic/557497-texture-reloading-when-toggling-fullscreen-sdlopengl/[/url]

Also, after browsing the BlitzMax implementation for a little bit it seems that it uses straight OpenGL that doesn't have GLFW's limitations - there doesn't seem to be a need to reload the textures there.

If this wasn't a family-friendly forum I'd have some choice words right now...


therevills(Posted 2012) [#11]
I havent done anything with this tonight, but found these links:

http://www.gamedev.net/topic/372906-glfw----change-video-mode----fullscreenwindowedresolution/

http://www.gamedev.net/topic/589777-toggle-fullscreen-with-glfw/

And going off the first one, it looks like we are trying to do it right... so we must be missing something :/


And I've found this:

http://www.philipppixel.de/thegame/TheGameDoc/html/files.html

Looks like a full game using GLFW and in the Start.cpp is a method to switch between full-screen and window mode... will need some time to go thru the code (ewwwwww C++)

Also found this: GLOOT, a modified version of GLFW which doesnt lose its context when switching to full-screen:

http://code.google.com/p/open-game-libraries/wiki/LibraryGloot


Chroma(Posted 2013) [#12]
Did anyone figure this out? Still can't switch graphics fullscreen on the fly?


Grey Alien(Posted 2013) [#13]
Sounds like it's a pretty important feature to have in order to not have to use BlitzMax.


therevills(Posted 2013) [#14]
From what I understand GLFW needs to be updated to support this, I do remember reading a roadmap of GLFW saying they were going to add this... but then Monkey would need to be changed to use that version.

http://wiki.glfw.org/wiki/Roadmap_for_GLFW

Status of GLFW 3.x

Window mode switching



But even if GFLW had this, it would still only be OpenGL...


Grey Alien(Posted 2013) [#15]
Oh that's also a bit of problem too I guess. So really we need a DX target for Monkey if we want to do everything in monkey instead of using BMax for desktop games.


Skn3(Posted 2013) [#16]
http://code.google.com/p/monkey-max/wiki/MonkeyMax

Grey, incase you didn't already know this, you can export to blitzmax if you need a dx target.


charlie(Posted 2013) [#17]
Max has an interesting function call GLShrareContexts(), which i used in Scoregasm so i didn't need to reload all the images on a fullscreen/window flick. Might be worth a look...

Cheers
Charlie


Skn3(Posted 2013) [#18]
I cracked this mofo!

http://www.skn3.com/junk/monkey/screen_mode_toggle.zip

So all you have to do is make sure you call image.Discard() on all images before changing screen mode. The trouble is this is not possible to do automatically without an image manager. Even with an image manager, 3rd party modules and the built in font system will break.

I have emailed mark for any suggestions and will post a module once I hear back.

The source for the demo above is here:
Import mojo
Import skn3.funcs.graphics
Import skn3.imagecache

Function Main:Int()
	New Demo
	Return 0
End

Class Demo Extends App
	Field fullScreen:= False
	Field image:ImageCache
	
	Method OnCreate:Int()
		' --- app is created ---
		SetUpdateRate(60)
		
		image = LoadImageCache("monkey1.png")
		
		Return 0
	End Method
	
	Method OnUpdate:Int()
		' --- app is updated ---
		If KeyHit(KEY_SPACE)
			FreeImageSources(True)
			
			GetFont().Discard()
			
			If fullScreen
				fullScreen = False
				SetGraphics(800, 600, fullScreen)
			Else
				fullScreen = True
				SetGraphics(1024, 768, fullScreen)
			EndIf
			
			SetFont(LoadImage("mojo_font.png", 96, Image.XPadding))
			
			'reload the images
			ReloadImageSources()
		EndIf
		
		Return 0
	End Method
	
	Method OnRender:Int()
		' --- app is rendered ---
		Cls(0, 0, 0)
		
		DrawImageCache(image, 50, 50)
		DrawText("press space to toggle screen mode", 5, 5)
		
		Return 0
	End Method
End


It is using an image cache module I wrote, but you can see the CLUDGE fix for the font reloading.


Grey Alien(Posted 2013) [#19]
Skn3: Sounds promising, nice one! I do have an image manager in my framework so it could work. Yeah I do know about MonkeyMax but I wasn't convinced it would be a smooth transition.


Skn3(Posted 2013) [#20]
Ok it seems the issue will have to be looked at again in monkey v67 but for now here is the code:

http://www.monkeycoder.co.nz/Community/posts.php?topic=4418

You will have to manually discard and load any images.