Quick answer: Check surface_exists(surf) before drawing to or from any persistent surface. Recreate with surface_create() if needed. GameMaker surfaces can be evicted from VRAM on alt-tab, resize, or fullscreen toggle — always assume they might be gone.

Here is how to fix GameMaker surface lost after window resize. You create a surface in Create event for a minimap, lighting overlay, or post-processing pass. It works beautifully. Player alt-tabs away and back — the surface is gone, and your draw event throws “surface does not exist” errors. Or the window resizes and the next frame the surface is random noise or pure black. GameMaker surfaces are GPU resources, and GPU resources do not survive OS events reliably.

The Symptom

A surface created in an object’s Create event works for a while, then stops. Triggers that cause the break:

Result is an error about invalid surface, or (worse) the surface reference is still “valid” but contains garbage pixels.

What Causes This

Surfaces are GPU-only. Unlike regular data, surfaces are not kept in RAM. They live in video memory. When the OS needs video memory for something else (another fullscreen app, display mode change, etc.), it can invalidate surfaces. GameMaker’s surface IDs survive but the underlying GPU texture is gone.

Fullscreen toggle recreates the GPU context. Switching between windowed and exclusive fullscreen destroys the current GPU device. All surfaces are invalidated. GameMaker fires the surface_reset event when this happens.

Window resize reallocates backbuffer. On resize, GameMaker resizes the default drawing surface. User-created surfaces of fixed size survive, but their contents may be cleared.

Assuming surfaces persist forever. A surface created in the Create event and never checked for existence again is a bug waiting to trigger. Works for 10 minutes of testing, fails during a 30-minute playthrough when the player tabs out.

The Fix

Step 1: Always check surface_exists before use. In any Draw event that uses a persistent surface:

// Draw event
if (!surface_exists(minimap_surf)) {
    minimap_surf = surface_create(256, 256);
    redraw_minimap = true;
}

if (redraw_minimap) {
    surface_set_target(minimap_surf);
    // redraw the minimap contents
    draw_clear(c_black);
    // ... draw world markers, player icon, etc. ...
    surface_reset_target();
    redraw_minimap = false;
}

// Now it's safe to draw the surface to screen
draw_surface(minimap_surf, 0, 0);

The check runs every frame but only recreates when needed. The redraw_minimap flag ensures the surface is repopulated after recreation — otherwise the first frame after alt-tab shows empty surface.

Step 2: Hook the system event. GameMaker fires an Async System event with event_type == ev_gui or specifically “System Event” with event_id indicating surface reset. Handle it to recreate all surfaces proactively:

// Async - System event
if (async_load[? "event_type"] == "surface_reset") {
    // Recreate every persistent surface
    if (!surface_exists(minimap_surf))
        minimap_surf = surface_create(256, 256);
    if (!surface_exists(lighting_surf))
        lighting_surf = surface_create(room_width, room_height);

    redraw_all = true;
}

Proactive recreation plus reactive checks in Draw covers both cases — explicit surface loss events and unexpected ones.

Step 3: Separate surface for each use. Do not reuse one surface for multiple render targets. If your minimap and lighting overlay share a surface, a resize invalidates both but your code only rebuilds one. Keep them separate so the rebuild is specific.

Step 4: Clean up unused surfaces. Surfaces consume VRAM. Freeing them when not in use reduces the chance of resource pressure triggering invalidations elsewhere. When your object is destroyed, free its surfaces:

// Destroy event
if (surface_exists(minimap_surf))
    surface_free(minimap_surf);

Avoid Dynamic Surface Sizes

Recreating a surface every time the window resizes (to match screen dimensions) is common but wasteful. Pick a fixed surface size that matches your game’s internal resolution (say 1920x1080) and scale it to fit the window when drawing. The surface is stable across resizes.

// Draw GUI event (works in GUI space, fixed coords)
draw_surface_stretched(game_surf, 0, 0,
    display_get_gui_width(), display_get_gui_height());

Debug Logging

Log surface state during suspect events to understand when losses happen:

// Draw event
if (!surface_exists(minimap_surf)) {
    show_debug_message("Surface lost at frame " + string(current_frame));
    minimap_surf = surface_create(256, 256);
}

You can correlate surface losses with game events — alt-tab, resize, etc. Pattern reveals whether it is environmental or triggered by your own code somewhere.

“Never trust a surface to exist. Check every frame. It costs one boolean op and saves you crash reports.”

Related Issues

For general GameMaker fixes, see GameMaker Alarm Not Firing. For broader graphics-resource loss patterns, Unity RenderTexture Memory Leak covers related lifetime issues in a different engine.

surface_exists every frame. Recreate on demand. Surfaces are never permanent.