Quick answer: GameMaker surfaces are volatile GPU textures. The OS can discard them on alt-tab, window resize, or fullscreen toggle. Always call surface_exists before using a surface, and rebuild the content from a source of truth when it returns false.

You cache a level background on a surface so you do not redraw the same tilemap every frame. Performance is great until a player alt-tabs to Discord and the level turns into a flickering black rectangle. The cached surface is gone and your game is trying to draw from a handle that no longer points anywhere. The fix is a three-line pattern you add every time you use a surface.

Why Surfaces Disappear

A GameMaker surface is a block of video memory you can render into and read back from. Unlike sprites, which are backed by a file on disk, surfaces only exist in GPU memory. When the graphics device is lost or reset — alt-tab in DirectX, window resize, fullscreen toggle, GPU driver restart — the driver may discard the contents of every surface. The handle is still valid, but the texture behind it is empty or garbage.

This is not a bug, it is how graphics APIs have worked for decades. The fix is to treat every surface as a cache that can evaporate at any time.

The Symptom

The Pattern

Every surface use in your game must follow this pattern:

// Create the surface: Create event
surf_lightmap = -1;

// Use the surface: Draw event
if (!surface_exists(surf_lightmap)) {
    surf_lightmap = surface_create(room_width, room_height);
    redraw_lightmap();  // redraw the static content
}

draw_surface(surf_lightmap, 0, 0);

The surface_exists check is cheap and idempotent. Call it every frame. If it returns false, recreate the surface and redraw the content. The pattern is identical everywhere you use a surface.

The Source of Truth

The “redraw the content” step assumes you can rebuild the surface from something. This “something” is your source of truth — usually a set of variables, a data file, or a procedural function. Keep the source of truth in regular GameMaker variables (not on the surface itself) and treat the surface as a derived cache.

For example, a minimap surface is derived from the tilemap and the player positions. A lighting surface is derived from the light objects. A text cache surface is derived from the text strings. Each of these can be regenerated from code — do not lose information when the surface disappears.

function redraw_minimap() {
    surface_set_target(surf_minimap);
    draw_clear_alpha(c_black, 0);
    with (obj_tile) {
        draw_rectangle_color(x / 8, y / 8,
            (x + 32) / 8, (y + 32) / 8,
            c_gray, c_gray, c_gray, c_gray, false);
    }
    with (obj_player) {
        draw_circle_color(x / 8, y / 8, 2, c_white, c_white, false);
    }
    surface_reset_target();
}

Preserving Data You Cannot Rebuild

Sometimes the surface holds data that cannot be regenerated — a drawing the player made, a procedurally generated image that used randomness, a capture from a camera. For these, use buffer_get_surface to copy the pixels into a CPU-side buffer before anything can clobber them:

var buf = buffer_create(surface_width * surface_height * 4, buffer_fixed, 1);
buffer_get_surface(buf, surf_player_drawing, 0);
// Keep buf around - it is safe from device loss

// Later, on surface recreation:
surf_player_drawing = surface_create(256, 256);
buffer_set_surface(buf, surf_player_drawing, 0);

Call this every time the player finishes a drawing action. It costs CPU memory but guarantees the data survives device loss.

Listening for Window Events

Instead of waiting for the surface_exists check to fail, you can proactively rebuild surfaces whenever a window event happens:

// Async - System event
var evt = async_load[? "event_type"];
if (evt == "window_focus_changed" or evt == "window_resize") {
    if (surface_exists(surf_lightmap)) {
        surface_free(surf_lightmap);
    }
    surf_lightmap = -1;  // force recreation next frame
}

This is optional — the per-frame surface_exists check catches it anyway — but it gives you a clean signal to run cleanup code.

Verifying the Fix

Launch the game, draw some content to the surface, and alt-tab. Alt-tab back. The content should reappear unchanged. If the content goes blank or flickers, the surface was lost and your recreation code either did not run or did not redraw the correct state. Add a print to the recreation path and confirm it fires exactly when the surface disappears.

“Every GameMaker surface is a cache. Every cache can disappear. Every frame, check that it still exists. It is three lines of code and skipping it will ruin your players’ day.”

Related Issues

For draw event bugs more broadly, see GameMaker draw event not firing. For room lifecycle issues, see GameMaker instance variables resetting on room change. For audio similarly breaking on alt-tab, see GameMaker audio not playing on Android export.

Never use a surface to store information you cannot rebuild. If you do, pair every draw with a buffer_get_surface so the data is safe from the GPU.