Fixing Black Textures After Pause and Resume in libGDX on Android
Sprites turn black after pausing an Android libGDX app? That's OpenGL context loss. Diagnose it with a cause table, ordered checks, and fixes via AssetManager or manual texture rebuilds.
30 Jun 2026, 23:38 UTC

The symptom: sprites turn black or white after returning from background
Your libGDX game runs fine on desktop and when first launched on Android. Then you press Home, reopen the app, and every sprite renders as a black or white rectangle — or the app crashes inside a GL call. Restarting the app fixes it until the next pause. This is the classic symptom of OpenGL context loss, and it is an app-level bug you can fix, not a device defect.
The cause: Android is allowed to destroy the OpenGL context when your activity is paused. Every GPU-resident resource — textures, shaders, meshes, framebuffers — becomes invalid, even though the Java objects wrapping them still exist. When the context is recreated, those stale handles point at nothing, and rendering produces garbage.
Cause and diagnostic table
| Observation | Likely cause | Where to look |
|---|---|---|
| Black or white quads where sprites were | Texture handles lost, never re-uploaded | Textures created with new Texture(Pixmap) or held outside AssetManager |
Crash in glTexImage2D or similar GL call | Invalid texture id used after context recreation | Any texture bound in render() without a validity check |
| Shader effects gone, uniforms at defaults | ShaderProgram not recompiled after resume | Shaders built once in create() and never rebuilt |
| Render-to-texture output is blank | FrameBuffer died with the context | FBOs created in create() and reused |
| Bug only on a real device, never on emulator | Emulator keeps the context alive | Test setup, not code — see reproduction steps below |
Ordered checks
- Reproduce reliably. On a physical Android device, enable "Don't keep activities" in Developer Options. Launch the game, press Home, wait a few seconds, and resume. This forces activity and context destruction so you are not guessing.
- Confirm
resume()is called. Add a log line in your ApplicationListener'sresume(). libGDX invokes it after the context is recreated; if it never fires, something else is wrong with your activity lifecycle. - Audit every
new Texture(...)call site. Textures created from aFileHandleregister with libGDX's managed list and reload automatically. Textures built directly from a Pixmap are not managed and are the usual offenders. - Check shaders and FBOs. Search for
new ShaderProgramandnew FrameBufferoutside of any reload path. These die with the context too. - Verify handles after resume. For any manually rebuilt texture, log
texture.getTextureObjectHandle()inresume()— a value greater than 0 means the GPU object exists again.
Fixes tied to each finding
Finding: assets loaded by hand — route them through AssetManager
The primary engineering fix is to load every Texture, TextureAtlas, BitmapFont, ShaderProgram, and Model through AssetManager. Managed assets are automatically reloaded on resume, which eliminates the whole class of bug:
assetManager.load("sprites/player.png", Texture.class);
assetManager.load("fonts/ui.fnt", BitmapFont.class);
assetManager.finishLoading();
Texture player = assetManager.get("sprites/player.png", Texture.class);
Run this in your screen's show() or a loading screen, inside your game's render thread — no special permissions needed. After the change, repeat the pause/resume cycle with "Don't keep activities" on and confirm all sprites and fonts render.
Finding: textures built from Pixmaps — rebuild them in resume()
Dynamically generated textures (procedural images, decoded bytes) cannot be managed automatically. Cache the source data and rebuild the GPU texture:
@Override
public void resume() {
if (dynamicTexture != null) {
dynamicTexture.dispose();
}
dynamicTexture = new Texture(cachedPixmap); // re-upload to the new context
}
Keep the Pixmap (or the bytes it was built from) alive for exactly this purpose. The risk: holding large Pixmaps doubles memory usage, so for big assets prefer writing to a temp file and loading via AssetManager instead.
Finding: shaders at defaults — recompile and check the log
@Override
public void resume() {
shader = new ShaderProgram(vertexSource, fragmentSource);
if (!shader.isCompiled()) {
Gdx.app.error("Shader", shader.getLog());
}
}
Always check isCompiled() and log getLog(); a shader that fails silently renders nothing.
Finding: blank FBO output — dispose and rebuild
FrameBuffers must be disposed and recreated in resume() with the same format and dimensions. Do not attempt to reuse the old instance.
When to escalate beyond app-level fixes
Escalate to a minimal reproduction and a libGDX issue report only if: all assets go through AssetManager, resume() demonstrably runs, handles are valid after resume, and textures still render black on one specific device. That pattern suggests a driver or backend quirk, not your code. Also note the reverse trap: if the bug never reproduces on your test hardware, that device is preserving the context — keep "Don't keep activities" enabled in CI-style manual testing, because your users' devices will not be so forgiving.
Limitations
Behavior described here applies to the Android backend across current libGDX 1.x releases; verify against your exact version's changelog if you are on an older build. Mixing managed and unmanaged assets makes diagnosis harder, so complete the audit in step 3 before assuming AssetManager is at fault.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.